create-principles-disciple 1.144.11 → 1.144.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/_release/product-identity.json +1 -1
- package/codex-adapter/dist/pd-hook.js +23 -5
- package/codex-adapter/dist/worker/workspace-worker.d.ts +6 -0
- package/codex-adapter/dist/worker/workspace-worker.js +7 -0
- package/codex-adapter/package.json +1 -1
- package/console/dist/server/models/ApprovalsConsoleModel.d.ts +1 -1
- package/console/dist/server/models/ApprovalsConsoleModel.js +4 -4
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.d.ts +5 -5
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +18 -18
- package/console/dist/shared/prompt-injection-contract.d.ts +5 -5
- package/console/dist/ui/pages/focus/FocusPage.js +1 -1
- package/console/dist/ui/pages/focus/focus-validation.js +1 -1
- package/console/dist/ui/utils/approval-warning-localization.js +1 -1
- package/console/dist/ui/utils/validators.d.ts +1 -1
- package/console/dist/ui/utils/validators.js +2 -2
- package/console/package.json +3 -3
- package/core/dist/principle-tree-ledger.d.ts +50 -0
- package/core/dist/principle-tree-ledger.d.ts.map +1 -1
- package/core/dist/principle-tree-ledger.js +133 -1
- package/core/dist/principle-tree-ledger.js.map +1 -1
- package/core/dist/runtime-v2/adapter/output-schema-registry.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/output-schema-registry.js +4 -0
- package/core/dist/runtime-v2/adapter/output-schema-registry.js.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts +24 -0
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js +36 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js.map +1 -1
- package/core/dist/runtime-v2/bridge-result-shaper.d.ts +8 -0
- package/core/dist/runtime-v2/bridge-result-shaper.d.ts.map +1 -1
- package/core/dist/runtime-v2/bridge-result-shaper.js +61 -3
- package/core/dist/runtime-v2/bridge-result-shaper.js.map +1 -1
- package/core/dist/runtime-v2/candidate-intake-service.d.ts +152 -3
- package/core/dist/runtime-v2/candidate-intake-service.d.ts.map +1 -1
- package/core/dist/runtime-v2/candidate-intake-service.js +405 -47
- package/core/dist/runtime-v2/candidate-intake-service.js.map +1 -1
- package/core/dist/runtime-v2/candidate-intake.d.ts +44 -0
- package/core/dist/runtime-v2/candidate-intake.d.ts.map +1 -1
- package/core/dist/runtime-v2/candidate-intake.js +7 -0
- package/core/dist/runtime-v2/candidate-intake.js.map +1 -1
- package/core/dist/runtime-v2/config/index.d.ts +2 -2
- package/core/dist/runtime-v2/config/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/index.js +1 -1
- package/core/dist/runtime-v2/config/index.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts +7 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.js +11 -0
- package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-effective.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-effective.js +24 -1
- package/core/dist/runtime-v2/config/pd-config-effective.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-redaction.js +2 -0
- package/core/dist/runtime-v2/config/pd-config-redaction.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.d.ts +39 -0
- package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-validate.js +44 -0
- package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +14 -1
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +21 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/pain-signal-bridge.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-signal-bridge.js +107 -2
- package/core/dist/runtime-v2/pain-signal-bridge.js.map +1 -1
- package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts +29 -14
- package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js +215 -46
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
- package/core/dist/runtime-v2/principle-reuse/reuse-domain.d.ts +89 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-domain.d.ts.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-domain.js +16 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-domain.js.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-output.d.ts +40 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-output.d.ts.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-output.js +97 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-output.js.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-runner.d.ts +111 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-runner.d.ts.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-runner.js +232 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-runner.js.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-proposal.d.ts +87 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-proposal.d.ts.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-proposal.js +99 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-proposal.js.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-retrieval.d.ts +36 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-retrieval.d.ts.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-retrieval.js +118 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-retrieval.js.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-shortlist.d.ts +40 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-shortlist.d.ts.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-shortlist.js +184 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-shortlist.js.map +1 -0
- package/core/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.d.ts +7 -0
- package/core/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.js.map +1 -1
- package/core/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.d.ts +1 -0
- package/core/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.js +12 -0
- package/core/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.js.map +1 -1
- package/core/dist/runtime-v2/store/runtime-state-manager.d.ts +5 -0
- package/core/dist/runtime-v2/store/runtime-state-manager.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/runtime-state-manager.js +8 -0
- package/core/dist/runtime-v2/store/runtime-state-manager.js.map +1 -1
- package/core/dist/runtime-v2/types/principle-schema.d.ts +65 -0
- package/core/dist/runtime-v2/types/principle-schema.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/principle-schema.js +14 -0
- package/core/dist/runtime-v2/types/principle-schema.js.map +1 -1
- package/core/dist/runtime-v2/types/principle-tree-store.d.ts +12 -0
- package/core/dist/runtime-v2/types/principle-tree-store.d.ts.map +1 -1
- package/core/dist/telemetry-event.d.ts +2 -2
- package/core/dist/telemetry-event.d.ts.map +1 -1
- package/core/dist/telemetry-event.js +13 -0
- package/core/dist/telemetry-event.js.map +1 -1
- package/core/package.json +2 -2
- package/host-runtime/dist/index.d.ts +2 -0
- package/host-runtime/dist/index.js +1 -0
- package/host-runtime/dist/production-rulehost-gate.d.ts +1 -0
- package/host-runtime/dist/production-rulehost-gate.js +1 -1
- package/host-runtime/dist/prompt-injection-projection.d.ts +5 -5
- package/host-runtime/dist/prompt-injection-projection.js +4 -4
- package/host-runtime/package.json +1 -1
- package/package.json +3 -3
- package/pd-cli/dist/commands/candidate.d.ts +27 -0
- package/pd-cli/dist/commands/candidate.d.ts.map +1 -1
- package/pd-cli/dist/commands/candidate.js +508 -14
- package/pd-cli/dist/commands/candidate.js.map +1 -1
- package/pd-cli/dist/commands/config-doctor.d.ts.map +1 -1
- package/pd-cli/dist/commands/config-doctor.js +14 -0
- package/pd-cli/dist/commands/config-doctor.js.map +1 -1
- package/pd-cli/dist/commands/diagnose.d.ts.map +1 -1
- package/pd-cli/dist/commands/diagnose.js +84 -14
- package/pd-cli/dist/commands/diagnose.js.map +1 -1
- package/pd-cli/dist/commands/pain-retry.d.ts.map +1 -1
- package/pd-cli/dist/commands/pain-retry.js +97 -14
- package/pd-cli/dist/commands/pain-retry.js.map +1 -1
- package/pd-cli/dist/index.js +16 -2
- package/pd-cli/dist/index.js.map +1 -1
- package/pd-cli/dist/services/config-doctor.d.ts +11 -0
- package/pd-cli/dist/services/config-doctor.d.ts.map +1 -1
- package/pd-cli/dist/services/config-doctor.js +6 -0
- package/pd-cli/dist/services/config-doctor.js.map +1 -1
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.d.ts +14 -0
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.d.ts.map +1 -1
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.js +20 -4
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
- package/pd-cli/dist/services/runtime-adapter-resolver.d.ts +6 -0
- package/pd-cli/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
- package/pd-cli/dist/services/runtime-adapter-resolver.js +11 -2
- package/pd-cli/dist/services/runtime-adapter-resolver.js.map +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +550 -546
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +5 -5
- package/release-manager/package.json +3 -3
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* pd candidate commands — Principle candidate inspection, intake, audit, repair.
|
|
2
|
+
* pd candidate commands — Principle candidate inspection, intake, audit, repair, reuse review.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* pd candidate list --task-id <taskId> --workspace <path> [--json]
|
|
6
6
|
* pd candidate show <candidateId> --workspace <path> [--json]
|
|
7
7
|
* pd candidate intake --candidate-id <id> [--workspace <path>] [--json] [--dry-run]
|
|
8
|
+
* pd candidate review --candidate-id <id> [--workspace <path>] [--json]
|
|
9
|
+
* pd candidate review --candidate-id <id> --decide reuse --principle-id <id> --reason "..." [--json]
|
|
10
|
+
* pd candidate review --candidate-id <id> --decide create --reason "..." [--json]
|
|
8
11
|
* pd candidate audit --workspace <path> [--json]
|
|
9
12
|
* pd candidate repair --candidate-id <id> --workspace <path> [--json]
|
|
10
13
|
* pd candidate route --candidate-id <id> --workspace <path> [--json]
|
|
11
14
|
*/
|
|
12
15
|
import { randomUUID } from 'crypto';
|
|
13
16
|
import * as path from 'path';
|
|
14
|
-
import { RuntimeStateManager, SqliteConnection, candidateList, candidateShow, CandidateIntakeService, CandidateIntakeError, INTAKE_ERROR_CODES, decideInternalizationRoute, buildDreamerSeedFromCandidate, findExistingDreamerTask, parseSeedSourcePainId, PrincipleTreeLedgerAdapter,
|
|
17
|
+
import { RuntimeStateManager, SqliteConnection, candidateList, candidateShow, CandidateIntakeService, CandidateIntakeError, INTAKE_ERROR_CODES, decideInternalizationRoute, buildDreamerSeedFromCandidate, findExistingDreamerTask, parseSeedSourcePainId, PrincipleTreeLedgerAdapter,
|
|
18
|
+
// PRI-917 PR3A: the review surface consumes the SAME proposal/semantics
|
|
19
|
+
// machinery as the intake gate (audit C4 — one implementation, no drift).
|
|
20
|
+
buildReuseProposal, buildReuseShortlist, extractIntakeRecommendation, isPrincipleLedgerEligibleKind, resolveOwnerIdentity, defaultOwnerIdentityHomeDir, ReuseEvaluationRunner, ReuseEvaluationError, storeEmitter, } from '@principles/core/runtime-v2';
|
|
15
21
|
import { loadLedger, getLedgerFilePathPublic } from '@principles/core/principle-tree-ledger';
|
|
16
22
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
23
|
+
import { loadPdConfig } from '../services/pd-config-loader.js';
|
|
24
|
+
import { resolveRuntimeAdapterFromConfig } from '../services/runtime-adapter-resolver.js';
|
|
17
25
|
import { resolvePromptFullPipelineSeedMode } from '../services/pd-config-loader.js';
|
|
18
26
|
import { createRemediationResult, remediationAction } from './remediation-output.js';
|
|
19
27
|
import { checkAdmissionGate } from './admission-gate.js';
|
|
@@ -380,6 +388,243 @@ export async function handleCandidateShow(opts) {
|
|
|
380
388
|
}
|
|
381
389
|
}
|
|
382
390
|
// ── Intake ───────────────────────────────────────────────────────────────────
|
|
391
|
+
// ── Shared intake/decision finalizers (PRI-917 PR3A) ─────────────────────────
|
|
392
|
+
/**
|
|
393
|
+
* PRI-917 PR3A (surface audit C2): unify the candidate terminal state after a
|
|
394
|
+
* successful ledger write OR a recorded reuse resolution — always through the
|
|
395
|
+
* guarded {@link updateCandidateStatus} (optimistic pending→consumed +
|
|
396
|
+
* consumed_at), never bypassing the existing mechanism (SPEC §13: the
|
|
397
|
+
* candidate still reaches `consumed`).
|
|
398
|
+
*
|
|
399
|
+
* Returns 'already_consumed' when the candidate was consumed before this call
|
|
400
|
+
* (e.g. an INV-R07 replay). Exits non-zero on a guarded-transition failure —
|
|
401
|
+
* the ledger/LEDGER fact is already durable, so the inconsistency must be
|
|
402
|
+
* loud (same posture as the pre-PR3A intake path).
|
|
403
|
+
*/
|
|
404
|
+
async function markCandidateConsumed(stateManager, candidateId, ledgerFact) {
|
|
405
|
+
const candidate = await stateManager.getCandidate(candidateId);
|
|
406
|
+
if (candidate?.status === 'consumed') {
|
|
407
|
+
return 'already_consumed';
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
await updateCandidateStatus({ stateManager, candidateId, targetStatus: 'consumed', expectedCurrentStatus: 'pending' });
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
const msg = `The ledger fact is durable (${ledgerFact}) but the candidate status update failed: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
414
|
+
`Candidate ${candidateId} may be in inconsistent state.`;
|
|
415
|
+
console.error(`ERROR: ${msg}`);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
return 'consumed';
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* PRI-917 PR3A (surface audit C1/C2): report a `reuse_selected` intake result
|
|
422
|
+
* as the SUCCESS it is — resolution + selected Principle + evidence, exit 0 —
|
|
423
|
+
* and unify the candidate terminal state (C2). Used by BOTH `pd candidate
|
|
424
|
+
* intake` (replay path) and `pd candidate review --decide reuse` (fresh
|
|
425
|
+
* decision). The CLI prints the durable fact; it does not create one.
|
|
426
|
+
*/
|
|
427
|
+
async function reportReuseResolution(opts) {
|
|
428
|
+
const { stateManager, candidateId, result, json } = opts;
|
|
429
|
+
if (!result.selectedPrincipleId || !result.reuseEvidence) {
|
|
430
|
+
// Adapter/service contract violation — never print a hollow resolution.
|
|
431
|
+
throw new CandidateIntakeError(INTAKE_ERROR_CODES.INPUT_INVALID, `reuse_selected result for candidate ${candidateId} is missing selectedPrincipleId/reuseEvidence (contract violation)`, { candidateId, reason: 'reuse_resolution_contract_violation' });
|
|
432
|
+
}
|
|
433
|
+
const consumedState = await markCandidateConsumed(stateManager, candidateId, `reuse evidence on ${result.selectedPrincipleId}`);
|
|
434
|
+
const replayed = result.reuseProposal === undefined;
|
|
435
|
+
const resolution = {
|
|
436
|
+
candidateId,
|
|
437
|
+
status: 'reused',
|
|
438
|
+
selectedPrincipleId: result.selectedPrincipleId,
|
|
439
|
+
reuseEvidence: result.reuseEvidence,
|
|
440
|
+
replayed,
|
|
441
|
+
candidateStatus: consumedState,
|
|
442
|
+
message: result.message,
|
|
443
|
+
};
|
|
444
|
+
if (json) {
|
|
445
|
+
console.log(JSON.stringify(resolution, null, 2));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
console.log(`\nPrinciple Candidate Reuse: ${candidateId}\n`);
|
|
449
|
+
console.log(` Candidate: ${candidateId}`);
|
|
450
|
+
console.log(` Reused Principle: ${result.selectedPrincipleId}`);
|
|
451
|
+
console.log(` Pain: ${result.reuseEvidence.painId}`);
|
|
452
|
+
console.log(` Decided By: ${result.reuseEvidence.actor.kind} ${result.reuseEvidence.actor.id}`);
|
|
453
|
+
console.log(` Decided At: ${result.reuseEvidence.decidedAt}`);
|
|
454
|
+
console.log(` Reason: ${result.reuseEvidence.reason}`);
|
|
455
|
+
console.log(` Candidate Status: ${consumedState === 'already_consumed' ? 'already consumed (INV-R07 replay)' : 'consumed'}`);
|
|
456
|
+
console.log(' Evidence: recorded on the Principle\'s reuseEvidence (append-only)\n');
|
|
457
|
+
}
|
|
458
|
+
async function evaluateReuseProposal(workspaceDir, claim, entries) {
|
|
459
|
+
const configLoad = loadPdConfig(workspaceDir);
|
|
460
|
+
const effective = configLoad.ok ? configLoad.effective : configLoad.defaults;
|
|
461
|
+
const capability = effective.config.reuseEvaluation;
|
|
462
|
+
if (!capability?.enabled) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
if (entries.length === 0) {
|
|
466
|
+
// Case A (SPEC §6): nothing credible was found — there is nothing to
|
|
467
|
+
// evaluate, and intake proceeds to create without an Owner decision.
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
// PRI-917 review (P2): timeout precedence — capability.timeoutMs wins,
|
|
471
|
+
// otherwise the resolved PROFILE's own timeout (its documented default),
|
|
472
|
+
// otherwise the runner's built-in default.
|
|
473
|
+
const capabilityProfile = effective.config.runtimeProfiles[capability.runtimeProfile ?? ''];
|
|
474
|
+
const profileTimeoutMs = capabilityProfile?.type === 'pi-ai' ? capabilityProfile.timeoutMs : undefined;
|
|
475
|
+
try {
|
|
476
|
+
const adapter = resolveRuntimeAdapterFromConfig({
|
|
477
|
+
runtimeKind: 'config',
|
|
478
|
+
workspaceDir,
|
|
479
|
+
runtimeProfileId: capability.runtimeProfile,
|
|
480
|
+
});
|
|
481
|
+
const runner = new ReuseEvaluationRunner({ runtimeAdapter: adapter }, { ...(capability.timeoutMs !== undefined ? { timeoutMs: capability.timeoutMs } : { ...(profileTimeoutMs !== undefined ? { timeoutMs: profileTimeoutMs } : {}) }) });
|
|
482
|
+
const output = await runner.recommend({
|
|
483
|
+
candidate: claim,
|
|
484
|
+
candidates: entries.map((entry) => ({
|
|
485
|
+
principleId: entry.principleId,
|
|
486
|
+
text: entry.existingPrinciple.text,
|
|
487
|
+
triggerPattern: entry.existingPrinciple.triggerPattern,
|
|
488
|
+
action: entry.existingPrinciple.action,
|
|
489
|
+
})),
|
|
490
|
+
});
|
|
491
|
+
// PRI-917 review (P2): the validator deliberately does NOT check id
|
|
492
|
+
// existence (INV-R08 lives one layer up, against the REAL shortlist).
|
|
493
|
+
// This is that layer: a hallucinated/foreign id must not reach the Owner
|
|
494
|
+
// as an executable-looking recommendation.
|
|
495
|
+
if (output.recommendation === 'reuse') {
|
|
496
|
+
const inShortlist = entries.some((entry) => entry.principleId === output.selectedPrincipleId);
|
|
497
|
+
if (!inShortlist) {
|
|
498
|
+
return {
|
|
499
|
+
status: 'unavailable',
|
|
500
|
+
reason: `selected_principle_not_in_shortlist:${output.selectedPrincipleId ?? '(absent)'}`,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
status: 'recommended',
|
|
506
|
+
recommendation: output.recommendation,
|
|
507
|
+
...(output.selectedPrincipleId !== undefined ? { selectedPrincipleId: output.selectedPrincipleId } : {}),
|
|
508
|
+
rationale: output.rationale,
|
|
509
|
+
confidence: output.confidence,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
const reason = err instanceof ReuseEvaluationError
|
|
514
|
+
? `${err.reason}: ${err.detail ?? err.message}`
|
|
515
|
+
: err instanceof Error
|
|
516
|
+
? err.message
|
|
517
|
+
: String(err);
|
|
518
|
+
return { status: 'unavailable', reason };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Decision mode of {@link handleCandidateReview}: validate the CLI contract
|
|
523
|
+
* (rc-3), resolve the Owner identity (fail-closed), then run the EXISTING
|
|
524
|
+
* intake chain with the §11 verdict injected. No CLI-side persistence.
|
|
525
|
+
*/
|
|
526
|
+
async function executeOwnerDecision(deps) {
|
|
527
|
+
const { opts, stateManager, ledgerAdapter, stateDir } = deps;
|
|
528
|
+
const failCli = (message, nextAction) => {
|
|
529
|
+
if (opts.json) {
|
|
530
|
+
console.log(JSON.stringify({ candidateId: opts.candidateId, status: 'refused', decision: opts.decide, error: message, nextAction }, null, 2));
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
console.error(`ERROR: ${message}`);
|
|
534
|
+
console.error(` Next Action: ${nextAction}`);
|
|
535
|
+
}
|
|
536
|
+
process.exit(1);
|
|
537
|
+
};
|
|
538
|
+
if (typeof opts.reason !== 'string' || opts.reason.trim() === '') {
|
|
539
|
+
failCli('--reason is required with --decide (the evidence entry must record why the Owner resolved this candidate)', 'Re-run with --reason "..." (non-empty).');
|
|
540
|
+
}
|
|
541
|
+
if (opts.decide === 'reuse' && (typeof opts.principleId !== 'string' || opts.principleId.trim() === '')) {
|
|
542
|
+
failCli('--principle-id is required with --decide reuse', 'Re-run with --principle-id <id from the review proposal>.');
|
|
543
|
+
}
|
|
544
|
+
// Owner identity: env > ~/.pd/owner.json > none (ADR-0022 resolver).
|
|
545
|
+
const identity = resolveOwnerIdentity(process.env, defaultOwnerIdentityHomeDir());
|
|
546
|
+
if ((identity.source !== 'env' && identity.source !== 'file') || !identity.ownerId) {
|
|
547
|
+
failCli(`Owner identity is required to record a ${opts.decide} decision (identity source: ${identity.source})${identity.error ? `: ${identity.error}` : ''}`, 'Register the Owner identity file (~/.pd/owner.json) or set PD_OWNER_ID and PD_OWNER_CREDENTIAL_ID together, then retry.');
|
|
548
|
+
}
|
|
549
|
+
const decision = opts.decide === 'reuse'
|
|
550
|
+
? {
|
|
551
|
+
decision: 'reuse',
|
|
552
|
+
selectedPrincipleId: opts.principleId,
|
|
553
|
+
actor: { kind: 'owner', id: identity.ownerId },
|
|
554
|
+
reason: opts.reason,
|
|
555
|
+
decidedAt: new Date().toISOString(),
|
|
556
|
+
}
|
|
557
|
+
: { decision: 'create' };
|
|
558
|
+
// INV-R08: verify the selected principle is in the current proposal.
|
|
559
|
+
// This prevents a stale or empty proposal from silently degrading reuse
|
|
560
|
+
// into create.
|
|
561
|
+
if (opts.decide === 'reuse') {
|
|
562
|
+
const candidate = await stateManager.getCandidate(opts.candidateId);
|
|
563
|
+
const artifact = candidate ? await stateManager.getArtifact(candidate.artifactId) : null;
|
|
564
|
+
const extracted = candidate ? extractIntakeRecommendation(candidate, artifact) : null;
|
|
565
|
+
if (!candidate || !extracted || !extracted.ok) {
|
|
566
|
+
failCli('Cannot build the reuse proposal for this candidate', 'Run `pd candidate review --candidate-id <id>` first.');
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const proposal = buildReuseProposal(opts.candidateId, buildReuseShortlist({
|
|
570
|
+
text: extracted.recommendation.text || candidate.description || '',
|
|
571
|
+
triggerPattern: extracted.recommendation.triggerPattern ?? '',
|
|
572
|
+
action: extracted.recommendation.action ?? '',
|
|
573
|
+
}, stateDir, { recommendationKind: candidate.rawRecommendationKind }));
|
|
574
|
+
if (!proposal.candidates.some((c) => c.principleId === opts.principleId)) {
|
|
575
|
+
failCli(`Principle ${opts.principleId} is not in the current reuse proposal`, 'Re-run `pd candidate review` to see the current proposal.');
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const service = new CandidateIntakeService({
|
|
579
|
+
stateManager,
|
|
580
|
+
ledgerAdapter,
|
|
581
|
+
// The verdict is fixed by the CLI invocation; the intake gate still
|
|
582
|
+
// validates it (rc-1/rc-2/rc-3) and checks the selection against the
|
|
583
|
+
// proposal (INV-R08) — the CLI cannot smuggle a decision past the gate.
|
|
584
|
+
reuseDecision: () => decision,
|
|
585
|
+
reuseStateDir: stateDir,
|
|
586
|
+
});
|
|
587
|
+
const intakeResult = await service.intake(opts.candidateId);
|
|
588
|
+
if (intakeResult.outcome === 'refused' && intakeResult.reason === 'reuse_selected') {
|
|
589
|
+
await reportReuseResolution({ stateManager, candidateId: opts.candidateId, result: intakeResult, json: opts.json === true });
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (intakeResult.outcome === 'refused') {
|
|
593
|
+
// A non-principle kind or unknown kind cannot carry a reuse decision.
|
|
594
|
+
throw new CandidateIntakeError(INTAKE_ERROR_CODES.INPUT_INVALID, `Principle Ledger write refused: ${intakeResult.message}`, { candidateId: opts.candidateId, reason: intakeResult.reason });
|
|
595
|
+
}
|
|
596
|
+
// create verdict (or gate absent shapes) — same durable outcome as intake.
|
|
597
|
+
const { entry } = intakeResult;
|
|
598
|
+
const consumedState = await markCandidateConsumed(stateManager, opts.candidateId, entry.id);
|
|
599
|
+
if (consumedState === 'already_consumed') {
|
|
600
|
+
// Consumed before this call — the ledger entry is identical by intake
|
|
601
|
+
// idempotency; report it like `pd candidate intake` does.
|
|
602
|
+
const infoMessage = `Candidate ${opts.candidateId} was already consumed. Ledger entry: ${entry.id}`;
|
|
603
|
+
if (opts.json) {
|
|
604
|
+
console.log(JSON.stringify({ candidateId: opts.candidateId, ledgerEntryId: entry.id, status: 'already_consumed', message: infoMessage }, null, 2));
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
console.log(infoMessage);
|
|
608
|
+
}
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const result = {
|
|
612
|
+
candidateId: opts.candidateId,
|
|
613
|
+
decision: opts.decide,
|
|
614
|
+
ledgerEntryId: entry.id,
|
|
615
|
+
status: 'consumed',
|
|
616
|
+
};
|
|
617
|
+
if (opts.json) {
|
|
618
|
+
console.log(JSON.stringify(result, null, 2));
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
console.log(`\nPrinciple Candidate Decision: ${opts.candidateId}\n`);
|
|
622
|
+
console.log(` Decision: ${opts.decide}`);
|
|
623
|
+
console.log(` Candidate: ${opts.candidateId}`);
|
|
624
|
+
console.log(` Ledger Entry: ${entry.id}`);
|
|
625
|
+
console.log(` Status: consumed\n`);
|
|
626
|
+
console.log('Decision complete.\n');
|
|
627
|
+
}
|
|
383
628
|
/**
|
|
384
629
|
* pd candidate intake --candidate-id <id> [--workspace <path>] [--json] [--dry-run]
|
|
385
630
|
*
|
|
@@ -465,12 +710,22 @@ export async function handleCandidateIntake(opts) {
|
|
|
465
710
|
// candidate id, so a Principle Ledger write boundary refusal is surfaced
|
|
466
711
|
// loudly (never silently swallowed) instead of being treated as success.
|
|
467
712
|
const intakeResult = await service.intake(opts.candidateId);
|
|
713
|
+
if (intakeResult.outcome === 'refused' && intakeResult.reason === 'reuse_selected') {
|
|
714
|
+
// PRI-917 PR3A (surface audit C1/C2): a recorded reuse resolution is a
|
|
715
|
+
// SUCCESS, not a failure — the evidence is already durable on the
|
|
716
|
+
// selected Principle (fresh decision) or was recorded earlier (INV-R07
|
|
717
|
+
// replay via intake step 2b, which is how this branch is reachable from
|
|
718
|
+
// the gate-less intake command). Report the resolution, unify the
|
|
719
|
+
// candidate terminal state with the automatic paths (SPEC §13), exit 0.
|
|
720
|
+
await reportReuseResolution({ stateManager, candidateId: opts.candidateId, result: intakeResult, json: opts.json === true });
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
468
723
|
if (intakeResult.outcome === 'refused') {
|
|
469
724
|
throw new CandidateIntakeError(INTAKE_ERROR_CODES.INPUT_INVALID, `Principle Ledger write refused: ${intakeResult.message}`, { candidateId: opts.candidateId, reason: intakeResult.reason });
|
|
470
725
|
}
|
|
471
726
|
const { entry } = intakeResult;
|
|
472
|
-
|
|
473
|
-
if (
|
|
727
|
+
const consumedState = await markCandidateConsumed(stateManager, opts.candidateId, entry.id);
|
|
728
|
+
if (consumedState === 'already_consumed') {
|
|
474
729
|
const infoMessage = `Candidate ${opts.candidateId} was already consumed. Ledger entry: ${entry.id}`;
|
|
475
730
|
if (opts.json) {
|
|
476
731
|
console.log(JSON.stringify({
|
|
@@ -485,16 +740,6 @@ export async function handleCandidateIntake(opts) {
|
|
|
485
740
|
}
|
|
486
741
|
return;
|
|
487
742
|
}
|
|
488
|
-
// Update DB status — this must succeed; if it fails, exit non-zero
|
|
489
|
-
try {
|
|
490
|
-
await updateCandidateStatus({ stateManager, candidateId: opts.candidateId, targetStatus: 'consumed', expectedCurrentStatus: 'pending' });
|
|
491
|
-
}
|
|
492
|
-
catch (err) {
|
|
493
|
-
const msg = `Ledger write succeeded (entry ${entry.id}) but DB status update failed: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
494
|
-
`Candidate ${opts.candidateId} may be in inconsistent state.`;
|
|
495
|
-
console.error(`ERROR: ${msg}`);
|
|
496
|
-
process.exit(1);
|
|
497
|
-
}
|
|
498
743
|
const result = {
|
|
499
744
|
candidateId: opts.candidateId,
|
|
500
745
|
ledgerEntryId: entry.id,
|
|
@@ -526,6 +771,255 @@ export async function handleCandidateIntake(opts) {
|
|
|
526
771
|
await stateManager.close();
|
|
527
772
|
}
|
|
528
773
|
}
|
|
774
|
+
/**
|
|
775
|
+
* pd candidate review — PRI-917 PR3A Owner decision surface (SPEC §18).
|
|
776
|
+
*
|
|
777
|
+
* Phase 1 (read-only): shows the candidate and the deterministic Top-K reuse
|
|
778
|
+
* proposal (score, reasons/shared terms, and the existing Principle's text /
|
|
779
|
+
* triggerPattern / action / status) so the Owner can decide with the same
|
|
780
|
+
* information the gate will consume.
|
|
781
|
+
*
|
|
782
|
+
* Phase 2 (`--decide`): constructs the SPEC §11 verdict (actor from
|
|
783
|
+
* resolveOwnerIdentity — fail-closed when unresolvable) and injects it into
|
|
784
|
+
* CandidateIntakeService, which runs the EXISTING gate → appendReuseEvidence
|
|
785
|
+
* chain. The CLI is an interaction layer only: it never writes reuseEvidence,
|
|
786
|
+
* decision files, or any other state (SPEC §18 — the only durable authority
|
|
787
|
+
* is `Principle.reuseEvidence[]`).
|
|
788
|
+
*/
|
|
789
|
+
export async function handleCandidateReview(opts) {
|
|
790
|
+
const workspaceDir = resolveWorkspaceDir(opts.workspace);
|
|
791
|
+
const stateManager = new RuntimeStateManager({ workspaceDir });
|
|
792
|
+
try {
|
|
793
|
+
await stateManager.initialize();
|
|
794
|
+
const stateDir = path.join(workspaceDir, '.state');
|
|
795
|
+
const ledgerAdapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
796
|
+
const candidate = await stateManager.getCandidate(opts.candidateId);
|
|
797
|
+
if (!candidate) {
|
|
798
|
+
console.error(`Candidate not found: ${opts.candidateId}`);
|
|
799
|
+
process.exit(1);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (opts.decide === 'reuse' || opts.decide === 'create') {
|
|
803
|
+
// PRI-442 Stage 4: admission gate check — same as handleCandidateIntake.
|
|
804
|
+
// Prevents CLI from bypassing the production admission gate.
|
|
805
|
+
//
|
|
806
|
+
// PRI-917 v0.3.3 (R5 F4 two-heads-block fix): the pre-check does NOT
|
|
807
|
+
// apply to a `reuse` verdict. Admission confidence protects the LEDGER
|
|
808
|
+
// from low-quality NEW principles; a reuse decision creates nothing — it
|
|
809
|
+
// appends one auditable evidence entry to an EXISTING principle whose
|
|
810
|
+
// recurrence is itself the proof of value. Blocking it here left every
|
|
811
|
+
// low-confidence historical candidate permanently undecidable while the
|
|
812
|
+
// auto path consumed high-confidence ones before any Owner could ask.
|
|
813
|
+
// `create` keeps the pre-check: a low-confidence candidate still cannot
|
|
814
|
+
// become a new Principle via the CLI.
|
|
815
|
+
if (opts.decide === 'create') {
|
|
816
|
+
const admissionBlock = checkAdmissionGate(candidate);
|
|
817
|
+
if (admissionBlock) {
|
|
818
|
+
const payload = {
|
|
819
|
+
candidateId: opts.candidateId,
|
|
820
|
+
status: 'refused',
|
|
821
|
+
admissionDecision: admissionBlock.decision,
|
|
822
|
+
reason: admissionBlock.reason,
|
|
823
|
+
nextAction: admissionBlock.nextAction,
|
|
824
|
+
};
|
|
825
|
+
if (opts.json) {
|
|
826
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
console.error(`Admission gate refused candidate ${opts.candidateId}: ${admissionBlock.decision}`);
|
|
830
|
+
console.error(` Reason: ${admissionBlock.reason}`);
|
|
831
|
+
console.error(` Next Action: ${admissionBlock.nextAction}`);
|
|
832
|
+
}
|
|
833
|
+
process.exit(1);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
// opts.decide is narrowed here — no cast needed to build the decision-mode shape.
|
|
838
|
+
await executeOwnerDecision({ opts: { ...opts, decide: opts.decide }, stateManager, ledgerAdapter, stateDir });
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
// ── Read-only proposal view ──
|
|
842
|
+
const rawKindEligible = isPrincipleLedgerEligibleKind(candidate.rawRecommendationKind);
|
|
843
|
+
if (!rawKindEligible) {
|
|
844
|
+
// Fail closed like the intake boundary: a non-principle candidate never
|
|
845
|
+
// reaches the reuse gate, so reviewing a proposal for it would lie.
|
|
846
|
+
const rawKind = typeof candidate.rawRecommendationKind === 'string' ? candidate.rawRecommendationKind : null;
|
|
847
|
+
const payload = {
|
|
848
|
+
candidateId: opts.candidateId,
|
|
849
|
+
recommendationKindEligible: false,
|
|
850
|
+
rawRecommendationKind: rawKind,
|
|
851
|
+
message: `Candidate ${opts.candidateId} has recommendation_kind '${rawKind ?? 'absent'}' which does not target the Principle Ledger; there is no reuse proposal to review.`,
|
|
852
|
+
nextAction: 'Run `pd candidate route --candidate-id <id>` for this candidate\'s internalization route instead.',
|
|
853
|
+
};
|
|
854
|
+
if (opts.json) {
|
|
855
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
console.log(`\nPrinciple Candidate Review: ${opts.candidateId}\n`);
|
|
859
|
+
console.log(` Kind: ${rawKind ?? 'absent'} (not Principle-Ledger eligible)`);
|
|
860
|
+
console.log(` ${payload.message}`);
|
|
861
|
+
console.log(` Next Action: ${payload.nextAction}\n`);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const artifact = await stateManager.getArtifact(candidate.artifactId);
|
|
865
|
+
if (!artifact) {
|
|
866
|
+
console.error(`Artifact not found for candidate: ${opts.candidateId} (artifact ${candidate.artifactId})`);
|
|
867
|
+
process.exit(1);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
// C4: the SAME extraction intake uses (4b/4c) — never a second parser.
|
|
871
|
+
const extracted = extractIntakeRecommendation(candidate, artifact);
|
|
872
|
+
if (!extracted.ok) {
|
|
873
|
+
throw extracted.error;
|
|
874
|
+
}
|
|
875
|
+
const { recommendation } = extracted;
|
|
876
|
+
const shortlist = buildReuseShortlist({
|
|
877
|
+
text: recommendation.text || candidate.description || '',
|
|
878
|
+
triggerPattern: recommendation.triggerPattern ?? '',
|
|
879
|
+
action: recommendation.action ?? '',
|
|
880
|
+
}, stateDir, { recommendationKind: candidate.rawRecommendationKind });
|
|
881
|
+
const proposal = buildReuseProposal(opts.candidateId, shortlist);
|
|
882
|
+
// Enrich each entry with the existing Principle's durable content.
|
|
883
|
+
const ledger = loadLedger(stateDir);
|
|
884
|
+
const entries = proposal.candidates.map((entry) => {
|
|
885
|
+
const principle = ledger.tree.principles[entry.principleId];
|
|
886
|
+
return {
|
|
887
|
+
principleId: entry.principleId,
|
|
888
|
+
score: entry.score,
|
|
889
|
+
reasons: entry.reasons,
|
|
890
|
+
existingPrinciple: principle
|
|
891
|
+
? { text: principle.text, triggerPattern: principle.triggerPattern, action: principle.action, status: principle.status }
|
|
892
|
+
: { text: '', triggerPattern: '', action: '', status: 'missing' },
|
|
893
|
+
};
|
|
894
|
+
});
|
|
895
|
+
// PRI-917 v0.3.2 (Phase 3C-4): run the Semantic Reuse Evaluation
|
|
896
|
+
// Capability as a DISPLAY enhancement on the read-only review path. Its
|
|
897
|
+
// output is a RECOMMENDATION for the Owner — it never decides, never
|
|
898
|
+
// writes, and its failure degrades observably to lexical-only (SPEC §9).
|
|
899
|
+
const evaluation = await evaluateReuseProposal(workspaceDir, {
|
|
900
|
+
text: recommendation.text || candidate.description || '',
|
|
901
|
+
triggerPattern: recommendation.triggerPattern ?? '',
|
|
902
|
+
action: recommendation.action ?? '',
|
|
903
|
+
}, entries);
|
|
904
|
+
// PRI-917 v0.3.2 §11: the events are OBSERVATION ONLY. They record what
|
|
905
|
+
// the capability recommended; they are never decision storage (the
|
|
906
|
+
// Owner's verdict lives exclusively in Principle.reuseEvidence[]) and
|
|
907
|
+
// no intake outcome reads them. Emitted only when the capability
|
|
908
|
+
// actually ran (disabled or nothing-to-evaluate emits nothing).
|
|
909
|
+
if (evaluation !== null) {
|
|
910
|
+
const timestamp = new Date().toISOString();
|
|
911
|
+
if (evaluation.status === 'recommended') {
|
|
912
|
+
storeEmitter.emitTelemetry({
|
|
913
|
+
eventType: 'reuse_evaluation_recommended',
|
|
914
|
+
traceId: opts.candidateId,
|
|
915
|
+
timestamp,
|
|
916
|
+
sessionId: 'pd-cli-review',
|
|
917
|
+
agentId: 'reuse-evaluation',
|
|
918
|
+
payload: {
|
|
919
|
+
candidateId: opts.candidateId,
|
|
920
|
+
recommendation: evaluation.recommendation,
|
|
921
|
+
...(evaluation.selectedPrincipleId !== undefined ? { selectedPrincipleId: evaluation.selectedPrincipleId } : {}),
|
|
922
|
+
confidence: evaluation.confidence,
|
|
923
|
+
},
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
storeEmitter.emitTelemetry({
|
|
928
|
+
eventType: 'reuse_evaluation_unavailable',
|
|
929
|
+
traceId: opts.candidateId,
|
|
930
|
+
timestamp,
|
|
931
|
+
sessionId: 'pd-cli-review',
|
|
932
|
+
agentId: 'reuse-evaluation',
|
|
933
|
+
payload: {
|
|
934
|
+
candidateId: opts.candidateId,
|
|
935
|
+
reason: evaluation.reason.slice(0, 200),
|
|
936
|
+
},
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const nextAction = entries.length > 0
|
|
941
|
+
? `pd candidate review --candidate-id ${opts.candidateId} --decide reuse --principle-id <id> --reason "..." (or --decide create --reason "...")`
|
|
942
|
+
: `No credible reuse proposal — intake will create a new Principle: pd candidate intake --candidate-id ${opts.candidateId} --workspace "${workspaceDir}"`;
|
|
943
|
+
if (opts.json) {
|
|
944
|
+
console.log(JSON.stringify({
|
|
945
|
+
candidateId: opts.candidateId,
|
|
946
|
+
title: candidate.title,
|
|
947
|
+
description: candidate.description,
|
|
948
|
+
status: candidate.status,
|
|
949
|
+
taskId: candidate.taskId,
|
|
950
|
+
recommendationKindEligible: true,
|
|
951
|
+
claim: { text: recommendation.text || candidate.description || '', triggerPattern: recommendation.triggerPattern ?? '', action: recommendation.action ?? '' },
|
|
952
|
+
proposal: {
|
|
953
|
+
status: proposal.status,
|
|
954
|
+
eligibleCount: proposal.eligibleCount,
|
|
955
|
+
candidates: entries.map((entry) => ({
|
|
956
|
+
...entry,
|
|
957
|
+
semanticMatch: evaluation?.status === 'recommended'
|
|
958
|
+
&& evaluation.recommendation === 'reuse'
|
|
959
|
+
&& evaluation.selectedPrincipleId === entry.principleId,
|
|
960
|
+
})),
|
|
961
|
+
},
|
|
962
|
+
...(evaluation ? { evaluation } : {}),
|
|
963
|
+
nextAction,
|
|
964
|
+
}, null, 2));
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
console.log(`\nPrinciple Candidate Review: ${opts.candidateId}\n`);
|
|
968
|
+
console.log(` Title: ${candidate.title}`);
|
|
969
|
+
console.log(` Description: ${candidate.description}`);
|
|
970
|
+
console.log(` Status: ${candidate.status}`);
|
|
971
|
+
console.log(` Task: ${candidate.taskId}`);
|
|
972
|
+
console.log(` Claim: ${recommendation.text || candidate.description || ''}`);
|
|
973
|
+
console.log(` Trigger: ${recommendation.triggerPattern ?? ''}`);
|
|
974
|
+
console.log(` Action: ${recommendation.action ?? ''}`);
|
|
975
|
+
console.log(`\n Reuse Proposal (${entries.length} of ${proposal.eligibleCount} eligible, Top-3):\n`);
|
|
976
|
+
if (entries.length === 0) {
|
|
977
|
+
console.log(' No credible existing Principle found — the intake gate will create a new one.\n');
|
|
978
|
+
}
|
|
979
|
+
entries.forEach((entry, index) => {
|
|
980
|
+
console.log(` ${index + 1}. ${entry.principleId} (score ${entry.score.toFixed(3)})`);
|
|
981
|
+
for (const reason of entry.reasons) {
|
|
982
|
+
console.log(` reason: ${reason}`);
|
|
983
|
+
}
|
|
984
|
+
console.log(` text: ${entry.existingPrinciple.text}`);
|
|
985
|
+
console.log(` trigger: ${entry.existingPrinciple.triggerPattern}`);
|
|
986
|
+
console.log(` action: ${entry.existingPrinciple.action}`);
|
|
987
|
+
console.log(` status: ${entry.existingPrinciple.status}`);
|
|
988
|
+
});
|
|
989
|
+
console.log('');
|
|
990
|
+
if (evaluation === null) {
|
|
991
|
+
// Capability disabled (or nothing to evaluate): lexical-only review,
|
|
992
|
+
// exactly the pre-3C-4 output.
|
|
993
|
+
}
|
|
994
|
+
else if (evaluation.status === 'recommended') {
|
|
995
|
+
console.log(' Semantic evaluation (advisory — the Owner decides):');
|
|
996
|
+
console.log(` recommendation: ${evaluation.recommendation}`);
|
|
997
|
+
if (evaluation.selectedPrincipleId) {
|
|
998
|
+
console.log(` principle: ${evaluation.selectedPrincipleId}`);
|
|
999
|
+
}
|
|
1000
|
+
console.log(` rationale: ${evaluation.rationale}`);
|
|
1001
|
+
console.log(` confidence: ${evaluation.confidence}`);
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
console.log(' Semantic evaluation: UNAVAILABLE (lexical proposal only — not semantically judged)');
|
|
1005
|
+
console.log(` reason: ${evaluation.reason}`);
|
|
1006
|
+
}
|
|
1007
|
+
console.log(`\n Next Action: ${nextAction}\n`);
|
|
1008
|
+
}
|
|
1009
|
+
catch (err) {
|
|
1010
|
+
if (err instanceof CandidateIntakeError || err.name === 'CandidateIntakeError') {
|
|
1011
|
+
const e = err;
|
|
1012
|
+
console.error(`Review failed [${e.code ?? 'unknown'}]: ${e.message}`);
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
console.error(`Review failed: ${String(err)}`);
|
|
1016
|
+
}
|
|
1017
|
+
process.exit(1);
|
|
1018
|
+
}
|
|
1019
|
+
finally {
|
|
1020
|
+
await stateManager.close();
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
529
1023
|
// ── Audit ─────────────────────────────────────────────────────────────────────
|
|
530
1024
|
/**
|
|
531
1025
|
* pd candidate audit --workspace <path> [--json]
|