@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,369 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Handler tests for `pd rulecode spec|validate|replay` (PRI-439 Phase 5).
|
|
3
|
-
*
|
|
4
|
-
* Tests the actual handler logic (not Commander parser wiring — that's in
|
|
5
|
-
* rulecode-flag-wiring.test.ts). Verifies:
|
|
6
|
-
* - spec returns the canonical RuleCode dialect spec text
|
|
7
|
-
* - validate detects forbidden patterns, missing return fields, matched=false
|
|
8
|
-
* - validate passes clean code
|
|
9
|
-
* - replay runs sandbox replay against a golden trace file
|
|
10
|
-
* - failure paths include structured reason + nextAction (CLI gate rule 6)
|
|
11
|
-
* - --json outputs exactly one parseable JSON object (CLI gate rule 1)
|
|
12
|
-
* - missing --code/--code-file fails loud with reason (ERR-009)
|
|
13
|
-
* - missing/malformed --golden-trace fails loud with reason (ERR-009)
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
17
|
-
import * as fs from 'node:fs';
|
|
18
|
-
import * as path from 'node:path';
|
|
19
|
-
import * as os from 'node:os';
|
|
20
|
-
import {
|
|
21
|
-
handleRulecodeSpec,
|
|
22
|
-
handleRulecodeValidate,
|
|
23
|
-
handleRulecodeReplay,
|
|
24
|
-
} from '../rulecode.js';
|
|
25
|
-
|
|
26
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
async function runHandler<T>(fn: () => Promise<T>): Promise<{ stdout: string; stderr: string; exitCode: number | undefined }> {
|
|
29
|
-
const stdoutChunks: string[] = [];
|
|
30
|
-
const stderrChunks: string[] = [];
|
|
31
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation((...args: unknown[]) => {
|
|
32
|
-
stdoutChunks.push(args.map(String).join(' '));
|
|
33
|
-
});
|
|
34
|
-
const errorSpy = vi.spyOn(console, 'error').mockImplementation((...args: unknown[]) => {
|
|
35
|
-
stderrChunks.push(args.map(String).join(' '));
|
|
36
|
-
});
|
|
37
|
-
process.exitCode = undefined;
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
await fn();
|
|
41
|
-
} finally {
|
|
42
|
-
logSpy.mockRestore();
|
|
43
|
-
errorSpy.mockRestore();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const exitCode = process.exitCode;
|
|
47
|
-
process.exitCode = undefined;
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
stdout: stdoutChunks.join(''),
|
|
51
|
-
stderr: stderrChunks.join(''),
|
|
52
|
-
exitCode,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function parseJson(stdout: string): unknown {
|
|
57
|
-
return JSON.parse(stdout);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const CLEAN_CODE = `function evaluate(input, helpers) {
|
|
61
|
-
if (helpers.getToolName() === 'Bash') {
|
|
62
|
-
return { decision: "block", matched: true, reason: "bash commands blocked" };
|
|
63
|
-
}
|
|
64
|
-
return { decision: "allow", matched: false, reason: "non-bash allowed" };
|
|
65
|
-
}`;
|
|
66
|
-
|
|
67
|
-
const FORBIDDEN_CODE = `function evaluate(input, helpers) {
|
|
68
|
-
require('fs');
|
|
69
|
-
return { decision: "allow", matched: false, reason: "x" };
|
|
70
|
-
}`;
|
|
71
|
-
|
|
72
|
-
const MISSING_FIELDS_CODE = `function evaluate(input, helpers) {
|
|
73
|
-
if (helpers.isRiskPath()) {
|
|
74
|
-
return { matched: true };
|
|
75
|
-
}
|
|
76
|
-
return { decision: "allow", matched: false, reason: "safe" };
|
|
77
|
-
}`;
|
|
78
|
-
|
|
79
|
-
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
describe('pd rulecode spec', () => {
|
|
82
|
-
it('returns the canonical spec text as JSON', async () => {
|
|
83
|
-
const { stdout, exitCode } = await runHandler(() => handleRulecodeSpec({ json: true }));
|
|
84
|
-
const output = parseJson(stdout) as { status: string; spec: string };
|
|
85
|
-
expect(output.status).toBe('ok');
|
|
86
|
-
expect(output.spec).toContain('RuleCode Dialect Spec');
|
|
87
|
-
expect(output.spec).toContain('CANONICAL FORM');
|
|
88
|
-
expect(output.spec).toContain('FORBIDDEN PATTERNS');
|
|
89
|
-
expect(exitCode).toBeUndefined();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('outputs text when --json is false', async () => {
|
|
93
|
-
const { stdout, exitCode } = await runHandler(() => handleRulecodeSpec({ json: false }));
|
|
94
|
-
expect(stdout).toContain('RuleCode Dialect Spec');
|
|
95
|
-
expect(stdout.startsWith('{')).toBe(false);
|
|
96
|
-
expect(exitCode).toBeUndefined();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('does not set exit code on success', async () => {
|
|
100
|
-
const { exitCode } = await runHandler(() => handleRulecodeSpec({ json: true }));
|
|
101
|
-
expect(exitCode).toBeUndefined();
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe('pd rulecode validate', () => {
|
|
106
|
-
it('passes clean code with valid=true', async () => {
|
|
107
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
108
|
-
handleRulecodeValidate({ code: CLEAN_CODE, json: true }),
|
|
109
|
-
);
|
|
110
|
-
const output = parseJson(stdout) as {
|
|
111
|
-
status: string; valid: boolean; violationCount: number; violations: string[];
|
|
112
|
-
};
|
|
113
|
-
expect(output.status).toBe('ok');
|
|
114
|
-
expect(output.valid).toBe(true);
|
|
115
|
-
expect(output.violationCount).toBe(0);
|
|
116
|
-
expect(output.violations).toEqual([]);
|
|
117
|
-
expect(exitCode).toBeUndefined();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('detects forbidden patterns', async () => {
|
|
121
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
122
|
-
handleRulecodeValidate({ code: FORBIDDEN_CODE, json: true }),
|
|
123
|
-
);
|
|
124
|
-
const output = parseJson(stdout) as {
|
|
125
|
-
status: string; valid: boolean; violationCount: number; violations: string[];
|
|
126
|
-
reason?: string; nextAction?: string;
|
|
127
|
-
};
|
|
128
|
-
expect(output.status).toBe('failed');
|
|
129
|
-
expect(output.valid).toBe(false);
|
|
130
|
-
expect(output.violationCount).toBeGreaterThan(0);
|
|
131
|
-
expect(output.violations.some((v) => v.includes('forbidden pattern'))).toBe(true);
|
|
132
|
-
expect(output.reason).toBeDefined();
|
|
133
|
-
expect(output.nextAction).toBeDefined();
|
|
134
|
-
expect(exitCode).toBe(1);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('detects missing return fields', async () => {
|
|
138
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
139
|
-
handleRulecodeValidate({ code: MISSING_FIELDS_CODE, json: true }),
|
|
140
|
-
);
|
|
141
|
-
const output = parseJson(stdout) as { valid: boolean; violations: string[] };
|
|
142
|
-
expect(output.valid).toBe(false);
|
|
143
|
-
expect(output.violations.length).toBeGreaterThan(0);
|
|
144
|
-
expect(exitCode).toBe(1);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('fails loud when no --code or --code-file provided', async () => {
|
|
148
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
149
|
-
handleRulecodeValidate({ json: true }),
|
|
150
|
-
);
|
|
151
|
-
const output = parseJson(stdout) as {
|
|
152
|
-
status: string; valid: boolean; reason: string; nextAction: string;
|
|
153
|
-
};
|
|
154
|
-
expect(output.status).toBe('failed');
|
|
155
|
-
expect(output.valid).toBe(false);
|
|
156
|
-
expect(output.reason).toContain('no code provided');
|
|
157
|
-
expect(output.nextAction).toContain('--code');
|
|
158
|
-
expect(exitCode).toBe(1);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('reads code from --code-file', async () => {
|
|
162
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-rulecode-test-'));
|
|
163
|
-
const codeFile = path.join(tmpDir, 'rule.js');
|
|
164
|
-
fs.writeFileSync(codeFile, CLEAN_CODE, 'utf8');
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
168
|
-
handleRulecodeValidate({ codeFile, json: true }),
|
|
169
|
-
);
|
|
170
|
-
const output = parseJson(stdout) as { valid: boolean };
|
|
171
|
-
expect(output.valid).toBe(true);
|
|
172
|
-
expect(exitCode).toBeUndefined();
|
|
173
|
-
} finally {
|
|
174
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('fails loud when --code-file does not exist', async () => {
|
|
179
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
180
|
-
handleRulecodeValidate({ codeFile: '/nonexistent/path/rule.js', json: true }),
|
|
181
|
-
);
|
|
182
|
-
const output = parseJson(stdout) as {
|
|
183
|
-
status: string; reason: string; nextAction: string;
|
|
184
|
-
};
|
|
185
|
-
expect(output.status).toBe('failed');
|
|
186
|
-
expect(output.reason).toContain('cannot read');
|
|
187
|
-
expect(output.nextAction).toBeDefined();
|
|
188
|
-
expect(exitCode).toBe(1);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('pd rulecode replay', () => {
|
|
193
|
-
const GOLDEN_TRACE_CASES = [
|
|
194
|
-
{
|
|
195
|
-
caseId: 'pos-1',
|
|
196
|
-
kind: 'positive' as const,
|
|
197
|
-
toolName: 'Write',
|
|
198
|
-
params: { normalizedPath: 'src/safe.ts' },
|
|
199
|
-
expectedDecision: 'allow' as const,
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
caseId: 'neg-1',
|
|
203
|
-
kind: 'negative' as const,
|
|
204
|
-
toolName: 'Bash',
|
|
205
|
-
params: { command: 'rm -rf /' },
|
|
206
|
-
expectedDecision: 'block' as const,
|
|
207
|
-
},
|
|
208
|
-
];
|
|
209
|
-
|
|
210
|
-
function writeGoldenTraceFile(cases: unknown): string {
|
|
211
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-rulecode-replay-'));
|
|
212
|
-
const filePath = path.join(tmpDir, 'trace.json');
|
|
213
|
-
fs.writeFileSync(filePath, JSON.stringify(cases, null, 2), 'utf8');
|
|
214
|
-
return filePath;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
it('passes replay with clean code and valid golden trace', async () => {
|
|
218
|
-
const traceFile = writeGoldenTraceFile(GOLDEN_TRACE_CASES);
|
|
219
|
-
try {
|
|
220
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
221
|
-
handleRulecodeReplay({ code: CLEAN_CODE, goldenTrace: traceFile, json: true }),
|
|
222
|
-
);
|
|
223
|
-
const output = parseJson(stdout) as {
|
|
224
|
-
status: string; decision: string; reasons: string[];
|
|
225
|
-
};
|
|
226
|
-
expect(output.status).toBe('ok');
|
|
227
|
-
expect(output.decision).toBe('accepted_shadow');
|
|
228
|
-
expect(exitCode).toBeUndefined();
|
|
229
|
-
} finally {
|
|
230
|
-
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('fails loud when --golden-trace file does not exist', async () => {
|
|
235
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
236
|
-
handleRulecodeReplay({
|
|
237
|
-
code: CLEAN_CODE,
|
|
238
|
-
goldenTrace: '/nonexistent/trace.json',
|
|
239
|
-
json: true,
|
|
240
|
-
}),
|
|
241
|
-
);
|
|
242
|
-
const output = parseJson(stdout) as {
|
|
243
|
-
status: string; reason: string; nextAction: string;
|
|
244
|
-
};
|
|
245
|
-
expect(output.status).toBe('failed');
|
|
246
|
-
expect(output.reason).toContain('cannot read');
|
|
247
|
-
expect(exitCode).toBe(1);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('fails loud when golden trace is not valid JSON', async () => {
|
|
251
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-rulecode-replay-'));
|
|
252
|
-
const traceFile = path.join(tmpDir, 'trace.json');
|
|
253
|
-
fs.writeFileSync(traceFile, '{ not valid json', 'utf8');
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
257
|
-
handleRulecodeReplay({ code: CLEAN_CODE, goldenTrace: traceFile, json: true }),
|
|
258
|
-
);
|
|
259
|
-
const output = parseJson(stdout) as {
|
|
260
|
-
status: string; reason: string; nextAction: string;
|
|
261
|
-
};
|
|
262
|
-
expect(output.status).toBe('failed');
|
|
263
|
-
expect(output.reason).toContain('not valid JSON');
|
|
264
|
-
expect(exitCode).toBe(1);
|
|
265
|
-
} finally {
|
|
266
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it('fails loud when golden trace is not an array', async () => {
|
|
271
|
-
const traceFile = writeGoldenTraceFile({ not: 'an array' });
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
275
|
-
handleRulecodeReplay({ code: CLEAN_CODE, goldenTrace: traceFile, json: true }),
|
|
276
|
-
);
|
|
277
|
-
const output = parseJson(stdout) as {
|
|
278
|
-
status: string; reason: string; nextAction: string;
|
|
279
|
-
};
|
|
280
|
-
expect(output.status).toBe('failed');
|
|
281
|
-
expect(output.reason).toContain('must contain a JSON array');
|
|
282
|
-
expect(exitCode).toBe(1);
|
|
283
|
-
} finally {
|
|
284
|
-
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it('fails loud when golden trace has fewer than 2 cases', async () => {
|
|
289
|
-
const traceFile = writeGoldenTraceFile([GOLDEN_TRACE_CASES[0]]);
|
|
290
|
-
|
|
291
|
-
try {
|
|
292
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
293
|
-
handleRulecodeReplay({ code: CLEAN_CODE, goldenTrace: traceFile, json: true }),
|
|
294
|
-
);
|
|
295
|
-
const output = parseJson(stdout) as {
|
|
296
|
-
status: string; reason: string; nextAction: string;
|
|
297
|
-
};
|
|
298
|
-
expect(output.status).toBe('failed');
|
|
299
|
-
expect(output.reason).toContain('at least 2 cases');
|
|
300
|
-
expect(exitCode).toBe(1);
|
|
301
|
-
} finally {
|
|
302
|
-
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('fails loud when a golden trace case is malformed', async () => {
|
|
307
|
-
const traceFile = writeGoldenTraceFile([
|
|
308
|
-
{ caseId: 'x' }, // missing required fields
|
|
309
|
-
GOLDEN_TRACE_CASES[1],
|
|
310
|
-
]);
|
|
311
|
-
|
|
312
|
-
try {
|
|
313
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
314
|
-
handleRulecodeReplay({ code: CLEAN_CODE, goldenTrace: traceFile, json: true }),
|
|
315
|
-
);
|
|
316
|
-
const output = parseJson(stdout) as {
|
|
317
|
-
status: string; reason: string; nextAction: string;
|
|
318
|
-
};
|
|
319
|
-
expect(output.status).toBe('failed');
|
|
320
|
-
expect(output.reason).toContain('malformed');
|
|
321
|
-
expect(exitCode).toBe(1);
|
|
322
|
-
} finally {
|
|
323
|
-
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('fails loud when no --code or --code-file provided', async () => {
|
|
328
|
-
const traceFile = writeGoldenTraceFile(GOLDEN_TRACE_CASES);
|
|
329
|
-
try {
|
|
330
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
331
|
-
handleRulecodeReplay({ goldenTrace: traceFile, json: true }),
|
|
332
|
-
);
|
|
333
|
-
const output = parseJson(stdout) as {
|
|
334
|
-
status: string; reason: string; nextAction: string;
|
|
335
|
-
};
|
|
336
|
-
expect(output.status).toBe('failed');
|
|
337
|
-
expect(output.reason).toContain('no code provided');
|
|
338
|
-
expect(exitCode).toBe(1);
|
|
339
|
-
} finally {
|
|
340
|
-
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it('reports sandbox failures with structured reason + nextAction', async () => {
|
|
345
|
-
const traceFile = writeGoldenTraceFile(GOLDEN_TRACE_CASES);
|
|
346
|
-
// Code with forbidden pattern — sandbox will reject
|
|
347
|
-
const badCode = `function evaluate(input, helpers) {
|
|
348
|
-
eval('1');
|
|
349
|
-
return { decision: "allow", matched: false, reason: "x" };
|
|
350
|
-
}`;
|
|
351
|
-
|
|
352
|
-
try {
|
|
353
|
-
const { stdout, exitCode } = await runHandler(() =>
|
|
354
|
-
handleRulecodeReplay({ code: badCode, goldenTrace: traceFile, json: true }),
|
|
355
|
-
);
|
|
356
|
-
const output = parseJson(stdout) as {
|
|
357
|
-
status: string; decision: string; reason?: string; nextAction?: string;
|
|
358
|
-
forbiddenPatternViolations: string[];
|
|
359
|
-
};
|
|
360
|
-
expect(output.status).toBe('failed');
|
|
361
|
-
expect(output.decision).not.toBe('accepted_shadow');
|
|
362
|
-
expect(output.reason).toBeDefined();
|
|
363
|
-
expect(output.nextAction).toBeDefined();
|
|
364
|
-
expect(exitCode).toBe(1);
|
|
365
|
-
} finally {
|
|
366
|
-
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
});
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parser-level tests for `pd runtime internalization run-rulehost` flags (PRI-429).
|
|
3
|
-
*
|
|
4
|
-
* CLI gate rule 7: "Test the real command wiring — when behavior depends on
|
|
5
|
-
* Commander options, add a command-registration or parser test that exercises
|
|
6
|
-
* the actual flags."
|
|
7
|
-
*
|
|
8
|
-
* Tests the real `registerRunRuleHostCommand` helper (single source of truth
|
|
9
|
-
* shared with `index.ts`). Flag typos in production surface here at
|
|
10
|
-
* parseAsync time, not at handler dispatch.
|
|
11
|
-
*
|
|
12
|
-
* Covers:
|
|
13
|
-
* - --dry-run and --confirm are registered
|
|
14
|
-
* - --dry-run and --confirm can both be parsed
|
|
15
|
-
* - --json is registered
|
|
16
|
-
* - --pain-id is required (Commander rejects missing required option)
|
|
17
|
-
* - --workspace / -w shorthand is registered
|
|
18
|
-
* - --no-dry-run / --no-confirm are NOT registered (no accidental negation)
|
|
19
|
-
* - mutual exclusivity is enforced at handler level (not parser level —
|
|
20
|
-
* Commander doesn't natively support .conflicts() for boolean flags
|
|
21
|
-
* without explicit registration; the handler validates this)
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { describe, it, expect } from 'vitest';
|
|
25
|
-
import { Command } from 'commander';
|
|
26
|
-
import { registerRunRuleHostCommand } from '../runtime-internalization-run-rulehost.js';
|
|
27
|
-
|
|
28
|
-
type ActionOptions = Record<string, unknown>;
|
|
29
|
-
|
|
30
|
-
interface CapturedAction {
|
|
31
|
-
opts: ActionOptions | null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function attachCapture(cmd: Command, state: CapturedAction): void {
|
|
35
|
-
cmd.action(function captureAction(...args: unknown[]): void {
|
|
36
|
-
let optsArg: unknown = null;
|
|
37
|
-
for (let i = args.length - 1; i >= 0; i--) {
|
|
38
|
-
const arg: unknown = args[i];
|
|
39
|
-
if (arg !== null && typeof arg === 'object' && !(arg instanceof Command)) {
|
|
40
|
-
optsArg = arg;
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (optsArg !== null && typeof optsArg === 'object') {
|
|
45
|
-
state.opts = optsArg as ActionOptions;
|
|
46
|
-
} else {
|
|
47
|
-
state.opts = {};
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function freshProgram(): Command {
|
|
53
|
-
const program = new Command();
|
|
54
|
-
program.name('pd').exitOverride();
|
|
55
|
-
return program;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
describe('pd runtime internalization run-rulehost — flag wiring (CLI gate rule 7)', () => {
|
|
59
|
-
// ── Option metadata ──────────────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
it('registers --dry-run flag', () => {
|
|
62
|
-
const program = freshProgram();
|
|
63
|
-
const intCmd = program.command('internalization');
|
|
64
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
65
|
-
|
|
66
|
-
const opt = runCmd.options.find((o) => o.long === '--dry-run');
|
|
67
|
-
expect(opt).toBeDefined();
|
|
68
|
-
expect(opt?.long).toBe('--dry-run');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('registers --confirm flag', () => {
|
|
72
|
-
const program = freshProgram();
|
|
73
|
-
const intCmd = program.command('internalization');
|
|
74
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
75
|
-
|
|
76
|
-
const opt = runCmd.options.find((o) => o.long === '--confirm');
|
|
77
|
-
expect(opt).toBeDefined();
|
|
78
|
-
expect(opt?.long).toBe('--confirm');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('registers --json flag', () => {
|
|
82
|
-
const program = freshProgram();
|
|
83
|
-
const intCmd = program.command('internalization');
|
|
84
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
85
|
-
|
|
86
|
-
const opt = runCmd.options.find((o) => o.long === '--json');
|
|
87
|
-
expect(opt).toBeDefined();
|
|
88
|
-
expect(opt?.long).toBe('--json');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('parses --behavior-examples as the Owner-labelled evidence file', async () => {
|
|
92
|
-
const program = freshProgram();
|
|
93
|
-
const intCmd = program.command('internalization');
|
|
94
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
95
|
-
const captured: CapturedAction = { opts: null };
|
|
96
|
-
attachCapture(runCmd, captured);
|
|
97
|
-
|
|
98
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1', '--behavior-examples', 'examples.json']);
|
|
99
|
-
|
|
100
|
-
expect(captured.opts?.behaviorExamples).toBe('examples.json');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('registers --pain-id as required option', () => {
|
|
104
|
-
const program = freshProgram();
|
|
105
|
-
const intCmd = program.command('internalization');
|
|
106
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
107
|
-
|
|
108
|
-
const opt = runCmd.options.find((o) => o.long === '--pain-id');
|
|
109
|
-
expect(opt).toBeDefined();
|
|
110
|
-
expect(opt?.long).toBe('--pain-id');
|
|
111
|
-
// requiredOption sets .required = true on the Option
|
|
112
|
-
expect(opt?.required).toBe(true);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('registers -w shorthand for --workspace', () => {
|
|
116
|
-
const program = freshProgram();
|
|
117
|
-
const intCmd = program.command('internalization');
|
|
118
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
119
|
-
|
|
120
|
-
const opt = runCmd.options.find((o) => o.short === '-w');
|
|
121
|
-
expect(opt).toBeDefined();
|
|
122
|
-
expect(opt?.long).toBe('--workspace');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('does NOT register --no-dry-run (no accidental negation)', () => {
|
|
126
|
-
const program = freshProgram();
|
|
127
|
-
const intCmd = program.command('internalization');
|
|
128
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
129
|
-
|
|
130
|
-
const noForm = runCmd.options.find((o) => o.long === '--no-dry-run');
|
|
131
|
-
expect(noForm).toBeUndefined();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('does NOT register --no-confirm (no accidental negation)', () => {
|
|
135
|
-
const program = freshProgram();
|
|
136
|
-
const intCmd = program.command('internalization');
|
|
137
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
138
|
-
|
|
139
|
-
const noForm = runCmd.options.find((o) => o.long === '--no-confirm');
|
|
140
|
-
expect(noForm).toBeUndefined();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// ── Parser-level tests (program.parseAsync) ───────────────────────────────
|
|
144
|
-
|
|
145
|
-
it('parses --dry-run as true', async () => {
|
|
146
|
-
const program = freshProgram();
|
|
147
|
-
const intCmd = program.command('internalization');
|
|
148
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
149
|
-
const captured: CapturedAction = { opts: null };
|
|
150
|
-
attachCapture(runCmd, captured);
|
|
151
|
-
|
|
152
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1', '--dry-run']);
|
|
153
|
-
|
|
154
|
-
expect(captured.opts).not.toBeNull();
|
|
155
|
-
expect(captured.opts?.dryRun).toBe(true);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('parses --confirm as true', async () => {
|
|
159
|
-
const program = freshProgram();
|
|
160
|
-
const intCmd = program.command('internalization');
|
|
161
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
162
|
-
const captured: CapturedAction = { opts: null };
|
|
163
|
-
attachCapture(runCmd, captured);
|
|
164
|
-
|
|
165
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1', '--confirm']);
|
|
166
|
-
|
|
167
|
-
expect(captured.opts).not.toBeNull();
|
|
168
|
-
expect(captured.opts?.confirm).toBe(true);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('parses both --dry-run and --confirm (handler enforces mutual exclusivity)', async () => {
|
|
172
|
-
// Commander parses both flags; the handler validates mutual exclusivity.
|
|
173
|
-
// This test proves the parser accepts both — the handler test in
|
|
174
|
-
// rulehost-pipeline-e2e.test.ts proves the handler rejects the combination.
|
|
175
|
-
const program = freshProgram();
|
|
176
|
-
const intCmd = program.command('internalization');
|
|
177
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
178
|
-
const captured: CapturedAction = { opts: null };
|
|
179
|
-
attachCapture(runCmd, captured);
|
|
180
|
-
|
|
181
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1', '--dry-run', '--confirm']);
|
|
182
|
-
|
|
183
|
-
expect(captured.opts).not.toBeNull();
|
|
184
|
-
expect(captured.opts?.dryRun).toBe(true);
|
|
185
|
-
expect(captured.opts?.confirm).toBe(true);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('defaults --dry-run and --confirm to undefined when neither is passed', async () => {
|
|
189
|
-
const program = freshProgram();
|
|
190
|
-
const intCmd = program.command('internalization');
|
|
191
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
192
|
-
const captured: CapturedAction = { opts: null };
|
|
193
|
-
attachCapture(runCmd, captured);
|
|
194
|
-
|
|
195
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1']);
|
|
196
|
-
|
|
197
|
-
expect(captured.opts).not.toBeNull();
|
|
198
|
-
expect(captured.opts?.dryRun).toBeUndefined();
|
|
199
|
-
expect(captured.opts?.confirm).toBeUndefined();
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('parses --json as true', async () => {
|
|
203
|
-
const program = freshProgram();
|
|
204
|
-
const intCmd = program.command('internalization');
|
|
205
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
206
|
-
const captured: CapturedAction = { opts: null };
|
|
207
|
-
attachCapture(runCmd, captured);
|
|
208
|
-
|
|
209
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1', '--json']);
|
|
210
|
-
|
|
211
|
-
expect(captured.opts).not.toBeNull();
|
|
212
|
-
expect(captured.opts?.json).toBe(true);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('parses -w shorthand for --workspace', async () => {
|
|
216
|
-
const program = freshProgram();
|
|
217
|
-
const intCmd = program.command('internalization');
|
|
218
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
219
|
-
const captured: CapturedAction = { opts: null };
|
|
220
|
-
attachCapture(runCmd, captured);
|
|
221
|
-
|
|
222
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1', '-w', '/tmp/test']);
|
|
223
|
-
|
|
224
|
-
expect(captured.opts).not.toBeNull();
|
|
225
|
-
expect(captured.opts?.workspace).toBe('/tmp/test');
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('rejects missing --pain-id (required option)', async () => {
|
|
229
|
-
const program = freshProgram();
|
|
230
|
-
const intCmd = program.command('internalization');
|
|
231
|
-
registerRunRuleHostCommand(intCmd);
|
|
232
|
-
|
|
233
|
-
// Commander should throw on missing required option
|
|
234
|
-
await expect(
|
|
235
|
-
program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--dry-run']),
|
|
236
|
-
).rejects.toThrow(/pain-id/);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('parses --channel with custom value', async () => {
|
|
240
|
-
const program = freshProgram();
|
|
241
|
-
const intCmd = program.command('internalization');
|
|
242
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
243
|
-
const captured: CapturedAction = { opts: null };
|
|
244
|
-
attachCapture(runCmd, captured);
|
|
245
|
-
|
|
246
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1', '--channel', 'prompt']);
|
|
247
|
-
|
|
248
|
-
expect(captured.opts).not.toBeNull();
|
|
249
|
-
expect(captured.opts?.channel).toBe('prompt');
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('defaults --channel to code_tool_hook', async () => {
|
|
253
|
-
const program = freshProgram();
|
|
254
|
-
const intCmd = program.command('internalization');
|
|
255
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
256
|
-
const captured: CapturedAction = { opts: null };
|
|
257
|
-
attachCapture(runCmd, captured);
|
|
258
|
-
|
|
259
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1']);
|
|
260
|
-
|
|
261
|
-
expect(captured.opts).not.toBeNull();
|
|
262
|
-
expect(captured.opts?.channel).toBe('code_tool_hook');
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it('parses --max-rounds as integer', async () => {
|
|
266
|
-
const program = freshProgram();
|
|
267
|
-
const intCmd = program.command('internalization');
|
|
268
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
269
|
-
const captured: CapturedAction = { opts: null };
|
|
270
|
-
attachCapture(runCmd, captured);
|
|
271
|
-
|
|
272
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1', '--max-rounds', '2']);
|
|
273
|
-
|
|
274
|
-
expect(captured.opts).not.toBeNull();
|
|
275
|
-
expect(captured.opts?.maxRounds).toBe(2);
|
|
276
|
-
expect(typeof captured.opts?.maxRounds).toBe('number');
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it('parses --timeout-ms as integer', async () => {
|
|
280
|
-
const program = freshProgram();
|
|
281
|
-
const intCmd = program.command('internalization');
|
|
282
|
-
const runCmd = registerRunRuleHostCommand(intCmd);
|
|
283
|
-
const captured: CapturedAction = { opts: null };
|
|
284
|
-
attachCapture(runCmd, captured);
|
|
285
|
-
|
|
286
|
-
await program.parseAsync(['node', 'pd', 'internalization', 'run-rulehost', '--pain-id', 'pain-1', '--timeout-ms', '600000']);
|
|
287
|
-
|
|
288
|
-
expect(captured.opts).not.toBeNull();
|
|
289
|
-
expect(captured.opts?.timeoutMs).toBe(600000);
|
|
290
|
-
expect(typeof captured.opts?.timeoutMs).toBe('number');
|
|
291
|
-
});
|
|
292
|
-
});
|