@principles/pd-cli 1.133.0 → 1.134.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/dist/commands/__tests__/intent-flag-wiring.test.js +74 -3
  2. package/dist/commands/__tests__/intent-flag-wiring.test.js.map +1 -1
  3. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +2 -0
  4. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +1 -0
  5. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +60 -0
  6. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +1 -0
  7. package/dist/commands/__tests__/rulecode-flag-wiring.test.js +11 -4
  8. package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +1 -1
  9. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +9 -0
  10. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +1 -1
  11. package/dist/commands/admission-gate.d.ts +24 -0
  12. package/dist/commands/admission-gate.d.ts.map +1 -0
  13. package/dist/commands/admission-gate.js +29 -0
  14. package/dist/commands/admission-gate.js.map +1 -0
  15. package/dist/commands/candidate.d.ts.map +1 -1
  16. package/dist/commands/candidate.js +72 -6
  17. package/dist/commands/candidate.js.map +1 -1
  18. package/dist/commands/console.d.ts.map +1 -1
  19. package/dist/commands/console.js +40 -0
  20. package/dist/commands/console.js.map +1 -1
  21. package/dist/commands/demo-story-a.d.ts.map +1 -1
  22. package/dist/commands/demo-story-a.js +4 -2
  23. package/dist/commands/demo-story-a.js.map +1 -1
  24. package/dist/commands/diagnose.d.ts.map +1 -1
  25. package/dist/commands/diagnose.js +155 -8
  26. package/dist/commands/diagnose.js.map +1 -1
  27. package/dist/commands/errors-list.d.ts +25 -0
  28. package/dist/commands/errors-list.d.ts.map +1 -0
  29. package/dist/commands/errors-list.js +368 -0
  30. package/dist/commands/errors-list.js.map +1 -0
  31. package/dist/commands/health-codex.d.ts +7 -0
  32. package/dist/commands/health-codex.d.ts.map +1 -0
  33. package/dist/commands/health-codex.js +209 -0
  34. package/dist/commands/health-codex.js.map +1 -0
  35. package/dist/commands/health.d.ts +24 -0
  36. package/dist/commands/health.d.ts.map +1 -1
  37. package/dist/commands/health.js +48 -0
  38. package/dist/commands/health.js.map +1 -1
  39. package/dist/commands/intent.d.ts +21 -4
  40. package/dist/commands/intent.d.ts.map +1 -1
  41. package/dist/commands/intent.js +88 -27
  42. package/dist/commands/intent.js.map +1 -1
  43. package/dist/commands/pain-evidence.d.ts +6 -0
  44. package/dist/commands/pain-evidence.d.ts.map +1 -1
  45. package/dist/commands/pain-evidence.js +6 -0
  46. package/dist/commands/pain-evidence.js.map +1 -1
  47. package/dist/commands/pain-record.d.ts.map +1 -1
  48. package/dist/commands/pain-record.js +21 -0
  49. package/dist/commands/pain-record.js.map +1 -1
  50. package/dist/commands/pain-retry.d.ts +4 -0
  51. package/dist/commands/pain-retry.d.ts.map +1 -1
  52. package/dist/commands/pain-retry.js +327 -32
  53. package/dist/commands/pain-retry.js.map +1 -1
  54. package/dist/commands/proven-channel-baseline.d.ts.map +1 -1
  55. package/dist/commands/proven-channel-baseline.js +4 -2
  56. package/dist/commands/proven-channel-baseline.js.map +1 -1
  57. package/dist/commands/runtime-activation.d.ts +30 -0
  58. package/dist/commands/runtime-activation.d.ts.map +1 -1
  59. package/dist/commands/runtime-activation.js +567 -11
  60. package/dist/commands/runtime-activation.js.map +1 -1
  61. package/dist/commands/runtime-canary.d.ts.map +1 -1
  62. package/dist/commands/runtime-canary.js +32 -7
  63. package/dist/commands/runtime-canary.js.map +1 -1
  64. package/dist/commands/runtime-health-snapshot.d.ts.map +1 -1
  65. package/dist/commands/runtime-health-snapshot.js +4 -2
  66. package/dist/commands/runtime-health-snapshot.js.map +1 -1
  67. package/dist/commands/runtime-init.d.ts +43 -0
  68. package/dist/commands/runtime-init.d.ts.map +1 -0
  69. package/dist/commands/runtime-init.js +264 -0
  70. package/dist/commands/runtime-init.js.map +1 -0
  71. package/dist/commands/runtime-internalization-context-trace.d.ts +25 -0
  72. package/dist/commands/runtime-internalization-context-trace.d.ts.map +1 -0
  73. package/dist/commands/runtime-internalization-context-trace.js +374 -0
  74. package/dist/commands/runtime-internalization-context-trace.js.map +1 -0
  75. package/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
  76. package/dist/commands/runtime-internalization-enqueue-successors.js +3 -1
  77. package/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
  78. package/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
  79. package/dist/commands/runtime-internalization-integrity.js +4 -2
  80. package/dist/commands/runtime-internalization-integrity.js.map +1 -1
  81. package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
  82. package/dist/commands/runtime-internalization-run-once.js +20 -5
  83. package/dist/commands/runtime-internalization-run-once.js.map +1 -1
  84. package/dist/commands/runtime-internalization-run-rulehost.d.ts +1 -0
  85. package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
  86. package/dist/commands/runtime-internalization-run-rulehost.js +149 -6
  87. package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
  88. package/dist/commands/runtime-uat.d.ts.map +1 -1
  89. package/dist/commands/runtime-uat.guard.test.js +13 -15
  90. package/dist/commands/runtime-uat.guard.test.js.map +1 -1
  91. package/dist/commands/runtime-uat.js +4 -3
  92. package/dist/commands/runtime-uat.js.map +1 -1
  93. package/dist/config-reader.js +1 -1
  94. package/dist/config-reader.js.map +1 -1
  95. package/dist/index.js +54 -78
  96. package/dist/index.js.map +1 -1
  97. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +2 -0
  98. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +1 -0
  99. package/dist/services/__tests__/evaluator-runner-deps.test.js +304 -0
  100. package/dist/services/__tests__/evaluator-runner-deps.test.js.map +1 -0
  101. package/dist/services/__tests__/rulehost-readiness.test.js.map +1 -1
  102. package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -1
  103. package/dist/services/config-doctor.d.ts.map +1 -1
  104. package/dist/services/config-doctor.js +9 -2
  105. package/dist/services/config-doctor.js.map +1 -1
  106. package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
  107. package/dist/services/pain-flood-simulation-runner.js +9 -0
  108. package/dist/services/pain-flood-simulation-runner.js.map +1 -1
  109. package/dist/services/pd-config-loader.js +2 -2
  110. package/dist/services/pd-config-loader.js.map +1 -1
  111. package/dist/services/proven-channel-baseline-runner.d.ts.map +1 -1
  112. package/dist/services/proven-channel-baseline-runner.js +10 -2
  113. package/dist/services/proven-channel-baseline-runner.js.map +1 -1
  114. package/dist/services/quality-scorecard/data-extractor.js.map +1 -1
  115. package/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
  116. package/dist/services/rulehost-pipeline-runner.d.ts +77 -1
  117. package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  118. package/dist/services/rulehost-pipeline-runner.js +111 -7
  119. package/dist/services/rulehost-pipeline-runner.js.map +1 -1
  120. package/dist/services/rulehost-readiness.js.map +1 -1
  121. package/dist/services/runtime-adapter-resolver.d.ts +1 -0
  122. package/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
  123. package/dist/services/runtime-adapter-resolver.js +3 -0
  124. package/dist/services/runtime-adapter-resolver.js.map +1 -1
  125. package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
  126. package/dist/services/synthetic-baseline-runner.js +11 -0
  127. package/dist/services/synthetic-baseline-runner.js.map +1 -1
  128. package/dist/utils/production-workspace-guard.d.ts +4 -4
  129. package/dist/utils/production-workspace-guard.d.ts.map +1 -1
  130. package/dist/utils/production-workspace-guard.js +19 -16
  131. package/dist/utils/production-workspace-guard.js.map +1 -1
  132. package/dist/utils/production-workspace-guard.test.js +54 -19
  133. package/dist/utils/production-workspace-guard.test.js.map +1 -1
  134. package/package.json +8 -5
  135. package/src/commands/__tests__/intent-flag-wiring.test.ts +88 -3
  136. package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +73 -0
  137. package/src/commands/__tests__/rulecode-flag-wiring.test.ts +31 -24
  138. package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +12 -0
  139. package/src/commands/admission-gate.ts +30 -0
  140. package/src/commands/candidate.ts +74 -6
  141. package/src/commands/console.ts +40 -0
  142. package/src/commands/demo-story-a.ts +4 -2
  143. package/src/commands/diagnose.ts +158 -10
  144. package/src/commands/errors-list.ts +440 -0
  145. package/src/commands/health-codex.ts +249 -0
  146. package/src/commands/health.ts +60 -0
  147. package/src/commands/intent.ts +98 -29
  148. package/src/commands/pain-evidence.ts +6 -0
  149. package/src/commands/pain-record.ts +21 -0
  150. package/src/commands/pain-retry.ts +353 -35
  151. package/src/commands/proven-channel-baseline.ts +4 -2
  152. package/src/commands/runtime-activation.ts +657 -12
  153. package/src/commands/runtime-canary.ts +31 -5
  154. package/src/commands/runtime-health-snapshot.ts +4 -2
  155. package/src/commands/runtime-init.ts +305 -0
  156. package/src/commands/runtime-internalization-context-trace.ts +445 -0
  157. package/src/commands/runtime-internalization-enqueue-successors.ts +3 -1
  158. package/src/commands/runtime-internalization-integrity.ts +4 -2
  159. package/src/commands/runtime-internalization-run-once.ts +20 -5
  160. package/src/commands/runtime-internalization-run-rulehost.ts +159 -6
  161. package/src/commands/runtime-uat.guard.test.ts +14 -16
  162. package/src/commands/runtime-uat.ts +4 -3
  163. package/src/config-reader.ts +1 -1
  164. package/src/index.ts +69 -78
  165. package/src/services/__tests__/evaluator-runner-deps.test.ts +341 -0
  166. package/src/services/config-doctor.ts +9 -2
  167. package/src/services/pain-flood-simulation-runner.ts +9 -0
  168. package/src/services/pd-config-loader.ts +2 -2
  169. package/src/services/proven-channel-baseline-runner.ts +10 -3
  170. package/src/services/rulehost-pipeline-runner.ts +157 -6
  171. package/src/services/runtime-adapter-resolver.ts +4 -0
  172. package/src/services/synthetic-baseline-runner.ts +11 -0
  173. package/src/utils/production-workspace-guard.test.ts +62 -20
  174. package/src/utils/production-workspace-guard.ts +19 -16
  175. package/tests/bdd/cli-contract.steps.ts +343 -0
  176. package/tests/bdd/context-trace.steps.ts +229 -0
  177. package/tests/bdd/support/gherkin-loader.ts +147 -0
  178. package/tests/bdd/support/repo-root.ts +62 -0
  179. package/tests/bdd/support/vitest-bdd.ts +179 -0
  180. package/tests/commands/admission-gate-coverage.test.ts +79 -0
  181. package/tests/commands/candidate-audit-repair.test.ts +77 -0
  182. package/tests/commands/candidate-intake.test.ts +155 -10
  183. package/tests/commands/candidate-internalization-backfill.test.ts +30 -0
  184. package/tests/commands/cli-help-snapshot.test.ts +15 -1
  185. package/tests/commands/console-open.test.ts +19 -0
  186. package/tests/commands/diagnose.test.ts +619 -14
  187. package/tests/commands/errors-list.test.ts +678 -0
  188. package/tests/commands/health-codex.test.ts +243 -0
  189. package/tests/commands/health-host-wiring.test.ts +76 -0
  190. package/tests/commands/intent.test.ts +4 -4
  191. package/tests/commands/pain-record.test.ts +80 -0
  192. package/tests/commands/pain-retry.test.ts +94 -3
  193. package/tests/commands/run-rulehost-handler.test.ts +133 -0
  194. package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +227 -0
  195. package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +167 -0
  196. package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +262 -0
  197. package/tests/commands/runtime-activation-list-flag-wiring.test.ts +193 -0
  198. package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +222 -0
  199. package/tests/commands/runtime-activation.test.ts +1090 -5
  200. package/tests/commands/runtime-canary.test.ts +50 -0
  201. package/tests/commands/runtime-features.test.ts +2 -0
  202. package/tests/commands/runtime-init-empty-workspace.test.ts +234 -0
  203. package/tests/commands/runtime-init.test.ts +346 -0
  204. package/tests/commands/runtime-internalization-context-trace.test.ts +110 -0
  205. package/tests/commands/runtime-internalization.test.ts +2 -2
  206. package/tests/config-reader.test.ts +6 -5
  207. package/tests/e2e/cross-package-acceptance.test.ts +55 -13
  208. package/tests/services/mainline-snapshot-assembler.test.ts +6 -3
  209. package/tests/services/resolve-runtime-from-pd-config.test.ts +2 -0
  210. package/tests/services/rulehost-pipeline-runner.test.ts +1 -1
  211. package/vitest.config.ts +1 -1
