@momentiq/dark-factory-cli 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +121 -40
  2. package/dist/adapters/codex-sdk.d.ts +66 -0
  3. package/dist/adapters/codex-sdk.d.ts.map +1 -1
  4. package/dist/adapters/codex-sdk.js +321 -4
  5. package/dist/adapters/codex-sdk.js.map +1 -1
  6. package/dist/adapters/critic-result-schema.d.ts +15 -10
  7. package/dist/adapters/critic-result-schema.d.ts.map +1 -1
  8. package/dist/adapters/critic-result-schema.js +10 -0
  9. package/dist/adapters/critic-result-schema.js.map +1 -1
  10. package/dist/adapters/cursor-cli.d.ts +19 -1
  11. package/dist/adapters/cursor-cli.d.ts.map +1 -1
  12. package/dist/adapters/cursor-cli.js +96 -14
  13. package/dist/adapters/cursor-cli.js.map +1 -1
  14. package/dist/adapters/index.d.ts +1 -0
  15. package/dist/adapters/index.d.ts.map +1 -1
  16. package/dist/adapters/index.js +7 -0
  17. package/dist/adapters/index.js.map +1 -1
  18. package/dist/adapters/static-schema-lint.d.ts +68 -0
  19. package/dist/adapters/static-schema-lint.d.ts.map +1 -0
  20. package/dist/adapters/static-schema-lint.js +813 -0
  21. package/dist/adapters/static-schema-lint.js.map +1 -0
  22. package/dist/branch-protection/spec-default.yaml +2 -2
  23. package/dist/cli.d.ts +17 -1
  24. package/dist/cli.d.ts.map +1 -1
  25. package/dist/cli.js +414 -113
  26. package/dist/cli.js.map +1 -1
  27. package/dist/commands/findings.d.ts +6 -0
  28. package/dist/commands/findings.d.ts.map +1 -0
  29. package/dist/commands/findings.js +196 -0
  30. package/dist/commands/findings.js.map +1 -0
  31. package/dist/commands/show.d.ts +11 -0
  32. package/dist/commands/show.d.ts.map +1 -0
  33. package/dist/commands/show.js +78 -0
  34. package/dist/commands/show.js.map +1 -0
  35. package/dist/commands/skills.d.ts +6 -0
  36. package/dist/commands/skills.d.ts.map +1 -0
  37. package/dist/commands/skills.js +248 -0
  38. package/dist/commands/skills.js.map +1 -0
  39. package/dist/commands/status.d.ts +6 -0
  40. package/dist/commands/status.d.ts.map +1 -0
  41. package/dist/commands/status.js +85 -0
  42. package/dist/commands/status.js.map +1 -0
  43. package/dist/compact/index.d.ts +2 -0
  44. package/dist/compact/index.d.ts.map +1 -0
  45. package/dist/compact/index.js +3 -0
  46. package/dist/compact/index.js.map +1 -0
  47. package/dist/compact/lockfile.d.ts +53 -0
  48. package/dist/compact/lockfile.d.ts.map +1 -0
  49. package/dist/compact/lockfile.js +626 -0
  50. package/dist/compact/lockfile.js.map +1 -0
  51. package/dist/cycle-doc-validator/validate_cycle_doc.py +39 -9
  52. package/dist/doctor.d.ts +51 -1
  53. package/dist/doctor.d.ts.map +1 -1
  54. package/dist/doctor.js +497 -1
  55. package/dist/doctor.js.map +1 -1
  56. package/dist/evidence/docker-build.d.ts +6 -0
  57. package/dist/evidence/docker-build.d.ts.map +1 -0
  58. package/dist/evidence/docker-build.js +199 -0
  59. package/dist/evidence/docker-build.js.map +1 -0
  60. package/dist/evidence/index.d.ts +1 -0
  61. package/dist/evidence/index.d.ts.map +1 -1
  62. package/dist/evidence/index.js +6 -0
  63. package/dist/evidence/index.js.map +1 -1
  64. package/dist/handoff/handoff-verb.d.ts.map +1 -1
  65. package/dist/handoff/handoff-verb.js +6 -3
  66. package/dist/handoff/handoff-verb.js.map +1 -1
  67. package/dist/handoff/ports.d.ts +8 -0
  68. package/dist/handoff/ports.d.ts.map +1 -1
  69. package/dist/handoff/real-clients.js +1 -1
  70. package/dist/handoff/real-clients.js.map +1 -1
  71. package/dist/index.d.ts +2 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +8 -0
  74. package/dist/index.js.map +1 -1
  75. package/dist/lib/show-status-core.d.ts +63 -0
  76. package/dist/lib/show-status-core.d.ts.map +1 -0
  77. package/dist/lib/show-status-core.js +156 -0
  78. package/dist/lib/show-status-core.js.map +1 -0
  79. package/dist/mcp/prompts.d.ts.map +1 -1
  80. package/dist/mcp/prompts.js +113 -78
  81. package/dist/mcp/prompts.js.map +1 -1
  82. package/dist/mcp/resources.js +1 -1
  83. package/dist/mcp/resources.js.map +1 -1
  84. package/dist/mcp/server.d.ts.map +1 -1
  85. package/dist/mcp/server.js +2 -0
  86. package/dist/mcp/server.js.map +1 -1
  87. package/dist/mcp/tools/doctor.d.ts.map +1 -1
  88. package/dist/mcp/tools/doctor.js +5 -0
  89. package/dist/mcp/tools/doctor.js.map +1 -1
  90. package/dist/mcp/tools/findings.d.ts +0 -22
  91. package/dist/mcp/tools/findings.d.ts.map +1 -1
  92. package/dist/mcp/tools/findings.js +5 -73
  93. package/dist/mcp/tools/findings.js.map +1 -1
  94. package/dist/mcp/tools/handoff.d.ts.map +1 -1
  95. package/dist/mcp/tools/handoff.js +10 -17
  96. package/dist/mcp/tools/handoff.js.map +1 -1
  97. package/dist/mcp/tools/review-bypass.d.ts.map +1 -1
  98. package/dist/mcp/tools/review-bypass.js +22 -1
  99. package/dist/mcp/tools/review-bypass.js.map +1 -1
  100. package/dist/mcp/tools/skills-install.d.ts +6 -0
  101. package/dist/mcp/tools/skills-install.d.ts.map +1 -0
  102. package/dist/mcp/tools/skills-install.js +260 -0
  103. package/dist/mcp/tools/skills-install.js.map +1 -0
  104. package/dist/mcp/tools/stats-gate.d.ts.map +1 -1
  105. package/dist/mcp/tools/stats-gate.js +63 -15
  106. package/dist/mcp/tools/stats-gate.js.map +1 -1
  107. package/dist/policy/baseline.d.ts.map +1 -1
  108. package/dist/policy/baseline.js +11 -3
  109. package/dist/policy/baseline.js.map +1 -1
  110. package/dist/policy/gate.d.ts +1 -1
  111. package/dist/policy/gate.d.ts.map +1 -1
  112. package/dist/policy/gate.js +96 -12
  113. package/dist/policy/gate.js.map +1 -1
  114. package/dist/prompt.d.ts +1 -0
  115. package/dist/prompt.d.ts.map +1 -1
  116. package/dist/prompt.js +123 -4
  117. package/dist/prompt.js.map +1 -1
  118. package/dist/report.d.ts +130 -3
  119. package/dist/report.d.ts.map +1 -1
  120. package/dist/report.js +367 -10
  121. package/dist/report.js.map +1 -1
  122. package/dist/runner.d.ts +6 -0
  123. package/dist/runner.d.ts.map +1 -1
  124. package/dist/runner.js +213 -4
  125. package/dist/runner.js.map +1 -1
  126. package/dist/self-consistency.d.ts +144 -0
  127. package/dist/self-consistency.d.ts.map +1 -0
  128. package/dist/self-consistency.js +368 -0
  129. package/dist/self-consistency.js.map +1 -0
  130. package/dist/skills/config.d.ts +176 -0
  131. package/dist/skills/config.d.ts.map +1 -0
  132. package/dist/skills/config.js +251 -0
  133. package/dist/skills/config.js.map +1 -0
  134. package/dist/skills/index.d.ts +4 -0
  135. package/dist/skills/index.d.ts.map +1 -0
  136. package/dist/skills/index.js +8 -0
  137. package/dist/skills/index.js.map +1 -0
  138. package/dist/skills/install.d.ts +62 -0
  139. package/dist/skills/install.d.ts.map +1 -0
  140. package/dist/skills/install.js +315 -0
  141. package/dist/skills/install.js.map +1 -0
  142. package/dist/skills/template.d.ts +42 -0
  143. package/dist/skills/template.d.ts.map +1 -0
  144. package/dist/skills/template.js +95 -0
  145. package/dist/skills/template.js.map +1 -0
  146. package/dist/trusted-surface/rebind.d.ts.map +1 -1
  147. package/dist/trusted-surface/rebind.js +199 -2
  148. package/dist/trusted-surface/rebind.js.map +1 -1
  149. package/package.json +36 -2
  150. package/skills/README.md +89 -0
  151. package/skills/chief-engineer-blitz/SKILL.md.tmpl +443 -0
  152. package/skills/chief-engineer-blitz/skill.json +53 -0
  153. package/skills/chief-engineer-review/SKILL.md.tmpl +184 -0
  154. package/skills/chief-engineer-review/references/review-examples.md.tmpl +130 -0
  155. package/skills/chief-engineer-review/skill.json +67 -0
  156. package/skills/chief-engineer-review/templates/code-review-prompt.md.tmpl +111 -0
  157. package/skills/chief-engineer-review/templates/escalation-prompt.md.tmpl +56 -0
  158. package/skills/chief-engineer-review/templates/plan-review-prompt.md.tmpl +74 -0
  159. package/skills/skill-schema.json +73 -0
