@davidahmann/mill 0.1.4

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 (184) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +335 -0
  3. package/dist/audit/repository.d.ts +8 -0
  4. package/dist/audit/repository.d.ts.map +1 -0
  5. package/dist/audit/repository.js +232 -0
  6. package/dist/audit/repository.js.map +1 -0
  7. package/dist/cli-program.d.ts +12 -0
  8. package/dist/cli-program.d.ts.map +1 -0
  9. package/dist/cli-program.js +1020 -0
  10. package/dist/cli-program.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +7 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config/lock.d.ts +12 -0
  16. package/dist/config/lock.d.ts.map +1 -0
  17. package/dist/config/lock.js +83 -0
  18. package/dist/config/lock.js.map +1 -0
  19. package/dist/contracts/canonical.d.ts +7 -0
  20. package/dist/contracts/canonical.d.ts.map +1 -0
  21. package/dist/contracts/canonical.js +30 -0
  22. package/dist/contracts/canonical.js.map +1 -0
  23. package/dist/contracts/schemas.d.ts +2705 -0
  24. package/dist/contracts/schemas.d.ts.map +1 -0
  25. package/dist/contracts/schemas.js +988 -0
  26. package/dist/contracts/schemas.js.map +1 -0
  27. package/dist/doctor.d.ts +21 -0
  28. package/dist/doctor.d.ts.map +1 -0
  29. package/dist/doctor.js +138 -0
  30. package/dist/doctor.js.map +1 -0
  31. package/dist/errors.d.ts +18 -0
  32. package/dist/errors.d.ts.map +1 -0
  33. package/dist/errors.js +31 -0
  34. package/dist/errors.js.map +1 -0
  35. package/dist/index.d.ts +20 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +20 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/intake/prd.d.ts +14 -0
  40. package/dist/intake/prd.d.ts.map +1 -0
  41. package/dist/intake/prd.js +39 -0
  42. package/dist/intake/prd.js.map +1 -0
  43. package/dist/planning/impact.d.ts +59 -0
  44. package/dist/planning/impact.d.ts.map +1 -0
  45. package/dist/planning/impact.js +400 -0
  46. package/dist/planning/impact.js.map +1 -0
  47. package/dist/planning/specification.d.ts +42 -0
  48. package/dist/planning/specification.d.ts.map +1 -0
  49. package/dist/planning/specification.js +235 -0
  50. package/dist/planning/specification.js.map +1 -0
  51. package/dist/qualification/public-alpha.d.ts +16 -0
  52. package/dist/qualification/public-alpha.d.ts.map +1 -0
  53. package/dist/qualification/public-alpha.js +128 -0
  54. package/dist/qualification/public-alpha.js.map +1 -0
  55. package/dist/recipes/node-typescript-next-web.d.ts +19 -0
  56. package/dist/recipes/node-typescript-next-web.d.ts.map +1 -0
  57. package/dist/recipes/node-typescript-next-web.js +136 -0
  58. package/dist/recipes/node-typescript-next-web.js.map +1 -0
  59. package/dist/repository/integration.d.ts +58 -0
  60. package/dist/repository/integration.d.ts.map +1 -0
  61. package/dist/repository/integration.js +1385 -0
  62. package/dist/repository/integration.js.map +1 -0
  63. package/dist/repository/scan.d.ts +27 -0
  64. package/dist/repository/scan.d.ts.map +1 -0
  65. package/dist/repository/scan.js +388 -0
  66. package/dist/repository/scan.js.map +1 -0
  67. package/dist/result.d.ts +23 -0
  68. package/dist/result.d.ts.map +1 -0
  69. package/dist/result.js +24 -0
  70. package/dist/result.js.map +1 -0
  71. package/dist/runtime/codex.d.ts +28 -0
  72. package/dist/runtime/codex.d.ts.map +1 -0
  73. package/dist/runtime/codex.js +439 -0
  74. package/dist/runtime/codex.js.map +1 -0
  75. package/dist/runtime/context.d.ts +10 -0
  76. package/dist/runtime/context.d.ts.map +1 -0
  77. package/dist/runtime/context.js +151 -0
  78. package/dist/runtime/context.js.map +1 -0
  79. package/dist/runtime/delivery.d.ts +58 -0
  80. package/dist/runtime/delivery.d.ts.map +1 -0
  81. package/dist/runtime/delivery.js +1073 -0
  82. package/dist/runtime/delivery.js.map +1 -0
  83. package/dist/runtime/dependencies.d.ts +19 -0
  84. package/dist/runtime/dependencies.d.ts.map +1 -0
  85. package/dist/runtime/dependencies.js +495 -0
  86. package/dist/runtime/dependencies.js.map +1 -0
  87. package/dist/runtime/github.d.ts +112 -0
  88. package/dist/runtime/github.d.ts.map +1 -0
  89. package/dist/runtime/github.js +510 -0
  90. package/dist/runtime/github.js.map +1 -0
  91. package/dist/runtime/inputs.d.ts +24 -0
  92. package/dist/runtime/inputs.d.ts.map +1 -0
  93. package/dist/runtime/inputs.js +237 -0
  94. package/dist/runtime/inputs.js.map +1 -0
  95. package/dist/runtime/lease.d.ts +11 -0
  96. package/dist/runtime/lease.d.ts.map +1 -0
  97. package/dist/runtime/lease.js +52 -0
  98. package/dist/runtime/lease.js.map +1 -0
  99. package/dist/runtime/lifecycle.d.ts +87 -0
  100. package/dist/runtime/lifecycle.d.ts.map +1 -0
  101. package/dist/runtime/lifecycle.js +1024 -0
  102. package/dist/runtime/lifecycle.js.map +1 -0
  103. package/dist/runtime/process.d.ts +39 -0
  104. package/dist/runtime/process.d.ts.map +1 -0
  105. package/dist/runtime/process.js +304 -0
  106. package/dist/runtime/process.js.map +1 -0
  107. package/dist/runtime/repository.d.ts +36 -0
  108. package/dist/runtime/repository.d.ts.map +1 -0
  109. package/dist/runtime/repository.js +458 -0
  110. package/dist/runtime/repository.js.map +1 -0
  111. package/dist/runtime/state.d.ts +121 -0
  112. package/dist/runtime/state.d.ts.map +1 -0
  113. package/dist/runtime/state.js +1017 -0
  114. package/dist/runtime/state.js.map +1 -0
  115. package/dist/runtime/verifier.d.ts +31 -0
  116. package/dist/runtime/verifier.d.ts.map +1 -0
  117. package/dist/runtime/verifier.js +478 -0
  118. package/dist/runtime/verifier.js.map +1 -0
  119. package/dist/runtime/worker.d.ts +67 -0
  120. package/dist/runtime/worker.d.ts.map +1 -0
  121. package/dist/runtime/worker.js +34 -0
  122. package/dist/runtime/worker.js.map +1 -0
  123. package/dist/security/safe-path.d.ts +3 -0
  124. package/dist/security/safe-path.d.ts.map +1 -0
  125. package/dist/security/safe-path.js +102 -0
  126. package/dist/security/safe-path.js.map +1 -0
  127. package/dist/version.d.ts +4 -0
  128. package/dist/version.d.ts.map +1 -0
  129. package/dist/version.js +4 -0
  130. package/dist/version.js.map +1 -0
  131. package/dist/workflows/founder.d.ts +33 -0
  132. package/dist/workflows/founder.d.ts.map +1 -0
  133. package/dist/workflows/founder.js +422 -0
  134. package/dist/workflows/founder.js.map +1 -0
  135. package/package.json +79 -0
  136. package/recipes/node-typescript-next-web/.github/workflows/ci.yml +34 -0
  137. package/recipes/node-typescript-next-web/.node-version +1 -0
  138. package/recipes/node-typescript-next-web/.tool-versions +1 -0
  139. package/recipes/node-typescript-next-web/AGENTS.md +44 -0
  140. package/recipes/node-typescript-next-web/README.md +16 -0
  141. package/recipes/node-typescript-next-web/WORKFLOW.md +6 -0
  142. package/recipes/node-typescript-next-web/app/api/health/route.ts +5 -0
  143. package/recipes/node-typescript-next-web/app/layout.tsx +19 -0
  144. package/recipes/node-typescript-next-web/app/page.tsx +12 -0
  145. package/recipes/node-typescript-next-web/app/styles.css +21 -0
  146. package/recipes/node-typescript-next-web/docs/development.md +16 -0
  147. package/recipes/node-typescript-next-web/eslint.config.mjs +9 -0
  148. package/recipes/node-typescript-next-web/gitignore.template +5 -0
  149. package/recipes/node-typescript-next-web/next-env.d.ts +7 -0
  150. package/recipes/node-typescript-next-web/next.config.ts +11 -0
  151. package/recipes/node-typescript-next-web/package-lock.json +7540 -0
  152. package/recipes/node-typescript-next-web/package.json +39 -0
  153. package/recipes/node-typescript-next-web/playwright.config.ts +23 -0
  154. package/recipes/node-typescript-next-web/recipe.yaml +71 -0
  155. package/recipes/node-typescript-next-web/scripts/check-package.mjs +3 -0
  156. package/recipes/node-typescript-next-web/src/health.ts +3 -0
  157. package/recipes/node-typescript-next-web/test/browser/home.spec.ts +14 -0
  158. package/recipes/node-typescript-next-web/test/integration/health-route.test.ts +11 -0
  159. package/recipes/node-typescript-next-web/test/unit/health.test.ts +9 -0
  160. package/recipes/node-typescript-next-web/tsconfig.json +33 -0
  161. package/schemas/README.md +64 -0
  162. package/schemas/audit-report.schema.json +59 -0
  163. package/schemas/blueprint.schema.json +34 -0
  164. package/schemas/context-manifest.schema.json +88 -0
  165. package/schemas/delivery-record.schema.json +266 -0
  166. package/schemas/impact-manifest.schema.json +146 -0
  167. package/schemas/managed-repository.schema.json +36 -0
  168. package/schemas/mill-config.schema.json +212 -0
  169. package/schemas/mill-lock.schema.json +96 -0
  170. package/schemas/outcome-plan.schema.json +51 -0
  171. package/schemas/product-contract.schema.json +181 -0
  172. package/schemas/public-alpha-qualification.schema.json +391 -0
  173. package/schemas/recipe-manifest.schema.json +187 -0
  174. package/schemas/release-evidence.schema.json +170 -0
  175. package/schemas/repository-integration-plan.schema.json +156 -0
  176. package/schemas/review-result.schema.json +57 -0
  177. package/schemas/scenario-set.schema.json +84 -0
  178. package/schemas/source-manifest.schema.json +96 -0
  179. package/schemas/specification-proposal.schema.json +406 -0
  180. package/schemas/support-tuple.schema.json +107 -0
  181. package/schemas/task-packet.schema.json +395 -0
  182. package/schemas/validation-evidence.schema.json +125 -0
  183. package/schemas/worker-invocation.schema.json +119 -0
  184. package/schemas/worker-profile.schema.json +53 -0