@@ -24,6 +24,7 @@
24
24
  * - --dry-run and --confirm are mutually exclusive
25
25
  */
26
26
  import * as path from 'path';
27
+ import * as fs from 'node:fs';
27
28
  import type { Command } from 'commander';
28
29
  import { runRuleHostPipeline } from '../services/rulehost-pipeline-runner.js';
29
30
  import type { RuleHostPipelineResult, CodeRuleCapability, RuleHostAgentAdapters } from '../services/rulehost-pipeline-runner.js';
@@ -37,6 +38,7 @@ import {
37
38
  isFeatureEnabled,
38
39
  } from '@principles/core/runtime-v2';
39
40
  import type { EffectivePdConfig, InternalAgentName, PDRuntimeAdapter } from '@principles/core/runtime-v2';
41
+ import type { BehaviorExamplePack } from '@principles/core/runtime-v2';
40
42
  import { resolveRuntimeFromPdConfig } from '../services/resolve-runtime-from-pd-config.js';
41
43
  import { resolveRuleHostReadiness } from '../services/rulehost-readiness.js';
42
44
  import type { RuleHostReadinessResult } from '../services/rulehost-readiness.js';
@@ -47,6 +49,7 @@ export interface RunRuleHostOptions {
47
49
  channel?: string;
48
50
  maxRounds?: number;
49
51
  timeoutMs?: number;
52
+ behaviorExamples?: string;
50
53
  json?: boolean;
51
54
  /** Dry-run mode (default). Validates inputs + config, reports capability status, does NOT run the pipeline. */
52
55
  dryRun?: boolean;
@@ -68,6 +71,49 @@ interface ResolvedRunRuleHostRuntime {
68
71
  readonly agentRuntimeProfiles: Partial<Record<InternalAgentName, string>>;
69
72
  readonly capability: CodeRuleCapability;
70
73
  readonly capabilityStatus: string;
74
+ readonly contextV2Enabled: boolean;
75
+ }
76
+
77
+ interface OwnerBehaviorExamplesInput {
78
+ readonly ownerDesiredOutcome: string;
79
+ readonly sourceNegativeToolCallId: number;
80
+ readonly positiveToolCallIds: readonly number[];
81
+ }
82
+
83
+ type OwnerBehaviorExamplesResult =
84
+ | { readonly ok: true; readonly value: OwnerBehaviorExamplesInput }
85
+ | { readonly ok: false; readonly reason: string };
86
+
87
+ function isRecord(value: unknown): value is Record<string, unknown> {
88
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
89
+ }
90
+
91
+ function readOwnerBehaviorExamples(filePath: string): OwnerBehaviorExamplesResult {
92
+ let parsed: unknown;
93
+ try {
94
+ parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
95
+ } catch (error) {
96
+ return { ok: false, reason: `behavior_examples_unreadable: ${error instanceof Error ? error.message : String(error)}` };
97
+ }
98
+ if (!isRecord(parsed)) return { ok: false, reason: 'behavior_examples_invalid: root must be an object' };
99
+ if (typeof parsed.ownerDesiredOutcome !== 'string' || parsed.ownerDesiredOutcome.trim() === '') {
100
+ return { ok: false, reason: 'behavior_examples_invalid: ownerDesiredOutcome must be a non-empty string' };
101
+ }
102
+ if (!Number.isSafeInteger(parsed.sourceNegativeToolCallId) || Number(parsed.sourceNegativeToolCallId) <= 0) {
103
+ return { ok: false, reason: 'behavior_examples_invalid: sourceNegativeToolCallId must be a positive integer' };
104
+ }
105
+ if (!Array.isArray(parsed.positiveToolCallIds) || parsed.positiveToolCallIds.length === 0 || parsed.positiveToolCallIds.length > 3
106
+ || parsed.positiveToolCallIds.some((id) => !Number.isSafeInteger(id) || Number(id) <= 0)) {
107
+ return { ok: false, reason: 'behavior_examples_invalid: positiveToolCallIds must contain 1 to 3 positive integers' };
108
+ }
109
+ return {
110
+ ok: true,
111
+ value: {
112
+ ownerDesiredOutcome: parsed.ownerDesiredOutcome,
113
+ sourceNegativeToolCallId: Number(parsed.sourceNegativeToolCallId),
114
+ positiveToolCallIds: parsed.positiveToolCallIds.map((id) => Number(id)),
115
+ },
116
+ };
71
117
  }
72
118
 
73
119
  function resolvePiAiAgentAdapter(
@@ -120,6 +166,8 @@ function resolveRunRuleHostRuntime(
120
166
  }
121
167
 
122
168
  const { effective } = configLoadResult;
169
+ const featureFlags = computeFeatureFlagsFromConfig(effective);
170
+ const contextV2Enabled = isFeatureEnabled(featureFlags, 'rulecode_context_v2');
123
171
  const adapterOptions = { workspaceDir, timeoutMs };
124
172
  const dreamer = resolvePiAiAgentAdapter(effective, 'dreamer', adapterOptions);
125
173
  const philosopher = resolvePiAiAgentAdapter(effective, 'philosopher', adapterOptions);
@@ -135,15 +183,16 @@ function resolveRunRuleHostRuntime(
135
183
  agentRuntimeProfiles,
136
184
  capability: { enabled: false, disabledReason: readiness.reason },
137
185
  capabilityStatus: `code_rule_capability: OFF (${readiness.reason})`,
186
+ contextV2Enabled,
138
187
  };
139
188
  }
140
- const featureFlags = computeFeatureFlagsFromConfig(effective);
141
189
  if (!isFeatureEnabled(featureFlags, 'code_rule_capability')) {
142
190
  return {
143
191
  agentAdapters: { dreamer: dreamer.adapter, philosopher: philosopher.adapter, scribe: scribe.adapter, evaluator: scribe.adapter },
144
192
  agentRuntimeProfiles,
145
193
  capability: { enabled: false, disabledReason: 'code_rule_capability feature flag is disabled' },
146
194
  capabilityStatus: 'code_rule_capability: OFF (feature flag disabled)',
195
+ contextV2Enabled,
147
196
  };
148
197
  }
149
198
 
@@ -157,6 +206,7 @@ function resolveRunRuleHostRuntime(
157
206
  agentRuntimeProfiles,
158
207
  capability: { enabled: false, disabledReason: `artificer agent disabled: ${artificerBinding.reason}` },
159
208
  capabilityStatus: `code_rule_capability: OFF (artificer disabled — ${artificerBinding.reason})`,
209
+ contextV2Enabled,
160
210
  };
161
211
  }
