@openrig/cli 0.3.4 → 0.4.1

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 (300) hide show
  1. package/daemon/assets/plugins/openrig-core/hooks/claude.json +11 -0
  2. package/daemon/assets/plugins/openrig-core/hooks/codex.json +14 -3
  3. package/daemon/assets/plugins/openrig-core/hooks/scripts/activity-relay.cjs +34 -0
  4. package/daemon/assets/plugins/openrig-core/hooks/scripts/compaction-restore-bridge.cjs +28 -24
  5. package/daemon/assets/plugins/openrig-core/skills/claude-compaction-restore/scripts/precompact-hook.mjs +72 -11
  6. package/daemon/assets/plugins/openrig-core/skills/queue-handoff/SKILL.md +62 -29
  7. package/daemon/dist/adapters/codex-resume.d.ts +2 -1
  8. package/daemon/dist/adapters/codex-resume.d.ts.map +1 -1
  9. package/daemon/dist/adapters/codex-resume.js +18 -6
  10. package/daemon/dist/adapters/codex-resume.js.map +1 -1
  11. package/daemon/dist/adapters/codex-runtime-adapter.d.ts +37 -1
  12. package/daemon/dist/adapters/codex-runtime-adapter.d.ts.map +1 -1
  13. package/daemon/dist/adapters/codex-runtime-adapter.js +212 -6
  14. package/daemon/dist/adapters/codex-runtime-adapter.js.map +1 -1
  15. package/daemon/dist/adapters/tmux.d.ts +28 -14
  16. package/daemon/dist/adapters/tmux.d.ts.map +1 -1
  17. package/daemon/dist/adapters/tmux.js +65 -30
  18. package/daemon/dist/adapters/tmux.js.map +1 -1
  19. package/daemon/dist/db/migrations/043_resume_provenance.d.ts +3 -0
  20. package/daemon/dist/db/migrations/043_resume_provenance.d.ts.map +1 -0
  21. package/daemon/dist/db/migrations/043_resume_provenance.js +5 -0
  22. package/daemon/dist/db/migrations/043_resume_provenance.js.map +1 -0
  23. package/daemon/dist/db/migrations/044_queue_item_summary.d.ts +21 -0
  24. package/daemon/dist/db/migrations/044_queue_item_summary.d.ts.map +1 -0
  25. package/daemon/dist/db/migrations/044_queue_item_summary.js +25 -0
  26. package/daemon/dist/db/migrations/044_queue_item_summary.js.map +1 -0
  27. package/daemon/dist/domain/agent-activity-store.d.ts +11 -1
  28. package/daemon/dist/domain/agent-activity-store.d.ts.map +1 -1
  29. package/daemon/dist/domain/agent-activity-store.js +22 -2
  30. package/daemon/dist/domain/agent-activity-store.js.map +1 -1
  31. package/daemon/dist/domain/claude-compaction-enforcer.d.ts.map +1 -1
  32. package/daemon/dist/domain/claude-compaction-enforcer.js +69 -1
  33. package/daemon/dist/domain/claude-compaction-enforcer.js.map +1 -1
  34. package/daemon/dist/domain/daemon-version.d.ts +15 -0
  35. package/daemon/dist/domain/daemon-version.d.ts.map +1 -0
  36. package/daemon/dist/domain/daemon-version.js +28 -0
  37. package/daemon/dist/domain/daemon-version.js.map +1 -0
  38. package/daemon/dist/domain/native-resume-probe.d.ts +2 -1
  39. package/daemon/dist/domain/native-resume-probe.d.ts.map +1 -1
  40. package/daemon/dist/domain/native-resume-probe.js +21 -2
  41. package/daemon/dist/domain/native-resume-probe.js.map +1 -1
  42. package/daemon/dist/domain/node-cmux-service.d.ts +1 -0
  43. package/daemon/dist/domain/node-cmux-service.d.ts.map +1 -1
  44. package/daemon/dist/domain/node-cmux-service.js +29 -2
  45. package/daemon/dist/domain/node-cmux-service.js.map +1 -1
  46. package/daemon/dist/domain/node-inventory.d.ts.map +1 -1
  47. package/daemon/dist/domain/node-inventory.js +70 -46
  48. package/daemon/dist/domain/node-inventory.js.map +1 -1
  49. package/daemon/dist/domain/node-launcher.d.ts +7 -7
  50. package/daemon/dist/domain/node-launcher.d.ts.map +1 -1
  51. package/daemon/dist/domain/node-launcher.js +0 -22
  52. package/daemon/dist/domain/node-launcher.js.map +1 -1
  53. package/daemon/dist/domain/profile-resolver.d.ts +4 -5
  54. package/daemon/dist/domain/profile-resolver.d.ts.map +1 -1
  55. package/daemon/dist/domain/profile-resolver.js.map +1 -1
  56. package/daemon/dist/domain/queue-repository.d.ts +22 -0
  57. package/daemon/dist/domain/queue-repository.d.ts.map +1 -1
  58. package/daemon/dist/domain/queue-repository.js +39 -2
  59. package/daemon/dist/domain/queue-repository.js.map +1 -1
  60. package/daemon/dist/domain/restore-check-service.d.ts +14 -0
  61. package/daemon/dist/domain/restore-check-service.d.ts.map +1 -1
  62. package/daemon/dist/domain/restore-check-service.js +78 -8
  63. package/daemon/dist/domain/restore-check-service.js.map +1 -1
  64. package/daemon/dist/domain/restore-orchestrator.d.ts.map +1 -1
  65. package/daemon/dist/domain/restore-orchestrator.js +3 -3
  66. package/daemon/dist/domain/restore-orchestrator.js.map +1 -1
  67. package/daemon/dist/domain/resume-metadata-refresher.js +1 -1
  68. package/daemon/dist/domain/resume-metadata-refresher.js.map +1 -1
  69. package/daemon/dist/domain/resume-token-validation.d.ts +17 -0
  70. package/daemon/dist/domain/resume-token-validation.d.ts.map +1 -0
  71. package/daemon/dist/domain/resume-token-validation.js +46 -0
  72. package/daemon/dist/domain/resume-token-validation.js.map +1 -0
  73. package/daemon/dist/domain/rigspec-instantiator.d.ts.map +1 -1
  74. package/daemon/dist/domain/rigspec-instantiator.js +3 -6
  75. package/daemon/dist/domain/rigspec-instantiator.js.map +1 -1
  76. package/daemon/dist/domain/runtime-adapter.d.ts.map +1 -1
  77. package/daemon/dist/domain/runtime-adapter.js +1 -0
  78. package/daemon/dist/domain/runtime-adapter.js.map +1 -1
  79. package/daemon/dist/domain/scope/dot-id.d.ts +43 -0
  80. package/daemon/dist/domain/scope/dot-id.d.ts.map +1 -0
  81. package/daemon/dist/domain/scope/dot-id.js +165 -0
  82. package/daemon/dist/domain/scope/dot-id.js.map +1 -0
  83. package/daemon/dist/domain/scope/scope-audit.d.ts +38 -0
  84. package/daemon/dist/domain/scope/scope-audit.d.ts.map +1 -0
  85. package/daemon/dist/domain/scope/scope-audit.js +191 -0
  86. package/daemon/dist/domain/scope/scope-audit.js.map +1 -0
  87. package/daemon/dist/domain/scope/types.d.ts +67 -0
  88. package/daemon/dist/domain/scope/types.d.ts.map +1 -0
  89. package/daemon/dist/domain/scope/types.js +46 -0
  90. package/daemon/dist/domain/scope/types.js.map +1 -0
  91. package/daemon/dist/domain/seat-attention-reconciler.d.ts +12 -0
  92. package/daemon/dist/domain/seat-attention-reconciler.d.ts.map +1 -1
  93. package/daemon/dist/domain/seat-attention-reconciler.js +116 -88
  94. package/daemon/dist/domain/seat-attention-reconciler.js.map +1 -1
  95. package/daemon/dist/domain/session-registry.d.ts +15 -1
  96. package/daemon/dist/domain/session-registry.d.ts.map +1 -1
  97. package/daemon/dist/domain/session-registry.js +46 -4
  98. package/daemon/dist/domain/session-registry.js.map +1 -1
  99. package/daemon/dist/domain/session-transport.d.ts +10 -0
  100. package/daemon/dist/domain/session-transport.d.ts.map +1 -1
  101. package/daemon/dist/domain/session-transport.js +153 -19
  102. package/daemon/dist/domain/session-transport.js.map +1 -1
  103. package/daemon/dist/domain/skill-audit.d.ts +47 -0
  104. package/daemon/dist/domain/skill-audit.d.ts.map +1 -0
  105. package/daemon/dist/domain/skill-audit.js +218 -0
  106. package/daemon/dist/domain/skill-audit.js.map +1 -0
  107. package/daemon/dist/domain/skill-discovery.d.ts +15 -0
  108. package/daemon/dist/domain/skill-discovery.d.ts.map +1 -1
  109. package/daemon/dist/domain/skill-discovery.js +70 -0
  110. package/daemon/dist/domain/skill-discovery.js.map +1 -1
  111. package/daemon/dist/domain/skill-mirror-drift.d.ts +10 -0
  112. package/daemon/dist/domain/skill-mirror-drift.d.ts.map +1 -0
  113. package/daemon/dist/domain/skill-mirror-drift.js +24 -0
  114. package/daemon/dist/domain/skill-mirror-drift.js.map +1 -0
  115. package/daemon/dist/domain/slices/slice-detail-projector.d.ts.map +1 -1
  116. package/daemon/dist/domain/slices/slice-detail-projector.js +10 -3
  117. package/daemon/dist/domain/slices/slice-detail-projector.js.map +1 -1
  118. package/daemon/dist/domain/startup-orchestrator.js +1 -1
  119. package/daemon/dist/domain/startup-orchestrator.js.map +1 -1
  120. package/daemon/dist/domain/types.d.ts +48 -14
  121. package/daemon/dist/domain/types.d.ts.map +1 -1
  122. package/daemon/dist/domain/types.js.map +1 -1
  123. package/daemon/dist/domain/user-settings/settings-store.d.ts +1 -1
  124. package/daemon/dist/domain/user-settings/settings-store.d.ts.map +1 -1
  125. package/daemon/dist/domain/user-settings/settings-store.js +4 -0
  126. package/daemon/dist/domain/user-settings/settings-store.js.map +1 -1
  127. package/daemon/dist/domain/whoami-service.d.ts +1 -0
  128. package/daemon/dist/domain/whoami-service.d.ts.map +1 -1
  129. package/daemon/dist/domain/whoami-service.js +8 -4
  130. package/daemon/dist/domain/whoami-service.js.map +1 -1
  131. package/daemon/dist/domain/workspace/default-workspace-scaffold.d.ts +1 -0
  132. package/daemon/dist/domain/workspace/default-workspace-scaffold.d.ts.map +1 -1
  133. package/daemon/dist/domain/workspace/default-workspace-scaffold.js +25 -1
  134. package/daemon/dist/domain/workspace/default-workspace-scaffold.js.map +1 -1
  135. package/daemon/dist/index.d.ts.map +1 -1
  136. package/daemon/dist/index.js +25 -3
  137. package/daemon/dist/index.js.map +1 -1
  138. package/daemon/dist/routes/activity.d.ts.map +1 -1
  139. package/daemon/dist/routes/activity.js +29 -0
  140. package/daemon/dist/routes/activity.js.map +1 -1
  141. package/daemon/dist/routes/bundles.d.ts.map +1 -1
  142. package/daemon/dist/routes/bundles.js +1 -18
  143. package/daemon/dist/routes/bundles.js.map +1 -1
  144. package/daemon/dist/routes/health-summary.d.ts.map +1 -1
  145. package/daemon/dist/routes/health-summary.js +13 -2
  146. package/daemon/dist/routes/health-summary.js.map +1 -1
  147. package/daemon/dist/routes/queue.d.ts.map +1 -1
  148. package/daemon/dist/routes/queue.js +11 -0
  149. package/daemon/dist/routes/queue.js.map +1 -1
  150. package/daemon/dist/routes/restore-check.js +33 -2
  151. package/daemon/dist/routes/restore-check.js.map +1 -1
  152. package/daemon/dist/routes/scope-audit.d.ts +3 -0
  153. package/daemon/dist/routes/scope-audit.d.ts.map +1 -0
  154. package/daemon/dist/routes/scope-audit.js +175 -0
  155. package/daemon/dist/routes/scope-audit.js.map +1 -0
  156. package/daemon/dist/routes/sessions.d.ts.map +1 -1
  157. package/daemon/dist/routes/sessions.js +67 -3
  158. package/daemon/dist/routes/sessions.js.map +1 -1
  159. package/daemon/dist/routes/skills.d.ts.map +1 -1
  160. package/daemon/dist/routes/skills.js +43 -0
  161. package/daemon/dist/routes/skills.js.map +1 -1
  162. package/daemon/dist/routes/terminal-ws.d.ts +7 -0
  163. package/daemon/dist/routes/terminal-ws.d.ts.map +1 -0
  164. package/daemon/dist/routes/terminal-ws.js +132 -0
  165. package/daemon/dist/routes/terminal-ws.js.map +1 -0
  166. package/daemon/dist/routes/transport.d.ts +3 -1
  167. package/daemon/dist/routes/transport.d.ts.map +1 -1
  168. package/daemon/dist/routes/transport.js +26 -1
  169. package/daemon/dist/routes/transport.js.map +1 -1
  170. package/daemon/dist/routes/whoami.d.ts.map +1 -1
  171. package/daemon/dist/routes/whoami.js +5 -0
  172. package/daemon/dist/routes/whoami.js.map +1 -1
  173. package/daemon/dist/server.d.ts +6 -0
  174. package/daemon/dist/server.d.ts.map +1 -1
  175. package/daemon/dist/server.js +29 -2
  176. package/daemon/dist/server.js.map +1 -1
  177. package/daemon/dist/startup.d.ts +8 -0
  178. package/daemon/dist/startup.d.ts.map +1 -1
  179. package/daemon/dist/startup.js +41 -10
  180. package/daemon/dist/startup.js.map +1 -1
  181. package/daemon/dist/terminal/TerminalSessionBroker.d.ts +173 -0
  182. package/daemon/dist/terminal/TerminalSessionBroker.d.ts.map +1 -0
  183. package/daemon/dist/terminal/TerminalSessionBroker.js +570 -0
  184. package/daemon/dist/terminal/TerminalSessionBroker.js.map +1 -0
  185. package/daemon/specs/agents/shared/skills/core/openrig-user/SKILL.md +131 -2
  186. package/daemon/specs/agents/shared/skills/pods/orchestration-team/SKILL.md +29 -0
  187. package/dist/client.d.ts +1 -0
  188. package/dist/client.d.ts.map +1 -1
  189. package/dist/client.js +23 -0
  190. package/dist/client.js.map +1 -1
  191. package/dist/commands/auth.d.ts +11 -0
  192. package/dist/commands/auth.d.ts.map +1 -0
  193. package/dist/commands/auth.js +145 -0
  194. package/dist/commands/auth.js.map +1 -0
  195. package/dist/commands/capture.d.ts.map +1 -1
  196. package/dist/commands/capture.js +6 -6
  197. package/dist/commands/capture.js.map +1 -1
  198. package/dist/commands/config-init-workspace.d.ts.map +1 -1
  199. package/dist/commands/config-init-workspace.js +7 -4
  200. package/dist/commands/config-init-workspace.js.map +1 -1
  201. package/dist/commands/context.d.ts.map +1 -1
  202. package/dist/commands/context.js +41 -5
  203. package/dist/commands/context.js.map +1 -1
  204. package/dist/commands/down.d.ts.map +1 -1
  205. package/dist/commands/down.js +27 -0
  206. package/dist/commands/down.js.map +1 -1
  207. package/dist/commands/launch.d.ts.map +1 -1
  208. package/dist/commands/launch.js +34 -0
  209. package/dist/commands/launch.js.map +1 -1
  210. package/dist/commands/ps.d.ts.map +1 -1
  211. package/dist/commands/ps.js +361 -53
  212. package/dist/commands/ps.js.map +1 -1
  213. package/dist/commands/queue.d.ts.map +1 -1
  214. package/dist/commands/queue.js +83 -3
  215. package/dist/commands/queue.js.map +1 -1
  216. package/dist/commands/restore-check.d.ts.map +1 -1
  217. package/dist/commands/restore-check.js +80 -2
  218. package/dist/commands/restore-check.js.map +1 -1
  219. package/dist/commands/scope.d.ts.map +1 -1
  220. package/dist/commands/scope.js +747 -5
  221. package/dist/commands/scope.js.map +1 -1
  222. package/dist/commands/seat.d.ts +3 -1
  223. package/dist/commands/seat.d.ts.map +1 -1
  224. package/dist/commands/seat.js +61 -1
  225. package/dist/commands/seat.js.map +1 -1
  226. package/dist/commands/send.d.ts.map +1 -1
  227. package/dist/commands/send.js +46 -13
  228. package/dist/commands/send.js.map +1 -1
  229. package/dist/commands/skill.d.ts +4 -0
  230. package/dist/commands/skill.d.ts.map +1 -0
  231. package/dist/commands/skill.js +77 -0
  232. package/dist/commands/skill.js.map +1 -0
  233. package/dist/commands/up.d.ts.map +1 -1
  234. package/dist/commands/up.js +27 -0
  235. package/dist/commands/up.js.map +1 -1
  236. package/dist/commands/whoami.d.ts.map +1 -1
  237. package/dist/commands/whoami.js +208 -20
  238. package/dist/commands/whoami.js.map +1 -1
  239. package/dist/cross-host-executor.d.ts +1 -1
  240. package/dist/cross-host-executor.d.ts.map +1 -1
  241. package/dist/cross-host-executor.js +3 -2
  242. package/dist/cross-host-executor.js.map +1 -1
  243. package/dist/cross-host-types.d.ts +2 -0
  244. package/dist/cross-host-types.d.ts.map +1 -0
  245. package/dist/cross-host-types.js +2 -0
  246. package/dist/cross-host-types.js.map +1 -0
  247. package/dist/host-registry.d.ts +27 -5
  248. package/dist/host-registry.d.ts.map +1 -1
  249. package/dist/host-registry.js +86 -19
  250. package/dist/host-registry.js.map +1 -1
  251. package/dist/index.d.ts +1 -0
  252. package/dist/index.d.ts.map +1 -1
  253. package/dist/index.js +4 -0
  254. package/dist/index.js.map +1 -1
  255. package/dist/lib/codex-auth.d.ts +124 -0
  256. package/dist/lib/codex-auth.d.ts.map +1 -0
  257. package/dist/lib/codex-auth.js +418 -0
  258. package/dist/lib/codex-auth.js.map +1 -0
  259. package/dist/lib/scope/progress-edit.d.ts +42 -0
  260. package/dist/lib/scope/progress-edit.d.ts.map +1 -0
  261. package/dist/lib/scope/progress-edit.js +171 -0
  262. package/dist/lib/scope/progress-edit.js.map +1 -0
  263. package/dist/lib/scope/scope-audit.d.ts +38 -0
  264. package/dist/lib/scope/scope-audit.d.ts.map +1 -0
  265. package/dist/lib/scope/scope-audit.js +191 -0
  266. package/dist/lib/scope/scope-audit.js.map +1 -0
  267. package/dist/lib/scope/templates.d.ts +11 -0
  268. package/dist/lib/scope/templates.d.ts.map +1 -1
  269. package/dist/lib/scope/templates.js +22 -1
  270. package/dist/lib/scope/templates.js.map +1 -1
  271. package/dist/lib/scope/trust.d.ts +30 -0
  272. package/dist/lib/scope/trust.d.ts.map +1 -0
  273. package/dist/lib/scope/trust.js +80 -0
  274. package/dist/lib/scope/trust.js.map +1 -0
  275. package/dist/lib/scope/types.d.ts +6 -0
  276. package/dist/lib/scope/types.d.ts.map +1 -1
  277. package/dist/lib/scope/types.js +8 -0
  278. package/dist/lib/scope/types.js.map +1 -1
  279. package/dist/lib/scope-templates/backlog-deprecation.md +1 -0
  280. package/dist/lib/scope-templates/backlog-tech-debt.md +1 -0
  281. package/dist/lib/scope-templates/bug-fix.md +1 -0
  282. package/dist/lib/scope-templates/mission-brief.md +20 -0
  283. package/dist/lib/scope-templates/mission-placeholder.md +1 -0
  284. package/dist/lib/scope-templates/mission-progress.md +12 -0
  285. package/dist/lib/scope-templates/mission-release.md +1 -0
  286. package/dist/lib/scope-templates/placeholder.md +9 -0
  287. package/dist/lib/scope-templates/proof.md +17 -0
  288. package/dist/lib/scope-templates/release-feature.md +1 -0
  289. package/dist/lib/scope-templates/research.md +1 -0
  290. package/dist/lib/scope-templates/slice-progress.md +11 -0
  291. package/dist/remote-host-ops.d.ts +24 -0
  292. package/dist/remote-host-ops.d.ts.map +1 -0
  293. package/dist/remote-host-ops.js +53 -0
  294. package/dist/remote-host-ops.js.map +1 -0
  295. package/package.json +2 -1
  296. package/ui/dist/assets/index-CnqRX7A3.css +32 -0
  297. package/ui/dist/assets/index-CtL9jXFt.js +582 -0
  298. package/ui/dist/index.html +2 -2
  299. package/ui/dist/assets/index-C_Q5FczU.css +0 -1
  300. package/ui/dist/assets/index-De6vv0yO.js +0 -552
