@principles/pd-cli 1.73.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 (298) hide show
  1. package/README.md +90 -0
  2. package/dist/commands/artifact.d.ts +14 -0
  3. package/dist/commands/artifact.d.ts.map +1 -0
  4. package/dist/commands/artifact.js +67 -0
  5. package/dist/commands/artifact.js.map +1 -0
  6. package/dist/commands/candidate.d.ts +83 -0
  7. package/dist/commands/candidate.d.ts.map +1 -0
  8. package/dist/commands/candidate.js +891 -0
  9. package/dist/commands/candidate.js.map +1 -0
  10. package/dist/commands/central-sync.d.ts +10 -0
  11. package/dist/commands/central-sync.d.ts.map +1 -0
  12. package/dist/commands/central-sync.js +32 -0
  13. package/dist/commands/central-sync.js.map +1 -0
  14. package/dist/commands/console.d.ts +9 -0
  15. package/dist/commands/console.d.ts.map +1 -0
  16. package/dist/commands/console.js +114 -0
  17. package/dist/commands/console.js.map +1 -0
  18. package/dist/commands/context.d.ts +7 -0
  19. package/dist/commands/context.d.ts.map +1 -0
  20. package/dist/commands/context.js +55 -0
  21. package/dist/commands/context.js.map +1 -0
  22. package/dist/commands/demo-story-a.d.ts +12 -0
  23. package/dist/commands/demo-story-a.d.ts.map +1 -0
  24. package/dist/commands/demo-story-a.js +175 -0
  25. package/dist/commands/demo-story-a.js.map +1 -0
  26. package/dist/commands/diagnose.d.ts +35 -0
  27. package/dist/commands/diagnose.d.ts.map +1 -0
  28. package/dist/commands/diagnose.js +390 -0
  29. package/dist/commands/diagnose.js.map +1 -0
  30. package/dist/commands/evolution-tasks-list.d.ts +15 -0
  31. package/dist/commands/evolution-tasks-list.d.ts.map +1 -0
  32. package/dist/commands/evolution-tasks-list.js +34 -0
  33. package/dist/commands/evolution-tasks-list.js.map +1 -0
  34. package/dist/commands/evolution-tasks-show.d.ts +14 -0
  35. package/dist/commands/evolution-tasks-show.d.ts.map +1 -0
  36. package/dist/commands/evolution-tasks-show.js +52 -0
  37. package/dist/commands/evolution-tasks-show.js.map +1 -0
  38. package/dist/commands/flow.d.ts +7 -0
  39. package/dist/commands/flow.d.ts.map +1 -0
  40. package/dist/commands/flow.js +57 -0
  41. package/dist/commands/flow.js.map +1 -0
  42. package/dist/commands/health.d.ts +16 -0
  43. package/dist/commands/health.d.ts.map +1 -0
  44. package/dist/commands/health.js +150 -0
  45. package/dist/commands/health.js.map +1 -0
  46. package/dist/commands/history.d.ts +11 -0
  47. package/dist/commands/history.d.ts.map +1 -0
  48. package/dist/commands/history.js +50 -0
  49. package/dist/commands/history.js.map +1 -0
  50. package/dist/commands/legacy-cleanup.d.ts +27 -0
  51. package/dist/commands/legacy-cleanup.d.ts.map +1 -0
  52. package/dist/commands/legacy-cleanup.js +171 -0
  53. package/dist/commands/legacy-cleanup.js.map +1 -0
  54. package/dist/commands/legacy-import.d.ts +7 -0
  55. package/dist/commands/legacy-import.d.ts.map +1 -0
  56. package/dist/commands/legacy-import.js +86 -0
  57. package/dist/commands/legacy-import.js.map +1 -0
  58. package/dist/commands/pain-record.d.ts +10 -0
  59. package/dist/commands/pain-record.d.ts.map +1 -0
  60. package/dist/commands/pain-record.js +162 -0
  61. package/dist/commands/pain-record.js.map +1 -0
  62. package/dist/commands/proven-channel-baseline.d.ts +12 -0
  63. package/dist/commands/proven-channel-baseline.d.ts.map +1 -0
  64. package/dist/commands/proven-channel-baseline.js +97 -0
  65. package/dist/commands/proven-channel-baseline.js.map +1 -0
  66. package/dist/commands/remediation-output.d.ts +40 -0
  67. package/dist/commands/remediation-output.d.ts.map +1 -0
  68. package/dist/commands/remediation-output.js +23 -0
  69. package/dist/commands/remediation-output.js.map +1 -0
  70. package/dist/commands/run.d.ts +10 -0
  71. package/dist/commands/run.d.ts.map +1 -0
  72. package/dist/commands/run.js +68 -0
  73. package/dist/commands/run.js.map +1 -0
  74. package/dist/commands/runtime-activation.d.ts +11 -0
  75. package/dist/commands/runtime-activation.d.ts.map +1 -0
  76. package/dist/commands/runtime-activation.js +150 -0
  77. package/dist/commands/runtime-activation.js.map +1 -0
  78. package/dist/commands/runtime-canary.d.ts +30 -0
  79. package/dist/commands/runtime-canary.d.ts.map +1 -0
  80. package/dist/commands/runtime-canary.js +343 -0
  81. package/dist/commands/runtime-canary.js.map +1 -0
  82. package/dist/commands/runtime-diagnostics-export.d.ts +20 -0
  83. package/dist/commands/runtime-diagnostics-export.d.ts.map +1 -0
  84. package/dist/commands/runtime-diagnostics-export.js +177 -0
  85. package/dist/commands/runtime-diagnostics-export.js.map +1 -0
  86. package/dist/commands/runtime-features.d.ts +26 -0
  87. package/dist/commands/runtime-features.d.ts.map +1 -0
  88. package/dist/commands/runtime-features.js +70 -0
  89. package/dist/commands/runtime-features.js.map +1 -0
  90. package/dist/commands/runtime-gfi-snapshot.d.ts +7 -0
  91. package/dist/commands/runtime-gfi-snapshot.d.ts.map +1 -0
  92. package/dist/commands/runtime-gfi-snapshot.js +101 -0
  93. package/dist/commands/runtime-gfi-snapshot.js.map +1 -0
  94. package/dist/commands/runtime-health-snapshot.d.ts +7 -0
  95. package/dist/commands/runtime-health-snapshot.d.ts.map +1 -0
  96. package/dist/commands/runtime-health-snapshot.js +93 -0
  97. package/dist/commands/runtime-health-snapshot.js.map +1 -0
  98. package/dist/commands/runtime-idle-trigger.d.ts +12 -0
  99. package/dist/commands/runtime-idle-trigger.d.ts.map +1 -0
  100. package/dist/commands/runtime-idle-trigger.js +102 -0
  101. package/dist/commands/runtime-idle-trigger.js.map +1 -0
  102. package/dist/commands/runtime-internalization-enqueue-successors.d.ts +9 -0
  103. package/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -0
  104. package/dist/commands/runtime-internalization-enqueue-successors.js +393 -0
  105. package/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -0
  106. package/dist/commands/runtime-internalization-integrity-repair.d.ts +9 -0
  107. package/dist/commands/runtime-internalization-integrity-repair.d.ts.map +1 -0
  108. package/dist/commands/runtime-internalization-integrity-repair.js +54 -0
  109. package/dist/commands/runtime-internalization-integrity-repair.js.map +1 -0
  110. package/dist/commands/runtime-internalization-integrity.d.ts +7 -0
  111. package/dist/commands/runtime-internalization-integrity.d.ts.map +1 -0
  112. package/dist/commands/runtime-internalization-integrity.js +53 -0
  113. package/dist/commands/runtime-internalization-integrity.js.map +1 -0
  114. package/dist/commands/runtime-internalization-queue.d.ts +7 -0
  115. package/dist/commands/runtime-internalization-queue.d.ts.map +1 -0
  116. package/dist/commands/runtime-internalization-queue.js +85 -0
  117. package/dist/commands/runtime-internalization-queue.js.map +1 -0
  118. package/dist/commands/runtime-internalization-run-once.d.ts +12 -0
  119. package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -0
  120. package/dist/commands/runtime-internalization-run-once.js +546 -0
  121. package/dist/commands/runtime-internalization-run-once.js.map +1 -0
  122. package/dist/commands/runtime-internalization-wake-once.d.ts +8 -0
  123. package/dist/commands/runtime-internalization-wake-once.d.ts.map +1 -0
  124. package/dist/commands/runtime-internalization-wake-once.js +72 -0
  125. package/dist/commands/runtime-internalization-wake-once.js.map +1 -0
  126. package/dist/commands/runtime-pain-flood-simulation.d.ts +10 -0
  127. package/dist/commands/runtime-pain-flood-simulation.d.ts.map +1 -0
  128. package/dist/commands/runtime-pain-flood-simulation.js +104 -0
  129. package/dist/commands/runtime-pain-flood-simulation.js.map +1 -0
  130. package/dist/commands/runtime-pruning.d.ts +45 -0
  131. package/dist/commands/runtime-pruning.d.ts.map +1 -0
  132. package/dist/commands/runtime-pruning.js +355 -0
  133. package/dist/commands/runtime-pruning.js.map +1 -0
  134. package/dist/commands/runtime-recovery.d.ts +9 -0
  135. package/dist/commands/runtime-recovery.d.ts.map +1 -0
  136. package/dist/commands/runtime-recovery.js +94 -0
  137. package/dist/commands/runtime-recovery.js.map +1 -0
  138. package/dist/commands/runtime-synthetic-baseline.d.ts +7 -0
  139. package/dist/commands/runtime-synthetic-baseline.d.ts.map +1 -0
  140. package/dist/commands/runtime-synthetic-baseline.js +59 -0
  141. package/dist/commands/runtime-synthetic-baseline.js.map +1 -0
  142. package/dist/commands/runtime-uat.d.ts +52 -0
  143. package/dist/commands/runtime-uat.d.ts.map +1 -0
  144. package/dist/commands/runtime-uat.js +274 -0
  145. package/dist/commands/runtime-uat.js.map +1 -0
  146. package/dist/commands/runtime.d.ts +20 -0
  147. package/dist/commands/runtime.d.ts.map +1 -0
  148. package/dist/commands/runtime.js +256 -0
  149. package/dist/commands/runtime.js.map +1 -0
  150. package/dist/commands/samples-list.d.ts +11 -0
  151. package/dist/commands/samples-list.d.ts.map +1 -0
  152. package/dist/commands/samples-list.js +37 -0
  153. package/dist/commands/samples-list.js.map +1 -0
  154. package/dist/commands/samples-review.d.ts +14 -0
  155. package/dist/commands/samples-review.d.ts.map +1 -0
  156. package/dist/commands/samples-review.js +22 -0
  157. package/dist/commands/samples-review.js.map +1 -0
  158. package/dist/commands/task.d.ts +14 -0
  159. package/dist/commands/task.d.ts.map +1 -0
  160. package/dist/commands/task.js +92 -0
  161. package/dist/commands/task.js.map +1 -0
  162. package/dist/commands/trace.d.ts +19 -0
  163. package/dist/commands/trace.d.ts.map +1 -0
  164. package/dist/commands/trace.js +154 -0
  165. package/dist/commands/trace.js.map +1 -0
  166. package/dist/commands/trajectory.d.ts +11 -0
  167. package/dist/commands/trajectory.d.ts.map +1 -0
  168. package/dist/commands/trajectory.js +47 -0
  169. package/dist/commands/trajectory.js.map +1 -0
  170. package/dist/index.d.ts +9 -0
  171. package/dist/index.d.ts.map +1 -0
  172. package/dist/index.js +736 -0
  173. package/dist/index.js.map +1 -0
  174. package/dist/legacy/legacy-import.d.ts +15 -0
  175. package/dist/legacy/legacy-import.d.ts.map +1 -0
  176. package/dist/legacy/legacy-import.js +141 -0
  177. package/dist/legacy/legacy-import.js.map +1 -0
  178. package/dist/legacy/session-history-import.d.ts +26 -0
  179. package/dist/legacy/session-history-import.d.ts.map +1 -0
  180. package/dist/legacy/session-history-import.js +151 -0
  181. package/dist/legacy/session-history-import.js.map +1 -0
  182. package/dist/principle-tree-ledger-adapter.d.ts +12 -0
  183. package/dist/principle-tree-ledger-adapter.d.ts.map +1 -0
  184. package/dist/principle-tree-ledger-adapter.js +12 -0
  185. package/dist/principle-tree-ledger-adapter.js.map +1 -0
  186. package/dist/resolve-workspace.d.ts +12 -0
  187. package/dist/resolve-workspace.d.ts.map +1 -0
  188. package/dist/resolve-workspace.js +20 -0
  189. package/dist/resolve-workspace.js.map +1 -0
  190. package/dist/services/demo-story-a-runner.d.ts +8 -0
  191. package/dist/services/demo-story-a-runner.d.ts.map +1 -0
  192. package/dist/services/demo-story-a-runner.js +369 -0
  193. package/dist/services/demo-story-a-runner.js.map +1 -0
  194. package/dist/services/feature-flag-loader.d.ts +6 -0
  195. package/dist/services/feature-flag-loader.d.ts.map +1 -0
  196. package/dist/services/feature-flag-loader.js +54 -0
  197. package/dist/services/feature-flag-loader.js.map +1 -0
  198. package/dist/services/pain-flood-simulation-runner.d.ts +10 -0
  199. package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -0
  200. package/dist/services/pain-flood-simulation-runner.js +289 -0
  201. package/dist/services/pain-flood-simulation-runner.js.map +1 -0
  202. package/dist/services/proven-channel-baseline-runner.d.ts +12 -0
  203. package/dist/services/proven-channel-baseline-runner.d.ts.map +1 -0
  204. package/dist/services/proven-channel-baseline-runner.js +114 -0
  205. package/dist/services/proven-channel-baseline-runner.js.map +1 -0
  206. package/dist/services/synthetic-baseline-runner.d.ts +8 -0
  207. package/dist/services/synthetic-baseline-runner.d.ts.map +1 -0
  208. package/dist/services/synthetic-baseline-runner.js +251 -0
  209. package/dist/services/synthetic-baseline-runner.js.map +1 -0
  210. package/package.json +35 -0
  211. package/src/commands/artifact.ts +82 -0
  212. package/src/commands/candidate.ts +1117 -0
  213. package/src/commands/central-sync.ts +44 -0
  214. package/src/commands/console.ts +121 -0
  215. package/src/commands/context.ts +72 -0
  216. package/src/commands/demo-story-a.ts +195 -0
  217. package/src/commands/diagnose.ts +452 -0
  218. package/src/commands/evolution-tasks-list.ts +44 -0
  219. package/src/commands/evolution-tasks-show.ts +60 -0
  220. package/src/commands/flow.ts +60 -0
  221. package/src/commands/health.ts +189 -0
  222. package/src/commands/history.ts +63 -0
  223. package/src/commands/legacy-cleanup.ts +206 -0
  224. package/src/commands/legacy-import.ts +104 -0
  225. package/src/commands/pain-record.ts +167 -0
  226. package/src/commands/proven-channel-baseline.ts +113 -0
  227. package/src/commands/remediation-output.ts +66 -0
  228. package/src/commands/run.ts +89 -0
  229. package/src/commands/runtime-activation.ts +176 -0
  230. package/src/commands/runtime-canary.ts +371 -0
  231. package/src/commands/runtime-diagnostics-export.ts +229 -0
  232. package/src/commands/runtime-features.ts +103 -0
  233. package/src/commands/runtime-gfi-snapshot.ts +135 -0
  234. package/src/commands/runtime-health-snapshot.ts +106 -0
  235. package/src/commands/runtime-internalization-enqueue-successors.ts +479 -0
  236. package/src/commands/runtime-internalization-integrity-repair.ts +69 -0
  237. package/src/commands/runtime-internalization-integrity.ts +63 -0
  238. package/src/commands/runtime-internalization-queue.ts +106 -0
  239. package/src/commands/runtime-internalization-run-once.ts +658 -0
  240. package/src/commands/runtime-internalization-wake-once.ts +87 -0
  241. package/src/commands/runtime-pain-flood-simulation.ts +121 -0
  242. package/src/commands/runtime-pruning.ts +438 -0
  243. package/src/commands/runtime-recovery.ts +107 -0
  244. package/src/commands/runtime-synthetic-baseline.ts +70 -0
  245. package/src/commands/runtime-uat.ts +339 -0
  246. package/src/commands/runtime.ts +281 -0
  247. package/src/commands/samples-list.ts +43 -0
  248. package/src/commands/samples-review.ts +32 -0
  249. package/src/commands/task.ts +130 -0
  250. package/src/commands/trace.ts +174 -0
  251. package/src/commands/trajectory.ts +64 -0
  252. package/src/index.ts +829 -0
  253. package/src/legacy/legacy-import.ts +179 -0
  254. package/src/legacy/session-history-import.ts +231 -0
  255. package/src/principle-tree-ledger-adapter.ts +13 -0
  256. package/src/resolve-workspace.ts +20 -0
  257. package/src/services/demo-story-a-runner.ts +472 -0
  258. package/src/services/feature-flag-loader.ts +73 -0
  259. package/src/services/pain-flood-simulation-runner.ts +354 -0
  260. package/src/services/proven-channel-baseline-runner.ts +150 -0
  261. package/src/services/synthetic-baseline-runner.ts +291 -0
  262. package/tests/commands/candidate-audit-repair.test.ts +338 -0
  263. package/tests/commands/candidate-intake.test.ts +589 -0
  264. package/tests/commands/candidate-internalization-backfill.test.ts +480 -0
  265. package/tests/commands/candidate-internalize.test.ts +272 -0
  266. package/tests/commands/candidate-route.test.ts +328 -0
  267. package/tests/commands/candidate-show.test.ts +95 -0
  268. package/tests/commands/cli-command-tree.test.ts +64 -0
  269. package/tests/commands/context.test.ts +114 -0
  270. package/tests/commands/demo-story-a.test.ts +255 -0
  271. package/tests/commands/diagnose.test.ts +792 -0
  272. package/tests/commands/health.test.ts +330 -0
  273. package/tests/commands/pain-record.test.ts +316 -0
  274. package/tests/commands/plugin-config-resolution-cutover.test.ts +220 -0
  275. package/tests/commands/proven-channel-baseline.test.ts +441 -0
  276. package/tests/commands/runtime-activation.test.ts +168 -0
  277. package/tests/commands/runtime-canary.test.ts +369 -0
  278. package/tests/commands/runtime-diagnostics-export.test.ts +170 -0
  279. package/tests/commands/runtime-features.test.ts +114 -0
  280. package/tests/commands/runtime-health-snapshot.test.ts +357 -0
  281. package/tests/commands/runtime-internalization-enqueue-successors.test.ts +803 -0
  282. package/tests/commands/runtime-internalization-integrity-repair.test.ts +169 -0
  283. package/tests/commands/runtime-internalization-integrity.test.ts +102 -0
  284. package/tests/commands/runtime-internalization-queue.test.ts +252 -0
  285. package/tests/commands/runtime-internalization-run-once.test.ts +1318 -0
  286. package/tests/commands/runtime-internalization-wake-once.test.ts +170 -0
  287. package/tests/commands/runtime-internalization.test.ts +52 -0
  288. package/tests/commands/runtime-pain-flood-simulation.test.ts +418 -0
  289. package/tests/commands/runtime-pruning.test.ts +693 -0
  290. package/tests/commands/runtime-recovery.test.ts +96 -0
  291. package/tests/commands/runtime-synthetic-baseline.test.ts +249 -0
  292. package/tests/commands/runtime-uat.test.ts +397 -0
  293. package/tests/commands/runtime.test.ts +262 -0
  294. package/tests/commands/trace.test.ts +314 -0
  295. package/tests/e2e/candidate-intake-e2e.test.ts +316 -0
  296. package/tests/services/feature-flag-loader.test.ts +207 -0
  297. package/tests/services/proven-channel-baseline-runner.test.ts +30 -0
  298. package/tsconfig.json +26 -0