package/dist/cli.js CHANGED
@@ -34,7 +34,7 @@
34
34
  // subcommands accept their own flags directly (see `cmdAudit` and
35
35
  // `cmdAdmitPr` below).
36
36
  import { spawnSync } from "node:child_process";
37
- import { accessSync, appendFileSync, constants as fsConstants, existsSync, readFileSync, } from "node:fs";
37
+ import { accessSync, appendFileSync, constants as fsConstants, existsSync, readFileSync, realpathSync, } from "node:fs";
38
38
  import { dirname, resolve } from "node:path";
39
39
  import { fileURLToPath } from "node:url";
40
40
  import { runValidateCycleDoc } from "./cycle-doc-validator/index.js";
@@ -52,12 +52,13 @@ import { AdapterRegistry, collectRequiredEnvVars, } from "./adapters/critic.js";
52
52
  // path). Phase B-PUBLISH-pkg (cycle 331.1, alpha.5): see
53
53
  // https://github.com/momentiq-ai/dark-factory/pull/<this-pr>.
54
54
  import { loadAgentReviewConfig } from "./policy/config.js";
55
- import { buildCriticReport } from "./report.js";
55
+ import { buildCriticReport, buildZeroEvidenceDiagnostic } from "./report.js";
56
56
  import { runReview, runCommitGate } from "./runner.js";
57
+ import { buildDefaultSelfConsistencyProbe, } from "./self-consistency.js";
57
58
  import { resolveArtifactDir, telemetryPath } from "./paths.js";
58
59
  // Phase F-LOCAL — hook-facing subcommand support.
59
60
  import { loadDopplerBootstrapEnv, DEFAULT_BOOTSTRAP_ALLOWLIST, } from "./doppler-bootstrap.js";
60
- import { runDoctor } from "./doctor.js";
61
+ import { classifyDoctorState, detectCloudEnv, runDoctor, } from "./doctor.js";
61
62
  import { resolveProfile } from "./policy/profile.js";
62
63
  import { commitsForPushUpdate, parsePrePushUpdates, resolveCommit, commitParent, changedFiles, } from "./git.js";
63
64
  import { runQualityGates } from "./evidence/quality-gates.js";
@@ -69,10 +70,21 @@ import { summarizeGate } from "./policy/gate.js";
69
70
  // stderr-only diagnostics) is structurally distinct from the other
70
71
  // subcommands. See docs/roadmap/cycles/cycle5-mcp-server.md.
71
72
  import { cmdMcp } from "./mcp/cli.js";
73
+ // DFP #192 — `df skills install/list` subcommand. The skills module is the
74
+ // install engine (consumer-config-driven template rendering); cmdSkills
75
+ // in commands/skills.ts is the CLI wrapper around it.
76
+ import { cmdSkills } from "./commands/skills.js";
72
77
  // Cycle 11 Phase 11.1 — `df flow` namespace surfacing the PR Flow
73
78
  // Assessor's records from momentiq-ai/df-assessments. See
74
79
  // docs/roadmap/cycles/cycle11-flow-assessor-surfacing-and-tools.md.
75
80
  import { cmdFlow } from "./commands/flow/index.js";
81
+ import { cmdShow } from "./commands/show.js";
82
+ import { cmdStatus } from "./commands/status.js";
83
+ // Cycle 13 (dark-factory-platform#149) — `df findings --range` surfaces
84
+ // the per-commit iteration-receipt artifacts that the new (default)
85
+ // final-commit-only `df gate-push` semantic leaves un-gated. See
86
+ // src/commands/findings.ts for the rationale.
87
+ import { cmdFindings } from "./commands/findings.js";
76
88
  // Cycle 12 Phase 12.2 — agent handoff protocol (v2 — Issue-anchored, native-
