nubos-pilot 0.1.0

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 (273) hide show
  1. package/agents/np-ai-researcher.md +140 -0
  2. package/agents/np-code-fixer.md +363 -0
  3. package/agents/np-code-reviewer.md +351 -0
  4. package/agents/np-domain-researcher.md +136 -0
  5. package/agents/np-eval-auditor.md +167 -0
  6. package/agents/np-eval-planner.md +153 -0
  7. package/agents/np-executor.md +72 -0
  8. package/agents/np-framework-selector.md +171 -0
  9. package/agents/np-nyquist-auditor.md +185 -0
  10. package/agents/np-plan-checker.md +165 -0
  11. package/agents/np-planner.md +199 -0
  12. package/agents/np-researcher.md +150 -0
  13. package/agents/np-security-auditor.md +206 -0
  14. package/agents/np-ui-auditor.md +369 -0
  15. package/agents/np-ui-checker.md +192 -0
  16. package/agents/np-ui-researcher.md +324 -0
  17. package/agents/np-verifier.md +79 -0
  18. package/bin/check-coverage.cjs +40 -0
  19. package/bin/check-workflows.cjs +171 -0
  20. package/bin/check-workflows.test.cjs +208 -0
  21. package/bin/install.js +500 -0
  22. package/bin/np-tools/_commands.cjs +70 -0
  23. package/bin/np-tools/add-tests.cjs +171 -0
  24. package/bin/np-tools/add-tests.test.cjs +122 -0
  25. package/bin/np-tools/add-todo.cjs +108 -0
  26. package/bin/np-tools/add-todo.test.cjs +112 -0
  27. package/bin/np-tools/agent-skills.cjs +14 -0
  28. package/bin/np-tools/agent-skills.test.cjs +42 -0
  29. package/bin/np-tools/ai-integration-phase.cjs +109 -0
  30. package/bin/np-tools/ai-integration-phase.test.cjs +123 -0
  31. package/bin/np-tools/askuser.cjs +53 -0
  32. package/bin/np-tools/askuser.test.cjs +49 -0
  33. package/bin/np-tools/autonomous.cjs +69 -0
  34. package/bin/np-tools/autonomous.test.cjs +74 -0
  35. package/bin/np-tools/checkpoint.cjs +101 -0
  36. package/bin/np-tools/checkpoint.test.cjs +119 -0
  37. package/bin/np-tools/code-review.cjs +133 -0
  38. package/bin/np-tools/code-review.test.cjs +96 -0
  39. package/bin/np-tools/commit-task.cjs +120 -0
  40. package/bin/np-tools/commit-task.test.cjs +160 -0
  41. package/bin/np-tools/commit.cjs +103 -0
  42. package/bin/np-tools/commit.test.cjs +93 -0
  43. package/bin/np-tools/config.cjs +101 -0
  44. package/bin/np-tools/config.test.cjs +71 -0
  45. package/bin/np-tools/discuss-phase-power.cjs +265 -0
  46. package/bin/np-tools/discuss-phase-power.test.cjs +242 -0
  47. package/bin/np-tools/discuss-phase.cjs +132 -0
  48. package/bin/np-tools/discuss-phase.test.cjs +148 -0
  49. package/bin/np-tools/dispatch.cjs +116 -0
  50. package/bin/np-tools/doctor.cjs +242 -0
  51. package/bin/np-tools/eval-review.cjs +116 -0
  52. package/bin/np-tools/eval-review.test.cjs +123 -0
  53. package/bin/np-tools/execute-phase.cjs +182 -0
  54. package/bin/np-tools/execute-phase.test.cjs +116 -0
  55. package/bin/np-tools/execute-plan.cjs +124 -0
  56. package/bin/np-tools/execute-plan.test.cjs +82 -0
  57. package/bin/np-tools/help.cjs +28 -0
  58. package/bin/np-tools/help.test.cjs +29 -0
  59. package/bin/np-tools/init-dispatch.test.cjs +91 -0
  60. package/bin/np-tools/metrics.cjs +97 -0
  61. package/bin/np-tools/metrics.test.cjs +188 -0
  62. package/bin/np-tools/new-milestone.cjs +288 -0
  63. package/bin/np-tools/new-milestone.test.cjs +166 -0
  64. package/bin/np-tools/new-project.cjs +284 -0
  65. package/bin/np-tools/new-project.test.cjs +165 -0
  66. package/bin/np-tools/next.cjs +7 -0
  67. package/bin/np-tools/next.test.cjs +30 -0
  68. package/bin/np-tools/park.cjs +48 -0
  69. package/bin/np-tools/park.test.cjs +50 -0
  70. package/bin/np-tools/pause-work.cjs +24 -0
  71. package/bin/np-tools/pause-work.test.cjs +74 -0
  72. package/bin/np-tools/phase.cjs +71 -0
  73. package/bin/np-tools/phase.test.cjs +81 -0
  74. package/bin/np-tools/plan-diff.cjs +57 -0
  75. package/bin/np-tools/plan-diff.test.cjs +134 -0
  76. package/bin/np-tools/plan-milestone-gaps.cjs +115 -0
  77. package/bin/np-tools/plan-milestone-gaps.test.cjs +122 -0
  78. package/bin/np-tools/plan-phase.cjs +350 -0
  79. package/bin/np-tools/plan-phase.test.cjs +263 -0
  80. package/bin/np-tools/progress.cjs +7 -0
  81. package/bin/np-tools/progress.test.cjs +44 -0
  82. package/bin/np-tools/queue.cjs +213 -0
  83. package/bin/np-tools/research-phase.cjs +144 -0
  84. package/bin/np-tools/research-phase.test.cjs +154 -0
  85. package/bin/np-tools/reset-slice.cjs +17 -0
  86. package/bin/np-tools/reset-slice.test.cjs +96 -0
  87. package/bin/np-tools/resolve-model.cjs +110 -0
  88. package/bin/np-tools/resolve-model.test.cjs +200 -0
  89. package/bin/np-tools/resume-work.cjs +76 -0
  90. package/bin/np-tools/resume-work.test.cjs +91 -0
  91. package/bin/np-tools/skip.cjs +48 -0
  92. package/bin/np-tools/skip.test.cjs +66 -0
  93. package/bin/np-tools/slug.cjs +34 -0
  94. package/bin/np-tools/slug.test.cjs +46 -0
  95. package/bin/np-tools/state.cjs +16 -0
  96. package/bin/np-tools/state.test.cjs +40 -0
  97. package/bin/np-tools/stats.cjs +151 -0
  98. package/bin/np-tools/stats.test.cjs +118 -0
  99. package/bin/np-tools/triage.cjs +128 -0
  100. package/bin/np-tools/ui-phase.cjs +108 -0
  101. package/bin/np-tools/ui-phase.test.cjs +121 -0
  102. package/bin/np-tools/ui-review.cjs +108 -0
  103. package/bin/np-tools/ui-review.test.cjs +120 -0
  104. package/bin/np-tools/undo-task.cjs +31 -0
  105. package/bin/np-tools/undo-task.test.cjs +117 -0
  106. package/bin/np-tools/undo.cjs +43 -0
  107. package/bin/np-tools/undo.test.cjs +120 -0
  108. package/bin/np-tools/unpark.cjs +48 -0
  109. package/bin/np-tools/unpark.test.cjs +50 -0
  110. package/bin/np-tools/verify-work.cjs +186 -0
  111. package/bin/np-tools/verify-work.test.cjs +97 -0
  112. package/docs/adr/0001-no-daemon-invariant.md +82 -0
  113. package/docs/adr/0002-zero-runtime-dependencies.md +90 -0
  114. package/docs/adr/0003-max-six-unit-types.md +85 -0
  115. package/docs/adr/0004-atomic-commit-per-unit.md +102 -0
  116. package/docs/adr/0005-three-orthogonal-file-trees.md +98 -0
  117. package/docs/adr/0006-yaml-dependency-amendment.md +60 -0
  118. package/docs/adr/README.md +27 -0
  119. package/docs/agent-frontmatter-schema.md +84 -0
  120. package/docs/phase-artifact-schemas.md +292 -0
  121. package/docs/phase-directory-layout.md +82 -0
  122. package/lib/__tests__/README.md +1 -0
  123. package/lib/agents.cjs +98 -0
  124. package/lib/agents.test.cjs +286 -0
  125. package/lib/askuser.cjs +36 -0
  126. package/lib/askuser.test.cjs +310 -0
  127. package/lib/checkpoint.cjs +135 -0
  128. package/lib/checkpoint.test.cjs +184 -0
  129. package/lib/core.cjs +165 -0
  130. package/lib/core.test.cjs +405 -0
  131. package/lib/fixtures/README.md +1 -0
  132. package/lib/fixtures/phase-tree/README.md +1 -0
  133. package/lib/fixtures/plans/cycle/PLAN.md +16 -0
  134. package/lib/fixtures/plans/cycle/tasks/T-01.md +20 -0
  135. package/lib/fixtures/plans/cycle/tasks/T-02.md +20 -0
  136. package/lib/fixtures/plans/cycle/tasks/T-03.md +20 -0
  137. package/lib/fixtures/plans/linear/PLAN.md +16 -0
  138. package/lib/fixtures/plans/linear/tasks/T-01.md +20 -0
  139. package/lib/fixtures/plans/linear/tasks/T-02.md +20 -0
  140. package/lib/fixtures/plans/linear/tasks/T-03.md +20 -0
  141. package/lib/fixtures/plans/parallel/PLAN.md +16 -0
  142. package/lib/fixtures/plans/parallel/tasks/T-01.md +20 -0
  143. package/lib/fixtures/plans/parallel/tasks/T-02.md +20 -0
  144. package/lib/fixtures/plans/parallel/tasks/T-03.md +20 -0
  145. package/lib/fixtures/plans/wave-conflict/PLAN.md +16 -0
  146. package/lib/fixtures/plans/wave-conflict/tasks/T-01.md +20 -0
  147. package/lib/fixtures/plans/wave-conflict/tasks/T-02.md +20 -0
  148. package/lib/fixtures/roadmap/ROADMAP-malformed.md +3 -0
  149. package/lib/fixtures/roadmap/ROADMAP-minimal.md +51 -0
  150. package/lib/fixtures/roadmap/roadmap-malformed.yaml +7 -0
  151. package/lib/fixtures/roadmap/roadmap-minimal.yaml +40 -0
  152. package/lib/fixtures/roadmap/roadmap-ten-phases.yaml +101 -0
  153. package/lib/fixtures/templates/phase-context.md +6 -0
  154. package/lib/fixtures/templates/plan-skeleton.md +6 -0
  155. package/lib/frontmatter.cjs +251 -0
  156. package/lib/frontmatter.test.cjs +177 -0
  157. package/lib/gaps.cjs +197 -0
  158. package/lib/gaps.test.cjs +200 -0
  159. package/lib/git.cjs +207 -0
  160. package/lib/git.test.cjs +305 -0
  161. package/lib/install/agents-md.cjs +77 -0
  162. package/lib/install/backup.cjs +70 -0
  163. package/lib/install/codex-toml.cjs +440 -0
  164. package/lib/install/managed-block.cjs +30 -0
  165. package/lib/install/manifest.cjs +148 -0
  166. package/lib/install/mcp-writer.cjs +127 -0
  167. package/lib/install/runtime-detect.cjs +44 -0
  168. package/lib/install/staging.cjs +149 -0
  169. package/lib/metrics-aggregate.cjs +229 -0
  170. package/lib/metrics-aggregate.test.cjs +192 -0
  171. package/lib/metrics.cjs +120 -0
  172. package/lib/metrics.test.cjs +182 -0
  173. package/lib/model-aliases.regression.test.cjs +16 -0
  174. package/lib/model-profiles.cjs +42 -0
  175. package/lib/model-profiles.test.cjs +61 -0
  176. package/lib/next.cjs +236 -0
  177. package/lib/next.test.cjs +194 -0
  178. package/lib/phase.cjs +95 -0
  179. package/lib/phase.test.cjs +189 -0
  180. package/lib/plan-checker-contract.test.cjs +72 -0
  181. package/lib/plan-diff.cjs +173 -0
  182. package/lib/plan-diff.test.cjs +217 -0
  183. package/lib/plan.cjs +85 -0
  184. package/lib/plan.test.cjs +263 -0
  185. package/lib/progress.cjs +95 -0
  186. package/lib/progress.test.cjs +116 -0
  187. package/lib/researcher-contract.test.cjs +61 -0
  188. package/lib/roadmap-render.cjs +206 -0
  189. package/lib/roadmap-render.test.cjs +121 -0
  190. package/lib/roadmap.cjs +416 -0
  191. package/lib/roadmap.test.cjs +371 -0
  192. package/lib/runtime/_contract.test.cjs +61 -0
  193. package/lib/runtime/_readline.cjs +119 -0
  194. package/lib/runtime/_readline.test.cjs +126 -0
  195. package/lib/runtime/claude.cjs +48 -0
  196. package/lib/runtime/claude.test.cjs +101 -0
  197. package/lib/runtime/codex.cjs +35 -0
  198. package/lib/runtime/codex.test.cjs +114 -0
  199. package/lib/runtime/gemini.cjs +35 -0
  200. package/lib/runtime/gemini.test.cjs +109 -0
  201. package/lib/runtime/index.cjs +49 -0
  202. package/lib/runtime/index.test.cjs +181 -0
  203. package/lib/runtime/opencode.cjs +35 -0
  204. package/lib/runtime/opencode.test.cjs +124 -0
  205. package/lib/state.cjs +205 -0
  206. package/lib/state.test.cjs +264 -0
  207. package/lib/surface-audit.test.cjs +46 -0
  208. package/lib/tasks.cjs +327 -0
  209. package/lib/tasks.test.cjs +389 -0
  210. package/lib/template.cjs +66 -0
  211. package/lib/template.test.cjs +159 -0
  212. package/lib/undo.cjs +179 -0
  213. package/lib/undo.test.cjs +261 -0
  214. package/lib/verify.cjs +116 -0
  215. package/lib/verify.test.cjs +187 -0
  216. package/np-tools.cjs +303 -0
  217. package/package.json +39 -0
  218. package/templates/AI-SPEC.md +90 -0
  219. package/templates/CONTEXT.md +32 -0
  220. package/templates/PLAN.md +69 -0
  221. package/templates/PROJECT.md +60 -0
  222. package/templates/REQUIREMENTS.md +38 -0
  223. package/templates/SECURITY.md +61 -0
  224. package/templates/UI-SPEC.md +64 -0
  225. package/templates/VALIDATION.md +76 -0
  226. package/templates/claude/payload/README.md +11 -0
  227. package/templates/opencode/opencode.json +6 -0
  228. package/templates/opencode/payload/AGENTS.md +9 -0
  229. package/workflows/add-backlog.md +212 -0
  230. package/workflows/add-tests.md +69 -0
  231. package/workflows/add-todo.md +222 -0
  232. package/workflows/ai-integration-phase.md +230 -0
  233. package/workflows/autonomous.md +94 -0
  234. package/workflows/cleanup.md +325 -0
  235. package/workflows/code-review-fix.md +435 -0
  236. package/workflows/code-review.md +447 -0
  237. package/workflows/discuss-phase-assumptions.md +269 -0
  238. package/workflows/discuss-phase-power.md +139 -0
  239. package/workflows/discuss-phase.md +386 -0
  240. package/workflows/dispatch.md +9 -0
  241. package/workflows/doctor.md +10 -0
  242. package/workflows/eval-review.md +243 -0
  243. package/workflows/execute-phase.md +142 -0
  244. package/workflows/execute-plan.md +82 -0
  245. package/workflows/help.md +8 -0
  246. package/workflows/new-milestone.md +166 -0
  247. package/workflows/new-project.md +213 -0
  248. package/workflows/next.md +8 -0
  249. package/workflows/note.md +244 -0
  250. package/workflows/park.md +29 -0
  251. package/workflows/pause-work.md +34 -0
  252. package/workflows/plan-milestone-gaps.md +233 -0
  253. package/workflows/plan-phase.md +351 -0
  254. package/workflows/progress.md +8 -0
  255. package/workflows/queue.md +9 -0
  256. package/workflows/research-phase.md +327 -0
  257. package/workflows/reset-slice.md +39 -0
  258. package/workflows/resume-work.md +79 -0
  259. package/workflows/review.md +489 -0
  260. package/workflows/secure-phase.md +209 -0
  261. package/workflows/session-report.md +243 -0
  262. package/workflows/skip.md +29 -0
  263. package/workflows/state.md +7 -0
  264. package/workflows/stats.md +170 -0
  265. package/workflows/thread.md +214 -0
  266. package/workflows/triage.md +9 -0
  267. package/workflows/ui-phase.md +246 -0
  268. package/workflows/ui-review.md +222 -0
  269. package/workflows/undo-task.md +42 -0
  270. package/workflows/undo.md +55 -0
  271. package/workflows/unpark.md +29 -0
  272. package/workflows/validate-phase.md +231 -0
  273. package/workflows/verify-work.md +83 -0