162
212
  if (!evaluatorBinding.ok) {
@@ -165,6 +215,7 @@ function resolveRunRuleHostRuntime(
165
215
  agentRuntimeProfiles,
166
216
  capability: { enabled: false, disabledReason: `evaluator agent disabled: ${evaluatorBinding.reason}` },
167
217
  capabilityStatus: `code_rule_capability: OFF (evaluator disabled — ${evaluatorBinding.reason})`,
218
+ contextV2Enabled,
168
219
  };
169
220
  }
170
221
 
@@ -177,6 +228,7 @@ function resolveRunRuleHostRuntime(
177
228
  agentRuntimeProfiles,
178
229
  capability: { enabled: false, disabledReason: `artificer runtime profile is not pi-ai (got '${artificerProfile.type}')` },
179
230
  capabilityStatus: `code_rule_capability: OFF (artificer profile type='${artificerProfile.type}', expected pi-ai)`,
231
+ contextV2Enabled,
180
232
  };
181
233
  }
182
234
 
@@ -187,6 +239,7 @@ function resolveRunRuleHostRuntime(
187
239
  agentRuntimeProfiles,
188
240
  capability: { enabled: false, disabledReason: `artificer apiKeyEnv '${artificerProfile.apiKeyEnv}' is not set in environment` },
189
241
  capabilityStatus: `code_rule_capability: OFF (artificer apiKeyEnv '${artificerProfile.apiKeyEnv}' not set)`,
242
+ contextV2Enabled,
190
243
  };