77
89
  // baton). The four cmd* functions below wrap the verb orchestrators exported
78
90
  // from src/handoff/index.ts and route to them at the bottom of main(). v1
@@ -106,58 +118,79 @@ function printHelp(meta) {
106
118
  " df --version Print version and exit",
107
119
  " df --help Print this help and exit",
108
120
  "",
109
- "Subcommands (Phase C — services #5/#7/#9):",
110
- " df validate-cycle-doc Validate a PR's Cycle:/Issue:/ProjectItem: trailers",
111
- " df audit-branch-protection Detect drift between spec.yaml and live GH ruleset",
112
- " df sync-trackers Reconcile GitHub tracker issues with cycle docs",
113
- " df attribute-pr Write PR's Cycle: trailer into project board's Cycle Ref field",
121
+ "Local critic workflow:",
122
+ " df review Run the local critic against a commit",
123
+ " (subscription auth Cursor / Codex /",
124
+ " Claude logins, NOT pay-per-token keys).",
125
+ " df gate-push Pre-push gate block a push when HEAD's",
126
+ " critic verdict has unresolved blockers.",
127
+ " Default since Cycle 13 (dark-factory-",
128
+ " platform#149): gates the HEAD commit",
129
+ " only; intermediate commits are iteration",
130
+ " receipts (see `df findings --range`).",
131
+ " Legacy per-commit gating via",
132
+ " `--full-range` / `DF_GATE_FULL_RANGE=1`.",
133
+ " df show Render the per-commit review artifact",
134
+ " (with --json for the structured form).",
135
+ " df status Terse verdict + per-critic status for",
136
+ " a commit (with --json for shell pipes).",
137
+ " df findings --range BASE..HEAD",
138
+ " Audit-inspect per-commit findings for the",
139
+ " iteration-receipt artifacts the default",
140
+ " final-commit-only `df gate-push` leaves",
141
+ " un-gated (Cycle 13). NOT a gate.",
142
+ " df gates Run configured quality gates and",
143
+ " triggered verification routes.",
144
+ " df stats Pretty-print critic call stats + bypass",
145
+ " audit (alias for `df audit stats`).",
146
+ " df doctor Verify env: node, hooks, artifact dir,",
147
+ " per-adapter auth.",
114
148
  "",
115
- "Subcommands (Phase D — services #6/#8):",
116
- " df audit stats Summarize the _runs.ndjson audit trail (service #8)",
117
- " df admit-pr Classify a PR as plan vs code (service #6)",
149
+ "CI / reusable-workflow gates:",
150
+ " df status-check Sentinel aggregator (pr-status-check).",
151
+ " Exits 0 merge queue is the real",
152
+ " aggregator.",
153
+ " df critic Real Critic Orchestrator wiring (agent-",
154
+ " critic gate). Loads .agent-review/",
155
+ " config.json, runs configured vendor",
156
+ " adapters via runReview, writes aggregate",
157
+ " verdict to .git/agent-reviews/<sha>.",
158
+ " Degrades-and-passes on any error.",
118
159
  "",
119
- "Subcommands (Phase F — reusable-workflow gates, dogfood-wired):",
120
- " df status-check Sentinel aggregator (PR Status Check gate).",
121
- " Exits 0 — merge queue is the real aggregator.",
122
- " df critic Real Critic Orchestrator wiring (agent-critic",
123
- " gate). Loads .agent-review/config.json, runs",
124
- " the 4 vendor adapters via runReview, writes",
125
- " aggregate verdict to .git/agent-reviews/<sha>.",
126
- " Degrades-and-passes on any error (exit 0).",
160
+ "Cycle-doc + project services:",
161
+ " df validate-cycle-doc Validate a PR's Cycle:/Issue:/",
162
+ " ProjectItem: trailers.",
163
+ " df audit-branch-protection Detect drift between spec.yaml and the",
164
+ " live GitHub ruleset.",
165
+ " df sync-trackers Reconcile GitHub tracker issues with",
166
+ " cycle docs.",
167
+ " df attribute-pr Write PR's Cycle: trailer into the",
168
+ " project board's Cycle Ref field.",
169
+ " df audit stats Summarize the _runs.ndjson audit trail.",
170
+ " df admit-pr Classify a PR as plan vs code.",
127
171
  "",
128
- "Subcommands (Phase F-LOCAL — hook-facing CLI, subscription cost model):",
129
- " df review Background-friendly local critic invocation",
130
- " for .husky/post-commit. Honors profile auth",
131
- " (chatgpt/composer/subscription) so Cursor +",
132
- " Codex + Claude SUBSCRIPTIONS are consumed,",
133
- " NOT pay-per-token API keys.",
134
- " df gate-push Read pre-push stdin + gate every commit in",
135
- " the pushed range. CI mode: --commit SHA --ci.",
136
- " Honors AGENT_REVIEW_BYPASS for emergencies.",
137
- " df doctor Verify env: node, hooks, hookPath, artifact",
138
- " dir, Doppler bootstrap, per-adapter auth.",
139
- " df gates Run configured `requiredQualityGates` and",
140
- " triggered verification routes. No LLM calls.",
141
- " df stats Pretty-print critic call stats + bypass audit.",
142
- " Alias for `df audit stats`. No LLM calls.",
143
- "",
144
- "Subcommands (Phase G — agentic MCP surface, cycle5):",
172
+ "Agentic surface (MCP server):",
145
173
  " df mcp Start the local stdio Model Context Protocol",
146
174
  " server. Exposes the CLI surface to any MCP-",
147
- " speaking agent (Claude Code, Cursor, Codex,",
148
- " Gemini) as a structured tool + resource +",
149
- " prompt catalog. Run `df mcp --help` for the",
175
+ " speaking agent as a structured tool + resource",
176
+ " + prompt catalog. Run `df mcp --help` for the",
150
177
  " .mcp.json wiring snippet.",
151
178
  "",
152
- "Subcommands (Cycle 11 — PR Flow Assessor surfacing):",
153
- " df flow Surface the PR Flow Assessor's records",
154
- " from momentiq-ai/df-assessments. Six",
155
- " sub-subcommands: show / agent / patterns /",
156
- " cost / trends / rollup. Each carries",
157
- " --json. Run `df flow --help` for the list,",
158
- " `df flow <sub> --help` for per-sub flags.",
179
+ "Bundled skills (consumer-shape):",
180
+ " df skills install <name> Render + install a bundled skill into",
181
+ " .claude/skills/<name>/, driven by the consumer's",
182
+ " darkfactory.yaml. --all installs every skill",
183
+ " flagged enabled: true.",
184
+ " df skills list List bundled skill names + summaries.",
185
+ "",
186
+ "PR Flow Assessor surfacing:",
187
+ " df flow Surface the PR Flow Assessor's records.",
188
+ " Six sub-subcommands: show / agent /",
189
+ " patterns / cost / trends / rollup.",
190
+ " Each carries --json. Run `df flow",
191
+ " --help` for the list.",
159
192
  "",
160
- "Subcommands (Cycle 12 — agent handoff protocol, v2 Issue-anchored):",
193
+ "Agent handoff protocol (v2 Issue-anchored):",
161
194
  " df handoff [issue] Put a work-stream on the handoff stack:",
162
195
  " [--link <ref>]... upsert the marker-bounded rehydration",
163
196
  " [--unlink <ref>]... note (read from stdin) as the dedicated",
@@ -168,7 +201,7 @@ function printHelp(meta) {
168
201
  " df handoffs List the stack of handed-off Issues (open,",
169
202
  " labeled `handoff`, unassigned).",
170
203
  " df accept <issue> Claim a handoff Issue (assign you), rehydrate,",
171
- " then close it (Commitment 10).",
204
+ " then close it.",
172
205
  " df rehydrate [issue] Read-only catch-up on a handoff Issue's note —",
173
206
  " derives LIVE state first, changes no ownership.",
174
207
  " No-arg: 2-tier (open+@me → closed+@me ≤7d).",
@@ -177,42 +210,25 @@ function printHelp(meta) {
177
210
  " The local hook path (review/gate-push) consumes Cursor / Codex / Claude",
178
211
  " SUBSCRIPTIONS via the developer's existing CLI logins. Vendor API keys",
179
212
  " (CURSOR_API_KEY / CODEX_API_KEY / GEMINI_API_KEY / XAI_API_KEY) are the",
180
- " CI cold-path fallback only. Profile `auth` pins (e.g. `chatgpt` on the",
181
- " codex critic) enforce subscription-only with no env-presence fallback",
182
- " (cycle 322.7 issue #2103).",
213
+ " CI cold-path fallback only.",
183
214
  "",
184
- "Each Phase C subcommand passes its remaining argv through to the bundled",
185
- "Python script verbatim; run `df <subcommand> --help` for full flags.",
186
- "Phase D subcommands parse flags directly see `df audit --help` and",
187
- "`df admit-pr --help`. Phase F's `df critic` accepts --ref/--config/--cwd",
188
- "(see `df critic --help`); status-check accepts no flags. Both subcommands",
189
- "exit 0 even on failure so the reusable workflows do not block the merge",
190
- "queue on a single vendor flake — vendor errors register in the artifact",
191
- "as `status=error` and the configured min-complete-quorum policy decides",
192
- "the aggregate verdict.",
215
+ "Each subcommand carries its own `--help` for full flag documentation.",
216
+ "Critic and status-check exit 0 even on failure so the reusable workflows",
217
+ "do not block the merge queue on a single vendor flake — vendor errors",
218
+ "register in the artifact as `status=error` and the configured aggregation",
219
+ "policy (min-complete-quorum, block-if-any) decides the aggregate verdict.",
193
220
  "",
194
221
  "System requirements:",
195
222
  " Node.js >=20",
196
223
  " python3 (>=3.11)",
197
- " gh CLI (authenticated) for service #5/#7/#9 GitHub API calls",
224
+ " gh CLI (authenticated) for project/cycle-doc GitHub API calls",
198
225
  " git on PATH",
199
226
  "",
200
227
  "Library usage today:",
201
228
  " import { runReview, evaluateCommitGate, buildReviewPacket }",
202
229
  ` from \"${name}\";`,
203
230
  "",
204
- "Status: 0.1.0-alpha.6 Phase F-LOCAL ports sage3c's hook-facing CLI",
205
- " subcommands (review/gate-push/doctor/gates/stats) so consumer",
206
- " repos can wire .husky hooks to the subscription-auth local",
207
- " critic path. Also: Phase B-PUBLISH-pkg fixes CLI loadability",
208
- " under `npm install --ignore-scripts` for all non-`df critic`",
209
- " subcommands. Phase F wires the real Critic Orchestrator and",
210
- " dogfoods dark-factory on its own PRs. Reusable workflows from",
211
- " Phase E now invoke real critic logic. The substrate validates",
212
- " itself end-to-end. Pure-TS port of #5/#7/#9 tracked as Phase",
213
- " C-PORT follow-up.",
214
- "",
215
- "Docs: https://github.com/momentiq-ai/dark-factory",
231
+ `Version: ${version}. Docs: https://github.com/momentiq-ai/dark-factory`,
216
232
  "",
217
233
  ].join("\n"));
218
234
  }
