@principles/pd-cli 1.132.0 → 1.134.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/__tests__/intent-flag-wiring.test.js +74 -3
- package/dist/commands/__tests__/intent-flag-wiring.test.js.map +1 -1
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +2 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +1 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +60 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +1 -0
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js +11 -4
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +1 -1
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +9 -0
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +1 -1
- package/dist/commands/admission-gate.d.ts +24 -0
- package/dist/commands/admission-gate.d.ts.map +1 -0
- package/dist/commands/admission-gate.js +29 -0
- package/dist/commands/admission-gate.js.map +1 -0
- package/dist/commands/candidate.d.ts.map +1 -1
- package/dist/commands/candidate.js +72 -6
- package/dist/commands/candidate.js.map +1 -1
- package/dist/commands/console.d.ts.map +1 -1
- package/dist/commands/console.js +40 -0
- package/dist/commands/console.js.map +1 -1
- package/dist/commands/demo-story-a.d.ts.map +1 -1
- package/dist/commands/demo-story-a.js +4 -2
- package/dist/commands/demo-story-a.js.map +1 -1
- package/dist/commands/diagnose.d.ts.map +1 -1
- package/dist/commands/diagnose.js +155 -8
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/errors-list.d.ts +25 -0
- package/dist/commands/errors-list.d.ts.map +1 -0
- package/dist/commands/errors-list.js +368 -0
- package/dist/commands/errors-list.js.map +1 -0
- package/dist/commands/health-codex.d.ts +7 -0
- package/dist/commands/health-codex.d.ts.map +1 -0
- package/dist/commands/health-codex.js +209 -0
- package/dist/commands/health-codex.js.map +1 -0
- package/dist/commands/health.d.ts +24 -0
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +48 -0
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/intent.d.ts +21 -4
- package/dist/commands/intent.d.ts.map +1 -1
- package/dist/commands/intent.js +88 -27
- package/dist/commands/intent.js.map +1 -1
- package/dist/commands/pain-evidence.d.ts +6 -0
- package/dist/commands/pain-evidence.d.ts.map +1 -1
- package/dist/commands/pain-evidence.js +6 -0
- package/dist/commands/pain-evidence.js.map +1 -1
- package/dist/commands/pain-record.d.ts.map +1 -1
- package/dist/commands/pain-record.js +21 -0
- package/dist/commands/pain-record.js.map +1 -1
- package/dist/commands/pain-retry.d.ts +4 -0
- package/dist/commands/pain-retry.d.ts.map +1 -1
- package/dist/commands/pain-retry.js +327 -32
- package/dist/commands/pain-retry.js.map +1 -1
- package/dist/commands/proven-channel-baseline.d.ts.map +1 -1
- package/dist/commands/proven-channel-baseline.js +4 -2
- package/dist/commands/proven-channel-baseline.js.map +1 -1
- package/dist/commands/runtime-activation.d.ts +30 -0
- package/dist/commands/runtime-activation.d.ts.map +1 -1
- package/dist/commands/runtime-activation.js +567 -11
- package/dist/commands/runtime-activation.js.map +1 -1
- package/dist/commands/runtime-canary.d.ts.map +1 -1
- package/dist/commands/runtime-canary.js +32 -7
- package/dist/commands/runtime-canary.js.map +1 -1
- package/dist/commands/runtime-health-snapshot.d.ts.map +1 -1
- package/dist/commands/runtime-health-snapshot.js +4 -2
- package/dist/commands/runtime-health-snapshot.js.map +1 -1
- package/dist/commands/runtime-init.d.ts +43 -0
- package/dist/commands/runtime-init.d.ts.map +1 -0
- package/dist/commands/runtime-init.js +264 -0
- package/dist/commands/runtime-init.js.map +1 -0
- package/dist/commands/runtime-internalization-context-trace.d.ts +25 -0
- package/dist/commands/runtime-internalization-context-trace.d.ts.map +1 -0
- package/dist/commands/runtime-internalization-context-trace.js +374 -0
- package/dist/commands/runtime-internalization-context-trace.js.map +1 -0
- package/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-enqueue-successors.js +3 -1
- package/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
- package/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-integrity.js +4 -2
- package/dist/commands/runtime-internalization-integrity.js.map +1 -1
- package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-once.js +20 -5
- package/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.d.ts +1 -0
- package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.js +149 -6
- package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
- package/dist/commands/runtime-uat.d.ts.map +1 -1
- package/dist/commands/runtime-uat.guard.test.js +13 -15
- package/dist/commands/runtime-uat.guard.test.js.map +1 -1
- package/dist/commands/runtime-uat.js +4 -3
- package/dist/commands/runtime-uat.js.map +1 -1
- package/dist/config-reader.js +1 -1
- package/dist/config-reader.js.map +1 -1
- package/dist/index.js +54 -78
- package/dist/index.js.map +1 -1
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +2 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +1 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.js +304 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.js.map +1 -0
- package/dist/services/__tests__/rulehost-readiness.test.js.map +1 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -1
- package/dist/services/config-doctor.d.ts.map +1 -1
- package/dist/services/config-doctor.js +9 -2
- package/dist/services/config-doctor.js.map +1 -1
- package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
- package/dist/services/pain-flood-simulation-runner.js +9 -0
- package/dist/services/pain-flood-simulation-runner.js.map +1 -1
- package/dist/services/pd-config-loader.js +2 -2
- package/dist/services/pd-config-loader.js.map +1 -1
- package/dist/services/proven-channel-baseline-runner.d.ts.map +1 -1
- package/dist/services/proven-channel-baseline-runner.js +10 -2
- package/dist/services/proven-channel-baseline-runner.js.map +1 -1
- package/dist/services/quality-scorecard/data-extractor.js.map +1 -1
- package/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
- package/dist/services/rulehost-pipeline-runner.d.ts +77 -1
- package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/dist/services/rulehost-pipeline-runner.js +111 -7
- package/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/dist/services/rulehost-readiness.js.map +1 -1
- package/dist/services/runtime-adapter-resolver.d.ts +1 -0
- package/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
- package/dist/services/runtime-adapter-resolver.js +3 -0
- package/dist/services/runtime-adapter-resolver.js.map +1 -1
- package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
- package/dist/services/synthetic-baseline-runner.js +11 -0
- package/dist/services/synthetic-baseline-runner.js.map +1 -1
- package/dist/utils/production-workspace-guard.d.ts +4 -4
- package/dist/utils/production-workspace-guard.d.ts.map +1 -1
- package/dist/utils/production-workspace-guard.js +19 -16
- package/dist/utils/production-workspace-guard.js.map +1 -1
- package/dist/utils/production-workspace-guard.test.js +54 -19
- package/dist/utils/production-workspace-guard.test.js.map +1 -1
- package/package.json +8 -5
- package/scripts/migrate-illegal-expected-decision.ts +252 -0
- package/src/commands/__tests__/intent-flag-wiring.test.ts +88 -3
- package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +73 -0
- package/src/commands/__tests__/rulecode-flag-wiring.test.ts +31 -24
- package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +12 -0
- package/src/commands/admission-gate.ts +30 -0
- package/src/commands/candidate.ts +74 -6
- package/src/commands/console.ts +40 -0
- package/src/commands/demo-story-a.ts +4 -2
- package/src/commands/diagnose.ts +158 -10
- package/src/commands/errors-list.ts +440 -0
- package/src/commands/health-codex.ts +249 -0
- package/src/commands/health.ts +60 -0
- package/src/commands/intent.ts +98 -29
- package/src/commands/pain-evidence.ts +6 -0
- package/src/commands/pain-record.ts +21 -0
- package/src/commands/pain-retry.ts +353 -35
- package/src/commands/proven-channel-baseline.ts +4 -2
- package/src/commands/runtime-activation.ts +657 -12
- package/src/commands/runtime-canary.ts +31 -5
- package/src/commands/runtime-health-snapshot.ts +4 -2
- package/src/commands/runtime-init.ts +305 -0
- package/src/commands/runtime-internalization-context-trace.ts +445 -0
- package/src/commands/runtime-internalization-enqueue-successors.ts +3 -1
- package/src/commands/runtime-internalization-integrity.ts +4 -2
- package/src/commands/runtime-internalization-run-once.ts +20 -5
- package/src/commands/runtime-internalization-run-rulehost.ts +159 -6
- package/src/commands/runtime-uat.guard.test.ts +14 -16
- package/src/commands/runtime-uat.ts +4 -3
- package/src/config-reader.ts +1 -1
- package/src/index.ts +69 -78
- package/src/services/__tests__/evaluator-runner-deps.test.ts +341 -0
- package/src/services/config-doctor.ts +9 -2
- package/src/services/pain-flood-simulation-runner.ts +9 -0
- package/src/services/pd-config-loader.ts +2 -2
- package/src/services/proven-channel-baseline-runner.ts +10 -3
- package/src/services/rulehost-pipeline-runner.ts +157 -6
- package/src/services/runtime-adapter-resolver.ts +4 -0
- package/src/services/synthetic-baseline-runner.ts +11 -0
- package/src/utils/production-workspace-guard.test.ts +62 -20
- package/src/utils/production-workspace-guard.ts +19 -16
- package/tests/bdd/cli-contract.steps.ts +343 -0
- package/tests/bdd/context-trace.steps.ts +229 -0
- package/tests/bdd/support/gherkin-loader.ts +147 -0
- package/tests/bdd/support/repo-root.ts +62 -0
- package/tests/bdd/support/vitest-bdd.ts +179 -0
- package/tests/commands/admission-gate-coverage.test.ts +79 -0
- package/tests/commands/candidate-audit-repair.test.ts +77 -0
- package/tests/commands/candidate-intake.test.ts +155 -10
- package/tests/commands/candidate-internalization-backfill.test.ts +30 -0
- package/tests/commands/cli-help-snapshot.test.ts +15 -1
- package/tests/commands/console-open.test.ts +19 -0
- package/tests/commands/diagnose.test.ts +619 -14
- package/tests/commands/errors-list.test.ts +678 -0
- package/tests/commands/health-codex.test.ts +243 -0
- package/tests/commands/health-host-wiring.test.ts +76 -0
- package/tests/commands/intent.test.ts +4 -4
- package/tests/commands/pain-record.test.ts +80 -0
- package/tests/commands/pain-retry.test.ts +94 -3
- package/tests/commands/run-rulehost-handler.test.ts +133 -0
- package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +227 -0
- package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +167 -0
- package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +262 -0
- package/tests/commands/runtime-activation-list-flag-wiring.test.ts +193 -0
- package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +222 -0
- package/tests/commands/runtime-activation.test.ts +1090 -5
- package/tests/commands/runtime-canary.test.ts +50 -0
- package/tests/commands/runtime-features.test.ts +2 -0
- package/tests/commands/runtime-init-empty-workspace.test.ts +234 -0
- package/tests/commands/runtime-init.test.ts +346 -0
- package/tests/commands/runtime-internalization-context-trace.test.ts +110 -0
- package/tests/commands/runtime-internalization.test.ts +2 -2
- package/tests/config-reader.test.ts +6 -5
- package/tests/e2e/cross-package-acceptance.test.ts +55 -13
- package/tests/services/mainline-snapshot-assembler.test.ts +6 -3
- package/tests/services/resolve-runtime-from-pd-config.test.ts +2 -0
- package/tests/services/rulehost-pipeline-runner.test.ts +1 -1
- package/vitest.config.ts +1 -1
|
@@ -27,6 +27,7 @@ import { loadLedger, getLedgerFilePathPublic } from '@principles/core/principle-
|
|
|
27
27
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
28
28
|
import { createRemediationResult, remediationAction } from './remediation-output.js';
|
|
29
29
|
import type { RemediationResult } from './remediation-output.js';
|
|
30
|
+
import { checkAdmissionGate } from './admission-gate.js';
|
|
30
31
|
|
|
31
32
|
// ── Interfaces ────────────────────────────────────────────────────────────────
|
|
32
33
|
|
|
@@ -353,6 +354,7 @@ export async function handleCandidateInternalize(opts: CandidateInternalizeOptio
|
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
const seed = buildDreamerSeedFromCandidate(candidate, { route: decision.route, ready: decision.ready, sourcePainId });
|
|
357
|
+
// eslint-disable-next-line no-restricted-syntax -- 'in' required for discriminated union narrowing (BridgeTaskSeed | BridgeDecision)
|
|
356
358
|
if ('decision' in seed) {
|
|
357
359
|
const decisionResult = seed as { decision: string; reason?: string; taskId?: string };
|
|
358
360
|
const reason = decisionResult.decision === 'already_exists'
|
|
@@ -494,6 +496,13 @@ export async function handleCandidateShow(opts: CandidateShowOptions): Promise<v
|
|
|
494
496
|
if (result.ledgerEntryId) {
|
|
495
497
|
console.log(` Ledger Entry: ${result.ledgerEntryId}`);
|
|
496
498
|
}
|
|
499
|
+
// F10-1: surface dangling sourceRunId warning in text mode (rc-9).
|
|
500
|
+
if (result.warning) {
|
|
501
|
+
console.log(` ⚠ WARNING: ${result.warning}`);
|
|
502
|
+
}
|
|
503
|
+
if (result.nextAction) {
|
|
504
|
+
console.log(` Next Action: ${result.nextAction}`);
|
|
505
|
+
}
|
|
497
506
|
console.log('');
|
|
498
507
|
} finally {
|
|
499
508
|
await stateManager.close();
|
|
@@ -520,12 +529,37 @@ export async function handleCandidateIntake(opts: CandidateIntakeOptions): Promi
|
|
|
520
529
|
const ledgerAdapter = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
|
|
521
530
|
const service = new CandidateIntakeService({ stateManager, ledgerAdapter });
|
|
522
531
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
532
|
+
// Fetch candidate once for admission gate check and downstream paths.
|
|
533
|
+
const candidate = await stateManager.getCandidate(opts.candidateId);
|
|
534
|
+
if (!candidate) {
|
|
535
|
+
console.error(`Candidate not found: ${opts.candidateId}`);
|
|
536
|
+
process.exit(1);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// PRI-442 Stage 4: admission gate check — refuse non-admitted candidates
|
|
541
|
+
// to prevent CLI from bypassing the production admission gate.
|
|
542
|
+
const admissionBlock = checkAdmissionGate(candidate);
|
|
543
|
+
if (admissionBlock) {
|
|
544
|
+
const result = {
|
|
545
|
+
candidateId: opts.candidateId,
|
|
546
|
+
status: 'refused',
|
|
547
|
+
admissionDecision: admissionBlock.decision,
|
|
548
|
+
reason: admissionBlock.reason,
|
|
549
|
+
nextAction: admissionBlock.nextAction,
|
|
550
|
+
};
|
|
551
|
+
if (opts.json) {
|
|
552
|
+
console.log(JSON.stringify(result, null, 2));
|
|
553
|
+
} else {
|
|
554
|
+
console.error(`Admission gate refused candidate ${opts.candidateId}: ${admissionBlock.decision}`);
|
|
555
|
+
console.error(` Reason: ${admissionBlock.reason}`);
|
|
556
|
+
console.error(` Next Action: ${admissionBlock.nextAction}`);
|
|
528
557
|
}
|
|
558
|
+
process.exit(1);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (opts.dryRun) {
|
|
529
563
|
const artifact = await stateManager.getArtifact(candidate.artifactId);
|
|
530
564
|
if (!artifact) {
|
|
531
565
|
console.error(`Artifact not found for candidate: ${opts.candidateId}`);
|
|
@@ -564,7 +598,6 @@ export async function handleCandidateIntake(opts: CandidateIntakeOptions): Promi
|
|
|
564
598
|
const entry = await service.intake(opts.candidateId);
|
|
565
599
|
|
|
566
600
|
// Check if already consumed before this call
|
|
567
|
-
const candidate = await stateManager.getCandidate(opts.candidateId);
|
|
568
601
|
if (candidate?.status === 'consumed') {
|
|
569
602
|
const infoMessage = `Candidate ${opts.candidateId} was already consumed. Ledger entry: ${entry.id}`;
|
|
570
603
|
if (opts.json) {
|
|
@@ -724,6 +757,30 @@ export async function handleCandidateRepair(opts: CandidateRepairOptions): Promi
|
|
|
724
757
|
process.exit(1);
|
|
725
758
|
}
|
|
726
759
|
|
|
760
|
+
// PRI-442 Stage 4: admission gate check — even for consumed candidates,
|
|
761
|
+
// re-verify admission before re-intaking to prevent bypassing the gate.
|
|
762
|
+
// A consumed candidate that would fail the gate indicates data corruption
|
|
763
|
+
// or a prior bypass; refuse and surface the reason.
|
|
764
|
+
const admissionBlock = checkAdmissionGate(candidate);
|
|
765
|
+
if (admissionBlock) {
|
|
766
|
+
const result = {
|
|
767
|
+
candidateId: opts.candidateId,
|
|
768
|
+
status: 'refused',
|
|
769
|
+
admissionDecision: admissionBlock.decision,
|
|
770
|
+
reason: admissionBlock.reason,
|
|
771
|
+
nextAction: admissionBlock.nextAction,
|
|
772
|
+
};
|
|
773
|
+
if (opts.json) {
|
|
774
|
+
console.log(JSON.stringify(result, null, 2));
|
|
775
|
+
} else {
|
|
776
|
+
console.error(`Admission gate refused candidate ${opts.candidateId}: ${admissionBlock.decision}`);
|
|
777
|
+
console.error(` Reason: ${admissionBlock.reason}`);
|
|
778
|
+
console.error(` Next Action: ${admissionBlock.nextAction}`);
|
|
779
|
+
}
|
|
780
|
+
process.exit(1);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
727
784
|
const ledgerAdapter = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
|
|
728
785
|
const service = new CandidateIntakeService({ stateManager, ledgerAdapter });
|
|
729
786
|
|
|
@@ -931,6 +988,7 @@ export async function handleCandidateInternalizationBackfill(opts: CandidateBack
|
|
|
931
988
|
continue;
|
|
932
989
|
}
|
|
933
990
|
const seed = buildDreamerSeedFromCandidate(candidate, { route: decision.route, ready: decision.ready, sourcePainId });
|
|
991
|
+
// eslint-disable-next-line no-restricted-syntax -- 'in' required for discriminated union narrowing (BridgeTaskSeed | BridgeDecision)
|
|
934
992
|
if ('decision' in seed) {
|
|
935
993
|
output.deferred++;
|
|
936
994
|
const decisionResult = seed as { decision: string; reason?: string; taskId?: string };
|
|
@@ -998,6 +1056,15 @@ export async function handleCandidateInternalizationBackfill(opts: CandidateBack
|
|
|
998
1056
|
continue;
|
|
999
1057
|
}
|
|
1000
1058
|
|
|
1059
|
+
// PRI-442 Stage 4: admission gate check — refuse non-admitted pending
|
|
1060
|
+
// candidates to prevent backfill from bypassing the admission gate.
|
|
1061
|
+
const admissionBlock = checkAdmissionGate(candidate);
|
|
1062
|
+
if (admissionBlock) {
|
|
1063
|
+
output.intakeFailed++;
|
|
1064
|
+
output.results.push({ candidateId, route: 'unknown', status: 'intake_failed', reason: `Admission gate refused: ${admissionBlock.reason}`, statusBefore: 'pending', statusAfter: 'pending', intakeDecision: 'skipped', seedDecision: 'skipped', nextAction: admissionBlock.nextAction });
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1001
1068
|
const recommendation = resolveCandidateRecommendation(candidate, stateManager, candidateId);
|
|
1002
1069
|
const decision = decideInternalizationRoute(recommendation as Parameters<typeof decideInternalizationRoute>[0]);
|
|
1003
1070
|
|
|
@@ -1010,6 +1077,7 @@ export async function handleCandidateInternalizationBackfill(opts: CandidateBack
|
|
|
1010
1077
|
continue;
|
|
1011
1078
|
}
|
|
1012
1079
|
const seed = buildDreamerSeedFromCandidate(candidate, { route: decision.route, ready: decision.ready, sourcePainId });
|
|
1080
|
+
// eslint-disable-next-line no-restricted-syntax -- 'in' required for discriminated union narrowing (BridgeTaskSeed | BridgeDecision)
|
|
1013
1081
|
if ('decision' in seed) {
|
|
1014
1082
|
output.deferred++;
|
|
1015
1083
|
const decisionResult = seed as { decision: string; reason?: string; taskId?: string };
|
package/src/commands/console.ts
CHANGED
|
@@ -72,6 +72,20 @@ export async function handleConsole(opts: ConsoleOptions = {}): Promise<void> {
|
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// EP-06 regression guard (PR #1169): verify web UI bundle exists before launch.
|
|
76
|
+
// Without dist/web/index.html the server returns 404 "Run npm run build:ui first".
|
|
77
|
+
const webIndex = path.join(consoleDir, 'dist', 'web', 'index.html');
|
|
78
|
+
if (!fs.existsSync(webIndex)) {
|
|
79
|
+
const msg = `Console web UI not found at ${webIndex}. The console bundle is corrupted. Re-run installer.`;
|
|
80
|
+
if (opts.json) {
|
|
81
|
+
console.log(JSON.stringify({ success: false, reason: msg, nextAction: 'npx create-principles-disciple' }));
|
|
82
|
+
} else {
|
|
83
|
+
console.error(msg);
|
|
84
|
+
}
|
|
85
|
+
process.exit(1);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
75
89
|
const port = opts.port || '3100';
|
|
76
90
|
const host = '127.0.0.1';
|
|
77
91
|
const args = [serverEntry, '--workspace', workspaceDir, '--port', port, '--host', host];
|
|
@@ -305,6 +319,32 @@ export async function handleConsoleOpen(opts: ConsoleOpenOptions = {}): Promise<
|
|
|
305
319
|
return;
|
|
306
320
|
}
|
|
307
321
|
|
|
322
|
+
// EP-06 regression guard (PR #1169): verify web UI bundle exists before launch.
|
|
323
|
+
// Without dist/web/index.html the server returns 404 "Run npm run build:ui first"
|
|
324
|
+
// — a fatal first-impression bug for new users.
|
|
325
|
+
const webIndex = path.join(consoleDir, 'dist', 'web', 'index.html');
|
|
326
|
+
if (!fs.existsSync(webIndex)) {
|
|
327
|
+
const result: ConsoleLaunchResult = {
|
|
328
|
+
status: 'failed',
|
|
329
|
+
url: '',
|
|
330
|
+
port: 0,
|
|
331
|
+
host: '127.0.0.1',
|
|
332
|
+
workspaceDir,
|
|
333
|
+
reused: false,
|
|
334
|
+
browserOpened: false,
|
|
335
|
+
reason: 'console_web_ui_missing',
|
|
336
|
+
nextAction: `Re-run installer: npx create-principles-disciple (expected ${webIndex})`,
|
|
337
|
+
};
|
|
338
|
+
if (opts.json) {
|
|
339
|
+
console.log(JSON.stringify(result, null, 2));
|
|
340
|
+
} else {
|
|
341
|
+
console.error(`error: ${result.reason}`);
|
|
342
|
+
console.error(`next: ${result.nextAction}`);
|
|
343
|
+
}
|
|
344
|
+
process.exit(1);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
308
348
|
// 4) Read auth token for health probes (PD_CONSOLE_TOKEN)
|
|
309
349
|
const token = opts.token ?? process.env.PD_CONSOLE_TOKEN;
|
|
310
350
|
|
|
@@ -183,8 +183,10 @@ export async function handleDemoStoryA(opts: DemoStoryAOptions): Promise<void> {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
if (result.status !== 'passed') {
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
if (!opts.json) {
|
|
187
|
+
console.error('');
|
|
188
|
+
console.error(`FAIL: status=${result.status}`);
|
|
189
|
+
}
|
|
188
190
|
process.exitCode = 1;
|
|
189
191
|
}
|
|
190
192
|
} finally {
|
package/src/commands/diagnose.ts
CHANGED
|
@@ -29,13 +29,23 @@ import {
|
|
|
29
29
|
run as diagnoseRun,
|
|
30
30
|
status as diagnoseStatus,
|
|
31
31
|
PrincipleTreeLedgerAdapter,
|
|
32
|
+
buildDreamerSeedFromCandidate,
|
|
33
|
+
CANDIDATE_KIND_TO_ROUTE,
|
|
34
|
+
ROUTE_CHANNEL_MAP,
|
|
35
|
+
MVP_ENABLED_CHANNELS,
|
|
32
36
|
} from '@principles/core/runtime-v2';
|
|
33
37
|
import type { PDRuntimeAdapter, OutputLanguage } from '@principles/core/runtime-v2';
|
|
34
38
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
35
39
|
import { readOutputLanguageFromWorkspace } from '../config-reader.js';
|
|
36
40
|
import { loadPdConfig, computeFlagsFromLoadResult } from '../services/pd-config-loader.js';
|
|
37
41
|
import { isFeatureEnabled, SPLIT_PIPELINE_TOTAL_TIMEOUT_MS } from '@principles/core/runtime-v2';
|
|
42
|
+
import { createHash } from 'node:crypto';
|
|
43
|
+
/** Layer 0 content-hash (design §6.1); injected so diag writers can attach predecessorSummary hashes. */
|
|
44
|
+
const contentHashFn = (input: string): string => createHash('sha256').update(input).digest('hex');
|
|
38
45
|
import { resolveRuntimeAdapterFromConfig, ConfigResolutionError } from '../services/runtime-adapter-resolver.js';
|
|
46
|
+
import { resolveRuntimeFromPdConfig } from '../services/resolve-runtime-from-pd-config.js';
|
|
47
|
+
import { checkAdmissionGate } from './admission-gate.js';
|
|
48
|
+
import { resolveSourcePainIdFromDiagnostician } from './candidate.js';
|
|
39
49
|
import * as path from 'path';
|
|
40
50
|
|
|
41
51
|
function validateStalledThreshold(val: unknown): number | undefined {
|
|
@@ -198,7 +208,38 @@ export async function handleDiagnoseRun(opts: DiagnoseRunOptions): Promise<void>
|
|
|
198
208
|
return;
|
|
199
209
|
}
|
|
200
210
|
|
|
201
|
-
|
|
211
|
+
// Resolve runtime kind. P1 fix (mirrors pain-retry.ts): pd diagnose run
|
|
212
|
+
// must NOT default to test-double. Without --runtime, the split pipeline
|
|
213
|
+
// would validate the test-double's stale DiagnosticianOutputV1-shaped
|
|
214
|
+
// payload against DiagRootCauseOutputV1Schema and fail with
|
|
215
|
+
// max_attempts_exceeded — silently producing failed/fake diagnostic data
|
|
216
|
+
// in a real workspace. Fall back to .pd/config.yaml first; refuse if
|
|
217
|
+
// nothing is bound. (rc-9-no-silent-fallback / ERR-002; EP-03, EP-04)
|
|
218
|
+
let runtimeKind = opts.runtime;
|
|
219
|
+
if (!runtimeKind) {
|
|
220
|
+
const resolved = resolveRuntimeFromPdConfig(workspaceDir);
|
|
221
|
+
if (!isRuntimeConfigError(resolved.result) && resolved.result.runtimeKind) {
|
|
222
|
+
({ runtimeKind } = resolved.result);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!runtimeKind) {
|
|
227
|
+
const missingRuntime = {
|
|
228
|
+
ok: false,
|
|
229
|
+
reason: 'missing_runtime',
|
|
230
|
+
message:
|
|
231
|
+
'No --runtime specified and no .pd/config.yaml runtime binding found. pd diagnose run must not default to test-double to prevent fake data in real workspaces.',
|
|
232
|
+
nextAction: `Specify --runtime explicitly: pd diagnose run --task-id ${opts.taskId} --runtime pi-ai --provider <provider> --model <model> --apiKeyEnv <ENV>`,
|
|
233
|
+
};
|
|
234
|
+
if (opts.json) {
|
|
235
|
+
console.log(JSON.stringify(missingRuntime, null, 2));
|
|
236
|
+
} else {
|
|
237
|
+
console.error(`error: ${missingRuntime.message}`);
|
|
238
|
+
console.error(`Next action: ${missingRuntime.nextAction}`);
|
|
239
|
+
}
|
|
240
|
+
process.exit(1);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
202
243
|
|
|
203
244
|
const stateManager = new RuntimeStateManager({ workspaceDir });
|
|
204
245
|
|
|
@@ -353,22 +394,37 @@ export async function handleDiagnoseRun(opts: DiagnoseRunOptions): Promise<void>
|
|
|
353
394
|
const featureFlags = computeFlagsFromLoadResult(configLoadResult);
|
|
354
395
|
const isSplitPipeline = isFeatureEnabled(featureFlags, 'diagnostician_split_pipeline');
|
|
355
396
|
const pipelineTimeoutMs = isSplitPipeline ? SPLIT_PIPELINE_TOTAL_TIMEOUT_MS : 300_000;
|
|
397
|
+
// BUG-1 (PRI-442): extract effectiveConfig so ADR-0019 LLM rate-limit
|
|
398
|
+
// degradation (isDegradationEnabled in base-peer-runner) can read the
|
|
399
|
+
// diagnostician_llm_degradation feature flag. Without this, the runners
|
|
400
|
+
// receive no effectiveConfig and degradation silently never fires.
|
|
401
|
+
// CR-1 (CodeRabbit P2, rc-9): warn when config load failed so the
|
|
402
|
+
// fallback to defaults is observable — no silent degradation.
|
|
403
|
+
if (!configLoadResult.ok) {
|
|
404
|
+
const errSummary = configLoadResult.errors
|
|
405
|
+
.map((e) => `${e.path}: ${e.reason}`)
|
|
406
|
+
.join('; ');
|
|
407
|
+
console.warn(
|
|
408
|
+
`[pd diagnose] .pd/config.yaml at ${configLoadResult.configPath} is malformed — falling back to default feature flags. Errors: ${errSummary}. Next action: ${configLoadResult.errors[0]?.nextAction ?? 'fix config errors and retry'}`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
const effectiveConfig = configLoadResult.ok ? configLoadResult.effective : configLoadResult.defaults;
|
|
356
412
|
|
|
357
413
|
let runner: DiagnosticianRunnerLike;
|
|
358
414
|
if (isSplitPipeline) {
|
|
359
415
|
const resolvedKind = typeof runtimeAdapter.kind === 'function' ? runtimeAdapter.kind() : runtimeKind;
|
|
360
416
|
const perStageTimeoutMs = pipelineTimeoutMs / 3;
|
|
361
417
|
const rootCauseRunner = new DiagRootCauseRunner(
|
|
362
|
-
{ stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, validator: new DefaultDiagRootCauseValidator(), contextAssembler },
|
|
363
|
-
{ owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs },
|
|
418
|
+
{ stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, validator: new DefaultDiagRootCauseValidator(), contextAssembler, contentHashFn },
|
|
419
|
+
{ owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs, effectiveConfig },
|
|
364
420
|
);
|
|
365
421
|
const distillerRunner = new DiagDistillerRunner(
|
|
366
|
-
{ stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, validator: new DefaultDiagDistillerValidator() },
|
|
367
|
-
{ owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs },
|
|
422
|
+
{ stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, validator: new DefaultDiagDistillerValidator(), contentHashFn },
|
|
423
|
+
{ owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs, effectiveConfig },
|
|
368
424
|
);
|
|
369
425
|
const routerRunner = new DiagRouterRunner(
|
|
370
|
-
{ stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, committer },
|
|
371
|
-
{ owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs },
|
|
426
|
+
{ stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, committer, contentHashFn },
|
|
427
|
+
{ owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs, effectiveConfig },
|
|
372
428
|
);
|
|
373
429
|
|
|
374
430
|
runner = new SplitDiagnosticianRunner({
|
|
@@ -463,6 +519,23 @@ export async function handleDiagnoseRun(opts: DiagnoseRunOptions): Promise<void>
|
|
|
463
519
|
const intakeService = new CandidateIntakeService({ stateManager, ledgerAdapter });
|
|
464
520
|
|
|
465
521
|
for (const candidate of candidates) {
|
|
522
|
+
// PRI-503: admission gate check — refuse non-admitted candidates before
|
|
523
|
+
// intake, mirroring candidate.ts (intake/repair/backfill). Without this,
|
|
524
|
+
// `pd diagnose --intake` could bypass the gate and intake unreviewed
|
|
525
|
+
// candidates. cli-5-failure-no-mutation: refusal path does not call
|
|
526
|
+
// intake(), does not update candidate status, does not write ledger.
|
|
527
|
+
// cli-6-output-next-action: refusal result carries admissionBlock.nextAction.
|
|
528
|
+
const admissionBlock = checkAdmissionGate(candidate);
|
|
529
|
+
if (admissionBlock) {
|
|
530
|
+
intakeFailed = true;
|
|
531
|
+
intakeResults.push({
|
|
532
|
+
candidateId: candidate.candidateId,
|
|
533
|
+
status: 'intake_failed',
|
|
534
|
+
error: `Admission gate refused: ${admissionBlock.reason}`,
|
|
535
|
+
nextAction: admissionBlock.nextAction,
|
|
536
|
+
});
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
466
539
|
try {
|
|
467
540
|
const entry = await intakeService.intake(candidate.candidateId);
|
|
468
541
|
if (candidate.status !== 'consumed') {
|
|
@@ -486,11 +559,79 @@ export async function handleDiagnoseRun(opts: DiagnoseRunOptions): Promise<void>
|
|
|
486
559
|
}
|
|
487
560
|
}
|
|
488
561
|
|
|
562
|
+
// Defect-004 fix: seed dreamer task for each consumed candidate whose
|
|
563
|
+
// recommendation_kind routes to an MVP-enabled internalization channel.
|
|
564
|
+
// Without this, the chain stops at 'consumed' and never reaches internalization
|
|
565
|
+
// (11 of 18 consumed candidates hit this in production). Mirrors the pattern
|
|
566
|
+
// in PainSignalBridge.onDiagnosisComplete (pain-signal-bridge.ts L310-338).
|
|
567
|
+
//
|
|
568
|
+
// DEFECT-005 (PRI-514): the dreamer seed loop is gated ONLY by `opts.intake`,
|
|
569
|
+
// NOT by a global `intakeFailed` flag. A single defer candidate whose intake
|
|
570
|
+
// is refused by the admission gate must NOT poison dreamer seed for sibling
|
|
571
|
+
// candidates that were successfully consumed (EP-03 / ERR-089 sibling-branch
|
|
572
|
+
// defect). Per-candidate eligibility is checked inside the loop via the
|
|
573
|
+
// intake result's status. `intakeFailed` is retained solely for the
|
|
574
|
+
// nextAction/exit-code signaling below (partial failure still exits non-zero).
|
|
575
|
+
if (opts.intake !== false) {
|
|
576
|
+
for (const candidate of candidates) {
|
|
577
|
+
const kind = candidate.recommendationKind;
|
|
578
|
+
if (kind === 'defer' || kind === 'implementation') continue;
|
|
579
|
+
const intakeResult = intakeResults.find((r) => r.candidateId === candidate.candidateId);
|
|
580
|
+
if (!intakeResult || intakeResult.status !== 'consumed') continue;
|
|
581
|
+
try {
|
|
582
|
+
const route = CANDIDATE_KIND_TO_ROUTE[kind ?? ''];
|
|
583
|
+
if (!route) continue;
|
|
584
|
+
const channel = ROUTE_CHANNEL_MAP[route];
|
|
585
|
+
const ready = !!channel && MVP_ENABLED_CHANNELS.has(channel);
|
|
586
|
+
// BUG-2 (PRI-442): resolve sourcePainId from the diagnostician task
|
|
587
|
+
// chain (rc-6 lineage consistency). The candidate itself doesn't carry
|
|
588
|
+
// sourcePainId; it must be resolved from the diagnostician task's
|
|
589
|
+
// diagnosticJson. ERR-004: never invent lineage.
|
|
590
|
+
const sourcePainId = await resolveSourcePainIdFromDiagnostician(stateManager, candidate);
|
|
591
|
+
const seed = buildDreamerSeedFromCandidate(candidate, { route, ready, sourcePainId: sourcePainId ?? undefined });
|
|
592
|
+
// eslint-disable-next-line no-restricted-syntax -- 'in' required for discriminated union narrowing (BridgeTaskSeed | BridgeDecision)
|
|
593
|
+
if ('decision' in seed) continue; // not_internalizable or invalid — skip
|
|
594
|
+
const existingTask = await stateManager.getTask(seed.taskId);
|
|
595
|
+
if (!existingTask) {
|
|
596
|
+
await stateManager.createTask({
|
|
597
|
+
taskId: seed.taskId,
|
|
598
|
+
taskKind: seed.taskKind,
|
|
599
|
+
inputRef: '',
|
|
600
|
+
status: seed.status,
|
|
601
|
+
attemptCount: seed.attemptCount,
|
|
602
|
+
maxAttempts: seed.maxAttempts,
|
|
603
|
+
diagnosticJson: seed.diagnosticJson,
|
|
604
|
+
});
|
|
605
|
+
intakeResults.push({
|
|
606
|
+
candidateId: candidate.candidateId,
|
|
607
|
+
status: 'dreamer_seeded',
|
|
608
|
+
nextAction: `pd task show ${seed.taskId} | pd dreamer run --task-id ${seed.taskId}`,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
} catch (seedErr) {
|
|
612
|
+
intakeResults.push({
|
|
613
|
+
candidateId: candidate.candidateId,
|
|
614
|
+
status: 'dreamer_seed_failed',
|
|
615
|
+
error: seedErr instanceof Error ? seedErr.message : String(seedErr),
|
|
616
|
+
nextAction: `pd candidate internalize --candidate-id ${candidate.candidateId}`,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
489
622
|
if (opts.json) {
|
|
490
623
|
const candidateIds = candidates.map((c) => c.candidateId);
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
624
|
+
// CodeRabbit review fix: reflect dreamer seed status in nextAction so
|
|
625
|
+
// the owner knows whether internalization has already started or still
|
|
626
|
+
// needs manual candidate internalize. Previously the message always
|
|
627
|
+
// said "NOT started automatically" even when dreamer tasks were seeded.
|
|
628
|
+
const dreamerSeededCount = intakeResults.filter((r) => r.status === 'dreamer_seeded').length;
|
|
629
|
+
const dreamerSeedFailedCount = intakeResults.filter((r) => r.status === 'dreamer_seed_failed').length;
|
|
630
|
+
const internalizeNextAction = dreamerSeededCount > 0
|
|
631
|
+
? `Dreamer tasks seeded automatically for ${dreamerSeededCount} candidate(s). To continue, run the dreamer tasks shown in intake.candidates[].${dreamerSeedFailedCount > 0 ? ` (${dreamerSeedFailedCount} candidate(s) failed seeding — see intake.candidates[] for retry guidance.)` : ''}`
|
|
632
|
+
: candidateIds.length > 0
|
|
633
|
+
? `Candidates generated but internalization has NOT started automatically. To begin internalization, run:\n ${candidateIds.map((id) => `pd candidate internalize --candidate-id ${id} --workspace "${workspaceDir}"`).join('\n ')}`
|
|
634
|
+
: 'No candidates were generated from this diagnosis.';
|
|
494
635
|
const jsonOutput = {
|
|
495
636
|
...result,
|
|
496
637
|
intake: {
|
|
@@ -534,6 +675,13 @@ export async function handleDiagnoseRun(opts: DiagnoseRunOptions): Promise<void>
|
|
|
534
675
|
} else if (ir.status === 'intake_failed') {
|
|
535
676
|
console.log(` ${ir.candidateId}: INTAKE FAILED — ${ir.error}`);
|
|
536
677
|
console.log(` Next action: pd candidate intake --candidate-id ${ir.candidateId} --workspace "${workspaceDir}"`);
|
|
678
|
+
} else if (ir.status === 'dreamer_seeded') {
|
|
679
|
+
// CodeRabbit review fix: surface dreamer seed success in TTY output
|
|
680
|
+
console.log(` ${ir.candidateId}: dreamer seeded — ${ir.nextAction}`);
|
|
681
|
+
} else if (ir.status === 'dreamer_seed_failed') {
|
|
682
|
+
// CodeRabbit review fix: surface dreamer seed failure in TTY output
|
|
683
|
+
console.log(` ${ir.candidateId}: DREAMER SEED FAILED — ${ir.error}`);
|
|
684
|
+
console.log(` Next action: ${ir.nextAction}`);
|
|
537
685
|
}
|
|
538
686
|
}
|
|
539
687
|
}
|