@open-agent-toolkit/cli 0.3.16 → 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 (116) hide show
  1. package/assets/agents/oat-phase-implementer.md +26 -6
  2. package/assets/agents/oat-reviewer.md +51 -1
  3. package/assets/bundle-metadata.json +1 -1
  4. package/assets/docs/contributing/documentation.md +4 -0
  5. package/assets/docs/contributing/skills.md +3 -1
  6. package/assets/docs/getting-started/bootstrap.md +1 -1
  7. package/assets/docs/getting-started/index.md +0 -4
  8. package/assets/docs/getting-started/tool-packs.md +0 -7
  9. package/assets/docs/reference/cli-reference.md +42 -1
  10. package/assets/docs/reference/config-and-local-state.md +11 -2
  11. package/assets/docs/reference/project-artifacts.md +43 -8
  12. package/assets/docs/skills/repo-improve.md +9 -7
  13. package/assets/docs/workflows/advanced/autonomy.md +40 -0
  14. package/assets/docs/workflows/backlog-and-planning/backlog-lifecycle.md +25 -24
  15. package/assets/docs/workflows/backlog-and-planning/remote-project-management.md +0 -7
  16. package/assets/docs/workflows/ideas/lifecycle.md +2 -7
  17. package/assets/docs/workflows/projects/reviews/review-flavors.md +8 -0
  18. package/assets/public-package-versions.json +4 -4
  19. package/assets/skills/oat-agent-instructions-apply/SKILL.md +14 -4
  20. package/assets/skills/oat-brainstorm/SKILL.md +17 -13
  21. package/assets/skills/oat-docs-apply/SKILL.md +13 -3
  22. package/assets/skills/oat-doctor/SKILL.md +11 -1
  23. package/assets/skills/oat-explainer-kit/SKILL.md +1 -1
  24. package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +6 -3
  25. package/assets/skills/oat-pjm-review-backlog/SKILL.md +2 -2
  26. package/assets/skills/oat-pjm-update-repo-reference/SKILL.md +28 -3
  27. package/assets/skills/oat-project-autonomous/SKILL.md +60 -5
  28. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +44 -6
  29. package/assets/skills/oat-project-capture/SKILL.md +7 -3
  30. package/assets/skills/oat-project-complete/SKILL.md +39 -34
  31. package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +106 -28
  32. package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +30 -3
  33. package/assets/skills/oat-project-design/SKILL.md +12 -9
  34. package/assets/skills/oat-project-discover/SKILL.md +8 -4
  35. package/assets/skills/oat-project-document/SKILL.md +14 -5
  36. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +44 -6
  37. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  38. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +6 -6
  39. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +44 -6
  40. package/assets/skills/oat-project-implement/references/phase-execution.md +20 -6
  41. package/assets/skills/oat-project-import-plan/SKILL.md +20 -6
  42. package/assets/skills/oat-project-lite/SKILL.md +7 -5
  43. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +44 -6
  44. package/assets/skills/oat-project-new/SKILL.md +6 -2
  45. package/assets/skills/oat-project-plan/SKILL.md +14 -6
  46. package/assets/skills/oat-project-pr-final/SKILL.md +1 -1
  47. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +44 -6
  48. package/assets/skills/oat-project-progress/SKILL.md +13 -1
  49. package/assets/skills/oat-project-promote-spec-driven/SKILL.md +7 -3
  50. package/assets/skills/oat-project-quick-start/SKILL.md +17 -12
  51. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +44 -6
  52. package/assets/skills/oat-project-reconcile/SKILL.md +8 -4
  53. package/assets/skills/oat-project-retro/SKILL.md +1 -1
  54. package/assets/skills/oat-project-retro/references/apply-procedure.md +6 -6
  55. package/assets/skills/oat-project-retro-file/SKILL.md +7 -3
  56. package/assets/skills/oat-project-review-provide/SKILL.md +23 -4
  57. package/assets/skills/oat-project-review-provide-remote/SKILL.md +19 -1
  58. package/assets/skills/oat-project-review-receive/SKILL.md +37 -12
  59. package/assets/skills/oat-project-review-receive-remote/SKILL.md +7 -3
  60. package/assets/skills/oat-project-revise/SKILL.md +13 -5
  61. package/assets/skills/oat-project-spec/SKILL.md +8 -4
  62. package/assets/skills/oat-project-summary/SKILL.md +11 -7
  63. package/assets/skills/oat-repo-knowledge-index/SKILL.md +54 -20
  64. package/assets/skills/oat-repo-knowledge-index/scripts/refresh-owned.mjs +118 -0
  65. package/assets/skills/oat-review-provide/SKILL.md +7 -3
  66. package/assets/skills/oat-review-receive-remote/SKILL.md +14 -4
  67. package/assets/skills/oat-wave-execute/SKILL.md +24 -11
  68. package/assets/skills/oat-wave-execute/assets/wrapper-plan-template.md +16 -2
  69. package/assets/skills/oat-worktree-bootstrap-auto/SKILL.md +45 -17
  70. package/assets/templates/pjm-agents.md +38 -8
  71. package/assets/templates/repo-agents.md +5 -0
  72. package/assets/templates/repo-readme.md +5 -0
  73. package/dist/commands/backlog/archive.d.ts +3 -0
  74. package/dist/commands/backlog/archive.d.ts.map +1 -1
  75. package/dist/commands/backlog/archive.js +63 -26
  76. package/dist/commands/backlog/index.d.ts.map +1 -1
  77. package/dist/commands/backlog/index.js +3 -0
  78. package/dist/commands/backlog/rewrite-references.d.ts +5 -0
  79. package/dist/commands/backlog/rewrite-references.d.ts.map +1 -1
  80. package/dist/commands/backlog/rewrite-references.js +39 -1
  81. package/dist/commands/init/index.d.ts.map +1 -1
  82. package/dist/commands/init/index.js +1 -0
  83. package/dist/commands/internal/commit-paths.d.ts +3 -0
  84. package/dist/commands/internal/commit-paths.d.ts.map +1 -0
  85. package/dist/commands/internal/commit-paths.js +31 -0
  86. package/dist/commands/internal/index.d.ts.map +1 -1
  87. package/dist/commands/internal/index.js +2 -0
  88. package/dist/commands/pjm/init.d.ts.map +1 -1
  89. package/dist/commands/pjm/init.js +37 -8
  90. package/dist/commands/project/archive/archive-utils.d.ts +6 -3
  91. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  92. package/dist/commands/project/archive/archive-utils.js +510 -104
  93. package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
  94. package/dist/commands/project/archive/push-runner.js +1 -0
  95. package/dist/commands/project/log/append.d.ts +2 -3
  96. package/dist/commands/project/log/append.d.ts.map +1 -1
  97. package/dist/commands/project/log/append.js +55 -101
  98. package/dist/commands/project/migrate/index.d.ts +1 -2
  99. package/dist/commands/project/migrate/index.d.ts.map +1 -1
  100. package/dist/commands/project/migrate/index.js +8 -2
  101. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  102. package/dist/commands/project/new/scaffold.js +4 -4
  103. package/dist/commands/project/promote/promote.d.ts +8 -0
  104. package/dist/commands/project/promote/promote.d.ts.map +1 -1
  105. package/dist/commands/project/promote/promote.js +37 -6
  106. package/dist/commands/project/status.d.ts.map +1 -1
  107. package/dist/commands/project/status.js +3 -0
  108. package/dist/commands/project/sync/ref-sync.d.ts +12 -1
  109. package/dist/commands/project/sync/ref-sync.d.ts.map +1 -1
  110. package/dist/commands/project/sync/ref-sync.js +347 -23
  111. package/dist/commands/shared/exact-path-commit.d.ts +32 -0
  112. package/dist/commands/shared/exact-path-commit.d.ts.map +1 -0
  113. package/dist/commands/shared/exact-path-commit.js +691 -0
  114. package/dist/validation/skills.d.ts.map +1 -1
  115. package/dist/validation/skills.js +138 -11
  116. package/package.json +3 -2
