@principles/pd-cli 1.152.12 → 1.152.14

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 (305) hide show
  1. package/dist/commands/candidate.d.ts.map +1 -1
  2. package/dist/commands/candidate.js +34 -7
  3. package/dist/commands/candidate.js.map +1 -1
  4. package/dist/commands/diagnose.d.ts.map +1 -1
  5. package/dist/commands/diagnose.js +23 -7
  6. package/dist/commands/diagnose.js.map +1 -1
  7. package/dist/commands/pain-retry.d.ts.map +1 -1
  8. package/dist/commands/pain-retry.js +21 -7
  9. package/dist/commands/pain-retry.js.map +1 -1
  10. package/package.json +13 -7
  11. package/CHANGELOG.md +0 -23
  12. package/dist/commands/__tests__/intent-flag-wiring.test.d.ts +0 -9
  13. package/dist/commands/__tests__/intent-flag-wiring.test.d.ts.map +0 -1
  14. package/dist/commands/__tests__/intent-flag-wiring.test.js +0 -237
  15. package/dist/commands/__tests__/intent-flag-wiring.test.js.map +0 -1
  16. package/dist/commands/__tests__/legacy-cleanup.test.d.ts +0 -18
  17. package/dist/commands/__tests__/legacy-cleanup.test.d.ts.map +0 -1
  18. package/dist/commands/__tests__/legacy-cleanup.test.js +0 -467
  19. package/dist/commands/__tests__/legacy-cleanup.test.js.map +0 -1
  20. package/dist/commands/__tests__/mvp-smoke.test.d.ts +0 -15
  21. package/dist/commands/__tests__/mvp-smoke.test.d.ts.map +0 -1
  22. package/dist/commands/__tests__/mvp-smoke.test.js +0 -262
  23. package/dist/commands/__tests__/mvp-smoke.test.js.map +0 -1
  24. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +0 -2
  25. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +0 -1
  26. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +0 -60
  27. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +0 -1
  28. package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts +0 -21
  29. package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts.map +0 -1
  30. package/dist/commands/__tests__/rulecode-flag-wiring.test.js +0 -186
  31. package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +0 -1
  32. package/dist/commands/__tests__/rulecode-handler.test.d.ts +0 -16
  33. package/dist/commands/__tests__/rulecode-handler.test.d.ts.map +0 -1
  34. package/dist/commands/__tests__/rulecode-handler.test.js +0 -285
  35. package/dist/commands/__tests__/rulecode-handler.test.js.map +0 -1
  36. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts +0 -24
  37. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts.map +0 -1
  38. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +0 -232
  39. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +0 -1
  40. package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts +0 -15
  41. package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts.map +0 -1
  42. package/dist/commands/__tests__/runtime-activation-nextaction.test.js +0 -73
  43. package/dist/commands/__tests__/runtime-activation-nextaction.test.js.map +0 -1
  44. package/dist/commands/__tests__/runtime-probe-config.test.d.ts +0 -20
  45. package/dist/commands/__tests__/runtime-probe-config.test.d.ts.map +0 -1
  46. package/dist/commands/__tests__/runtime-probe-config.test.js +0 -389
  47. package/dist/commands/__tests__/runtime-probe-config.test.js.map +0 -1
  48. package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts +0 -9
  49. package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts.map +0 -1
  50. package/dist/commands/__tests__/telemetry-flag-wiring.test.js +0 -76
  51. package/dist/commands/__tests__/telemetry-flag-wiring.test.js.map +0 -1
  52. package/dist/commands/__tests__/version.test.d.ts +0 -2
  53. package/dist/commands/__tests__/version.test.d.ts.map +0 -1
  54. package/dist/commands/__tests__/version.test.js +0 -238
  55. package/dist/commands/__tests__/version.test.js.map +0 -1
  56. package/dist/commands/runtime-uat.guard.test.d.ts +0 -2
  57. package/dist/commands/runtime-uat.guard.test.d.ts.map +0 -1
  58. package/dist/commands/runtime-uat.guard.test.js +0 -153
  59. package/dist/commands/runtime-uat.guard.test.js.map +0 -1
  60. package/dist/services/__tests__/cli-output.test.d.ts +0 -18
  61. package/dist/services/__tests__/cli-output.test.d.ts.map +0 -1
  62. package/dist/services/__tests__/cli-output.test.js +0 -103
  63. package/dist/services/__tests__/cli-output.test.js.map +0 -1
  64. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +0 -2
  65. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +0 -1
  66. package/dist/services/__tests__/evaluator-runner-deps.test.js +0 -343
  67. package/dist/services/__tests__/evaluator-runner-deps.test.js.map +0 -1
  68. package/dist/services/__tests__/rulehost-readiness.test.d.ts +0 -2
  69. package/dist/services/__tests__/rulehost-readiness.test.d.ts.map +0 -1
  70. package/dist/services/__tests__/rulehost-readiness.test.js +0 -311
  71. package/dist/services/__tests__/rulehost-readiness.test.js.map +0 -1
  72. package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts +0 -18
  73. package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts.map +0 -1
  74. package/dist/services/__tests__/runtime-adapter-resolver.test.js +0 -673
  75. package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +0 -1
  76. package/dist/utils/production-workspace-guard.test.d.ts +0 -2
  77. package/dist/utils/production-workspace-guard.test.d.ts.map +0 -1
  78. package/dist/utils/production-workspace-guard.test.js +0 -137
  79. package/dist/utils/production-workspace-guard.test.js.map +0 -1
  80. package/scripts/llm-dogfood.ts +0 -456
  81. package/scripts/migrate-illegal-expected-decision.ts +0 -252
  82. package/src/commands/__tests__/intent-flag-wiring.test.ts +0 -282
  83. package/src/commands/__tests__/legacy-cleanup.test.ts +0 -602
  84. package/src/commands/__tests__/mvp-smoke.test.ts +0 -300
  85. package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +0 -73
  86. package/src/commands/__tests__/rulecode-flag-wiring.test.ts +0 -237
  87. package/src/commands/__tests__/rulecode-handler.test.ts +0 -369
  88. package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +0 -292
  89. package/src/commands/__tests__/runtime-activation-nextaction.test.ts +0 -81
  90. package/src/commands/__tests__/runtime-probe-config.test.ts +0 -432
  91. package/src/commands/__tests__/telemetry-flag-wiring.test.ts +0 -84
  92. package/src/commands/__tests__/version.test.ts +0 -251
  93. package/src/commands/admission-gate.ts +0 -30
  94. package/src/commands/artifact.ts +0 -82
  95. package/src/commands/build-trajectory-evidence.ts +0 -298
  96. package/src/commands/candidate.ts +0 -1250
  97. package/src/commands/codex-ingest-catchup.ts +0 -114
  98. package/src/commands/codex-ingest-quarantine.ts +0 -127
  99. package/src/commands/codex-reconcile.ts +0 -84
  100. package/src/commands/codex-setup.ts +0 -476
  101. package/src/commands/codex-worker.ts +0 -185
  102. package/src/commands/command-helpers.ts +0 -24
  103. package/src/commands/config-doctor.ts +0 -193
  104. package/src/commands/console.ts +0 -797
  105. package/src/commands/context.ts +0 -72
  106. package/src/commands/demo-story-a.ts +0 -241
  107. package/src/commands/diagnose.ts +0 -756
  108. package/src/commands/errors-list.ts +0 -441
  109. package/src/commands/evolution-tasks-list.ts +0 -48
  110. package/src/commands/evolution-tasks-show.ts +0 -62
  111. package/src/commands/flow.ts +0 -60
  112. package/src/commands/health-codex.ts +0 -576
  113. package/src/commands/health.ts +0 -315
  114. package/src/commands/history.ts +0 -63
  115. package/src/commands/intent.ts +0 -457
  116. package/src/commands/legacy-cleanup.ts +0 -531
  117. package/src/commands/legacy-import.ts +0 -104
  118. package/src/commands/mvp-smoke.ts +0 -160
  119. package/src/commands/pain-evidence.ts +0 -226
  120. package/src/commands/pain-list.ts +0 -239
  121. package/src/commands/pain-record.ts +0 -612
  122. package/src/commands/pain-retry.ts +0 -916
  123. package/src/commands/principles-stats.ts +0 -700
  124. package/src/commands/proven-channel-baseline.ts +0 -115
  125. package/src/commands/quality-scorecard.ts +0 -272
  126. package/src/commands/remediation-output.ts +0 -66
  127. package/src/commands/rulecode.ts +0 -465
  128. package/src/commands/run.ts +0 -89
  129. package/src/commands/runtime-activation.ts +0 -1629
  130. package/src/commands/runtime-artifact-repair.ts +0 -524
  131. package/src/commands/runtime-canary.ts +0 -395
  132. package/src/commands/runtime-compatibility-scan.ts +0 -105
  133. package/src/commands/runtime-diagnostics-export.ts +0 -229
  134. package/src/commands/runtime-features.ts +0 -153
  135. package/src/commands/runtime-gfi-snapshot.ts +0 -135
  136. package/src/commands/runtime-health-snapshot.ts +0 -108
  137. package/src/commands/runtime-init.ts +0 -429
  138. package/src/commands/runtime-internalization-enqueue-successors.ts +0 -530
  139. package/src/commands/runtime-internalization-integrity-repair.ts +0 -79
  140. package/src/commands/runtime-internalization-integrity.ts +0 -107
  141. package/src/commands/runtime-internalization-queue.ts +0 -135
  142. package/src/commands/runtime-internalization-retry.ts +0 -191
  143. package/src/commands/runtime-internalization-run-once.ts +0 -791
  144. package/src/commands/runtime-internalization-run-rulehost.ts +0 -749
  145. package/src/commands/runtime-internalization-wake-once.ts +0 -87
  146. package/src/commands/runtime-pain-flood-simulation.ts +0 -121
  147. package/src/commands/runtime-pruning.ts +0 -438
  148. package/src/commands/runtime-recovery-failed-tasks.ts +0 -258
  149. package/src/commands/runtime-recovery.ts +0 -109
  150. package/src/commands/runtime-synthetic-baseline.ts +0 -70
  151. package/src/commands/runtime-uat.guard.test.ts +0 -175
  152. package/src/commands/runtime-uat.ts +0 -380
  153. package/src/commands/runtime.ts +0 -473
  154. package/src/commands/samples-list.ts +0 -39
  155. package/src/commands/samples-review.ts +0 -36
  156. package/src/commands/task.ts +0 -271
  157. package/src/commands/telemetry.ts +0 -349
  158. package/src/commands/trace.ts +0 -174
  159. package/src/commands/trajectory-db-unavailable.ts +0 -19
  160. package/src/commands/trajectory.ts +0 -64
  161. package/src/commands/version.ts +0 -49
  162. package/src/config-reader.ts +0 -122
  163. package/src/index.ts +0 -1276
  164. package/src/legacy/legacy-import.ts +0 -179
  165. package/src/legacy/session-history-import.ts +0 -203
  166. package/src/resolve-workspace.ts +0 -106
  167. package/src/services/__tests__/cli-output.test.ts +0 -130
  168. package/src/services/__tests__/evaluator-runner-deps.test.ts +0 -382
  169. package/src/services/__tests__/rulehost-readiness.test.ts +0 -363
  170. package/src/services/__tests__/runtime-adapter-resolver.test.ts +0 -793
  171. package/src/services/cli-output.ts +0 -95
  172. package/src/services/config-doctor.ts +0 -697
  173. package/src/services/console-launcher.ts +0 -537
  174. package/src/services/demo-rule-compiler.ts +0 -40
  175. package/src/services/demo-story-a-runner.ts +0 -471
  176. package/src/services/mainline-snapshot-assembler.ts +0 -579
  177. package/src/services/pain-flood-simulation-runner.ts +0 -462
  178. package/src/services/pd-config-loader.ts +0 -433
  179. package/src/services/proven-channel-baseline-runner.ts +0 -157
  180. package/src/services/quality-scorecard/data-extractor.ts +0 -150
  181. package/src/services/quality-scorecard/local-evaluator.ts +0 -142
  182. package/src/services/quality-scorecard/strong-model-gate.ts +0 -202
  183. package/src/services/resolve-runtime-from-pd-config.ts +0 -230
  184. package/src/services/rulehost-pipeline-runner.ts +0 -1214
  185. package/src/services/rulehost-readiness.ts +0 -326
  186. package/src/services/runtime-adapter-resolver.ts +0 -364
  187. package/src/services/synthetic-baseline-runner.ts +0 -400
  188. package/src/services/version-report.ts +0 -253
  189. package/src/services/workspace-tool-semantics.ts +0 -26
  190. package/src/utils/path-security.ts +0 -96
  191. package/src/utils/production-workspace-guard.test.ts +0 -157
  192. package/src/utils/production-workspace-guard.ts +0 -210
  193. package/tests/bdd/cli-contract.steps.ts +0 -346
  194. package/tests/bdd/principles-stats.steps.ts +0 -211
  195. package/tests/bdd/support/gherkin-loader.ts +0 -147
  196. package/tests/bdd/support/repo-root.ts +0 -62
  197. package/tests/bdd/support/vitest-bdd.ts +0 -179
  198. package/tests/commands/admission-gate-coverage.test.ts +0 -79
  199. package/tests/commands/build-trajectory-evidence.test.ts +0 -508
  200. package/tests/commands/candidate-audit-repair.test.ts +0 -421
  201. package/tests/commands/candidate-intake.test.ts +0 -732
  202. package/tests/commands/candidate-internalization-backfill.test.ts +0 -548
  203. package/tests/commands/candidate-internalize-lineage.test.ts +0 -628
  204. package/tests/commands/candidate-internalize.test.ts +0 -299
  205. package/tests/commands/candidate-route.test.ts +0 -329
  206. package/tests/commands/candidate-show.test.ts +0 -95
  207. package/tests/commands/cli-command-tree.test.ts +0 -144
  208. package/tests/commands/cli-help-snapshot.test.ts +0 -155
  209. package/tests/commands/cli-skill-contract.test.ts +0 -121
  210. package/tests/commands/codex-consent-reversibility.steps.test.ts +0 -299
  211. package/tests/commands/codex-ingest-quarantine.test.ts +0 -136
  212. package/tests/commands/codex-setup.test.ts +0 -297
  213. package/tests/commands/codex-worker-daemon.test.ts +0 -95
  214. package/tests/commands/codex-worker-registration.test.ts +0 -207
  215. package/tests/commands/config-doctor.test.ts +0 -336
  216. package/tests/commands/console-launcher-edge-cases.test.ts +0 -388
  217. package/tests/commands/console-open.test.ts +0 -1381
  218. package/tests/commands/context.test.ts +0 -114
  219. package/tests/commands/demo-story-a.test.ts +0 -318
  220. package/tests/commands/diagnose.test.ts +0 -1803
  221. package/tests/commands/errors-list.test.ts +0 -679
  222. package/tests/commands/evolution-tasks-list.test.ts +0 -41
  223. package/tests/commands/evolution-tasks-show.test.ts +0 -46
  224. package/tests/commands/health-codex.test.ts +0 -349
  225. package/tests/commands/health-host-wiring.test.ts +0 -76
  226. package/tests/commands/health-reliability.test.ts +0 -99
  227. package/tests/commands/health.test.ts +0 -413
  228. package/tests/commands/intent.test.ts +0 -321
  229. package/tests/commands/legacy-cleanup.test.ts +0 -148
  230. package/tests/commands/pain-evidence.test.ts +0 -518
  231. package/tests/commands/pain-list.test.ts +0 -219
  232. package/tests/commands/pain-record-async.test.ts +0 -335
  233. package/tests/commands/pain-record-session-parser.test.ts +0 -228
  234. package/tests/commands/pain-record.test.ts +0 -851
  235. package/tests/commands/pain-retry.test.ts +0 -1121
  236. package/tests/commands/plugin-config-resolution-cutover.test.ts +0 -229
  237. package/tests/commands/pri-393-runtime-config-unification.test.ts +0 -443
  238. package/tests/commands/principles-stats-wiring.test.ts +0 -135
  239. package/tests/commands/principles-stats.test.ts +0 -274
  240. package/tests/commands/product-path-regression.test.ts +0 -86
  241. package/tests/commands/proven-channel-baseline.test.ts +0 -441
  242. package/tests/commands/rulecode.test.ts +0 -177
  243. package/tests/commands/run-rulehost-handler.test.ts +0 -908
  244. package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +0 -227
  245. package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +0 -167
  246. package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +0 -262
  247. package/tests/commands/runtime-activation-governance-audit.test.ts +0 -95
  248. package/tests/commands/runtime-activation-list-flag-wiring.test.ts +0 -193
  249. package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +0 -248
  250. package/tests/commands/runtime-activation-shadow-telemetry.test.ts +0 -130
  251. package/tests/commands/runtime-activation.test.ts +0 -2052
  252. package/tests/commands/runtime-artifact-repair-registration.test.ts +0 -74
  253. package/tests/commands/runtime-artifact-repair.test.ts +0 -404
  254. package/tests/commands/runtime-canary.test.ts +0 -430
  255. package/tests/commands/runtime-compatibility-scan.test.ts +0 -146
  256. package/tests/commands/runtime-diagnostics-export.test.ts +0 -175
  257. package/tests/commands/runtime-features.test.ts +0 -249
  258. package/tests/commands/runtime-health-snapshot.test.ts +0 -358
  259. package/tests/commands/runtime-init-empty-workspace.test.ts +0 -252
  260. package/tests/commands/runtime-init.test.ts +0 -420
  261. package/tests/commands/runtime-internalization-enqueue-successors.test.ts +0 -782
  262. package/tests/commands/runtime-internalization-integrity-repair.test.ts +0 -239
  263. package/tests/commands/runtime-internalization-integrity.test.ts +0 -140
  264. package/tests/commands/runtime-internalization-queue.test.ts +0 -380
  265. package/tests/commands/runtime-internalization-retry-owner-authority.test.ts +0 -431
  266. package/tests/commands/runtime-internalization-run-once-evaluator-parity.test.ts +0 -269
  267. package/tests/commands/runtime-internalization-run-once-language.test.ts +0 -303
  268. package/tests/commands/runtime-internalization-run-once-real-config.test.ts +0 -237
  269. package/tests/commands/runtime-internalization-run-once-rollout-parity.test.ts +0 -706
  270. package/tests/commands/runtime-internalization-run-once.test.ts +0 -1791
  271. package/tests/commands/runtime-internalization-wake-once.test.ts +0 -171
  272. package/tests/commands/runtime-internalization.test.ts +0 -53
  273. package/tests/commands/runtime-pain-flood-simulation.test.ts +0 -418
  274. package/tests/commands/runtime-pruning.test.ts +0 -694
  275. package/tests/commands/runtime-recovery-failed-tasks.test.ts +0 -328
  276. package/tests/commands/runtime-recovery.test.ts +0 -120
  277. package/tests/commands/runtime-synthetic-baseline.test.ts +0 -249
  278. package/tests/commands/runtime-uat.test.ts +0 -397
  279. package/tests/commands/runtime.test.ts +0 -392
  280. package/tests/commands/samples-list.test.ts +0 -43
  281. package/tests/commands/samples-review.test.ts +0 -41
  282. package/tests/commands/task.test.ts +0 -153
  283. package/tests/commands/telemetry.test.ts +0 -190
  284. package/tests/commands/trace.test.ts +0 -316
  285. package/tests/config-reader.test.ts +0 -143
  286. package/tests/e2e/candidate-intake-e2e.test.ts +0 -338
  287. package/tests/e2e/cli-full-flow.test.ts +0 -233
  288. package/tests/e2e/cross-package-acceptance.test.ts +0 -635
  289. package/tests/helpers/pd-cli-path.ts +0 -5
  290. package/tests/pd-cli-smoke.test.ts +0 -13
  291. package/tests/resolve-workspace.test.ts +0 -196
  292. package/tests/services/config-doctor-signal-health.test.ts +0 -109
  293. package/tests/services/console-launcher.test.ts +0 -114
  294. package/tests/services/demo-rule-compiler.test.ts +0 -242
  295. package/tests/services/mainline-snapshot-assembler.test.ts +0 -572
  296. package/tests/services/pd-config-loader.test.ts +0 -505
  297. package/tests/services/proven-channel-baseline-runner.test.ts +0 -30
  298. package/tests/services/quality-scorecard/strong-model-gate.test.ts +0 -133
  299. package/tests/services/resolve-runtime-from-pd-config.test.ts +0 -485
  300. package/tests/services/rulehost-pipeline-e2e.test.ts +0 -538
  301. package/tests/services/rulehost-pipeline-runner.test.ts +0 -1242
  302. package/tests/services/workspace-tool-semantics.test.ts +0 -76
  303. package/tests/utils/path-security.test.ts +0 -180
  304. package/tsconfig.json +0 -26
  305. package/vitest.config.ts +0 -10