@@ -220,11 +236,9 @@ function printVersion(meta) {
220
236
  process.stdout.write(`${meta.version ?? "unknown"}\n`);
221
237
  }
222
238
  function notImplemented(sub) {
223
- process.stderr.write(`df: subcommand "${sub}" is not implemented in this alpha build.\n` +
224
- ` Phase B/C/D ship services as library + Python-wrapped subcommands;\n` +
225
- ` Phase E added reusable workflow stubs (status-check, critic);\n` +
226
- ` Phase F upgraded both to real implementations;\n` +
227
- ` "${sub}" lands in Phase G or later (see \`df --help\`).\n`);
239
+ process.stderr.write(`df: subcommand "${sub}" is not implemented in this build.\n` +
240
+ ` Run \`df --help\` for the list of available subcommands.\n` +
241
+ ` Track / request at: https://github.com/momentiq-ai/dark-factory/issues\n`);
228
242
  return 2;
229
243
  }
230
244
  async function runPhaseCSubcommand(sub, rest) {
@@ -264,7 +278,7 @@ const PHASE_D_SUBCOMMANDS = new Set(["audit", "admit-pr"]);
264
278
  //
265
279
  // - `status-check` stays thin: a sentinel aggregator. The merge queue's
266
280
  // ALLGREEN rule already does the real cross-check aggregation; this
267
- // subcommand exists for the `PR Status Check` ruleset context.
281
+ // subcommand exists for the `pr-status-check` ruleset context.
268
282
  //
269
283
  // - `critic` is now wired to the real Critic Orchestrator (Phase B
270
284
  // extraction). It loads `.agent-review/config.json`, instantiates
@@ -308,10 +322,27 @@ const CYCLE12_SUBCOMMANDS = new Set([
308
322
  // not at the top level, so the surface stays grouped (`df flow --help` is
309
323
  // authoritative) and we don't pollute the top-level `df` namespace.
310
324
  const CYCLE11_SUBCOMMANDS = new Set(["flow"]);
325
+ // `df show` / `df status` / `df findings` — CLI artifact-inspection
326
+ // subcommands. `show` + `status` mirror the df_show_run / df_findings MCP
327
+ // tools per-commit (cycle 5); `findings --range <base>..<head>` (Cycle
328
+ // 13, dark-factory-platform#149) walks the iteration-receipt artifacts
329
+ // the new default final-commit-only `df gate-push` semantic leaves
330
+ // un-gated. All three share the loader + mappers in
331
+ // src/lib/show-status-core.ts so the CLI's `--json` output stays
332
+ // byte-equivalent with the MCP tool's structuredContent envelope (cycle 5
333
+ // spec requirement). Registered here so the early --help interception
334
+ // forwards each `df <cmd> --help` to its per-subcommand help printer.
335
+ const SHOW_STATUS_SUBCOMMANDS = new Set(["show", "status", "findings"]);
336
+ // `df skills` — bundled-skill installer (Cycle: DFP #192). The namespace
337
+ // fans out into `install/list` sub-subcommands handled inside cmdSkills;
338
+ // registered here so the early --help interception above forwards
339
+ // `df skills --help` to cmdSkills' own help printer instead of the
340
+ // top-level printHelp.
341
+ const SKILLS_SUBCOMMANDS = new Set(["skills"]);
311
342
  function cmdStatusCheck(_rest) {
312
- // PR Status Check is a sentinel aggregator. As cycle 331.1 Phase E
343
+ // pr-status-check is a sentinel aggregator. As cycle 331.1 Phase E
313
344
  // documents in `.github/workflows/pr-status-check.yml`, this gate is
314
- // present specifically to satisfy the `PR Status Check` ruleset context
345
+ // present specifically to satisfy the `pr-status-check` ruleset context
315
346
  // — its passage means "the workflow itself reached this step", which
316
347
  // is the contract every other status check separately enforces. There
317
348
  // is no useful aggregation work to do here that the merge queue's
@@ -351,6 +382,12 @@ const ADAPTER_LOADERS = [
351
382
  { id: "codex-sdk", modulePath: "./adapters/codex-sdk.js", className: "CodexSdkAdapter" },
352
383
  { id: "gemini-sdk", modulePath: "./adapters/gemini-sdk.js", className: "GeminiSdkAdapter" },
353
384
  { id: "grok-direct-sdk", modulePath: "./adapters/grok-direct-sdk.js", className: "GrokDirectSdkAdapter" },
385
+ // Consumer dark-factory-platform#107 — deterministic schema-lint adapter.
386
+ // No vendor SDK, no env vars, no network; pure ajv-backed validation of
387
+ // schema-annotated code blocks in changed markdown / config files.
388
+ // Loads alongside the LLM adapters so the local profile can include it
389
+ // without any additional toolchain.
390
+ { id: "static-schema-lint", modulePath: "./adapters/static-schema-lint.js", className: "StaticSchemaLintAdapter" },
354
391
  ];
355
392
  async function buildDefaultAdapterRegistry() {
356
393
  // Each adapter reads its API key from the corresponding env var by
@@ -403,6 +440,31 @@ function appendStepSummary(markdown) {
403
440
  // best-effort — never let a summary-write failure change the gate outcome.
404
441
  }
405
442
  }
443
+ /**
444
+ * Cursor finding (cli.ts:560) — resolve the production
445
+ * self-consistency probe so the policy `unilateralVetoRules.requireCorroborationFor`
446
+ * actually demotes findings on the default OSS CLI path. Returns the
447
+ * probe callable when (a) the loaded policy lists `self_inconsistent`
448
+ * AND (b) `buildDefaultSelfConsistencyProbe()` returns a non-null
449
+ * factory (i.e. the env has a `GEMINI_API_KEY`). Otherwise returns
450
+ * undefined, which makes the runner skip the probe pass entirely
451
+ * (byte-identical pre-#112 behavior).
452
+ *
453
+ * Operators who want a non-Gemini probe (or the hosted worker's
454
+ * proprietary calibrated probe) replace the callable by importing
455
+ * `runReview` directly with their own `selfConsistencyProbe`.
456
+ */
457
+ export function resolveProductionSelfConsistencyProbe(loaded) {
458
+ const flags = loaded.config.aggregation.unilateralVetoRules?.requireCorroborationFor ?? [];
459
+ if (!flags.includes("self_inconsistent"))
460
+ return undefined;
461
+ const probe = buildDefaultSelfConsistencyProbe();
462
+ if (probe === null) {
463
+ process.stderr.write("[critic] self-consistency probe disabled — GEMINI_API_KEY unset; policy `unilateralVetoRules.requireCorroborationFor: [self_inconsistent]` is configured but probe-flagged findings will not be demoted on this run.\n");
464
+ return undefined;
465
+ }
466
+ return probe;
467
+ }
406
468
  async function cmdCritic(rest) {
407
469
  if (rest.includes("--help") || rest.includes("-h")) {
408
470
  process.stdout.write([
@@ -443,11 +505,13 @@ async function cmdCritic(rest) {
443
505
  // audit trail on its own PRs.
444
506
  const artifactDir = await resolveArtifactDir(loaded);
445
507
  const sink = new FileTelemetrySink(telemetryPath(artifactDir));
508
+ const selfConsistencyProbe = resolveProductionSelfConsistencyProbe(loaded);
446
509
  const outcome = await runReview({
447
510
  loaded,
448
511
  registry,
449
512
  ref: opts.ref,
450
513
  telemetry: sink,
514
+ ...(selfConsistencyProbe !== undefined ? { selfConsistencyProbe } : {}),
451
515
  });
452
516
  // sage3c#2213 — surface per-critic errors + a loud degradation
453
517
  // warning. `buildCriticReport` is pure; it turns the artifact's
@@ -631,13 +695,28 @@ async function cmdReview(rest) {
631
695
  const ref = flags["commit"] ?? "HEAD";
632
696
  const foreground = flags["foreground"] === true || flags["foreground"] === "true";
633
697
  try {
698
+ const selfConsistencyProbe = resolveProductionSelfConsistencyProbe(loaded);
634
699
  const outcome = await runReview({
635
700
  loaded,
636
701
  registry,
637
702
  ref,
638
703
  telemetry: sink,
639
704
  profileName,
705
+ ...(selfConsistencyProbe !== undefined ? { selfConsistencyProbe } : {}),
640
706
  });
707
+ // Issue #51 — loud post-completion diagnostic for zero-evidence
708
+ // reviews. When every critic errored (no completed verdicts), the
709
+ // gate will silently block at push time and operators reach for
710
+ // AGENT_REVIEW_BYPASS as the workaround. Surface the failure here
711
+ // so the operator sees it at commit time, with a specific
712
+ // remediation per critic and a pointer to the artifact JSON for
713
+ // deeper triage. The helper is pure (no I/O); the only side effect
714
+ // is the stderr write below.
715
+ const configHasProfiles = hasProfileEntry(loaded.config, profileName);
716
+ const diagnostic = buildZeroEvidenceDiagnostic(outcome.artifact, outcome.paths.jsonPath, { configHasProfiles });
717
+ if (diagnostic.isZeroEvidence) {
718
+ process.stderr.write(diagnostic.stderr);
719
+ }
641
720
  if (foreground) {
642
721
  process.stdout.write(`df review: ${outcome.artifact.gateVerdict ?? "complete"} for ${outcome.artifact.commit.slice(0, 12)}\n`);
643
722
  process.stdout.write(` json: ${outcome.paths.jsonPath}\n`);
@@ -650,20 +729,108 @@ async function cmdReview(rest) {
650
729
  return 1;
651
730
  }
652
731
  }
732
+ // Issue #51 — the post-completion diagnostic needs to know whether
733
+ // the loaded config has the requested profile so it can prepend a
734
+ // "add 'profiles' to .agent-review/config.json" remediation when the
735
+ // seed config is the broken sage-blueprint shape (`profiles: {}`).
736
+ // Returns `true` when both a profiles map exists AND the requested
737
+ // name is present; `false` otherwise. (Back-compat configs with no
738
+ // profiles map AT ALL also return `false`, which is the correct
739
+ // signal — the diagnostic surfaces the seed remediation in both
740
+ // cases.)
741
+ function hasProfileEntry(config, profileName) {
742
+ if (!config.profiles)
743
+ return false;
744
+ return config.profiles[profileName] !== undefined;
745
+ }
653
746
  // ----- df gate-push -----
747
+ //
748
+ // Cycle 13 (dark-factory-platform#149) — semantic flip:
749
+ //
750
+ // DEFAULT MODE: final-commit-only. The HEAD (last) commit of each
751
+ // push update is the only one whose verdict decides the gate. The
752
+ // intermediate commits' per-SHA artifacts still exist on disk
753
+ // (`.git/agent-reviews/<sha>.json`) as iteration receipts and stay
754
+ // visible via `df findings --range <base>..<head>`, but they do
755
+ // NOT influence the push outcome. This matches the documented
756
+ // find-fix-new-commit pattern: commit B fixes commit A's blockers,
757
+ // B's verdict is what gates the push.
758
+ //
759
+ // Soundness caveat: each per-SHA artifact reviews `parent..commit`
760
+ // only, NOT `base..tip`. HEAD's APPROVED verdict therefore proves
761
+ // the LAST incremental change is safe; it does NOT prove the
762
+ // cumulative `base..tip` diff is safe. The banner + help text
763
+ // surface this so operators can choose `--full-range` (or rely on
764
+ // the CI cold-path agent-critic workflow that reviews the full PR
765
+ // diff) when cumulative soundness matters more than termination of
766
+ // the find-fix loop.
767
+ //
768
+ // LEGACY MODE: full-range — gate every commit in the push update,
769
+ // block on any single commit's blockers. Opt-in via `--full-range`
770
+ // OR `DF_GATE_FULL_RANGE=1`. Use cases: forensic replay, per-commit
771
+ // audit (each commit is independently reviewed in a deploy log),
772
+ // cumulative-state evidence requirements.
773
+ //
774
+ // The legacy mode was the implicit default before Cycle 13. It
775
+ // makes the find-fix-new-commit pattern non-terminating in practice
776
+ // (any iteration round produces stale intermediate `CHANGES_REQUESTED`
777
+ // verdicts that block the push and force a squash, which spawns a
778
+ // fresh full-diff review that surfaces new findings — see
779
+ // consumer-side dark-factory-platform#149 for the full failure mode).
780
+ //
781
+ // The mode banner — `GATE MODE: final-commit-only (HEAD=<sha>)` or
782
+ // `GATE MODE: full-range (N commits)` — prints once per push update so
783
+ // operators see which semantic is active without needing to remember
784
+ // the env-var / flag state.
785
+ //
786
+ // `--commit SHA --ci` mode is a single-commit replay path (CI cold-
787
+ // path); the legacy/default split does not apply to it (one commit
788
+ // only).
789
+ function isFullRangeRequested(flags) {
790
+ if (flags["full-range"] === true || flags["full-range"] === "true") {
791
+ return true;
792
+ }
793
+ const env = process.env["DF_GATE_FULL_RANGE"];
794
+ if (env !== undefined && env !== "" && env !== "0" && env.toLowerCase() !== "false") {
795
+ return true;
796
+ }
797
+ return false;
798
+ }
654
799
  async function cmdGatePush(rest) {
655
800
  if (rest.includes("--help") || rest.includes("-h")) {
656
801
  process.stdout.write([
657
- "df gate-push — block a push when prior commits have unresolved blockers.",
802
+ "df gate-push — gate a push on the local-critic verdict.",
658
803
  "",
659
804
  "Usage:",
660
- " df gate-push [--profile NAME] # local pre-push",
661
- " df gate-push --commit SHA --ci [--profile NAME] # CI replay",
805
+ " df gate-push [--profile NAME] # local pre-push",
806
+ " df gate-push --full-range [--profile NAME] # legacy: gate every commit",
807
+ " df gate-push --commit SHA --ci [--profile NAME] # CI replay (single commit)",
808
+ "",
809
+ "Designed for .husky/pre-push. Reads git's pre-push protocol on stdin.",
810
+ "",
811
+ "Default mode (Cycle 13 — dark-factory-platform#149):",
812
+ " Gates ONLY the HEAD (final) commit of each push update. Intermediate",
813
+ " commits' per-SHA artifacts at .git/agent-reviews/<sha>.json are still",
814
+ " written (iteration audit), but they DO NOT influence the gate.",
815
+ " Inspect them with: `df findings --range <base>..HEAD`.",
816
+ "",
817
+ "Legacy mode — opt in via `--full-range` OR `DF_GATE_FULL_RANGE=1`:",
818
+ " Gates EVERY commit in the push range; any single commit's blocker",
819
+ " blocks the push. Use for forensic replay or per-commit deploy-log",
820
+ " audit. Note: this was the implicit default before Cycle 13 and is",
821
+ " what produced the find-fix-new-commit termination failure.",
662
822
  "",
663
- "Designed for .husky/pre-push. Reads git's pre-push protocol on stdin",
664
- "and gates each commit in the pushed range against the per-SHA artifact",
665
- "written by `df review`. Exit 1 if any commit has unresolved BLOCKER",
666
- "findings under the configured aggregation policy.",
823
+ "Soundness caveat (default mode):",
824
+ " Each per-SHA artifact reviews `parent..commit` only, NOT `base..tip`.",
825
+ " HEAD's APPROVED verdict therefore proves the LAST incremental change",
826
+ " is safe; it does NOT prove the cumulative `base..tip` diff is safe.",
827
+ " For cumulative-state evidence either pass `--full-range` (or set",
828
+ " `DF_GATE_FULL_RANGE=1`) for per-commit gating, or rely on the CI",
829
+ " cold-path agent-critic workflow (which reviews the full PR diff).",
830
+ "",
831
+ "Exit code:",
832
+ " 1 if the gating verdict (HEAD-only or full-range, per mode) blocks.",
833
+ " 0 otherwise.",
667
834
  "",
668
835
  "Bypass:",
669
836
  " AGENT_REVIEW_BYPASS=\"<reason>\" git push # logged to _runs.ndjson",
@@ -702,6 +869,7 @@ async function cmdGatePush(rest) {
702
869
  process.stdout.write(`-- ${sha.slice(0, 12)}\n${summarizeGate(result)}\n`);
703
870
  return result.blocked ? 1 : 0;
704
871
  }
872
+ const fullRange = isFullRangeRequested(flags);
705
873
  const stdin = await readStdinUtf8FromTtyOrStream();
706
874
  const updates = parsePrePushUpdates(stdin);
707
875
  if (updates.length === 0) {
@@ -715,18 +883,45 @@ async function cmdGatePush(rest) {
715
883
  const commits = await commitsForPushUpdate(update);
716
884
  if (commits.length === 0)
717
885
  continue;
718
- process.stdout.write(`df gate-push: gating ${commits.length} commit(s) on ${update.localRef} -> ${update.remoteRef}\n`);
719
- for (const sha of commits) {
720
- const result = await runCommitGate({
721
- loaded,
722
- commit: sha,
723
- telemetry: sink,
724
- profileName,
725
- });
726
- process.stdout.write(`-- ${sha.slice(0, 12)}\n${summarizeGate(result)}\n`);
727
- if (result.blocked)
728
- blockedAny = true;
886
+ if (fullRange) {
887
+ // Legacy mode gate every commit in the range. Pre-Cycle-13
888
+ // behavior, kept for forensic replay and per-commit audit
889
+ // contexts (e.g. production-deploy push where each commit is
890
+ // independently reviewed in the deploy log).
891
+ process.stdout.write(`GATE MODE: full-range (${commits.length} commits) — ${update.localRef} -> ${update.remoteRef}\n`);
892
+ for (const sha of commits) {
893
+ const result = await runCommitGate({
894
+ loaded,
895
+ commit: sha,
896
+ telemetry: sink,
897
+ profileName,
898
+ });
899
+ process.stdout.write(`-- ${sha.slice(0, 12)}\n${summarizeGate(result)}\n`);
900
+ if (result.blocked)
901
+ blockedAny = true;
902
+ }
903
+ continue;
729
904
  }
905
+ // Default mode (Cycle 13) — final-commit-only. The last commit in
906
+ // the range (`commitsForPushUpdate` returns the rev-list reverse,
907
+ // so HEAD is the last element) is the only one whose verdict
908
+ // gates the push. Intermediate commits' artifacts are iteration
909
+ // receipts; inspect them with `df findings --range`.
910
+ const headSha = commits[commits.length - 1] ?? "";
911
+ process.stdout.write(`GATE MODE: final-commit-only (HEAD=${headSha.slice(0, 12)}) — ${update.localRef} -> ${update.remoteRef}\n`);
912
+ if (commits.length > 1) {
913
+ process.stdout.write(` intermediate commits (${commits.length - 1}) are iteration receipts; inspect with: df findings --range ${update.remoteRef === "" ? "<base>" : `${update.remoteSha.slice(0, 12)}..${headSha.slice(0, 12)}`}\n`);
914
+ process.stdout.write(` soundness caveat: HEAD's verdict covers parent..HEAD only, NOT base..HEAD; use --full-range (or the CI agent-critic on the full PR diff) when cumulative-state evidence is required.\n`);
915
+ }
916
+ const result = await runCommitGate({
917
+ loaded,
918
+ commit: headSha,
919
+ telemetry: sink,
920
+ profileName,
921
+ });
922
+ process.stdout.write(`-- ${headSha.slice(0, 12)}\n${summarizeGate(result)}\n`);
923
+ if (result.blocked)
924
+ blockedAny = true;
730
925
  }
731
926
  return blockedAny ? 1 : 0;
732
927
  }
@@ -737,11 +932,18 @@ async function cmdDoctor(rest) {
737
932
  "df doctor — verify the environment for hook-facing critic invocation.",
738
933
  "",
739
934
  "Usage:",
740
- " df doctor [--profile NAME]",
935
+ " df doctor [--profile NAME] [--json]",
741
936
  "",
742
937
  "Checks: node version, hooks dir, hook executable, core.hooksPath,",
743
- "artifact dir, doppler bootstrap, per-adapter doctor() (subscription",
744
- "auth lives here).",
938
+ "artifact dir, doppler bootstrap, cloud-env detection, per-adapter",
939
+ "doctor() (subscription auth lives here; skipped in cloud envs).",
940
+ "",
941
+ "Flags:",
942
+ " --json Emit a machine-readable JSON",
943
+ " report on stdout instead of the",
944
+ " human-readable INFO/OK/FAIL lines",
945
+ " (for consumer-side pre-push hooks",
946
+ " that fail-fast on auth_pending).",
745
947
  "",
746
948
  "Environment:",
747
949
  " AGENT_REVIEW_PROFILE=<name> Profile to validate (default: local)",
@@ -749,6 +951,12 @@ async function cmdDoctor(rest) {
749
951
  " DF_DOCTOR_SKIP_HOOKS=1 Skip git-core-hookspath check",
750
952
  " DF_DOCTOR_SKIP_DOPPLER=1 Skip doppler-cli-on-path check",
751
953
  "",
954
+ "Cloud-env markers (any-of triggers subscription-auth skip):",
955
+ " CODESPACES=true GitHub Codespaces",
956
+ " REMOTE_CONTAINERS=true VS Code Dev Containers",
957
+ " CLAUDE_CODE_SANDBOX=true Claude Code web sandbox",
958
+ " DEVCONTAINER=true generic devcontainer images",
959
+ "",
752
960
  ].join("\n"));
753
961
  return 0;
754
962
  }
@@ -770,15 +978,55 @@ async function cmdDoctor(rest) {
770
978
  bootstrap,
771
979
  profileName,
772
980
  });
773
- let allOk = true;
981
+ // Issue #51 — surface the 3-state triage classification FIRST, before
982
+ // the per-check INFO/OK/FAIL block. The headline tells the operator
983
+ // whether they're in config_missing / auth_pending / ok before they
984
+ // have to parse per-critic noise.
985
+ const triage = classifyDoctorState({
986
+ config: loaded.config,
987
+ profileName,
988
+ perCriticChecks: checks,
989
+ });
990
+ const cloudEnv = detectCloudEnv();
991
+ const allOk = checks.every((c) => c.passed || c.optional);
992
+ // --json — machine-readable output for consumer-side pre-push hooks
993
+ // (DFP issue #56). Shape is stable and documented in
994
+ // packages/schemas/src/index.ts (DoctorReport). Exit code semantics
995
+ // are the same as the human path: 0 = all required checks passed,
996
+ // 1 = at least one required check failed.
997
+ if (flags["json"]) {
998
+ const report = {
999
+ version: 1,
1000
+ schema: "df-doctor-report-v1",
1001
+ triage: { state: triage.state, line: triage.line },
1002
+ cloudEnv: {
1003
+ detected: cloudEnv.detected,
1004
+ markers: cloudEnv.markers,
1005
+ },
1006
+ // `profile` is part of the stable field set; `JSON.stringify`
1007
+ // omits `undefined`, so we default explicitly here even though
1008
+ // `resolveProfile` already returns `"local"` on the no-flag /
1009
+ // no-env path. Belt-and-braces — keeps the contract intact if the
1010
+ // resolver signature ever loosens.
1011
+ profile: profileName || "local",
1012
+ ok: allOk,
1013
+ checks,
1014
+ };
1015
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
1016
+ return allOk ? 0 : 1;
1017
+ }
1018
+ process.stdout.write(`${triage.line}\n`);
774
1019
  for (const c of checks) {
775
- const label = c.passed ? "OK" : c.optional ? "INFO" : "FAIL";
1020
+ const label = c.passed ? (c.optional ? "INFO" : "OK") : c.optional ? "INFO" : "FAIL";
776
1021
  process.stdout.write(`[${label}] ${c.name}: ${c.detail}\n`);
777
1022
  if (!c.passed && c.remediation) {
778
1023
  process.stdout.write(` fix: ${c.remediation}\n`);
779
1024
  }
780
- if (!c.passed && !c.optional)
781
- allOk = false;
1025
+ else if (c.passed && c.optional && c.remediation) {
1026
+ // INFO-with-remediation surfaces the cloud-env bypass hint loudly
1027
+ // even though the check itself "passed" (it's informational).
1028
+ process.stdout.write(` note: ${c.remediation}\n`);
1029
+ }
782
1030
  }
783
1031
  return allOk ? 0 : 1;
784
1032
  }
@@ -1468,7 +1716,9 @@ async function main(argv) {
1468
1716
  !PHASE_F_LOCAL_SUBCOMMANDS.has(sub0) &&
1469
1717
  !PHASE_G_SUBCOMMANDS.has(sub0) &&
1470
1718
  !CYCLE12_SUBCOMMANDS.has(sub0) &&
1471
- !CYCLE11_SUBCOMMANDS.has(sub0)) {
1719
+ !CYCLE11_SUBCOMMANDS.has(sub0) &&
1720
+ !SHOW_STATUS_SUBCOMMANDS.has(sub0) &&
1721
+ !SKILLS_SUBCOMMANDS.has(sub0)) {
1472
1722
  printHelp(meta);
1473
1723
  return 0;
1474
1724
  }
@@ -1539,12 +1789,63 @@ async function main(argv) {
1539
1789
  parseFlags,
1540
1790
  });
1541
1791
  }
1792
+ // `df show` / `df status` — CLI mirrors of the df_show_run / df_findings
1793
+ // MCP tools. See src/lib/show-status-core.ts for the shared backend.
1794
+ if (sub === "show") {
1795
+ return await cmdShow(rest, {
1796
+ stdout: (s) => process.stdout.write(s),
1797
+ stderr: (s) => process.stderr.write(s),
1798
+ });
1799
+ }
1800
+ if (sub === "status") {
1801
+ return await cmdStatus(rest, {
1802
+ stdout: (s) => process.stdout.write(s),
1803
+ stderr: (s) => process.stderr.write(s),
1804
+ });
1805
+ }
1806
+ // Cycle 13 (dark-factory-platform#149) — `df findings --range` audit
1807
+ // surface for the iteration-receipt artifacts the new default
1808
+ // final-commit-only `df gate-push` semantic intentionally leaves
1809
+ // un-gated. NOT a gate; opt-in inspection only.
1810
+ if (sub === "findings") {
1811
+ return await cmdFindings(rest, {
1812
+ stdout: (s) => process.stdout.write(s),
1813
+ stderr: (s) => process.stderr.write(s),
1814
+ });
1815
+ }
1816
+ // DFP #192 — bundled-skill installer surface. The subcommand fans out
1817
+ // into `install/list` inside cmdSkills, mirroring the `df flow` pattern.
1818
+ if (sub === "skills") {
1819
+ return await cmdSkills(rest);
1820
+ }
1542
1821
  return notImplemented(sub);
1543
1822
  }
1544
- main(process.argv).then((code) => {
1545
- process.exitCode = code;
1546
- }, (err) => {
1547
- process.stderr.write(`df: fatal: ${err.message}\n`);
1548
- process.exitCode = 1;
1549
- });
1823
+ // Only run main() when this module is the program entry point.
1824
+ // Tests and other modules can `import` cli.ts to reuse helpers
1825
+ // (e.g. `resolveProductionSelfConsistencyProbe`) without triggering
1826
+ // `main(process.argv)` as a side-effect of the import. The realpath
1827
+ // dance handles symlinked installs (npm bin shims often symlink
1828
+ // `dist/cli.js` into `node_modules/.bin/df`). Same posture as
1829
+ // sage-cli (see commit f0f945e). Cursor finding (cli.ts:560) tests
1830
+ // reach `resolveProductionSelfConsistencyProbe` by importing this
1831
+ // module; without the guard they'd trigger the help path on import.
1832
+ function isInvokedAsMain() {
1833
+ const entry = process.argv[1];
1834
+ if (!entry)
1835
+ return false;
1836
+ try {
1837
+ return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
1838
+ }
1839
+ catch {
1840
+ return false;
1841
+ }
1842
+ }
1843
+ if (isInvokedAsMain()) {
1844
+ main(process.argv).then((code) => {
1845
+ process.exitCode = code;
1846
+ }, (err) => {
1847
+ process.stderr.write(`df: fatal: ${err.message}\n`);
1848
+ process.exitCode = 1;
1849
+ });
1850
+ }
1550
1851
  //# sourceMappingURL=cli.js.map