@@ -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
  };
@@ -5,13 +5,17 @@ disable-model-invocation: true
5
5
  user-invocable: true
6
6
  allowed-tools: Read, Write, Bash(git:*), Bash(oat template:*), Glob, Grep, AskUserQuestion
7
7
  metadata:
8
- version: 2.3.7
8
+ version: 2.3.8
9
9
  ---
10
10
 
11
11
  # Design Phase
12
12
 
13
13
  Transform specification requirements into a detailed technical design with architecture, components, and implementation strategy.
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
  ## Prerequisites
16
20
 
17
21
  **Required:** Completed discovery (`discovery.md` with `oat_status: complete`). Specification is optional — this skill folds requirements confirmation into its flow and produces `spec.md` as a byproduct (see Step 2). If the user already ran `oat-project-spec` standalone, that spec is reused.
@@ -328,8 +332,8 @@ PROJECT_SCOPE=$(oat project scope "$PROJECT_PATH" --format value) || { echo "oat
328
332
  if [ "$PROJECT_SCOPE" = "synced" ]; then
329
333
  oat project push "$PROJECT_PATH" --message "docs: confirm requirements for {project-name}" || { echo "oat: project push failed; run oat project pull, resolve the reported state, and retry" >&2; exit 1; }
330
334
  else
331
- git add "$PROJECT_PATH/spec.md"
332
- git commit -m "docs: confirm requirements for {project-name}"
335
+ OWNED_COMMIT_PATHS=("$PROJECT_PATH/spec.md")
336
+ oat internal commit-paths --identity "${COMMIT_IDENTITY:?set once and retain for retries}:oat-project-design:1" --message "docs: confirm requirements for {project-name}" -- "${OWNED_COMMIT_PATHS[@]}" || exit 1
333
337
  fi
334
338
  ```
335
339
 
@@ -603,8 +607,8 @@ PROJECT_SCOPE=$(oat project scope "$PROJECT_PATH" --format value) || { echo "oat
603
607
  if [ "$PROJECT_SCOPE" = "synced" ]; then
604
608
  oat project push "$PROJECT_PATH" --message "docs: draft design for {project-name} (awaiting review)" || { echo "oat: project push failed; run oat project pull, resolve the reported state, and retry" >&2; exit 1; }
605
609
  else
606
- git add "$PROJECT_PATH/spec.md" "$PROJECT_PATH/design.md" "$PROJECT_PATH/state.md"
607
- git diff --cached --quiet || git commit -m "docs: draft design for {project-name} (awaiting review)"
610
+ OWNED_COMMIT_PATHS=("$PROJECT_PATH/spec.md" "$PROJECT_PATH/design.md" "$PROJECT_PATH/state.md")
611
+ oat internal commit-paths --identity "${COMMIT_IDENTITY:?set once and retain for retries}:oat-project-design:2" --message "docs: draft design for {project-name} (awaiting review)" -- "${OWNED_COMMIT_PATHS[@]}" || exit 1
608
612
  fi
609
613
  ```
610
614
 
@@ -646,8 +650,7 @@ Wait for user response:
646
650
  if [ "$PROJECT_SCOPE" = "synced" ]; then
647
651
  oat project push "$PROJECT_PATH" --message "docs: revise design after user review feedback" || { echo "oat: project push failed; run oat project pull, resolve the reported state, and retry" >&2; exit 1; }
648
652
  else
649
- git add "$PROJECT_PATH/spec.md" "$PROJECT_PATH/design.md"
650
- git commit -m "docs: revise design after user review feedback"
653
+ oat internal commit-paths --identity "${COMMIT_IDENTITY:?set once and retain for retries}:oat-project-design:3" --message "docs: revise design after user review feedback" -- "$PROJECT_PATH/spec.md" "$PROJECT_PATH/design.md" || exit 1
651
654
  fi
652
655
  Re-present this prompt.
653
656
  - If user does not approve yet (wants to pause without explicit change
@@ -813,8 +816,8 @@ PROJECT_SCOPE=$(oat project scope "$PROJECT_PATH" --format value) || { echo "oat
813
816
  if [ "$PROJECT_SCOPE" = "synced" ]; then
814
817
  oat project push "$PROJECT_PATH" --message "chore(oat): mark design complete for {project-name}" || { echo "oat: project push failed; run oat project pull, resolve the reported state, and retry" >&2; exit 1; }
815
818
  else
816
- git add "$PROJECT_PATH/design.md" "$PROJECT_PATH/state.md"
817
- git diff --cached --quiet || git commit -m "chore(oat): mark design complete for {project-name}"
819
+ OWNED_COMMIT_PATHS=("$PROJECT_PATH/design.md" "$PROJECT_PATH/state.md")
820
+ oat internal commit-paths --identity "${COMMIT_IDENTITY:?set once and retain for retries}:oat-project-design:4" --message "chore(oat): mark design complete for {project-name}" -- "${OWNED_COMMIT_PATHS[@]}" || exit 1
818
821
  fi
819
822
  ```
820
823
 
@@ -5,13 +5,17 @@ disable-model-invocation: false
5
5
  user-invocable: true
6
6
  allowed-tools: Read, Write, Bash(git:*), Bash(oat:*), Bash(pnpm:*), Glob, Grep, AskUserQuestion
7
7
  metadata:
8
- version: 2.2.8
8
+ version: 2.2.9
9
9
  ---
10
10
 
11
11
  # Discovery Phase
12
12
 
13
13
  Gather requirements and understand the problem space through natural collaborative dialogue.
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
  ## Prerequisites
16
20
 
17
21
  **Required:** Knowledge base must exist. If missing, run the `oat-repo-knowledge-index` skill first.
@@ -575,14 +579,14 @@ Key decisions:
575
579
  Ready for design phase" || { echo "oat: project push failed; run oat project pull, resolve the reported state, and retry" >&2; exit 1; }
576
580
  else
577
581
  PROJECT_OUTPUT_PATHS=("$PROJECT_PATH/discovery.md" "$PROJECT_PATH/state.md")
578
- git add -- "${PROJECT_OUTPUT_PATHS[@]}"
579
- git commit -m "docs: complete discovery for {project-name}
582
+ OWNED_COMMIT_PATHS=("${PROJECT_OUTPUT_PATHS[@]}")
583
+ oat internal commit-paths --identity "${COMMIT_IDENTITY:?set once and retain for retries}:oat-project-discover:1" --message "docs: complete discovery for {project-name}
580
584
 
581
585
  Key decisions:
582
586
  - {Decision 1}
583
587
  - {Decision 2}
584
588
 
585
- Ready for design phase"
589
+ Ready for design phase" -- "${OWNED_COMMIT_PATHS[@]}" || exit 1
586
590
  fi
587
591
  ```
588
592
 
@@ -6,13 +6,17 @@ disable-model-invocation: false
6
6
  user-invocable: true
7
7
  allowed-tools: Read, Write, Edit, Bash(git:*), Bash(jq:*), Bash(oat pjm:*), Bash(oat project push:*), Bash(oat project scope:*), Bash(oat tools:*), Glob, Grep, AskUserQuestion, Skill
8
8
  metadata:
9
- version: 1.8.7
9
+ version: 1.8.8
10
10
  ---
11
11
 
12
12
  # Project Documentation Sync
13
13
 
14
14
  Read project artifacts and implementation code to identify documentation surfaces that need updating, present a delta plan for approval, and apply changes — all in a single invocation.
15
15
 
16
+ ## Hook-safe exact-path commits
17
+
18
+ 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`.
19
+
16
20
  ## Prerequisites
17
21
 
18
22
  **Required:**
@@ -490,6 +494,11 @@ Track which recommendations were approved for Step 7.
490
494
 
491
495
  ### Step 7: Apply Approved Changes
492
496
 
497
+ Initialize `OWNED_OUTPUT_FILES=()` before documentation writes. Append each
498
+ exact successfully written or removed documentation path, including both
499
+ rename sides and generated index/navigation files actually changed. Retain this
500
+ producer list for the Step 8a helper commit and retries.
501
+
493
502
  Execute the approved documentation updates.
494
503
 
495
504
  **For each approved recommendation:**
@@ -540,8 +549,8 @@ If `$DOCS_CONFIG` exists and new files were created in the docs directory:
540
549
  **8a. Stage and commit documentation changes:**
541
550
 
542
551
  ```bash
543
- git add {list of changed/created documentation files}
544
- git diff --cached --quiet || git commit -m "docs({project-name}): update documentation from project artifacts"
552
+ OWNED_COMMIT_PATHS=("${OWNED_OUTPUT_FILES[@]}")
553
+ oat internal commit-paths --identity "${COMMIT_IDENTITY:?set once and retain for retries}:oat-project-document:1" --message "docs({project-name}): update documentation from project artifacts" -- "${OWNED_COMMIT_PATHS[@]}" || exit 1
545
554
  ```
546
555
 
547
556
  Only stage files that were actually changed or created in Step 7. Do not use `git add -A`.
@@ -559,8 +568,8 @@ PROJECT_SCOPE=$(oat project scope "$PROJECT_PATH" --format value) || { echo "oat
559
568
  if [ "$PROJECT_SCOPE" = "synced" ]; then
560
569
  oat project push "$PROJECT_PATH" --message "chore({project-name}): mark docs updated" || { echo "oat: project push failed; run oat project pull, resolve the reported state, and retry" >&2; exit 1; }
561
570
  else
562
- git add "$PROJECT_PATH/state.md"
563
- git diff --cached --quiet || git commit -m "chore({project-name}): mark docs updated"
571
+ OWNED_COMMIT_PATHS=("$PROJECT_PATH/state.md")
572
+ oat internal commit-paths --identity "${COMMIT_IDENTITY:?set once and retain for retries}:oat-project-document:2" --message "chore({project-name}): mark docs updated" -- "${OWNED_COMMIT_PATHS[@]}" || exit 1
564
573
  fi
565
574
  ```
566
575