@@ -1,476 +0,0 @@
1
- /**
2
- * pd codex setup — consent UX for Codex conversation ingestion (Codex
3
- * Governance Closure Slice D, PRI-625; SPEC rev 2 §17; G2A frozen disclosure).
4
- *
5
- * The ONE authority for enabling `codex_conversation_ingestion`: presents the
6
- * G2A-frozen disclosure text verbatim (Chinese SSoT; optional English
7
- * rendering) and records the Owner's explicit decision BEFORE the flag is
8
- * flipped. Declining leaves the flag off, leaves every existing
9
- * prompt/RuleHost/tool governance surface untouched, and never opens a
10
- * transcript (this command performs no Codex-home I/O at all). Upgrade paths
11
- * never call this command, so upgrading can never enable ingestion.
12
- *
13
- * Modes:
14
- * - default: print disclosure, then interactive prompt (TTY) for an explicit
15
- * yes/no; EOF/abort mutates nothing and records nothing.
16
- * - --accept / --decline: non-interactive explicit decision (the plugin's
17
- * $pd-setup presents the disclosure itself, then calls one of these).
18
- * - --show-disclosure: print the frozen text for the requested language and
19
- * exit; no mutation, no record.
20
- *
21
- * Config writes are line-targeted edits of .pd/config.yaml (comments
22
- * preserved), atomic, and round-trip verified: if the rewritten config does
23
- * not validate with the flag at the intended value, the previous content is
24
- * restored and the failure is loud — a half-consented state is impossible.
25
- *
26
- * CLI gate compliance:
27
- * - cli-1: --json outputs exactly one parseable JSON object on stdout.
28
- * - cli-2: exit paths stop execution.
29
- * - cli-4: --accept and --decline are mutually exclusive.
30
- * - cli-5: refused/failed runs mutate nothing (consent + flag both untouched).
31
- * - cli-6: every refusal/degradation carries reason + nextAction.
32
- */
33
- import * as fs from 'node:fs';
34
- import * as path from 'node:path';
35
- import * as readline from 'node:readline/promises';
36
- import { resolveWorkspaceDir } from '../resolve-workspace.js';
37
- import {
38
- CODEX_INGESTION_DISCLOSURE_VERSION,
39
- getCodexIngestionDisclosureText,
40
- deriveCodexIngestionConsentState,
41
- getPdConfigPath,
42
- loadPdConfigForPlugin,
43
- readCodexIngestionConsent,
44
- recordCodexIngestionConsent,
45
- type CodexIngestionConsentDecision,
46
- type CodexIngestionConsentState,
47
- type CodexIngestionDisclosureLanguage,
48
- } from '@principles/host-runtime';
49
- import { computeEffectivePdConfig, computeFeatureFlagsFromConfig, isFeatureEnabled } from '@principles/core/runtime-v2';
50
-
51
- export interface CodexSetupOptions {
52
- workspace?: string;
53
- json?: boolean;
54
- lang?: string;
55
- accept?: boolean;
56
- decline?: boolean;
57
- showDisclosure?: boolean;
58
- }
59
-
60
- export interface CodexSetupReport {
61
- generatedAt: string;
62
- host: 'codex';
63
- workspace: string;
64
- status: 'ok' | 'degraded';
65
- decision?: CodexIngestionConsentDecision;
66
- consentStateBefore: CodexIngestionConsentState;
67
- consentState: CodexIngestionConsentState;
68
- disclosureVersion: string;
69
- ingestionFlag: { name: 'codex_conversation_ingestion'; enabled: boolean; source: string };
70
- hostCodexFlagEnabled: boolean;
71
- warnings: string[];
72
- reason?: string;
73
- nextAction?: string;
74
- }
75
-
76
- // ── Targeted config.yaml flag edit ───────────────────────────────────────────
77
-
78
- type FlagWriteResult = { ok: true; enabled: boolean } | { ok: false; reason: string; nextAction: string };
79
-
80
- const FEATURES_LINE = /^features:\s*(?:#.*)?$/;
81
- const INGESTION_KEY_LINE = /^ {2}codex_conversation_ingestion:\s*(?:#.*)?$/;
82
- const INGESTION_ENABLED_LINE = /^ {4}enabled:\s*(?:true|false)\s*(?:#.*)?$/;
83
-
84
- function stripYamlComment(line: string): string {
85
- const hash = line.indexOf('#');
86
- return (hash === -1 ? line : line.slice(0, hash)).trim();
87
- }
88
-
89
- function isEmptyFeaturesMapping(line: string): boolean {
90
- // Matches `features: {}` (with optional YAML comment) without a regex
91
- // literal over brace syntax.
92
- return stripYamlComment(line).replace(/\s+/g, '') === 'features:' + String.fromCharCode(123, 125);
93
- }
94
-
95
- function trailingComment(line: string): string {
96
- // ' #' starts a plain-scalar YAML comment on these simple enabled lines.
97
- const hash = line.indexOf(' #');
98
- return hash === -1 ? '' : ' ' + line.slice(hash + 1);
99
- }
100
-
101
- /**
102
- * Enable/disable `features.codex_conversation_ingestion.enabled` in the
103
- * workspace config.yaml with a line-targeted edit that preserves all other
104
- * content (comments included). Atomic write; round-trip verified with the
105
- * production loader, restored on any mismatch.
106
- */
107
- export function setCodexConversationIngestionFlag(workspaceDir: string, enabled: boolean): FlagWriteResult {
108
- const configPath = getPdConfigPath(workspaceDir);
109
- let raw: string;
110
- try {
111
- raw = fs.readFileSync(configPath, 'utf8');
112
- } catch (error) {
113
- const code = typeof error === 'object' && error !== null && Object.hasOwn(error, 'code')
114
- ? String((error as Record<string, unknown>).code)
115
- : String(error);
116
- return {
117
- ok: false,
118
- reason: 'config_unreadable: ' + code,
119
- nextAction: 'Run `pd runtime init --confirm` in this workspace to create .pd/config.yaml, then re-run `pd codex setup`.',
120
- };
121
- }
122
- const lineEnding = raw.includes('\r\n') ? '\r\n' : '\n';
123
- const lines = raw.replace(/\r\n/g, '\n').split('\n');
124
-
125
- // Every feature override requires category+enabled (validatePdConfig), so
126
- // inserted blocks carry the registry's current category for this flag.
127
- const registryCategory =
128
- computeFeatureFlagsFromConfig(computeEffectivePdConfig(null)).flags.codex_conversation_ingestion?.category ?? 'quiet';
129
- // A bare `features:` line and an inline empty mapping both count as the
130
- // features section; anything else is a mapping with entries.
131
- const featuresIndex = lines.findIndex((line) => FEATURES_LINE.test(line) || isEmptyFeaturesMapping(line));
132
- const blockLines = [
133
- ' codex_conversation_ingestion:',
134
- ' category: ' + registryCategory,
135
- ' enabled: ' + String(enabled),
136
- ];
137
-
138
- let mutated = false;
139
- if (featuresIndex === -1) {
140
- // Unreachable behind the workspace_config gate (validatePdConfig requires
141
- // a features section); refuse loudly instead of inventing one (cli-5).
142
- return {
143
- ok: false,
144
- reason: 'config_features_section_missing',
145
- nextAction: 'Add a features section to ' + configPath + ' and re-run `pd codex setup`; config.yaml was left unchanged.',
146
- };
147
- }
148
- const featuresLine: string | undefined = lines[featuresIndex];
149
- if (featuresLine === undefined) {
150
- return { ok: false, reason: 'config_features_line_undefined', nextAction: 'config.yaml was left unchanged; re-run `pd codex setup`.' };
151
- }
152
- if (isEmptyFeaturesMapping(featuresLine)) {
153
- // The inline empty mapping IS the features key — keep the parent key and
154
- // expand its children in place.
155
- lines.splice(featuresIndex, 1, 'features:', ...blockLines);
156
- mutated = true;
157
- } else {
158
- // Find the ingestion key's block under features: (2-space indent) and the
159
- // end of that block (next 2-space-indented key or next top-level key).
160
- let keyIndex = -1;
161
- let blockEnd = lines.length;
162
- for (let i = featuresIndex + 1; i < lines.length; i += 1) {
163
- const line: string | undefined = lines[i];
164
- if (line === undefined || line.trim() === '') continue;
165
- if (INGESTION_KEY_LINE.test(line)) {
166
- keyIndex = i;
167
- continue;
168
- }
169
- if (keyIndex !== -1 && /^( {0,1}\S| {2}\S)/.test(line)) {
170
- blockEnd = i;
171
- break;
172
- }
173
- }
174
- if (keyIndex === -1) {
175
- lines.splice(featuresIndex + 1, 0, ...blockLines);
176
- mutated = true;
177
- } else {
178
- // enabled-line search: found → rewrite (or keep); NOT found → insert.
179
- // `enabledFound` is tracked separately from `mutated` so an existing
180
- // line that already carries the target value does NOT trigger a
181
- // second insertion (duplicate-key bug, review round 3).
182
- let enabledFound = false;
183
- for (let i = keyIndex + 1; i < blockEnd; i += 1) {
184
- const current: string | undefined = lines[i];
185
- if (current === undefined || !INGESTION_ENABLED_LINE.test(current)) continue;
186
- enabledFound = true;
187
- const next = ' enabled: ' + String(enabled) + trailingComment(current);
188
- if (current !== next) {
189
- lines[i] = next;
190
- mutated = true;
191
- }
192
- break;
193
- }
194
- if (!enabledFound) {
195
- // Key exists but no enabled line inside its block — add it as the
196
- // first entry of the block so the mapping is not folded into a sibling.
197
- lines.splice(keyIndex + 1, 0, ' enabled: ' + String(enabled));
198
- mutated = true;
199
- }
200
- }
201
- }
202
-
203
- if (!mutated) {
204
- return { ok: true, enabled };
205
- }
206
- const nextContent = lines.join(lineEnding);
207
- const tmpPath = configPath + '.tmp-setup-' + String(process.pid) + '-' + String(Date.now());
208
- try {
209
- fs.mkdirSync(path.dirname(configPath), { recursive: true });
210
- fs.writeFileSync(tmpPath, nextContent, { encoding: 'utf8' });
211
- fs.renameSync(tmpPath, configPath);
212
- } catch (error) {
213
- const message = error instanceof Error ? error.message : String(error);
214
- try {
215
- fs.rmSync(tmpPath, { force: true });
216
- } catch {
217
- // best-effort cleanup; the failure below is the loud signal
218
- }
219
- return {
220
- ok: false,
221
- reason: 'config_write_failed: ' + message.slice(0, 160),
222
- nextAction: 'Check permissions on ' + path.dirname(configPath) + '; config.yaml was left unchanged.',
223
- };
224
- }
225
- // Round-trip: the production loader must validate the rewrite AND report
226
- // the flag at the intended value. Anything else → restore, fail loud.
227
- const verification = loadPdConfigForPlugin(workspaceDir);
228
- const verifiedEnabled = verification.ok
229
- ? isFeatureEnabled(computeFeatureFlagsFromConfig(verification.effective), 'codex_conversation_ingestion')
230
- : undefined;
231
- if (!verification.ok || verifiedEnabled !== enabled) {
232
- try {
233
- fs.writeFileSync(configPath, raw, { encoding: 'utf8' });
234
- } catch {
235
- // restoration failure must not mask the primary failure
236
- }
237
- return {
238
- ok: false,
239
- reason: verification.ok ? 'config_roundtrip_mismatch' : 'config_roundtrip_invalid: ' + (verification.errors[0]?.reason ?? 'unknown'),
240
- nextAction: 'config.yaml was restored to its previous content. Fix the features block manually in ' + configPath + ' and re-run `pd codex setup`.',
241
- };
242
- }
243
- return { ok: true, enabled };
244
- }
245
-
246
- // ── Interactive decision ─────────────────────────────────────────────────────
247
-
248
- async function promptExplicitDecision(disclosure: string): Promise<'granted' | 'declined' | 'aborted'> {
249
- process.stdout.write(disclosure);
250
- process.stdout.write(
251
- '\n按上述说明做出选择(y = 开启对话观察并写入治理闭环 / n = 拒绝,保持关闭)。\n' +
252
- 'Make your choice per the disclosure above (y = enable / n = decline, keep off):\n> ',
253
- );
254
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
255
- try {
256
- const answer = (await rl.question('')).trim().toLowerCase();
257
- if (answer === 'y' || answer === 'yes') return 'granted';
258
- if (answer === 'n' || answer === 'no') return 'declined';
259
- return 'aborted';
260
- } finally {
261
- rl.close();
262
- }
263
- }
264
-
265
- // ── Command handler ──────────────────────────────────────────────────────────
266
-
267
- function languageFrom(lang: string | undefined): CodexIngestionDisclosureLanguage {
268
- return lang === 'en' ? 'en' : 'zh';
269
- }
270
-
271
- export async function handleCodexSetup(options: CodexSetupOptions): Promise<void> {
272
- const generatedAt = new Date().toISOString();
273
-
274
- if (options.showDisclosure) {
275
- process.stdout.write(getCodexIngestionDisclosureText(languageFrom(options.lang)));
276
- process.stdout.write('\n');
277
- return;
278
- }
279
-
280
- const workspace = resolveWorkspaceDir(options.workspace);
281
- const warnings: string[] = [];
282
- const finish = (report: CodexSetupReport): void => {
283
- if (options.json) {
284
- // cli-1: exactly one parseable JSON object on stdout.
285
- console.log(JSON.stringify(report));
286
- } else {
287
- const lines = [
288
- 'Codex conversation-ingestion setup (' + report.workspace + ')',
289
- ' status: ' + report.status,
290
- ' disclosureVersion: ' + report.disclosureVersion,
291
- ' consent (before): ' + report.consentStateBefore,
292
- ' consent (now): ' + report.consentState,
293
- ' ingestion flag: ' + String(report.ingestionFlag.enabled) + ' (source: ' + report.ingestionFlag.source + ')',
294
- ' host.codex flag: ' + String(report.hostCodexFlagEnabled),
295
- ];
296
- for (const warning of report.warnings) lines.push(' warning: ' + warning);
297
- if (report.reason !== undefined) lines.push(' reason: ' + report.reason);
298
- if (report.nextAction !== undefined) lines.push(' next action: ' + report.nextAction);
299
- console.log(lines.join('\n'));
300
- }
301
- if (report.status === 'degraded') process.exitCode = 1;
302
- };
303
-
304
- const refuse = (reason: string, nextAction: string, consentStateBefore: CodexIngestionConsentState = 'not_present'): void => {
305
- finish({
306
- generatedAt, host: 'codex', workspace, status: 'degraded',
307
- consentStateBefore, consentState: consentStateBefore, disclosureVersion: CODEX_INGESTION_DISCLOSURE_VERSION,
308
- ingestionFlag: { name: 'codex_conversation_ingestion', enabled: false, source: 'unknown' },
309
- hostCodexFlagEnabled: false, warnings, reason, nextAction,
310
- });
311
- };
312
-
313
- // cli-4: explicit mutual exclusion.
314
- if (options.accept && options.decline) {
315
- refuse('accept_decline_mutex', 'Pass either --accept or --decline, not both.');
316
- return;
317
- }
318
-
319
- // Workspace must exist as a PD workspace (config present) before anything
320
- // can be consented or mutated (cli-5).
321
- const configPath = getPdConfigPath(workspace);
322
- if (!fs.existsSync(configPath)) {
323
- refuse('workspace_config_not_found', 'No .pd/config.yaml at ' + workspace + '. Run `pd runtime init --confirm` first, then re-run `pd codex setup`.');
324
- return;
325
- }
326
- const configLoad = loadPdConfigForPlugin(workspace);
327
- if (!configLoad.ok) {
328
- refuse(
329
- 'workspace_config_malformed: ' + (configLoad.errors[0]?.reason ?? 'unknown'),
330
- 'Fix .pd/config.yaml first (' + (configLoad.errors[0]?.nextAction ?? 'fix YAML syntax') + '); PD will not consent or mutate a config it cannot validate.',
331
- );
332
- return;
333
- }
334
- const flags = computeFeatureFlagsFromConfig(configLoad.effective);
335
- const ingestionEnabledBefore = isFeatureEnabled(flags, 'codex_conversation_ingestion');
336
- const hostCodexEnabled = isFeatureEnabled(flags, 'host.codex');
337
- const flagSource = configLoad.source;
338
-
339
- const consentRead = readCodexIngestionConsent(workspace);
340
- if (!consentRead.ok) {
341
- refuse(consentRead.reason, consentRead.nextAction);
342
- return;
343
- }
344
- const consentStateBefore = deriveCodexIngestionConsentState(consentRead.record, ingestionEnabledBefore);
345
-
346
- // Resolve the decision.
347
- let decision: 'granted' | 'declined' | 'aborted';
348
- if (options.accept) decision = 'granted';
349
- else if (options.decline) decision = 'declined';
350
- else if (options.json) {
351
- refuse('decision_required', 'Machine mode must state the decision explicitly: re-run with --accept or --decline (use --show-disclosure to print the frozen text for presentation first).');
352
- return;
353
- } else if (process.stdin.isTTY) {
354
- decision = await promptExplicitDecision(getCodexIngestionDisclosureText(languageFrom(options.lang)) + '\n');
355
- if (decision === 'aborted') {
356
- refuse('decision_aborted', 'Nothing was changed or recorded. Re-run `pd codex setup` to see the disclosure again.');
357
- return;
358
- }
359
- } else {
360
- refuse('decision_required', 'No TTY available for the interactive prompt. Present the disclosure (`pd codex setup --show-disclosure`), then re-run with --accept or --decline.');
361
- return;
362
- }
363
-
364
- // Consent state machine (review round 2): the record must always explain
365
- // the flag. `granted` is NEVER recorded before the runtime activation has
366
- // actually succeeded.
367
- //
368
- // ACCEPT: record 'pending' → enable flag → 'granted' on success, 'failed'
369
- // (with the activation reason) on failure.
370
- // DECLINE: force the flag OFF first → 'revoked' after the flag is off;
371
- // if the flag-off write fails → 'failed' (a revoked record beside a live
372
- // flag would be unexplainable). If the flag was already off, decline is a
373
- // pure record write.
374
- const recordConsent = (decisionState: CodexIngestionConsentDecision, failureReason?: string) =>
375
- recordCodexIngestionConsent(workspace, {
376
- decision: decisionState,
377
- decidedVia: 'pd_codex_setup',
378
- ...(failureReason !== undefined ? { failureReason } : {}),
379
- });
380
-
381
- if (decision === 'declined') {
382
- let flagResult: FlagWriteResult = { ok: true, enabled: ingestionEnabledBefore };
383
- if (ingestionEnabledBefore) {
384
- flagResult = setCodexConversationIngestionFlag(workspace, false);
385
- }
386
- if (!flagResult.ok) {
387
- const failed = recordConsent('failed', 'decline could not disable the ingestion flag: ' + flagResult.reason);
388
- finish({
389
- generatedAt, host: 'codex', workspace, status: 'degraded',
390
- decision: 'failed', consentStateBefore,
391
- consentState: failed.ok ? 'failed' : consentStateBefore,
392
- disclosureVersion: CODEX_INGESTION_DISCLOSURE_VERSION,
393
- ingestionFlag: { name: 'codex_conversation_ingestion', enabled: true, source: flagSource },
394
- hostCodexFlagEnabled: hostCodexEnabled, warnings,
395
- reason: flagResult.reason, nextAction: flagResult.nextAction,
396
- });
397
- return;
398
- }
399
- const revoked = recordConsent('revoked');
400
- if (!revoked.ok) {
401
- refuse(revoked.reason, revoked.nextAction, consentStateBefore);
402
- return;
403
- }
404
- finish({
405
- generatedAt, host: 'codex', workspace, status: 'ok',
406
- decision: 'revoked',
407
- consentStateBefore,
408
- consentState: deriveCodexIngestionConsentState(revoked.record, false),
409
- disclosureVersion: revoked.record.disclosureVersion,
410
- ingestionFlag: { name: 'codex_conversation_ingestion', enabled: false, source: flagSource },
411
- hostCodexFlagEnabled: hostCodexEnabled,
412
- warnings,
413
- nextAction: 'Ingestion stays off; prompt injection, RuleHost, and tool governance are unchanged. No transcript was or will be read.',
414
- });
415
- return;
416
- }
417
-
418
- // ACCEPT flow.
419
- const pending = recordConsent('pending');
420
- if (!pending.ok) {
421
- refuse(pending.reason, pending.nextAction, consentStateBefore);
422
- return;
423
- }
424
-
425
- let flagResult: FlagWriteResult = { ok: true, enabled: ingestionEnabledBefore };
426
- if (!ingestionEnabledBefore) {
427
- flagResult = setCodexConversationIngestionFlag(workspace, true);
428
- }
429
- if (!flagResult.ok) {
430
- // Activation failed: consent must NOT be granted. Record the explained
431
- // failure and report degraded — flag stays off, state stays interpretable.
432
- const failed = recordConsent('failed', 'flag activation failed: ' + flagResult.reason);
433
- finish({
434
- generatedAt, host: 'codex', workspace, status: 'degraded',
435
- decision: 'failed', consentStateBefore,
436
- consentState: failed.ok ? 'failed' : 'pending',
437
- disclosureVersion: CODEX_INGESTION_DISCLOSURE_VERSION,
438
- ingestionFlag: { name: 'codex_conversation_ingestion', enabled: false, source: flagSource },
439
- hostCodexFlagEnabled: hostCodexEnabled, warnings,
440
- reason: flagResult.reason, nextAction: flagResult.nextAction,
441
- });
442
- return;
443
- }
444
-
445
- const granted = recordConsent('granted');
446
- if (!granted.ok) {
447
- // The flag IS enabled but the terminal write failed — this is exactly the
448
- // 'pending + flag on' state: explainable, and health blocks on it.
449
- finish({
450
- generatedAt, host: 'codex', workspace, status: 'degraded',
451
- decision: 'pending', consentStateBefore,
452
- consentState: 'pending',
453
- disclosureVersion: CODEX_INGESTION_DISCLOSURE_VERSION,
454
- ingestionFlag: { name: 'codex_conversation_ingestion', enabled: true, source: flagSource },
455
- hostCodexFlagEnabled: hostCodexEnabled, warnings,
456
- reason: granted.reason, nextAction: granted.nextAction,
457
- });
458
- return;
459
- }
460
-
461
- const nextWarnings = [...warnings];
462
- if (!hostCodexEnabled) {
463
- nextWarnings.push('host.codex is disabled — ingestion stays inactive until features.host.codex.enabled=true; enable it explicitly if this workspace should run Codex governance at all.');
464
- }
465
-
466
- finish({
467
- generatedAt, host: 'codex', workspace, status: 'ok',
468
- decision: 'granted',
469
- consentStateBefore,
470
- consentState: deriveCodexIngestionConsentState(granted.record, true),
471
- disclosureVersion: granted.record.disclosureVersion,
472
- ingestionFlag: { name: 'codex_conversation_ingestion', enabled: true, source: flagSource },
473
- hostCodexFlagEnabled: hostCodexEnabled,
474
- warnings: nextWarnings,
475
- });
476
- }
@@ -1,185 +0,0 @@
1
- /**
2
- * pd codex worker command implementation (Codex Governance Closure Slice C,
3
- * PRI-624; SPEC §13/§15; ADR-0020 §11.1).
4
- *
5
- * Runs the Workspace-scoped worker cycle — catch-up → reconciliation → one
6
- * Diagnostician execution → one bounded downstream consumer cycle — either
7
- * once (manual / Companion-supervised restart mode) or continuously.
8
- *
9
- * `status` answers the SPEC §15 worker-mode question without executing
10
- * anything: `manual_action_required` means no Companion-registered worker
11
- * serves this workspace (it is not in the install manifest) and the manual
12
- * commands below are the recovery path.
13
- *
14
- * CLI gate compliance:
15
- * - cli-1: --json is restricted to the single-output forms (--once / --status)
16
- * so stdout is exactly one parseable JSON object; continuous mode streams
17
- * human-readable reports instead (review P2).
18
- * - cli-2: exit paths stop execution.
19
- * - cli-6: paused/degraded modes carry reason + nextAction.
20
- */
21
- import fs from 'node:fs';
22
- import os from 'node:os';
23
- import path from 'node:path';
24
- import { resolveWorkspaceDir } from '../resolve-workspace.js';
25
- import { runCodexWorkspaceWorkerCycle, type CodexWorkerCycleResult } from '@principles/codex-adapter';
26
- import { getInstallLayoutPaths, parseInstallManifest } from '@principles/install-layout';
27
-
28
- const DEFAULT_INTERVAL_MS = 120_000;
29
-
30
- interface CodexWorkerOptions {
31
- workspace?: string;
32
- json?: boolean;
33
- once?: boolean;
34
- intervalMs?: number;
35
- status?: boolean;
36
- }
37
-
38
- export interface CodexWorkerStatusReport {
39
- generatedAt: string;
40
- host: 'codex';
41
- workspace: string;
42
- mode: 'ready' | 'manual_action_required' | 'paused' | 'degraded';
43
- registeredInInstallManifest: boolean;
44
- reason?: string;
45
- nextAction?: string;
46
- }
47
-
48
- async function printStatusReport(workspace: string, json: boolean): Promise<void> {
49
- const generatedAt = new Date().toISOString();
50
- const paths = getInstallLayoutPaths(os.homedir());
51
- let registered = false;
52
- let manifestError: string | undefined;
53
- try {
54
- const raw = JSON.parse(fs.readFileSync(paths.manifest, 'utf8')) as unknown;
55
- const parsed = parseInstallManifest(raw);
56
- if (parsed.manifest !== undefined) {
57
- registered = parsed.manifest.workspaces?.some((entry) => path.resolve(entry) === path.resolve(workspace)) ?? false;
58
- } else {
59
- manifestError = parsed.error;
60
- }
61
- } catch {
62
- manifestError = 'install_manifest_unreadable';
63
- }
64
-
65
- // The status scan itself delegates mode computation to the cycle module's
66
- // flag/config semantics by running a paused-compatible evaluation: a real
67
- // cycle is NOT run (no execution, no lease, no transcript I/O).
68
- const { computeCodexWorkerStatusMode } = await import('@principles/codex-adapter');
69
- const evaluation = computeCodexWorkerStatusMode({ workspaceDir: workspace, registeredInInstallManifest: registered });
70
-
71
- const report: CodexWorkerStatusReport = {
72
- generatedAt,
73
- host: 'codex',
74
- workspace,
75
- mode: evaluation.mode,
76
- registeredInInstallManifest: registered,
77
- ...(evaluation.reason !== undefined ? { reason: evaluation.reason } : {}),
78
- ...(evaluation.nextAction !== undefined ? { nextAction: evaluation.nextAction } : {}),
79
- ...(manifestError !== undefined ? { reason: `${evaluation.reason ?? manifestError}` } : {}),
80
- };
81
- if (json) {
82
- console.log(JSON.stringify(report));
83
- return;
84
- }
85
- const lines = [
86
- `Codex workspace worker status (${workspace})`,
87
- ` mode: ${report.mode}`,
88
- ` registered in install manifest: ${registered}`,
89
- ];
90
- if (report.reason !== undefined) lines.push(` reason: ${report.reason}`);
91
- if (report.nextAction !== undefined) lines.push(` next action: ${report.nextAction}`);
92
- console.log(lines.join('\n'));
93
- if (report.mode === 'degraded') process.exitCode = 1;
94
- }
95
-
96
- function printCycleReport(result: CodexWorkerCycleResult, json: boolean): void {
97
- if (json) {
98
- console.log(JSON.stringify(result));
99
- return;
100
- }
101
- const lines = [
102
- `Codex workspace worker (${result.workspaceDir})`,
103
- ` mode: ${result.mode}${result.reason !== undefined ? ` (${result.reason})` : ''}`,
104
- ];
105
- if (result.report !== undefined) {
106
- const {catchUp} = result.report;
107
- const catchUpSummary = catchUp.status === 'skipped' ? `skipped (${catchUp.reason})` : `${catchUp.status} — processed=${catchUp.rollouts.length} lag=${catchUp.remainingLagRollouts.length}`;
108
- lines.push(` catch-up: ${catchUpSummary}`);
109
- lines.push(` reconcile: ok=${result.report.reconcile.ok} ensured=${result.report.reconcile.tasksEnsured} linksRepaired=${result.report.reconcile.linksRepaired}`);
110
- if (result.report.diagnostician !== null) {
111
- lines.push(` diagnostician: ${result.report.diagnostician.status} (${result.report.diagnostician.taskId})`);
112
- } else {
113
- lines.push(' diagnostician: none pending');
114
- }
115
- if (result.report.downstream !== null) {
116
- lines.push(` downstream: ran=${result.report.downstream.ran}${result.report.downstream.taskKind !== undefined ? ` kind=${result.report.downstream.taskKind}` : ''}${result.report.downstream.skipReason !== undefined ? ` skip=${result.report.downstream.skipReason}` : ''}`);
117
- }
118
- }
119
- if (result.nextAction !== undefined) lines.push(` next action: ${result.nextAction}`);
120
- console.log(lines.join('\n'));
121
- }
122
-
123
- export async function handleCodexWorker(options: CodexWorkerOptions): Promise<void> {
124
- // cli-1: continuous mode streams one report per cycle — that is NOT one
125
- // parseable JSON document. --json is only valid for the single-output
126
- // forms (--once / --status), enforced here (review P2).
127
- if (options.json === true && options.once !== true && options.status !== true) {
128
- const error = {
129
- generatedAt: new Date().toISOString(),
130
- host: 'codex',
131
- error: 'cli_contract',
132
- reason: 'json_without_once_or_status',
133
- nextAction: 'Use --json with --once (single cycle) or --status (no-op scan); continuous mode streams human-readable reports instead.',
134
- };
135
- console.log(JSON.stringify(error));
136
- process.exitCode = 1;
137
- return;
138
- }
139
-
140
- const workspace = resolveWorkspaceDir(options.workspace);
141
-
142
- if (options.status === true) {
143
- await printStatusReport(workspace, options.json === true);
144
- return;
145
- }
146
-
147
- if (options.once === true) {
148
- const result = await runCodexWorkspaceWorkerCycle({ workspaceDir: workspace, env: { CODEX_HOME: process.env.CODEX_HOME } });
149
- printCycleReport(result, options.json === true);
150
- if (result.mode === 'degraded') process.exitCode = 1;
151
- return;
152
- }
153
-
154
- const interval = typeof options.intervalMs === 'number' && options.intervalMs >= 1000 ? options.intervalMs : DEFAULT_INTERVAL_MS;
155
- let stopped = false;
156
- const onSignal = (): void => {
157
- stopped = true;
158
- };
159
- process.on('SIGINT', onSignal);
160
- process.on('SIGTERM', onSignal);
161
- try {
162
- while (!stopped) {
163
- // PRI-655: one escaping cycle exception must not kill the daemon —
164
- // runCodexWorkspaceWorkerCycle reports component failures as degraded
165
- // results, but anything it still throws would propagate out of this
166
- // handler (commander's sync parse does not catch action rejections) and
167
- // crash the process. Log with a next action and keep the loop alive.
168
- try {
169
- const result = await runCodexWorkspaceWorkerCycle({ workspaceDir: workspace, env: { CODEX_HOME: process.env.CODEX_HOME } });
170
- printCycleReport(result, false);
171
- } catch (err) {
172
- const message = err instanceof Error ? err.message : String(err);
173
- console.error(`[PD:CodexWorker] cycle failed: ${message}`);
174
- console.error(` continuing in ${interval}ms. Investigate: pd codex worker --status --workspace "${workspace}"`);
175
- }
176
- if (stopped) break;
177
- await new Promise((resolve) => setTimeout(resolve, interval));
178
- }
179
- } finally {
180
- process.off('SIGINT', onSignal);
181
- process.off('SIGTERM', onSignal);
182
- }
183
- }
184
-
185
- /** SPEC §15 worker mode, evaluated without executing anything. */
@@ -1,24 +0,0 @@
1
- /**
2
- * Command registration helpers (PRI-397 / C5 follow-up).
3
- *
4
- * These helpers are the single source of truth for which options a command
5
- * supports. They are called by BOTH:
6
- * - `packages/pd-cli/src/index.ts` (production registration)
7
- * - parser-level tests (mvp-smoke.test.ts)
8
- *
9
- * Tests reuse the same registration functions so a typo in production
10
- * (e.g., a flag mismatch between registration and handler) shows up at
11
- * `program.parseAsync(...)` time, not just at handler dispatch (EP-04).
12
- */
13
-
14
- import type { Command } from 'commander';
15
-
16
- /**
17
- * Add the standard `--workspace <path>` / `--json` flag pair to a command.
18
- * Returns the same command for chaining.
19
- */
20
- export function withWorkspaceAndJson(cmd: Command): Command {
21
- return cmd
22
- .option('-w, --workspace <path>', 'Workspace directory')
23
- .option('--json', 'Output raw JSON');
24
- }