@open-agent-toolkit/cli 0.3.15 → 0.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/assets/NOTICES.md +48 -0
  2. package/assets/agents/oat-phase-implementer.md +26 -6
  3. package/assets/agents/oat-reviewer.md +51 -1
  4. package/assets/bundle-metadata.json +1 -1
  5. package/assets/docs/contributing/documentation.md +4 -0
  6. package/assets/docs/contributing/skills.md +3 -1
  7. package/assets/docs/getting-started/bootstrap.md +1 -1
  8. package/assets/docs/getting-started/index.md +0 -4
  9. package/assets/docs/getting-started/tool-packs.md +0 -7
  10. package/assets/docs/provider-sync/commands.md +21 -0
  11. package/assets/docs/reference/cli-reference.md +44 -2
  12. package/assets/docs/reference/config-and-local-state.md +11 -2
  13. package/assets/docs/reference/configuration.md +10 -2
  14. package/assets/docs/reference/project-artifacts.md +43 -8
  15. package/assets/docs/skills/index.md +1 -0
  16. package/assets/docs/skills/recon.md +8 -5
  17. package/assets/docs/skills/repo-improve.md +9 -7
  18. package/assets/docs/workflows/advanced/autonomy.md +50 -0
  19. package/assets/docs/workflows/advanced/workflow-gates.md +109 -10
  20. package/assets/docs/workflows/approvals-and-automation.md +3 -1
  21. package/assets/docs/workflows/backlog-and-planning/backlog-lifecycle.md +25 -24
  22. package/assets/docs/workflows/backlog-and-planning/remote-project-management.md +0 -7
  23. package/assets/docs/workflows/ideas/lifecycle.md +2 -7
  24. package/assets/docs/workflows/projects/closeout/closeout-skills.md +62 -1
  25. package/assets/docs/workflows/projects/execution/implementation-execution.md +23 -3
  26. package/assets/docs/workflows/projects/execution/project-log.md +6 -0
  27. package/assets/docs/workflows/projects/lifecycle.md +7 -0
  28. package/assets/docs/workflows/projects/reviews/index.md +63 -1
  29. package/assets/docs/workflows/projects/reviews/review-flavors.md +8 -0
  30. package/assets/docs/workflows/waves/wave-workflows.md +11 -3
  31. package/assets/public-package-versions.json +4 -4
  32. package/assets/skills/oat-agent-instructions-apply/SKILL.md +14 -4
  33. package/assets/skills/oat-brainstorm/SKILL.md +17 -13
  34. package/assets/skills/oat-docs-apply/SKILL.md +13 -3
  35. package/assets/skills/oat-doctor/SKILL.md +11 -1
  36. package/assets/skills/oat-explainer-kit/SKILL.md +1 -1
  37. package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +6 -3
  38. package/assets/skills/oat-pjm-review-backlog/SKILL.md +2 -2
  39. package/assets/skills/oat-pjm-update-repo-reference/SKILL.md +28 -3
  40. package/assets/skills/oat-project-autonomous/SKILL.md +60 -5
  41. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +210 -165
  42. package/assets/skills/oat-project-capture/SKILL.md +7 -3
  43. package/assets/skills/oat-project-complete/SKILL.md +39 -34
  44. package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +106 -28
  45. package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +30 -3
  46. package/assets/skills/oat-project-complete-auto/SKILL.md +388 -0
  47. package/assets/skills/oat-project-design/SKILL.md +12 -9
  48. package/assets/skills/oat-project-discover/SKILL.md +8 -4
  49. package/assets/skills/oat-project-document/SKILL.md +14 -5
  50. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +210 -165
  51. package/assets/skills/oat-project-implement/SKILL.md +6 -1
  52. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +34 -7
  53. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +210 -165
  54. package/assets/skills/oat-project-implement/references/docs/complexity-review-fallback.md +262 -0
  55. package/assets/skills/oat-project-implement/references/docs/gate-approval-record.md +80 -0
  56. package/assets/skills/oat-project-implement/references/phase-execution.md +38 -6
  57. package/assets/skills/oat-project-import-plan/SKILL.md +20 -6
  58. package/assets/skills/oat-project-lite/SKILL.md +7 -5
  59. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +210 -165
  60. package/assets/skills/oat-project-new/SKILL.md +6 -2
  61. package/assets/skills/oat-project-next/SKILL.md +19 -2
  62. package/assets/skills/oat-project-next/references/docs/gate-approval-record.md +80 -0
  63. package/assets/skills/oat-project-plan/SKILL.md +14 -6
  64. package/assets/skills/oat-project-pr-final/SKILL.md +4 -3
  65. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +210 -165
  66. package/assets/skills/oat-project-progress/SKILL.md +27 -1
  67. package/assets/skills/oat-project-progress/references/docs/gate-approval-record.md +80 -0
  68. package/assets/skills/oat-project-promote-spec-driven/SKILL.md +7 -3
  69. package/assets/skills/oat-project-quick-start/SKILL.md +47 -14
  70. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +210 -165
  71. package/assets/skills/oat-project-quick-start/references/docs/complexity-review-fallback.md +262 -0
  72. package/assets/skills/oat-project-quick-start/references/docs/gate-approval-record.md +80 -0
  73. package/assets/skills/oat-project-reconcile/SKILL.md +8 -4
  74. package/assets/skills/oat-project-retro/SKILL.md +1 -1
  75. package/assets/skills/oat-project-retro/references/apply-procedure.md +6 -6
  76. package/assets/skills/oat-project-retro-file/SKILL.md +7 -3
  77. package/assets/skills/oat-project-review-provide/SKILL.md +23 -4
  78. package/assets/skills/oat-project-review-provide-remote/SKILL.md +19 -1
  79. package/assets/skills/oat-project-review-receive/SKILL.md +57 -14
  80. package/assets/skills/oat-project-review-receive/references/docs/complexity-review-fallback.md +262 -0
  81. package/assets/skills/oat-project-review-receive-remote/SKILL.md +7 -3
  82. package/assets/skills/oat-project-revise/SKILL.md +13 -5
  83. package/assets/skills/oat-project-spec/SKILL.md +8 -4
  84. package/assets/skills/oat-project-summary/SKILL.md +11 -7
  85. package/assets/skills/oat-repo-knowledge-index/SKILL.md +54 -20
  86. package/assets/skills/oat-repo-knowledge-index/scripts/refresh-owned.mjs +118 -0
  87. package/assets/skills/oat-review-provide/SKILL.md +7 -3
  88. package/assets/skills/oat-review-receive-remote/SKILL.md +14 -4
  89. package/assets/skills/oat-wave-execute/SKILL.md +72 -38
  90. package/assets/skills/oat-wave-execute/assets/wrapper-plan-template.md +16 -2
  91. package/assets/skills/oat-wave-program/SKILL.md +15 -6
  92. package/assets/skills/oat-worktree-bootstrap-auto/SKILL.md +45 -17
  93. package/assets/skills/oat-wrap-up/SKILL.md +9 -3
  94. package/assets/skills/oat-wrap-up/references/report-template.md +1 -1
  95. package/assets/skills/recon/SKILL.md +1 -1
  96. package/assets/skills/recon/references/packet-contract.md +18 -3
  97. package/assets/skills/recon/scripts/lib/contracts.mjs +16 -2
  98. package/assets/skills/recon/scripts/reconcile-ledger.mjs +20 -10
  99. package/assets/skills/recon/scripts/render-packet.mjs +31 -3
  100. package/assets/skills/recon/scripts/validate-packet.mjs +5 -5
  101. package/assets/templates/pjm-agents.md +38 -8
  102. package/assets/templates/repo-agents.md +5 -0
  103. package/assets/templates/repo-readme.md +5 -0
  104. package/assets/templates/state.md +7 -0
  105. package/dist/commands/backlog/archive.d.ts +3 -0
  106. package/dist/commands/backlog/archive.d.ts.map +1 -1
  107. package/dist/commands/backlog/archive.js +63 -26
  108. package/dist/commands/backlog/index.d.ts.map +1 -1
  109. package/dist/commands/backlog/index.js +3 -0
  110. package/dist/commands/backlog/rewrite-references.d.ts +5 -0
  111. package/dist/commands/backlog/rewrite-references.d.ts.map +1 -1
  112. package/dist/commands/backlog/rewrite-references.js +39 -1
  113. package/dist/commands/config/index.d.ts.map +1 -1
  114. package/dist/commands/config/index.js +13 -0
  115. package/dist/commands/gate/index.d.ts +5 -0
  116. package/dist/commands/gate/index.d.ts.map +1 -1
  117. package/dist/commands/gate/index.js +270 -9
  118. package/dist/commands/init/index.d.ts.map +1 -1
  119. package/dist/commands/init/index.js +1 -0
  120. package/dist/commands/internal/commit-paths.d.ts +3 -0
  121. package/dist/commands/internal/commit-paths.d.ts.map +1 -0
  122. package/dist/commands/internal/commit-paths.js +31 -0
  123. package/dist/commands/internal/index.d.ts.map +1 -1
  124. package/dist/commands/internal/index.js +2 -0
  125. package/dist/commands/pjm/init.d.ts.map +1 -1
  126. package/dist/commands/pjm/init.js +37 -8
  127. package/dist/commands/project/archive/archive-utils.d.ts +6 -3
  128. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  129. package/dist/commands/project/archive/archive-utils.js +510 -104
  130. package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
  131. package/dist/commands/project/archive/push-runner.js +1 -0
  132. package/dist/commands/project/log/append.d.ts +2 -3
  133. package/dist/commands/project/log/append.d.ts.map +1 -1
  134. package/dist/commands/project/log/append.js +55 -101
  135. package/dist/commands/project/migrate/index.d.ts +1 -2
  136. package/dist/commands/project/migrate/index.d.ts.map +1 -1
  137. package/dist/commands/project/migrate/index.js +8 -2
  138. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  139. package/dist/commands/project/new/scaffold.js +4 -4
  140. package/dist/commands/project/promote/promote.d.ts +8 -0
  141. package/dist/commands/project/promote/promote.d.ts.map +1 -1
  142. package/dist/commands/project/promote/promote.js +37 -6
  143. package/dist/commands/project/status.d.ts.map +1 -1
  144. package/dist/commands/project/status.js +3 -0
  145. package/dist/commands/project/sync/ref-sync.d.ts +12 -1
  146. package/dist/commands/project/sync/ref-sync.d.ts.map +1 -1
  147. package/dist/commands/project/sync/ref-sync.js +347 -23
  148. package/dist/commands/shared/exact-path-commit.d.ts +32 -0
  149. package/dist/commands/shared/exact-path-commit.d.ts.map +1 -0
  150. package/dist/commands/shared/exact-path-commit.js +691 -0
  151. package/dist/commands/shared/frontmatter.d.ts +1 -1
  152. package/dist/commands/shared/frontmatter.d.ts.map +1 -1
  153. package/dist/commands/shared/frontmatter.js +1 -0
  154. package/dist/commands/state/generate.d.ts +1 -1
  155. package/dist/commands/state/generate.d.ts.map +1 -1
  156. package/dist/commands/state/generate.js +87 -8
  157. package/dist/commands/sync/apply.d.ts.map +1 -1
  158. package/dist/commands/sync/apply.js +31 -5
  159. package/dist/commands/sync/dry-run.d.ts.map +1 -1
  160. package/dist/commands/sync/dry-run.js +28 -11
  161. package/dist/commands/sync/sync.utils.d.ts +13 -0
  162. package/dist/commands/sync/sync.utils.d.ts.map +1 -1
  163. package/dist/commands/sync/sync.utils.js +21 -1
  164. package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
  165. package/dist/commands/tools/shared/pack-manifest.js +1 -0
  166. package/dist/config/oat-config.d.ts +1 -0
  167. package/dist/config/oat-config.d.ts.map +1 -1
  168. package/dist/config/oat-config.js +3 -0
  169. package/dist/config/resolve.d.ts.map +1 -1
  170. package/dist/config/resolve.js +1 -0
  171. package/dist/drift/detector.js +6 -6
  172. package/dist/engine/compute-plan.d.ts.map +1 -1
  173. package/dist/engine/compute-plan.js +87 -2
  174. package/dist/engine/engine.types.d.ts +10 -1
  175. package/dist/engine/engine.types.d.ts.map +1 -1
  176. package/dist/engine/engine.types.js +4 -0
  177. package/dist/engine/execute-plan.d.ts.map +1 -1
  178. package/dist/engine/execute-plan.js +42 -5
  179. package/dist/engine/managed-copy-hash.d.ts +4 -3
  180. package/dist/engine/managed-copy-hash.d.ts.map +1 -1
  181. package/dist/engine/managed-copy-hash.js +4 -3
  182. package/dist/fs/assets.d.ts.map +1 -1
  183. package/dist/fs/assets.js +7 -1
  184. package/dist/manifest/hash.d.ts +8 -10
  185. package/dist/manifest/hash.d.ts.map +1 -1
  186. package/dist/ui/output.js +1 -1
  187. package/dist/validation/skills.d.ts.map +1 -1
  188. package/dist/validation/skills.js +151 -11
  189. package/package.json +3 -2
