一键导入
oat-project-complete
Use when all implementation work is finished and the project is ready to close. Marks the OAT project lifecycle as complete.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when all implementation work is finished and the project is ready to close. Marks the OAT project lifecycle as complete.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Run when you need to evaluate agent instruction file coverage, quality, and drift. Produces a severity-rated analysis artifact. Run before oat-agent-instructions-apply to identify what needs improvement.
Use when OAT work is mentioned in a Cursor Cloud environment. Orients agents to cloud detection, repo-rooted project homes, user-first assets, CLI availability, and Cursor dispatch context without owning lifecycle execution.
Use when an OAT skill or workflow needs provider-neutral selection, launch, recovery, or evidence for bounded subagent work without project lifecycle policy.
Use when an OAT project lifecycle skill needs to translate project state, phase or task scope, gates, and write authority into a provider-neutral subagent dispatch.
Use when the user requests or confirms documenting an active OAT project — e.g. "document the project", "update the docs", "run oat-project-document", or confirms a previously offered documentation run. Do NOT auto-invoke when implementation completes. Analyzes project artifacts, presents a documentation delta plan, and applies approved changes.
Use when plan.md is ready for execution. Dispatches one phase implementer per phase, owns independent phase review and bounded fix routing, and supports plan-declared worktree-isolated parallel phases.
| name | oat-project-complete |
| version | 1.5.4 |
| description | Use when all implementation work is finished and the project is ready to close. Marks the OAT project lifecycle as complete. |
| disable-model-invocation | true |
| user-invocable | true |
| allowed-tools | Read, Write, Bash, AskUserQuestion |
| metadata | {"internal":true} |
Mark the active OAT project lifecycle as complete.
When executing this skill, provide lightweight progress feedback so the user can tell what's happening after they confirm.
Print a phase banner once at start using horizontal separators, e.g.:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ OAT ▸ COMPLETE PROJECT ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Before multi-step work, print step indicators, e.g.:
[1/6] Resolving project + collecting user choices…[2/6] Checking completion gates…[3/6] Completing lifecycle…[4/6] Generating PR description + archiving…[5/6] Refreshing dashboard + committing…[6/6] Opening PR or syncing description…PROJECT_PATH=$(oat config get activeProject 2>/dev/null || true)
if [[ -z "$PROJECT_PATH" ]]; then
echo "Error: No active project set. Use the oat-project-open skill first." >&2
exit 1
fi
PROJECT_NAME=$(basename "$PROJECT_PATH")
PROJECTS_ROOT="${OAT_PROJECTS_ROOT:-$(oat config get projects.root 2>/dev/null || echo ".oat/projects/shared")}"
PROJECTS_ROOT="${PROJECTS_ROOT%/}"
IS_SHARED_PROJECT="false"
case "$PROJECT_PATH" in
"${PROJECTS_ROOT}/"*) IS_SHARED_PROJECT="true" ;;
esac
Ask all user questions at once so the user can answer them in a single interaction, then the rest of the skill runs without further prompts.
Host-specific structured input guidance:
AskUserQuestion when availableBefore asking the batched questions, read oat_pr_status and oat_pr_url from state.md frontmatter.
Capture pre-mutation PR state for later steps. The skill mutates state.md (Step 5) and the project tree (Step 8) before Step 11.5 needs to know whether the PR was already open at the start. Persist that decision in a shell variable now:
WAS_PR_OPEN_AT_START="false"
if [[ "${oat_pr_status:-}" == "open" ]]; then
WAS_PR_OPEN_AT_START="true"
fi
Use the same state.md read you already perform for oat_pr_status/oat_pr_url — do not re-read after Step 5. Step 11.5 (Sync Open-PR Description on GitHub) consumes this value.
Workflow preference checks (before asking questions):
Some questions can be answered automatically from workflow preferences. Read each preference before deciding whether to include its question in the batched prompt:
Both lifecycle orderings are supported:
An open PR is not a blocker. When completion archives project artifacts, the existing archive-aware flow regenerates and syncs the open PR body so its links remain valid.
ARCHIVE_PREF=$(oat config get workflow.archiveOnComplete 2>/dev/null || true)
PR_ON_COMPLETE=$(oat config get workflow.createPrOnComplete 2>/dev/null || true)
PROJECT_RECAP_CONFIG=$(oat config get workflow.explainers.projectRecap --json 2>/dev/null || true)
ARCHIVE_PREF is true: Set SHOULD_ARCHIVE="true". Skip the archive question. Print Archive on complete: enabled (from workflow.archiveOnComplete).ARCHIVE_PREF is false: Set SHOULD_ARCHIVE="false". Skip the archive question. Print Archive on complete: disabled (from workflow.archiveOnComplete).PR_ON_COMPLETE is true AND no tracked open PR exists: Set SHOULD_OPEN_PR="true". Skip the Open PR question. Print PR on complete: enabled (from workflow.createPrOnComplete).PR_ON_COMPLETE is false: Set SHOULD_OPEN_PR="false". Skip the Open PR question. Print PR on complete: disabled (from workflow.createPrOnComplete).PR_ON_COMPLETE is unset: Include the Open PR question in the batched prompt as normal (backward compatible).oat_pr_status is open, do not ask the Open PR question and do not honor PR_ON_COMPLETE=true — the PR already exists.The "Ready to mark complete?" confirmation is always asked — it is a meaningful "are you sure" moment, not a preference.
Resolve projectRecap intent before presenting the batched completion prompt.
Use the oat-explainer-kit lifecycle intent resolver in interactive mode with
the current oat_project_recap value from the same state.md read and the
source-aware workflow.explainers.projectRecap preference. Preserve the state
content hash required by the adapter's safe intent persistence contract.
When resolution returns needsPrompt: true, add exactly one project-recap question to that same batched prompt: "Generate a final project recap as part of completion?" Do not open a second prompt. Resolve the answer as generate or skip, then use the adapter's intent persistence helper with the captured state hash. Persist either generate or skip as the returned interactive record before continuing. If persistence reports a stale write, re-read state and resolve precedence again; never retry the stale record blindly. A valid persisted oat_project_recap decision prevents another prompt.
Set SHOULD_GENERATE_RECAP="true" only when the final resolved decision is
generate; otherwise set it to "false". Direct always or never workflow
preference results are effective for this run but are not copied into project
state.
Also preflight summary status using the same freshness rules as oat-project-summary:
summary.md is missing when {PROJECT_PATH}/summary.md does not existsummary.md is stale when the tracking frontmatter fields oat_summary_last_task, oat_summary_revision_count, or oat_summary_includes_revisions no longer match current_last_task, current_rev_count, or current_rev_list as defined in oat-project-summary Step 3summary.md is current when those tracking fields still match the oat-project-summary Step 3 comparison inputsQuestions to ask (in a single prompt):
IS_SHARED_PROJECT is true): "Archive the project after completion?"missing or stale): present the status explicitly:
needsPrompt: true): "Generate a final project recap as part of completion?"If oat_pr_status is open, do not ask the Open PR question. Set SHOULD_OPEN_PR="false" and treat the existing PR as already tracked.
Present all applicable questions together. Example combined prompt:
Ready to complete project **{PROJECT_NAME}**?
1. Archive the project after completion? (yes/no)
2. A summary has not been generated yet. Generate it now as part of completion? (yes/no)
3. Generate a final project recap as part of completion? (yes/no)
4. Open a PR in GitHub? (yes/no)
If the user declines the completion confirmation, exit gracefully.
After the user accepts the completion confirmation, store the answers as SHOULD_ARCHIVE, SHOULD_GENERATE_SUMMARY, SHOULD_GENERATE_RECAP, and SHOULD_OPEN_PR for use in later steps. Persist a prompted recap answer only after that confirmation is accepted.
If the summary status is current, set SHOULD_GENERATE_SUMMARY="false" and note that a current summary is already available.
If oat_pr_url is present, show it in the completion summary.
Read oat_phase_status from state.md frontmatter and handle permissively:
pr_open: Proceed normally. This is the expected entry point after oat-project-pr-final.complete: Proceed normally. Implementation is done.in_progress: Note: "Project is still in progress. Completing anyway." — proceed without additional confirmation.All three are valid starting states for completion. Do not block on any phase status value.
Run all gate checks and collect warnings. These are informational — they don't require individual user answers.
PLAN_FILE="${PROJECT_PATH}/plan.md"
if [[ -f "$PLAN_FILE" ]]; then
reviews_section=$(awk '
/^## Reviews[[:space:]]*$/ { in_reviews = 1; next }
in_reviews && /^##[[:space:]]/ { exit }
in_reviews { print }
' "$PLAN_FILE")
final_row=$(printf '%s\n' "$reviews_section" | grep -E "^\|\s*final\s*\|\s*code\s*\|" | tail -1 || true)
if [[ -z "$final_row" ]]; then
echo "Warning: No final review row found in plan.md."
elif ! echo "$final_row" | grep -qE "\|\s*passed\s*\|"; then
echo "Warning: Final code review is not marked passed."
echo "Recommendation: run the oat-project-review-provide skill with code final and oat-project-review-receive before completing."
fi
else
echo "Warning: plan.md not found, unable to verify final review status."
fi
reviews_section is strictly the ## Reviews section through the next
level-two heading. Within that ledger, final_row is the latest appended event
whose Scope is final and Type is code; earlier events remain history.
IMPL_FILE="${PROJECT_PATH}/implementation.md"
if [[ -f "$IMPL_FILE" ]]; then
medium_items=$(awk '
BEGIN { in_medium = 0 }
/^\*\*Deferred Findings \(Medium\):\*\*/ { in_medium = 1; next }
/^\*\*Deferred Findings \(Medium\/Minor\):\*\*/ { in_medium = 1; next }
in_medium && /^\*\*/ { in_medium = 0; next }
in_medium && /^[[:space:]]*-[[:space:]]+/ { print }
' "$IMPL_FILE")
has_unresolved_medium="false"
while IFS= read -r line; do
item=$(echo "$line" | sed -E 's/^[[:space:]]*-[[:space:]]+//')
if ! echo "$item" | grep -qiE '^none([[:space:]]|[[:punct:]]|$)'; then
has_unresolved_medium="true"
break
fi
done <<< "$medium_items"
if [[ "$has_unresolved_medium" == "true" ]]; then
echo "Warning: Deferred Medium findings are recorded in implementation.md."
echo "Recommendation: resurface via final review and explicitly disposition before completion."
fi
fi
DOCS_UPDATED=$(oat project status --field project.docsUpdated 2>/dev/null || echo null)
# Read policy from config (default: false = soft suggestion)
REQUIRE_DOCS=$(oat config get documentation.requireForProjectCompletion 2>/dev/null || echo "false")
if [[ "$DOCS_UPDATED" == "null" || -z "$DOCS_UPDATED" ]]; then
if [[ "$REQUIRE_DOCS" == "true" ]]; then
echo "Gate: Documentation sync required (documentation.requireForProjectCompletion is true)."
echo "Action: Run oat-project-document first, or choose to skip."
else
echo "Suggestion: Consider running oat-project-document to sync documentation before completing."
fi
fi
If oat_docs_updated is null or empty:
requireForProjectCompletion is true: Hard gate — ask user to run oat-project-document or explicitly skip. If user chooses to skip, update state.md frontmatter to set oat_docs_updated: skipped.requireForProjectCompletion is false (default): Soft suggestion — inform user about oat-project-document and allow proceeding. If user wants to skip, set oat_docs_updated: skipped.If oat_docs_updated is skipped or complete: proceed normally.
After collecting all warnings from 3.1, 3.2, and 3.3:
passed, unresolved deferred Medium findings, or documentation gate blocking), present all warnings together and ask one confirmation:
Check if {PROJECT_PATH}/summary.md exists and whether it is current against the implementation state:
summary.md is missing or stale and SHOULD_GENERATE_SUMMARY="true", generate or refresh it before completing.oat-project-summary skill when skill-to-skill invocation is available in the current host/runtime.summary.md inline by following the same synthesis rules as oat-project-summary (validate implementation state, read the same project artifacts, apply the same freshness checks, update the same frontmatter tracking fields, and write a complete summary.md before continuing).oat-project-summary is a shell command on PATH. Only execute a shell command with that name if the environment explicitly provides a real executable.summary.md is missing or stale and SHOULD_GENERATE_SUMMARY="false", emit: Warning: Proceeding without summary generation.summary.md available for PR and archive steps.summary.md already exists and is current, note it as available. Summary.md will be:
Run this gate after the optional summary refresh and before any lifecycle
mutation. Initialize SELECTED_PROJECT_RECAP_RUN="".
When SHOULD_GENERATE_RECAP="true", inspect manifests under
{PROJECT_PATH}/explainers/ before generating. A fresh project-recap manifest for the current completed implementation is reused without invoking the adapter again. Fresh means the manifest identifies recipe project-recap, belongs to this project, has a terminal outcome, and its recorded source hashes match the current approved implementation inputs, including the refreshed summary when present.
If no fresh recap exists, invoke scripts/run.mjs#runOatExplainer exactly once with recipe project-recap, project invocation, the active project, and unattended lifecycle mode so approved OAT artifacts do not trigger a second content prompt. A failed adapter run warns but does not block completion. Use a returned valid terminal project-recap manifest as the selected run; do not rerun to improve its outcome.
Supply the provider-neutral critic callback (or validated critic module entry point for JSON/CLI invocation) on every federated adapter run.
Set SELECTED_PROJECT_RECAP_RUN only to the final selected project-recap run. The value must be project-relative in the form explainers/<run-slug> so it can be passed safely to the archive CLI. An incomplete, stale, wrong-project, or project-explainer manifest is never selected as the final recap.
When recap intent resolves to skip, or generation produces no valid final recap, leave SELECTED_PROJECT_RECAP_RUN empty and complete without a recap. Record any failed recap attempt as a warning rather than changing project completion status.
project-explainer runs are active-project working artifacts, not durable post-completion reference products. Do not export, re-attest, or add archive-aware PR or summary reference links for a project-explainer run.
For IS_SHARED_PROJECT="false", never export a tracked project recap and never construct or pass --project-recap-run. A local-scope recap remains built-not-durable unless its manifest already contains independently verified publish evidence. Do not treat local filesystem presence as durability. Completion-bookkeeping durability, relocation re-attestation, and archive-aware recap links are handled by the later durability stage, not by this selection gate.
Run the project-log status probe before any lifecycle mutation or archive work:
PROJECT_LOG_CHECK=$(oat project log check --project "$PROJECT_PATH" --json)
Route on the structured result:
status: "absent": the feature is inert; proceed without a roll-up or seal
append.status: "synthesis_pending" or synthesisPending: true: emit
Warning: Project-log end-of-run synthesis is pending. Complete it with oat project log synthesize.
Offer to invoke oat project log synthesize, but do not block completion if
the synthesis remains pending. Synthesis is warn-only.summary.md. This hard gate
overrides Step 3.5's tolerance for declined, skipped, missing, or failed
summary generation: invoke oat-project-summary when available or author a
complete summary inline before continuing.For a log with entries, reuse the summary flow's structured roll-up result only
when this completion run has that exact result in memory and it reports
status: "ok". Otherwise run the idempotent enforcement surface:
PROJECT_LOG_ROLLUP=$(oat project log rollup --project "$PROJECT_PATH" --json)
Do not set lifecycle complete, seal, or archive unless the structured
ProjectLogRollupResult reports status: "ok".
ledgerOutcome: "appended" or "deduplicated" with status: "ok":
proceed.ledgerOutcome: "skipped_permitted" with status: "ok": proceed and report
the permitted skip; the absent default reference layer is not a block.status: "failed", ledgerOutcome: "failed", malformed JSON, or a command
error: stop and surface the roll-up failure. Never continue to seal or
archive.When the status probe found an existing project log, append the completion seal as the final project-log entry before any lifecycle-complete mutation:
oat project log append \
--project "$PROJECT_PATH" \
--structural \
--producer oat-project-complete \
--ref seal \
--body "Completion sealed at $(date -u +%Y-%m-%dT%H:%M:%SZ); project-log roll-up status: ok."
Only append this seal after Step 3.7 has either confirmed there are no entries
to roll up or obtained status: "ok". If the append fails for an existing log,
stop before setting lifecycle complete or archiving. No project-log append may follow the seal.
Detect any leftover active review artifacts in the top level of "$PROJECT_PATH/reviews/":
find "$PROJECT_PATH/reviews" -maxdepth 1 -type f -name "*.md" 2>/dev/null
If any active review artifacts exist:
"$PROJECT_PATH/reviews/archived" if needed.reviews/{filename}.md to reviews/archived/{filename}.md in:
"$PROJECT_PATH/plan.md""$PROJECT_PATH/implementation.md""$PROJECT_PATH/state.md"reviews/archived/, adding a timestamp suffix if needed to avoid overwriting prior history.Rules:
reviews/archived/ untouched.reviews/archived/; do not route them through the shared-project archive destination logic in Step 6.Delegate the canonical state.md completion mutation to the CLI:
COMPLETE_STATE_ARGS=("$PROJECT_PATH")
if [[ "$SHOULD_ARCHIVE" == "true" && "$IS_SHARED_PROJECT" == "true" ]]; then
COMPLETE_STATE_ARGS+=("--archived")
fi
oat project complete-state "${COMPLETE_STATE_ARGS[@]}"
The CLI command owns both the frontmatter completion fields and the canonical markdown body updates for state.md.
It must set oat_lifecycle: complete, completion timestamps, **Status:** Complete, **Last Updated:**, the canonical ## Current Phase body, normalized ## Progress, and ## Next Milestone.
Clear the active project pointer immediately. If the user is completing a project, clearing the pointer is implicit — no confirmation needed.
oat config set activeProject ""
echo "Active project pointer cleared."
PR description generation is automatic — it always runs as part of project completion. This must happen before archiving so that project artifacts are still at their tracked paths and blob links resolve correctly.
Follow the oat-project-pr-final skill's process (Steps 0.5 through 4) inline:
plan.md, implementation.md, spec.md, design.md, discovery.md) based on workflow mode from state.md.summary.md exists (from Step 3.5), use it as the primary source for the PR description's Summary section (per oat-project-pr-final Step 3.0). Read remaining artifacts and collect git context:BRANCH=$(git rev-parse --abbrev-ref HEAD)
MERGE_BASE=$(git merge-base origin/main HEAD 2>/dev/null || git merge-base main HEAD 2>/dev/null || echo "")
if [[ -n "$MERGE_BASE" ]]; then
git log --oneline "${MERGE_BASE}..HEAD"
git diff --shortstat "${MERGE_BASE}..HEAD"
fi
{PROJECT_PATH}/pr/project-pr-YYYY-MM-DD.md following the template and policies from oat-project-pr-final Step 4 (frontmatter policy, reference links policy, local path exclusion).If a PR description artifact already exists at {PROJECT_PATH}/pr/project-pr-*.md:
SHOULD_ARCHIVE is true, regenerate it (overwrite). The existing artifact was authored by oat-project-pr-final before any archive intent existed and links to artifact paths that will be local-only after Step 8. Regenerating ensures Step 11 / Step 11.5 push a body whose links still resolve on the remote.SHOULD_ARCHIVE is false, skip generation and use the existing artifact as-is. No archive means the existing blob links remain valid.Archive-aware References (required when SHOULD_ARCHIVE is true):
When archiving, the project artifacts at {PROJECT_PATH}/{plan,implementation,discovery,spec,design,summary}.md will move to a gitignored archive location in Step 8. After commit + push (Step 10), those paths no longer exist on the branch and any blob link to them returns 404 on GitHub. The PR description must anticipate this:
plan.md, implementation.md, discovery.md, spec.md, design.md, summary.md, references/imported-plan.mdreviews/ (the active project tree, including reviews/, moves with the archive)archive.summaryExportPath is configured and summary.md exists:
${SUMMARY_EXPORT_PATH}/$(date +%Y%m%d)-${PROJECT_NAME}.md (matches archive-utils.ts naming).- Project record: [${SUMMARY_EXPORT_PATH}/${YYYYMMDD}-${PROJECT_NAME}.md]({REPO_WEB}/blob/{BRANCH}/${SUMMARY_EXPORT_PATH}/${YYYYMMDD}-${PROJECT_NAME}.md){BRANCH} value used by oat-project-pr-final Step 4 for every other reference). Step 8 creates the export on the current checkout and Step 10 commits + pushes it on the feature branch, so the link resolves immediately while the PR is open and continues to resolve after merge once the file lands on the base branch.main / resolved default branch). The export does not exist on the base branch until the PR merges, so a blob/main/... link 404s for the entire window the PR is open — the same class of broken link this whole step exists to prevent.archive.summaryExportPath is unset or summary.md is missing, omit this bullet rather than emit a broken link..oat/repo/pjm/backlog/, decision record links under .oat/repo/reference/decisions/, repo-reference docs, ticket URLs, and anything else under tracked paths outside the project directory.localPaths-based exclusion rule from oat-project-pr-final Step 4 on top of these rules — it already covers .oat/**/pr and .oat/**/reviews/archived and may catch additional patterns configured per repo.project-explainer run. Only the selected final project-recap can enter the tracked completion export path.projectRecapExport.exportRoot. Do not predict that path from
the date or project name.Anti-pattern: do not "rescue" a dropped artifact by linking to its archived path under .oat/projects/archived/<name>/.... That path is gitignored on every checkout and never reaches the remote.
Skip if SHOULD_ARCHIVE is false or IS_SHARED_PROJECT is false.
This conditional skips archive movement only; it does not skip the Step 3.7 seal append for an existing project log.
Archive happens after PR description generation (so artifacts are readable at tracked paths) but before commit+push (so the archive deletion is included in the commit).
The archive-side effects in this step are CLI-owned. Do not reimplement local archive movement, summary export, S3 sync, AWS credential handling, or worktree durability checks in the skill.
ARCHIVE_OUTPUT=""
ARCHIVE_ARGS=("$PROJECT_PATH")
if [[ -n "$SELECTED_PROJECT_RECAP_RUN" ]]; then
ARCHIVE_ARGS+=("--project-recap-run" "$SELECTED_PROJECT_RECAP_RUN")
fi
if ! ARCHIVE_OUTPUT=$(oat project archive "${ARCHIVE_ARGS[@]}" --json 2>&1); then
printf '%s\n' "$ARCHIVE_OUTPUT" >&2
echo "Error: Project archive failed." >&2
exit 1
fi
printf '%s\n' "$ARCHIVE_OUTPUT"
Parse ARCHIVE_OUTPUT as the oat project archive --json report. Require
status: "ok", mode: "apply", and a non-empty archivePath; use its
s3Path, summaryExportFile, and warnings fields for later reporting. Set
ARCHIVE_PATH from archivePath, then set PROJECT_PATH="$ARCHIVE_PATH".
When SELECTED_PROJECT_RECAP_RUN is non-empty, also require the report's
projectRecapExport.sourceRunRoot, projectRecapExport.exportRoot, and
projectRecapExport.manifest.relativePath === "manifest.json". Confirm the
reported source is the selected run under the pre-archive project path and the
export root is inside the tracked
.oat/repo/reference/project-recaps/ root. Record:
sourceRunRoot as the relocation source;exportRoot as the final recap run root; andexportRoot/manifest.relativePath as the final manifest.Do not infer or reconstruct the recap export root. The archive report is authoritative. A missing, malformed, mismatched, outside-root, or gitignored export report is an archive failure; stop before lifecycle bookkeeping. Never use the gitignored archive as evidence or a link target.
SELECTED_PROJECT_RECAP_RUN must be project-relative. Never add --project-recap-run when SELECTED_PROJECT_RECAP_RUN is empty. The empty case remains the existing archive behavior. Because this step runs only for shared projects, local-scope projects never pass a recap archive argument.
The no-recap invocation remains oat project archive "$PROJECT_PATH" with
--json added only to select the machine-readable report.
Use ARCHIVE_S3_CONTEXT in Step 12 if the command reports profile/region details.
Run this only when archive returned a projectRecapExport.
Rewrite recap links in the tracked summary export and the PR description body from projectRecapExport.exportRoot; do not derive them from the local archive.
Use a repository-relative path under
.oat/repo/reference/project-recaps/ and a blob URL on the current head branch
while the PR is open. If summaryExportFile is non-null, update its concise
Explainer Outcome recap link. Update the archived PR-description artifact
used by Step 11 or 11.5 so its recap reference points to the same tracked root.
Omit either link when its containing artifact does not exist.
Use the current head branch for the blob URL while the PR is open. Never link to .oat/projects/archived/; it is gitignored and will return 404 remotely.
Regenerate the repo state dashboard so the completion status is reflected before committing.
oat state refresh
Completion is not done until lifecycle changes are committed. This commit also anchors commit durability for a selected shared-project recap. Do not push yet when recap attestation is pending.
Expected changes may include:
{PROJECT_PATH}/state.md{PROJECT_PATH}/implementation.md (if touched earlier in the lifecycle closeout){PROJECT_PATH}/plan.md (if review receive just ran){PROJECT_PATH}/pr/project-pr-*.md (PR description artifact).oat/state.md is regenerated locally in Step 9 but should not be staged; it is generated dashboard state and normally gitignored..oat/config.local.json (if activeProject cleared){PROJECTS_ROOT}/{PROJECT_NAME} (if archived)Run:
git status --short
git add -- <exact completion and lifecycle paths>
git commit -m "chore(oat): complete project lifecycle for ${PROJECT_NAME}"
LIFECYCLE_COMMIT=$(git rev-parse HEAD)
Rules:
git add -A when
unrelated changes exist.Skip when no final recap was selected, for local-scope projects, or when the selected recap is already durable solely through independently verified publish evidence.
For an archived recap, consume the exact projectRecapExport values recorded
in Step 8. Plan finalization through
oat-explainer-kit/scripts/finalize-tracked-run.mjs#planTrackedRunFinalization
with:
runRoot: projectRecapExport.exportRoot;manifestPath:
projectRecapExport.exportRoot/projectRecapExport.manifest.relativePath;completion-bookkeeping;sourceRunRoot; andartifactCommit: the full LIFECYCLE_COMMIT SHA.For a shared project that was not archived, use the selected active run and
omit relocatedFrom, but keep the same completion-bookkeeping mode.
The lifecycle bookkeeping commit is the artifact commit. Call the compatible
core's recordDurability(...) with the finalizer's planned request. Submit only immutable paths under projectRecapExport.exportRoot as commit evidence for an archived recap; manifest.json and build-record.json are mutable records and
must not appear in that evidence path list. The successful exported-path
attestation supersedes the prior active-path evidence. Verify the resulting
manifest records the old evidence in supersedes and reports the final
tracked export path.
Never submit the gitignored archive path as commit evidence. Local archive presence cannot make a recap durable.
A failed exported recap attestation does not fail project completion. Preserve
the tracked export, report built-not-durable, retain actionable recovery
details, and continue to the evidence commit.
When Step 10.5 ran, create the evidence update. Commit only the exported manifest.json and build-record.json as the evidence update, including warning-bearing records from a failed attestation. On failure, commit the warning-bearing manifest.json and build-record.json. Run
verifyTrackedRunFinalization(...) with the artifact commit, immediate evidence
commit parent/order, exact evidence paths, attestation outcome, and unchanged
unrelated-change snapshots.
Archive completion is exactly two commits when recap attestation runs:
Push once after both commits exist so they travel together. If no attestation ran, push the lifecycle bookkeeping commit once. If verification detects contamination or wrong commit order, do not push. If push fails, report the failure and do not claim completion is fully recorded.
Skip if SHOULD_OPEN_PR is false.
CRITICAL — Strip YAML frontmatter before submitting to GitHub.
The local artifact file contains YAML frontmatter (--- delimited block at the top) for OAT metadata. This frontmatter MUST NOT appear in the GitHub PR body. Before passing the file to gh pr create, strip everything from the start of the file through and including the closing --- line. Verify the resulting body starts with the markdown heading (e.g., # feat: ...), not YAML keys.
Steps:
{PROJECT_PATH}/pr/project-pr-*.md.--- through the closing ---, inclusive).gh pr create --base main --title "{title}" --body-file "$TMP_BODY"
Do not assume gh is installed; if missing, instruct manual PR creation using the file contents.
Run only when WAS_PR_OPEN_AT_START="true" AND SHOULD_ARCHIVE="true".
When the PR was already open at the start of this skill (typically because oat-project-pr-final ran earlier in the lifecycle) AND we just archived, the GitHub PR description authored by oat-project-pr-final still points to the active artifact paths. Step 8 moved those artifacts to a gitignored archive location and Step 10.6 pushed the move, so any blob link in the open PR body now 404s. Push the regenerated archive-aware body to the existing PR.
Skip this step when:
WAS_PR_OPEN_AT_START="false") — Step 11 already created the PR with the archive-aware body.SHOULD_ARCHIVE="false") — the original blob links still resolve.IS_SHARED_PROJECT="false" — non-shared projects are not archived in this skill, so no link breakage.Steps:
Locate the PR description artifact at {PROJECT_PATH}/pr/project-pr-*.md. After Step 8, PROJECT_PATH points at the archived location, so the artifact lives at {ARCHIVE_PATH}/pr/project-pr-*.md.
Strip YAML frontmatter (everything from the opening --- through and including the closing ---) and write the result to a temporary file. Verify the temp file does not start with YAML frontmatter keys.
Resolve the open PR. Prefer the tracked URL captured in Step 2:
PR_REF="${oat_pr_url:-}"
if [[ -z "$PR_REF" ]]; then
# Fall back to the head branch — gh auto-resolves to the open PR for the current branch.
PR_REF=$(git rev-parse --abbrev-ref HEAD)
fi
Push the updated body:
gh pr edit "$PR_REF" --body-file "$TMP_BODY"
Clean up the temp file.
Failure handling:
gh is missing, warn and print the path to the regenerated artifact body so the user can paste it into the PR manually. Do not fail the skill.gh pr edit fails (e.g. PR was merged between Step 2 and now, or the auth token lacks edit permission), warn and continue. Step 12's completion summary should call out that the PR body was not updated and surface the artifact path so the user can update it manually.Show user:
ARCHIVE_S3_CONTEXT when the archive command reported profile/region details. If only ARCHIVE_S3_PATH is available, include the S3 destination and note that profile/region context was not reported by the command. Never echo raw credentials (AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, etc.).built-not-durable, not a project-completion
failure.oat_pr_url is present, show it in the completion summary even when PR creation was skipped because the project already tracked an open PR.PR description synced: <PR URL>) or warn that the sync failed and surface the artifact path so the user can update it manually.