@@ -0,0 +1,107 @@
1
+ import * as path from 'path';
2
+ import { createRecoverySweepService } from '@principles/core/runtime-v2';
3
+ import { resolveWorkspaceDir } from '../resolve-workspace.js';
4
+ import { createRemediationResult, remediationAction } from './remediation-output.js';
5
+ import type { RemediationResult } from './remediation-output.js';
6
+
7
+ interface RecoverySweepOptions {
8
+ workspace?: string;
9
+ dryRun?: boolean;
10
+ confirm?: boolean;
11
+ json?: boolean;
12
+ }
13
+
14
+ function formatTextOutput(output: RemediationResult): string {
15
+ const lines: string[] = [];
16
+
17
+ lines.push(`Recovery Sweep (${output.mode})`);
18
+ lines.push(` status: ${output.status}`);
19
+ lines.push(` safeToConfirm: ${output.safeToConfirm}`);
20
+ lines.push(` repairedCount: ${output.repairedCount}`);
21
+ lines.push(` skippedCount: ${output.skippedCount}`);
22
+
23
+ if (output.warnings.length > 0) {
24
+ lines.push(` warnings:`);
25
+ for (const warning of output.warnings) lines.push(` - ${warning}`);
26
+ }
27
+ for (const action of output.actions.slice(0, 5)) {
28
+ lines.push(` ${action.targetId}: ${action.previousState ?? '(unknown)'} → ${action.nextState ?? '(unknown)'}`);
29
+ lines.push(` action: ${action.action}`);
30
+ lines.push(` reason: ${action.reason}`);
31
+ }
32
+ if (output.mode === 'dry_run' && output.actions.length > 0) {
33
+ lines.push(` (use --confirm to recover expired leases)`);
34
+ } else if (output.actions.length === 0) {
35
+ lines.push(` no expired leases found`);
36
+ }
37
+
38
+ return lines.join('\n');
39
+ }
40
+
41
+ export async function handleRuntimeRecoverySweep(opts: RecoverySweepOptions): Promise<void> {
42
+ if (opts.dryRun && opts.confirm) {
43
+ console.error('Error: --dry-run and --confirm are mutually exclusive');
44
+ process.exitCode = 1;
45
+ return;
46
+ }
47
+ const workspaceDir = opts.workspace ? path.resolve(opts.workspace) : resolveWorkspaceDir();
48
+ const isConfirm = opts.confirm ?? false;
49
+ const isDryRun = !isConfirm;
50
+
51
+ const { service, close } = await createRecoverySweepService({ workspaceDir });
52
+
53
+ try {
54
+ const expiredLeaseTaskIds = await service.detectExpiredLeases();
55
+
56
+ const actions = expiredLeaseTaskIds.map((taskId) => remediationAction({
57
+ action: 'recover_expired_lease',
58
+ targetId: taskId,
59
+ previousState: 'leased',
60
+ nextState: 'retry_wait',
61
+ reason: 'Task lease is expired and can be recovered by the operator sweep.',
62
+ }));
63
+ const warnings: string[] = [];
64
+ let repairedCount = 0;
65
+ let failedCount = 0;
66
+
67
+ if (isConfirm && expiredLeaseTaskIds.length > 0) {
68
+ for (const taskId of expiredLeaseTaskIds) {
69
+ try {
70
+ const result = await service.recoverTask(taskId);
71
+ if (result) {
72
+ repairedCount++;
73
+ const action = actions.find((a) => a.targetId === taskId);
74
+ if (action) {
75
+ action.previousState = result.previousStatus;
76
+ action.nextState = result.newStatus;
77
+ }
78
+ }
79
+ } catch (err) {
80
+ failedCount++;
81
+ warnings.push(`${taskId}: ${err instanceof Error ? err.message : String(err)}`);
82
+ }
83
+ }
84
+ }
85
+ const output = createRemediationResult({
86
+ mode: isDryRun ? 'dry_run' : 'confirm',
87
+ repairedCount,
88
+ skippedCount: failedCount,
89
+ actions,
90
+ warnings,
91
+ status: failedCount > 0 && repairedCount === 0 ? 'error' : undefined,
92
+ safeToConfirm: isDryRun && expiredLeaseTaskIds.length > 0,
93
+ });
94
+
95
+ if (opts.json) {
96
+ console.log(JSON.stringify(output, null, 2));
97
+ } else {
98
+ console.log(formatTextOutput(output));
99
+ }
100
+
101
+ if (expiredLeaseTaskIds.length > 0 && isDryRun) {
102
+ process.exitCode = 1;
103
+ }
104
+ } finally {
105
+ await close();
106
+ }
107
+ }
@@ -0,0 +1,70 @@
1
+ import * as path from 'path';
2
+ import * as os from 'os';
3
+ import * as fs from 'fs';
4
+ import { runSyntheticBaseline } from '../services/synthetic-baseline-runner.js';
5
+ import type { SyntheticBaselineSummary } from '@principles/core/runtime-v2';
6
+
7
+ interface CliSyntheticBaselineOptions {
8
+ workspace?: string;
9
+ json?: boolean;
10
+ }
11
+
12
+ function formatTextOutput(summary: SyntheticBaselineSummary): string {
13
+ const lines: string[] = [];
14
+ const icon = summary.status === 'passed' ? '✓' : summary.status === 'degraded' ? '⚠' : '✗';
15
+
16
+ lines.push('PD Synthetic Workload Baseline');
17
+ lines.push(`generatedAt: ${summary.generatedAt}`);
18
+ lines.push(`workspaceMode: ${summary.workspaceMode}`);
19
+ lines.push(`OVERALL: ${icon} ${summary.status.toUpperCase()}`);
20
+ lines.push('');
21
+
22
+ for (const stage of summary.stages) {
23
+ const stageIcon = stage.status === 'passed' ? '✓' : stage.status === 'skipped' ? '○' : '✗';
24
+ lines.push(` ${stageIcon} ${stage.name}: ${stage.status}`);
25
+ if (stage.reason) {
26
+ lines.push(` reason: ${stage.reason}`);
27
+ }
28
+ }
29
+
30
+ if (summary.recommendedNextIssue) {
31
+ lines.push('');
32
+ lines.push(`Recommended next issue: ${summary.recommendedNextIssue}`);
33
+ }
34
+
35
+ return lines.join('\n');
36
+ }
37
+
38
+ export async function handleRuntimeSyntheticBaseline(opts: CliSyntheticBaselineOptions): Promise<void> {
39
+ const workspaceDir = opts.workspace
40
+ ? path.resolve(opts.workspace)
41
+ : fs.mkdtempSync(path.join(os.tmpdir(), 'pd-synth-baseline-'));
42
+ const workspaceMode: 'temp' | 'explicit_workspace' = opts.workspace ? 'explicit_workspace' : 'temp';
43
+
44
+ try {
45
+ const summary = await runSyntheticBaseline({
46
+ workspaceDir,
47
+ workspaceMode,
48
+ });
49
+
50
+ if (opts.json) {
51
+ console.log(JSON.stringify(summary, null, 2));
52
+ } else {
53
+ console.log(formatTextOutput(summary));
54
+ }
55
+
56
+ if (summary.status !== 'passed') {
57
+ console.error('');
58
+ console.error(`FAIL: status=${summary.status}`);
59
+ process.exitCode = 1;
60
+ }
61
+ } finally {
62
+ if (workspaceMode === 'temp') {
63
+ try {
64
+ fs.rmSync(workspaceDir, { recursive: true, force: true });
65
+ } catch {
66
+ // ignore cleanup errors on Windows
67
+ }
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,339 @@
1
+ /**
2
+ * pd runtime uat command — Runtime V2 chain UAT baseline runner.
3
+ *
4
+ * Usage:
5
+ * pd runtime uat --workspace <path> --count <N> [--min-success-rate <rate>] [--json]
6
+ *
7
+ * Runs N consecutive pd pain record iterations and verifies:
8
+ * - Every run produces painId, taskId, runId, artifactId, candidateIds, ledgerEntryIds
9
+ * - Candidate audit returns "ok" after each run
10
+ * - Consistency and latency statistics with threshold-based exit
11
+ *
12
+ * Requirements:
13
+ * - MINIMAX_CN_API_KEY environment variable
14
+ * - Built pd-cli (node packages/pd-cli/dist/index.js must be resolvable)
15
+ */
16
+ import { execFileSync } from 'child_process';
17
+ import { fileURLToPath } from 'node:url';
18
+ import * as path from 'path';
19
+ import * as fs from 'fs';
20
+
21
+ // ── Types ────────────────────────────────────────────────────────────────────
22
+
23
+ interface UatOptions {
24
+ workspace?: string;
25
+ count?: number;
26
+ minSuccessRate?: number;
27
+ json?: boolean;
28
+ }
29
+
30
+ interface PainRecordResult {
31
+ iteration: number;
32
+ painId?: string;
33
+ taskId?: string;
34
+ runId?: string;
35
+ artifactId?: string;
36
+ candidateIds: string[];
37
+ ledgerEntryIds: string[];
38
+ status: string;
39
+ failureCategory?: string;
40
+ latencyMs?: number;
41
+ wallTimeMs: number;
42
+ auditStatus: string;
43
+ error?: string;
44
+ rawOutput?: string;
45
+ }
46
+
47
+ interface UatSummary {
48
+ generatedAt: string;
49
+ workspace: string;
50
+ totalRuns: number;
51
+ successful: number;
52
+ failed: number;
53
+ successRate: number;
54
+ p50LatencyMs?: number;
55
+ p95LatencyMs?: number;
56
+ failuresByCategory: Record<string, number>;
57
+ ledgerConsistencyOk: boolean;
58
+ allHaveCandidates: boolean;
59
+ allHaveLedger: boolean;
60
+ perRun: PainRecordResult[];
61
+ }
62
+
63
+ // ── Argument parsing ─────────────────────────────────────────────────────────
64
+
65
+ export function parseUatArgs(argv: string[]): UatOptions {
66
+ const args: UatOptions = { count: 5 };
67
+ for (let i = 0; i < argv.length; i++) {
68
+ if (argv[i] === '--workspace' || argv[i] === '-w') {
69
+ args.workspace = argv[++i] ?? '';
70
+ } else if (argv[i] === '--count') {
71
+ const n = parseInt(argv[++i] ?? '5', 10);
72
+ args.count = isNaN(n) ? 5 : n;
73
+ } else if (argv[i] === '--min-success-rate') {
74
+ const rate = parseFloat(argv[++i] ?? '1.0');
75
+ args.minSuccessRate = isNaN(rate) ? 1.0 : rate;
76
+ }
77
+ }
78
+ return args;
79
+ }
80
+
81
+ // ── Helpers ─────────────────────────────────────────────────────────────────
82
+
83
+ export function parseJsonOutput(output: string): unknown {
84
+ try {
85
+ return JSON.parse(output.trim());
86
+ } catch {
87
+ // Find the last line that looks like a JSON object
88
+ const lines = output.trim().split('\n');
89
+ for (let i = lines.length - 1; i >= 0; i--) {
90
+ const line = lines[i].trim();
91
+ if (line.startsWith('{')) {
92
+ try {
93
+ return JSON.parse(line);
94
+ } catch {
95
+ // keep searching
96
+ }
97
+ }
98
+ }
99
+ throw new Error(`No JSON found in output: ${output.slice(0, 200)}`);
100
+ }
101
+ }
102
+
103
+ export function percentile(arr: number[], p: number): number | undefined {
104
+ if (arr.length === 0) return undefined;
105
+ const sorted = [...arr].sort((a, b) => a - b);
106
+ const idx = Math.ceil(sorted.length * p / 100) - 1;
107
+ return sorted[Math.max(0, idx)];
108
+ }
109
+
110
+ // ── Cross-platform pd CLI invocation ─────────────────────────────────────────
111
+
112
+ function findPdCliPath(): string {
113
+ // Resolve path relative to this file's location in dist/commands/
114
+ // dist/commands/runtime-uat.js → dist/index.js
115
+ // Use import.meta.url (ESM) instead of __filename (CJS)
116
+ const currentFile = fileURLToPath(import.meta.url);
117
+ const distDir = path.dirname(currentFile);
118
+ const cliPath = path.resolve(distDir, '..', 'index.js');
119
+ if (fs.existsSync(cliPath)) return cliPath;
120
+ throw new Error(`pd CLI not found at ${cliPath} — run: npm run build --workspace=@principles/pd-cli`);
121
+ }
122
+
123
+ function pd(args: string[], workspace: string, timeoutMs = 300_000): string {
124
+ // Arguments: subcommand args first, then --workspace and path at the end
125
+ // Correct: node pd pain record ... --workspace <path>
126
+ const fullArgs = [...args, '--workspace', workspace];
127
+ const cliPath = findPdCliPath();
128
+ try {
129
+ return execFileSync(process.execPath, [cliPath, ...fullArgs], {
130
+ encoding: 'utf8',
131
+ timeout: timeoutMs,
132
+ env: { ...process.env },
133
+ stdio: ['pipe', 'pipe', 'pipe'],
134
+ });
135
+ } catch (err: unknown) {
136
+ if (err instanceof Error && 'code' in err && (err as { code: string }).code === 'ENOENT') {
137
+ throw new Error(`pd CLI not found at ${cliPath} — run: npm run build --workspace=@principles/pd-cli`, { cause: err });
138
+ }
139
+ if (err && typeof err === 'object' && 'stdout' in err) {
140
+ return String((err).stdout);
141
+ }
142
+ throw err;
143
+ }
144
+ }
145
+
146
+ interface IterationConfig {
147
+ iteration: number;
148
+ reason: string;
149
+ workspace: string;
150
+ timeoutMs?: number;
151
+ }
152
+
153
+ export function runUatIteration(config: IterationConfig): PainRecordResult {
154
+ const { iteration, reason, workspace, timeoutMs = 300_000 } = config;
155
+ const iterStart = Date.now();
156
+
157
+ // eslint-disable-next-line @typescript-eslint/init-declarations
158
+ let recordOutput: string;
159
+ try {
160
+ recordOutput = pd(['pain', 'record', '--reason', reason, '--score', '85', '--source', 'manual', '--json'], workspace, timeoutMs);
161
+ } catch (err: unknown) {
162
+ const cause = err instanceof Error ? err.message : String(err);
163
+ return {
164
+ iteration,
165
+ status: 'script_error',
166
+ failureCategory: 'runtime_unavailable',
167
+ error: cause,
168
+ wallTimeMs: Date.now() - iterStart,
169
+ auditStatus: 'unknown',
170
+ candidateIds: [],
171
+ ledgerEntryIds: [],
172
+ };
173
+ }
174
+
175
+ const wallTimeMs = Date.now() - iterStart;
176
+ // eslint-disable-next-line @typescript-eslint/init-declarations
177
+ let parsed: Record<string, unknown>;
178
+ try {
179
+ parsed = parseJsonOutput(recordOutput) as Record<string, unknown>;
180
+ } catch {
181
+ return {
182
+ iteration,
183
+ status: 'parse_error',
184
+ failureCategory: 'output_invalid',
185
+ rawOutput: recordOutput.slice(0, 500),
186
+ wallTimeMs,
187
+ auditStatus: 'unknown',
188
+ candidateIds: [],
189
+ ledgerEntryIds: [],
190
+ };
191
+ }
192
+
193
+ // eslint-disable-next-line @typescript-eslint/init-declarations
194
+ let auditStatus: string;
195
+ try {
196
+ const auditOut = pd(['candidate', 'audit', '--json'], workspace, 30_000);
197
+ const audit = parseJsonOutput(auditOut) as { status?: string };
198
+ auditStatus = audit.status ?? 'unknown';
199
+ } catch {
200
+ auditStatus = 'audit_error';
201
+ }
202
+
203
+ return {
204
+ iteration,
205
+ painId: parsed.painId as string | undefined,
206
+ taskId: parsed.taskId as string | undefined,
207
+ runId: parsed.runId as string | undefined,
208
+ artifactId: parsed.artifactId as string | undefined,
209
+ candidateIds: (parsed.candidateIds as string[]) ?? [],
210
+ ledgerEntryIds: (parsed.ledgerEntryIds as string[]) ?? [],
211
+ status: parsed.status as string ?? 'unknown',
212
+ failureCategory: parsed.failureCategory as string | undefined,
213
+ latencyMs: parsed.latencyMs as number | undefined,
214
+ wallTimeMs,
215
+ auditStatus,
216
+ };
217
+ }
218
+
219
+ export function computeUatSummary(results: PainRecordResult[], workspace: string): UatSummary {
220
+ const succeeded = results.filter(r => r.status === 'succeeded');
221
+ const failed = results.filter(r => r.status === 'failed' || r.status === 'script_error' || r.status === 'parse_error');
222
+ const successRate = results.length > 0 ? succeeded.length / results.length : 0;
223
+
224
+ const latencies = results
225
+ .map(r => r.wallTimeMs)
226
+ .filter(ms => typeof ms === 'number' && ms > 0);
227
+
228
+ const failuresByCategory: Record<string, number> = {};
229
+ for (const r of results) {
230
+ const cat = r.failureCategory ?? (r.status !== 'succeeded' ? 'unknown' : null);
231
+ if (cat) {
232
+ failuresByCategory[cat] = (failuresByCategory[cat] ?? 0) + 1;
233
+ }
234
+ }
235
+
236
+ const auditsOk = results.filter(r => r.auditStatus === 'ok').length;
237
+ const ledgerConsistencyOk = auditsOk === results.length;
238
+ const allHaveCandidates = results.every(r => r.candidateIds && r.candidateIds.length > 0);
239
+ const allHaveLedger = results.every(r => r.ledgerEntryIds && r.ledgerEntryIds.length > 0);
240
+
241
+ return {
242
+ generatedAt: new Date().toISOString(),
243
+ workspace,
244
+ totalRuns: results.length,
245
+ successful: succeeded.length,
246
+ failed: failed.length,
247
+ successRate: Number(successRate.toFixed(2)),
248
+ p50LatencyMs: percentile(latencies, 50),
249
+ p95LatencyMs: percentile(latencies, 95),
250
+ failuresByCategory,
251
+ ledgerConsistencyOk,
252
+ allHaveCandidates,
253
+ allHaveLedger,
254
+ perRun: results,
255
+ };
256
+ }
257
+
258
+ export function shouldExitWithError(summary: UatSummary, minSuccessRate = 1.0): boolean {
259
+ if (summary.successRate < minSuccessRate) return true;
260
+ if (!summary.ledgerConsistencyOk) return true;
261
+ if (!summary.allHaveCandidates) return true;
262
+ if (!summary.allHaveLedger) return true;
263
+ return false;
264
+ }
265
+
266
+ // ── CLI handler ───────────────────────────────────────────────────────────────
267
+
268
+ export async function handleRuntimeUat(opts: UatOptions): Promise<void> {
269
+ const workspace = opts.workspace
270
+ ? path.resolve(opts.workspace)
271
+ : '';
272
+
273
+ const count = Math.max(1, Math.min(opts.count ?? 5, 50));
274
+ const minSuccessRate = opts.minSuccessRate ?? 1.0;
275
+
276
+ if (!workspace) {
277
+ console.error('Error: --workspace <path> is required');
278
+ process.exit(1);
279
+ }
280
+
281
+ console.error(`[${new Date().toISOString()}] Runtime V2 Chain UAT — workspace: ${workspace}, count: ${count}`);
282
+
283
+ // Check MINIMAX_CN_API_KEY
284
+ if (!process.env.MINIMAX_CN_API_KEY) {
285
+ console.error('Error: MINIMAX_CN_API_KEY environment variable not set');
286
+ process.exit(1);
287
+ }
288
+
289
+ const results: PainRecordResult[] = [];
290
+
291
+ for (let i = 0; i < count; i++) {
292
+ const reason = `UAT chain test ${i + 1}/${count} — ${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
293
+ const result = runUatIteration({ iteration: i + 1, reason, workspace });
294
+ results.push(result);
295
+
296
+ const icon = result.status === 'succeeded' ? '✓' : '✗';
297
+ console.error(
298
+ ` ${icon} iter=${result.iteration} status=${result.status} ` +
299
+ `candidates=${result.candidateIds.length} ` +
300
+ `ledger=${result.ledgerEntryIds.length} ` +
301
+ `wallTime=${result.wallTimeMs}ms` +
302
+ (result.failureCategory ? ` category=${result.failureCategory}` : '')
303
+ );
304
+ }
305
+
306
+ const summary = computeUatSummary(results, workspace);
307
+
308
+ if (opts.json) {
309
+ console.log(JSON.stringify(summary, null, 2));
310
+ } else {
311
+ console.error('');
312
+ console.error('═'.repeat(60));
313
+ console.error('UAT SUMMARY');
314
+ console.error('═'.repeat(60));
315
+ console.error(` totalRuns: ${summary.totalRuns}`);
316
+ console.error(` successful: ${summary.successful}`);
317
+ console.error(` failed: ${summary.failed}`);
318
+ console.error(` successRate: ${summary.successRate}`);
319
+ if (summary.p50LatencyMs !== undefined) console.error(` p50LatencyMs: ${summary.p50LatencyMs}`);
320
+ if (summary.p95LatencyMs !== undefined) console.error(` p95LatencyMs: ${summary.p95LatencyMs}`);
321
+ console.error(` ledgerConsistencyOk:${summary.ledgerConsistencyOk}`);
322
+ console.error(` allHaveCandidates: ${summary.allHaveCandidates}`);
323
+ console.error(` allHaveLedger: ${summary.allHaveLedger}`);
324
+ const cats = Object.entries(summary.failuresByCategory);
325
+ if (cats.length > 0) {
326
+ console.error(` failuresByCategory: ${JSON.stringify(summary.failuresByCategory)}`);
327
+ }
328
+ }
329
+
330
+ if (shouldExitWithError(summary, minSuccessRate)) {
331
+ console.error('');
332
+ console.error(`FAIL: successRate=${summary.successRate} (threshold: ${minSuccessRate}) ` +
333
+ `ledger=${summary.ledgerConsistencyOk} candidates=${summary.allHaveCandidates} ledger=${summary.allHaveLedger}`);
334
+ process.exit(1);
335
+ }
336
+
337
+ console.error('');
338
+ console.error('✓ ALL CHECKS PASSED');
339
+ }