@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,538 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* runRuleHost production-wiring test (PRI-429) — DETERMINISTIC, NO REAL LLM.
|
|
3
|
-
*
|
|
4
|
-
* Replaces the skippable e2e test (G fix). This test verifies the production
|
|
5
|
-
* wiring is real: the CLI handler resolves per-agent config, constructs the
|
|
6
|
-
* ArtificerL2Adapter when both artificer+evaluator are enabled, and passes the
|
|
7
|
-
* CodeRuleCapability to the pipeline. It does NOT require a real LLM — it tests
|
|
8
|
-
* the dry-run path which resolves config + capability status without running
|
|
9
|
-
* the pipeline.
|
|
10
|
-
*
|
|
11
|
-
* Atomic capability contract (per user correction 2026-06-18):
|
|
12
|
-
* - Both artificer AND evaluator must be enabled → capability ON
|
|
13
|
-
* - Either disabled → capability OFF with structured reason
|
|
14
|
-
* - API key missing → capability OFF with structured reason
|
|
15
|
-
*
|
|
16
|
-
* CLI gate compliance:
|
|
17
|
-
* - --json outputs exactly one parseable JSON object
|
|
18
|
-
* - --dry-run is default; --confirm required for mutation
|
|
19
|
-
* - --dry-run and --confirm are mutually exclusive
|
|
20
|
-
* - failure paths include structured reason + nextAction
|
|
21
|
-
*
|
|
22
|
-
* ERR refs considered:
|
|
23
|
-
* - ERR-001: treat parsed JSON as unknown — JSON.parse output is validated
|
|
24
|
-
* - ERR-002: fail loud with reason — all failure paths include reason + nextAction
|
|
25
|
-
* - ERR-009: required fields fail loud — missing painId is rejected
|
|
26
|
-
* - ERR-013: Object.hasOwn for key checks — not relevant here (no untrusted key checks)
|
|
27
|
-
*/
|
|
28
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
29
|
-
import * as fs from 'node:fs';
|
|
30
|
-
import * as path from 'node:path';
|
|
31
|
-
import * as os from 'node:os';
|
|
32
|
-
import * as yaml from 'js-yaml';
|
|
33
|
-
import { handleRunRuleHost } from '../../src/commands/runtime-internalization-run-rulehost.js';
|
|
34
|
-
|
|
35
|
-
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
function mkTmpDir(prefix = 'pd-rulehost-wiring-'): string {
|
|
38
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function rmTmpDir(dir: string): void {
|
|
42
|
-
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function writeConfig(workspaceDir: string, content: object): void {
|
|
46
|
-
const pdDir = path.join(workspaceDir, '.pd');
|
|
47
|
-
fs.mkdirSync(pdDir, { recursive: true });
|
|
48
|
-
fs.writeFileSync(
|
|
49
|
-
path.join(pdDir, 'config.yaml'),
|
|
50
|
-
yaml.dump(content, { lineWidth: -1 }),
|
|
51
|
-
'utf8',
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Type guard: narrows `unknown` to `Record<string, unknown>` without `as`. */
|
|
56
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
57
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
|
61
|
-
if (!isRecord(value)) {
|
|
62
|
-
throw new Error(`${label} is not a record`);
|
|
63
|
-
}
|
|
64
|
-
return value;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Config with both artificer+evaluator enabled, pi-ai profile, API key env set. */
|
|
68
|
-
function makeCapabilityOnConfig(workspaceDir: string): object {
|
|
69
|
-
return {
|
|
70
|
-
version: 1,
|
|
71
|
-
features: {
|
|
72
|
-
prompt: { enabled: true, category: 'core' },
|
|
73
|
-
code_tool_hook: { enabled: true, category: 'core' },
|
|
74
|
-
defer_archive: { enabled: true, category: 'core' },
|
|
75
|
-
code_rule_capability: { enabled: true, category: 'core' },
|
|
76
|
-
},
|
|
77
|
-
workspace: { default: workspaceDir },
|
|
78
|
-
runtimeProfiles: {
|
|
79
|
-
'pi-ai.test': {
|
|
80
|
-
type: 'pi-ai',
|
|
81
|
-
provider: 'openrouter',
|
|
82
|
-
model: 'anthropic/claude-sonnet-4',
|
|
83
|
-
apiKeyEnv: 'TEST_RULEHOST_API_KEY',
|
|
84
|
-
baseUrl: 'https://openrouter.ai/api/v1',
|
|
85
|
-
timeoutMs: 300000,
|
|
86
|
-
maxRetries: 3,
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
internalAgents: {
|
|
90
|
-
defaultRuntime: 'pi-ai.test',
|
|
91
|
-
agents: {
|
|
92
|
-
diagnostician: { enabled: true, runtimeProfile: 'pi-ai.test' },
|
|
93
|
-
dreamer: { enabled: true },
|
|
94
|
-
philosopher: { enabled: true },
|
|
95
|
-
scribe: { enabled: true },
|
|
96
|
-
artificer: { enabled: true, runtimeProfile: 'pi-ai.test' },
|
|
97
|
-
evaluator: { enabled: true, runtimeProfile: 'pi-ai.test' },
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Config with artificer disabled (capability must be OFF). */
|
|
104
|
-
function makeArtificerDisabledConfig(workspaceDir: string): object {
|
|
105
|
-
const cfg = makeCapabilityOnConfig(workspaceDir);
|
|
106
|
-
const internalAgents = requireRecord(Reflect.get(cfg, 'internalAgents'), 'internalAgents');
|
|
107
|
-
const agents = requireRecord(Reflect.get(internalAgents, 'agents'), 'internalAgents.agents');
|
|
108
|
-
Reflect.set(agents, 'artificer', { enabled: false });
|
|
109
|
-
return cfg;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Config with evaluator disabled (capability must be OFF). */
|
|
113
|
-
function makeEvaluatorDisabledConfig(workspaceDir: string): object {
|
|
114
|
-
const cfg = makeCapabilityOnConfig(workspaceDir);
|
|
115
|
-
const internalAgents = requireRecord(Reflect.get(cfg, 'internalAgents'), 'internalAgents');
|
|
116
|
-
const agents = requireRecord(Reflect.get(internalAgents, 'agents'), 'internalAgents.agents');
|
|
117
|
-
Reflect.set(agents, 'evaluator', { enabled: false });
|
|
118
|
-
return cfg;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ── Test setup ─────────────────────────────────────────────────────────────
|
|
122
|
-
|
|
123
|
-
describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', () => {
|
|
124
|
-
let stdoutSpy: ReturnType<typeof vi.spyOn>;
|
|
125
|
-
let stderrSpy: ReturnType<typeof vi.spyOn>;
|
|
126
|
-
let originalExitCode: number | undefined;
|
|
127
|
-
let originalApiKey: string | undefined;
|
|
128
|
-
let originalBaseKey: string | undefined;
|
|
129
|
-
let originalArtificerKey: string | undefined;
|
|
130
|
-
let tmpDirs: string[];
|
|
131
|
-
|
|
132
|
-
beforeEach(() => {
|
|
133
|
-
stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
134
|
-
stderrSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
135
|
-
originalExitCode = process.exitCode;
|
|
136
|
-
process.exitCode = undefined;
|
|
137
|
-
originalApiKey = process.env.TEST_RULEHOST_API_KEY;
|
|
138
|
-
originalBaseKey = process.env.TEST_RULEHOST_BASE_KEY;
|
|
139
|
-
originalArtificerKey = process.env.TEST_RULEHOST_ARTIFICER_KEY;
|
|
140
|
-
tmpDirs = [];
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
afterEach(() => {
|
|
144
|
-
stdoutSpy.mockRestore();
|
|
145
|
-
stderrSpy.mockRestore();
|
|
146
|
-
process.exitCode = originalExitCode;
|
|
147
|
-
if (originalApiKey === undefined) {
|
|
148
|
-
delete process.env.TEST_RULEHOST_API_KEY;
|
|
149
|
-
} else {
|
|
150
|
-
process.env.TEST_RULEHOST_API_KEY = originalApiKey;
|
|
151
|
-
}
|
|
152
|
-
if (originalBaseKey === undefined) {
|
|
153
|
-
delete process.env.TEST_RULEHOST_BASE_KEY;
|
|
154
|
-
} else {
|
|
155
|
-
process.env.TEST_RULEHOST_BASE_KEY = originalBaseKey;
|
|
156
|
-
}
|
|
157
|
-
if (originalArtificerKey === undefined) {
|
|
158
|
-
delete process.env.TEST_RULEHOST_ARTIFICER_KEY;
|
|
159
|
-
} else {
|
|
160
|
-
process.env.TEST_RULEHOST_ARTIFICER_KEY = originalArtificerKey;
|
|
161
|
-
}
|
|
162
|
-
for (const dir of tmpDirs) {
|
|
163
|
-
rmTmpDir(dir);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
function makeWorkspace(configFactory: (dir: string) => object): string {
|
|
168
|
-
const dir = mkTmpDir();
|
|
169
|
-
tmpDirs.push(dir);
|
|
170
|
-
writeConfig(dir, configFactory(dir));
|
|
171
|
-
// PRI-780: v2-only generation — every dry-run in this suite carries a
|
|
172
|
-
// valid Owner-labelled behavior-examples file so the capability assertions
|
|
173
|
-
// isolate their intended variable (agent/flag/key state), not the
|
|
174
|
-
// behavior_examples_missing refusal.
|
|
175
|
-
fs.writeFileSync(
|
|
176
|
-
path.join(dir, 'behavior-examples.json'),
|
|
177
|
-
JSON.stringify({ ownerDesiredOutcome: 'block system path writes', sourceNegativeToolCallId: 1, positiveToolCallIds: [2] }),
|
|
178
|
-
'utf8',
|
|
179
|
-
);
|
|
180
|
-
return dir;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** Extract the single JSON object written to stdout. Fails if not exactly one write. */
|
|
184
|
-
function parseJsonOutput(): Record<string, unknown> {
|
|
185
|
-
expect(stdoutSpy).toHaveBeenCalledTimes(1);
|
|
186
|
-
const raw = stdoutSpy.mock.calls[0][0] as string;
|
|
187
|
-
const parsed: unknown = JSON.parse(raw);
|
|
188
|
-
return requireRecord(parsed, 'stdout JSON');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ── Capability ON: both agents enabled, API key set ──────────────────────
|
|
192
|
-
|
|
193
|
-
it('dry-run reports code_rule_capability: ON when artificer+evaluator enabled and API key set', async () => {
|
|
194
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
195
|
-
const workspace = makeWorkspace(makeCapabilityOnConfig);
|
|
196
|
-
|
|
197
|
-
await handleRunRuleHost({
|
|
198
|
-
workspace,
|
|
199
|
-
painId: 'pain-wiring-001',
|
|
200
|
-
behaviorExamples: 'behavior-examples.json',
|
|
201
|
-
dryRun: true,
|
|
202
|
-
json: true,
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
const output = parseJsonOutput();
|
|
206
|
-
const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
|
|
207
|
-
expect(output.status).toBe('dry_run');
|
|
208
|
-
expect(codeRuleCapability.enabled).toBe(true);
|
|
209
|
-
expect(codeRuleCapability.disabledReason).toBeUndefined();
|
|
210
|
-
expect(String(output.capabilityStatus)).toContain('ON');
|
|
211
|
-
// exitCode must NOT be set for dry_run success
|
|
212
|
-
expect(process.exitCode).toBeUndefined();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('keeps code_rule_capability ON even when the feature flag is omitted (PRI-435: core flag)', async () => {
|
|
216
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
217
|
-
const workspace = makeWorkspace((dir) => {
|
|
218
|
-
const config = makeCapabilityOnConfig(dir);
|
|
219
|
-
const features = Reflect.get(config, 'features');
|
|
220
|
-
if (features === null || typeof features !== 'object') throw new Error('features fixture missing');
|
|
221
|
-
Reflect.deleteProperty(features, 'code_rule_capability');
|
|
222
|
-
return config;
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
await handleRunRuleHost({ workspace, painId: 'pain-flag-default', behaviorExamples: 'behavior-examples.json', dryRun: true, json: true });
|
|
226
|
-
|
|
227
|
-
const output = parseJsonOutput();
|
|
228
|
-
const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
|
|
229
|
-
expect(output.status).toBe('dry_run');
|
|
230
|
-
// PRI-435: code_rule_capability is now a core flag — defaults ON even when omitted from config.
|
|
231
|
-
// It cannot be disabled by config. The capability is ON when artificer+evaluator are configured.
|
|
232
|
-
expect(codeRuleCapability).toEqual(expect.objectContaining({ enabled: true }));
|
|
233
|
-
expect(codeRuleCapability.disabledReason).toBeUndefined();
|
|
234
|
-
expect(String(output.capabilityStatus)).toContain('ON');
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('PRI-435: explicit emergency disable via code_rule_capability.enabled=false is observable', async () => {
|
|
238
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
239
|
-
const workspace = makeWorkspace((dir) => {
|
|
240
|
-
const config = makeCapabilityOnConfig(dir);
|
|
241
|
-
const features = Reflect.get(config, 'features');
|
|
242
|
-
if (features === null || typeof features !== 'object') throw new Error('features fixture missing');
|
|
243
|
-
// Explicitly disable the flag for emergency disable
|
|
244
|
-
Reflect.set(features, 'code_rule_capability', { enabled: false, category: 'core' });
|
|
245
|
-
return config;
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
await handleRunRuleHost({ workspace, painId: 'pain-emergency-disable', behaviorExamples: 'behavior-examples.json', dryRun: true, json: true });
|
|
249
|
-
|
|
250
|
-
const output = parseJsonOutput();
|
|
251
|
-
const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
|
|
252
|
-
expect(output.status).toBe('dry_run');
|
|
253
|
-
// PRI-435: Emergency disable via code_rule_capability.enabled=false is preserved.
|
|
254
|
-
// The capability is OFF with a structured reason.
|
|
255
|
-
expect(codeRuleCapability).toEqual(expect.objectContaining({ enabled: false }));
|
|
256
|
-
expect(String(codeRuleCapability.disabledReason)).toContain('feature flag');
|
|
257
|
-
expect(String(output.capabilityStatus)).toContain('OFF');
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it('reports the resolved runtime profile for every executed agent', async () => {
|
|
261
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
262
|
-
const workspace = makeWorkspace(makeCapabilityOnConfig);
|
|
263
|
-
|
|
264
|
-
await handleRunRuleHost({ workspace, painId: 'pain-profiles', dryRun: true, json: true });
|
|
265
|
-
|
|
266
|
-
const output = parseJsonOutput();
|
|
267
|
-
expect(output.agentRuntimeProfiles).toEqual({
|
|
268
|
-
dreamer: 'pi-ai.test',
|
|
269
|
-
philosopher: 'pi-ai.test',
|
|
270
|
-
scribe: 'pi-ai.test',
|
|
271
|
-
artificer: 'pi-ai.test',
|
|
272
|
-
evaluator: 'pi-ai.test',
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('fails loud before mutation when philosopher is disabled', async () => {
|
|
277
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
278
|
-
const workspace = makeWorkspace((dir) => {
|
|
279
|
-
const config = makeCapabilityOnConfig(dir);
|
|
280
|
-
const internalAgents = Reflect.get(config, 'internalAgents');
|
|
281
|
-
if (internalAgents === null || typeof internalAgents !== 'object') throw new Error('internalAgents fixture missing');
|
|
282
|
-
const agents = Reflect.get(internalAgents, 'agents');
|
|
283
|
-
if (agents === null || typeof agents !== 'object') throw new Error('agents fixture missing');
|
|
284
|
-
Reflect.set(agents, 'philosopher', { enabled: false });
|
|
285
|
-
return config;
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
await handleRunRuleHost({ workspace, painId: 'pain-disabled-philosopher', confirm: true, json: true });
|
|
289
|
-
|
|
290
|
-
// PRI-461: readiness gate catches disabled required agents BEFORE adapter
|
|
291
|
-
// construction, returning status='refused' with a structured reason instead
|
|
292
|
-
// of the old opaque 'agent_runtime_resolution_failed' error.
|
|
293
|
-
const output = parseJsonOutput();
|
|
294
|
-
expect(isRecord(output)).toBe(true);
|
|
295
|
-
if (!isRecord(output)) {
|
|
296
|
-
throw new Error('output is not a record');
|
|
297
|
-
}
|
|
298
|
-
expect(output.status).toBe('refused');
|
|
299
|
-
expect(String(output.reason)).toContain('philosopher');
|
|
300
|
-
expect(typeof output.nextAction).toBe('string');
|
|
301
|
-
expect(process.exitCode).toBe(1);
|
|
302
|
-
expect(fs.existsSync(path.join(workspace, '.state', 'runtime-v2.sqlite'))).toBe(false);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('rejects fractional numeric handler options as non-integers', async () => {
|
|
306
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
307
|
-
await handleRunRuleHost({
|
|
308
|
-
workspace: makeWorkspace(makeCapabilityOnConfig),
|
|
309
|
-
painId: 'pain-fractional',
|
|
310
|
-
maxRounds: 1.5,
|
|
311
|
-
dryRun: true,
|
|
312
|
-
json: true,
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
const output = parseJsonOutput();
|
|
316
|
-
expect(output.status).toBe('failed');
|
|
317
|
-
expect(String(output.reason)).toContain('invalid --max-rounds');
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
// ── Capability OFF: artificer disabled ───────────────────────────────────
|
|
321
|
-
|
|
322
|
-
it('dry-run reports code_rule_capability: OFF when artificer disabled', async () => {
|
|
323
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
324
|
-
const workspace = makeWorkspace(makeArtificerDisabledConfig);
|
|
325
|
-
|
|
326
|
-
await handleRunRuleHost({
|
|
327
|
-
workspace,
|
|
328
|
-
painId: 'pain-wiring-002',
|
|
329
|
-
behaviorExamples: 'behavior-examples.json',
|
|
330
|
-
dryRun: true,
|
|
331
|
-
json: true,
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
const output = parseJsonOutput();
|
|
335
|
-
const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
|
|
336
|
-
expect(output.status).toBe('dry_run');
|
|
337
|
-
expect(codeRuleCapability.enabled).toBe(false);
|
|
338
|
-
expect(String(codeRuleCapability.disabledReason)).toContain('artificer');
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// ── Capability OFF: evaluator disabled ───────────────────────────────────
|
|
342
|
-
|
|
343
|
-
it('dry-run reports code_rule_capability: OFF when evaluator disabled', async () => {
|
|
344
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
345
|
-
const workspace = makeWorkspace(makeEvaluatorDisabledConfig);
|
|
346
|
-
|
|
347
|
-
await handleRunRuleHost({
|
|
348
|
-
workspace,
|
|
349
|
-
painId: 'pain-wiring-003',
|
|
350
|
-
behaviorExamples: 'behavior-examples.json',
|
|
351
|
-
dryRun: true,
|
|
352
|
-
json: true,
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
const output = parseJsonOutput();
|
|
356
|
-
const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
|
|
357
|
-
expect(output.status).toBe('dry_run');
|
|
358
|
-
expect(codeRuleCapability.enabled).toBe(false);
|
|
359
|
-
expect(String(codeRuleCapability.disabledReason)).toContain('evaluator');
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
// ── Capability OFF: API key not set ──────────────────────────────────────
|
|
363
|
-
|
|
364
|
-
it('dry-run reports code_rule_capability: OFF when artificer API key env var is not set', async () => {
|
|
365
|
-
// Base adapter uses BASE_API_KEY (set); artificer uses ARTIFICER_API_KEY (NOT set).
|
|
366
|
-
// This isolates the capability check from the base adapter resolution.
|
|
367
|
-
process.env.TEST_RULEHOST_BASE_KEY = 'sk-base-key-12345';
|
|
368
|
-
delete process.env.TEST_RULEHOST_ARTIFICER_KEY;
|
|
369
|
-
const dir = mkTmpDir();
|
|
370
|
-
tmpDirs.push(dir);
|
|
371
|
-
writeConfig(dir, {
|
|
372
|
-
version: 1,
|
|
373
|
-
features: {
|
|
374
|
-
prompt: { enabled: true, category: 'core' },
|
|
375
|
-
code_tool_hook: { enabled: true, category: 'core' },
|
|
376
|
-
defer_archive: { enabled: true, category: 'core' },
|
|
377
|
-
code_rule_capability: { enabled: true, category: 'core' },
|
|
378
|
-
},
|
|
379
|
-
workspace: { default: dir },
|
|
380
|
-
runtimeProfiles: {
|
|
381
|
-
'pi-ai.base': {
|
|
382
|
-
type: 'pi-ai',
|
|
383
|
-
provider: 'openrouter',
|
|
384
|
-
model: 'anthropic/claude-sonnet-4',
|
|
385
|
-
apiKeyEnv: 'TEST_RULEHOST_BASE_KEY',
|
|
386
|
-
baseUrl: 'https://openrouter.ai/api/v1',
|
|
387
|
-
timeoutMs: 300000,
|
|
388
|
-
maxRetries: 3,
|
|
389
|
-
},
|
|
390
|
-
'pi-ai.artificer': {
|
|
391
|
-
type: 'pi-ai',
|
|
392
|
-
provider: 'openrouter',
|
|
393
|
-
model: 'anthropic/claude-sonnet-4',
|
|
394
|
-
apiKeyEnv: 'TEST_RULEHOST_ARTIFICER_KEY',
|
|
395
|
-
baseUrl: 'https://openrouter.ai/api/v1',
|
|
396
|
-
timeoutMs: 300000,
|
|
397
|
-
maxRetries: 3,
|
|
398
|
-
},
|
|
399
|
-
},
|
|
400
|
-
internalAgents: {
|
|
401
|
-
defaultRuntime: 'pi-ai.base',
|
|
402
|
-
agents: {
|
|
403
|
-
diagnostician: { enabled: true, runtimeProfile: 'pi-ai.base' },
|
|
404
|
-
dreamer: { enabled: true },
|
|
405
|
-
philosopher: { enabled: true },
|
|
406
|
-
scribe: { enabled: true },
|
|
407
|
-
artificer: { enabled: true, runtimeProfile: 'pi-ai.artificer' },
|
|
408
|
-
evaluator: { enabled: true, runtimeProfile: 'pi-ai.base' },
|
|
409
|
-
},
|
|
410
|
-
},
|
|
411
|
-
});
|
|
412
|
-
fs.writeFileSync(
|
|
413
|
-
path.join(dir, 'behavior-examples.json'),
|
|
414
|
-
JSON.stringify({ ownerDesiredOutcome: 'block system path writes', sourceNegativeToolCallId: 1, positiveToolCallIds: [2] }),
|
|
415
|
-
'utf8',
|
|
416
|
-
);
|
|
417
|
-
|
|
418
|
-
await handleRunRuleHost({
|
|
419
|
-
workspace: dir,
|
|
420
|
-
painId: 'pain-wiring-004',
|
|
421
|
-
behaviorExamples: 'behavior-examples.json',
|
|
422
|
-
dryRun: true,
|
|
423
|
-
json: true,
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
const output = parseJsonOutput();
|
|
427
|
-
const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
|
|
428
|
-
expect(output.status).toBe('dry_run');
|
|
429
|
-
expect(codeRuleCapability.enabled).toBe(false);
|
|
430
|
-
expect(String(codeRuleCapability.disabledReason)).toContain('TEST_RULEHOST_ARTIFICER_KEY');
|
|
431
|
-
expect(String(codeRuleCapability.disabledReason)).toContain('not set');
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
// ── CLI gate: mutual exclusivity ─────────────────────────────────────────
|
|
435
|
-
|
|
436
|
-
it('rejects --dry-run and --confirm together with structured reason', async () => {
|
|
437
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
438
|
-
const workspace = makeWorkspace(makeCapabilityOnConfig);
|
|
439
|
-
|
|
440
|
-
await handleRunRuleHost({
|
|
441
|
-
workspace,
|
|
442
|
-
painId: 'pain-wiring-005',
|
|
443
|
-
dryRun: true,
|
|
444
|
-
confirm: true,
|
|
445
|
-
json: true,
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
const output = parseJsonOutput();
|
|
449
|
-
expect(output.status).toBe('failed');
|
|
450
|
-
expect(String(output.reason)).toContain('mutually exclusive');
|
|
451
|
-
expect(output.nextAction).toBeTruthy();
|
|
452
|
-
expect(process.exitCode).toBe(1);
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// ── CLI gate: missing painId ─────────────────────────────────────────────
|
|
456
|
-
|
|
457
|
-
it('rejects missing painId with structured reason', async () => {
|
|
458
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
459
|
-
const workspace = makeWorkspace(makeCapabilityOnConfig);
|
|
460
|
-
|
|
461
|
-
await handleRunRuleHost({
|
|
462
|
-
workspace,
|
|
463
|
-
painId: '',
|
|
464
|
-
dryRun: true,
|
|
465
|
-
json: true,
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
const output = parseJsonOutput();
|
|
469
|
-
expect(output.status).toBe('failed');
|
|
470
|
-
expect(String(output.reason)).toContain('painId');
|
|
471
|
-
expect(output.nextAction).toBeTruthy();
|
|
472
|
-
expect(process.exitCode).toBe(1);
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
// ── CLI gate: JSON output purity ─────────────────────────────────────────
|
|
476
|
-
|
|
477
|
-
it('--json dry-run outputs exactly one parseable JSON object (no banners, no extra lines)', async () => {
|
|
478
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
479
|
-
const workspace = makeWorkspace(makeCapabilityOnConfig);
|
|
480
|
-
|
|
481
|
-
await handleRunRuleHost({
|
|
482
|
-
workspace,
|
|
483
|
-
painId: 'pain-wiring-006',
|
|
484
|
-
dryRun: true,
|
|
485
|
-
json: true,
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
// Exactly one write call to stdout
|
|
489
|
-
expect(stdoutSpy).toHaveBeenCalledTimes(1);
|
|
490
|
-
const raw = stdoutSpy.mock.calls[0][0] as string;
|
|
491
|
-
// Must parse as a single JSON object
|
|
492
|
-
expect(() => JSON.parse(raw)).not.toThrow();
|
|
493
|
-
const parsed = JSON.parse(raw);
|
|
494
|
-
expect(typeof parsed).toBe('object');
|
|
495
|
-
expect(parsed).not.toBeNull();
|
|
496
|
-
expect(Array.isArray(parsed)).toBe(false);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
// ── CLI gate: default is dry-run (no --confirm = dry-run) ────────────────
|
|
500
|
-
|
|
501
|
-
it('defaults to dry-run when neither --dry-run nor --confirm is passed', async () => {
|
|
502
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
503
|
-
const workspace = makeWorkspace(makeCapabilityOnConfig);
|
|
504
|
-
|
|
505
|
-
await handleRunRuleHost({
|
|
506
|
-
workspace,
|
|
507
|
-
painId: 'pain-wiring-007',
|
|
508
|
-
json: true,
|
|
509
|
-
// Neither dryRun nor confirm — should default to dry-run
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
const output = parseJsonOutput();
|
|
513
|
-
expect(output.status).toBe('dry_run');
|
|
514
|
-
// Must NOT set exitCode for dry_run
|
|
515
|
-
expect(process.exitCode).toBeUndefined();
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
// ── CLI gate: unsupported channel ────────────────────────────────────────
|
|
519
|
-
|
|
520
|
-
it('rejects unsupported channel with structured reason', async () => {
|
|
521
|
-
process.env.TEST_RULEHOST_API_KEY = 'sk-test-key-12345';
|
|
522
|
-
const workspace = makeWorkspace(makeCapabilityOnConfig);
|
|
523
|
-
|
|
524
|
-
await handleRunRuleHost({
|
|
525
|
-
workspace,
|
|
526
|
-
painId: 'pain-wiring-008',
|
|
527
|
-
channel: 'invalid_channel',
|
|
528
|
-
dryRun: true,
|
|
529
|
-
json: true,
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
const output = parseJsonOutput();
|
|
533
|
-
expect(output.status).toBe('failed');
|
|
534
|
-
expect(String(output.reason)).toContain('unsupported channel');
|
|
535
|
-
expect(output.nextAction).toBeTruthy();
|
|
536
|
-
expect(process.exitCode).toBe(1);
|
|
537
|
-
});
|
|
538
|
-
});
|