@principles/pd-cli 1.152.11 → 1.152.13

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 (303) hide show
  1. package/dist/commands/candidate.d.ts +3 -3
  2. package/dist/commands/candidate.d.ts.map +1 -1
  3. package/dist/commands/candidate.js +9 -21
  4. package/dist/commands/candidate.js.map +1 -1
  5. package/dist/index.js +0 -0
  6. package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  7. package/dist/services/rulehost-pipeline-runner.js +4 -17
  8. package/dist/services/rulehost-pipeline-runner.js.map +1 -1
  9. package/package.json +13 -7
  10. package/dist/commands/__tests__/intent-flag-wiring.test.d.ts +0 -9
  11. package/dist/commands/__tests__/intent-flag-wiring.test.d.ts.map +0 -1
  12. package/dist/commands/__tests__/intent-flag-wiring.test.js +0 -237
  13. package/dist/commands/__tests__/intent-flag-wiring.test.js.map +0 -1
  14. package/dist/commands/__tests__/legacy-cleanup.test.d.ts +0 -18
  15. package/dist/commands/__tests__/legacy-cleanup.test.d.ts.map +0 -1
  16. package/dist/commands/__tests__/legacy-cleanup.test.js +0 -467
  17. package/dist/commands/__tests__/legacy-cleanup.test.js.map +0 -1
  18. package/dist/commands/__tests__/mvp-smoke.test.d.ts +0 -15
  19. package/dist/commands/__tests__/mvp-smoke.test.d.ts.map +0 -1
  20. package/dist/commands/__tests__/mvp-smoke.test.js +0 -262
  21. package/dist/commands/__tests__/mvp-smoke.test.js.map +0 -1
  22. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +0 -2
  23. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +0 -1
  24. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +0 -60
  25. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +0 -1
  26. package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts +0 -21
  27. package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts.map +0 -1
  28. package/dist/commands/__tests__/rulecode-flag-wiring.test.js +0 -186
  29. package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +0 -1
  30. package/dist/commands/__tests__/rulecode-handler.test.d.ts +0 -16
  31. package/dist/commands/__tests__/rulecode-handler.test.d.ts.map +0 -1
  32. package/dist/commands/__tests__/rulecode-handler.test.js +0 -285
  33. package/dist/commands/__tests__/rulecode-handler.test.js.map +0 -1
  34. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts +0 -24
  35. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts.map +0 -1
  36. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +0 -232
  37. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +0 -1
  38. package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts +0 -15
  39. package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts.map +0 -1
  40. package/dist/commands/__tests__/runtime-activation-nextaction.test.js +0 -73
  41. package/dist/commands/__tests__/runtime-activation-nextaction.test.js.map +0 -1
  42. package/dist/commands/__tests__/runtime-probe-config.test.d.ts +0 -20
  43. package/dist/commands/__tests__/runtime-probe-config.test.d.ts.map +0 -1
  44. package/dist/commands/__tests__/runtime-probe-config.test.js +0 -389
  45. package/dist/commands/__tests__/runtime-probe-config.test.js.map +0 -1
  46. package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts +0 -9
  47. package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts.map +0 -1
  48. package/dist/commands/__tests__/telemetry-flag-wiring.test.js +0 -76
  49. package/dist/commands/__tests__/telemetry-flag-wiring.test.js.map +0 -1
  50. package/dist/commands/__tests__/version.test.d.ts +0 -2
  51. package/dist/commands/__tests__/version.test.d.ts.map +0 -1
  52. package/dist/commands/__tests__/version.test.js +0 -238
  53. package/dist/commands/__tests__/version.test.js.map +0 -1
  54. package/dist/commands/runtime-uat.guard.test.d.ts +0 -2
  55. package/dist/commands/runtime-uat.guard.test.d.ts.map +0 -1
  56. package/dist/commands/runtime-uat.guard.test.js +0 -153
  57. package/dist/commands/runtime-uat.guard.test.js.map +0 -1
  58. package/dist/services/__tests__/cli-output.test.d.ts +0 -18
  59. package/dist/services/__tests__/cli-output.test.d.ts.map +0 -1
  60. package/dist/services/__tests__/cli-output.test.js +0 -103
  61. package/dist/services/__tests__/cli-output.test.js.map +0 -1
  62. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +0 -2
  63. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +0 -1
  64. package/dist/services/__tests__/evaluator-runner-deps.test.js +0 -343
  65. package/dist/services/__tests__/evaluator-runner-deps.test.js.map +0 -1
  66. package/dist/services/__tests__/rulehost-readiness.test.d.ts +0 -2
  67. package/dist/services/__tests__/rulehost-readiness.test.d.ts.map +0 -1
  68. package/dist/services/__tests__/rulehost-readiness.test.js +0 -311
  69. package/dist/services/__tests__/rulehost-readiness.test.js.map +0 -1
  70. package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts +0 -18
  71. package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts.map +0 -1
  72. package/dist/services/__tests__/runtime-adapter-resolver.test.js +0 -673
  73. package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +0 -1
  74. package/dist/utils/production-workspace-guard.test.d.ts +0 -2
  75. package/dist/utils/production-workspace-guard.test.d.ts.map +0 -1
  76. package/dist/utils/production-workspace-guard.test.js +0 -137
  77. package/dist/utils/production-workspace-guard.test.js.map +0 -1
  78. package/scripts/llm-dogfood.ts +0 -456
  79. package/scripts/migrate-illegal-expected-decision.ts +0 -252
  80. package/src/commands/__tests__/intent-flag-wiring.test.ts +0 -282
  81. package/src/commands/__tests__/legacy-cleanup.test.ts +0 -602
  82. package/src/commands/__tests__/mvp-smoke.test.ts +0 -300
  83. package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +0 -73
  84. package/src/commands/__tests__/rulecode-flag-wiring.test.ts +0 -237
  85. package/src/commands/__tests__/rulecode-handler.test.ts +0 -369
  86. package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +0 -292
  87. package/src/commands/__tests__/runtime-activation-nextaction.test.ts +0 -81
  88. package/src/commands/__tests__/runtime-probe-config.test.ts +0 -432
  89. package/src/commands/__tests__/telemetry-flag-wiring.test.ts +0 -84
  90. package/src/commands/__tests__/version.test.ts +0 -251
  91. package/src/commands/admission-gate.ts +0 -30
  92. package/src/commands/artifact.ts +0 -82
  93. package/src/commands/build-trajectory-evidence.ts +0 -298
  94. package/src/commands/candidate.ts +0 -1260
  95. package/src/commands/codex-ingest-catchup.ts +0 -114
  96. package/src/commands/codex-ingest-quarantine.ts +0 -127
  97. package/src/commands/codex-reconcile.ts +0 -84
  98. package/src/commands/codex-setup.ts +0 -476
  99. package/src/commands/codex-worker.ts +0 -185
  100. package/src/commands/command-helpers.ts +0 -24
  101. package/src/commands/config-doctor.ts +0 -193
  102. package/src/commands/console.ts +0 -797
  103. package/src/commands/context.ts +0 -72
  104. package/src/commands/demo-story-a.ts +0 -241
  105. package/src/commands/diagnose.ts +0 -756
  106. package/src/commands/errors-list.ts +0 -441
  107. package/src/commands/evolution-tasks-list.ts +0 -48
  108. package/src/commands/evolution-tasks-show.ts +0 -62
  109. package/src/commands/flow.ts +0 -60
  110. package/src/commands/health-codex.ts +0 -576
  111. package/src/commands/health.ts +0 -315
  112. package/src/commands/history.ts +0 -63
  113. package/src/commands/intent.ts +0 -457
  114. package/src/commands/legacy-cleanup.ts +0 -531
  115. package/src/commands/legacy-import.ts +0 -104
  116. package/src/commands/mvp-smoke.ts +0 -160
  117. package/src/commands/pain-evidence.ts +0 -226
  118. package/src/commands/pain-list.ts +0 -239
  119. package/src/commands/pain-record.ts +0 -612
  120. package/src/commands/pain-retry.ts +0 -916
  121. package/src/commands/principles-stats.ts +0 -700
  122. package/src/commands/proven-channel-baseline.ts +0 -115
  123. package/src/commands/quality-scorecard.ts +0 -272
  124. package/src/commands/remediation-output.ts +0 -66
  125. package/src/commands/rulecode.ts +0 -465
  126. package/src/commands/run.ts +0 -89
  127. package/src/commands/runtime-activation.ts +0 -1629
  128. package/src/commands/runtime-artifact-repair.ts +0 -524
  129. package/src/commands/runtime-canary.ts +0 -395
  130. package/src/commands/runtime-compatibility-scan.ts +0 -105
  131. package/src/commands/runtime-diagnostics-export.ts +0 -229
  132. package/src/commands/runtime-features.ts +0 -153
  133. package/src/commands/runtime-gfi-snapshot.ts +0 -135
  134. package/src/commands/runtime-health-snapshot.ts +0 -108
  135. package/src/commands/runtime-init.ts +0 -429
  136. package/src/commands/runtime-internalization-enqueue-successors.ts +0 -530
  137. package/src/commands/runtime-internalization-integrity-repair.ts +0 -79
  138. package/src/commands/runtime-internalization-integrity.ts +0 -107
  139. package/src/commands/runtime-internalization-queue.ts +0 -135
  140. package/src/commands/runtime-internalization-retry.ts +0 -191
  141. package/src/commands/runtime-internalization-run-once.ts +0 -791
  142. package/src/commands/runtime-internalization-run-rulehost.ts +0 -749
  143. package/src/commands/runtime-internalization-wake-once.ts +0 -87
  144. package/src/commands/runtime-pain-flood-simulation.ts +0 -121
  145. package/src/commands/runtime-pruning.ts +0 -438
  146. package/src/commands/runtime-recovery-failed-tasks.ts +0 -258
  147. package/src/commands/runtime-recovery.ts +0 -109
  148. package/src/commands/runtime-synthetic-baseline.ts +0 -70
  149. package/src/commands/runtime-uat.guard.test.ts +0 -175
  150. package/src/commands/runtime-uat.ts +0 -380
  151. package/src/commands/runtime.ts +0 -473
  152. package/src/commands/samples-list.ts +0 -39
  153. package/src/commands/samples-review.ts +0 -36
  154. package/src/commands/task.ts +0 -271
  155. package/src/commands/telemetry.ts +0 -349
  156. package/src/commands/trace.ts +0 -174
  157. package/src/commands/trajectory-db-unavailable.ts +0 -19
  158. package/src/commands/trajectory.ts +0 -64
  159. package/src/commands/version.ts +0 -49
  160. package/src/config-reader.ts +0 -122
  161. package/src/index.ts +0 -1276
  162. package/src/legacy/legacy-import.ts +0 -179
  163. package/src/legacy/session-history-import.ts +0 -203
  164. package/src/resolve-workspace.ts +0 -106
  165. package/src/services/__tests__/cli-output.test.ts +0 -130
  166. package/src/services/__tests__/evaluator-runner-deps.test.ts +0 -382
  167. package/src/services/__tests__/rulehost-readiness.test.ts +0 -363
  168. package/src/services/__tests__/runtime-adapter-resolver.test.ts +0 -793
  169. package/src/services/cli-output.ts +0 -95
  170. package/src/services/config-doctor.ts +0 -697
  171. package/src/services/console-launcher.ts +0 -537
  172. package/src/services/demo-rule-compiler.ts +0 -40
  173. package/src/services/demo-story-a-runner.ts +0 -471
  174. package/src/services/mainline-snapshot-assembler.ts +0 -579
  175. package/src/services/pain-flood-simulation-runner.ts +0 -462
  176. package/src/services/pd-config-loader.ts +0 -433
  177. package/src/services/proven-channel-baseline-runner.ts +0 -157
  178. package/src/services/quality-scorecard/data-extractor.ts +0 -150
  179. package/src/services/quality-scorecard/local-evaluator.ts +0 -142
  180. package/src/services/quality-scorecard/strong-model-gate.ts +0 -202
  181. package/src/services/resolve-runtime-from-pd-config.ts +0 -230
  182. package/src/services/rulehost-pipeline-runner.ts +0 -1216
  183. package/src/services/rulehost-readiness.ts +0 -326
  184. package/src/services/runtime-adapter-resolver.ts +0 -364
  185. package/src/services/synthetic-baseline-runner.ts +0 -400
  186. package/src/services/version-report.ts +0 -253
  187. package/src/services/workspace-tool-semantics.ts +0 -26
  188. package/src/utils/path-security.ts +0 -96
  189. package/src/utils/production-workspace-guard.test.ts +0 -157
  190. package/src/utils/production-workspace-guard.ts +0 -210
  191. package/tests/bdd/cli-contract.steps.ts +0 -346
  192. package/tests/bdd/principles-stats.steps.ts +0 -211
  193. package/tests/bdd/support/gherkin-loader.ts +0 -147
  194. package/tests/bdd/support/repo-root.ts +0 -62
  195. package/tests/bdd/support/vitest-bdd.ts +0 -179
  196. package/tests/commands/admission-gate-coverage.test.ts +0 -79
  197. package/tests/commands/build-trajectory-evidence.test.ts +0 -508
  198. package/tests/commands/candidate-audit-repair.test.ts +0 -421
  199. package/tests/commands/candidate-intake.test.ts +0 -732
  200. package/tests/commands/candidate-internalization-backfill.test.ts +0 -548
  201. package/tests/commands/candidate-internalize-lineage.test.ts +0 -565
  202. package/tests/commands/candidate-internalize.test.ts +0 -299
  203. package/tests/commands/candidate-route.test.ts +0 -329
  204. package/tests/commands/candidate-show.test.ts +0 -95
  205. package/tests/commands/cli-command-tree.test.ts +0 -144
  206. package/tests/commands/cli-help-snapshot.test.ts +0 -155
  207. package/tests/commands/cli-skill-contract.test.ts +0 -121
  208. package/tests/commands/codex-consent-reversibility.steps.test.ts +0 -299
  209. package/tests/commands/codex-ingest-quarantine.test.ts +0 -136
  210. package/tests/commands/codex-setup.test.ts +0 -297
  211. package/tests/commands/codex-worker-daemon.test.ts +0 -95
  212. package/tests/commands/codex-worker-registration.test.ts +0 -207
  213. package/tests/commands/config-doctor.test.ts +0 -336
  214. package/tests/commands/console-launcher-edge-cases.test.ts +0 -388
  215. package/tests/commands/console-open.test.ts +0 -1381
  216. package/tests/commands/context.test.ts +0 -114
  217. package/tests/commands/demo-story-a.test.ts +0 -318
  218. package/tests/commands/diagnose.test.ts +0 -1803
  219. package/tests/commands/errors-list.test.ts +0 -679
  220. package/tests/commands/evolution-tasks-list.test.ts +0 -41
  221. package/tests/commands/evolution-tasks-show.test.ts +0 -46
  222. package/tests/commands/health-codex.test.ts +0 -349
  223. package/tests/commands/health-host-wiring.test.ts +0 -76
  224. package/tests/commands/health-reliability.test.ts +0 -99
  225. package/tests/commands/health.test.ts +0 -413
  226. package/tests/commands/intent.test.ts +0 -321
  227. package/tests/commands/legacy-cleanup.test.ts +0 -148
  228. package/tests/commands/pain-evidence.test.ts +0 -518
  229. package/tests/commands/pain-list.test.ts +0 -219
  230. package/tests/commands/pain-record-async.test.ts +0 -335
  231. package/tests/commands/pain-record-session-parser.test.ts +0 -228
  232. package/tests/commands/pain-record.test.ts +0 -851
  233. package/tests/commands/pain-retry.test.ts +0 -1121
  234. package/tests/commands/plugin-config-resolution-cutover.test.ts +0 -229
  235. package/tests/commands/pri-393-runtime-config-unification.test.ts +0 -443
  236. package/tests/commands/principles-stats-wiring.test.ts +0 -135
  237. package/tests/commands/principles-stats.test.ts +0 -274
  238. package/tests/commands/product-path-regression.test.ts +0 -86
  239. package/tests/commands/proven-channel-baseline.test.ts +0 -441
  240. package/tests/commands/rulecode.test.ts +0 -177
  241. package/tests/commands/run-rulehost-handler.test.ts +0 -908
  242. package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +0 -227
  243. package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +0 -167
  244. package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +0 -262
  245. package/tests/commands/runtime-activation-governance-audit.test.ts +0 -95
  246. package/tests/commands/runtime-activation-list-flag-wiring.test.ts +0 -193
  247. package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +0 -248
  248. package/tests/commands/runtime-activation-shadow-telemetry.test.ts +0 -130
  249. package/tests/commands/runtime-activation.test.ts +0 -2052
  250. package/tests/commands/runtime-artifact-repair-registration.test.ts +0 -74
  251. package/tests/commands/runtime-artifact-repair.test.ts +0 -404
  252. package/tests/commands/runtime-canary.test.ts +0 -430
  253. package/tests/commands/runtime-compatibility-scan.test.ts +0 -146
  254. package/tests/commands/runtime-diagnostics-export.test.ts +0 -175
  255. package/tests/commands/runtime-features.test.ts +0 -249
  256. package/tests/commands/runtime-health-snapshot.test.ts +0 -358
  257. package/tests/commands/runtime-init-empty-workspace.test.ts +0 -252
  258. package/tests/commands/runtime-init.test.ts +0 -420
  259. package/tests/commands/runtime-internalization-enqueue-successors.test.ts +0 -782
  260. package/tests/commands/runtime-internalization-integrity-repair.test.ts +0 -239
  261. package/tests/commands/runtime-internalization-integrity.test.ts +0 -140
  262. package/tests/commands/runtime-internalization-queue.test.ts +0 -380
  263. package/tests/commands/runtime-internalization-retry-owner-authority.test.ts +0 -431
  264. package/tests/commands/runtime-internalization-run-once-evaluator-parity.test.ts +0 -269
  265. package/tests/commands/runtime-internalization-run-once-language.test.ts +0 -303
  266. package/tests/commands/runtime-internalization-run-once-real-config.test.ts +0 -237
  267. package/tests/commands/runtime-internalization-run-once-rollout-parity.test.ts +0 -706
  268. package/tests/commands/runtime-internalization-run-once.test.ts +0 -1791
  269. package/tests/commands/runtime-internalization-wake-once.test.ts +0 -171
  270. package/tests/commands/runtime-internalization.test.ts +0 -53
  271. package/tests/commands/runtime-pain-flood-simulation.test.ts +0 -418
  272. package/tests/commands/runtime-pruning.test.ts +0 -694
  273. package/tests/commands/runtime-recovery-failed-tasks.test.ts +0 -328
  274. package/tests/commands/runtime-recovery.test.ts +0 -120
  275. package/tests/commands/runtime-synthetic-baseline.test.ts +0 -249
  276. package/tests/commands/runtime-uat.test.ts +0 -397
  277. package/tests/commands/runtime.test.ts +0 -392
  278. package/tests/commands/samples-list.test.ts +0 -43
  279. package/tests/commands/samples-review.test.ts +0 -41
  280. package/tests/commands/task.test.ts +0 -153
  281. package/tests/commands/telemetry.test.ts +0 -190
  282. package/tests/commands/trace.test.ts +0 -316
  283. package/tests/config-reader.test.ts +0 -143
  284. package/tests/e2e/candidate-intake-e2e.test.ts +0 -338
  285. package/tests/e2e/cli-full-flow.test.ts +0 -233
  286. package/tests/e2e/cross-package-acceptance.test.ts +0 -635
  287. package/tests/helpers/pd-cli-path.ts +0 -5
  288. package/tests/pd-cli-smoke.test.ts +0 -13
  289. package/tests/resolve-workspace.test.ts +0 -196
  290. package/tests/services/config-doctor-signal-health.test.ts +0 -109
  291. package/tests/services/console-launcher.test.ts +0 -114
  292. package/tests/services/demo-rule-compiler.test.ts +0 -242
  293. package/tests/services/mainline-snapshot-assembler.test.ts +0 -572
  294. package/tests/services/pd-config-loader.test.ts +0 -505
  295. package/tests/services/proven-channel-baseline-runner.test.ts +0 -30
  296. package/tests/services/quality-scorecard/strong-model-gate.test.ts +0 -133
  297. package/tests/services/resolve-runtime-from-pd-config.test.ts +0 -485
  298. package/tests/services/rulehost-pipeline-e2e.test.ts +0 -538
  299. package/tests/services/rulehost-pipeline-runner.test.ts +0 -1175
  300. package/tests/services/workspace-tool-semantics.test.ts +0 -76
  301. package/tests/utils/path-security.test.ts +0 -180
  302. package/tsconfig.json +0 -26
  303. package/vitest.config.ts +0 -9
@@ -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
- }