@@ -0,0 +1,1020 @@
1
+ import path from "node:path";
2
+ import { Command, CommanderError, InvalidArgumentError } from "commander";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { auditRepository } from "./audit/repository.js";
5
+ import { findRepositoryRoot, enforceExactVersion } from "./config/lock.js";
6
+ import { contractSchemas } from "./contracts/schemas.js";
7
+ import { doctor, doctorReady } from "./doctor.js";
8
+ import { asMillError, ExitCode, MillError } from "./errors.js";
9
+ import { inspectPrd } from "./intake/prd.js";
10
+ import { scanRepository } from "./repository/scan.js";
11
+ import { applyAdoptionIntegration, applyGreenfieldIntegration, planAdoptionIntegration, planDetach, planGreenfieldIntegration, } from "./repository/integration.js";
12
+ import { assessSpecificationProposal, loadPlanningSources, loadSpecificationProposal, promoteSpecificationProposal, semanticProposalDiff, } from "./planning/specification.js";
13
+ import { assessImpactManifest, loadImpactPlanningInputs, } from "./planning/impact.js";
14
+ import { loadPublicAlphaQualification } from "./qualification/public-alpha.js";
15
+ import { finalizeDraftPr, observeDraftPr, openDraftPr, planDraftPr, reconcileDraftPr, } from "./runtime/delivery.js";
16
+ import { cancelRun, codexAuthStatus, qualifyBaseline, resumeRun, reviewRun, runStatus, startLocalRun, stateBackup, statePurge, stateRestore, supportBundle, verifyRun, } from "./runtime/lifecycle.js";
17
+ import { commandResult, formatHuman } from "./result.js";
18
+ import { safeReadText } from "./security/safe-path.js";
19
+ import { MILL_VERSION } from "./version.js";
20
+ import { prepareRepositoryDependencies, startNextReadyOutcome, startFounderDelivery, } from "./workflows/founder.js";
21
+ function parseMode(value) {
22
+ if (value === "inspect" || value === "build" || value === "propose") {
23
+ return value;
24
+ }
25
+ throw new InvalidArgumentError("mode must be inspect, build, or propose");
26
+ }
27
+ function emit(io, json, result) {
28
+ io.stdout.write(json ? `${JSON.stringify(result)}\n` : formatHuman(result));
29
+ }
30
+ function globals(program) {
31
+ return program.opts();
32
+ }
33
+ function requiredValue(value, option) {
34
+ if (value === undefined || value.trim().length === 0) {
35
+ throw new MillError("USAGE_ERROR", `${option} is required for this operation.`, ExitCode.usage);
36
+ }
37
+ return value;
38
+ }
39
+ function integrationOptions(root, options) {
40
+ return {
41
+ sourceRoot: root,
42
+ prdPath: requiredValue(options.prd, "--prd"),
43
+ sourceManifestPath: requiredValue(options.sources, "--sources"),
44
+ proposalPath: requiredValue(options.proposal, "--proposal"),
45
+ productApprovalDigest: requiredValue(options.approveProduct, "--approve-product"),
46
+ repositoryId: requiredValue(options.repositoryId, "--repository-id"),
47
+ approvedBy: requiredValue(options.approvedBy, "--approved-by"),
48
+ approvedAt: requiredValue(options.approvedAt, "--approved-at"),
49
+ authorName: requiredValue(options.authorName, "--author-name"),
50
+ authorEmail: requiredValue(options.authorEmail, "--author-email"),
51
+ };
52
+ }
53
+ export function createProgram(io, jsonErrors = false) {
54
+ const program = new Command();
55
+ program
56
+ .name("millctl")
57
+ .description("Local-first, repo-native software delivery")
58
+ .version(MILL_VERSION)
59
+ .option("--json", "emit a stable JSON result envelope")
60
+ .option("--cwd <path>", "repository or directory to inspect", process.cwd())
61
+ .exitOverride()
62
+ .configureOutput({
63
+ writeOut: (value) => {
64
+ if (!jsonErrors) {
65
+ io.stdout.write(value);
66
+ }
67
+ },
68
+ writeErr: (value) => {
69
+ if (!jsonErrors) {
70
+ io.stderr.write(value);
71
+ }
72
+ },
73
+ });
74
+ program
75
+ .command("doctor")
76
+ .description("report host and repository readiness without running repository code")
77
+ .option("--mode <mode>", "readiness target", parseMode, "inspect")
78
+ .action(async (options) => {
79
+ const global = globals(program);
80
+ const report = await doctor(global.cwd, options.mode);
81
+ const ok = doctorReady(report);
82
+ emit(io, global.json === true, commandResult({
83
+ command: "doctor",
84
+ ok,
85
+ data: report,
86
+ reasons: ok
87
+ ? []
88
+ : [
89
+ {
90
+ code: "READINESS_BLOCKED",
91
+ message: "One or more required runtime, tool, or lock checks failed.",
92
+ },
93
+ ],
94
+ }));
95
+ if (!ok) {
96
+ throw new MillError("READINESS_BLOCKED", "Readiness checks failed.", ExitCode.unavailable, { resultAlreadyEmitted: true });
97
+ }
98
+ });
99
+ program
100
+ .command("inspect")
101
+ .description("inspect an untrusted PRD without executing it")
102
+ .requiredOption("--prd <path>", "PRD path inside the selected root")
103
+ .action(async (options) => {
104
+ const global = globals(program);
105
+ const root = await findRepositoryRoot(global.cwd);
106
+ await enforceExactVersion(root);
107
+ const inspection = await inspectPrd(root, options.prd);
108
+ emit(io, global.json === true, commandResult({ command: "inspect", ok: true, data: inspection }));
109
+ });
110
+ program
111
+ .command("new <directory>")
112
+ .description("preview or transactionally create the supported web recipe")
113
+ .option("--dry-run", "return the exact approved file plan without writes")
114
+ .option("--apply", "apply the exact approved file plan")
115
+ .option("--prd <path>", "PRD path inside the selected root")
116
+ .option("--sources <path>", "source manifest path")
117
+ .option("--proposal <path>", "approved specification proposal path")
118
+ .option("--approve-product <digest>", "exact specification approval digest")
119
+ .option("--approve-plan <digest>", "exact integration-plan approval digest")
120
+ .option("--repository-id <uuid>", "new stable repository UUID")
121
+ .option("--approved-by <identity>", "human integration approver")
122
+ .option("--approved-at <time>", "exact ISO approval time")
123
+ .option("--author-name <name>", "initial commit author name")
124
+ .option("--author-email <email>", "initial commit author email")
125
+ .option("--attended", "approve disclosed registry and local Git effects")
126
+ .action(async (directory, options) => {
127
+ const global = globals(program);
128
+ const root = await findRepositoryRoot(global.cwd);
129
+ await enforceExactVersion(root);
130
+ if (options.dryRun === options.apply) {
131
+ throw new MillError("USAGE_ERROR", "Choose exactly one of --dry-run or --apply.", ExitCode.usage);
132
+ }
133
+ const common = {
134
+ ...integrationOptions(root, options),
135
+ targetDirectory: directory,
136
+ };
137
+ if (options.dryRun === true) {
138
+ const planned = await planGreenfieldIntegration(common);
139
+ emit(io, global.json === true, commandResult({
140
+ command: "new.plan",
141
+ ok: true,
142
+ data: {
143
+ plan: planned.plan,
144
+ approvalDigest: planned.approvalDigest,
145
+ },
146
+ }));
147
+ return;
148
+ }
149
+ if (options.attended !== true) {
150
+ throw new MillError("ATTENDANCE_REQUIRED", "Greenfield apply requires attended registry and local Git authority.", ExitCode.configuration);
151
+ }
152
+ const result = await applyGreenfieldIntegration({
153
+ ...common,
154
+ attended: true,
155
+ planApprovalDigest: requiredValue(options.approvePlan, "--approve-plan"),
156
+ });
157
+ emit(io, global.json === true, commandResult({ command: "new.apply", ok: true, data: result }));
158
+ });
159
+ program
160
+ .command("adopt")
161
+ .description("scan, plan, or transactionally integrate an existing repository")
162
+ .option("--scan-only", "perform only the static adoption scan")
163
+ .option("--plan", "return an exact adoption file plan")
164
+ .option("--apply", "apply an approved plan in an isolated branch")
165
+ .option("--prd <path>", "repo-native PRD path")
166
+ .option("--sources <path>", "source manifest path")
167
+ .option("--proposal <path>", "approved specification proposal path")
168
+ .option("--approve-product <digest>", "exact specification approval digest")
169
+ .option("--approve-plan <digest>", "exact integration-plan approval digest")
170
+ .option("--repository-id <uuid>", "new stable repository UUID")
171
+ .option("--approved-by <identity>", "human integration approver")
172
+ .option("--approved-at <time>", "exact ISO approval time")
173
+ .option("--author-name <name>", "adoption commit author name")
174
+ .option("--author-email <email>", "adoption commit author email")
175
+ .option("--attended", "approve isolated local branch creation")
176
+ .action(async (options) => {
177
+ const global = globals(program);
178
+ const root = await findRepositoryRoot(global.cwd);
179
+ await enforceExactVersion(root);
180
+ const selected = [options.scanOnly, options.plan, options.apply].filter((value) => value === true).length;
181
+ if (selected !== 1) {
182
+ throw new MillError("USAGE_ERROR", "Choose exactly one of --scan-only, --plan, or --apply.", ExitCode.usage);
183
+ }
184
+ if (options.plan === true || options.apply === true) {
185
+ const common = {
186
+ ...integrationOptions(root, options),
187
+ repositoryRoot: root,
188
+ };
189
+ if (options.plan === true) {
190
+ const planned = await planAdoptionIntegration(common);
191
+ emit(io, global.json === true, commandResult({
192
+ command: "adopt.plan",
193
+ ok: true,
194
+ data: {
195
+ plan: planned.plan,
196
+ approvalDigest: planned.approvalDigest,
197
+ },
198
+ }));
199
+ return;
200
+ }
201
+ if (options.attended !== true) {
202
+ throw new MillError("ATTENDANCE_REQUIRED", "Adoption apply requires attended local branch authority.", ExitCode.configuration);
203
+ }
204
+ const result = await applyAdoptionIntegration({
205
+ ...common,
206
+ attended: true,
207
+ planApprovalDigest: requiredValue(options.approvePlan, "--approve-plan"),
208
+ });
209
+ emit(io, global.json === true, commandResult({ command: "adopt.apply", ok: true, data: result }));
210
+ return;
211
+ }
212
+ const report = await scanRepository(root);
213
+ const blocked = report.gitConfigHazards.length > 0 ||
214
+ report.truncatedDirectories.length > 0 ||
215
+ report.symlinksSkipped.length > 0 ||
216
+ report.secretReferences.length > 0;
217
+ const reasons = [
218
+ ...(report.gitConfigHazards.length > 0
219
+ ? [
220
+ {
221
+ code: "UNSAFE_GIT_CONFIGURATION",
222
+ message: "Repository Git configuration requires human disposition.",
223
+ },
224
+ ]
225
+ : []),
226
+ ...(report.truncatedDirectories.length > 0
227
+ ? [
228
+ {
229
+ code: "SCAN_INCOMPLETE",
230
+ message: "Repository scan reached its depth limit and is incomplete.",
231
+ },
232
+ ]
233
+ : []),
234
+ ...(report.symlinksSkipped.length > 0
235
+ ? [
236
+ {
237
+ code: "SYMLINKS_UNSUPPORTED",
238
+ message: "Repository symbolic links require human disposition before adoption.",
239
+ },
240
+ ]
241
+ : []),
242
+ ...(report.secretReferences.length > 0
243
+ ? [
244
+ {
245
+ code: "SENSITIVE_PATHS_PRESENT",
246
+ message: "Repository credential-like files require human disposition before adoption.",
247
+ },
248
+ ]
249
+ : []),
250
+ ];
251
+ emit(io, global.json === true, commandResult({
252
+ command: "adopt.scan",
253
+ ok: !blocked,
254
+ status: blocked ? "blocked" : "ok",
255
+ data: report,
256
+ reasons,
257
+ }));
258
+ if (blocked) {
259
+ throw new MillError(reasons[0]?.code ?? "SCAN_INCOMPLETE", reasons[0]?.message ?? "Repository scan is incomplete.", ExitCode.configuration, { resultAlreadyEmitted: true });
260
+ }
261
+ });
262
+ const dependencies = program
263
+ .command("dependencies")
264
+ .description("manage exact local verifier dependency snapshots");
265
+ dependencies
266
+ .command("prepare")
267
+ .description("prepare one lock-bound snapshot through the exact OCI image")
268
+ .requiredOption("--attended", "approve disclosed npm registry network access")
269
+ .action(async () => {
270
+ const global = globals(program);
271
+ const root = await findRepositoryRoot(global.cwd);
272
+ await enforceExactVersion(root);
273
+ const result = await prepareRepositoryDependencies(root, true);
274
+ emit(io, global.json === true, commandResult({
275
+ command: "dependencies.prepare",
276
+ ok: true,
277
+ data: result ?? { configured: false },
278
+ }));
279
+ });
280
+ const detach = program
281
+ .command("detach")
282
+ .description("inspect manual Mill detachment");
283
+ detach
284
+ .command("plan")
285
+ .description("report Mill-only removals and downstream-owned retained files")
286
+ .action(async () => {
287
+ const global = globals(program);
288
+ const root = await findRepositoryRoot(global.cwd);
289
+ await enforceExactVersion(root);
290
+ emit(io, global.json === true, commandResult({
291
+ command: "detach.plan",
292
+ ok: true,
293
+ data: await planDetach(root),
294
+ }));
295
+ });
296
+ program
297
+ .command("validate-contract")
298
+ .description("validate one compact Mill YAML or JSON contract")
299
+ .requiredOption("--kind <kind>", "contract kind")
300
+ .requiredOption("--file <path>", "contract path inside the selected root")
301
+ .action(async (options) => {
302
+ const global = globals(program);
303
+ const root = await findRepositoryRoot(global.cwd);
304
+ await enforceExactVersion(root);
305
+ if (!Object.hasOwn(contractSchemas, options.kind)) {
306
+ throw new MillError("UNKNOWN_CONTRACT_KIND", `Unknown contract kind: ${options.kind}`, ExitCode.usage, { supported: Object.keys(contractSchemas) });
307
+ }
308
+ const source = await safeReadText(root, options.file);
309
+ const extension = path.extname(options.file).toLowerCase();
310
+ let raw;
311
+ try {
312
+ raw = extension === ".json" ? JSON.parse(source) : parseYaml(source);
313
+ }
314
+ catch (error) {
315
+ throw new MillError("INVALID_CONTRACT", `Contract syntax is invalid: ${String(error)}`, ExitCode.data);
316
+ }
317
+ const parsed = contractSchemas[options.kind].safeParse(raw);
318
+ if (!parsed.success) {
319
+ throw new MillError("INVALID_CONTRACT", "Contract validation failed.", ExitCode.data, { issues: parsed.error.issues });
320
+ }
321
+ emit(io, global.json === true, commandResult({
322
+ command: "validate-contract",
323
+ ok: true,
324
+ data: { kind: options.kind, file: options.file },
325
+ }));
326
+ });
327
+ const plan = program
328
+ .command("plan")
329
+ .description("compile and assess product-continuity authority without writes");
330
+ plan
331
+ .command("specification")
332
+ .description("assess one source-backed product proposal and approval digest")
333
+ .requiredOption("--prd <path>", "PRD path inside the selected root")
334
+ .requiredOption("--sources <path>", "source manifest path inside the selected root")
335
+ .requiredOption("--proposal <path>", "specification proposal path")
336
+ .action(async (options) => {
337
+ const global = globals(program);
338
+ const root = await findRepositoryRoot(global.cwd);
339
+ await enforceExactVersion(root);
340
+ const [planning, proposal] = await Promise.all([
341
+ loadPlanningSources({
342
+ root,
343
+ prdPath: options.prd,
344
+ sourceManifestPath: options.sources,
345
+ }),
346
+ loadSpecificationProposal(root, options.proposal),
347
+ ]);
348
+ const assessment = assessSpecificationProposal({
349
+ proposal,
350
+ prdPath: planning.prdPath,
351
+ prdDigest: planning.prdDigest,
352
+ sourceManifest: planning.sourceManifest,
353
+ sourceManifestDigest: planning.sourceManifestDigest,
354
+ });
355
+ emit(io, global.json === true, commandResult({
356
+ command: "plan.specification",
357
+ ok: assessment.promotable,
358
+ status: assessment.promotable ? "ok" : "blocked",
359
+ data: assessment,
360
+ reasons: assessment.blockers.map((message) => ({
361
+ code: "SPECIFICATION_PROMOTION_BLOCKED",
362
+ message,
363
+ })),
364
+ }));
365
+ if (!assessment.promotable) {
366
+ throw new MillError("SPECIFICATION_PROMOTION_BLOCKED", "The specification proposal is not promotable.", ExitCode.configuration, { resultAlreadyEmitted: true });
367
+ }
368
+ });
369
+ plan
370
+ .command("promote")
371
+ .description("return exact frozen artifacts for one explicitly approved proposal")
372
+ .requiredOption("--prd <path>", "PRD path inside the selected root")
373
+ .requiredOption("--sources <path>", "source manifest path")
374
+ .requiredOption("--proposal <path>", "specification proposal path")
375
+ .requiredOption("--approve <digest>", "exact proposal digest")
376
+ .action(async (options) => {
377
+ const global = globals(program);
378
+ const root = await findRepositoryRoot(global.cwd);
379
+ await enforceExactVersion(root);
380
+ const [planning, proposal] = await Promise.all([
381
+ loadPlanningSources({
382
+ root,
383
+ prdPath: options.prd,
384
+ sourceManifestPath: options.sources,
385
+ }),
386
+ loadSpecificationProposal(root, options.proposal),
387
+ ]);
388
+ const assessment = assessSpecificationProposal({
389
+ proposal,
390
+ prdPath: planning.prdPath,
391
+ prdDigest: planning.prdDigest,
392
+ sourceManifest: planning.sourceManifest,
393
+ sourceManifestDigest: planning.sourceManifestDigest,
394
+ });
395
+ const promoted = promoteSpecificationProposal({
396
+ proposal,
397
+ approvalDigest: options.approve,
398
+ assessment,
399
+ });
400
+ emit(io, global.json === true, commandResult({
401
+ command: "plan.promote",
402
+ ok: true,
403
+ data: promoted,
404
+ }));
405
+ });
406
+ plan
407
+ .command("diff")
408
+ .description("compare a regenerated proposal without replacing approval")
409
+ .requiredOption("--approved <path>", "approved proposal path")
410
+ .requiredOption("--proposal <path>", "regenerated proposal path")
411
+ .action(async (options) => {
412
+ const global = globals(program);
413
+ const root = await findRepositoryRoot(global.cwd);
414
+ await enforceExactVersion(root);
415
+ const [approved, proposal] = await Promise.all([
416
+ loadSpecificationProposal(root, options.approved),
417
+ loadSpecificationProposal(root, options.proposal),
418
+ ]);
419
+ emit(io, global.json === true, commandResult({
420
+ command: "plan.diff",
421
+ ok: true,
422
+ data: { changedPaths: semanticProposalDiff(approved, proposal) },
423
+ }));
424
+ });
425
+ plan
426
+ .command("impact")
427
+ .description("assess one exact approved JIT impact manifest")
428
+ .requiredOption("--product <path>", "product contract path")
429
+ .requiredOption("--scenarios <path>", "scenario set path")
430
+ .requiredOption("--manifest <path>", "impact manifest path")
431
+ .action(async (options) => {
432
+ const global = globals(program);
433
+ const root = await findRepositoryRoot(global.cwd);
434
+ await enforceExactVersion(root);
435
+ const inputs = await loadImpactPlanningInputs({
436
+ root,
437
+ productPath: options.product,
438
+ scenarioPath: options.scenarios,
439
+ impactPath: options.manifest,
440
+ });
441
+ const assessment = assessImpactManifest(inputs);
442
+ emit(io, global.json === true, commandResult({
443
+ command: "plan.impact",
444
+ ok: assessment.approved,
445
+ status: assessment.approved ? "ok" : "blocked",
446
+ data: assessment,
447
+ reasons: assessment.blockers.map((message) => ({
448
+ code: "IMPACT_PROMOTION_BLOCKED",
449
+ message,
450
+ })),
451
+ }));
452
+ if (!assessment.approved) {
453
+ throw new MillError("IMPACT_PROMOTION_BLOCKED", "The impact manifest is not approved for execution.", ExitCode.configuration, { resultAlreadyEmitted: true });
454
+ }
455
+ });
456
+ const auth = program
457
+ .command("auth")
458
+ .description("inspect adapter authentication readiness");
459
+ auth
460
+ .command("status")
461
+ .description("report operator-owned Codex authentication readiness")
462
+ .action(async () => {
463
+ const global = globals(program);
464
+ const root = await findRepositoryRoot(global.cwd);
465
+ await enforceExactVersion(root);
466
+ const status = await codexAuthStatus(root);
467
+ emit(io, global.json === true, commandResult({
468
+ command: "auth.status",
469
+ ok: status.available,
470
+ data: status,
471
+ }));
472
+ if (!status.available) {
473
+ throw new MillError("CODEX_AUTH_UNAVAILABLE", "The operator's Codex CLI is not logged in.", ExitCode.unavailable, { resultAlreadyEmitted: true });
474
+ }
475
+ });
476
+ const qualify = program
477
+ .command("qualify")
478
+ .description("qualify declared commands in a disposable exact-base worktree")
479
+ .option("--baseline", "qualify the pre-change base")
480
+ .option("--task <path>", "approved task packet path")
481
+ .action(async (options) => {
482
+ const global = globals(program);
483
+ const root = await findRepositoryRoot(global.cwd);
484
+ await enforceExactVersion(root);
485
+ if (options.baseline !== true) {
486
+ throw new MillError("USAGE_ERROR", "Baseline qualification requires --baseline.", ExitCode.usage);
487
+ }
488
+ const result = await qualifyBaseline({
489
+ root,
490
+ taskPath: requiredValue(options.task, "--task"),
491
+ });
492
+ const { evidence } = result;
493
+ emit(io, global.json === true, commandResult({
494
+ command: "qualify.baseline",
495
+ ok: evidence.passed,
496
+ status: evidence.passed ? "ok" : "blocked",
497
+ data: result,
498
+ reasons: evidence.passed
499
+ ? []
500
+ : [
501
+ {
502
+ code: "BASELINE_QUALIFICATION_FAILED",
503
+ message: "A required baseline command failed or was blocked.",
504
+ },
505
+ ],
506
+ }));
507
+ if (!evidence.passed) {
508
+ throw new MillError("BASELINE_QUALIFICATION_FAILED", "A required baseline command failed or was blocked.", ExitCode.configuration, { resultAlreadyEmitted: true });
509
+ }
510
+ });
511
+ qualify
512
+ .command("public-alpha")
513
+ .description("assess one exact longitudinal and clean-room qualification record")
514
+ .requiredOption("--file <path>", "qualification record path")
515
+ .action(async (options) => {
516
+ const global = globals(program);
517
+ const root = await findRepositoryRoot(global.cwd);
518
+ await enforceExactVersion(root);
519
+ const assessment = await loadPublicAlphaQualification({
520
+ root,
521
+ file: options.file,
522
+ });
523
+ emit(io, global.json === true, commandResult({
524
+ command: "qualify.public-alpha",
525
+ ok: assessment.passed,
526
+ status: assessment.passed ? "ok" : "blocked",
527
+ data: assessment,
528
+ reasons: assessment.blockers.map((message) => ({
529
+ code: "PUBLIC_ALPHA_QUALIFICATION_BLOCKED",
530
+ message,
531
+ })),
532
+ }));
533
+ if (!assessment.passed) {
534
+ throw new MillError("PUBLIC_ALPHA_QUALIFICATION_BLOCKED", "The public-alpha qualification record is not promotable.", ExitCode.configuration, { resultAlreadyEmitted: true });
535
+ }
536
+ });
537
+ program
538
+ .command("audit")
539
+ .description("run the bounded read-only public-alpha repository audit")
540
+ .action(async () => {
541
+ const global = globals(program);
542
+ const root = await findRepositoryRoot(global.cwd);
543
+ await enforceExactVersion(root);
544
+ const report = await auditRepository({ root });
545
+ const ok = report.status === "passed";
546
+ emit(io, global.json === true, commandResult({
547
+ command: "audit",
548
+ ok,
549
+ status: ok ? "ok" : "blocked",
550
+ data: report,
551
+ reasons: report.checks
552
+ .filter((check) => check.status === "blocked")
553
+ .map((check) => ({
554
+ code: "AUDIT_CHECK_BLOCKED",
555
+ message: check.summary,
556
+ details: { category: check.category, checkId: check.id },
557
+ })),
558
+ }));
559
+ if (!ok) {
560
+ throw new MillError("AUDIT_BLOCKED", "One or more bounded audit categories are blocked.", ExitCode.configuration, { resultAlreadyEmitted: true });
561
+ }
562
+ });
563
+ const run = program
564
+ .command("run")
565
+ .description("build one explicitly approved task in an isolated local worktree")
566
+ .option("--task <path>", "approved task packet path")
567
+ .option("--approve <digest>", "approval digest from successful matching baseline qualification")
568
+ .option("--attended", "acknowledge attended trusted-host Codex execution")
569
+ .action(async (options) => {
570
+ const global = globals(program);
571
+ const root = await findRepositoryRoot(global.cwd);
572
+ await enforceExactVersion(root);
573
+ if (options.attended !== true) {
574
+ throw new MillError("ATTENDANCE_REQUIRED", "Run requires attended trusted-host Codex authority.", ExitCode.configuration);
575
+ }
576
+ const result = await startLocalRun({
577
+ root,
578
+ taskPath: requiredValue(options.task, "--task"),
579
+ approvalDigest: requiredValue(options.approve, "--approve"),
580
+ });
581
+ emit(io, global.json === true, commandResult({ command: "run", ok: true, data: result }));
582
+ });
583
+ run
584
+ .command("next")
585
+ .description("run the one ready approved outcome through the existing lifecycle")
586
+ .requiredOption("--approve <digest>", "approval digest from successful matching baseline qualification")
587
+ .requiredOption("--attended", "acknowledge attended trusted-host execution")
588
+ .action(async (options) => {
589
+ const global = globals(program);
590
+ const root = await findRepositoryRoot(global.cwd);
591
+ await enforceExactVersion(root);
592
+ const started = await startNextReadyOutcome({
593
+ root,
594
+ approvalDigest: options.approve,
595
+ });
596
+ emit(io, global.json === true, commandResult({
597
+ command: "run.next",
598
+ ok: true,
599
+ data: { outcome: started.outcome, ...started.result },
600
+ }));
601
+ });
602
+ program
603
+ .command("start")
604
+ .description("advance the one ready outcome through the resumable founder path")
605
+ .requiredOption("--prd <path>", "approved repo-native PRD path")
606
+ .option("--repo <path>", "managed repository path")
607
+ .option("--draft-pr", "prepare the later exact reviewed candidate's draft-PR plan")
608
+ .requiredOption("--attended", "approve attended dependency, model, and scoped forge use")
609
+ .action(async (options) => {
610
+ const global = globals(program);
611
+ const root = await findRepositoryRoot(options.repo ?? global.cwd);
612
+ await enforceExactVersion(root);
613
+ const inspectedPrd = path
614
+ .normalize(options.prd)
615
+ .split(path.sep)
616
+ .join("/");
617
+ await safeReadText(root, inspectedPrd, 2 * 1024 * 1024);
618
+ const result = await startFounderDelivery({
619
+ root,
620
+ prdPath: inspectedPrd,
621
+ attended: true,
622
+ draftPr: options.draftPr === true,
623
+ });
624
+ emit(io, global.json === true, commandResult({ command: "start", ok: true, data: result }));
625
+ });
626
+ program
627
+ .command("status")
628
+ .description("report durable local run state")
629
+ .option("--run <id>", "run identifier")
630
+ .action(async (options) => {
631
+ const global = globals(program);
632
+ const root = await findRepositoryRoot(global.cwd);
633
+ await enforceExactVersion(root);
634
+ const data = await runStatus({
635
+ root,
636
+ ...(options.run === undefined ? {} : { runId: options.run }),
637
+ });
638
+ emit(io, global.json === true, commandResult({ command: "status", ok: true, data }));
639
+ });
640
+ program
641
+ .command("verify")
642
+ .description("validate an exact committed candidate through declared commands")
643
+ .requiredOption("--task <path>", "approved task packet path")
644
+ .requiredOption("--run <id>", "run identifier")
645
+ .action(async (options) => {
646
+ const global = globals(program);
647
+ const root = await findRepositoryRoot(global.cwd);
648
+ await enforceExactVersion(root);
649
+ const result = await verifyRun({
650
+ root,
651
+ taskPath: options.task,
652
+ runId: options.run,
653
+ });
654
+ emit(io, global.json === true, commandResult({
655
+ command: "verify",
656
+ ok: result.evidence.passed,
657
+ status: result.evidence.passed ? "ok" : "blocked",
658
+ data: result,
659
+ reasons: result.evidence.passed
660
+ ? []
661
+ : [
662
+ {
663
+ code: "VALIDATION_FAILED",
664
+ message: "A required command failed or was blocked.",
665
+ },
666
+ ],
667
+ }));
668
+ if (!result.evidence.passed) {
669
+ throw new MillError("VALIDATION_FAILED", "A required command failed or was blocked.", ExitCode.configuration, { resultAlreadyEmitted: true });
670
+ }
671
+ });
672
+ program
673
+ .command("review")
674
+ .description("obtain a fresh read-only review of the exact verified candidate")
675
+ .requiredOption("--task <path>", "approved task packet path")
676
+ .requiredOption("--run <id>", "run identifier")
677
+ .action(async (options) => {
678
+ const global = globals(program);
679
+ const root = await findRepositoryRoot(global.cwd);
680
+ await enforceExactVersion(root);
681
+ const result = await reviewRun({
682
+ root,
683
+ taskPath: options.task,
684
+ runId: options.run,
685
+ });
686
+ const ok = result.review.findings.length === 0;
687
+ emit(io, global.json === true, commandResult({
688
+ command: "review",
689
+ ok,
690
+ status: ok ? "ok" : "blocked",
691
+ data: result,
692
+ reasons: ok
693
+ ? []
694
+ : [
695
+ {
696
+ code: result.run.blockCode ?? "REVIEW_FINDINGS",
697
+ message: "The exact-candidate review reported actionable findings.",
698
+ },
699
+ ],
700
+ }));
701
+ if (!ok) {
702
+ throw new MillError(result.run.blockCode ?? "REVIEW_FINDINGS", "The exact-candidate review reported actionable findings.", ExitCode.configuration, { resultAlreadyEmitted: true });
703
+ }
704
+ });
705
+ program
706
+ .command("resume")
707
+ .description("resume one safe blocked checkpoint within its retry budget")
708
+ .requiredOption("--task <path>", "approved task packet path")
709
+ .requiredOption("--run <id>", "run identifier")
710
+ .action(async (options) => {
711
+ const global = globals(program);
712
+ const root = await findRepositoryRoot(global.cwd);
713
+ await enforceExactVersion(root);
714
+ const run = await resumeRun({
715
+ root,
716
+ taskPath: options.task,
717
+ runId: options.run,
718
+ });
719
+ emit(io, global.json === true, commandResult({ command: "resume", ok: true, data: { run } }));
720
+ });
721
+ const pr = program
722
+ .command("pr")
723
+ .description("deliver an exact reviewed candidate through a draft PR");
724
+ pr.command("plan")
725
+ .description("read live GitHub identity and create a local approval plan")
726
+ .requiredOption("--task <path>", "approved task packet path")
727
+ .requiredOption("--run <id>", "run identifier")
728
+ .action(async (options) => {
729
+ const global = globals(program);
730
+ const root = await findRepositoryRoot(global.cwd);
731
+ await enforceExactVersion(root);
732
+ const result = await planDraftPr({
733
+ root,
734
+ taskPath: options.task,
735
+ runId: options.run,
736
+ });
737
+ emit(io, global.json === true, commandResult({ command: "pr.plan", ok: true, data: result }));
738
+ });
739
+ pr.command("open")
740
+ .description("push and open the explicitly approved draft PR")
741
+ .requiredOption("--task <path>", "approved task packet path")
742
+ .requiredOption("--run <id>", "run identifier")
743
+ .requiredOption("--approve <digest>", "exact, unexpired proposal digest returned by pr plan")
744
+ .requiredOption("--attended", "acknowledge attended use of the operator-owned GitHub session")
745
+ .action(async (options) => {
746
+ const global = globals(program);
747
+ const root = await findRepositoryRoot(global.cwd);
748
+ await enforceExactVersion(root);
749
+ const result = await openDraftPr({
750
+ root,
751
+ taskPath: options.task,
752
+ runId: options.run,
753
+ approvalDigest: options.approve,
754
+ attended: options.attended,
755
+ });
756
+ emit(io, global.json === true, commandResult({ command: "pr.open", ok: true, data: result }));
757
+ });
758
+ pr.command("reconcile")
759
+ .description("classify one unknown GitHub effect through readback only")
760
+ .requiredOption("--task <path>", "approved task packet path")
761
+ .requiredOption("--run <id>", "run identifier")
762
+ .action(async (options) => {
763
+ const global = globals(program);
764
+ const root = await findRepositoryRoot(global.cwd);
765
+ await enforceExactVersion(root);
766
+ const result = await reconcileDraftPr({
767
+ root,
768
+ taskPath: options.task,
769
+ runId: options.run,
770
+ });
771
+ emit(io, global.json === true, commandResult({ command: "pr.reconcile", ok: true, data: result }));
772
+ });
773
+ pr.command("observe")
774
+ .description("read exact-head checks and configured GitHub review policy")
775
+ .requiredOption("--task <path>", "approved task packet path")
776
+ .requiredOption("--run <id>", "run identifier")
777
+ .action(async (options) => {
778
+ const global = globals(program);
779
+ const root = await findRepositoryRoot(global.cwd);
780
+ await enforceExactVersion(root);
781
+ const result = await observeDraftPr({
782
+ root,
783
+ taskPath: options.task,
784
+ runId: options.run,
785
+ });
786
+ const blocked = result.run.status === "blocked";
787
+ emit(io, global.json === true, commandResult({
788
+ command: "pr.observe",
789
+ ok: !blocked,
790
+ status: blocked ? "blocked" : "ok",
791
+ data: result,
792
+ reasons: blocked
793
+ ? [
794
+ {
795
+ code: result.run.blockCode ?? "REMOTE_POLICY_BLOCKED",
796
+ message: "GitHub checks or configured review policy blocked this exact candidate.",
797
+ },
798
+ ]
799
+ : [],
800
+ }));
801
+ if (blocked) {
802
+ throw new MillError(result.run.blockCode ?? "REMOTE_POLICY_BLOCKED", "GitHub policy blocked this exact candidate.", ExitCode.configuration, { resultAlreadyEmitted: true });
803
+ }
804
+ });
805
+ pr.command("finalize")
806
+ .description("read back human merge and exact main checks before closure")
807
+ .requiredOption("--task <path>", "approved task packet path")
808
+ .requiredOption("--run <id>", "run identifier")
809
+ .action(async (options) => {
810
+ const global = globals(program);
811
+ const root = await findRepositoryRoot(global.cwd);
812
+ await enforceExactVersion(root);
813
+ const result = await finalizeDraftPr({
814
+ root,
815
+ taskPath: options.task,
816
+ runId: options.run,
817
+ });
818
+ const closed = result.run.status === "closed";
819
+ const reasonCode = result.run.status === "blocked"
820
+ ? (result.run.blockCode ?? "POST_MERGE_CHECKS_FAILED")
821
+ : "POST_MERGE_CHECKS_PENDING";
822
+ emit(io, global.json === true, commandResult({
823
+ command: "pr.finalize",
824
+ ok: closed,
825
+ status: closed ? "ok" : "blocked",
826
+ data: result,
827
+ reasons: closed
828
+ ? []
829
+ : [
830
+ {
831
+ code: reasonCode,
832
+ message: "Post-merge required checks have not produced passing exact-commit evidence.",
833
+ },
834
+ ],
835
+ }));
836
+ if (!closed) {
837
+ throw new MillError(reasonCode, "Post-merge verification is not complete.", result.run.status === "blocked"
838
+ ? ExitCode.configuration
839
+ : ExitCode.temporary, { resultAlreadyEmitted: true });
840
+ }
841
+ });
842
+ program
843
+ .command("ship")
844
+ .description("plan or open one draft PR through the existing delivery state machine")
845
+ .requiredOption("--draft", "limit shipping to one draft pull request")
846
+ .requiredOption("--task <path>", "approved task packet path")
847
+ .requiredOption("--run <id>", "reviewed run identifier")
848
+ .option("--approve <digest>", "exact unexpired digest returned by this wrapper")
849
+ .option("--attended", "acknowledge attended GitHub authority")
850
+ .action(async (options) => {
851
+ const global = globals(program);
852
+ const root = await findRepositoryRoot(global.cwd);
853
+ await enforceExactVersion(root);
854
+ if (options.approve === undefined) {
855
+ const result = await planDraftPr({
856
+ root,
857
+ taskPath: options.task,
858
+ runId: options.run,
859
+ });
860
+ emit(io, global.json === true, commandResult({
861
+ command: "ship.draft.plan",
862
+ ok: true,
863
+ data: result,
864
+ }));
865
+ return;
866
+ }
867
+ if (options.attended !== true) {
868
+ throw new MillError("ATTENDANCE_REQUIRED", "Draft shipping requires attended use of the operator-owned GitHub session.", ExitCode.configuration);
869
+ }
870
+ const result = await openDraftPr({
871
+ root,
872
+ taskPath: options.task,
873
+ runId: options.run,
874
+ approvalDigest: options.approve,
875
+ attended: true,
876
+ });
877
+ emit(io, global.json === true, commandResult({ command: "ship.draft.open", ok: true, data: result }));
878
+ });
879
+ program
880
+ .command("cancel")
881
+ .description("persist cancellation for the exact foreground controller")
882
+ .requiredOption("--run <id>", "run identifier")
883
+ .action(async (options) => {
884
+ const global = globals(program);
885
+ const root = await findRepositoryRoot(global.cwd);
886
+ await enforceExactVersion(root);
887
+ const run = await cancelRun({ root, runId: options.run });
888
+ emit(io, global.json === true, commandResult({ command: "cancel", ok: true, data: { run } }));
889
+ });
890
+ const state = program
891
+ .command("state")
892
+ .description("manage local operational state");
893
+ state
894
+ .command("backup")
895
+ .description("create a user-only SQLite backup")
896
+ .action(async () => {
897
+ const global = globals(program);
898
+ const root = await findRepositoryRoot(global.cwd);
899
+ await enforceExactVersion(root);
900
+ const backupPath = await stateBackup({ root });
901
+ emit(io, global.json === true, commandResult({
902
+ command: "state.backup",
903
+ ok: true,
904
+ data: { backupPath },
905
+ }));
906
+ });
907
+ state
908
+ .command("restore")
909
+ .description("restore one Mill-owned state backup")
910
+ .requiredOption("--from <path>", "backup path returned by state backup")
911
+ .action(async (options) => {
912
+ const global = globals(program);
913
+ const root = await findRepositoryRoot(global.cwd);
914
+ await enforceExactVersion(root);
915
+ const report = await stateRestore({ root, backupPath: options.from });
916
+ emit(io, global.json === true, commandResult({ command: "state.restore", ok: true, data: report }));
917
+ });
918
+ state
919
+ .command("purge")
920
+ .description("remove purge-safe local state and disposable worktrees")
921
+ .requiredOption("--confirm <repository-id>", "exact repository UUID acknowledgement")
922
+ .action(async (options) => {
923
+ const global = globals(program);
924
+ const root = await findRepositoryRoot(global.cwd);
925
+ await enforceExactVersion(root);
926
+ await statePurge({ root, confirmation: options.confirm });
927
+ emit(io, global.json === true, commandResult({ command: "state.purge", ok: true, data: {} }));
928
+ });
929
+ program
930
+ .command("support-bundle")
931
+ .description("emit a redacted static support bundle")
932
+ .option("--run <id>", "run identifier")
933
+ .action(async (options) => {
934
+ const global = globals(program);
935
+ const root = await findRepositoryRoot(global.cwd);
936
+ await enforceExactVersion(root);
937
+ const data = await supportBundle({
938
+ root,
939
+ ...(options.run === undefined ? {} : { runId: options.run }),
940
+ });
941
+ emit(io, global.json === true, commandResult({ command: "support-bundle", ok: true, data }));
942
+ });
943
+ return program;
944
+ }
945
+ export async function runCli(argv, io) {
946
+ const jsonRequested = argv.includes("--json");
947
+ if (jsonRequested && (argv.includes("--version") || argv.includes("-V"))) {
948
+ emit(io, true, commandResult({
949
+ command: "version",
950
+ ok: true,
951
+ data: { version: MILL_VERSION },
952
+ }));
953
+ return ExitCode.ok;
954
+ }
955
+ if (jsonRequested && (argv.includes("--help") || argv.includes("-h"))) {
956
+ emit(io, true, commandResult({
957
+ command: "millctl",
958
+ ok: false,
959
+ status: "error",
960
+ data: {},
961
+ reasons: [
962
+ {
963
+ code: "USAGE_ERROR",
964
+ message: "JSON mode does not support help output; omit --json.",
965
+ },
966
+ ],
967
+ }));
968
+ return ExitCode.usage;
969
+ }
970
+ const program = createProgram(io, jsonRequested);
971
+ try {
972
+ await program.parseAsync(argv, { from: "user" });
973
+ return ExitCode.ok;
974
+ }
975
+ catch (error) {
976
+ if (error instanceof CommanderError) {
977
+ if (error.code === "commander.version" ||
978
+ error.code === "commander.helpDisplayed" ||
979
+ (!jsonRequested && error.code === "commander.help")) {
980
+ return ExitCode.ok;
981
+ }
982
+ if (jsonRequested) {
983
+ emit(io, true, commandResult({
984
+ command: program.args[0] ?? "millctl",
985
+ ok: false,
986
+ status: "error",
987
+ data: {},
988
+ reasons: [
989
+ {
990
+ code: "USAGE_ERROR",
991
+ message: error.message,
992
+ details: { commanderCode: error.code },
993
+ },
994
+ ],
995
+ }));
996
+ }
997
+ return ExitCode.usage;
998
+ }
999
+ const millError = asMillError(error);
1000
+ if (millError.details.resultAlreadyEmitted !== true) {
1001
+ const global = program.opts();
1002
+ const result = commandResult({
1003
+ command: program.args[0] ?? "millctl",
1004
+ ok: false,
1005
+ status: "error",
1006
+ data: {},
1007
+ reasons: [
1008
+ {
1009
+ code: millError.code,
1010
+ message: millError.message,
1011
+ details: millError.details,
1012
+ },
1013
+ ],
1014
+ });
1015
+ emit(io, global.json === true, result);
1016
+ }
1017
+ return millError.exitCode;
1018
+ }
1019
+ }
1020
+ //# sourceMappingURL=cli-program.js.map