@@ -8,10 +8,12 @@
8
8
  import fs from "node:fs";
9
9
  import path from "node:path";
10
10
  import { Command } from "commander";
11
- import { CLOSE_REASONS, MISSION_TEMPLATE_KINDS, SLICE_TEMPLATE_KINDS, ScopeCliError, } from "../lib/scope/types.js";
11
+ import { CLOSE_REASONS, MISSION_TEMPLATE_KINDS, SLICE_TEMPLATE_KINDS, STAGE_VALUES, ScopeCliError, } from "../lib/scope/types.js";
12
12
  import { DEFAULT_PROJECT_PREFIX, inferMissionDotId, isMissionDotId, nextEscapeBandOrdinal, sliceIdFromMission, } from "../lib/scope/dot-id.js";
13
- import { ensureMissionIdPersisted, findMission, findSlice, listMissions, listSlices, moveSlice, nextSliceNN, pad2, resolveMissionsRoot, splitFrontmatter, todayDateISO, updateFrontmatter, } from "../lib/scope/scope-fs.js";
14
- import { renderMissionNotesTemplate, renderMissionTemplate, renderSliceTemplate, titleFromSlug, } from "../lib/scope/templates.js";
13
+ import { ensureMissionId, ensureMissionIdPersisted, findMission, findSlice, listMissions, listSlices, moveSlice, nextSliceNN, pad2, readFrontmatter, resolveMissionsRoot, splitFrontmatter, todayDateISO, updateFrontmatter, } from "../lib/scope/scope-fs.js";
14
+ import { renderMissionBriefTemplate, renderMissionNotesTemplate, renderMissionProgressTemplate, renderMissionTemplate, renderSliceProofTemplate, renderSliceProgressTemplate, renderSliceTemplate, titleFromSlug, } from "../lib/scope/templates.js";
15
+ import { addProgressRow, DEFAULT_PROGRESS_SECTION, parseStatus, PROGRESS_STATUSES, setProgressRow, } from "../lib/scope/progress-edit.js";
16
+ import { deriveScopeTrust } from "../lib/scope/trust.js";
15
17
  function makeStdout() {
16
18
  return { write: (text) => process.stdout.write(text) };
17
19
  }