191
244
  }
192
245
 
@@ -209,6 +262,7 @@ function resolveRunRuleHostRuntime(
209
262
  agentRuntimeProfiles,
210
263
  capability: { enabled: true, artificerAdapter },
211
264
  capabilityStatus: `code_rule_capability: ON (artificer profile='${artificerBinding.profileId}', evaluator profile='${evaluatorBinding.profileId}')`,
265
+ contextV2Enabled,
212
266
  };
213
267
  }
214
268
 
@@ -374,6 +428,91 @@ export async function handleRunRuleHost(opts: RunRuleHostOptions): Promise<void>
374
428
  return;
375
429
  }
376
430
 
431
+ let effectiveCapability = resolvedRuntime.capability;
432
+ let contextMode: 'v1' | 'v2' = 'v1';
433
+ let behaviorExamplePack: BehaviorExamplePack | undefined;
434
+ let behaviorExamplesReason: string | undefined;
435
+ const behaviorExamplesPath = opts.behaviorExamples
436
+ ? path.resolve(workspaceDir, opts.behaviorExamples)
437
+ : undefined;
438
+
439
+ if (!resolvedRuntime.contextV2Enabled && behaviorExamplesPath) {
440
+ const reason = 'behavior_examples_not_allowed: rulecode_context_v2 is disabled';
441
+ if (opts.json) {
442
+ process.stdout.write(JSON.stringify({ status: 'failed', reason, nextAction: 'enable rulecode_context_v2 or remove --behavior-examples' }) + '\n');
443
+ } else {
444
+ console.error(`Error: ${reason}. Enable rulecode_context_v2 or remove --behavior-examples.`);
445
+ }
446
+ process.exitCode = 1;
447
+ return;
448
+ }
449
+
450
+ if (resolvedRuntime.contextV2Enabled) {
451
+ contextMode = 'v2';
452
+ if (!behaviorExamplesPath) {
453
+ behaviorExamplesReason = 'behavior_examples_missing';
454
+ } else {
455
+ const examplesResult = readOwnerBehaviorExamples(behaviorExamplesPath);
456
+ if (!examplesResult.ok) {
457
+ // P1 fix (CodeRabbit PR2 Comment 1): fail fast on parse failure instead
458
+ // of silently degrading to text_principle_only (rc-9-no-silent-fallback).
459
+ // examplesResult.reason already carries a stable prefix
460
+ // (behavior_examples_unreadable: | behavior_examples_invalid:).
461
+ const { reason } = examplesResult;
462
+ if (opts.json) {
463
+ process.stdout.write(JSON.stringify({
464
+ status: 'failed',
465
+ reason,
466
+ nextAction: 'fix the --behavior-examples JSON payload and retry',
467
+ }) + '\n');
468
+ } else {
469
+ console.error(`Error: ${reason}. Fix the --behavior-examples JSON payload and retry.`);
470
+ }
471
+ process.exitCode = 1;
472
+ return;
473
+ }
474
+ if (opts.confirm) {
475
+ // Lazy import: avoids loading `principles-disciple` at module init time.
476
+ // `pd --version` (and the create-principles-disciple smoke test) loads
477
+ // this file statically via index.ts. If we used a static import, the
478
+ // module would fail with MODULE_NOT_FOUND in bundled environments where
479
+ // `principles-disciple` is not resolvable from pd-cli's node_modules.
480
+ // The import only executes when `--confirm` is actually used.
481
+ const { BehaviorExamplePackAssembler, RuleHostEvidenceRegistry } = await import('principles-disciple/rulehost-evidence');
482
+ try {
483
+ const assembler = new BehaviorExamplePackAssembler({ workspaceDir, stateDir: path.join(workspaceDir, '.state') });
484
+ behaviorExamplePack = assembler.assemble({
485
+ sourcePainId: opts.painId,
486
+ ownerDesiredOutcome: examplesResult.value.ownerDesiredOutcome,
487
+ sourceNegativeToolCallId: examplesResult.value.sourceNegativeToolCallId,
488
+ positiveToolCallIds: examplesResult.value.positiveToolCallIds,
489
+ projectDir: workspaceDir,
490
+ });
491
+ } catch (error) {
492
+ // P1 fix (CodeRabbit PR2 Comment 1): fail fast on assembly failure
493
+ // instead of silently degrading to text_principle_only.
494
+ const reason = `behavior_examples_unreliable: ${error instanceof Error ? error.message : String(error)}`;
495
+ if (opts.json) {
496
+ process.stdout.write(JSON.stringify({
497
+ status: 'failed',
498
+ reason,
499
+ nextAction: 'verify pain lineage and tool call IDs reference existing trajectory rows; rerun pd pain record if needed',
500
+ }) + '\n');
501
+ } else {
502
+ console.error(`Error: ${reason}. Verify pain lineage and tool call IDs reference existing trajectory rows; rerun pd pain record if needed.`);
503
+ }
504
+ process.exitCode = 1;
505
+ return;
506
+ } finally {
507
+ RuleHostEvidenceRegistry.dispose(workspaceDir);
508
+ }
509
+ }
510
+ }
511
+ if (behaviorExamplesReason) {
512
+ effectiveCapability = { enabled: false, disabledReason: `${behaviorExamplesReason}; nextAction: provide reliable Owner-labelled tool call IDs with --behavior-examples` };
513
+ }
514
+ }
515
+
377
516
  // ── Dry-run mode: report what would happen, don't run the pipeline ──