@@ -5,13 +5,17 @@ disable-model-invocation: true
5
5
  user-invocable: true
6
6
  allowed-tools: Read, Write, Bash, Glob, Grep, AskUserQuestion
7
7
  metadata:
8
- version: 1.0.3
8
+ version: 1.0.4
9
9
  ---
10
10
 
11
11
  # Capture Untracked Work
12
12
 
13
13
  Retroactively create an OAT project from work that happened outside the structured workflow. Populates `discovery.md` from conversation context and `implementation.md` from commit history, producing a tracked project ready for review or PR.
14
14
 
15
+ ## Hook-safe exact-path commits
16
+
17
+ Before a parent-branch commit, verify `oat internal commit-paths --help` succeeds. If unavailable, stop and update the OAT CLI; never fall back to a staged-index or pathspec-only commit. Set `COMMIT_IDENTITY` to a unique operation/artifact identity before the first attempt and retain it for every retry; use a new identity for a new operation. Pass only the exact produced file list, including tracked removals and both names of a rename. A blocked/failed result stops the workflow; retain its receipt and follow its diagnostics. Hooks remain enabled. Synced project artifacts continue through `oat project push`.
18
+
15
19
  ## When to Use
16
20
 
17
21
  Use when:
@@ -268,8 +272,8 @@ PROJECT_SCOPE=$(oat project scope "$PROJECT_PATH" --format value) || {
268
272
  if [ "$PROJECT_SCOPE" = "synced" ]; then
269
273
  oat project push "$PROJECT_PATH" --message "chore(oat): capture existing work for {name}" || { echo "oat: project push failed; run oat project pull, resolve the reported state, and retry" >&2; exit 1; }
270
274
  else
271
- git add "$PROJECT_PATH/discovery.md" "$PROJECT_PATH/implementation.md" "$PROJECT_PATH/state.md"
272
- git diff --cached --quiet || git commit -m "chore(oat): capture existing work for {name}"
275
+ OWNED_COMMIT_PATHS=("$PROJECT_PATH/discovery.md" "$PROJECT_PATH/implementation.md" "$PROJECT_PATH/state.md")
276
+ oat internal commit-paths --identity "${COMMIT_IDENTITY:?set once and retain for retries}:oat-project-capture:1" --message "chore(oat): capture existing work for {name}" -- "${OWNED_COMMIT_PATHS[@]}" || exit 1
273
277
  fi
274
278
  ```
275
279
 
@@ -5,13 +5,17 @@ disable-model-invocation: true
5
5
  user-invocable: true
6
6
  allowed-tools: Read, Write, Bash, AskUserQuestion
7
7
  metadata:
8
- version: 1.7.14
8
+ version: 1.7.15
9
9
  ---
10
10
 
11
11
  # Complete Project
12
12
 
13
13
  Mark the active OAT project lifecycle as complete.
14
14
 
15
+ ## Hook-safe exact-path commits
16
+
17
+ Before a parent-branch commit, verify `oat internal commit-paths --help` succeeds. If unavailable, stop and update the OAT CLI; never fall back to a staged-index or pathspec-only commit. Set `COMMIT_IDENTITY` to a unique operation/artifact identity before the first attempt and retain it for every retry; use a new identity for a new operation. Pass only the exact produced file list, including tracked removals and both names of a rename. A blocked/failed result stops the workflow; retain its receipt and follow its diagnostics. Hooks remain enabled. Synced project artifacts continue through `oat project push`.
18
+
15
19
  ## Progress Indicators (User-Facing)
16
20
 
17
21
  When executing this skill, provide lightweight progress feedback so the user can tell what's happening after they confirm.
@@ -1237,21 +1241,23 @@ another lifecycle commit. `removed` is the completed-only terminal shape;
1237
1241
  `retained` is the equally terminal same-SHA active alias shape. A differing-SHA
1238
1242
  state is never a successful report.
1239
1243
 
1240
- When `SELECTED_PROJECT_RECAP_RUN` is non-empty, also require the report's
1241
- `projectRecapExport.sourceRunRoot`, `projectRecapExport.exportRoot`, and
1242
- `projectRecapExport.manifest.relativePath === "manifest.json"`. Confirm the
1243
- reported source is the selected run under the pre-archive project path and the
1244
- export root is inside the tracked
1245
- `.oat/repo/reference/project-recaps/` root. Record:
1246
-
1247
- - `sourceRunRoot` as the relocation source;
1248
- - `exportRoot` as the final recap run root; and
1249
- - `exportRoot/manifest.relativePath` as the final manifest.
1250
-
1251
- Do not infer or reconstruct the recap export root. The archive report is
1252
- authoritative. A missing, malformed, mismatched, outside-root, or gitignored
1253
- export report is an archive failure; stop before lifecycle bookkeeping.
1254
- Never use the gitignored archive as evidence or a link target.
1244
+ When `SELECTED_PROJECT_RECAP_RUN` is non-empty, require the report's
1245
+ `projectRecapExport.sourceRunRoot`, `exportRoot`, `runId`,
1246
+ `page.sourceRelativePath`, `page.originalSha256`, `page.exportedSha256`, and
1247
+ positive `verifiedArtifactCount`. The export is one tracked
1248
+ `.oat/repo/reference/project-recaps/<snapshotId>.html` page. Confirm the source
1249
+ is the selected run under the pre-archive project path, or under `archivePath`
1250
+ on a terminal retry. Verify the run ID and manifest-declared original page/hash
1251
+ against the retained archived run, and the exported hash against the tracked
1252
+ page. Record `exportRoot` as the final page, never as a package directory.
1253
+
1254
+ The archive report is authoritative. A missing, malformed, mismatched,
1255
+ outside-root, or gitignored export report is an archive failure; stop before
1256
+ lifecycle bookkeeping. Link only to the tracked page. The full original run,
1257
+ including source, manifest, and QA evidence, remains in the archived project;
1258
+ it is retained evidence rather than a remote link target. Persisted older
1259
+ receipts with `manifest.relativePath === "manifest.json"` remain readable as
1260
+ legacy package receipts; new exports never create a tracked manifest sidecar.
1255
1261
 
1256
1262
  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 durable projects, local-scope projects never pass a recap archive argument.
1257
1263
 
@@ -1286,12 +1292,12 @@ final synced links,
1286
1292
  dashboard refresh, the required bookkeeping push, tracked-PR closeout when
1287
1293
  applicable, and final confirmation.
1288
1294
 
1289
- Rewrite recap links in the tracked summary export and the PR description body from `projectRecapExport.exportRoot`; do not derive them from the local archive.
1295
+ Rewrite recap links in the tracked summary export and the PR description body from the reported HTML `projectRecapExport.exportRoot`; do not derive them from the local archive. Preserve the reported run ID and original/exported page hashes in the summary outcome. On resume use `EXPORTED_PAGE_PATH`; `EXPORTED_MANIFEST_PATH` is populated only for a persisted legacy package receipt.
1290
1296
  Use a repository-relative path under
1291
1297
  `.oat/repo/reference/project-recaps/` and a blob URL on the current head branch
1292
1298
  while the PR is open. If `summaryExportFile` is non-null, update its concise
1293
1299
  `Explainer Outcome` recap link. Update the archived PR-description artifact
1294
- used by Step 11 or 11.5 so its recap reference points to the same tracked root.
1300
+ used by Step 11 or 11.5 so its recap reference points to the same tracked HTML page.
1295
1301
  Omit either link when its containing artifact does not exist.
1296
1302
 
1297
1303
  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.
@@ -1423,9 +1429,16 @@ Expected changes may include:
1423
1429
  - `.oat/config.local.json` (if `activeProject` cleared)
1424
1430
  - Shared-project deletions; synced archive record deletion is already sealed by
1425
1431
  the archive-owned lifecycle commit
1426
- - The complete tracked recap export and tracked summary export reported by
1432
+ - The tracked recap page and tracked summary export reported by
1427
1433
  archive (if present)
1428
1434
 
1435
+ Initialize `COMPLETION_OUTPUT_PATHS=()` before closeout writes. Each producer
1436
+ appends its exact successful output paths from the expected changes above,
1437
+ including tracked removals and both names of a rename; omit ignored local
1438
+ config/dashboard outputs. Retain that list for retries. Do not infer ownership
1439
+ from the staged index or pass project/export directories. The synced record
1440
+ producer already supplies the single exact `SYNCED_RECORD_PATH`.
1441
+
1429
1442
  Run:
1430
1443
 
1431
1444
  ```bash
@@ -1433,23 +1446,15 @@ if [[ "$PROJECT_SCOPE" == "synced" ]]; then
1433
1446
  if [[ "$SHOULD_ARCHIVE" == "true" ]]; then
1434
1447
  test -n "$LIFECYCLE_COMMIT"
1435
1448
  else
1436
- git add -- "$SYNCED_RECORD_PATH"
1437
- if git diff --cached --quiet -- "$SYNCED_RECORD_PATH"; then
1438
- LIFECYCLE_COMMIT=$(git log -1 --format=%H -- "$SYNCED_RECORD_PATH")
1439
- node "$NONARCHIVE_LIFECYCLE_RECEIPT_SCRIPT" \
1440
- "$SYNCED_RECORD_PATH" "$LIFECYCLE_COMMIT" "$PROJECT_NAME" || exit 1
1441
- else
1442
- git commit --only "$SYNCED_RECORD_PATH" \
1443
- -m "chore(oat): complete synced project ${PROJECT_NAME}" &&
1444
- LIFECYCLE_COMMIT=$(git rev-parse HEAD) &&
1445
- node "$NONARCHIVE_LIFECYCLE_RECEIPT_SCRIPT" \
1446
- "$SYNCED_RECORD_PATH" "$LIFECYCLE_COMMIT" "$PROJECT_NAME" || exit 1
1447
- fi
1449
+ oat internal commit-paths --identity "${COMMIT_IDENTITY:?set once and retain for retries}:oat-project-complete:1" --message "chore(oat): complete synced project ${PROJECT_NAME}" -- "$SYNCED_RECORD_PATH" || exit 1
1450
+ LIFECYCLE_COMMIT=$(git log -1 --format=%H -- "$SYNCED_RECORD_PATH") || exit 1
1451
+ node "$NONARCHIVE_LIFECYCLE_RECEIPT_SCRIPT" \
1452
+ "$SYNCED_RECORD_PATH" "$LIFECYCLE_COMMIT" "$PROJECT_NAME" || exit 1
1448
1453
  fi
1449
1454
  else
1450
1455
  git status --short
1451
- git add -- <exact completion and lifecycle paths>
1452
- git commit -m "chore(oat): complete project lifecycle for ${PROJECT_NAME}"
1456
+ : "${COMPLETION_OUTPUT_PATHS:?retain the exact lifecycle producer file list}"
1457
+ oat internal commit-paths --identity "${COMMIT_IDENTITY:?set once and retain for retries}:oat-project-complete:2" --message "chore(oat): complete project lifecycle for ${PROJECT_NAME}" -- "${COMPLETION_OUTPUT_PATHS[@]}" || exit 1
1453
1458
  LIFECYCLE_COMMIT=$(git rev-parse HEAD)
1454
1459
  fi
1455
1460
  ```
@@ -1623,7 +1628,7 @@ Show user:
1623
1628
  - If archived: "Archived location: **{PROJECT_PATH}**"
1624
1629
  - If S3 archive sync ran: include `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.).