@@ -63,6 +65,16 @@ function getOpts(cmd) {
63
65
  }
64
66
  return {};
65
67
  }
68
+ /** FR-5: a one-line human render of the derived stage. Shows the declared
69
+ * stage, and (when a weak `verified` downgrades it) the effective stage +
70
+ * why. Derived at read time; nothing is written. */
71
+ function formatTrustLine(trust) {
72
+ const declared = trust.declaredStage || "—";
73
+ if (trust.downgraded) {
74
+ return ` stage: ${declared} (effective: ${trust.effectiveStage} — ${trust.verified.status})\n`;
75
+ }
76
+ return ` stage: ${declared}\n`;
77
+ }
66
78
  // ---------------------------------------------------------------------
67
79
  // rig scope slice ls
68
80
  // ---------------------------------------------------------------------
@@ -131,6 +143,8 @@ function buildSliceShowCommand() {
131
143
  const readme = slice.readmePath ? fs.readFileSync(slice.readmePath, "utf8") : null;
132
144
  const children = fs.readdirSync(slice.absPath, { withFileTypes: true })
133
145
  .map((e) => ({ name: e.name, kind: e.isDirectory() ? "dir" : "file" }));
146
+ // FR-5: derive read-time trust from (stage x verified) — NEVER stored.
147
+ const trust = deriveScopeTrust(slice.frontmatter);
134
148
  const payload = {
135
149
  ok: true,
136
150
  slice: {
@@ -140,6 +154,7 @@ function buildSliceShowCommand() {
140
154
  status: slice.status,
141
155
  path: slice.absPath,
142
156
  frontmatter: slice.frontmatter,
157
+ trust,
143
158
  readme,
144
159
  children,
145
160
  },
@@ -151,6 +166,7 @@ function buildSliceShowCommand() {
151
166
  out.write(`Slice: ${slice.missionName}/${slice.name}\n`);
152
167
  out.write(` id: ${slice.id ?? "—"}\n`);
153
168
  out.write(` status: ${slice.status ?? "—"}\n`);
169
+ out.write(formatTrustLine(trust));
154
170
  out.write(` path: ${slice.absPath}\n`);
155
171
  out.write(` children: ${children.length}\n`);
156
172
  if (readme) {
@@ -176,6 +192,7 @@ function buildSliceCreateCommand() {
176
192
  .argument("<slug>", "Short slug (becomes the folder name's suffix)")
177
193
  .option("--template <kind>", `Template: ${SLICE_TEMPLATE_KINDS.join(" | ")}`, "placeholder")
178
194
  .option("--title <text>", "Display title (defaults to titlecased slug)")
195
+ .option("--readme-only", "Write progress_rail: readme-only in README frontmatter instead of scaffolding PROGRESS.md")
179
196
  .option("--json", "Machine-readable output")
180
197
  .action(async (missionName, rawSlug, opts, command) => {
181
198
  const out = makeStdout();
@@ -216,17 +233,30 @@ function buildSliceCreateCommand() {
216
233
  }
217
234
  const id = sliceIdFromMission(missionId, nn);
218
235
  const title = opts.title ?? titleFromSlug(slug);
236
+ const createdDate = todayDateISO();
219
237
  const body = renderSliceTemplate(kind, {
220
238
  id,
221
239
  slice_number: pad2(nn),
222
240
  slug,
223
241
  mission: mission.name,
224
242
  title,
225
- created_date: todayDateISO(),
243
+ created_date: createdDate,
226
244
  });
245
+ const proofBody = renderSliceProofTemplate({ id, title });
227
246
  fs.mkdirSync(sliceAbs, { recursive: true });
247
+ fs.mkdirSync(path.join(sliceAbs, "proof"), { recursive: true });
228
248
  const readmePath = path.join(sliceAbs, "README.md");
229
- fs.writeFileSync(readmePath, body, "utf8");
249
+ const readmeOnly = Boolean(opts.readmeOnly);
250
+ if (readmeOnly) {
251
+ const markerBody = body.replace(/^(---\n)/, `---\nprogress_rail: readme-only\n`);
252
+ fs.writeFileSync(readmePath, markerBody, "utf8");
253
+ }
254
+ else {
255
+ fs.writeFileSync(readmePath, body, "utf8");
256
+ const progressPath = path.join(sliceAbs, "PROGRESS.md");
257
+ fs.writeFileSync(progressPath, renderSliceProgressTemplate(title), "utf8");
258
+ }
259
+ fs.writeFileSync(path.join(sliceAbs, "PROOF.md"), proofBody, "utf8");
230
260
  const payload = {
231
261
  ok: true,
232
262
  slice: {
@@ -464,6 +494,8 @@ function buildMissionShowCommand() {
464
494
  const slices = listSlices(mission, "all").map((s) => ({
465
495
  name: s.name, id: s.id, status: s.status, nn: s.nn,
466
496
  }));
497
+ // FR-5: derive read-time trust from (stage x verified) — NEVER stored.
498
+ const trust = deriveScopeTrust(mission.frontmatter);
467
499
  const payload = {
468
500
  ok: true,
469
501
  mission: {
@@ -473,6 +505,7 @@ function buildMissionShowCommand() {
473
505
  activeSliceCount: mission.activeSliceCount,
474
506
  closedSliceCount: mission.closedSliceCount,
475
507
  frontmatter: mission.frontmatter,
508
+ trust,
476
509
  readme,
477
510
  slices,
478
511
  },
@@ -483,6 +516,7 @@ function buildMissionShowCommand() {
483
516
  else {
484
517
  out.write(`Mission: ${mission.name}\n`);
485
518
  out.write(` id: ${mission.id ?? "—"}\n`);
519
+ out.write(formatTrustLine(trust));
486
520
  out.write(` active slices: ${mission.activeSliceCount}\n`);
487
521
  out.write(` closed slices: ${mission.closedSliceCount}\n`);
488
522
  out.write(` path: ${mission.absPath}\n`);
@@ -593,11 +627,17 @@ function buildMissionCreateCommand() {
593
627
  });
594
628
  missionNotesRendered = { rendered: r.rendered, resolvedFrom: r.resolvedFrom };
595
629
  }
630
+ const progressBody = renderMissionProgressTemplate(title);
631
+ const missionBriefBody = renderMissionBriefTemplate(title);
596
632
  // All renders succeeded — safe to touch the filesystem.
597
633
  fs.mkdirSync(absPath, { recursive: true });
598
634
  fs.mkdirSync(path.join(absPath, "slices"), { recursive: true });
599
635
  const readmePath = path.join(absPath, "README.md");
600
636
  fs.writeFileSync(readmePath, readmeBody, "utf8");
637
+ const progressPath = path.join(absPath, "PROGRESS.md");
638
+ fs.writeFileSync(progressPath, progressBody, "utf8");
639
+ const missionBriefPath = path.join(absPath, "MISSION_BRIEF.md");
640
+ fs.writeFileSync(missionBriefPath, missionBriefBody, "utf8");
601
641
  let missionNotesPath = null;
602
642
  let missionNotesResolvedFrom = null;
603
643
  if (missionNotesRendered) {
@@ -622,6 +662,7 @@ function buildMissionCreateCommand() {
622
662
  template: templateKind,
623
663
  path: absPath,
624
664
  readmePath,
665
+ missionBriefPath,
625
666
  missionNotesPath,
626
667
  missionNotesResolvedFrom,
627
668
  },
@@ -633,6 +674,698 @@ function buildMissionCreateCommand() {
633
674
  });
634
675
  }
635
676
  // ---------------------------------------------------------------------
677
+ // Audit (B2 — read-only scope audit)
678
+ // ---------------------------------------------------------------------
679
+ function buildAuditCommand() {
680
+ return new Command("audit")
681
+ .description("Read-only scope audit: flag missing/malformed progress rails and registration ghosts")
682
+ .requiredOption("--mission <name>", "Mission to audit")
683
+ .option("--json", "Machine-readable JSON output")
684
+ .action(async (opts, command) => {
685
+ const out = makeStdout();
686
+ const json = Boolean(opts.json);
687
+ try {
688
+ const missionsRoot = resolveMissionsRoot({ override: getOpts(command).workspace });
689
+ const { classifyScopeItem } = await import("../lib/scope/scope-audit.js");
690
+ const missionName = opts.mission;
691
+ const missionDir = path.join(missionsRoot, missionName);
692
+ if (!fs.existsSync(missionDir)) {
693
+ throw new ScopeCliError({ fact: `Mission "${missionName}" not found at ${missionDir}.`, consequence: "Cannot audit.", action: "Check the mission name." });
694
+ }
695
+ const missionReadme = path.join(missionDir, "README.md");
696
+ const missionProgress = path.join(missionDir, "PROGRESS.md");
697
+ const missionBrief = path.join(missionDir, "MISSION_BRIEF.md");
698
+ const missionNotes = path.join(missionDir, "MISSION_NOTES.md");
699
+ const missionReadmeExists = fs.existsSync(missionReadme);
700
+ const missionProgressExists = fs.existsSync(missionProgress);
701
+ const missionBriefExists = fs.existsSync(missionBrief);
702
+ let missionResult;
703
+ if (!missionReadmeExists && missionProgressExists) {
704
+ missionResult = {
705
+ railStatus: "malformed",
706
+ findings: [{
707
+ kind: "orphan_progress",
708
+ severity: "high",
709
+ path: missionDir,
710
+ message: `PROGRESS.md exists but no README.md (orphan progress rail, no backing scope item)`,
711
+ remediation: `Add a README.md with frontmatter id, or remove the orphan PROGRESS.md`,
712
+ }],
713
+ frontmatterError: null,
714
+ };
715
+ }
716
+ else {
717
+ const missionFm = missionReadmeExists
718
+ ? extractFrontmatterRaw(fs.readFileSync(missionReadme, "utf-8"))
719
+ : null;
720
+ missionResult = classifyScopeItem({
721
+ id: null,
722
+ path: missionDir,
723
+ readmeFrontmatterRaw: missionFm,
724
+ progressFileExists: missionProgressExists,
725
+ readmeOnlyMarker: false,
726
+ isActiveRelease: true,
727
+ level: "mission",
728
+ missionBriefExists,
729
+ missionBriefPath: missionBrief,
730
+ missionBriefContent: missionBriefExists ? fs.readFileSync(missionBrief, "utf-8") : null,
731
+ missionNotesExists: fs.existsSync(missionNotes),
732
+ missionNotesPath: missionNotes,
733
+ });
734
+ }
735
+ const slicesDir = path.join(missionDir, "slices");
736
+ const dogfoodEvidenceRoot = defaultDogfoodEvidenceRoot(missionsRoot);
737
+ const sliceResults = [];
738
+ if (fs.existsSync(slicesDir)) {
739
+ for (const entry of fs.readdirSync(slicesDir)) {
740
+ const sliceDir = path.join(slicesDir, entry);
741
+ if (!fs.statSync(sliceDir).isDirectory())
742
+ continue;
743
+ const sliceReadme = path.join(sliceDir, "README.md");
744
+ const sliceProgress = path.join(sliceDir, "PROGRESS.md");
745
+ const proofFile = path.join(sliceDir, "PROOF.md");
746
+ const proofDir = path.join(sliceDir, "proof");
747
+ if (!fs.existsSync(sliceReadme)) {
748
+ if (fs.existsSync(sliceProgress)) {
749
+ sliceResults.push({
750
+ name: entry,
751
+ result: {
752
+ railStatus: "malformed",
753
+ findings: [{
754
+ kind: "orphan_progress",
755
+ severity: "high",
756
+ path: sliceDir,
757
+ message: `PROGRESS.md exists but no README.md (orphan progress rail, no backing scope item)`,
758
+ remediation: `Add a README.md with frontmatter id, or remove the orphan PROGRESS.md`,
759
+ }],
760
+ frontmatterError: null,
761
+ },
762
+ });
763
+ }
764
+ else {
765
+ const noReadmeResult = classifyScopeItem({
766
+ id: null,
767
+ path: sliceDir,
768
+ readmeFrontmatterRaw: null,
769
+ progressFileExists: false,
770
+ readmeOnlyMarker: false,
771
+ isActiveRelease: true,
772
+ level: "slice",
773
+ });
774
+ sliceResults.push({ name: entry, result: noReadmeResult });
775
+ }
776
+ continue;
777
+ }
778
+ const sliceFm = extractFrontmatterRaw(fs.readFileSync(sliceReadme, "utf-8"));
779
+ const readmeOnlyMarker = sliceFm !== null && /^progress_rail\s*:\s*readme-only/m.test(sliceFm);
780
+ const sliceResult = classifyScopeItem({
781
+ id: null,
782
+ path: sliceDir,
783
+ readmeFrontmatterRaw: sliceFm,
784
+ progressFileExists: fs.existsSync(sliceProgress),
785
+ readmeOnlyMarker,
786
+ isActiveRelease: true,
787
+ level: "slice",
788
+ proofFileExists: fs.existsSync(proofFile),
789
+ proofFilePath: proofFile,
790
+ proofDirExists: fs.existsSync(proofDir),
791
+ proofDirPath: proofDir,
792
+ proofDirHasEntries: directoryHasEntries(proofDir),
793
+ hasProofPacket: hasProofPacketForSlice(dogfoodEvidenceRoot, entry),
794
+ });
795
+ if (!/^\d{2}-/.test(entry)) {
796
+ sliceResult.findings.push({
797
+ kind: "id_convention_violation",
798
+ severity: "high",
799
+ path: sliceDir,
800
+ message: `Directory "${entry}" does not match the NN-slug slice naming convention (e.g. 01-my-slice)`,
801
+ remediation: `Rename to NN-slug format or move out of slices/`,
802
+ });
803
+ }
804
+ sliceResults.push({ name: entry, result: sliceResult });
805
+ }
806
+ }
807
+ const allFindings = [
808
+ ...missionResult.findings.map((f) => ({ ...f, scope: "mission", scopeName: missionName })),
809
+ ...sliceResults.flatMap((s) => s.result.findings.map((f) => ({ ...f, scope: "slice", scopeName: s.name }))),
810
+ ];
811
+ const hardFindings = allFindings.filter((f) => f.severity === "high");
812
+ if (json) {
813
+ out.write(JSON.stringify({
814
+ ok: hardFindings.length === 0,
815
+ mission: { name: missionName, railStatus: missionResult.railStatus, frontmatterError: missionResult.frontmatterError, findings: missionResult.findings },
816
+ slices: sliceResults.map((s) => ({ name: s.name, railStatus: s.result.railStatus, frontmatterError: s.result.frontmatterError, findings: s.result.findings })),
817
+ totalFindings: allFindings.length,
818
+ }, null, 2));
819
+ out.write("\n");
820
+ if (hardFindings.length > 0)
821
+ process.exitCode = 1;
822
+ return;
823
+ }
824
+ out.write(`Scope audit: ${missionName}\n`);
825
+ out.write(`Mission rail: ${missionResult.railStatus}\n`);
826
+ out.write(`Slices: ${sliceResults.length} total\n\n`);
827
+ if (allFindings.length > 0) {
828
+ out.write("FINDINGS:\n");
829
+ for (const f of allFindings) {
830
+ out.write(` [${f.severity}] [${f.kind}] ${f.scope}/${f.scopeName}\n`);
831
+ out.write(` ${f.message}\n`);
832
+ out.write(` fix: ${f.remediation}\n`);
833
+ }
834
+ if (hardFindings.length > 0) {
835
+ out.write(`\nFAIL: ${allFindings.length} finding(s)\n`);
836
+ process.exitCode = 1;
837
+ }
838
+ else {
839
+ out.write(`\nWARN: ${allFindings.length} advisory finding(s)\n`);
840
+ }
841
+ }
842
+ else {
843
+ out.write("PASS: all scope items have valid rails\n");
844
+ }
845
+ }
846
+ catch (err) {
847
+ if (err instanceof ScopeCliError) {
848
+ fail(err, json, out);
849
+ }
850
+ throw err;
851
+ }
852
+ });
853
+ }
854
+ function extractFrontmatterRaw(content) {
855
+ if (!content.startsWith("---"))
856
+ return null;
857
+ const match = /^---\s*\n([\s\S]*?)\n---/.exec(content);
858
+ return match ? match[1] : null;
859
+ }
860
+ function directoryHasEntries(dir) {
861
+ try {
862
+ return fs.readdirSync(dir).some((entry) => !entry.startsWith("."));
863
+ }
864
+ catch {
865
+ return false;
866
+ }
867
+ }
868
+ function defaultDogfoodEvidenceRoot(missionsRoot) {
869
+ return path.join(path.dirname(missionsRoot), "dogfood-evidence");
870
+ }
871
+ function hasProofPacketForSlice(dogfoodEvidenceRoot, sliceName) {
872
+ let entries;
873
+ try {
874
+ entries = fs.readdirSync(dogfoodEvidenceRoot, { withFileTypes: true });
875
+ }
876
+ catch {
877
+ return false;
878
+ }
879
+ const sliceTokens = sliceName.split("-").filter((token) => token.length > 0 && !/^v\d+$/.test(token));
880
+ return entries.some((entry) => {
881
+ if (!entry.isDirectory())
882
+ return false;
883
+ const dirTokenSet = new Set(entry.name.split(/[-._]/).filter((token) => token.length > 0));
884
+ return sliceTokens.every((token) => dirTokenSet.has(token));
885
+ });
886
+ }
887
+ // ---------------------------------------------------------------------
888
+ // rig scope <tier> progress (OPR.0.4.0.33 FR-3 — deterministic update)
889
+ // ---------------------------------------------------------------------
890
+ /** Resolve which file a progress update edits for a scope dir: the
891
+ * PROGRESS.md when present, else the README's rail for a readme-only
892
+ * scope, else an error directing to create/repair (the verb UPDATES an
893
+ * existing surface; it does not scaffold). */
894
+ function resolveProgressTarget(scopeDir, level) {
895
+ const progressPath = path.join(scopeDir, "PROGRESS.md");
896
+ if (fs.existsSync(progressPath))
897
+ return { targetPath: progressPath, kind: "progress" };
898
+ const readmePath = path.join(scopeDir, "README.md");
899
+ if (fs.existsSync(readmePath)) {
900
+ const fm = readFrontmatter(readmePath);
901
+ if (String(fm.progress_rail ?? "") === "readme-only") {
902
+ return { targetPath: readmePath, kind: "readme-only" };
903
+ }
904
+ }
905
+ throw new ScopeCliError({
906
+ fact: `${level} at ${scopeDir} has no progress surface (no PROGRESS.md and no readme-only rail).`,
907
+ consequence: "The progress verb updates an existing surface; it does not scaffold.",
908
+ action: `Backfill it with: rig scope ${level} repair <target> (creates PROGRESS.md), or rig scope ${level} create.`,
909
+ });
910
+ }
911
+ /** Shared body for slice/mission progress: validate the mutually
912
+ * exclusive --add/--set modes, edit the resolved surface, write only
913
+ * on change. */
914
+ function runProgressUpdate(scopeDir, level, scopeName, opts, out, json) {
915
+ const hasAdd = typeof opts.add === "string";
916
+ const hasSet = typeof opts.set === "string";
917
+ if (hasAdd === hasSet) {
918
+ throw new ScopeCliError({
919
+ fact: hasAdd
920
+ ? "Both --add and --set were given."
921
+ : "Neither --add nor --set was given.",
922
+ consequence: "No progress update was made.",
923
+ action: 'Pass exactly one of --add "<row text>" or --set "<row text>".',
924
+ });
925
+ }
926
+ const status = parseStatus(opts.status ?? "active");
927
+ const { targetPath, kind } = resolveProgressTarget(scopeDir, level);
928
+ const before = fs.readFileSync(targetPath, "utf8");
929
+ let result;
930
+ let operation;
931
+ if (hasAdd) {
932
+ operation = "add";
933
+ result = addProgressRow(before, {
934
+ section: opts.section ?? DEFAULT_PROGRESS_SECTION,
935
+ text: opts.add,
936
+ status,
937
+ });
938
+ }
939
+ else {
940
+ operation = "set";
941
+ result = setProgressRow(before, { text: opts.set, status });
942
+ }
943
+ if (result.changed)
944
+ fs.writeFileSync(targetPath, result.content, "utf8");
945
+ emit(out, {
946
+ ok: true,
947
+ progress: {
948
+ scope: level,
949
+ name: scopeName,
950
+ target: targetPath,
951
+ kind,
952
+ operation,
953
+ status,
954
+ changed: result.changed,
955
+ },
956
+ }, json, [
957
+ `${result.changed ? "Updated" : "No change"} ${level} ${scopeName} progress (${operation})`,
958
+ ` target: ${targetPath}`,
959
+ ` status: ${status}`,
960
+ ]);
961
+ }
962
+ function buildSliceProgressCommand() {
963
+ return new Command("progress")
964
+ .description("Update a slice's progress rail deterministically (append a row, or set a row's status)")
965
+ .argument("<slice-path>", "Slice path (absolute, relative, or NN-slug)")
966
+ .option("--mission <name>", "Hint mission when slice-path is just NN-slug")
967
+ .option("--add <text>", "Append a checkbox row with this text")
968
+ .option("--set <text>", "Set the status of the row whose trimmed text exactly matches")
969
+ .option("--section <heading>", `Section heading for --add (default: ${DEFAULT_PROGRESS_SECTION})`)
970
+ .option("--status <status>", `Row status: ${PROGRESS_STATUSES.join(" | ")}`, "active")
971
+ .option("--json", "Machine-readable output")
972
+ .action(async (slicePath, opts, command) => {
973
+ const out = makeStdout();
974
+ const json = Boolean(opts.json);
975
+ try {
976
+ const missionsRoot = resolveMissionsRoot({ override: getOpts(command).workspace });
977
+ const slice = findSlice(missionsRoot, slicePath, opts.mission ?? null);
978
+ runProgressUpdate(slice.absPath, "slice", slice.name, opts, out, json);
979
+ }
980
+ catch (err) {
981
+ fail(err, json, out);
982
+ }
983
+ });
984
+ }
985
+ function buildMissionProgressCommand() {
986
+ return new Command("progress")
987
+ .description("Update a mission's progress rail deterministically (append a row, or set a row's status)")
988
+ .argument("<mission>", "Mission name")
989
+ .option("--add <text>", "Append a checkbox row with this text")
990
+ .option("--set <text>", "Set the status of the row whose trimmed text exactly matches")
991
+ .option("--section <heading>", `Section heading for --add (default: ${DEFAULT_PROGRESS_SECTION})`)
992
+ .option("--status <status>", `Row status: ${PROGRESS_STATUSES.join(" | ")}`, "active")
993
+ .option("--json", "Machine-readable output")
994
+ .action(async (missionName, opts, command) => {
995
+ const out = makeStdout();
996
+ const json = Boolean(opts.json);
997
+ try {
998
+ const missionsRoot = resolveMissionsRoot({ override: getOpts(command).workspace });
999
+ const mission = findMission(missionsRoot, missionName);
1000
+ runProgressUpdate(mission.absPath, "mission", mission.name, opts, out, json);
1001
+ }
1002
+ catch (err) {
1003
+ fail(err, json, out);
1004
+ }
1005
+ });
1006
+ }
1007
+ /** Mirror the create-time title derivation so a backfilled PROGRESS.md
1008
+ * is byte-identical to what create would have written. */
1009
+ function backfillTitle(level, scopeDir) {
1010
+ const base = path.basename(scopeDir);
1011
+ return level === "mission"
1012
+ ? titleFromSlug(base.replace(/^release-/, ""))
1013
+ : titleFromSlug(base.replace(/^\d+-/, ""));
1014
+ }
1015
+ /** Create a missing PROGRESS.md for a single scope dir. Idempotent
1016
+ * (skips when one exists) and non-clobbering (skips an intentional
1017
+ * readme-only scope, and skips README-less dirs that are not declared
1018
+ * scopes). */
1019
+ function backfillScopeProgress(scopeDir, level) {
1020
+ const name = path.basename(scopeDir);
1021
+ const readmePath = path.join(scopeDir, "README.md");
1022
+ if (!fs.existsSync(readmePath)) {
1023
+ return { scope: level, name, created: false, reason: "no-readme (not a declared scope)", path: null };
1024
+ }
1025
+ const progressPath = path.join(scopeDir, "PROGRESS.md");
1026
+ if (fs.existsSync(progressPath)) {
1027
+ return { scope: level, name, created: false, reason: "already-present", path: progressPath };
1028
+ }
1029
+ const fm = readFrontmatter(readmePath);
1030
+ if (String(fm.progress_rail ?? "") === "readme-only") {
1031
+ return { scope: level, name, created: false, reason: "readme-only (intentional opt-out)", path: null };
1032
+ }
1033
+ const title = backfillTitle(level, scopeDir);
1034
+ const body = level === "mission"
1035
+ ? renderMissionProgressTemplate(title)
1036
+ : renderSliceProgressTemplate(title);
1037
+ fs.writeFileSync(progressPath, body, "utf8");
1038
+ return { scope: level, name, created: true, reason: "backfilled", path: progressPath };
1039
+ }
1040
+ // ---------------------------------------------------------------------
1041
+ // OPR.0.4.1.6 — stage + verified verbs (deterministic §2 maturity edits)
1042
+ // ---------------------------------------------------------------------
1043
+ /** Validate a stage against the §2 enum, rejecting invented values. */
1044
+ function validateStage(raw) {
1045
+ if (!STAGE_VALUES.includes(raw)) {
1046
+ throw new ScopeCliError({
1047
+ fact: `Invalid stage "${raw}".`,
1048
+ consequence: "Stage not changed.",
1049
+ action: `Use one of: ${STAGE_VALUES.join(" | ")}.`,
1050
+ });
1051
+ }
1052
+ return raw;
1053
+ }
1054
+ /** Surgically set `stage` (+ `superseded-by` when superseded) on a scope
1055
+ * README, enforcing the §2 superseded-needs-successor rule. */
1056
+ function applyStage(readmePath, stage, successor) {
1057
+ const updates = { stage };
1058
+ if (stage === "superseded") {
1059
+ const id = typeof successor === "string" ? successor.trim() : "";
1060
+ if (!id) {
1061
+ throw new ScopeCliError({
1062
+ fact: "stage 'superseded' requires a successor.",
1063
+ consequence: "Stage not changed (a superseded scope must name what replaces it, per scope-and-versioning §2).",
1064
+ action: "Re-run with --successor <id>, e.g. --successor OPR.0.4.1.7.",
1065
+ });
1066
+ }
1067
+ updates["superseded-by"] = id;
1068
+ }
1069
+ updateFrontmatter(readmePath, updates);
1070
+ }
1071
+ /** `retired` is an exit, not a rung — warn (do not block). */
1072
+ function warnRetired(stage) {
1073
+ if (stage === "retired") {
1074
+ process.stderr.write("Warning: stage 'retired' means do-not-use (an exit, not a maturity rung).\n");
1075
+ }
1076
+ }
1077
+ /** Validate a --against provenance: mandatory, non-empty, non-whitespace
1078
+ * (the §2 "no bare timestamp" rule). Returns the trimmed source. */
1079
+ function validateAgainst(raw) {
1080
+ const source = typeof raw === "string" ? raw.trim() : "";
1081
+ if (!source) {
1082
+ throw new ScopeCliError({
1083
+ fact: "--against provenance is empty or missing.",
1084
+ consequence: "verified not stamped — scope-and-versioning §2 forbids a bare timestamp without a named source.",
1085
+ action: 'Provide what it was verified against, e.g. --against "runtime (npm+tag+origin)".',
1086
+ });
1087
+ }
1088
+ return source;
1089
+ }
1090
+ function buildSliceStageCommand() {
1091
+ return new Command("stage")
1092
+ .description(`Set a slice's epistemic stage (${STAGE_VALUES.join(" | ")}); superseded needs --successor`)
1093
+ .argument("<slice-path>", "Slice path (absolute, relative, or NN-slug)")
1094
+ .argument("<new-stage>", `New stage: ${STAGE_VALUES.join(" | ")}`)
1095
+ .option("--successor <id>", "Successor scope id — REQUIRED when new-stage is superseded")
1096
+ .option("--mission <name>", "Hint mission when slice-path is just NN-slug")
1097
+ .option("--json", "Machine-readable output")
1098
+ .action(async (slicePath, newStage, opts, command) => {
1099
+ const out = makeStdout();
1100
+ const json = Boolean(opts.json);
1101
+ try {
1102
+ const stage = validateStage(newStage);
1103
+ const missionsRoot = resolveMissionsRoot({ override: getOpts(command).workspace });
1104
+ const slice = findSlice(missionsRoot, slicePath, opts.mission ?? null);
1105
+ if (!slice.readmePath) {
1106
+ throw new ScopeCliError({
1107
+ fact: `Slice ${slice.name} has no README.md.`,
1108
+ consequence: "Stage is a frontmatter field on the README; nothing to write.",
1109
+ action: "Create the slice README (rig scope slice create) before setting its stage.",
1110
+ });
1111
+ }
1112
+ applyStage(slice.readmePath, stage, opts.successor);
1113
+ warnRetired(stage);
1114
+ const supersededBy = stage === "superseded" ? String(opts.successor).trim() : undefined;
1115
+ emit(out, { ok: true, scope: { tier: "slice", mission: slice.missionName, name: slice.name, id: slice.id, stage, ...(supersededBy ? { supersededBy } : {}) } }, json, [
1116
+ `Set ${slice.missionName}/${slice.name} stage: ${stage}`,
1117
+ ...(supersededBy ? [` superseded-by: ${supersededBy}`] : []),
1118
+ ]);
1119
+ }
1120
+ catch (err) {
1121
+ fail(err, json, out);
1122
+ }
1123
+ });
1124
+ }
1125
+ function buildMissionStageCommand() {
1126
+ return new Command("stage")
1127
+ .description(`Set a mission's epistemic stage (${STAGE_VALUES.join(" | ")}); superseded needs --successor`)
1128
+ .argument("<mission>", "Mission name")
1129
+ .argument("<new-stage>", `New stage: ${STAGE_VALUES.join(" | ")}`)
1130
+ .option("--successor <id>", "Successor scope id — REQUIRED when new-stage is superseded")
1131
+ .option("--json", "Machine-readable output")
1132
+ .action(async (missionName, newStage, opts, command) => {
1133
+ const out = makeStdout();
1134
+ const json = Boolean(opts.json);
1135
+ try {
1136
+ const stage = validateStage(newStage);
1137
+ const missionsRoot = resolveMissionsRoot({ override: getOpts(command).workspace });
1138
+ const mission = findMission(missionsRoot, missionName);
1139
+ if (!mission.readmePath) {
1140
+ throw new ScopeCliError({
1141
+ fact: `Mission ${mission.name} has no README.md.`,
1142
+ consequence: "Stage is a frontmatter field on the README; nothing to write.",
1143
+ action: "Create the mission README before setting its stage.",
1144
+ });
1145
+ }
1146
+ applyStage(mission.readmePath, stage, opts.successor);
1147
+ warnRetired(stage);
1148
+ const supersededBy = stage === "superseded" ? String(opts.successor).trim() : undefined;
1149
+ emit(out, { ok: true, scope: { tier: "mission", name: mission.name, id: mission.id, stage, ...(supersededBy ? { supersededBy } : {}) } }, json, [
1150
+ `Set ${mission.name} stage: ${stage}`,
1151
+ ...(supersededBy ? [` superseded-by: ${supersededBy}`] : []),
1152
+ ]);
1153
+ }
1154
+ catch (err) {
1155
+ fail(err, json, out);
1156
+ }
1157
+ });
1158
+ }
1159
+ function buildSliceVerifiedCommand() {
1160
+ return new Command("verified")
1161
+ .description("Stamp a slice's verified line: <today> against <source> (provenance mandatory; overwrites the prior line)")
1162
+ .argument("<slice-path>", "Slice path (absolute, relative, or NN-slug)")
1163
+ .option("--against <source>", "What it was verified against — MANDATORY (no bare timestamps)")
1164
+ .option("--mission <name>", "Hint mission when slice-path is just NN-slug")
1165
+ .option("--json", "Machine-readable output")
1166
+ .action(async (slicePath, opts, command) => {
1167
+ const out = makeStdout();
1168
+ const json = Boolean(opts.json);
1169
+ try {
1170
+ const source = validateAgainst(opts.against);
1171
+ const missionsRoot = resolveMissionsRoot({ override: getOpts(command).workspace });
1172
+ const slice = findSlice(missionsRoot, slicePath, opts.mission ?? null);
1173
+ if (!slice.readmePath) {
1174
+ throw new ScopeCliError({
1175
+ fact: `Slice ${slice.name} has no README.md.`,
1176
+ consequence: "verified is a frontmatter field on the README; nothing to write.",
1177
+ action: "Create the slice README before stamping verified.",
1178
+ });
1179
+ }
1180
+ const verified = `${todayDateISO()} against ${source}`;
1181
+ updateFrontmatter(slice.readmePath, { verified });
1182
+ emit(out, { ok: true, scope: { tier: "slice", mission: slice.missionName, name: slice.name, id: slice.id, verified } }, json, [
1183
+ `Stamped ${slice.missionName}/${slice.name} verified: ${verified}`,
1184
+ ]);
1185
+ }
1186
+ catch (err) {
1187
+ fail(err, json, out);
1188
+ }
1189
+ });
1190
+ }
1191
+ function buildMissionVerifiedCommand() {
1192
+ return new Command("verified")
1193
+ .description("Stamp a mission's verified line: <today> against <source> (provenance mandatory; overwrites the prior line)")
1194
+ .argument("<mission>", "Mission name")
1195
+ .option("--against <source>", "What it was verified against — MANDATORY (no bare timestamps)")
1196
+ .option("--json", "Machine-readable output")
1197
+ .action(async (missionName, opts, command) => {
1198
+ const out = makeStdout();
1199
+ const json = Boolean(opts.json);
1200
+ try {
1201
+ const source = validateAgainst(opts.against);
1202
+ const missionsRoot = resolveMissionsRoot({ override: getOpts(command).workspace });
1203
+ const mission = findMission(missionsRoot, missionName);
1204
+ if (!mission.readmePath) {
1205
+ throw new ScopeCliError({
1206
+ fact: `Mission ${mission.name} has no README.md.`,
1207
+ consequence: "verified is a frontmatter field on the README; nothing to write.",
1208
+ action: "Create the mission README before stamping verified.",
1209
+ });
1210
+ }
1211
+ const verified = `${todayDateISO()} against ${source}`;
1212
+ updateFrontmatter(mission.readmePath, { verified });
1213
+ emit(out, { ok: true, scope: { tier: "mission", name: mission.name, id: mission.id, verified } }, json, [
1214
+ `Stamped ${mission.name} verified: ${verified}`,
1215
+ ]);
1216
+ }
1217
+ catch (err) {
1218
+ fail(err, json, out);
1219
+ }
1220
+ });
1221
+ }
1222
+ /** Map a legacy `status:` to a §4 migration stage. Default `wip` when absent
1223
+ * or unmapped (the safe floor). */
1224
+ function mapLegacyStatusToStage(status) {
1225
+ const s = typeof status === "string" ? status.toLowerCase().trim() : "";
1226
+ if (s === "placeholder")
1227
+ return "wip";
1228
+ if (s === "draft" || s === "draft-for-comms")
1229
+ return "wip";
1230
+ if (s === "active" || s === "in-flight")
1231
+ return "established";
1232
+ if (s.startsWith("shipped") || s.startsWith("closed"))
1233
+ return "established";
1234
+ if (s === "ready-for-mission" || s === "ready-for-orch-dispatch")
1235
+ return "provisional";
1236
+ return "wip";
1237
+ }
1238
+ /** Idempotently conform a scope README's mandatory frontmatter. Only ADDS
1239
+ * absent fields — never clobbers an existing id/stage/verified. `mintId` is
1240
+ * called only when `id` is absent (it may persist a parent id per §1 lazy
1241
+ * adoption). */
1242
+ function conformReadmeFrontmatter(readmePath, mintId) {
1243
+ const fm = readFrontmatter(readmePath);
1244
+ const updates = {};
1245
+ let idAdded = null;
1246
+ const hasId = typeof fm.id === "string" && fm.id.length > 0;
1247
+ if (!hasId) {
1248
+ const minted = mintId();
1249
+ if (minted) {
1250
+ idAdded = minted;
1251
+ updates.id = minted;
1252
+ }
1253
+ }
1254
+ let stageAdded = null;
1255
+ if (!fm.stage) {
1256
+ stageAdded = mapLegacyStatusToStage(fm.status);
1257
+ updates.stage = stageAdded;
1258
+ }
1259
+ let verifiedAdded = null;
1260
+ if (!fm.verified) {
1261
+ verifiedAdded = `${todayDateISO()} against backfill (rig scope repair)`;
1262
+ updates.verified = verifiedAdded;
1263
+ }
1264
+ const changed = Object.keys(updates).length > 0;
1265
+ if (changed)
1266
+ updateFrontmatter(readmePath, updates);
1267
+ return { idAdded, stageAdded, verifiedAdded, changed };
1268
+ }
1269
+ /** Mint a slice's id from its (persisted) parent mission id + NN — the §1
1270
+ * lazy parent-ID adoption site. Null when the folder has no NN. */
1271
+ function mintSliceIdClosure(slice, missionsRoot) {
1272
+ return () => {
1273
+ if (slice.nn == null)
1274
+ return null;
1275
+ const mission = findMission(missionsRoot, slice.missionName);
1276
+ const missionId = ensureMissionIdPersisted(mission, missionsRoot);
1277
+ return sliceIdFromMission(missionId, slice.nn);
1278
+ };
1279
+ }
1280
+ function conformLines(scope, r) {
1281
+ if (!r.changed)
1282
+ return [` frontmatter: conformant (no change)`];
1283
+ const parts = [];
1284
+ if (r.idAdded)
1285
+ parts.push(`id=${r.idAdded}`);
1286
+ if (r.stageAdded)
1287
+ parts.push(`stage=${r.stageAdded}`);
1288
+ if (r.verifiedAdded)
1289
+ parts.push(`verified=${r.verifiedAdded}`);
1290
+ return [` frontmatter conformed: ${parts.join(", ")}`];
1291
+ }
1292
+ function buildSliceRepairCommand() {
1293
+ return new Command("repair")
1294
+ .description("Backfill a slice's missing PROGRESS.md + conform mandatory frontmatter (id/stage/verified); idempotent")
1295
+ .argument("<slice-path>", "Slice path (absolute, relative, or NN-slug)")
1296
+ .option("--mission <name>", "Hint mission when slice-path is just NN-slug")
1297
+ .option("--json", "Machine-readable output")
1298
+ .action(async (slicePath, opts, command) => {
1299
+ const out = makeStdout();
1300
+ const json = Boolean(opts.json);
1301
+ try {
1302
+ const missionsRoot = resolveMissionsRoot({ override: getOpts(command).workspace });
1303
+ const slice = findSlice(missionsRoot, slicePath, opts.mission ?? null);
1304
+ const result = backfillScopeProgress(slice.absPath, "slice");
1305
+ const frontmatter = slice.readmePath
1306
+ ? conformReadmeFrontmatter(slice.readmePath, mintSliceIdClosure(slice, missionsRoot))
1307
+ : { idAdded: null, stageAdded: null, verifiedAdded: null, changed: false };
1308
+ emit(out, { ok: true, result, frontmatter }, json, [
1309
+ `${result.created ? "Backfilled" : "Skipped"} ${slice.name}: ${result.reason}`,
1310
+ ...(result.path ? [` path: ${result.path}`] : []),
1311
+ ...conformLines("slice", frontmatter),
1312
+ ]);
1313
+ }
1314
+ catch (err) {
1315
+ fail(err, json, out);
1316
+ }
1317
+ });
1318
+ }
1319
+ function buildMissionRepairCommand() {
1320
+ return new Command("repair")
1321
+ .description("Backfill missing PROGRESS.md + conform mandatory frontmatter (id/stage/verified) for a mission and its slices; idempotent")
1322
+ .argument("<mission>", "Mission name")
1323
+ .option("--json", "Machine-readable output")
1324
+ .action(async (missionName, opts, command) => {
1325
+ const out = makeStdout();
1326
+ const json = Boolean(opts.json);
1327
+ try {
1328
+ const missionsRoot = resolveMissionsRoot({ override: getOpts(command).workspace });
1329
+ const mission = findMission(missionsRoot, missionName);
1330
+ const results = [];
1331
+ results.push(backfillScopeProgress(mission.absPath, "mission"));
1332
+ const slicesDir = path.join(mission.absPath, "slices");
1333
+ if (fs.existsSync(slicesDir)) {
1334
+ for (const entry of fs.readdirSync(slicesDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
1335
+ if (!entry.isDirectory() || !/^\d+-/.test(entry.name))
1336
+ continue;
1337
+ results.push(backfillScopeProgress(path.join(slicesDir, entry.name), "slice"));
1338
+ }
1339
+ }
1340
+ // FR-4: conform mandatory frontmatter — mission first (mints+persists
1341
+ // the mission id), then each slice (child ids derive from the now-
1342
+ // persisted parent id).
1343
+ const conformed = [];
1344
+ if (mission.readmePath) {
1345
+ const fm = conformReadmeFrontmatter(mission.readmePath, () => ensureMissionId(mission, missionsRoot));
1346
+ conformed.push({ scope: "mission", name: mission.name, frontmatter: fm });
1347
+ }
1348
+ const freshMission = findMission(missionsRoot, mission.name);
1349
+ for (const slice of listSlices(freshMission, "all")) {
1350
+ if (!slice.readmePath)
1351
+ continue;
1352
+ const fm = conformReadmeFrontmatter(slice.readmePath, mintSliceIdClosure(slice, missionsRoot));
1353
+ conformed.push({ scope: "slice", name: slice.name, frontmatter: fm });
1354
+ }
1355
+ const created = results.filter((r) => r.created);
1356
+ const fmChanged = conformed.filter((c) => c.frontmatter.changed);
1357
+ emit(out, { ok: true, mission: mission.name, created, results, conformed }, json, [
1358
+ `Repaired ${mission.name}: ${created.length} PROGRESS.md backfilled, ${fmChanged.length} frontmatter conformed`,
1359
+ ...created.map((r) => ` + PROGRESS ${r.scope}/${r.name}`),
1360
+ ...fmChanged.map((c) => ` ~ frontmatter ${c.scope}/${c.name}`),
1361
+ ]);
1362
+ }
1363
+ catch (err) {
1364
+ fail(err, json, out);
1365
+ }
1366
+ });
1367
+ }
1368
+ // ---------------------------------------------------------------------
636
1369
  // Aggregate
637
1370
  // ---------------------------------------------------------------------
638
1371
  export function scopeCommand() {
@@ -646,12 +1379,21 @@ export function scopeCommand() {
646
1379
  slice.addCommand(buildSliceShipCommand());
647
1380
  slice.addCommand(buildSliceCloseCommand());
648
1381
  slice.addCommand(buildSliceMoveCommand());
1382
+ slice.addCommand(buildSliceProgressCommand());
1383
+ slice.addCommand(buildSliceRepairCommand());
1384
+ slice.addCommand(buildSliceStageCommand());
1385
+ slice.addCommand(buildSliceVerifiedCommand());
649
1386
  cmd.addCommand(slice);
650
1387
  const mission = new Command("mission").description("Mission-tier commands");
651
1388
  mission.addCommand(buildMissionLsCommand());
652
1389
  mission.addCommand(buildMissionShowCommand());
653
1390
  mission.addCommand(buildMissionCreateCommand());
1391
+ mission.addCommand(buildMissionProgressCommand());
1392
+ mission.addCommand(buildMissionRepairCommand());
1393
+ mission.addCommand(buildMissionStageCommand());
1394
+ mission.addCommand(buildMissionVerifiedCommand());
654
1395
  cmd.addCommand(mission);
1396
+ cmd.addCommand(buildAuditCommand());
655
1397
  return cmd;
656
1398
  }
657
1399
  // Re-exports for tests.