@principles/pd-cli 1.152.11 → 1.152.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/candidate.d.ts +3 -3
- package/dist/commands/candidate.d.ts.map +1 -1
- package/dist/commands/candidate.js +9 -21
- package/dist/commands/candidate.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/dist/services/rulehost-pipeline-runner.js +4 -17
- package/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/package.json +13 -7
- package/dist/commands/__tests__/intent-flag-wiring.test.d.ts +0 -9
- package/dist/commands/__tests__/intent-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/intent-flag-wiring.test.js +0 -237
- package/dist/commands/__tests__/intent-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/legacy-cleanup.test.d.ts +0 -18
- package/dist/commands/__tests__/legacy-cleanup.test.d.ts.map +0 -1
- package/dist/commands/__tests__/legacy-cleanup.test.js +0 -467
- package/dist/commands/__tests__/legacy-cleanup.test.js.map +0 -1
- package/dist/commands/__tests__/mvp-smoke.test.d.ts +0 -15
- package/dist/commands/__tests__/mvp-smoke.test.d.ts.map +0 -1
- package/dist/commands/__tests__/mvp-smoke.test.js +0 -262
- package/dist/commands/__tests__/mvp-smoke.test.js.map +0 -1
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +0 -2
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +0 -1
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +0 -60
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +0 -1
- package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts +0 -21
- package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js +0 -186
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/rulecode-handler.test.d.ts +0 -16
- package/dist/commands/__tests__/rulecode-handler.test.d.ts.map +0 -1
- package/dist/commands/__tests__/rulecode-handler.test.js +0 -285
- package/dist/commands/__tests__/rulecode-handler.test.js.map +0 -1
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts +0 -24
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +0 -232
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts +0 -15
- package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts.map +0 -1
- package/dist/commands/__tests__/runtime-activation-nextaction.test.js +0 -73
- package/dist/commands/__tests__/runtime-activation-nextaction.test.js.map +0 -1
- package/dist/commands/__tests__/runtime-probe-config.test.d.ts +0 -20
- package/dist/commands/__tests__/runtime-probe-config.test.d.ts.map +0 -1
- package/dist/commands/__tests__/runtime-probe-config.test.js +0 -389
- package/dist/commands/__tests__/runtime-probe-config.test.js.map +0 -1
- package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts +0 -9
- package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/telemetry-flag-wiring.test.js +0 -76
- package/dist/commands/__tests__/telemetry-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/version.test.d.ts +0 -2
- package/dist/commands/__tests__/version.test.d.ts.map +0 -1
- package/dist/commands/__tests__/version.test.js +0 -238
- package/dist/commands/__tests__/version.test.js.map +0 -1
- package/dist/commands/runtime-uat.guard.test.d.ts +0 -2
- package/dist/commands/runtime-uat.guard.test.d.ts.map +0 -1
- package/dist/commands/runtime-uat.guard.test.js +0 -153
- package/dist/commands/runtime-uat.guard.test.js.map +0 -1
- package/dist/services/__tests__/cli-output.test.d.ts +0 -18
- package/dist/services/__tests__/cli-output.test.d.ts.map +0 -1
- package/dist/services/__tests__/cli-output.test.js +0 -103
- package/dist/services/__tests__/cli-output.test.js.map +0 -1
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +0 -2
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +0 -1
- package/dist/services/__tests__/evaluator-runner-deps.test.js +0 -343
- package/dist/services/__tests__/evaluator-runner-deps.test.js.map +0 -1
- package/dist/services/__tests__/rulehost-readiness.test.d.ts +0 -2
- package/dist/services/__tests__/rulehost-readiness.test.d.ts.map +0 -1
- package/dist/services/__tests__/rulehost-readiness.test.js +0 -311
- package/dist/services/__tests__/rulehost-readiness.test.js.map +0 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts +0 -18
- package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts.map +0 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.js +0 -673
- package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +0 -1
- package/dist/utils/production-workspace-guard.test.d.ts +0 -2
- package/dist/utils/production-workspace-guard.test.d.ts.map +0 -1
- package/dist/utils/production-workspace-guard.test.js +0 -137
- package/dist/utils/production-workspace-guard.test.js.map +0 -1
- package/scripts/llm-dogfood.ts +0 -456
- package/scripts/migrate-illegal-expected-decision.ts +0 -252
- package/src/commands/__tests__/intent-flag-wiring.test.ts +0 -282
- package/src/commands/__tests__/legacy-cleanup.test.ts +0 -602
- package/src/commands/__tests__/mvp-smoke.test.ts +0 -300
- package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +0 -73
- package/src/commands/__tests__/rulecode-flag-wiring.test.ts +0 -237
- package/src/commands/__tests__/rulecode-handler.test.ts +0 -369
- package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +0 -292
- package/src/commands/__tests__/runtime-activation-nextaction.test.ts +0 -81
- package/src/commands/__tests__/runtime-probe-config.test.ts +0 -432
- package/src/commands/__tests__/telemetry-flag-wiring.test.ts +0 -84
- package/src/commands/__tests__/version.test.ts +0 -251
- package/src/commands/admission-gate.ts +0 -30
- package/src/commands/artifact.ts +0 -82
- package/src/commands/build-trajectory-evidence.ts +0 -298
- package/src/commands/candidate.ts +0 -1260
- package/src/commands/codex-ingest-catchup.ts +0 -114
- package/src/commands/codex-ingest-quarantine.ts +0 -127
- package/src/commands/codex-reconcile.ts +0 -84
- package/src/commands/codex-setup.ts +0 -476
- package/src/commands/codex-worker.ts +0 -185
- package/src/commands/command-helpers.ts +0 -24
- package/src/commands/config-doctor.ts +0 -193
- package/src/commands/console.ts +0 -797
- package/src/commands/context.ts +0 -72
- package/src/commands/demo-story-a.ts +0 -241
- package/src/commands/diagnose.ts +0 -756
- package/src/commands/errors-list.ts +0 -441
- package/src/commands/evolution-tasks-list.ts +0 -48
- package/src/commands/evolution-tasks-show.ts +0 -62
- package/src/commands/flow.ts +0 -60
- package/src/commands/health-codex.ts +0 -576
- package/src/commands/health.ts +0 -315
- package/src/commands/history.ts +0 -63
- package/src/commands/intent.ts +0 -457
- package/src/commands/legacy-cleanup.ts +0 -531
- package/src/commands/legacy-import.ts +0 -104
- package/src/commands/mvp-smoke.ts +0 -160
- package/src/commands/pain-evidence.ts +0 -226
- package/src/commands/pain-list.ts +0 -239
- package/src/commands/pain-record.ts +0 -612
- package/src/commands/pain-retry.ts +0 -916
- package/src/commands/principles-stats.ts +0 -700
- package/src/commands/proven-channel-baseline.ts +0 -115
- package/src/commands/quality-scorecard.ts +0 -272
- package/src/commands/remediation-output.ts +0 -66
- package/src/commands/rulecode.ts +0 -465
- package/src/commands/run.ts +0 -89
- package/src/commands/runtime-activation.ts +0 -1629
- package/src/commands/runtime-artifact-repair.ts +0 -524
- package/src/commands/runtime-canary.ts +0 -395
- package/src/commands/runtime-compatibility-scan.ts +0 -105
- package/src/commands/runtime-diagnostics-export.ts +0 -229
- package/src/commands/runtime-features.ts +0 -153
- package/src/commands/runtime-gfi-snapshot.ts +0 -135
- package/src/commands/runtime-health-snapshot.ts +0 -108
- package/src/commands/runtime-init.ts +0 -429
- package/src/commands/runtime-internalization-enqueue-successors.ts +0 -530
- package/src/commands/runtime-internalization-integrity-repair.ts +0 -79
- package/src/commands/runtime-internalization-integrity.ts +0 -107
- package/src/commands/runtime-internalization-queue.ts +0 -135
- package/src/commands/runtime-internalization-retry.ts +0 -191
- package/src/commands/runtime-internalization-run-once.ts +0 -791
- package/src/commands/runtime-internalization-run-rulehost.ts +0 -749
- package/src/commands/runtime-internalization-wake-once.ts +0 -87
- package/src/commands/runtime-pain-flood-simulation.ts +0 -121
- package/src/commands/runtime-pruning.ts +0 -438
- package/src/commands/runtime-recovery-failed-tasks.ts +0 -258
- package/src/commands/runtime-recovery.ts +0 -109
- package/src/commands/runtime-synthetic-baseline.ts +0 -70
- package/src/commands/runtime-uat.guard.test.ts +0 -175
- package/src/commands/runtime-uat.ts +0 -380
- package/src/commands/runtime.ts +0 -473
- package/src/commands/samples-list.ts +0 -39
- package/src/commands/samples-review.ts +0 -36
- package/src/commands/task.ts +0 -271
- package/src/commands/telemetry.ts +0 -349
- package/src/commands/trace.ts +0 -174
- package/src/commands/trajectory-db-unavailable.ts +0 -19
- package/src/commands/trajectory.ts +0 -64
- package/src/commands/version.ts +0 -49
- package/src/config-reader.ts +0 -122
- package/src/index.ts +0 -1276
- package/src/legacy/legacy-import.ts +0 -179
- package/src/legacy/session-history-import.ts +0 -203
- package/src/resolve-workspace.ts +0 -106
- package/src/services/__tests__/cli-output.test.ts +0 -130
- package/src/services/__tests__/evaluator-runner-deps.test.ts +0 -382
- package/src/services/__tests__/rulehost-readiness.test.ts +0 -363
- package/src/services/__tests__/runtime-adapter-resolver.test.ts +0 -793
- package/src/services/cli-output.ts +0 -95
- package/src/services/config-doctor.ts +0 -697
- package/src/services/console-launcher.ts +0 -537
- package/src/services/demo-rule-compiler.ts +0 -40
- package/src/services/demo-story-a-runner.ts +0 -471
- package/src/services/mainline-snapshot-assembler.ts +0 -579
- package/src/services/pain-flood-simulation-runner.ts +0 -462
- package/src/services/pd-config-loader.ts +0 -433
- package/src/services/proven-channel-baseline-runner.ts +0 -157
- package/src/services/quality-scorecard/data-extractor.ts +0 -150
- package/src/services/quality-scorecard/local-evaluator.ts +0 -142
- package/src/services/quality-scorecard/strong-model-gate.ts +0 -202
- package/src/services/resolve-runtime-from-pd-config.ts +0 -230
- package/src/services/rulehost-pipeline-runner.ts +0 -1216
- package/src/services/rulehost-readiness.ts +0 -326
- package/src/services/runtime-adapter-resolver.ts +0 -364
- package/src/services/synthetic-baseline-runner.ts +0 -400
- package/src/services/version-report.ts +0 -253
- package/src/services/workspace-tool-semantics.ts +0 -26
- package/src/utils/path-security.ts +0 -96
- package/src/utils/production-workspace-guard.test.ts +0 -157
- package/src/utils/production-workspace-guard.ts +0 -210
- package/tests/bdd/cli-contract.steps.ts +0 -346
- package/tests/bdd/principles-stats.steps.ts +0 -211
- package/tests/bdd/support/gherkin-loader.ts +0 -147
- package/tests/bdd/support/repo-root.ts +0 -62
- package/tests/bdd/support/vitest-bdd.ts +0 -179
- package/tests/commands/admission-gate-coverage.test.ts +0 -79
- package/tests/commands/build-trajectory-evidence.test.ts +0 -508
- package/tests/commands/candidate-audit-repair.test.ts +0 -421
- package/tests/commands/candidate-intake.test.ts +0 -732
- package/tests/commands/candidate-internalization-backfill.test.ts +0 -548
- package/tests/commands/candidate-internalize-lineage.test.ts +0 -565
- package/tests/commands/candidate-internalize.test.ts +0 -299
- package/tests/commands/candidate-route.test.ts +0 -329
- package/tests/commands/candidate-show.test.ts +0 -95
- package/tests/commands/cli-command-tree.test.ts +0 -144
- package/tests/commands/cli-help-snapshot.test.ts +0 -155
- package/tests/commands/cli-skill-contract.test.ts +0 -121
- package/tests/commands/codex-consent-reversibility.steps.test.ts +0 -299
- package/tests/commands/codex-ingest-quarantine.test.ts +0 -136
- package/tests/commands/codex-setup.test.ts +0 -297
- package/tests/commands/codex-worker-daemon.test.ts +0 -95
- package/tests/commands/codex-worker-registration.test.ts +0 -207
- package/tests/commands/config-doctor.test.ts +0 -336
- package/tests/commands/console-launcher-edge-cases.test.ts +0 -388
- package/tests/commands/console-open.test.ts +0 -1381
- package/tests/commands/context.test.ts +0 -114
- package/tests/commands/demo-story-a.test.ts +0 -318
- package/tests/commands/diagnose.test.ts +0 -1803
- package/tests/commands/errors-list.test.ts +0 -679
- package/tests/commands/evolution-tasks-list.test.ts +0 -41
- package/tests/commands/evolution-tasks-show.test.ts +0 -46
- package/tests/commands/health-codex.test.ts +0 -349
- package/tests/commands/health-host-wiring.test.ts +0 -76
- package/tests/commands/health-reliability.test.ts +0 -99
- package/tests/commands/health.test.ts +0 -413
- package/tests/commands/intent.test.ts +0 -321
- package/tests/commands/legacy-cleanup.test.ts +0 -148
- package/tests/commands/pain-evidence.test.ts +0 -518
- package/tests/commands/pain-list.test.ts +0 -219
- package/tests/commands/pain-record-async.test.ts +0 -335
- package/tests/commands/pain-record-session-parser.test.ts +0 -228
- package/tests/commands/pain-record.test.ts +0 -851
- package/tests/commands/pain-retry.test.ts +0 -1121
- package/tests/commands/plugin-config-resolution-cutover.test.ts +0 -229
- package/tests/commands/pri-393-runtime-config-unification.test.ts +0 -443
- package/tests/commands/principles-stats-wiring.test.ts +0 -135
- package/tests/commands/principles-stats.test.ts +0 -274
- package/tests/commands/product-path-regression.test.ts +0 -86
- package/tests/commands/proven-channel-baseline.test.ts +0 -441
- package/tests/commands/rulecode.test.ts +0 -177
- package/tests/commands/run-rulehost-handler.test.ts +0 -908
- package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +0 -227
- package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +0 -167
- package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +0 -262
- package/tests/commands/runtime-activation-governance-audit.test.ts +0 -95
- package/tests/commands/runtime-activation-list-flag-wiring.test.ts +0 -193
- package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +0 -248
- package/tests/commands/runtime-activation-shadow-telemetry.test.ts +0 -130
- package/tests/commands/runtime-activation.test.ts +0 -2052
- package/tests/commands/runtime-artifact-repair-registration.test.ts +0 -74
- package/tests/commands/runtime-artifact-repair.test.ts +0 -404
- package/tests/commands/runtime-canary.test.ts +0 -430
- package/tests/commands/runtime-compatibility-scan.test.ts +0 -146
- package/tests/commands/runtime-diagnostics-export.test.ts +0 -175
- package/tests/commands/runtime-features.test.ts +0 -249
- package/tests/commands/runtime-health-snapshot.test.ts +0 -358
- package/tests/commands/runtime-init-empty-workspace.test.ts +0 -252
- package/tests/commands/runtime-init.test.ts +0 -420
- package/tests/commands/runtime-internalization-enqueue-successors.test.ts +0 -782
- package/tests/commands/runtime-internalization-integrity-repair.test.ts +0 -239
- package/tests/commands/runtime-internalization-integrity.test.ts +0 -140
- package/tests/commands/runtime-internalization-queue.test.ts +0 -380
- package/tests/commands/runtime-internalization-retry-owner-authority.test.ts +0 -431
- package/tests/commands/runtime-internalization-run-once-evaluator-parity.test.ts +0 -269
- package/tests/commands/runtime-internalization-run-once-language.test.ts +0 -303
- package/tests/commands/runtime-internalization-run-once-real-config.test.ts +0 -237
- package/tests/commands/runtime-internalization-run-once-rollout-parity.test.ts +0 -706
- package/tests/commands/runtime-internalization-run-once.test.ts +0 -1791
- package/tests/commands/runtime-internalization-wake-once.test.ts +0 -171
- package/tests/commands/runtime-internalization.test.ts +0 -53
- package/tests/commands/runtime-pain-flood-simulation.test.ts +0 -418
- package/tests/commands/runtime-pruning.test.ts +0 -694
- package/tests/commands/runtime-recovery-failed-tasks.test.ts +0 -328
- package/tests/commands/runtime-recovery.test.ts +0 -120
- package/tests/commands/runtime-synthetic-baseline.test.ts +0 -249
- package/tests/commands/runtime-uat.test.ts +0 -397
- package/tests/commands/runtime.test.ts +0 -392
- package/tests/commands/samples-list.test.ts +0 -43
- package/tests/commands/samples-review.test.ts +0 -41
- package/tests/commands/task.test.ts +0 -153
- package/tests/commands/telemetry.test.ts +0 -190
- package/tests/commands/trace.test.ts +0 -316
- package/tests/config-reader.test.ts +0 -143
- package/tests/e2e/candidate-intake-e2e.test.ts +0 -338
- package/tests/e2e/cli-full-flow.test.ts +0 -233
- package/tests/e2e/cross-package-acceptance.test.ts +0 -635
- package/tests/helpers/pd-cli-path.ts +0 -5
- package/tests/pd-cli-smoke.test.ts +0 -13
- package/tests/resolve-workspace.test.ts +0 -196
- package/tests/services/config-doctor-signal-health.test.ts +0 -109
- package/tests/services/console-launcher.test.ts +0 -114
- package/tests/services/demo-rule-compiler.test.ts +0 -242
- package/tests/services/mainline-snapshot-assembler.test.ts +0 -572
- package/tests/services/pd-config-loader.test.ts +0 -505
- package/tests/services/proven-channel-baseline-runner.test.ts +0 -30
- package/tests/services/quality-scorecard/strong-model-gate.test.ts +0 -133
- package/tests/services/resolve-runtime-from-pd-config.test.ts +0 -485
- package/tests/services/rulehost-pipeline-e2e.test.ts +0 -538
- package/tests/services/rulehost-pipeline-runner.test.ts +0 -1175
- package/tests/services/workspace-tool-semantics.test.ts +0 -76
- package/tests/utils/path-security.test.ts +0 -180
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -9
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pd mvp smoke — MVP mainline readiness check (PRI-397 / C5).
|
|
3
|
-
*
|
|
4
|
-
* Assembles a MainlineSnapshot from the shared reader, judges it via the
|
|
5
|
-
* pure assertMainlineContract, and outputs a single JSON verdict.
|
|
6
|
-
*
|
|
7
|
-
* Rules (EP-04, EP-02):
|
|
8
|
-
* - Read-only by default. NEVER mutates workspace state.
|
|
9
|
-
* - Uses the shared mainline-snapshot-assembler (no new chain logic).
|
|
10
|
-
* - --json mode: stdout = exactly one parseable JSON object — even on failure.
|
|
11
|
-
* Every degraded/refused path emits a structured `{ok, reason, nextAction}`
|
|
12
|
-
* JSON so CI/script consumers can branch on outcome (EP-04 Rule 6).
|
|
13
|
-
* - Human mode: formatted output to stdout.
|
|
14
|
-
* - Exit code: 0 on overall === 'ok', 1 on overall === 'violation' or failure.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { Command } from 'commander';
|
|
18
|
-
import {
|
|
19
|
-
assembleMainlineSnapshot,
|
|
20
|
-
assertMainlineContract,
|
|
21
|
-
} from '../services/mainline-snapshot-assembler.js';
|
|
22
|
-
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
23
|
-
import { withWorkspaceAndJson } from './command-helpers.js';
|
|
24
|
-
|
|
25
|
-
export interface MvpSmokeOptions {
|
|
26
|
-
workspace?: string;
|
|
27
|
-
json?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Classify a thrown error so the failure JSON can name a real next action.
|
|
32
|
-
* EP-03: never silently swallow; emit a structured reason + actionable next step.
|
|
33
|
-
*/
|
|
34
|
-
function classifySmokeError(err: unknown): { reason: string; nextAction: string } {
|
|
35
|
-
if (err instanceof Error) {
|
|
36
|
-
const msg = err.message;
|
|
37
|
-
const lower = msg.toLowerCase();
|
|
38
|
-
// No .pd/state.db on disk (fresh / reset workspace) is the post-PRI-398
|
|
39
|
-
// expected first failure — name the recovery path explicitly.
|
|
40
|
-
if (
|
|
41
|
-
msg.includes('SQLITE_CANTOPEN') ||
|
|
42
|
-
lower.includes('no such file') ||
|
|
43
|
-
lower.includes('no such table') ||
|
|
44
|
-
lower.includes('cannot open database') ||
|
|
45
|
-
lower.includes('directory does not exist')
|
|
46
|
-
) {
|
|
47
|
-
return {
|
|
48
|
-
reason: `Workspace database is missing or unreadable: ${msg}`,
|
|
49
|
-
nextAction: 'Run "pd runtime internalization integrity-repair --confirm" or restore the workspace; this command requires a bootstrapped .pd/state.db.',
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
if (lower.includes('workspace') && lower.includes('not configured')) {
|
|
53
|
-
return {
|
|
54
|
-
reason: msg,
|
|
55
|
-
nextAction: 'Set --workspace <path>, PD_WORKSPACE_DIR, or add workspace.default to .pd/config.yaml.',
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
reason: msg,
|
|
60
|
-
nextAction: 'Inspect the workspace state and retry. Run "pd config doctor --json" to validate the workspace.',
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
reason: `Unknown failure: ${String(err)}`,
|
|
65
|
-
nextAction: 'Inspect the workspace state and retry. Run "pd config doctor --json" to validate the workspace.',
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export async function handleMvpSmoke(opts: MvpSmokeOptions): Promise<void> {
|
|
70
|
-
// Pass through resolveWorkspaceDir to honor the consistency warning for
|
|
71
|
-
// --workspace flags that disagree with the config default. The resolver
|
|
72
|
-
// returns an absolute, normalized path either way.
|
|
73
|
-
const workspaceDir = resolveWorkspaceDir(opts.workspace);
|
|
74
|
-
|
|
75
|
-
let result;
|
|
76
|
-
try {
|
|
77
|
-
result = await assembleMainlineSnapshot({ workspaceDir });
|
|
78
|
-
} catch (err: unknown) {
|
|
79
|
-
const { reason, nextAction } = classifySmokeError(err);
|
|
80
|
-
if (opts.json) {
|
|
81
|
-
// EP-04 Rule 1 + 6: stdout = exactly one parseable JSON object carrying
|
|
82
|
-
// a structured reason and nextAction, even on the failure path.
|
|
83
|
-
console.log(JSON.stringify({
|
|
84
|
-
ok: false,
|
|
85
|
-
reason,
|
|
86
|
-
nextAction,
|
|
87
|
-
workspace: workspaceDir,
|
|
88
|
-
}, null, 2));
|
|
89
|
-
} else {
|
|
90
|
-
console.error(`MVP smoke failed: ${reason}`);
|
|
91
|
-
console.error(`nextAction: ${nextAction}`);
|
|
92
|
-
}
|
|
93
|
-
process.exit(1);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const verdict = assertMainlineContract(result.snapshot);
|
|
98
|
-
|
|
99
|
-
if (opts.json) {
|
|
100
|
-
console.log(JSON.stringify({
|
|
101
|
-
ok: verdict.overall === 'ok',
|
|
102
|
-
verdict,
|
|
103
|
-
warnings: result.warnings,
|
|
104
|
-
resolvedPainId: result.resolvedPainId,
|
|
105
|
-
}, null, 2));
|
|
106
|
-
} else {
|
|
107
|
-
console.log(`\nMVP Smoke — ${workspaceDir}\n`);
|
|
108
|
-
console.log(` Overall: ${verdict.overall}`);
|
|
109
|
-
console.log(` Pain ID: ${verdict.painId ?? '(none)'}`);
|
|
110
|
-
console.log(` Generated At: ${verdict.generatedAt}\n`);
|
|
111
|
-
|
|
112
|
-
console.log(' Stages:');
|
|
113
|
-
for (const s of verdict.stages) {
|
|
114
|
-
const icon = s.status === 'ok' ? ' OK' : s.status === 'violation' ? ' VIOLATION' : ' SKIP';
|
|
115
|
-
console.log(` [${icon}] ${s.stage}`);
|
|
116
|
-
console.log(` ${s.reason}`);
|
|
117
|
-
if (s.nextAction) {
|
|
118
|
-
console.log(` nextAction: ${s.nextAction}`);
|
|
119
|
-
}
|
|
120
|
-
console.log('');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (result.warnings.length > 0) {
|
|
124
|
-
console.log(' Warnings:');
|
|
125
|
-
for (const w of result.warnings) {
|
|
126
|
-
console.log(` - ${w}`);
|
|
127
|
-
}
|
|
128
|
-
console.log('');
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (verdict.overall === 'violation') {
|
|
133
|
-
process.exit(1);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Register the `pd mvp` parent command and its `smoke` subcommand.
|
|
140
|
-
*
|
|
141
|
-
* Single source of truth for both production (`index.ts`) and parser tests
|
|
142
|
-
* (`mvp-smoke.test.ts`). If a test passes against this function, the same
|
|
143
|
-
* registration runs in production — flag typos surface at parseAsync time.
|
|
144
|
-
*/
|
|
145
|
-
export function registerMvpCommands(program: Command): Command {
|
|
146
|
-
const mvpCmd = program
|
|
147
|
-
.command('mvp', { hidden: true })
|
|
148
|
-
.description('MVP readiness commands');
|
|
149
|
-
|
|
150
|
-
withWorkspaceAndJson(
|
|
151
|
-
mvpCmd
|
|
152
|
-
.command('smoke')
|
|
153
|
-
.description('Check MVP mainline readiness: assemble snapshot → assert contract → structured verdict')
|
|
154
|
-
.action(async (opts: { workspace?: string; json?: boolean }) => {
|
|
155
|
-
await handleMvpSmoke({ workspace: opts.workspace, json: opts.json });
|
|
156
|
-
}),
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
return mvpCmd;
|
|
160
|
-
}
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pd pain evidence command — PEAT-B2
|
|
3
|
-
*
|
|
4
|
-
* Query recent admission/trigger decisions from PD logs.
|
|
5
|
-
* This is a read-only diagnostic command — no side effects.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* pd pain evidence [--workspace <path>] [--limit N] [--json]
|
|
9
|
-
*
|
|
10
|
-
* Shows the most recent TRIGGER_DECISION log entries.
|
|
11
|
-
*
|
|
12
|
-
* Note: TRIGGER_DECISION entries are pain-signal admission gate decisions
|
|
13
|
-
* (Gate B / TriggerController). These are distinct from GFI (General
|
|
14
|
-
* Friction Index) friction events like GFI_SLICE_RESET, which track
|
|
15
|
-
* session-level tool-friction resets and are NOT pain signals. This command
|
|
16
|
-
* does not surface GFI events; use `pd runtime health gfi` for GFI state.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import * as fs from 'fs';
|
|
20
|
-
import * as path from 'path';
|
|
21
|
-
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
22
|
-
import { assertSafeDirectoryRoot, isPathInside } from '../utils/path-security.js';
|
|
23
|
-
|
|
24
|
-
interface EvidenceOptions {
|
|
25
|
-
workspace?: string;
|
|
26
|
-
limit?: number;
|
|
27
|
-
json?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface TriggerDecisionEntry {
|
|
31
|
-
timestamp: string;
|
|
32
|
-
outcome: string;
|
|
33
|
-
sourceKind: string;
|
|
34
|
-
reason: string;
|
|
35
|
-
nextAction: string;
|
|
36
|
-
tool?: string;
|
|
37
|
-
path?: string;
|
|
38
|
-
painId?: string;
|
|
39
|
-
score?: number;
|
|
40
|
-
sessionId?: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get the memory/logs directory for a workspace (canonical root).
|
|
45
|
-
* SystemLogger writes to <workspace>/memory/logs/SYSTEM_YYYY-MM-DD.log.
|
|
46
|
-
* The workspace root is canonicalized once here so downstream containment
|
|
47
|
-
* checks compare canonical paths (relative --workspace inputs work).
|
|
48
|
-
*/
|
|
49
|
-
function getLogDir(workspaceDir: string): string {
|
|
50
|
-
const root = assertSafeDirectoryRoot(workspaceDir, 'workspace path');
|
|
51
|
-
return path.join(root, 'memory', 'logs');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Parse TRIGGER_DECISION entries from a log file.
|
|
56
|
-
* Returns entries in reverse chronological order (newest first).
|
|
57
|
-
*/
|
|
58
|
-
function parseTriggerDecisions(logContent: string): TriggerDecisionEntry[] {
|
|
59
|
-
const entries: TriggerDecisionEntry[] = [];
|
|
60
|
-
const lines = logContent.split('\n');
|
|
61
|
-
|
|
62
|
-
for (const line of lines) {
|
|
63
|
-
if (!line.includes('TRIGGER_DECISION')) continue;
|
|
64
|
-
|
|
65
|
-
// Extract JSON payload from log line
|
|
66
|
-
const jsonStart = line.indexOf('{');
|
|
67
|
-
if (jsonStart === -1) continue;
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const payload = JSON.parse(line.slice(jsonStart));
|
|
71
|
-
// Extract timestamp from log line prefix.
|
|
72
|
-
// SystemLogger produces two formats:
|
|
73
|
-
// - Plain: "2026-06-08 10:16:00 [INFO] ..."
|
|
74
|
-
// - Bracketed ISO: "[2026-06-08T10:16:00.123Z] [INFO] ..."
|
|
75
|
-
const tsMatch = /^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/.exec(line)
|
|
76
|
-
?? /^\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)\]/.exec(line);
|
|
77
|
-
entries.push({
|
|
78
|
-
timestamp: tsMatch?.[1] ?? new Date().toISOString(),
|
|
79
|
-
outcome: typeof payload.outcome === 'string' ? payload.outcome : 'unknown',
|
|
80
|
-
sourceKind: typeof payload.sourceKind === 'string' ? payload.sourceKind : 'unknown',
|
|
81
|
-
reason: typeof payload.reason === 'string' ? payload.reason : '',
|
|
82
|
-
nextAction: typeof payload.nextAction === 'string' ? payload.nextAction : '',
|
|
83
|
-
tool: typeof payload.tool === 'string' ? payload.tool : undefined,
|
|
84
|
-
path: typeof payload.path === 'string' ? payload.path : undefined,
|
|
85
|
-
painId: typeof payload.painId === 'string' ? payload.painId : undefined,
|
|
86
|
-
score: typeof payload.score === 'number' ? payload.score : undefined,
|
|
87
|
-
sessionId: typeof payload.sessionId === 'string' ? payload.sessionId : undefined,
|
|
88
|
-
});
|
|
89
|
-
} catch (e) {
|
|
90
|
-
// Skip malformed entries — always log for operator visibility (EP-03)
|
|
91
|
-
console.error(`WARN: Malformed TRIGGER_DECISION entry: ${String(e).slice(0, 100)}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return entries;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Read recent trigger decisions from memory/logs files.
|
|
100
|
-
* SystemLogger writes date-stamped files: SYSTEM_YYYY-MM-DD.log.
|
|
101
|
-
*/
|
|
102
|
-
function readRecentDecisions(logDir: string, limit: number): TriggerDecisionEntry[] {
|
|
103
|
-
if (!fs.existsSync(logDir)) return [];
|
|
104
|
-
|
|
105
|
-
const logFiles = fs.readdirSync(logDir)
|
|
106
|
-
.filter(f => f.startsWith('SYSTEM_') && f.endsWith('.log'))
|
|
107
|
-
.sort()
|
|
108
|
-
.reverse(); // newest first
|
|
109
|
-
|
|
110
|
-
const allEntries: TriggerDecisionEntry[] = [];
|
|
111
|
-
|
|
112
|
-
for (const logFile of logFiles) {
|
|
113
|
-
if (allEntries.length >= limit) break;
|
|
114
|
-
|
|
115
|
-
// CWE-22: verify the canonical log path stays inside the canonical
|
|
116
|
-
// logDir before reading (canonical-vs-canonical containment, so relative
|
|
117
|
-
// workspace roots work).
|
|
118
|
-
const filePath = path.resolve(logDir, logFile);
|
|
119
|
-
if (!isPathInside(logDir, filePath)) continue;
|
|
120
|
-
try {
|
|
121
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
122
|
-
const entries = parseTriggerDecisions(content);
|
|
123
|
-
// Reverse per-file entries so combined list is newest-first.
|
|
124
|
-
// Log files are already sorted newest-first, but entries within
|
|
125
|
-
// each file are in chronological (oldest-first) order.
|
|
126
|
-
allEntries.push(...entries.reverse());
|
|
127
|
-
} catch (e) {
|
|
128
|
-
// Skip unreadable files — always log for operator visibility (EP-03)
|
|
129
|
-
console.error(`WARN: Could not read log file ${logFile}: ${String(e).slice(0, 100)}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return allEntries.slice(0, limit);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function getOutcomeEmoji(outcome: string): string {
|
|
137
|
-
switch (outcome) {
|
|
138
|
-
case 'diagnosis_created': return '🔧';
|
|
139
|
-
case 'manual_owner_admitted': return '✅';
|
|
140
|
-
case 'evidence_only': return '📋';
|
|
141
|
-
case 'health_only': return '💚';
|
|
142
|
-
case 'cooldown_skipped': return '⏳';
|
|
143
|
-
case 'owner_confirm_required': return '❓';
|
|
144
|
-
case 'refused': return '🚫';
|
|
145
|
-
default: return '❔';
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function truncate(s: string, maxLen: number): string {
|
|
150
|
-
if (s.length <= maxLen) return s;
|
|
151
|
-
return s.slice(0, maxLen - 3) + '...';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export async function handlePainEvidence(opts: EvidenceOptions): Promise<void> {
|
|
155
|
-
const { workspace, limit: rawLimit, json } = opts;
|
|
156
|
-
const workspaceDir = resolveWorkspaceDir(workspace);
|
|
157
|
-
const logDir = getLogDir(workspaceDir);
|
|
158
|
-
|
|
159
|
-
// Validate limit — fail loud for invalid values
|
|
160
|
-
let effectiveLimit = 20;
|
|
161
|
-
if (rawLimit !== undefined) {
|
|
162
|
-
if (!Number.isInteger(rawLimit) || rawLimit < 1 || rawLimit > 10000) {
|
|
163
|
-
const err = {
|
|
164
|
-
status: 'refused',
|
|
165
|
-
reason: `invalid_limit: limit must be an integer between 1 and 10000, got ${rawLimit}`,
|
|
166
|
-
nextAction: 'Pass --limit with a valid integer (1-10000)',
|
|
167
|
-
};
|
|
168
|
-
const { reason, nextAction } = err;
|
|
169
|
-
if (json) {
|
|
170
|
-
console.log(JSON.stringify({ count: 0, error: err }));
|
|
171
|
-
} else {
|
|
172
|
-
console.error('Error: ' + reason);
|
|
173
|
-
console.error('Next: ' + nextAction);
|
|
174
|
-
}
|
|
175
|
-
process.exit(1);
|
|
176
|
-
return; // guard: test stubs of process.exit continue execution
|
|
177
|
-
}
|
|
178
|
-
effectiveLimit = rawLimit;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const decisions = readRecentDecisions(logDir, effectiveLimit);
|
|
182
|
-
|
|
183
|
-
if (json) {
|
|
184
|
-
console.log(JSON.stringify({ count: decisions.length, decisions, searchedPath: path.join(logDir, 'SYSTEM_*.log') }, null, 2));
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (decisions.length === 0) {
|
|
189
|
-
console.log('No trigger decisions found in logs.');
|
|
190
|
-
console.log(`Searched: ${path.join(logDir, 'SYSTEM_*.log')}`);
|
|
191
|
-
// PRI-763: the historic painEvidenceAdmission flag (and its advice) was
|
|
192
|
-
// retired — it never had executable consumers; Gate B records
|
|
193
|
-
// unconditionally when the plugin runs.
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
console.log(`Recent Pain Evidence Admission Decisions (${decisions.length}):`);
|
|
198
|
-
console.log('─'.repeat(80));
|
|
199
|
-
|
|
200
|
-
for (const d of decisions) {
|
|
201
|
-
const outcomeEmoji = getOutcomeEmoji(d.outcome);
|
|
202
|
-
console.log(` ${outcomeEmoji} [${d.timestamp}] ${d.outcome}`);
|
|
203
|
-
console.log(` Source: ${d.sourceKind} | Score: ${d.score ?? 'N/A'}`);
|
|
204
|
-
console.log(` Reason: ${truncate(d.reason, 100)}`);
|
|
205
|
-
if (d.nextAction && d.nextAction !== 'none') {
|
|
206
|
-
console.log(` Next: ${truncate(d.nextAction, 80)}`);
|
|
207
|
-
}
|
|
208
|
-
if (d.tool) console.log(` Tool: ${d.tool} | Path: ${d.path ?? 'N/A'}`);
|
|
209
|
-
console.log();
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Summary
|
|
213
|
-
const byOutcome = new Map<string, number>();
|
|
214
|
-
for (const d of decisions) {
|
|
215
|
-
byOutcome.set(d.outcome, (byOutcome.get(d.outcome) ?? 0) + 1);
|
|
216
|
-
}
|
|
217
|
-
console.log('─'.repeat(80));
|
|
218
|
-
console.log('Summary:');
|
|
219
|
-
for (const [outcome, count] of byOutcome) {
|
|
220
|
-
console.log(` ${getOutcomeEmoji(outcome)} ${outcome}: ${count}`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Export for testing
|
|
225
|
-
// istanbul ignore next
|
|
226
|
-
export { parseTriggerDecisions, getLogDir };
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pd pain list command — PRI-640 (Governance Signal Host Attribution v0.1)
|
|
3
|
-
*
|
|
4
|
-
* Read-only: lists canonical pain_events rows from trajectory.db with their
|
|
5
|
-
* host attribution. `--host` filters by openclaw / codex / unknown.
|
|
6
|
-
* PRI-743: the result (human and --json) also carries `byHost`, the
|
|
7
|
-
* full-table host distribution independent of --host/--limit.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* pd pain list [--workspace <path>] [--limit N] [--host openclaw|codex|unknown] [--json]
|
|
11
|
-
*
|
|
12
|
-
* Host semantics (SPEC §6/§12): host_kind is observability metadata only.
|
|
13
|
-
* NULL (legacy / manual / unprovable) is reported as `unknown` — never guessed.
|
|
14
|
-
* On a pre-PRI-640 database without the host_kind column every row reports
|
|
15
|
-
* `unknown` and a `host_kind_column_missing` warning is emitted (rc-9).
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import * as fs from 'fs';
|
|
19
|
-
import * as path from 'path';
|
|
20
|
-
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
21
|
-
|
|
22
|
-
export type PainListHostFilter = 'openclaw' | 'codex' | 'unknown';
|
|
23
|
-
|
|
24
|
-
export interface PainListOptions {
|
|
25
|
-
workspace?: string;
|
|
26
|
-
limit?: number;
|
|
27
|
-
host?: string;
|
|
28
|
-
json?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface PainListEntry {
|
|
32
|
-
/** Canonical pain identity; `row:<id>` when the legacy row has none. */
|
|
33
|
-
painId: string;
|
|
34
|
-
source: string;
|
|
35
|
-
/** 'openclaw' | 'codex' | 'unknown' — unknown covers NULL/unprovable. */
|
|
36
|
-
host: PainListHostFilter | 'unknown';
|
|
37
|
-
score: number;
|
|
38
|
-
severity: string | null;
|
|
39
|
-
createdAt: string;
|
|
40
|
-
runtimeTaskId: string | null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface PainListResult {
|
|
44
|
-
count: number;
|
|
45
|
-
pains: PainListEntry[];
|
|
46
|
-
workspace: string;
|
|
47
|
-
hostFilter: PainListHostFilter | null;
|
|
48
|
-
/**
|
|
49
|
-
* PRI-743: full-table host distribution (independent of --limit and --host
|
|
50
|
-
* filtering) so operator tooling can group by host without an external
|
|
51
|
-
* script. `null` only on a pre-PRI-640 database without the host_kind
|
|
52
|
-
* column — the distribution is unprovable there, never guessed (rc-3/rc-9;
|
|
53
|
-
* see the accompanying host_kind_column_missing warning).
|
|
54
|
-
*/
|
|
55
|
-
byHost: { openclaw: number; codex: number; unknown: number } | null;
|
|
56
|
-
warnings: string[];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
60
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function ownField(row: unknown, key: string): unknown {
|
|
64
|
-
return isRecord(row) && Object.hasOwn(row, key) ? row[key] : undefined;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Runtime Contract #1/#4: validate each DB row; skip malformed rows loudly. */
|
|
68
|
-
function toPainEntry(row: unknown): PainListEntry | null {
|
|
69
|
-
const id = ownField(row, 'id');
|
|
70
|
-
const source = ownField(row, 'source');
|
|
71
|
-
const score = ownField(row, 'score');
|
|
72
|
-
const createdAt = ownField(row, 'created_at');
|
|
73
|
-
if (typeof id !== 'number' || typeof source !== 'string' || typeof score !== 'number' || typeof createdAt !== 'string') {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
const canonicalPainId = ownField(row, 'canonical_pain_id');
|
|
77
|
-
const hostKind = ownField(row, 'host_kind');
|
|
78
|
-
const severity = ownField(row, 'severity');
|
|
79
|
-
const runtimeTaskId = ownField(row, 'runtime_task_id');
|
|
80
|
-
const host: PainListEntry['host'] =
|
|
81
|
-
hostKind === 'openclaw' || hostKind === 'codex' ? hostKind : 'unknown';
|
|
82
|
-
return {
|
|
83
|
-
painId: typeof canonicalPainId === 'string' && canonicalPainId.length > 0 ? canonicalPainId : `row:${id}`,
|
|
84
|
-
source,
|
|
85
|
-
host,
|
|
86
|
-
score,
|
|
87
|
-
severity: typeof severity === 'string' ? severity : null,
|
|
88
|
-
createdAt,
|
|
89
|
-
runtimeTaskId: typeof runtimeTaskId === 'string' && runtimeTaskId.length > 0 ? runtimeTaskId : null,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export async function listPains(dbPath: string, options: { limit?: number; host?: PainListHostFilter } = {}): Promise<PainListResult> {
|
|
94
|
-
const Database = (await import('better-sqlite3')).default;
|
|
95
|
-
const db = new Database(dbPath, { readonly: true });
|
|
96
|
-
try {
|
|
97
|
-
const warnings: string[] = [];
|
|
98
|
-
const columns: unknown[] = db.prepare('PRAGMA table_info(pain_events)').all();
|
|
99
|
-
const hasHostKindColumn = columns.some((row) => ownField(row, 'name') === 'host_kind');
|
|
100
|
-
if (!hasHostKindColumn) {
|
|
101
|
-
warnings.push('host_kind_column_missing');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// PRI-743: full-table distribution, deliberately NOT narrowed by the
|
|
105
|
-
// --host filter or --limit below — the operator question this answers is
|
|
106
|
-
// "how do pains split across hosts in this workspace", which a filtered
|
|
107
|
-
// count cannot answer. Non-openclaw/codex values (incl. NULL) count as
|
|
108
|
-
// unknown, mirroring toPainEntry's read-side normalization.
|
|
109
|
-
const byHost: PainListResult['byHost'] = hasHostKindColumn
|
|
110
|
-
? { openclaw: 0, codex: 0, unknown: 0 }
|
|
111
|
-
: null;
|
|
112
|
-
if (byHost) {
|
|
113
|
-
const distRows: unknown[] = db
|
|
114
|
-
.prepare("SELECT COALESCE(host_kind, 'unknown') AS host_bucket, COUNT(*) AS n FROM pain_events GROUP BY host_kind")
|
|
115
|
-
.all();
|
|
116
|
-
for (const row of distRows) {
|
|
117
|
-
const bucket = ownField(row, 'host_bucket');
|
|
118
|
-
const n = ownField(row, 'n');
|
|
119
|
-
if ((bucket === 'openclaw' || bucket === 'codex' || bucket === 'unknown') && typeof n === 'number') {
|
|
120
|
-
byHost[bucket] += n;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const params: (string | number)[] = [];
|
|
126
|
-
let query = 'SELECT id, source, score, severity, created_at, canonical_pain_id, runtime_task_id';
|
|
127
|
-
query += hasHostKindColumn ? ', host_kind' : ", NULL AS host_kind";
|
|
128
|
-
query += ' FROM pain_events WHERE 1=1';
|
|
129
|
-
if (options.host === 'unknown') {
|
|
130
|
-
query += hasHostKindColumn ? ' AND host_kind IS NULL' : '';
|
|
131
|
-
} else if (options.host === 'openclaw' || options.host === 'codex') {
|
|
132
|
-
if (!hasHostKindColumn) {
|
|
133
|
-
return { count: 0, pains: [], workspace: path.dirname(path.dirname(dbPath)), hostFilter: options.host, byHost, warnings };
|
|
134
|
-
}
|
|
135
|
-
query += ' AND host_kind = ?';
|
|
136
|
-
params.push(options.host);
|
|
137
|
-
}
|
|
138
|
-
query += ' ORDER BY created_at DESC, id DESC';
|
|
139
|
-
const limit = options.limit ?? 20;
|
|
140
|
-
query += ' LIMIT ?';
|
|
141
|
-
params.push(limit);
|
|
142
|
-
|
|
143
|
-
const rawRows: unknown[] = db.prepare(query).all(...params);
|
|
144
|
-
const skipped = rawRows.length - rawRows.map(toPainEntry).filter((entry) => entry !== null).length;
|
|
145
|
-
if (skipped > 0) {
|
|
146
|
-
warnings.push(`malformed_rows_skipped:${skipped}`);
|
|
147
|
-
}
|
|
148
|
-
const pains = rawRows
|
|
149
|
-
.map(toPainEntry)
|
|
150
|
-
.filter((entry): entry is PainListEntry => entry !== null);
|
|
151
|
-
return {
|
|
152
|
-
count: pains.length,
|
|
153
|
-
pains,
|
|
154
|
-
workspace: path.dirname(path.dirname(dbPath)),
|
|
155
|
-
hostFilter: options.host ?? null,
|
|
156
|
-
byHost,
|
|
157
|
-
warnings,
|
|
158
|
-
};
|
|
159
|
-
} finally {
|
|
160
|
-
db.close();
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function printHuman(result: PainListResult): void {
|
|
165
|
-
const filterNote = result.hostFilter ? ` (host: ${result.hostFilter})` : '';
|
|
166
|
-
console.log(`Pain events — ${result.count} shown${filterNote}`);
|
|
167
|
-
if (result.byHost) {
|
|
168
|
-
const { openclaw, codex, unknown } = result.byHost;
|
|
169
|
-
console.log(`By host: openclaw=${openclaw} codex=${codex} unknown=${unknown}`);
|
|
170
|
-
}
|
|
171
|
-
console.log('─'.repeat(96));
|
|
172
|
-
for (const pain of result.pains) {
|
|
173
|
-
const painId = pain.painId.length > 40 ? `${pain.painId.slice(0, 37)}...` : pain.painId;
|
|
174
|
-
console.log(` ${pain.createdAt} host=${pain.host.padEnd(8)} score=${String(pain.score).padEnd(3)} ${pain.severity ?? '-'.padEnd(6)} ${pain.source}`);
|
|
175
|
-
console.log(` id: ${painId}${pain.runtimeTaskId ? ` | task: ${pain.runtimeTaskId}` : ''}`);
|
|
176
|
-
}
|
|
177
|
-
console.log('─'.repeat(96));
|
|
178
|
-
for (const warning of result.warnings) {
|
|
179
|
-
console.error(`WARN: ${warning}`);
|
|
180
|
-
}
|
|
181
|
-
if (result.warnings.includes('host_kind_column_missing')) {
|
|
182
|
-
console.error('Next: run `pd runtime init --workspace <dir>` (or restart the OpenClaw plugin) to migrate this workspace schema.');
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export async function handlePainList(opts: PainListOptions): Promise<void> {
|
|
187
|
-
const { workspace, limit: rawLimit, host: rawHost, json } = opts;
|
|
188
|
-
|
|
189
|
-
if (rawHost !== undefined && rawHost !== 'openclaw' && rawHost !== 'codex' && rawHost !== 'unknown') {
|
|
190
|
-
const message = `invalid host filter: expected openclaw | codex | unknown, got ${rawHost}`;
|
|
191
|
-
if (json) {
|
|
192
|
-
console.log(JSON.stringify({ status: 'failed', reason: 'invalid_host_filter', message, nextAction: 'Pass --host openclaw, --host codex, or --host unknown.' }));
|
|
193
|
-
} else {
|
|
194
|
-
console.error('Error: ' + message);
|
|
195
|
-
console.error('Next: Pass --host openclaw, --host codex, or --host unknown.');
|
|
196
|
-
}
|
|
197
|
-
process.exit(1);
|
|
198
|
-
return; // guard: test stubs of process.exit continue execution (cli-2-exit-stops)
|
|
199
|
-
}
|
|
200
|
-
const hostFilter: PainListHostFilter | undefined = rawHost;
|
|
201
|
-
|
|
202
|
-
let effectiveLimit = 20;
|
|
203
|
-
if (rawLimit !== undefined) {
|
|
204
|
-
if (!Number.isInteger(rawLimit) || rawLimit < 1 || rawLimit > 10000) {
|
|
205
|
-
const message = `invalid limit: limit must be an integer between 1 and 10000, got ${rawLimit}`;
|
|
206
|
-
if (json) {
|
|
207
|
-
console.log(JSON.stringify({ status: 'failed', reason: 'invalid_limit', message, nextAction: 'Pass --limit with a valid integer (1-10000).' }));
|
|
208
|
-
} else {
|
|
209
|
-
console.error('Error: ' + message);
|
|
210
|
-
console.error('Next: Pass --limit with a valid integer (1-10000).');
|
|
211
|
-
}
|
|
212
|
-
process.exit(1);
|
|
213
|
-
return; // guard: test stubs of process.exit continue execution (cli-2-exit-stops)
|
|
214
|
-
}
|
|
215
|
-
effectiveLimit = rawLimit;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const workspaceDir = resolveWorkspaceDir(workspace);
|
|
219
|
-
const dbPath = path.join(workspaceDir, '.state', 'trajectory.db');
|
|
220
|
-
if (!fs.existsSync(dbPath)) {
|
|
221
|
-
const message = `trajectory database not found at ${dbPath}`;
|
|
222
|
-
if (json) {
|
|
223
|
-
console.log(JSON.stringify({ status: 'failed', reason: 'trajectory_db_not_found', message, nextAction: 'Initialize the PD workspace first (pd runtime init), or pass --workspace pointing at an initialized workspace.' }));
|
|
224
|
-
} else {
|
|
225
|
-
console.error('Error: ' + message);
|
|
226
|
-
console.error('Next: Initialize the PD workspace first (pd runtime init), or pass --workspace pointing at an initialized workspace.');
|
|
227
|
-
}
|
|
228
|
-
process.exit(1);
|
|
229
|
-
return; // guard: test stubs of process.exit continue execution (cli-2-exit-stops)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const result = await listPains(dbPath, { limit: effectiveLimit, ...(hostFilter ? { host: hostFilter } : {}) });
|
|
233
|
-
|
|
234
|
-
if (json) {
|
|
235
|
-
console.log(JSON.stringify(result, null, 2));
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
printHuman(result);
|
|
239
|
-
}
|