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