@@ -0,0 +1,230 @@
1
+ ---
2
+ command: np:ai-integration-phase
3
+ description: Generate AI-SPEC.md for AI/ML phases via 4-agent chain (np-framework-selector → np-ai-researcher → np-domain-researcher → np-eval-planner) with a completeness validation gate.
4
+ ---
5
+
6
+ # np:ai-integration-phase
7
+
8
+ Produces `{phase_dir}/{padded}-AI-SPEC.md` by running a strict-serial 4-agent chain.
9
+ Inserts between `/np:discuss-phase` and `/np:plan-phase`. Locks domain
10
+ context, framework selection, implementation patterns, and evaluation
11
+ strategy BEFORE the planner creates tasks.
12
+
13
+ Every Task-spawn site is wrapped in the Plan 09-05 metrics + resolve-model
14
+ pattern (D-06, D-01). `RUNTIME` is detected once at the top of the bash
15
+ block and re-used by every `metrics record` call.
16
+
17
+ ## Initialize
18
+
19
+ ```bash
20
+ PHASE="$1"
21
+ if [[ -z "$PHASE" ]]; then
22
+ echo "Usage: /np:ai-integration-phase <phase-number>" >&2
23
+ exit 2
24
+ fi
25
+
26
+ INIT=$(node np-tools.cjs init ai-integration-phase "$PHASE")
27
+ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
28
+ RUNTIME=$(node -e "console.log(require('./lib/runtime/index.cjs').detect().runtime)")
29
+ ```
30
+
31
+ Parse JSON for: `phase`, `padded`, `phase_dir`, `ai_spec_path`,
32
+ `has_ai_spec`, `template_path`, `agents.framework_selector`,
33
+ `agents.ai_researcher`, `agents.domain_researcher`, `agents.eval_planner`.
34
+
35
+ ```bash
36
+ PADDED=$(echo "$INIT" | jq -r '.padded')
37
+ PHASE_DIR=$(echo "$INIT" | jq -r '.phase_dir')
38
+ AI_SPEC_PATH=$(echo "$INIT" | jq -r '.ai_spec_path')
39
+ HAS_AI_SPEC=$(echo "$INIT" | jq -r '.has_ai_spec')
40
+ TEMPLATE_PATH=$(echo "$INIT" | jq -r '.template_path')
41
+ PLAN_ID="${PADDED}-ai-integration"
42
+ TASK_ID="${PADDED}-ai-integration"
43
+ ```
44
+
45
+ ## Pre-Flight Gates
46
+
47
+ <pre_flight>
48
+
49
+ ### Gate 1 — AI-SPEC.md already exists
50
+
51
+ If `has_ai_spec == true`:
52
+
53
+ ```bash
54
+ if [[ "$HAS_AI_SPEC" == "true" ]]; then
55
+ CHOICE=$(node np-tools.cjs askuser --json '{
56
+ "type": "select",
57
+ "header": "Existing AI-SPEC",
58
+ "question": "AI-SPEC.md already exists for Phase '"$PHASE"'. What would you like to do?",
59
+ "options": [
60
+ {"label": "Update — re-run with existing as baseline", "description": "Re-runs the 4-agent chain against the current spec."},
61
+ {"label": "View — display current AI-SPEC and exit", "description": "Reads the file and exits without changes."},
62
+ {"label": "Skip — keep current AI-SPEC and exit", "description": "Leaves the file untouched."}
63
+ ]
64
+ }')
65
+ case "$CHOICE" in
66
+ "View"*) cat "$AI_SPEC_PATH"; exit 0 ;;
67
+ "Skip"*) exit 0 ;;
68
+ esac
69
+ fi
70
+ ```
71
+
72
+ </pre_flight>
73
+
74
+ ## Philosophy
75
+
76
+ <philosophy>
77
+ AI-SPEC.md locks the four things AI projects most often ship wrong: the
78
+ framework choice (too early / for the wrong reason), the domain rubrics
79
+ (assumed without asking an expert), the implementation pattern (copy-paste
80
+ from outdated docs), and the evaluation strategy (retrofitted after code
81
+ is merged). Running this workflow BEFORE `/np:plan-phase` surfaces those
82
+ decisions as explicit artifacts the planner and plan-checker can then
83
+ enforce.
84
+ </philosophy>
85
+
86
+ ## Main Flow
87
+
88
+ The 4 agents run in strict serial order. Each spawn is wrapped in the
89
+ Plan-09-05 metrics pattern. A failure in any step aborts the chain — the
90
+ next agent reads the partial AI-SPEC.md that prior agents wrote.
91
+
92
+ ### Step 0 — Initialize AI-SPEC.md from template
93
+
94
+ ```bash
95
+ if [[ "$HAS_AI_SPEC" != "true" ]]; then
96
+ cp "$TEMPLATE_PATH" "$AI_SPEC_PATH"
97
+ fi
98
+ ```
99
+
100
+ ### Step 1 — Framework selection (np-framework-selector, opus)
101
+
102
+ ```bash
103
+ START=$(node np-tools.cjs metrics start-timestamp)
104
+ MODEL=$(node np-tools.cjs resolve-model np-framework-selector --profile balanced)
105
+ # Spawn agent=np-framework-selector model=$MODEL
106
+ # input: phase_number=$PHASE, ai_spec_path=$AI_SPEC_PATH, context=$PHASE_DIR/$PADDED-CONTEXT.md
107
+ # output: section 2 of AI-SPEC.md (framework scoring + selected)
108
+ END=$(node np-tools.cjs metrics end-timestamp)
109
+ node np-tools.cjs metrics record \
110
+ --agent np-framework-selector --tier opus --resolved-model "$MODEL" \
111
+ --phase "$PHASE" --plan "$PLAN_ID" --task "$TASK_ID" \
112
+ --started "$START" --ended "$END" \
113
+ --tokens-in "${TOKENS_IN:-0}" --tokens-out "${TOKENS_OUT:-0}" \
114
+ --retry-count 0 --status ok --runtime "$RUNTIME"
115
+ ```
116
+
117
+ ### Step 2 — AI researcher (np-ai-researcher, sonnet)
118
+
119
+ ```bash
120
+ START=$(node np-tools.cjs metrics start-timestamp)
121
+ MODEL=$(node np-tools.cjs resolve-model np-ai-researcher --profile balanced)
122
+ # Spawn agent=np-ai-researcher model=$MODEL
123
+ # input: ai_spec_path=$AI_SPEC_PATH (framework from step 1 already present)
124
+ # output: sections 3 (Implementation) + 4b (Pydantic models) of AI-SPEC.md
125
+ END=$(node np-tools.cjs metrics end-timestamp)
126
+ node np-tools.cjs metrics record \
127
+ --agent np-ai-researcher --tier sonnet --resolved-model "$MODEL" \
128
+ --phase "$PHASE" --plan "$PLAN_ID" --task "$TASK_ID" \
129
+ --started "$START" --ended "$END" \
130
+ --tokens-in "${TOKENS_IN:-0}" --tokens-out "${TOKENS_OUT:-0}" \
131
+ --retry-count 0 --status ok --runtime "$RUNTIME"
132
+ ```
133
+
134
+ ### Step 3 — Domain researcher (np-domain-researcher, sonnet)
135
+
136
+ ```bash
137
+ START=$(node np-tools.cjs metrics start-timestamp)
138
+ MODEL=$(node np-tools.cjs resolve-model np-domain-researcher --profile balanced)
139
+ # Spawn agent=np-domain-researcher model=$MODEL
140
+ # input: ai_spec_path=$AI_SPEC_PATH
141
+ # output: section 1b (Domain Context) of AI-SPEC.md
142
+ END=$(node np-tools.cjs metrics end-timestamp)
143
+ node np-tools.cjs metrics record \
144
+ --agent np-domain-researcher --tier sonnet --resolved-model "$MODEL" \
145
+ --phase "$PHASE" --plan "$PLAN_ID" --task "$TASK_ID" \
146
+ --started "$START" --ended "$END" \
147
+ --tokens-in "${TOKENS_IN:-0}" --tokens-out "${TOKENS_OUT:-0}" \
148
+ --retry-count 0 --status ok --runtime "$RUNTIME"
149
+ ```
150
+
151
+ ### Step 4 — Eval planner (np-eval-planner, opus)
152
+
153
+ ```bash
154
+ START=$(node np-tools.cjs metrics start-timestamp)
155
+ MODEL=$(node np-tools.cjs resolve-model np-eval-planner --profile balanced)
156
+ # Spawn agent=np-eval-planner model=$MODEL
157
+ # input: ai_spec_path=$AI_SPEC_PATH (domain rubrics from step 3 ground the dimensions)
158
+ # output: sections 5 (Eval Dimensions), 6 (Guardrails), 7 (Monitoring)
159
+ END=$(node np-tools.cjs metrics end-timestamp)
160
+ node np-tools.cjs metrics record \
161
+ --agent np-eval-planner --tier opus --resolved-model "$MODEL" \
162
+ --phase "$PHASE" --plan "$PLAN_ID" --task "$TASK_ID" \
163
+ --started "$START" --ended "$END" \
164
+ --tokens-in "${TOKENS_IN:-0}" --tokens-out "${TOKENS_OUT:-0}" \
165
+ --retry-count 0 --status ok --runtime "$RUNTIME"
166
+ ```
167
+
168
+ ## Validation Gate
169
+
170
+ After all 4 agents finish, grep the completed AI-SPEC.md for the required
171
+ section headers. Missing any → ask the user to re-run that step or accept
172
+ with warnings.
173
+
174
+ ```bash
175
+ MISSING=""
176
+ for SECTION in "## 1b. Domain Context" "## 2. Framework Selection" "## 3. Implementation" "## 5. Eval Dimensions" "## 6. Guardrails"; do
177
+ if ! grep -qF "$SECTION" "$AI_SPEC_PATH"; then
178
+ MISSING="$MISSING\n - missing: $SECTION"
179
+ fi
180
+ done
181
+
182
+ if [[ -n "$MISSING" ]]; then
183
+ CHOICE=$(node np-tools.cjs askuser --json '{
184
+ "type": "select",
185
+ "header": "AI-SPEC validation — missing sections",
186
+ "question": "Some required AI-SPEC sections are missing. What would you like to do?",
187
+ "options": [
188
+ {"label": "Re-run eval planner", "description": "Spawn np-eval-planner once more with current AI-SPEC state."},
189
+ {"label": "Accept with warnings", "description": "Proceed and surface the gaps in the commit message."},
190
+ {"label": "Abort", "description": "Exit without committing."}
191
+ ]
192
+ }')
193
+ case "$CHOICE" in
194
+ "Abort") exit 1 ;;
195
+ esac
196
+ fi
197
+ ```
198
+
199
+ ## Commit
200
+
201
+ ```bash
202
+ git add "$AI_SPEC_PATH"
203
+ git commit -m "docs(${PADDED}): generate AI-SPEC.md via 4-agent chain"
204
+ ```
205
+
206
+ ## Scope Guardrail
207
+
208
+ <scope_guardrail>
209
+ **Do:**
210
+ - Run the 4 agents in strict serial order (selector → ai → domain → eval).
211
+ - Emit a metrics record AFTER every Task spawn (D-06).
212
+ - Resolve every MODEL via `np-tools.cjs resolve-model` — no hardcoded IDs.
213
+ - Use `np-tools.cjs askuser` for every prompt (Phase-3 INST-03 invariant).
214
+ - Commit the final AI-SPEC.md when validation passes or the user accepts.
215
+
216
+ **Don't:**
217
+ - Invoke the host-specific prompt tool directly — always route through `np-tools.cjs askuser`.
218
+ - Parallelize the chain — later agents read what earlier agents wrote.
219
+ - Call any tools binary other than `np-tools.cjs` (the sole CLI entry per Plan 09-05 D-14).
220
+ - Reference legacy homedir payload paths — those directories do not exist
221
+ in nubos-pilot projects.
222
+ - Skip any metrics record block — the Phase-10 np:stats consumer expects
223
+ one record per Task spawn.
224
+ </scope_guardrail>
225
+
226
+ ## Output
227
+
228
+ - `{phase_dir}/{padded}-AI-SPEC.md` — filled-in AI design contract
229
+ - 4 metrics records in `.nubos-pilot/metrics/phase-${PHASE}.jsonl`
230
+ - One git commit (when validation passes or user accepts with warnings)
@@ -0,0 +1,94 @@
1
+ ---
2
+ command: np:autonomous
3
+ description: In-session auto-advance loop — runs phase after phase, asking the user between phases. NOT a daemon (ADR-0001 No-Daemon Invariant).
4
+ ---
5
+
6
+ # /np:autonomous
7
+
8
+ <objective>
9
+ Iterate `resolveGate` → `/np:execute-phase` → `/np:verify-work` → askUser
10
+ until the user says stop. The loop is an in-session for-loop executed by
11
+ the host agent — it does NOT fork, spawn a detached process, or survive
12
+ session end (ADR-0001).
13
+ </objective>
14
+
15
+ ## Initialize
16
+
17
+ ```bash
18
+ PHASE="$1"
19
+ GATE_JSON=$(node np-tools.cjs init autonomous "$PHASE")
20
+ ```
21
+
22
+ Parse: `status` (ok | advancement-blocked), `gate.rule`, `gate.task`.
23
+
24
+ If `status == advancement-blocked`:
25
+
26
+ ```bash
27
+ CHOICE=$(node np-tools.cjs askuser --json '{
28
+ "type": "select",
29
+ "header": "Phase kann nicht fortschreiten",
30
+ "question": "Jede Pending-Task in Wave 1 ist skipped oder parked. Was tun?",
31
+ "options": [
32
+ {"label": "Unpark tasks and continue", "description": "Dispatch /np:unpark zur Reaktivierung, dann erneut /np:autonomous starten."},
33
+ {"label": "Abort", "description": "Exit; User entscheidet manuell."}
34
+ ]
35
+ }')
36
+ exit 0
37
+ ```
38
+
39
+ ## Execution — in-session loop (NO daemon, NO background)
40
+
41
+ The loop runs inside the agent context. Each iteration:
42
+
43
+ 1. Re-read gate for `$PHASE`: `node np-tools.cjs init autonomous "$PHASE"`.
44
+ 2. If `gate.rule == 1` → dispatch `/np:discuss-phase $PHASE`, wait for
45
+ completion, continue.
46
+ 3. If `gate.rule == 2` → dispatch `/np:plan-phase $PHASE`, wait, continue.
47
+ 4. If `gate.rule == 3` → dispatch `/np:execute-phase $PHASE`, wait, continue.
48
+ 5. If `gate.rule == 4` → dispatch `/np:verify-work $PHASE`, wait, continue.
49
+ 6. If `gate.rule == 5` → phase verified. Ask the user:
50
+
51
+ ```bash
52
+ CHOICE=$(node np-tools.cjs askuser --json '{
53
+ "type": "select",
54
+ "header": "Phase verifiziert",
55
+ "question": "Phase '"$PHASE"' ist verified. Weitermachen mit der nächsten Phase?",
56
+ "options": [
57
+ {"label": "Continue", "description": "Nächste Phase automatisch starten."},
58
+ {"label": "Pause", "description": "Stop, handoff via /np:pause-work."},
59
+ {"label": "Abort", "description": "Exit."}
60
+ ]
61
+ }')
62
+ case "$CHOICE" in
63
+ "Continue") PHASE=$((PHASE + 1));;
64
+ "Pause") node np-tools.cjs init pause-work; exit 0 ;;
65
+ "Abort") exit 0 ;;
66
+ esac
67
+ ```
68
+
69
+ ## Hard-stops (D-15)
70
+
71
+ - Any `NubosPilotError` with severity fatal → exit non-zero immediately.
72
+ - commit-task loud-fails (gitignored-only) → propagate, do not retry.
73
+ - `advancement-blocked` → askUser gate above, never silent spin.
74
+
75
+ ## Scope Guardrail
76
+
77
+ <!-- scope_guardrail -->
78
+ **Do:**
79
+ - Drive the loop inside the current agent session.
80
+ - Ask the user at every phase boundary (D-14).
81
+ - Respect `resolveGate` outputs verbatim.
82
+
83
+ **Don't:**
84
+ - Fork, `setInterval`, `nohup`, `spawn --detached`, or `&` the loop body —
85
+ ADR-0001 bans daemon behavior (T-06-14).
86
+ - Skip the inter-phase askUser gate.
87
+ - Silently advance past a Fail verdict from `/np:verify-work`.
88
+ <!-- /scope_guardrail -->
89
+
90
+ ## Output
91
+
92
+ - N phases advanced (N = user choice count).
93
+ - Full audit trail in per-phase `PLAN-REVIEW.md` + `VERIFICATION.md`.
94
+ - Session ends cleanly; resume via `/np:resume-work`.
@@ -0,0 +1,325 @@
1
+ ---
2
+ command: np:cleanup
3
+ description: Archive completed milestones to .nubos-pilot/archive/v<X.Y>/ (D-06). Collapses each archived milestone's ROADMAP.md block into a <details> summary (D-07). Idempotent per D-08 (target-path existence check). One atomic chore commit (D-09). Path-preserving git mv (rename history kept).
4
+ ---
5
+
6
+ # np:cleanup
7
+
8
+ Implements UTIL-08. Enumerates milestones whose every phase is marked
9
+ complete, previews the archive plan, moves phase directories into
10
+ `.nubos-pilot/archive/v<X.Y>/` with `git mv` (preserving rename
11
+ history), collapses the milestone's ROADMAP.md block into a
12
+ `<details>` wrapper via `lib/roadmap.cjs.collapseMilestone` (landed
13
+ Plan 10-01-T05), and commits the whole operation as a single atomic
14
+ chore unit.
15
+
16
+ Four deliberate design choices:
17
+
18
+ - **D-06** — archive layout lives at `.nubos-pilot/archive/v<X.Y>/`,
19
+ the canonical home under the project-state tree (ADR-0005
20
+ 3-file-tree invariant).
21
+ - **D-07** — collapsed milestones render as `<details>` blocks in
22
+ the generated ROADMAP.md rather than being deleted. Milestone
23
+ history stays visible on demand.
24
+ - **D-08** — idempotency. If `archive/v<X.Y>/` already exists for a
25
+ milestone, the workflow skips it. Partial-move recovery is safe
26
+ because per-milestone state is all-or-nothing.
27
+ - **D-09** — single atomic commit per run. All milestones archived
28
+ in one invocation land in one commit (ADR-0004).
29
+
30
+ Security-relevant defenses built in:
31
+
32
+ - **T-10-06-03 (partial-move)** — idempotent skip + per-mv failure
33
+ abort + single commit after ALL mvs succeed.
34
+ - **T-10-06-04 (symlink traversal)** — `lstat`-based reject before
35
+ every `git mv` (pattern reused from Phase 7 Plan 07-02 backup.cjs).
36
+
37
+ Pure CRUD / filesystem workflow — no agent spawn, no resolve-model,
38
+ no metrics record. Pitfall 9 / `workflow-missing-metrics` is exempt.
39
+
40
+ ## Initialize
41
+
42
+ ```bash
43
+ STATE_DIR=$(node -e "console.log(require('./lib/core.cjs').projectStateDir(process.cwd()))")
44
+ ARCHIVE_ROOT="${STATE_DIR}/archive"
45
+ mkdir -p "$ARCHIVE_ROOT"
46
+
47
+ DRY_RUN_FLAG=""
48
+ for arg in "$@"; do
49
+ case "$arg" in
50
+ --dry-run) DRY_RUN_FLAG="1" ;;
51
+ esac
52
+ done
53
+ ```
54
+
55
+ ## Enumerate Completed Milestones
56
+
57
+ All enumeration happens in Node so we can consume `lib/roadmap.cjs`
58
+ parse output directly (not re-implement YAML parsing). The query
59
+ filters: (a) skip the synthetic `backlog` milestone, (b) require
60
+ every phase in the milestone to have `status === "complete"`, (c)
61
+ skip if `archive/<id>/` already exists (D-08 idempotency), (d) list
62
+ the phase directories that exist on disk.
63
+
64
+ ```bash
65
+ ACTIONS_JSON=$(node -e '
66
+ const { parseRoadmap } = require("./lib/roadmap.cjs");
67
+ const fs = require("node:fs");
68
+ const path = require("node:path");
69
+ const stateDir = process.argv[1];
70
+ const archive = path.join(stateDir, "archive");
71
+ const phasesRoot = path.join(stateDir, "phases");
72
+ const parsed = parseRoadmap(process.cwd());
73
+ const raw = parsed && parsed.doc ? parsed.doc : null;
74
+ const out = [];
75
+ for (const m of (raw && raw.milestones) || []) {
76
+ if (!m || m.id === "backlog") continue;
77
+ const phases = m.phases || [];
78
+ if (phases.length === 0) continue;
79
+ const allDone = phases.every((ph) => ph && ph.status === "complete");
80
+ if (!allDone) continue;
81
+ const target = path.join(archive, m.id);
82
+ if (fs.existsSync(target)) continue;
83
+ let entries = [];
84
+ try { entries = fs.readdirSync(phasesRoot, { withFileTypes: true }); } catch (_e) {}
85
+ const phaseDirs = [];
86
+ for (const ph of phases) {
87
+ const padded = String(ph.number).padStart(2, "0");
88
+ const match = entries.filter((e) => e.isDirectory())
89
+ .map((e) => e.name)
90
+ .find((n) => n === padded || n.startsWith(padded + "-"));
91
+ if (match) phaseDirs.push(path.join(phasesRoot, match));
92
+ }
93
+ if (phaseDirs.length === 0) continue;
94
+ out.push({ milestone_id: m.id, target_dir: target, phase_dirs: phaseDirs });
95
+ }
96
+ process.stdout.write(JSON.stringify(out));
97
+ ' "$STATE_DIR")
98
+
99
+ ACTION_COUNT=$(echo "$ACTIONS_JSON" | jq 'length')
100
+ if [[ "$ACTION_COUNT" -eq 0 ]]; then
101
+ echo "Nothing to archive (no complete + un-archived milestones)."
102
+ exit 0
103
+ fi
104
+ ```
105
+
106
+ ## Symlink Safety Check (T-10-06-04)
107
+
108
+ Before any `git mv`, `lstat` each phase directory and reject
109
+ symlinks. A symlinked phase dir would escape the archive root on
110
+ rename (the link target might point outside `.nubos-pilot/phases/`).
111
+ Pattern reused from Phase 7 Plan 07-02 `backup.cjs`.
112
+
113
+ ```bash
114
+ SYMLINK_FOUND=$(node -e '
115
+ const fs = require("node:fs");
116
+ const actions = JSON.parse(process.argv[1]);
117
+ for (const a of actions) {
118
+ for (const d of a.phase_dirs) {
119
+ try {
120
+ const st = fs.lstatSync(d);
121
+ if (st.isSymbolicLink()) { process.stdout.write(d); process.exit(0); }
122
+ } catch (_e) {}
123
+ }
124
+ }
125
+ ' "$ACTIONS_JSON")
126
+ if [[ -n "$SYMLINK_FOUND" ]]; then
127
+ echo "Error: symlink detected at $SYMLINK_FOUND — refusing to archive. Remove or unlink and retry." >&2
128
+ exit 1
129
+ fi
130
+ ```
131
+
132
+ ## Dry-Run Summary + askuser Confirm
133
+
134
+ Show the plan before mutating anything. `--dry-run` exits after the
135
+ preview; otherwise confirm via `askuser` Pattern S-3.
136
+
137
+ ```bash
138
+ echo "$ACTIONS_JSON" | jq -r '.[] | "Milestone " + .milestone_id + ":\n → " + .target_dir + "\n Phases: " + ([.phase_dirs[] | split("/") | last] | join(", "))'
139
+
140
+ if [[ -n "$DRY_RUN_FLAG" ]]; then
141
+ echo "Dry-run: no changes made."
142
+ exit 0
143
+ fi
144
+
145
+ CHOICE=$(node np-tools.cjs askuser --json '{
146
+ "type": "select",
147
+ "header": "Cleanup Preview",
148
+ "question": "Archive '"$ACTION_COUNT"' milestone(s) listed above?",
149
+ "options": [
150
+ {"label": "Yes — archive listed phases", "description": "Creates .nubos-pilot/archive/<id>/ + ROADMAP details-block + 1 atomic commit"},
151
+ {"label": "Cancel", "description": "Exits without mutation"}
152
+ ]
153
+ }')
154
+ case "$CHOICE" in
155
+ "Cancel"*) exit 0 ;;
156
+ esac
157
+ ```
158
+
159
+ ## Execute Archive
160
+
161
+ Per-milestone loop: create the archive target dir, re-`lstat` every
162
+ phase directory IMMEDIATELY before each `git mv` and abort if a
163
+ symlink is detected, then call `lib/roadmap.cjs.collapseMilestone`
164
+ to flip the `collapsed: true` flag so `lib/roadmap-render.cjs` wraps
165
+ the block in a `<details>` summary (landed Plan 10-01-T05).
166
+
167
+ The symlink re-check is intentionally collocated with `git mv`
168
+ inside ONE Node process (no intermediate bash fork) so there is no
169
+ TOCTOU window between check and move. The earlier "Symlink Safety
170
+ Check" section is a defense-in-depth pre-flight that fails fast
171
+ before the askuser confirm; this block is the authoritative
172
+ just-in-time guard (T-10-06-04 mitigation).
173
+
174
+ ```bash
175
+ node -e '
176
+ const fs = require("node:fs");
177
+ const { execFileSync } = require("node:child_process");
178
+ const { collapseMilestone } = require("./lib/roadmap.cjs");
179
+ const actions = JSON.parse(process.argv[1]);
180
+ for (const a of actions) {
181
+ fs.mkdirSync(a.target_dir, { recursive: true });
182
+ for (const src of a.phase_dirs) {
183
+ const st = fs.lstatSync(src);
184
+ if (st.isSymbolicLink()) {
185
+ process.stderr.write("Error: symlink detected at " + src + " — refusing to archive.\n");
186
+ process.exit(1);
187
+ }
188
+ try {
189
+ execFileSync("git", ["mv", src, a.target_dir + "/"], { stdio: "inherit" });
190
+ } catch (err) {
191
+ process.stderr.write("git mv failed on " + src + "\n");
192
+ process.exit(1);
193
+ }
194
+ }
195
+ collapseMilestone(a.milestone_id);
196
+ }
197
+ ' "$ACTIONS_JSON"
198
+ ```
199
+
200
+ `git mv` preserves rename history (per Plan §interfaces / Research
201
+ §4) — later blame/log on the archived files still traces back
202
+ through the rename.
203
+
204
+ ## Commit (D-09)
205
+
206
+ Single atomic commit per run. All milestones archived together land
207
+ in one `chore(10): ...` commit. Subject line names the first
208
+ milestone and a count suffix when N > 1.
209
+
210
+ ```bash
211
+ FIRST_MILESTONE=$(echo "$ACTIONS_JSON" | jq -r '.[0].milestone_id')
212
+ SUFFIX=""
213
+ if [[ "$ACTION_COUNT" -gt 1 ]]; then
214
+ SUFFIX=" and $((ACTION_COUNT - 1)) other(s)"
215
+ fi
216
+ node np-tools.cjs commit "chore(10): archive milestone ${FIRST_MILESTONE}${SUFFIX}" \
217
+ --files "${STATE_DIR}/archive/" "${STATE_DIR}/ROADMAP.md" "${STATE_DIR}/roadmap.yaml" "${STATE_DIR}/phases/"
218
+ ```
219
+
220
+ ## If a cleanup is interrupted
221
+
222
+ If `git mv` fails mid-operation (power loss, Ctrl-C), the working
223
+ tree may be in a mixed state — some phase dirs moved, some not.
224
+ Running `/np:cleanup` again is SAFE because:
225
+
226
+ - **Per-milestone idempotency (D-08):** milestones whose archive
227
+ target dir already exists are skipped.
228
+ - Within a milestone, `git mv` on a non-existent source fails
229
+ loudly; the workflow aborts WITHOUT committing.
230
+
231
+ If you see a partial state, either:
232
+
233
+ 1. Run `/np:cleanup` again — re-processes only the un-archived
234
+ milestones.
235
+ 2. Manually `git mv` the remaining phase dirs into their archive
236
+ target, then run
237
+ `node -e "require('./lib/roadmap.cjs').collapseMilestone('v<X.Y>')"`
238
+ and commit manually.
239
+
240
+ No automatic `git reset --hard HEAD` is performed — that would
241
+ destroy unrelated uncommitted work (Open Question #3 recommendation,
242
+ T-10-06-03 mitigation).
243
+
244
+ ## Scope Guardrail
245
+
246
+ <scope_guardrail>
247
+ **Do:**
248
+ - Use `git mv` (not plain `mv`) — preserves rename history for
249
+ `git blame` / `git log --follow`.
250
+ - Check target-dir existence BEFORE attempting mvs (D-08
251
+ idempotency).
252
+ - Reject symlinks via `lstat` before every `git mv` (T-10-06-04).
253
+ - Single atomic commit after ALL mvs + `collapseMilestone` mutations
254
+ succeed (D-09).
255
+ - Show the dry-run preview before any mutation (S-3 pattern).
256
+
257
+ **Don't:**
258
+ - `git reset --hard` on failure (destroys unrelated user work per
259
+ Open Question #3 / T-10-06-03).
260
+ - Mutate archive targets that already exist (idempotency).
261
+ - Skip the `collapseMilestone` call — ROADMAP must reflect the
262
+ archive state (D-07).
263
+ - Invoke host-specific prompt tools directly (the BARE_ASKUSER lint
264
+ in `bin/check-workflows.cjs` blocks them) — route through
265
+ `node np-tools.cjs askuser --json '…'`.
266
+ - Add a `metrics record` block. No Task/Spawn site; Pitfall 9 /
267
+ `workflow-missing-metrics` is exempt.
268
+ </scope_guardrail>
269
+
270
+ ## Output
271
+
272
+ - `.nubos-pilot/archive/v<X.Y>/` — one directory per archived
273
+ milestone containing the milestone's phase dirs (moved via
274
+ `git mv`, rename history preserved).
275
+ - `.nubos-pilot/ROADMAP.md` — regenerated with `<details>` wrapper
276
+ around each archived milestone's block (D-07 via
277
+ `lib/roadmap.cjs.collapseMilestone`).
278
+ - `.nubos-pilot/roadmap.yaml` — `collapsed: true` and `collapsed_at`
279
+ set for each archived milestone.
280
+ - `.nubos-pilot/phases/` — archived phase dirs removed.
281
+ - One atomic git commit
282
+ `chore(10): archive milestone <id> [and N other(s)]` (ADR-0004,
283
+ D-09).
284
+
285
+ ## Success Criteria
286
+
287
+ - [ ] Synthetic `backlog` milestone is never archived.
288
+ - [ ] Only milestones with `every phase status === "complete"` are
289
+ considered.
290
+ - [ ] Milestones whose archive target already exists are skipped
291
+ (D-08 idempotency).
292
+ - [ ] Symlinks in candidate phase dirs abort the workflow
293
+ (T-10-06-04 mitigation).
294
+ - [ ] `--dry-run` exits after the preview with no mutation.
295
+ - [ ] Confirmation via `askuser` Pattern S-3 before any mutation.
296
+ - [ ] Phase dirs moved via `git mv` (preserves rename history).
297
+ - [ ] `lib/roadmap.cjs.collapseMilestone` called for every archived
298
+ milestone (D-07).
299
+ - [ ] Single atomic commit via `np-tools.cjs commit` after ALL mvs +
300
+ collapseMilestone calls succeed (D-09).
301
+ - [ ] Commit subject shape `chore(10): archive milestone <id>
302
+ [and N other(s)]`.
303
+ - [ ] Partial-move recovery is idempotent — re-running picks up
304
+ only un-archived milestones (T-10-06-03 mitigation).
305
+ - [ ] Lint clean under `bin/check-workflows.cjs` — no BARE_ASKUSER
306
+ violations, no DIRECT_READ matches.
307
+
308
+ ## Related Workflows
309
+
310
+ - **`/np:session-report`** — per-session snapshot report (distinct
311
+ from milestone archival).
312
+ - **`/np:stats`** — read-only current-state view.
313
+ - **`/np:add-backlog <title>`** — adds ideas to the synthetic
314
+ backlog milestone (which `/np:cleanup` deliberately never
315
+ archives).
316
+
317
+ ## Design Notes
318
+
319
+ D-06 fixes archive layout at `.nubos-pilot/archive/v<X.Y>/` — the
320
+ canonical home under the project-state tree (ADR-0005). D-07 collapse
321
+ renders archived milestones as `<details>` blocks instead of deleting
322
+ them so history stays visible on demand. D-08 idempotency via
323
+ existence-check enables safe re-runs. D-09 single-commit shape matches
324
+ ADR-0004. Symlink rejection via `lstat` (T-10-06-04) and path-preserving
325
+ `git mv` guard against filesystem-level surprises.