378
517
  // Default is dry-run (CLI gate rule 4: mutating commands default to dry-run).
379
518
  const isDryRun = opts.dryRun || !opts.confirm;
@@ -386,9 +525,15 @@ export async function handleRunRuleHost(opts: RunRuleHostOptions): Promise<void>
386
525
  channel,
387
526
  readiness,
388
527
  readinessStatus: readiness.status,
389
- capabilityStatus: resolvedRuntime.capabilityStatus,
528
+ capabilityStatus: behaviorExamplesReason
529
+ ? `code_rule_capability: OFF (${behaviorExamplesReason})`
530
+ : resolvedRuntime.capabilityStatus,
390
531
  agentRuntimeProfiles: resolvedRuntime.agentRuntimeProfiles,
391
- codeRuleCapability: { enabled: resolvedRuntime.capability.enabled, disabledReason: resolvedRuntime.capability.disabledReason },
532
+ codeRuleCapability: { enabled: effectiveCapability.enabled, disabledReason: effectiveCapability.disabledReason },
533
+ contextMode,
534
+ behaviorExamples: behaviorExamplesPath
535
+ ? { path: behaviorExamplesPath, status: behaviorExamplesReason ? 'unreliable' : 'provided' }
536
+ : { status: resolvedRuntime.contextV2Enabled ? 'missing' : 'not_required' },
392
537
  nextAction: readiness.status === 'ready'
393
538
  ? 'pass --confirm to run the full pipeline'
394
539
  : readiness.status === 'text_principle_only'
@@ -396,7 +541,12 @@ export async function handleRunRuleHost(opts: RunRuleHostOptions): Promise<void>
396
541
  : 'fix the readiness issues above before running the pipeline',
397
542
  }) + '\n');
398
543
  } else {
399
- process.stdout.write(formatDryRunOutput({ opts, capabilityStatus: resolvedRuntime.capabilityStatus, workspaceDir, readiness }) + '\n');
544
+ // P2 fix (CodeRabbit PR2 Comment 2): text branch must use the same v2-aware
545
+ // capabilityStatus source as the JSON branch (behaviorExamplesReason-aware).
546
+ const effectiveCapabilityStatus = behaviorExamplesReason
547
+ ? `code_rule_capability: OFF (${behaviorExamplesReason})`
548
+ : resolvedRuntime.capabilityStatus;
549
+ process.stdout.write(formatDryRunOutput({ opts, capabilityStatus: effectiveCapabilityStatus, workspaceDir, readiness }) + '\n');
400
550
  }
401
551
  return;
402
552
  }
@@ -409,7 +559,9 @@ export async function handleRunRuleHost(opts: RunRuleHostOptions): Promise<void>
409
559
  painId: opts.painId,
410
560
  runtimeAdapter: resolvedRuntime.agentAdapters.dreamer,
411
561
  agentAdapters: resolvedRuntime.agentAdapters,
412
- codeRuleCapability: resolvedRuntime.capability,
562
+ codeRuleCapability: effectiveCapability,
563
+ contextMode,
564
+ behaviorExamplePack,
413
565
  channel: channel as RuleHostChannel,
414
566
  maxRounds: opts.maxRounds,
415
567
  timeoutMs: opts.timeoutMs,
@@ -465,10 +617,11 @@ export function registerRunRuleHostCommand(internalizationCmd: Command): Command
465
617
  .option('--channel <channel>', 'Activation channel: code_tool_hook, prompt, defer_archive (default: code_tool_hook)', 'code_tool_hook')
466
618
  .option('--max-rounds <n>', 'Max adversarial rounds (PRD cap = 2)', parseInt)
467
619
  .option('--timeout-ms <ms>', 'Per-LLM-call timeout in milliseconds (default: 300000)', parseInt)
620
+ .option('--behavior-examples <json>', 'Owner-labelled negative/positive tool call IDs for RuleContext v2')
468
621
  .option('--dry-run', 'Validate inputs + config, report capability status, do NOT run the pipeline (default)')
469
622
  .option('--confirm', 'Actually run the pipeline (mutually exclusive with --dry-run)')
470
623
  .option('--json', 'Output raw JSON')
471
624
  .action(async (opts) => {
472
- await handleRunRuleHost({ workspace: opts.workspace, painId: opts.painId, channel: opts.channel, maxRounds: opts.maxRounds, timeoutMs: opts.timeoutMs, dryRun: opts.dryRun, confirm: opts.confirm, json: opts.json });
625
+ await handleRunRuleHost({ workspace: opts.workspace, painId: opts.painId, channel: opts.channel, maxRounds: opts.maxRounds, timeoutMs: opts.timeoutMs, behaviorExamples: opts.behaviorExamples, dryRun: opts.dryRun, confirm: opts.confirm, json: opts.json });
473
626
  });
474
627
  }