1625
1630
  - Include the lifecycle bookkeeping commit hash and the single push result.
1626
- - Report the final recap outcome and tracked reference root.
1631
+ - Report the final recap outcome and tracked recap page path.
1627
1632
  - Report every absorbed-project retirement finding from the Step 3.7 sweep with
1628
1633
  its disposition. This is the required destination whenever the sweep could not
1629
1634
  append them to the project log — an absent log, or a resume whose log is
@@ -1,7 +1,17 @@
1
1
  import { execFile as execFileCallback } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
2
3
  import { realpathSync } from 'node:fs';
3
4
  import { readFile } from 'node:fs/promises';
4
- import { isAbsolute, posix, relative, sep, resolve } from 'node:path';
5
+ import {
6
+ isAbsolute,
7
+ posix,
8
+ relative,
9
+ sep,
10
+ resolve,
11
+ join,
12
+ dirname,
13
+ basename,
14
+ } from 'node:path';
5
15
  import { fileURLToPath } from 'node:url';
6
16
  import { promisify } from 'node:util';
7
17
 
@@ -23,45 +33,112 @@ async function buildPostArchiveContinuation(
23
33
  ) {
24
34
  let selectedProjectRecapRun = '';
25
35
  let exportedManifestPath = '';
26
- if (archiveReport.projectRecapExport != null) {
27
- const sourceRunRoot = archiveReport.projectRecapExport?.sourceRunRoot;
28
- const exportRoot = archiveReport.projectRecapExport?.exportRoot;
29
- const manifestPath =
30
- archiveReport.projectRecapExport?.manifest?.relativePath;
31
- if (
32
- typeof sourceRunRoot !== 'string' ||
33
- typeof exportRoot !== 'string' ||
34
- manifestPath !== 'manifest.json'
35
- ) {
36
- throw executionError(
37
- 'Archive resume report has an invalid project recap export receipt.',
38
- );
36
+ let exportedPagePath = '';
37
+ const canonicalPath = (value) => {
38
+ try {
39
+ return realpathSync(value);
40
+ } catch (error) {
41
+ if (error.code !== 'ENOENT') throw error;
42
+ const parent = dirname(value);
43
+ if (parent === value) throw error;
44
+ return join(canonicalPath(parent), basename(value));
39
45
  }
40
- selectedProjectRecapRun = relative(projectPath, sourceRunRoot)
41
- .split(sep)
42
- .join('/');
46
+ };
47
+ const recap = archiveReport.projectRecapExport;
48
+ if (recap != null) {
49
+ const inside = (root, target) => {
50
+ if (typeof target !== 'string' || !isAbsolute(target)) return null;
51
+ const child = relative(canonicalPath(root), canonicalPath(target))
52
+ .split(sep)
53
+ .join('/');
54
+ return child && !/^\.\.(?:[/\\]|$)/.test(child) && !isAbsolute(child)
55
+ ? child
56
+ : null;
57
+ };
58
+ selectedProjectRecapRun =
59
+ inside(projectPath, recap.sourceRunRoot) ??
60
+ inside(archiveReport.archivePath, recap.sourceRunRoot);
61
+ const exportRelative = inside(
62
+ join(repoRoot, '.oat/repo/reference/project-recaps'),
63
+ recap.exportRoot,
64
+ );
43
65
  if (
44
- selectedProjectRecapRun.length === 0 ||
45
- /^\.\.(?:[/\\]|$)/.test(selectedProjectRecapRun) ||
46
- isAbsolute(selectedProjectRecapRun)
66
+ !selectedProjectRecapRun?.startsWith('explainers/') ||
67
+ !exportRelative
47
68
  ) {
48
69
  throw executionError(
49
- 'Archive resume recap source is outside the original project path.',
70
+ 'Archive resume recap source or export is outside its allowed root.',
50
71
  );
51
72
  }
52
- const exportRootRelative = relative(repoRoot, exportRoot)
73
+ const repoRelativeExport = relative(
74
+ canonicalPath(repoRoot),
75
+ canonicalPath(recap.exportRoot),
76
+ )
53
77
  .split(sep)
54
78
  .join('/');
55
79
  if (
56
- exportRootRelative.length === 0 ||
57
- /^\.\.(?:[/\\]|$)/.test(exportRootRelative) ||
58
- isAbsolute(exportRootRelative)
80
+ recap.manifest?.relativePath === 'manifest.json' &&
81
+ recap.page == null
59
82
  ) {
60
- throw executionError(
61
- 'Archive resume recap export is outside the repository root.',
83
+ // Persisted pre-flat-export receipts remain readable.
84
+ exportedManifestPath = posix.join(repoRelativeExport, 'manifest.json');
85
+ } else {
86
+ const page = recap.page;
87
+ const sourcePage = page?.sourceRelativePath;
88
+ if (
89
+ exportRelative !== `${archiveReport.snapshotId}.html` ||
90
+ typeof recap.runId !== 'string' ||
91
+ !recap.runId ||
92
+ !Number.isInteger(recap.verifiedArtifactCount) ||
93
+ recap.verifiedArtifactCount < 1 ||
94
+ typeof sourcePage !== 'string' ||
95
+ !sourcePage.startsWith('site/') ||
96
+ !sourcePage.endsWith('.html') ||
97
+ sourcePage.split('/').some((part) => part === '..') ||
98
+ !/^sha256:[0-9a-f]{64}$/.test(page?.originalSha256) ||
99
+ !/^sha256:[0-9a-f]{64}$/.test(page?.exportedSha256)
100
+ )
101
+ throw executionError(
102
+ 'Archive resume report has an invalid flat recap receipt.',
103
+ );
104
+ const archivedRun = join(
105
+ archiveReport.archivePath,
106
+ selectedProjectRecapRun,
62
107
  );
108
+ const hash = (bytes) =>
109
+ `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
110
+ try {
111
+ if (
112
+ !inside(archiveReport.archivePath, archivedRun) ||
113
+ !inside(archivedRun, join(archivedRun, sourcePage)) ||
114
+ !inside(archivedRun, join(archivedRun, 'manifest.json'))
115
+ )
116
+ throw new Error('archived source escapes its root');
117
+ const manifest = JSON.parse(
118
+ await readFile(join(archivedRun, 'manifest.json'), 'utf8'),
119
+ );
120
+ const original = await readFile(join(archivedRun, sourcePage));
121
+ const exported = await readFile(recap.exportRoot);
122
+ if (
123
+ manifest.runId !== recap.runId ||
124
+ Object.keys(manifest.immutableHashes).length !==
125
+ recap.verifiedArtifactCount ||
126
+ !manifest.artifacts.some(
127
+ (artifact) =>
128
+ (artifact.renderedPath ?? artifact.contentPath) === sourcePage,
129
+ ) ||
130
+ manifest.immutableHashes[sourcePage] !== page.originalSha256 ||
131
+ hash(original) !== page.originalSha256 ||
132
+ hash(exported) !== page.exportedSha256
133
+ )
134
+ throw new Error('run, page, or hash mismatch');
135
+ } catch (error) {
136
+ throw executionError(
137
+ `Archive resume flat recap identity failed: ${error.message}`,
138
+ );
139
+ }
140
+ exportedPagePath = repoRelativeExport;
63
141
  }
64
- exportedManifestPath = posix.join(exportRootRelative, manifestPath);
65
142
  }
66
143
  return {
67
144
  required: true,
@@ -73,6 +150,7 @@ async function buildPostArchiveContinuation(
73
150
  selectedProjectRecapRun,
74
151
  projectRecapExport: archiveReport.projectRecapExport ?? null,
75
152
  exportedManifestPath,
153
+ exportedPagePath,
76
154
  };
77
155
  }
78
156
 
@@ -15,6 +15,21 @@ function shellQuote(value) {
15
15
  export function parseSyncedArchiveResumeFields(result) {
16
16
  const continuation = result?.continuation;
17
17
  const hasRecapExport = continuation?.projectRecapExport !== null;
18
+ const recap = continuation?.projectRecapExport;
19
+ const flatPage = `.oat/repo/reference/project-recaps/${result?.archiveReport?.snapshotId}.html`;
20
+ const invalidFlat =
21
+ recap?.page &&
22
+ (typeof recap.runId !== 'string' ||
23
+ !recap.runId ||
24
+ typeof recap.page.sourceRelativePath !== 'string' ||
25
+ !recap.page.sourceRelativePath.startsWith('site/') ||
26
+ !/^sha256:[0-9a-f]{64}$/.test(recap.page.originalSha256) ||
27
+ !/^sha256:[0-9a-f]{64}$/.test(recap.page.exportedSha256) ||
28
+ !Number.isInteger(recap.verifiedArtifactCount) ||
29
+ recap.verifiedArtifactCount < 1 ||
30
+ continuation.exportedPagePath !== flatPage ||
31
+ typeof recap.exportRoot !== 'string' ||
32
+ !recap.exportRoot.endsWith(`/${flatPage}`));
18
33
  const allowedContinuationFields = new Set([
19
34
  'required',
20
35
  'rejoinStep',
@@ -25,6 +40,7 @@ export function parseSyncedArchiveResumeFields(result) {
25
40
  'selectedProjectRecapRun',
26
41
  'projectRecapExport',
27
42
  'exportedManifestPath',
43
+ 'exportedPagePath',
28
44
  ]);
29
45
  if (
30
46
  !continuation ||
@@ -42,10 +58,20 @@ export function parseSyncedArchiveResumeFields(result) {
42
58
  continuation.archivePath.length === 0 ||
43
59
  typeof continuation.lifecycleCommit !== 'string' ||
44
60
  !/^[0-9a-f]{40}$/.test(continuation.lifecycleCommit) ||
61
+ invalidFlat ||
45
62
  (hasRecapExport &&
46
- (typeof continuation.exportedManifestPath !== 'string' ||
47
- continuation.exportedManifestPath.length === 0)) ||
48
- (!hasRecapExport && continuation.exportedManifestPath !== '')
63
+ (JSON.stringify(continuation.projectRecapExport) !==
64
+ JSON.stringify(result.archiveReport?.projectRecapExport) ||
65
+ (continuation.projectRecapExport?.page
66
+ ? typeof continuation.exportedPagePath !== 'string' ||
67
+ !continuation.exportedPagePath.endsWith('.html') ||
68
+ continuation.exportedManifestPath !== ''
69
+ : typeof continuation.exportedManifestPath !== 'string' ||
70
+ !continuation.exportedManifestPath.endsWith('/manifest.json') ||
71
+ Boolean(continuation.exportedPagePath)))) ||
72
+ (!hasRecapExport &&
73
+ (continuation.exportedManifestPath !== '' ||
74
+ Boolean(continuation.exportedPagePath)))
49
75
  ) {
50
76
  throw fieldsError(
51
77
  'Synced archive resume result has no verified post-archive continuation.',
@@ -66,6 +92,7 @@ export function parseSyncedArchiveResumeFields(result) {
66
92
  continuation.projectRecapExport ?? null,
67
93
  ),
68
94
  EXPORTED_MANIFEST_PATH: continuation.exportedManifestPath,
95
+ EXPORTED_PAGE_PATH: continuation.exportedPagePath ?? '',
69
96
  PROJECT_REF_COMMIT: '',
70
97
  SHOULD_OPEN_PR: 'false',
71
98
  };