@principles/pd-cli 1.152.12 → 1.152.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -7
- package/CHANGELOG.md +0 -23
- 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 -1250
- 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 -1214
- 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 -628
- 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 -1242
- 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 -10
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for pd pain evidence command.
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* - Command parser/registration: --workspace, --limit, --json
|
|
6
|
-
* - Real SYSTEM log fixture parsing: TRIGGER_DECISION entries
|
|
7
|
-
* - Malformed log entries: no crash, visible reason/diagnostic
|
|
8
|
-
* - --json stdout: exactly one JSON object
|
|
9
|
-
* - --limit invalid values: fail loud with reason + nextAction
|
|
10
|
-
* - Searched path matches actual log directory
|
|
11
|
-
*/
|
|
12
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
13
|
-
import { Command } from 'commander';
|
|
14
|
-
import * as fs from 'fs';
|
|
15
|
-
import * as path from 'path';
|
|
16
|
-
import os from 'os';
|
|
17
|
-
|
|
18
|
-
// ── Fixture: Real SYSTEM log content with TRIGGER_DECISION entries ──────────
|
|
19
|
-
|
|
20
|
-
// Historical SystemLogger fixture (kept verbatim — documents pre-PRI-763 log
|
|
21
|
-
// shape; the painEvidenceAdmission flag line is legacy text, not a consumer).
|
|
22
|
-
const LOG_HEADER_LINES = [
|
|
23
|
-
'2026-06-08 10:15:30 [INFO] [PD:SystemLogger] Session started session_abc',
|
|
24
|
-
'2026-06-08 10:15:31 [INFO] [PD:SystemLogger] painEvidenceAdmission feature flag enabled',
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
const TRIGGER_DECISION_ENTRIES = [
|
|
28
|
-
'2026-06-08 10:16:00 [INFO] [PD:SystemLogger] TRIGGER_DECISION {"outcome":"evidence_only","sourceKind":"tool_failure","reason":"Tool failure is infrastructure noise.","nextAction":"store_as_evidence","triageDecision":"evidence_only","tool":"read","sessionId":"session_abc"}',
|
|
29
|
-
'2026-06-08 10:17:00 [INFO] [PD:SystemLogger] TRIGGER_DECISION {"outcome":"health_only","sourceKind":"provider_failure","reason":"Infrastructure health signal.","nextAction":"monitor","triageDecision":"health_only","sessionId":"session_abc"}',
|
|
30
|
-
'2026-06-08 10:18:00 [INFO] [PD:SystemLogger] TRIGGER_DECISION {"outcome":"manual_owner_admitted","sourceKind":"owner_reported","reason":"Owner explicit manual pain. Bypasses triage and cooldown.","nextAction":"create_diagnostic_task","painId":"pain_123","score":100}',
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
const MALFORMED_ENTRY = '2026-06-08 10:19:00 [INFO] [PD:SystemLogger] TRIGGER_DECISION {not valid json';
|
|
34
|
-
const NON_TRIGGER_ENTRY = '2026-06-08 10:20:00 [INFO] [PD:SystemLogger] PAIN_GATE_REJECTED {"reason":"cooldown"}';
|
|
35
|
-
|
|
36
|
-
const FULL_LOG_CONTENT = [
|
|
37
|
-
...LOG_HEADER_LINES,
|
|
38
|
-
...TRIGGER_DECISION_ENTRIES.slice(0, 2),
|
|
39
|
-
MALFORMED_ENTRY,
|
|
40
|
-
...TRIGGER_DECISION_ENTRIES.slice(2),
|
|
41
|
-
NON_TRIGGER_ENTRY,
|
|
42
|
-
].join('\n');
|
|
43
|
-
|
|
44
|
-
// ── Imports (under test) ───────────────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
import { parseTriggerDecisions, getLogDir } from '../../src/commands/pain-evidence.js';
|
|
47
|
-
|
|
48
|
-
declare module '../../src/commands/pain-evidence.js' {
|
|
49
|
-
export function parseTriggerDecisions(logContent: string): import('../../src/commands/pain-evidence.js').TriggerDecisionEntry[];
|
|
50
|
-
export function getLogDir(workspaceDir: string): string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ── Parser Tests ───────────────────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
describe('parseTriggerDecisions', () => {
|
|
56
|
-
it('PARSER-01: parses TRIGGER_DECISION entries from log content', () => {
|
|
57
|
-
const entries = parseTriggerDecisions(FULL_LOG_CONTENT);
|
|
58
|
-
|
|
59
|
-
// Should parse 3 valid entries, skip malformed and non-trigger lines
|
|
60
|
-
expect(entries.length).toBe(3);
|
|
61
|
-
|
|
62
|
-
// First: evidence_only from tool_failure
|
|
63
|
-
expect(entries[0].outcome).toBe('evidence_only');
|
|
64
|
-
expect(entries[0].sourceKind).toBe('tool_failure');
|
|
65
|
-
expect(entries[0].reason).toContain('infrastructure');
|
|
66
|
-
expect(entries[0].timestamp).toBe('2026-06-08 10:16:00');
|
|
67
|
-
|
|
68
|
-
// Second: health_only from provider_failure
|
|
69
|
-
expect(entries[1].outcome).toBe('health_only');
|
|
70
|
-
expect(entries[1].sourceKind).toBe('provider_failure');
|
|
71
|
-
|
|
72
|
-
// Third: manual_owner_admitted
|
|
73
|
-
expect(entries[2].outcome).toBe('manual_owner_admitted');
|
|
74
|
-
expect(entries[2].sourceKind).toBe('owner_reported');
|
|
75
|
-
expect(entries[2].painId).toBe('pain_123');
|
|
76
|
-
expect(entries[2].score).toBe(100);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('PARSER-02: returns empty array for content without TRIGGER_DECISION', () => {
|
|
80
|
-
const entries = parseTriggerDecisions('2026-06-08 10:15:30 [INFO] Normal log line\nMore normal log');
|
|
81
|
-
expect(entries.length).toBe(0);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('PARSER-03: returns empty array for empty content', () => {
|
|
85
|
-
const entries = parseTriggerDecisions('');
|
|
86
|
-
expect(entries.length).toBe(0);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('PARSER-04: malformed JSON entries do not crash and are silently skipped', () => {
|
|
90
|
-
const content = [
|
|
91
|
-
'2026-06-08 10:16:00 [INFO] TRIGGER_DECISION {"outcome":"evidence_only","sourceKind":"tool_failure","reason":"valid","nextAction":"store"}',
|
|
92
|
-
'2026-06-08 10:17:00 [INFO] TRIGGER_DECISION {invalid json}',
|
|
93
|
-
'2026-06-08 10:18:00 [INFO] TRIGGER_DECISION {"outcome":"diagnosis_created","sourceKind":"owner_reported","reason":"valid2","nextAction":"diagnose"}',
|
|
94
|
-
].join('\n');
|
|
95
|
-
|
|
96
|
-
const entries = parseTriggerDecisions(content);
|
|
97
|
-
expect(entries.length).toBe(2); // malformed skipped
|
|
98
|
-
expect(entries[0].outcome).toBe('evidence_only');
|
|
99
|
-
expect(entries[1].outcome).toBe('diagnosis_created');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('PARSER-05: runtime type validation catches non-string fields', () => {
|
|
103
|
-
const content = '2026-06-08 10:16:00 [INFO] TRIGGER_DECISION {"outcome":123,"sourceKind":["array"],"reason":null,"nextAction":true}';
|
|
104
|
-
const entries = parseTriggerDecisions(content);
|
|
105
|
-
expect(entries.length).toBe(1);
|
|
106
|
-
// Should fall back to defaults when types don't match
|
|
107
|
-
expect(entries[0].outcome).toBe('unknown');
|
|
108
|
-
expect(entries[0].sourceKind).toBe('unknown');
|
|
109
|
-
expect(entries[0].reason).toBe('');
|
|
110
|
-
expect(entries[0].nextAction).toBe('');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('PARSER-06: entries preserve parse order (oldest-first within file)', () => {
|
|
114
|
-
const content = [
|
|
115
|
-
'2026-06-08 10:15:00 [INFO] TRIGGER_DECISION {"outcome":"evidence_only","sourceKind":"a","reason":"r1","nextAction":"n"}',
|
|
116
|
-
'2026-06-08 10:16:00 [INFO] TRIGGER_DECISION {"outcome":"health_only","sourceKind":"b","reason":"r2","nextAction":"n"}',
|
|
117
|
-
'2026-06-08 10:17:00 [INFO] TRIGGER_DECISION {"outcome":"diagnosis_created","sourceKind":"c","reason":"r3","nextAction":"n"}',
|
|
118
|
-
].join('\n');
|
|
119
|
-
|
|
120
|
-
const entries = parseTriggerDecisions(content);
|
|
121
|
-
expect(entries.length).toBe(3);
|
|
122
|
-
// File is parsed top-to-bottom, entries preserve reading order
|
|
123
|
-
// (readRecentDecisions reverses per-file entries for newest-first)
|
|
124
|
-
expect(entries[0].timestamp).toBe('2026-06-08 10:15:00');
|
|
125
|
-
expect(entries[1].timestamp).toBe('2026-06-08 10:16:00');
|
|
126
|
-
expect(entries[2].timestamp).toBe('2026-06-08 10:17:00');
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// ── Integration: Real Log File Fixture ─────────────────────────────────────
|
|
131
|
-
|
|
132
|
-
describe('real SYSTEM log fixture', () => {
|
|
133
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-test-evidence-'));
|
|
134
|
-
const logDir = path.join(tmpDir, 'memory', 'logs');
|
|
135
|
-
|
|
136
|
-
beforeEach(() => {
|
|
137
|
-
// Create log directory with fixture file
|
|
138
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
139
|
-
fs.writeFileSync(path.join(logDir, 'SYSTEM_2026-06-08.log'), FULL_LOG_CONTENT, 'utf8');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
afterEach(() => {
|
|
143
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('FIXTURE-01: reads and parses TRIGGER_DECISION from real SYSTEM log', async () => {
|
|
147
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
148
|
-
|
|
149
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
150
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
151
|
-
|
|
152
|
-
await handlePainEvidence({
|
|
153
|
-
workspace: tmpDir,
|
|
154
|
-
limit: 10,
|
|
155
|
-
json: true,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Find the JSON output call
|
|
159
|
-
const jsonCall = logSpy.mock.calls.find((call) => {
|
|
160
|
-
try { JSON.parse(call[0] as string); return true; } catch { return false; }
|
|
161
|
-
});
|
|
162
|
-
expect(jsonCall).toBeDefined();
|
|
163
|
-
const output = JSON.parse(jsonCall![0] as string);
|
|
164
|
-
expect(output.count).toBe(3);
|
|
165
|
-
expect(output.decisions.length).toBe(3);
|
|
166
|
-
expect(output.searchedPath).toContain(path.join('memory', 'logs', 'SYSTEM_*.log'));
|
|
167
|
-
// Entries are newest-first after per-file reversal
|
|
168
|
-
expect(output.decisions[0].outcome).toBe('manual_owner_admitted');
|
|
169
|
-
expect(output.decisions[1].outcome).toBe('health_only');
|
|
170
|
-
expect(output.decisions[2].outcome).toBe('evidence_only');
|
|
171
|
-
|
|
172
|
-
logSpy.mockRestore();
|
|
173
|
-
exitSpy.mockRestore();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('FIXTURE-02: --json stdout is exactly one parseable JSON object', async () => {
|
|
177
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
178
|
-
|
|
179
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
180
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
181
|
-
|
|
182
|
-
await handlePainEvidence({
|
|
183
|
-
workspace: tmpDir,
|
|
184
|
-
limit: 5,
|
|
185
|
-
json: true,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Should produce a valid JSON output
|
|
189
|
-
const jsonCalls = logSpy.mock.calls.filter((call) => {
|
|
190
|
-
try { JSON.parse(call[0] as string); return true; } catch { return false; }
|
|
191
|
-
});
|
|
192
|
-
expect(jsonCalls.length).toBeGreaterThanOrEqual(1);
|
|
193
|
-
const parsed = JSON.parse(jsonCalls[0][0] as string);
|
|
194
|
-
expect(parsed.count).toBe(3);
|
|
195
|
-
expect(Array.isArray(parsed.decisions)).toBe(true);
|
|
196
|
-
|
|
197
|
-
logSpy.mockRestore();
|
|
198
|
-
exitSpy.mockRestore();
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('FIXTURE-03: non-JSON output shows formatted entries', async () => {
|
|
202
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
203
|
-
|
|
204
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
205
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
206
|
-
|
|
207
|
-
await handlePainEvidence({
|
|
208
|
-
workspace: tmpDir,
|
|
209
|
-
limit: 10,
|
|
210
|
-
json: false,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
const allOutput = logSpy.mock.calls.map(c => c[0]).join('\n');
|
|
214
|
-
expect(allOutput).toContain('evidence_only');
|
|
215
|
-
expect(allOutput).toContain('health_only');
|
|
216
|
-
expect(allOutput).toContain('manual_owner_admitted');
|
|
217
|
-
expect(allOutput).toContain('Tool failure is infrastructure noise');
|
|
218
|
-
expect(allOutput).toContain('Summary:');
|
|
219
|
-
|
|
220
|
-
logSpy.mockRestore();
|
|
221
|
-
exitSpy.mockRestore();
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('FIXTURE-04: empty log returns zero results', async () => {
|
|
225
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
226
|
-
|
|
227
|
-
// Create a log file with no TRIGGER_DECISION entries
|
|
228
|
-
const noTriggerLog = '2026-06-08 [INFO] Normal log content without any trigger decisions.\n';
|
|
229
|
-
fs.writeFileSync(path.join(logDir, 'SYSTEM_2026-06-08.log'), noTriggerLog, 'utf8');
|
|
230
|
-
|
|
231
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
232
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
233
|
-
|
|
234
|
-
await handlePainEvidence({
|
|
235
|
-
workspace: tmpDir,
|
|
236
|
-
limit: 5,
|
|
237
|
-
json: true,
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
const jsonCall = logSpy.mock.calls.find((call) => {
|
|
241
|
-
try { JSON.parse(call[0] as string); return true; } catch { return false; }
|
|
242
|
-
});
|
|
243
|
-
expect(jsonCall).toBeDefined();
|
|
244
|
-
const output = JSON.parse(jsonCall![0] as string);
|
|
245
|
-
expect(output.count).toBe(0);
|
|
246
|
-
expect(output.decisions).toEqual([]);
|
|
247
|
-
|
|
248
|
-
logSpy.mockRestore();
|
|
249
|
-
exitSpy.mockRestore();
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('FIXTURE-05: --limit filters entries', async () => {
|
|
253
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
254
|
-
|
|
255
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
256
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
257
|
-
|
|
258
|
-
// With limit 2, should return only 2 entries
|
|
259
|
-
await handlePainEvidence({
|
|
260
|
-
workspace: tmpDir,
|
|
261
|
-
limit: 2,
|
|
262
|
-
json: true,
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
const jsonCall = logSpy.mock.calls.find((call) => {
|
|
266
|
-
try { JSON.parse(call[0] as string); return true; } catch { return false; }
|
|
267
|
-
});
|
|
268
|
-
expect(jsonCall).toBeDefined();
|
|
269
|
-
const output = JSON.parse(jsonCall![0] as string);
|
|
270
|
-
expect(output.count).toBe(2);
|
|
271
|
-
expect(output.decisions.length).toBe(2);
|
|
272
|
-
|
|
273
|
-
logSpy.mockRestore();
|
|
274
|
-
exitSpy.mockRestore();
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('FIXTURE-06: relative --workspace reads SYSTEM logs (regression)', async () => {
|
|
278
|
-
// Regression: a relative workspace root must not break containment.
|
|
279
|
-
// getLogDir canonicalizes via assertSafeDirectoryRoot and the log-file
|
|
280
|
-
// check uses isPathInside (canonical-vs-canonical), so a relative root
|
|
281
|
-
// must resolve and contain its own log dir.
|
|
282
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
283
|
-
|
|
284
|
-
// Create a workspace under cwd so a true relative path is possible.
|
|
285
|
-
const relTmp = fs.mkdtempSync(path.join(process.cwd(), '.tmp-rel-ws-'));
|
|
286
|
-
try {
|
|
287
|
-
const relLogDir = path.join(relTmp, 'memory', 'logs');
|
|
288
|
-
fs.mkdirSync(relLogDir, { recursive: true });
|
|
289
|
-
fs.writeFileSync(path.join(relLogDir, 'SYSTEM_2026-06-08.log'), FULL_LOG_CONTENT, 'utf8');
|
|
290
|
-
const relWorkspace = path.relative(process.cwd(), relTmp);
|
|
291
|
-
expect(path.isAbsolute(relWorkspace)).toBe(false);
|
|
292
|
-
|
|
293
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
294
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
295
|
-
|
|
296
|
-
await handlePainEvidence({ workspace: relWorkspace, limit: 10, json: true });
|
|
297
|
-
|
|
298
|
-
const jsonCall = logSpy.mock.calls.find((call) => {
|
|
299
|
-
try { JSON.parse(call[0] as string); return true; } catch { return false; }
|
|
300
|
-
});
|
|
301
|
-
expect(jsonCall).toBeDefined();
|
|
302
|
-
const output = JSON.parse(jsonCall![0] as string);
|
|
303
|
-
expect(output.count).toBe(3);
|
|
304
|
-
expect(output.searchedPath).toContain(path.join('memory', 'logs', 'SYSTEM_*.log'));
|
|
305
|
-
expect(output.decisions[0].outcome).toBe('manual_owner_admitted');
|
|
306
|
-
|
|
307
|
-
logSpy.mockRestore();
|
|
308
|
-
exitSpy.mockRestore();
|
|
309
|
-
} finally {
|
|
310
|
-
fs.rmSync(relTmp, { recursive: true, force: true });
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
// ── Commander Registration Tests ───────────────────────────────────────────
|
|
316
|
-
|
|
317
|
-
describe('Commander wiring for pd pain evidence', () => {
|
|
318
|
-
function createEvidenceProgram(): { program: Command; capturedOpts: Record<string, unknown> } {
|
|
319
|
-
const program = new Command();
|
|
320
|
-
program.exitOverride();
|
|
321
|
-
const capturedOpts: Record<string, unknown> = {};
|
|
322
|
-
|
|
323
|
-
program
|
|
324
|
-
.command('pain')
|
|
325
|
-
.command('evidence')
|
|
326
|
-
.option('-w, --workspace <path>', 'Workspace directory')
|
|
327
|
-
.option('-l, --limit <number>', 'Max entries to show', parseInt)
|
|
328
|
-
.option('--json', 'Output raw JSON')
|
|
329
|
-
.action(async (opts) => {
|
|
330
|
-
Object.assign(capturedOpts, opts);
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
return { program, capturedOpts };
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
it('CMD-01: --workspace sets opts.workspace', async () => {
|
|
337
|
-
const { program, capturedOpts } = createEvidenceProgram();
|
|
338
|
-
await program.parseAsync(['node', 'pd', 'pain', 'evidence', '--workspace', '/tmp/ws']);
|
|
339
|
-
expect(capturedOpts.workspace).toBe('/tmp/ws');
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it('CMD-02: -w short form sets opts.workspace', async () => {
|
|
343
|
-
const { program, capturedOpts } = createEvidenceProgram();
|
|
344
|
-
await program.parseAsync(['node', 'pd', 'pain', 'evidence', '-w', '/tmp/ws']);
|
|
345
|
-
expect(capturedOpts.workspace).toBe('/tmp/ws');
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it('CMD-03: --limit sets opts.limit as number', async () => {
|
|
349
|
-
const { program, capturedOpts } = createEvidenceProgram();
|
|
350
|
-
await program.parseAsync(['node', 'pd', 'pain', 'evidence', '--limit', '5']);
|
|
351
|
-
expect(capturedOpts.limit).toBe(5);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('CMD-04: -l short form sets opts.limit', async () => {
|
|
355
|
-
const { program, capturedOpts } = createEvidenceProgram();
|
|
356
|
-
await program.parseAsync(['node', 'pd', 'pain', 'evidence', '-l', '10']);
|
|
357
|
-
expect(capturedOpts.limit).toBe(10);
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it('CMD-05: default (no args) → opts.limit undefined', async () => {
|
|
361
|
-
const { program, capturedOpts } = createEvidenceProgram();
|
|
362
|
-
await program.parseAsync(['node', 'pd', 'pain', 'evidence']);
|
|
363
|
-
expect(capturedOpts.limit).toBeUndefined();
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it('CMD-06: --json sets opts.json === true', async () => {
|
|
367
|
-
const { program, capturedOpts } = createEvidenceProgram();
|
|
368
|
-
await program.parseAsync(['node', 'pd', 'pain', 'evidence', '--json']);
|
|
369
|
-
expect(capturedOpts.json).toBe(true);
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
it('CMD-07: no --json → opts.json undefined', async () => {
|
|
373
|
-
const { program, capturedOpts } = createEvidenceProgram();
|
|
374
|
-
await program.parseAsync(['node', 'pd', 'pain', 'evidence']);
|
|
375
|
-
expect(capturedOpts.json).toBeUndefined();
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it('CMD-08: --limit with non-numeric value → NaN', async () => {
|
|
379
|
-
const { program, capturedOpts } = createEvidenceProgram();
|
|
380
|
-
await program.parseAsync(['node', 'pd', 'pain', 'evidence', '--limit', 'abc']);
|
|
381
|
-
expect(capturedOpts.limit).toBeNaN();
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
// ── Limit Validation Tests ─────────────────────────────────────────────────
|
|
386
|
-
|
|
387
|
-
describe('pain evidence — limit validation', () => {
|
|
388
|
-
it('INVALID-01: --limit 0 fails loud with reason + nextAction (JSON)', async () => {
|
|
389
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
390
|
-
|
|
391
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
392
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
393
|
-
|
|
394
|
-
await handlePainEvidence({
|
|
395
|
-
workspace: '/nonexistent',
|
|
396
|
-
limit: 0,
|
|
397
|
-
json: true,
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
const jsonCall = logSpy.mock.calls.find((call) => {
|
|
401
|
-
try { JSON.parse(call[0] as string); return true; } catch { return false; }
|
|
402
|
-
});
|
|
403
|
-
expect(jsonCall).toBeDefined();
|
|
404
|
-
const output = JSON.parse(jsonCall![0] as string);
|
|
405
|
-
expect(output.error).toBeDefined();
|
|
406
|
-
expect(output.error.status).toBe('refused');
|
|
407
|
-
expect(output.error.reason).toContain('invalid_limit');
|
|
408
|
-
expect(output.error.reason).toContain('0');
|
|
409
|
-
expect(output.error.nextAction).toBeDefined();
|
|
410
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
411
|
-
// Guard: no readRecentDecisions after invalid limit
|
|
412
|
-
expect(output.decisions).toBeUndefined();
|
|
413
|
-
expect(output.count).toBe(0);
|
|
414
|
-
// Exactly one JSON output, no second JSON from readRecentDecisions
|
|
415
|
-
const jsonCountAfterInvalid = logSpy.mock.calls.filter((call) => {
|
|
416
|
-
try { return typeof call[0] === 'string' && JSON.parse(call[0]); } catch { return false; }
|
|
417
|
-
}).length;
|
|
418
|
-
expect(jsonCountAfterInvalid).toBe(1);
|
|
419
|
-
|
|
420
|
-
logSpy.mockRestore();
|
|
421
|
-
exitSpy.mockRestore();
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it('INVALID-02: --limit -1 fails loud (JSON)', async () => {
|
|
425
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
426
|
-
|
|
427
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
428
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
429
|
-
|
|
430
|
-
await handlePainEvidence({
|
|
431
|
-
workspace: '/nonexistent',
|
|
432
|
-
limit: -1,
|
|
433
|
-
json: true,
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
const jsonCall = logSpy.mock.calls.find((call) => {
|
|
437
|
-
try { JSON.parse(call[0] as string); return true; } catch { return false; }
|
|
438
|
-
});
|
|
439
|
-
expect(jsonCall).toBeDefined();
|
|
440
|
-
const output = JSON.parse(jsonCall![0] as string);
|
|
441
|
-
expect(output.error.status).toBe('refused');
|
|
442
|
-
expect(output.error.reason).toContain('invalid_limit');
|
|
443
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
444
|
-
|
|
445
|
-
logSpy.mockRestore();
|
|
446
|
-
exitSpy.mockRestore();
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
it('INVALID-03: --limit 10001 fails loud (JSON)', async () => {
|
|
450
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
451
|
-
|
|
452
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
453
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
454
|
-
|
|
455
|
-
await handlePainEvidence({
|
|
456
|
-
workspace: '/nonexistent',
|
|
457
|
-
limit: 10001,
|
|
458
|
-
json: true,
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
const jsonCall = logSpy.mock.calls.find((call) => {
|
|
462
|
-
try { JSON.parse(call[0] as string); return true; } catch { return false; }
|
|
463
|
-
});
|
|
464
|
-
expect(jsonCall).toBeDefined();
|
|
465
|
-
const output = JSON.parse(jsonCall![0] as string);
|
|
466
|
-
expect(output.error.status).toBe('refused');
|
|
467
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
468
|
-
|
|
469
|
-
logSpy.mockRestore();
|
|
470
|
-
exitSpy.mockRestore();
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
it('INVALID-04: non-integer limit fails loud (JSON)', async () => {
|
|
474
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
475
|
-
|
|
476
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
477
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
478
|
-
|
|
479
|
-
await handlePainEvidence({
|
|
480
|
-
workspace: '/nonexistent',
|
|
481
|
-
limit: 3.5,
|
|
482
|
-
json: true,
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
const jsonCall = logSpy.mock.calls.find((call) => {
|
|
486
|
-
try { JSON.parse(call[0] as string); return true; } catch { return false; }
|
|
487
|
-
});
|
|
488
|
-
expect(jsonCall).toBeDefined();
|
|
489
|
-
const output = JSON.parse(jsonCall![0] as string);
|
|
490
|
-
expect(output.error.status).toBe('refused');
|
|
491
|
-
expect(output.error.reason).toContain('invalid_limit');
|
|
492
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
493
|
-
|
|
494
|
-
logSpy.mockRestore();
|
|
495
|
-
exitSpy.mockRestore();
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it('INVALID-05: --limit 0 fails loud with human-readable error', async () => {
|
|
499
|
-
const { handlePainEvidence } = await import('../../src/commands/pain-evidence.js');
|
|
500
|
-
|
|
501
|
-
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
502
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as () => never);
|
|
503
|
-
|
|
504
|
-
await handlePainEvidence({
|
|
505
|
-
workspace: '/nonexistent',
|
|
506
|
-
limit: 0,
|
|
507
|
-
json: false,
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
const allErrors = errorSpy.mock.calls.map(c => c[0]).join('\n');
|
|
511
|
-
expect(allErrors).toContain('invalid_limit');
|
|
512
|
-
expect(allErrors).toContain('Next:');
|
|
513
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
514
|
-
|
|
515
|
-
errorSpy.mockRestore();
|
|
516
|
-
exitSpy.mockRestore();
|
|
517
|
-
});
|
|
518
|
-
});
|