@@ -18,6 +18,11 @@ vi.mock('child_process', () => ({
18
18
  const { handleRuntimeUat } = await import('./runtime-uat.js');
19
19
  const { guardUatWorkspace } = await import('../utils/production-workspace-guard.js');
20
20
 
21
+ // ─── Cross-platform production workspace path ─────────────────────────────────
22
+ // Mirrors the default in production-workspace-guard.ts: ~/.openclaw/workspace.
23
+ // Using this instead of hardcoded "D:\..." paths ensures tests work on Linux/macOS CI.
24
+ const PROD_WORKSPACE = path.join(os.homedir(), '.openclaw', 'workspace');
25
+
21
26
  // ─── Test Setup ─────────────────────────────────────────────────────────────
22
27
  const capturedStderr: string[] = [];
23
28
  const capturedStdout: string[] = [];
@@ -53,23 +58,16 @@ beforeEach(() => {
53
58
 
54
59
  // ─── Tests ──────────────────────────────────────────────────────────────────
55
60
  describe('PRI-334: production workspace refusal', () => {
56
- it('refuses D: workspace with exit code 1', async () => {
57
- mockExecFileSync.mockClear();
58
- await handleRuntimeUat({ workspace: 'D:\\.openclaw\\workspace', count: 1 });
59
- expect(capturedExitCode).toBe(1);
60
- expect(mockExecFileSync).not.toHaveBeenCalled();
61
- });
62
-
63
- it('refuses C: workspace with exit code 1', async () => {
61
+ it('refuses default production workspace with exit code 1', async () => {
64
62
  mockExecFileSync.mockClear();
65
- await handleRuntimeUat({ workspace: 'C:\\.openclaw\\workspace', count: 1 });
63
+ await handleRuntimeUat({ workspace: PROD_WORKSPACE, count: 1 });
66
64
  expect(capturedExitCode).toBe(1);
67
65
  expect(mockExecFileSync).not.toHaveBeenCalled();
68
66
  });
69
67
 
70
- it('refuses production workspace subdirectory', async () => {
68
+ it('refuses production workspace subdirectory with exit code 1', async () => {
71
69
  mockExecFileSync.mockClear();
72
- await handleRuntimeUat({ workspace: 'D:\\.openclaw\\workspace\\sub\\path', count: 1 });
70
+ await handleRuntimeUat({ workspace: path.join(PROD_WORKSPACE, 'sub', 'path'), count: 1 });
73
71
  expect(capturedExitCode).toBe(1);
74
72
  expect(mockExecFileSync).not.toHaveBeenCalled();
75
73
  });
@@ -82,11 +80,11 @@ describe('PRI-334: allowed paths (guard level)', () => {
82
80
  });
83
81
 
84
82
  it('allows workspace-test sibling (ERR-030)', () => {
85
- expect(guardUatWorkspace('D:\\.openclaw\\workspace-test', 'test').refused).toBe(false);
83
+ expect(guardUatWorkspace(path.join(os.homedir(), '.openclaw', 'workspace-test'), 'test').refused).toBe(false);
86
84
  });
87
85
 
88
86
  it('allows workspace-backup sibling (ERR-030)', () => {
89
- expect(guardUatWorkspace('D:\\.openclaw\\workspace-backup', 'test').refused).toBe(false);
87
+ expect(guardUatWorkspace(path.join(os.homedir(), '.openclaw', 'workspace-backup'), 'test').refused).toBe(false);
90
88
  });
91
89
  });
92
90
 
@@ -94,7 +92,7 @@ describe('PRI-334: JSON output (EP-04)', () => {
94
92
  it('outputs single JSON object with reason and nextAction', async () => {
95
93
  mockExecFileSync.mockClear();
96
94
  capturedStdout.length = 0;
97
- await handleRuntimeUat({ workspace: 'D:\\.openclaw\\workspace', count: 1, json: true });
95
+ await handleRuntimeUat({ workspace: PROD_WORKSPACE, count: 1, json: true });
98
96
  expect(capturedExitCode).toBe(1);
99
97
  expect(mockExecFileSync).not.toHaveBeenCalled();
100
98
 
@@ -115,7 +113,7 @@ describe('PRI-334: escape hatch', () => {
115
113
  capturedStderr.length = 0;
116
114
  capturedExitCode = null;
117
115
 
118
- await handleRuntimeUat({ workspace: 'D:\\.openclaw\\workspace', count: 1, allowProductionWorkspaceForUat: true });
116
+ await handleRuntimeUat({ workspace: PROD_WORKSPACE, count: 1, allowProductionWorkspaceForUat: true });
119
117
  const stderrText = capturedStderr.join('\n');
120
118
  expect(stderrText).toContain('WARNING');
121
119
  expect(stderrText).toContain('--allow-production-workspace-for-uat');
@@ -125,7 +123,7 @@ describe('PRI-334: escape hatch', () => {
125
123
  describe('PRI-334: mutation prevention', () => {
126
124
  it('does not call execFileSync after guard refusal', async () => {
127
125
  mockExecFileSync.mockClear();
128
- await handleRuntimeUat({ workspace: 'D:\\.openclaw\\workspace', count: 1 });
126
+ await handleRuntimeUat({ workspace: PROD_WORKSPACE, count: 1 });
129
127
  expect(mockExecFileSync).not.toHaveBeenCalled();
130
128
  });
131
129
  });
@@ -144,11 +144,12 @@ function pd(args: string[], workspace: string, timeoutMs = 300_000): string {
144
144
  stdio: ['pipe', 'pipe', 'pipe'],
145
145
  });
146
146
  } catch (err: unknown) {
147
- if (err instanceof Error && 'code' in err && (err as { code: string }).code === 'ENOENT') {
147
+ if (err instanceof Error && // eslint-disable-next-line no-restricted-syntax -- 'in' required for Error subtype narrowing (err.code access)
148
+ 'code' in err && (err as { code: string }).code === 'ENOENT') {
148
149
  throw new Error(`pd CLI not found at ${cliPath} — run: npm run build --workspace=@principles/pd-cli`, { cause: err });
149
150
  }
150
- if (err && typeof err === 'object' && 'stdout' in err) {
151
- return String((err).stdout);
151
+ if (err && typeof err === 'object' && Object.hasOwn(err, 'stdout')) {
152
+ return String((err as { stdout: unknown }).stdout);
152
153
  }
153
154
  throw err;
154
155
  }
@@ -13,7 +13,7 @@
13
13
 
14
14
  import * as fs from 'fs';
15
15
  import * as path from 'path';
16
- import yaml from 'js-yaml';
16
+ import * as yaml from 'js-yaml';
17
17
  import { resolveOutputLanguage, DEFAULT_OUTPUT_LANGUAGE } from '@principles/core/runtime-v2';
18
18
  import type { ResolvedOutputLanguage } from '@principles/core/runtime-v2';
19
19
 
package/src/index.ts CHANGED
@@ -8,13 +8,13 @@
8
8
 
9
9
  import { Command } from 'commander';
10
10
  import { handlePainRecord } from './commands/pain-record.js';
11
- import { handlePainRetry } from './commands/pain-retry.js';
11
+ import { registerPainRetryCommand } from './commands/pain-retry.js';
12
12
  import { handlePainEvidence } from './commands/pain-evidence.js';
13
13
  import { handleSamplesList } from './commands/samples-list.js';
14
14
  import { handleSamplesReview } from './commands/samples-review.js';
15
15
  import { handleEvolutionTasksList } from './commands/evolution-tasks-list.js';
16
16
  import { handleEvolutionTasksShow } from './commands/evolution-tasks-show.js';
17
- import { handleHealth } from './commands/health.js';
17
+ import { registerHealthCommand } from './commands/health.js';
18
18
  import { handleTaskShow, registerTaskListCommand } from './commands/task.js';
19
19
  import { handleRunList, handleRunShow } from './commands/run.js';
20
20
  import { handleTrajectoryLocate } from './commands/trajectory.js';
@@ -42,11 +42,20 @@ import { handleRuntimePainFlood } from './commands/runtime-pain-flood-simulation
42
42
  import { handleRuntimeInternalizationIntegrity } from './commands/runtime-internalization-integrity.js';
43
43
  import { handleRuntimeInternalizationIntegrityRepair } from './commands/runtime-internalization-integrity-repair.js';
44
44
  import { handleRuntimeInternalizationEnqueueSuccessors } from './commands/runtime-internalization-enqueue-successors.js';
45
+ import { handleRuntimeInternalizationContextTrace } from './commands/runtime-internalization-context-trace.js';
45
46
  import { handleRuntimeDiagnosticsExport } from './commands/runtime-diagnostics-export.js';
46
47
  import { handleRuntimeRecoverySweep } from './commands/runtime-recovery.js';
47
48
  import { handleRuntimeRecoveryFailedTasks } from './commands/runtime-recovery-failed-tasks.js';
48
- import { handleRuntimeActivationDispatch } from './commands/runtime-activation.js';
49
- import { handleRuntimeActivationDeactivate, handleRuntimeActivationList, handleRuntimeActivationEdit } from './commands/runtime-activation.js';
49
+ import {
50
+ handleRuntimeActivationDeactivate,
51
+ handleRuntimeActivationList,
52
+ handleRuntimeActivationEdit,
53
+ registerRuntimeActivationPromoteCommand,
54
+ registerRuntimeActivationDeactivateCommand,
55
+ registerRuntimeActivationListCommand,
56
+ registerRuntimeActivationDispatchCommand,
57
+ registerRuntimeActivationApproveCommand,
58
+ } from './commands/runtime-activation.js';
50
59
  import { handleProvenChannelBaseline } from './commands/proven-channel-baseline.js';
51
60
  import { handleDemoStoryA } from './commands/demo-story-a.js';
52
61
  import { handleRuntimeFeaturesStatus } from './commands/runtime-features.js';
@@ -54,6 +63,7 @@ import { handleConfigDoctor } from './commands/config-doctor.js';
54
63
  import { registerMvpCommands } from './commands/mvp-smoke.js';
55
64
  import { registerRulecodeCommand } from './commands/rulecode.js';
56
65
  import { registerIntentCommand } from './commands/intent.js';
66
+ import { registerErrorsListCommand } from './commands/errors-list.js';
57
67
 
58
68
  import { createRequire } from 'module';
59
69
  const require = createRequire(import.meta.url);
@@ -85,30 +95,11 @@ painCmd
85
95
  await handlePainRecord(opts);
86
96
  });
87
97
 
88
- painCmd
89
- .command('retry')
90
- .description('Retry a failed diagnosis by pain ID')
91
- .requiredOption('-p, --pain-id <painId>', 'Pain ID to retry diagnosis for')
92
- .option('-w, --workspace <path>', 'Workspace directory')
93
- .option('-r, --runtime <kind>', "Runtime kind: 'openclaw-cli', 'test-double', 'pi-ai'")
94
- .option('--openclaw-local', 'Use local OpenClaw (mutually exclusive with --openclaw-gateway)')
95
- .option('--openclaw-gateway', 'Use gateway OpenClaw (mutually exclusive with --openclaw-local)')
96
- .option('-a, --agent <agentId>', 'Agent ID to invoke')
97
- .option('--provider <name>', 'LLM provider (e.g., openrouter) — for pi-ai, falls back to policy')
98
- .option('--model <id>', 'Model ID (e.g., anthropic/claude-sonnet-4) — for pi-ai, falls back to policy')
99
- .option('--apiKeyEnv <name>', 'Env var name for API key — for pi-ai, falls back to policy')
100
- .option('--baseUrl <url>', 'Custom base URL — for pi-ai, falls back to policy')
101
- .option('--maxRetries <n>', 'Max retry attempts for LLM failures — for pi-ai, falls back to policy', parseInt)
102
- .option('--timeoutMs <ms>', 'Timeout in milliseconds — for pi-ai, falls back to policy', parseInt)
103
- .option('--force', 'Allow retry of already-succeeded tasks')
104
- .option('--json', 'Output raw JSON')
105
- .action(async (opts) => {
106
- await handlePainRetry(opts);
107
- });
98
+ registerPainRetryCommand(painCmd);
108
99
 
109
100
  painCmd
110
101
  .command('evidence')
111
- .description('Show recent pain admission/trigger decisions (PEAT-B2)')
102
+ .description('Show recent TRIGGER_DECISION log entries — pain admission gate decisions only (PEAT-B2)')
112
103
  .option('-w, --workspace <path>', 'Workspace directory')
113
104
  .option('-l, --limit <number>', 'Max entries to show (default: 20)', parseInt)
114
105
  .option('--json', 'Output raw JSON')
@@ -169,14 +160,7 @@ tasksCmd
169
160
  await handleEvolutionTasksShow({ id });
170
161
  });
171
162
 
172
- program
173
- .command('health')
174
- .description('Show health diagnostics for all workspaces')
175
- .option('-w, --workspace <path>', 'Workspace directory')
176
- .option('--json', 'Output raw JSON')
177
- .action(async (opts) => {
178
- await handleHealth(opts);
179
- });
163
+ registerHealthCommand(program);
180
164
 
181
165
  // ── Runtime v2 task/run commands ──────────────────────────────────────────────鈹€鈹€鈹€鈹€鈹€鈹€
182
166
 
@@ -303,7 +287,7 @@ diagnoseCmd
303
287
  .description('Execute diagnostician runner for a task')
304
288
  .requiredOption('-t, --task-id <taskId>', 'Task ID to execute')
305
289
  .option('-w, --workspace <path>', 'Workspace directory')
306
- .option('-r, --runtime <kind>', "Runtime kind: 'openclaw-cli', 'test-double', 'pi-ai'")
290
+ .option('-r, --runtime <kind>', "Runtime kind: 'openclaw-cli', 'test-double', 'pi-ai'. If omitted, resolved from .pd/config.yaml; refuses if no binding (no test-double default).")
307
291
  .option('--openclaw-local', 'Use local OpenClaw (mutually exclusive with --openclaw-gateway)')
308
292
  .option('--openclaw-gateway', 'Use gateway OpenClaw (mutually exclusive with --openclaw-local)')
309
293
  .option('-a, --agent <agentId>', 'Agent ID to invoke')
@@ -334,6 +318,28 @@ runtimeCmd
334
318
  await handleRuntimeCanary({ workspace: opts.workspace, json: opts.json });
335
319
  });
336
320
 
321
+ // pd runtime init — Initialize all PD SQLite databases for a workspace.
322
+ // Lazy import: avoids loading `principles-disciple` at module init time, which
323
+ // would crash `pd --version` in packaged installs where the plugin's transitive
324
+ // deps are not resolvable from pd-cli's node_modules. The import only executes
325
+ // when `pd runtime init` is actually invoked.
326
+ runtimeCmd
327
+ .command('init')
328
+ .description('Initialize all PD SQLite databases (state.db, trajectory.db, subagent_workflows.db)')
329
+ .option('-w, --workspace <path>', 'Workspace directory')
330
+ .option('--dry-run', 'Show what would be initialized without writing (default)')
331
+ .option('--confirm', 'Actually initialize the databases (required to write)')
332
+ .option('--json', 'Output raw JSON')
333
+ .action(async (opts) => {
334
+ const { handleRuntimeInit } = await import('./commands/runtime-init.js');
335
+ await handleRuntimeInit({
336
+ workspace: opts.workspace,
337
+ dryRun: opts.dryRun === true,
338
+ confirm: opts.confirm === true,
339
+ json: opts.json === true,
340
+ });
341
+ });
342
+
337
343
  const synthCmd = runtimeCmd
338
344
  .command('synthetic', { hidden: true })
339
345
  .description('Synthetic workload baseline commands');
@@ -414,31 +420,15 @@ const activationTopCmd = program
414
420
  .command('activation')
415
421
  .description('Activation management — list active activations, deactivate (Story A\' Steps 5-6)');
416
422
 
417
- activationTopCmd
418
- .command('list')
419
- .description('List all activations for a workspace')
420
- .option('-w, --workspace <path>', 'Workspace directory')
421
- .option('-c, --channel <channel>', 'Filter by channel (prompt|code_tool_hook)')
422
- .option('--include-deactivated', 'Include deactivated records in output')
423
- .option('--json', 'Output raw JSON')
424
- .action(async (opts) => {
425
- await handleRuntimeActivationList({
426
- workspace: opts.workspace,
427
- channel: opts.channel,
428
- includeDeactivated: opts.includeDeactivated,
429
- json: opts.json,
430
- });
431
- });
423
+ registerRuntimeActivationListCommand(activationTopCmd);
432
424
 
433
- activationTopCmd
434
- .command('deactivate')
435
- .description('Deactivate an activation by activation ID')
436
- .requiredOption('--activation-id <id>', 'Activation ID to deactivate')
437
- .option('-w, --workspace <path>', 'Workspace directory')
438
- .option('--json', 'Output raw JSON')
439
- .action(async (opts) => {
440
- await handleRuntimeActivationDeactivate({ activationId: opts.activationId, workspace: opts.workspace, json: opts.json });
441
- });
425
+ registerRuntimeActivationDeactivateCommand(activationTopCmd);
426
+
427
+ // Bug-M fix: CLI closed loop — approve a pending approval and dispatch its activation.
428
+ // Reuses the same ApprovalQueue + ApprovalCompletionService as the Console model.
429
+ registerRuntimeActivationApproveCommand(activationTopCmd);
430
+
431
+ registerRuntimeActivationPromoteCommand(activationTopCmd);
442
432
 
443
433
  const configCmd = program
444
434
  .command('config')
@@ -459,6 +449,7 @@ const demoCmd = program
459
449
 
460
450
  demoCmd
461
451
  .command('story-a')
452
+ .alias('first-principle')
462
453
  .description('Run Story A\'s proven-channel demo (PRI-246) — full evidence→proposal→approval→activation→observation chain')
463
454
  .option('-w, --workspace <path>', 'Workspace directory (default: temp workspace)')
464
455
  .option('--json', 'Output raw JSON')
@@ -541,7 +532,7 @@ runtimeHealthCmd
541
532
  });
542
533
 
543
534
  const internalizationCmd = runtimeCmd
544
- .command('internalization', { hidden: true })
535
+ .command('internalization')
545
536
  .description('Internalization Engine operator visibility');
546
537
 
547
538
  internalizationCmd
@@ -610,29 +601,23 @@ internalizationCmd
610
601
  await handleRuntimeInternalizationEnqueueSuccessors({ workspace: opts.workspace, dryRun: opts.dryRun, confirm: opts.confirm, json: opts.json });
611
602
  });
612
603
 
604
+ // Layer 3 (design §6.7, PR 5): read-only context-trace command.
605
+ internalizationCmd
606
+ .command('context-trace')
607
+ .description('Trace the internalization context chain: summaries, three-segment diagnosis, truncations, degradations')
608
+ .requiredOption('-t, --task <taskId>', 'Task ID to trace')
609
+ .option('-a, --artifact <artifactId>', 'Specific artifact ID to start from')
610
+ .option('-w, --workspace <path>', 'Workspace directory')
611
+ .option('--json', 'Output raw JSON (default)')
612
+ .action(async (opts) => {
613
+ await handleRuntimeInternalizationContextTrace({ workspace: opts.workspace, task: opts.task, artifact: opts.artifact, json: opts.json });
614
+ });
615
+
613
616
  const activationCmd = runtimeCmd
614
617
  .command('activation', { hidden: true })
615
618
  .description('Activation dispatch operations (hidden — use pd activation for list/deactivate)');
616
619
 
617
- activationCmd
618
- .command('dispatch')
619
- .description('Dispatch an activation for a rollout-reviewed artifact')
620
- .option('-a, --artifact-id <id>', 'PIArtifact ID to activate')
621
- .option('-w, --workspace <path>', 'Workspace directory')
622
- .option('-c, --channel <channel>', 'Activation channel (prompt|defer_archive)', 'prompt')
623
- .option('--dry-run', 'Dry-run mode (default, no writes)')
624
- .option('--confirm', 'Confirm and write activation record')
625
- .option('--json', 'Output raw JSON')
626
- .action(async (opts) => {
627
- await handleRuntimeActivationDispatch({
628
- workspace: opts.workspace,
629
- artifactId: opts.artifactId,
630
- channel: opts.channel,
631
- dryRun: opts.dryRun,
632
- confirm: opts.confirm,
633
- json: opts.json,
634
- });
635
- });
620
+ registerRuntimeActivationDispatchCommand(activationCmd);
636
621
 
637
622
  // PRI-408 Contract E: Owner-initiated rollback/deactivate of an activation.
638
623
  // Idempotent — calling twice on the same ID is safe and returns ok=false with reason.
@@ -977,6 +962,12 @@ registerRulecodeCommand(program);
977
962
 
978
963
  registerIntentCommand(program);
979
964
 
965
+ // ─── Errors List (Task 14: Feedback Pipeline Observability) ────────────────
966
+ // Operator diagnostic: aggregates SQLite failed/needs_human_review tasks +
967
+ // worker-status.json errors array. Read-only.
968
+
969
+ registerErrorsListCommand(program);
970
+
980
971
  const consoleCmd = program
981
972
  .command('console')
982
973
  .description('Start the pd-console web UI for principle review (default: fallback launcher)')