@duo-director/cli 0.2.2 → 0.3.0-rc.3
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/{cli-QTN5SRNE.js → cli-DJGVC2WO.js} +828 -400
- package/dist/duoctl.js +1 -1
- package/dist/runtime-tree.json +2 -2
- package/dist/ui/app.js +6 -6
- package/package.json +1 -1
|
@@ -157,6 +157,8 @@ var DIAGNOSTIC_SEVERITY = {
|
|
|
157
157
|
DECISION_SUPERSEDE_TARGET_INVALID: "error",
|
|
158
158
|
/** DecisionService: another process holds the repository decision lock. */
|
|
159
159
|
DECISION_LOCK_BUSY: "error",
|
|
160
|
+
/** DecisionService (T34.2): the candidate changed after the preview a person reviewed; nothing was confirmed. */
|
|
161
|
+
DECISION_CONFIRM_PREVIEW_CHANGED: "error",
|
|
160
162
|
/** A confirmed Decision's lock.digest does not match its content fields (a detection aid, not a signature). */
|
|
161
163
|
DECISION_LOCK_MISMATCH: "warning",
|
|
162
164
|
/** T40 (N1): an authoritative operation refused partial Project Truth; the loader errors follow it. */
|
|
@@ -300,6 +302,7 @@ var DIAGNOSTIC_PERSISTENCE = {
|
|
|
300
302
|
DECISION_TARGET_UNSUPPORTED: T,
|
|
301
303
|
DECISION_SUPERSEDE_TARGET_INVALID: T,
|
|
302
304
|
DECISION_LOCK_BUSY: T,
|
|
305
|
+
DECISION_CONFIRM_PREVIEW_CHANGED: T,
|
|
303
306
|
PROJECT_TRUTH_INVALID: T,
|
|
304
307
|
DECISION_LOCK_MISMATCH: P,
|
|
305
308
|
SOURCE_LOCATION_INVALID: P,
|
|
@@ -1314,7 +1317,8 @@ var decisionShape = {
|
|
|
1314
1317
|
rationale: text().optional(),
|
|
1315
1318
|
owner: z.literal("human").optional(),
|
|
1316
1319
|
governs: z.strictObject({ requirements: ids().optional(), paths: texts().optional(), symbols: texts().optional() }).optional(),
|
|
1317
|
-
|
|
1320
|
+
/** imported_paths (H-72): repository path patterns; a changed import resolving to a matching file conflicts. */
|
|
1321
|
+
forbids: z.strictObject({ dependencies: texts().optional(), symbols: texts().optional(), paths: texts().optional(), imported_paths: texts().optional() }).optional(),
|
|
1318
1322
|
supersedes: id().nullable().optional(),
|
|
1319
1323
|
evidence: z.array(EvidencePointerSchema).optional(),
|
|
1320
1324
|
source: sources,
|
|
@@ -1595,7 +1599,9 @@ function mapDecisionContent(d, ctx, governsRelation) {
|
|
|
1595
1599
|
forbids: {
|
|
1596
1600
|
dependencies: d.forbids?.dependencies ?? [],
|
|
1597
1601
|
symbols: d.forbids?.symbols ?? [],
|
|
1598
|
-
paths: ctx.patterns(["forbids", "paths"], d.forbids?.paths)
|
|
1602
|
+
paths: ctx.patterns(["forbids", "paths"], d.forbids?.paths),
|
|
1603
|
+
// H-72: only when present and non-empty; absent stays absent (lock digest compatibility).
|
|
1604
|
+
...(d.forbids?.imported_paths?.length ?? 0) > 0 ? { importedPaths: ctx.patterns(["forbids", "imported_paths"], d.forbids?.imported_paths) } : {}
|
|
1599
1605
|
},
|
|
1600
1606
|
supersedes: d.supersedes ?? null,
|
|
1601
1607
|
evidence: ctx.evidence(["evidence"], d.evidence),
|
|
@@ -2219,6 +2225,9 @@ function canonical(value) {
|
|
|
2219
2225
|
}
|
|
2220
2226
|
return value;
|
|
2221
2227
|
}
|
|
2228
|
+
function candidateDigest(value) {
|
|
2229
|
+
return sha256(stableJson(value));
|
|
2230
|
+
}
|
|
2222
2231
|
function stableJson(value) {
|
|
2223
2232
|
return JSON.stringify(canonical(value));
|
|
2224
2233
|
}
|
|
@@ -2382,6 +2391,7 @@ var DECISION_PERMISSIONS = {
|
|
|
2382
2391
|
confirm: ["human"],
|
|
2383
2392
|
reject: ["human"]
|
|
2384
2393
|
};
|
|
2394
|
+
var CANDIDATE_FIELDS = ["title", "kind", "question", "answer", "rationale", "governs", "forbids", "enforcement", "supersedes", "evidence", "source", "extensions"];
|
|
2385
2395
|
var SYSTEM = { kind: "system", name: "duo" };
|
|
2386
2396
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2387
2397
|
var idTokens = (message2) => message2.split(/[^A-Za-z0-9-]+/u);
|
|
@@ -2609,7 +2619,7 @@ function createDecisionService(options) {
|
|
|
2609
2619
|
return success({ proposalId: created.value.id, path: created.value.path, indexRequired: false, repaired }, prepared.diagnostics);
|
|
2610
2620
|
});
|
|
2611
2621
|
},
|
|
2612
|
-
async confirm(actor, id3) {
|
|
2622
|
+
async confirm(actor, id3, confirmOptions = {}) {
|
|
2613
2623
|
const denied = forbidden(actor, "confirm");
|
|
2614
2624
|
if (denied.length > 0)
|
|
2615
2625
|
return failure(denied);
|
|
@@ -2621,7 +2631,13 @@ function createDecisionService(options) {
|
|
|
2621
2631
|
return failure(prepared.diagnostics);
|
|
2622
2632
|
const { truth, repaired } = prepared.value;
|
|
2623
2633
|
const warnings = [...prepared.diagnostics];
|
|
2624
|
-
|
|
2634
|
+
const c = await candidateOf(truth, id3);
|
|
2635
|
+
if (c.value === void 0)
|
|
2636
|
+
return failure(c.diagnostics);
|
|
2637
|
+
if (confirmOptions.expectedDigest !== void 0 && confirmOptions.expectedDigest !== c.value.digest) {
|
|
2638
|
+
return failure([createDiagnostic("DECISION_CONFIRM_PREVIEW_CHANGED", "The Decision changed after you reviewed it. Review the current contents and confirm again.", { path: c.value.sourcePath })]);
|
|
2639
|
+
}
|
|
2640
|
+
return c.value.sourceKind === "decision" ? confirmDecision(actor, c.value, truth, repaired, warnings) : confirmProposal(actor, c.value, truth, repaired, warnings);
|
|
2625
2641
|
});
|
|
2626
2642
|
},
|
|
2627
2643
|
async reject(actor, proposalId, reason) {
|
|
@@ -2656,39 +2672,70 @@ function createDecisionService(options) {
|
|
|
2656
2672
|
return prepared.value === void 0 ? failure(prepared.diagnostics) : success({ repaired: prepared.value.repaired }, prepared.diagnostics);
|
|
2657
2673
|
});
|
|
2658
2674
|
},
|
|
2659
|
-
async previewConfirm(
|
|
2660
|
-
if (!PROPOSAL_ID_PATTERN.test(
|
|
2661
|
-
return failure([createDiagnostic("INVALID_ID", `"${
|
|
2675
|
+
async previewConfirm(id3) {
|
|
2676
|
+
if (!PROPOSAL_ID_PATTERN.test(id3) && !DECISION_ID.test(id3))
|
|
2677
|
+
return failure([createDiagnostic("INVALID_ID", `"${id3}" is not a proposal or Decision ID`)]);
|
|
2662
2678
|
const loaded = load();
|
|
2663
2679
|
if (loaded.value === void 0)
|
|
2664
2680
|
return failure(loaded.diagnostics);
|
|
2665
2681
|
const truth = loaded.value;
|
|
2666
|
-
const
|
|
2667
|
-
if (
|
|
2668
|
-
return failure(
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
const stale = staleness(truth, entry.proposal);
|
|
2672
|
-
const target = entry.proposal.supersedes === null ? void 0 : truth.decisions.find((d) => d.id === entry.proposal.supersedes);
|
|
2682
|
+
const c = await candidateOf(truth, id3);
|
|
2683
|
+
if (c.value === void 0)
|
|
2684
|
+
return failure(c.diagnostics);
|
|
2685
|
+
const v = c.value;
|
|
2686
|
+
const expected = v.sourceKind === "decision" ? id3 : await allocate("D", truth);
|
|
2673
2687
|
return success({
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2688
|
+
sourceId: id3,
|
|
2689
|
+
sourceKind: v.sourceKind,
|
|
2690
|
+
sourcePath: v.sourcePath,
|
|
2691
|
+
action: v.action,
|
|
2692
|
+
...v.sourceKind === "proposal" ? { proposalId: id3 } : {},
|
|
2693
|
+
expectedDecisionId: expected,
|
|
2694
|
+
nextDecisionId: expected,
|
|
2695
|
+
candidate: v.candidate,
|
|
2696
|
+
...v.proposedBy === void 0 ? {} : { proposedBy: v.proposedBy },
|
|
2697
|
+
...v.proposedByKind === void 0 ? {} : { proposedByKind: v.proposedByKind },
|
|
2698
|
+
...v.stale === void 0 ? {} : { stale: v.stale },
|
|
2699
|
+
...v.target === void 0 ? {} : { supersedes: { id: v.target.id, title: v.target.title, state: v.target.state, path: v.target.location.path } },
|
|
2700
|
+
digest: v.digest
|
|
2678
2701
|
});
|
|
2679
2702
|
},
|
|
2680
|
-
verifyLock(
|
|
2703
|
+
verifyLock(decisionId2) {
|
|
2681
2704
|
const loaded = load();
|
|
2682
2705
|
if (loaded.value === void 0)
|
|
2683
2706
|
return failure(loaded.diagnostics);
|
|
2684
|
-
const d = loaded.value.decisions.find((x) => x.id ===
|
|
2707
|
+
const d = loaded.value.decisions.find((x) => x.id === decisionId2);
|
|
2685
2708
|
if (d === void 0)
|
|
2686
|
-
return failure([createDiagnostic("PROPOSAL_NOT_FOUND", `No Decision ${
|
|
2709
|
+
return failure([createDiagnostic("PROPOSAL_NOT_FOUND", `No Decision ${decisionId2}`)]);
|
|
2687
2710
|
const r = verifyDecisionLock(d);
|
|
2688
2711
|
return success(r.value, r.diagnostics);
|
|
2689
2712
|
}
|
|
2690
2713
|
};
|
|
2691
|
-
async function
|
|
2714
|
+
async function candidateOf(truth, id3) {
|
|
2715
|
+
if (DECISION_ID.test(id3)) {
|
|
2716
|
+
const d = truth.decisions.find((x) => x.id === id3);
|
|
2717
|
+
if (d === void 0)
|
|
2718
|
+
return failure([createDiagnostic("PROPOSAL_NOT_FOUND", `No Decision ${id3}`)]);
|
|
2719
|
+
if (!isServiceFile(d))
|
|
2720
|
+
return failure([createDiagnostic("DECISION_TARGET_UNSUPPORTED", `${id3} is not a decisions/D-###.yaml file; DUO does not rewrite it`, d.location)]);
|
|
2721
|
+
if (d.state === "superseded" || d.state === CONFIRMED && d.lock !== void 0) {
|
|
2722
|
+
return failure([createDiagnostic("DECISION_LOCKED", `${id3} is ${d.state} and locked; supersede it with a new proposal`, d.location)]);
|
|
2723
|
+
}
|
|
2724
|
+
if (d.state === "rejected")
|
|
2725
|
+
return failure([createDiagnostic("PROPOSAL_NOT_PENDING", `${id3} is rejected`, d.location)]);
|
|
2726
|
+
const data2 = await rawData(d.location.path);
|
|
2727
|
+
if (data2 === void 0)
|
|
2728
|
+
return failure([createDiagnostic("PROPOSAL_NOT_FOUND", `${d.location.path} is missing`)]);
|
|
2729
|
+
return success(finishCandidate(truth, {
|
|
2730
|
+
sourceId: id3,
|
|
2731
|
+
sourceKind: "decision",
|
|
2732
|
+
sourcePath: d.location.path,
|
|
2733
|
+
action: d.state === CONFIRMED ? "add-lock" : "confirm-in-place",
|
|
2734
|
+
data: data2,
|
|
2735
|
+
supersedesId: d.supersedes,
|
|
2736
|
+
decision: d
|
|
2737
|
+
}));
|
|
2738
|
+
}
|
|
2692
2739
|
const done = truth.decisions.find((d) => d.proposalId === id3);
|
|
2693
2740
|
if (done !== void 0)
|
|
2694
2741
|
return failure([createDiagnostic("PROPOSAL_NOT_PENDING", `${id3} was already confirmed as ${done.id}`)]);
|
|
@@ -2697,16 +2744,54 @@ function createDecisionService(options) {
|
|
|
2697
2744
|
return failure([createDiagnostic("PROPOSAL_NOT_FOUND", `No proposal ${id3}`)]);
|
|
2698
2745
|
if (entry.status !== "pending")
|
|
2699
2746
|
return failure([createDiagnostic("PROPOSAL_NOT_PENDING", `${id3} is ${entry.status}`, entry.proposal.location)]);
|
|
2700
|
-
const
|
|
2701
|
-
|
|
2747
|
+
const data = await rawData(entry.proposal.location.path);
|
|
2748
|
+
if (data === void 0)
|
|
2749
|
+
return failure([createDiagnostic("PROPOSAL_NOT_FOUND", `${entry.proposal.location.path} is missing`)]);
|
|
2750
|
+
const stale = staleness(truth, entry.proposal);
|
|
2751
|
+
return success(finishCandidate(truth, {
|
|
2752
|
+
sourceId: id3,
|
|
2753
|
+
sourceKind: "proposal",
|
|
2754
|
+
sourcePath: entry.proposal.location.path,
|
|
2755
|
+
action: "create",
|
|
2756
|
+
data,
|
|
2757
|
+
supersedesId: entry.proposal.supersedes,
|
|
2758
|
+
proposal: entry.proposal,
|
|
2759
|
+
...stale === void 0 ? {} : { stale },
|
|
2760
|
+
...typeof data.proposed_by === "string" ? { proposedBy: data.proposed_by } : {},
|
|
2761
|
+
...typeof data.proposed_by_kind === "string" ? { proposedByKind: data.proposed_by_kind } : {}
|
|
2762
|
+
}));
|
|
2763
|
+
}
|
|
2764
|
+
async function rawData(repoPath3) {
|
|
2765
|
+
const text4 = await fs28.readText(`${root}/${repoPath3}`);
|
|
2766
|
+
if (text4 === void 0)
|
|
2767
|
+
return void 0;
|
|
2768
|
+
return parseYaml({ path: repoPath3, text: text4 }).value?.data ?? {};
|
|
2769
|
+
}
|
|
2770
|
+
function finishCandidate(truth, base) {
|
|
2771
|
+
const candidate = defined(Object.fromEntries(CANDIDATE_FIELDS.map((k) => [k, base.data[k]])));
|
|
2772
|
+
const target = base.supersedesId === null ? void 0 : truth.decisions.find((t2) => t2.id === base.supersedesId);
|
|
2773
|
+
const digest = candidateDigest({
|
|
2774
|
+
sourceId: base.sourceId,
|
|
2775
|
+
sourceKind: base.sourceKind,
|
|
2776
|
+
sourcePath: base.sourcePath,
|
|
2777
|
+
action: base.action,
|
|
2778
|
+
candidate,
|
|
2779
|
+
stale: base.stale ?? null,
|
|
2780
|
+
proposedBy: base.proposedBy ?? null,
|
|
2781
|
+
proposedByKind: base.proposedByKind ?? null,
|
|
2782
|
+
supersedes: base.supersedesId === null ? null : target === void 0 ? { id: base.supersedesId, missing: true } : { id: target.id, title: target.title, state: target.state, supersededBy: target.supersededBy, path: target.location.path, content: decisionLockDigest(target) }
|
|
2783
|
+
});
|
|
2784
|
+
return { ...base, candidate, ...target === void 0 ? {} : { target }, digest };
|
|
2785
|
+
}
|
|
2786
|
+
async function confirmProposal(actor, c, truth, repaired, warnings) {
|
|
2787
|
+
const id3 = c.sourceId;
|
|
2788
|
+
const proposal = c.proposal;
|
|
2789
|
+
const stale = c.stale;
|
|
2702
2790
|
if (stale !== void 0) {
|
|
2703
2791
|
warnings.push(createDiagnostic("PROPOSAL_STALE", `${id3} was made on an older Project Truth${stale.changedRefs.length > 0 ? ` (changed: ${stale.changedRefs.join(", ")})` : ""}; confirmed anyway`, proposal.location));
|
|
2704
2792
|
}
|
|
2705
|
-
const
|
|
2706
|
-
|
|
2707
|
-
return failure([createDiagnostic("PROPOSAL_NOT_FOUND", `${proposal.location.path} is missing`)]);
|
|
2708
|
-
const data = parseYaml({ path: proposal.location.path, text: text4 }).value?.data ?? {};
|
|
2709
|
-
const target = proposal.supersedes === null ? void 0 : truth.decisions.find((d) => d.id === proposal.supersedes);
|
|
2793
|
+
const data = c.data;
|
|
2794
|
+
const target = c.target;
|
|
2710
2795
|
if (proposal.supersedes !== null) {
|
|
2711
2796
|
if (target === void 0)
|
|
2712
2797
|
return failure([createDiagnostic("BROKEN_REFERENCE", `${id3} supersedes ${proposal.supersedes}, which does not exist`, proposal.location)]);
|
|
@@ -2716,8 +2801,8 @@ function createDecisionService(options) {
|
|
|
2716
2801
|
return failure([createDiagnostic("DECISION_SUPERSEDE_TARGET_INVALID", `${target.id} is ${target.state}; only a confirmed Decision can be superseded`, target.location)]);
|
|
2717
2802
|
}
|
|
2718
2803
|
const confirmedAt = now();
|
|
2719
|
-
const content = (
|
|
2720
|
-
id:
|
|
2804
|
+
const content = (decisionId2, digest) => yaml(defined({
|
|
2805
|
+
id: decisionId2,
|
|
2721
2806
|
title: data.title,
|
|
2722
2807
|
kind: data.kind,
|
|
2723
2808
|
state: CONFIRMED,
|
|
@@ -2740,9 +2825,9 @@ function createDecisionService(options) {
|
|
|
2740
2825
|
confirmed_by: actor.name,
|
|
2741
2826
|
lock: digest === void 0 ? void 0 : { digest }
|
|
2742
2827
|
}));
|
|
2743
|
-
const render = (
|
|
2744
|
-
const draft = parseDecisionFile(decisionPath(
|
|
2745
|
-
return content(
|
|
2828
|
+
const render = (decisionId2) => {
|
|
2829
|
+
const draft = parseDecisionFile(decisionPath(decisionId2), content(decisionId2));
|
|
2830
|
+
return content(decisionId2, draft.value === void 0 ? void 0 : decisionLockDigest(draft.value));
|
|
2746
2831
|
};
|
|
2747
2832
|
const probeId = await allocate("D", truth);
|
|
2748
2833
|
const probe = parseDecisionFile(decisionPath(probeId), render(probeId));
|
|
@@ -2775,17 +2860,9 @@ function createDecisionService(options) {
|
|
|
2775
2860
|
repaired
|
|
2776
2861
|
}, [...warnings, ...after.map((d) => ({ ...d, severity: "warning" }))]);
|
|
2777
2862
|
}
|
|
2778
|
-
async function confirmDecision(actor,
|
|
2779
|
-
const
|
|
2780
|
-
|
|
2781
|
-
return failure([createDiagnostic("PROPOSAL_NOT_FOUND", `No Decision ${id3}`)]);
|
|
2782
|
-
if (!isServiceFile(d))
|
|
2783
|
-
return failure([createDiagnostic("DECISION_TARGET_UNSUPPORTED", `${id3} is not a decisions/D-###.yaml file; DUO does not rewrite it`, d.location)]);
|
|
2784
|
-
if (d.state === "superseded" || d.state === CONFIRMED && d.lock !== void 0) {
|
|
2785
|
-
return failure([createDiagnostic("DECISION_LOCKED", `${id3} is ${d.state} and locked; supersede it with a new proposal`, d.location)]);
|
|
2786
|
-
}
|
|
2787
|
-
if (d.state === "rejected")
|
|
2788
|
-
return failure([createDiagnostic("PROPOSAL_NOT_PENDING", `${id3} is rejected`, d.location)]);
|
|
2863
|
+
async function confirmDecision(actor, c, truth, repaired, warnings) {
|
|
2864
|
+
const id3 = c.sourceId;
|
|
2865
|
+
const d = c.decision;
|
|
2789
2866
|
const digest = decisionLockDigest(d);
|
|
2790
2867
|
const values = d.state === CONFIRMED ? { lock: { digest }, confirmed_at: d.confirmedAt ?? now(), confirmed_by: d.confirmedBy ?? actor.name } : { state: CONFIRMED, owner: "human", confirmed_at: now(), confirmed_by: actor.name, lock: { digest } };
|
|
2791
2868
|
const target = d.supersedes === null ? void 0 : truth.decisions.find((t2) => t2.id === d.supersedes);
|
|
@@ -9554,6 +9631,45 @@ function sameFingerprint(a, b) {
|
|
|
9554
9631
|
return a !== void 0 && b !== void 0 && a.state === b.state && a.fingerprintMode === b.fingerprintMode && a.contentHash === b.contentHash && a.size === b.size && a.gitBlobOid === b.gitBlobOid;
|
|
9555
9632
|
}
|
|
9556
9633
|
|
|
9634
|
+
// packages/graph/dist/incremental/references.js
|
|
9635
|
+
async function readIndexedModuleReferences(root, paths) {
|
|
9636
|
+
const read2 = readIndexState(root);
|
|
9637
|
+
if (read2.state === void 0)
|
|
9638
|
+
return paths.map((path35) => ({ status: "unavailable", path: path35, reason: read2.problem ?? "index state" }));
|
|
9639
|
+
const files = new Map(read2.state.files.map((f) => [f.path, f]));
|
|
9640
|
+
const keys = paths.map((path35) => {
|
|
9641
|
+
const f = files.get(path35);
|
|
9642
|
+
return f?.analysis === void 0 || f.analysis.status !== "ok" || f.resolution === void 0 ? void 0 : { path: path35, contentHash: f.contentHash, analyzer: f.analysis.analyzer, analyzerIdentity: f.analysis.identity };
|
|
9643
|
+
});
|
|
9644
|
+
const present = keys.filter((k) => k !== void 0);
|
|
9645
|
+
const cached = await readCachedAnalyses(root, present);
|
|
9646
|
+
const byPath = new Map(present.map((k, i) => [k.path, cached[i]]));
|
|
9647
|
+
return paths.map((path35, i) => {
|
|
9648
|
+
const f = files.get(path35);
|
|
9649
|
+
if (f === void 0)
|
|
9650
|
+
return { status: "not-indexed", path: path35 };
|
|
9651
|
+
if (keys[i] === void 0)
|
|
9652
|
+
return { status: "not-analyzed", path: path35 };
|
|
9653
|
+
const entry = byPath.get(path35);
|
|
9654
|
+
const modules = f.resolution?.modules ?? [];
|
|
9655
|
+
if (entry === void 0)
|
|
9656
|
+
return { status: "unavailable", path: path35, reason: "analysis cache entry missing" };
|
|
9657
|
+
const refs = entry.analysis.moduleReferences;
|
|
9658
|
+
if (refs.length !== modules.length)
|
|
9659
|
+
return { status: "unavailable", path: path35, reason: "analysis and resolution do not match" };
|
|
9660
|
+
return {
|
|
9661
|
+
status: "ok",
|
|
9662
|
+
path: path35,
|
|
9663
|
+
language: entry.analysis.language,
|
|
9664
|
+
references: refs.map((r, j) => {
|
|
9665
|
+
const resolution = modules[j];
|
|
9666
|
+
const target = resolution.status === "resolved" && resolution.path !== path35 && files.has(resolution.path) ? resolution.path : void 0;
|
|
9667
|
+
return { kind: r.kind, specifier: r.specifier, typeOnly: r.typeOnly, location: r.location, resolution, ...target === void 0 ? {} : { target } };
|
|
9668
|
+
})
|
|
9669
|
+
};
|
|
9670
|
+
});
|
|
9671
|
+
}
|
|
9672
|
+
|
|
9557
9673
|
// packages/graph/dist/query/impact.js
|
|
9558
9674
|
var RANK = { direct: 0, structural: 1, historical: 2 };
|
|
9559
9675
|
var STEPS = {
|
|
@@ -10152,6 +10268,27 @@ function testRunEvidence(store, run2, test) {
|
|
|
10152
10268
|
metadata: { status, ...run2.command === void 0 ? {} : { command: run2.command } }
|
|
10153
10269
|
});
|
|
10154
10270
|
}
|
|
10271
|
+
function moduleReferenceEvidence(store, reader, r, commit) {
|
|
10272
|
+
const slice = reader.slice(r.location);
|
|
10273
|
+
if (slice.value === void 0)
|
|
10274
|
+
return void 0;
|
|
10275
|
+
const contentHash = sha256Text(slice.value);
|
|
10276
|
+
const range = lines(r.location);
|
|
10277
|
+
return store.add({
|
|
10278
|
+
id: evidenceId("repository", "file", `${r.path}
|
|
10279
|
+
module-reference
|
|
10280
|
+
${range?.join("-") ?? ""}
|
|
10281
|
+
${contentHash}`),
|
|
10282
|
+
basis: "repository",
|
|
10283
|
+
kind: "file",
|
|
10284
|
+
entity: { type: "file", path: r.path },
|
|
10285
|
+
source: r.location,
|
|
10286
|
+
contentHash,
|
|
10287
|
+
summary: `${r.path}:${range?.[0] ?? "?"} ${r.kind} "${r.specifier}" \u2192 ${r.target}`,
|
|
10288
|
+
pointer: { kind: "file", path: r.path, ...range === void 0 ? {} : { lines: range }, commit, contentHash },
|
|
10289
|
+
metadata: { moduleReference: r.kind, specifier: r.specifier, typeOnly: r.typeOnly, resolvedTo: r.target }
|
|
10290
|
+
}, slice.value);
|
|
10291
|
+
}
|
|
10155
10292
|
|
|
10156
10293
|
// packages/director/dist/llm/contract/evidence.js
|
|
10157
10294
|
var OBSERVABLE = /* @__PURE__ */ new Set(["repository", "git", "test"]);
|
|
@@ -10175,7 +10312,7 @@ function makeClaim(ctx, input) {
|
|
|
10175
10312
|
const basis = BASIS_ORDER.filter((b) => bases.has(b));
|
|
10176
10313
|
const enforced = input.enforced ?? false;
|
|
10177
10314
|
const v = input.violation;
|
|
10178
|
-
const unverifiable = v?.language !== void 0 && ctx.baselineStructuralLanguages !== void 0 && !ctx.baselineStructuralLanguages.has(v.language);
|
|
10315
|
+
const unverifiable = v?.unverifiable === true || v?.language !== void 0 && ctx.baselineStructuralLanguages !== void 0 && !ctx.baselineStructuralLanguages.has(v.language);
|
|
10179
10316
|
const provenance = v === void 0 || ctx.baselineKeys === void 0 || input.alignment === "ALIGNED" ? void 0 : !ctx.baselineKeys.has(v.key) ? unverifiable ? "unverified-at-adoption" : "introduced" : v.touched ? "pre-existing-touched" : "pre-existing";
|
|
10180
10317
|
const preExisting = provenance === "pre-existing" || provenance === "pre-existing-touched";
|
|
10181
10318
|
return {
|
|
@@ -10324,77 +10461,333 @@ function declaredReferenceParts(message2) {
|
|
|
10324
10461
|
return m === null ? void 0 : { governing: m[1] ?? "", offending: `ref:${m[2] ?? ""}:${m[3] ?? ""}` };
|
|
10325
10462
|
}
|
|
10326
10463
|
|
|
10327
|
-
// packages/director/dist/
|
|
10328
|
-
var
|
|
10329
|
-
var
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10464
|
+
// packages/director/dist/context/policy.js
|
|
10465
|
+
var CONTEXT_POLICY_VERSION = "3";
|
|
10466
|
+
var EDGE_WEIGHTS = {
|
|
10467
|
+
GOVERNS: 1,
|
|
10468
|
+
IMPLEMENTS: 0.9,
|
|
10469
|
+
TRACKED_BY: 0.9,
|
|
10470
|
+
VALIDATED_BY: 0.8,
|
|
10471
|
+
REQUIRES: 0.7,
|
|
10472
|
+
CALLS: 0.7,
|
|
10473
|
+
CONTAINS: 0.6,
|
|
10474
|
+
IMPORTS: 0.5,
|
|
10475
|
+
CHANGED_WITH: 0.3
|
|
10476
|
+
};
|
|
10477
|
+
var SEED_STRENGTH = { id: 1, path: 1, symbol: 1, "symbol-name": 0.9, keyword: 0.6, diff: 1 };
|
|
10478
|
+
var CROSS_LANGUAGE_FACTOR = 0.5;
|
|
10479
|
+
var DEFAULT_LIMITS = {
|
|
10480
|
+
/** Upper bound; project.yaml context.max_depth (1–3, default 2) replaces it. */
|
|
10481
|
+
maxDepth: 2,
|
|
10482
|
+
/** Finalized candidates (05 nodeLimit 200). */
|
|
10483
|
+
nodeLimit: 200,
|
|
10484
|
+
/** Edges read per expanded node. */
|
|
10485
|
+
edgeLimit: 2e3
|
|
10486
|
+
};
|
|
10487
|
+
var MIN_BUDGET = 1e3;
|
|
10488
|
+
var MAX_BUDGET = 1e6;
|
|
10489
|
+
var TASK_TOKEN_LIMIT = 200;
|
|
10490
|
+
var KEYWORD = {
|
|
10491
|
+
/** Keyword seeds kept (05: top 8). */
|
|
10492
|
+
top: 8,
|
|
10493
|
+
/** Keyword matches below this fraction of the best one are not seeds. */
|
|
10494
|
+
minFraction: 0.3,
|
|
10495
|
+
/** BM25 parameters. */
|
|
10496
|
+
k1: 1.2,
|
|
10497
|
+
b: 0.75
|
|
10498
|
+
};
|
|
10499
|
+
var SYMBOL_NAME_MAX_MATCHES = 5;
|
|
10500
|
+
var TIER_ORDER = [
|
|
10501
|
+
"requirement",
|
|
10502
|
+
"decision",
|
|
10503
|
+
"pending",
|
|
10504
|
+
"code-direct",
|
|
10505
|
+
"test",
|
|
10506
|
+
"issue",
|
|
10507
|
+
"code-structural",
|
|
10508
|
+
"code-historical"
|
|
10509
|
+
];
|
|
10510
|
+
var TIER_SHARE = {
|
|
10511
|
+
requirement: 0.25,
|
|
10512
|
+
decision: 0.15,
|
|
10513
|
+
pending: 0.05,
|
|
10514
|
+
"code-direct": 0.3,
|
|
10515
|
+
test: 0.12,
|
|
10516
|
+
issue: 0.05,
|
|
10517
|
+
"code-structural": 0.06,
|
|
10518
|
+
"code-historical": 0.02
|
|
10519
|
+
};
|
|
10520
|
+
var L3_PER_FILE_FIRST_PASS = 2;
|
|
10521
|
+
var LEADING_CONTEXT_LINES = 12;
|
|
10522
|
+
var GENERIC_FILE_WINDOW = { lines: 40, chars: 2e3 };
|
|
10523
|
+
|
|
10524
|
+
// packages/director/dist/context/expand.js
|
|
10525
|
+
var HUBS = /* @__PURE__ */ new Set(["project", "milestone", "decision", "issue"]);
|
|
10526
|
+
function displayRef(id3) {
|
|
10527
|
+
const ref = parseNodeId(id3);
|
|
10528
|
+
if (ref === void 0)
|
|
10529
|
+
return id3;
|
|
10530
|
+
switch (ref.type) {
|
|
10531
|
+
case "project":
|
|
10532
|
+
return "project";
|
|
10533
|
+
case "file":
|
|
10534
|
+
return ref.path;
|
|
10535
|
+
case "symbol":
|
|
10536
|
+
return `${ref.path}#${ref.symbol}`;
|
|
10537
|
+
case "test":
|
|
10538
|
+
return `${ref.path}#${ref.name}`;
|
|
10539
|
+
default:
|
|
10540
|
+
return ref.id;
|
|
10541
|
+
}
|
|
10336
10542
|
}
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
return readSourceFile(ctx.root, file).value;
|
|
10340
|
-
const source = blobSource(side);
|
|
10341
|
-
if (source === void 0)
|
|
10342
|
-
return void 0;
|
|
10343
|
-
const blob = await ctx.git.readBlob(source, file);
|
|
10344
|
-
return blob.value === void 0 ? void 0 : canonicalSourceText(decoder2.decode(blob.value));
|
|
10543
|
+
function better(a, b) {
|
|
10544
|
+
return b.score - a.score || a.depth - b.depth || compareUtf8(a.node.id, b.node.id);
|
|
10345
10545
|
}
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10546
|
+
var Heap = class {
|
|
10547
|
+
items = [];
|
|
10548
|
+
get size() {
|
|
10549
|
+
return this.items.length;
|
|
10550
|
+
}
|
|
10551
|
+
push(c) {
|
|
10552
|
+
const a = this.items;
|
|
10553
|
+
a.push(c);
|
|
10554
|
+
let i = a.length - 1;
|
|
10555
|
+
while (i > 0) {
|
|
10556
|
+
const p = i - 1 >> 1;
|
|
10557
|
+
if (better(a[i], a[p]) >= 0)
|
|
10558
|
+
break;
|
|
10559
|
+
[a[i], a[p]] = [a[p], a[i]];
|
|
10560
|
+
i = p;
|
|
10561
|
+
}
|
|
10562
|
+
}
|
|
10563
|
+
pop() {
|
|
10564
|
+
const a = this.items;
|
|
10565
|
+
const top = a[0];
|
|
10566
|
+
const last = a.pop();
|
|
10567
|
+
if (a.length > 0 && last !== void 0) {
|
|
10568
|
+
a[0] = last;
|
|
10569
|
+
let i = 0;
|
|
10570
|
+
for (; ; ) {
|
|
10571
|
+
const l = 2 * i + 1, r = l + 1;
|
|
10572
|
+
let m = i;
|
|
10573
|
+
if (l < a.length && better(a[l], a[m]) < 0)
|
|
10574
|
+
m = l;
|
|
10575
|
+
if (r < a.length && better(a[r], a[m]) < 0)
|
|
10576
|
+
m = r;
|
|
10577
|
+
if (m === i)
|
|
10578
|
+
break;
|
|
10579
|
+
[a[i], a[m]] = [a[m], a[i]];
|
|
10580
|
+
i = m;
|
|
10581
|
+
}
|
|
10582
|
+
}
|
|
10583
|
+
return top;
|
|
10584
|
+
}
|
|
10585
|
+
};
|
|
10586
|
+
function step(edge) {
|
|
10587
|
+
const provenance = typeof edge.metadata.provenance === "string" ? edge.metadata.provenance : "unknown";
|
|
10588
|
+
return { from: displayRef(edge.from), type: edge.type, to: displayRef(edge.to), provenance };
|
|
10351
10589
|
}
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
const
|
|
10356
|
-
for (const
|
|
10357
|
-
const
|
|
10358
|
-
const
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
if (c2 !== void 0)
|
|
10383
|
-
out.push(c2);
|
|
10384
|
-
}
|
|
10590
|
+
function expandCandidates(store, seeds, options) {
|
|
10591
|
+
const heap = new Heap();
|
|
10592
|
+
const best = /* @__PURE__ */ new Map();
|
|
10593
|
+
const seedIds = new Set(seeds.map((s) => s.id));
|
|
10594
|
+
for (const s of seeds) {
|
|
10595
|
+
const ref = parseNodeId(s.id);
|
|
10596
|
+
const node = ref === void 0 ? void 0 : store.getNode(ref);
|
|
10597
|
+
if (node === void 0)
|
|
10598
|
+
continue;
|
|
10599
|
+
heap.push({ node, score: s.strength, depth: 0, seed: s.id, steps: [] });
|
|
10600
|
+
best.set(s.id, Math.max(best.get(s.id) ?? 0, s.strength));
|
|
10601
|
+
}
|
|
10602
|
+
const done = /* @__PURE__ */ new Map();
|
|
10603
|
+
let truncated = false;
|
|
10604
|
+
while (heap.size > 0) {
|
|
10605
|
+
const c = heap.pop();
|
|
10606
|
+
if (done.has(c.node.id))
|
|
10607
|
+
continue;
|
|
10608
|
+
if (done.size >= options.nodeLimit) {
|
|
10609
|
+
truncated = true;
|
|
10610
|
+
break;
|
|
10611
|
+
}
|
|
10612
|
+
done.set(c.node.id, c);
|
|
10613
|
+
if (c.depth >= options.maxDepth || HUBS.has(c.node.type) && !(c.depth === 0 && seedIds.has(c.node.id)))
|
|
10614
|
+
continue;
|
|
10615
|
+
const adjacent = store.adjacentEdges([c.node.ref], { direction: "both", limit: options.edgeLimit });
|
|
10616
|
+
if (adjacent.truncated)
|
|
10617
|
+
truncated = true;
|
|
10618
|
+
for (const edge of adjacent.edges) {
|
|
10619
|
+
if (edge.type === "SUPERSEDES")
|
|
10385
10620
|
continue;
|
|
10386
|
-
|
|
10387
|
-
if (
|
|
10621
|
+
const other = edge.from === c.node.id ? edge.to : edge.from;
|
|
10622
|
+
if (done.has(other) || other.startsWith("project:"))
|
|
10388
10623
|
continue;
|
|
10389
|
-
const
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10624
|
+
const score = c.score * EDGE_WEIGHTS[edge.type];
|
|
10625
|
+
if (score <= (best.get(other) ?? 0))
|
|
10626
|
+
continue;
|
|
10627
|
+
const ref = parseNodeId(other);
|
|
10628
|
+
const node = ref === void 0 ? void 0 : store.getNode(ref);
|
|
10629
|
+
if (node === void 0)
|
|
10630
|
+
continue;
|
|
10631
|
+
best.set(other, score);
|
|
10632
|
+
heap.push({ node, score, depth: c.depth + 1, seed: c.seed, steps: [...c.steps, step(edge)] });
|
|
10633
|
+
}
|
|
10634
|
+
}
|
|
10635
|
+
return { candidates: [...done.values()].sort(better), truncated };
|
|
10636
|
+
}
|
|
10637
|
+
|
|
10638
|
+
// packages/director/dist/review/seeds.js
|
|
10639
|
+
var PROPOSALS = `${STATE_DIR_NAME}/decisions/proposals/`;
|
|
10640
|
+
function hunkRange(h) {
|
|
10641
|
+
return h.newLines > 0 ? [h.newStart, h.newStart + h.newLines - 1] : [Math.max(1, h.newStart), Math.max(1, h.newStart)];
|
|
10642
|
+
}
|
|
10643
|
+
function overlaps(loc2, range) {
|
|
10644
|
+
if (loc2?.startLine === void 0)
|
|
10645
|
+
return false;
|
|
10646
|
+
const end2 = loc2.endLine ?? loc2.startLine;
|
|
10647
|
+
return range[0] <= end2 && range[1] >= loc2.startLine;
|
|
10648
|
+
}
|
|
10649
|
+
function owned(graph, path35) {
|
|
10650
|
+
const out = [];
|
|
10651
|
+
let afterId;
|
|
10652
|
+
for (; ; ) {
|
|
10653
|
+
const page = graph.listNodes({ ownerFile: path35, limit: 1e3, ...afterId === void 0 ? {} : { afterId } });
|
|
10654
|
+
out.push(...page);
|
|
10655
|
+
if (page.length < 1e3)
|
|
10656
|
+
return out;
|
|
10657
|
+
afterId = page[page.length - 1]?.id;
|
|
10658
|
+
}
|
|
10659
|
+
}
|
|
10660
|
+
var width = (loc2) => (loc2?.endLine ?? 0) - (loc2?.startLine ?? 0);
|
|
10661
|
+
function truthDefinitions(truth) {
|
|
10662
|
+
return [
|
|
10663
|
+
...truth.requirements.map((d) => ({ ref: definitionRef("requirement", d.id), location: d.location })),
|
|
10664
|
+
...truth.decisions.map((d) => ({ ref: definitionRef("decision", d.id), location: d.location })),
|
|
10665
|
+
...truth.constraints.map((d) => ({ ref: definitionRef("decision", d.id), location: d.location })),
|
|
10666
|
+
...truth.issues.map((d) => ({ ref: definitionRef("issue", d.id), location: d.location })),
|
|
10667
|
+
...truth.milestones.map((d) => ({ ref: definitionRef("milestone", d.id), location: d.location }))
|
|
10668
|
+
];
|
|
10669
|
+
}
|
|
10670
|
+
function diffSeeds(files, graph, truth) {
|
|
10671
|
+
const seeds = /* @__PURE__ */ new Map();
|
|
10672
|
+
const put = (entity, reason, path35, evidenceId2) => {
|
|
10673
|
+
if (graph.getNode(entity) === void 0)
|
|
10674
|
+
return;
|
|
10675
|
+
const id3 = nodeId(entity);
|
|
10676
|
+
const s = seeds.get(id3) ?? { entity, reason, path: path35, evidence: /* @__PURE__ */ new Set() };
|
|
10677
|
+
s.evidence.add(evidenceId2);
|
|
10678
|
+
seeds.set(id3, s);
|
|
10679
|
+
};
|
|
10680
|
+
const defs = truthDefinitions(truth);
|
|
10681
|
+
for (const f of files) {
|
|
10682
|
+
if (f.kind === "deleted")
|
|
10683
|
+
continue;
|
|
10684
|
+
if (f.path.startsWith(`${STATE_DIR_NAME}/`)) {
|
|
10685
|
+
if (f.path.startsWith(PROPOSALS))
|
|
10686
|
+
continue;
|
|
10687
|
+
for (const h of f.hunks) {
|
|
10688
|
+
for (const d of defs)
|
|
10689
|
+
if (d.location.path === f.path && overlaps(d.location, hunkRange(h)))
|
|
10690
|
+
put(d.ref, "truth-changed", f.path, h.evidenceId);
|
|
10691
|
+
}
|
|
10692
|
+
continue;
|
|
10693
|
+
}
|
|
10694
|
+
const nodes = owned(graph, f.path).filter((n) => n.type === "symbol" || n.type === "test");
|
|
10695
|
+
const ranges = new Map(nodes.map((n) => [n.id, nodeLocations(n)]));
|
|
10696
|
+
let touched = false;
|
|
10697
|
+
for (const h of f.hunks) {
|
|
10698
|
+
const range = hunkRange(h);
|
|
10699
|
+
const hits = nodes.map((n) => ({ n, at: (ranges.get(n.id) ?? []).filter((l) => overlaps(l, range)) })).filter((x) => x.at.length > 0);
|
|
10700
|
+
const inner = hits.filter((x) => !hits.some((o) => o !== x && o.at.some((ol) => x.at.some((xl) => width(ol) < width(xl) && overlaps(xl, [ol.startLine ?? 0, ol.endLine ?? 0])))));
|
|
10701
|
+
for (const { n } of inner) {
|
|
10702
|
+
put(n.ref, "hunk-overlap", f.path, h.evidenceId);
|
|
10703
|
+
touched = true;
|
|
10704
|
+
}
|
|
10705
|
+
}
|
|
10706
|
+
if (!touched)
|
|
10707
|
+
for (const e of f.evidenceIds)
|
|
10708
|
+
put(fileRef(f.path), "file-changed", f.path, e);
|
|
10709
|
+
}
|
|
10710
|
+
return [...seeds.entries()].sort(([a], [b]) => compareUtf8(a, b)).map(([id3, s]) => ({
|
|
10711
|
+
id: id3,
|
|
10712
|
+
ref: displayRef(id3),
|
|
10713
|
+
entity: s.entity,
|
|
10714
|
+
reason: s.reason,
|
|
10715
|
+
path: s.path,
|
|
10716
|
+
evidenceIds: [...s.evidence].sort(compareUtf8)
|
|
10717
|
+
}));
|
|
10718
|
+
}
|
|
10719
|
+
|
|
10720
|
+
// packages/director/dist/review/rules.js
|
|
10721
|
+
var DECISIONS = `${STATE_DIR_NAME}/decisions/`;
|
|
10722
|
+
var PROPOSALS2 = `${STATE_DIR_NAME}/decisions/proposals/`;
|
|
10723
|
+
var TEST_FILE3 = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
|
|
10724
|
+
var decoder2 = new TextDecoder("utf-8");
|
|
10725
|
+
function blobSource(end2) {
|
|
10726
|
+
if (end2 === "HEAD" || end2 === "INDEX")
|
|
10727
|
+
return end2;
|
|
10728
|
+
return typeof end2 === "string" ? void 0 : { commit: end2.commit };
|
|
10729
|
+
}
|
|
10730
|
+
async function sideText(ctx, side, file) {
|
|
10731
|
+
if (side === "WORKTREE")
|
|
10732
|
+
return readSourceFile(ctx.root, file).value;
|
|
10733
|
+
const source = blobSource(side);
|
|
10734
|
+
if (source === void 0)
|
|
10735
|
+
return void 0;
|
|
10736
|
+
const blob = await ctx.git.readBlob(source, file);
|
|
10737
|
+
return blob.value === void 0 ? void 0 : canonicalSourceText(decoder2.decode(blob.value));
|
|
10738
|
+
}
|
|
10739
|
+
function decisionsOf(file, text4) {
|
|
10740
|
+
if (file.endsWith(".md"))
|
|
10741
|
+
return [...parseDefinitionMarkdown(file, text4).value?.decisions ?? []];
|
|
10742
|
+
const d = parseDecisionFile(file, text4).value;
|
|
10743
|
+
return d === void 0 ? [] : [d];
|
|
10744
|
+
}
|
|
10745
|
+
var decisionFiles = (ctx) => ctx.files.filter((f) => f.path.startsWith(DECISIONS) && !f.path.startsWith(PROPOSALS2));
|
|
10746
|
+
var codeSeeds = (ctx) => ctx.seeds.filter((s) => s.entity.type === "symbol" || s.entity.type === "file");
|
|
10747
|
+
async function decisionIntegrity(ctx) {
|
|
10748
|
+
const out = [];
|
|
10749
|
+
for (const f of decisionFiles(ctx)) {
|
|
10750
|
+
const oldPath = f.oldPath ?? f.path;
|
|
10751
|
+
const oldText = f.kind === "added" || f.kind === "untracked" ? void 0 : await sideText(ctx, ctx.from, oldPath);
|
|
10752
|
+
const before = oldText === void 0 ? [] : decisionsOf(oldPath, oldText);
|
|
10753
|
+
const after = f.kind === "deleted" ? [] : ctx.truth.decisions.filter((d) => d.location.path === f.path);
|
|
10754
|
+
const ids2 = [.../* @__PURE__ */ new Set([...before.map((d) => d.id), ...after.map((d) => d.id)])].sort(compareUtf8);
|
|
10755
|
+
for (const id3 of ids2) {
|
|
10756
|
+
const o = before.find((d) => d.id === id3);
|
|
10757
|
+
const n = after.find((d) => d.id === id3);
|
|
10758
|
+
const oldEvidence = o === void 0 || oldText === void 0 ? void 0 : truthEvidenceFromText(ctx.store, { kind: "decision", id: id3, title: o.title, location: o.location }, oldText, `old:${ctx.fromLabel}`);
|
|
10759
|
+
const newEvidence = n === void 0 ? void 0 : truthEvidence(ctx.store, ctx.reader, { kind: "decision", id: id3, title: n.title, location: n.location });
|
|
10760
|
+
const evidence = [oldEvidence, newEvidence, ...f.evidenceIds];
|
|
10761
|
+
const subject = { kind: "decision", id: id3 };
|
|
10762
|
+
const wasConfirmed = o !== void 0 && (o.state === "confirmed" || o.state === "superseded");
|
|
10763
|
+
if (n === void 0) {
|
|
10764
|
+
if (wasConfirmed) {
|
|
10765
|
+
const c2 = makeClaim(ctx, {
|
|
10766
|
+
rule: "decision-integrity",
|
|
10767
|
+
subject,
|
|
10768
|
+
alignment: "CONFLICT",
|
|
10769
|
+
reason: "confirmed-decision-removed",
|
|
10770
|
+
enforced: true,
|
|
10771
|
+
evidence,
|
|
10772
|
+
expected: `${id3} stays in Project Truth (confirmed Decisions are superseded, not deleted)`,
|
|
10773
|
+
observed: `${id3} was removed`
|
|
10774
|
+
});
|
|
10775
|
+
if (c2 !== void 0)
|
|
10776
|
+
out.push(c2);
|
|
10777
|
+
}
|
|
10778
|
+
continue;
|
|
10779
|
+
}
|
|
10780
|
+
if (n.state !== "confirmed" && n.state !== "superseded")
|
|
10781
|
+
continue;
|
|
10782
|
+
const lock = verifyDecisionLock(n).value;
|
|
10783
|
+
let c;
|
|
10784
|
+
if (wasConfirmed && decisionLockDigest(o) !== decisionLockDigest(n)) {
|
|
10785
|
+
c = makeClaim(ctx, {
|
|
10786
|
+
rule: "decision-integrity",
|
|
10787
|
+
subject,
|
|
10788
|
+
alignment: "CONFLICT",
|
|
10789
|
+
reason: "confirmed-content-changed",
|
|
10790
|
+
enforced: true,
|
|
10398
10791
|
evidence,
|
|
10399
10792
|
expected: `confirmed ${id3} content unchanged (supersede it with a new Decision instead)`,
|
|
10400
10793
|
observed: `content fields of ${id3} changed`
|
|
@@ -10813,6 +11206,124 @@ function scopeRelevance(ctx, covered = /* @__PURE__ */ new Set()) {
|
|
|
10813
11206
|
}
|
|
10814
11207
|
return out;
|
|
10815
11208
|
}
|
|
11209
|
+
var overlapsLines = (loc2, [a, b]) => {
|
|
11210
|
+
const start2 = loc2.startLine ?? 1;
|
|
11211
|
+
return start2 <= b && (loc2.endLine ?? start2) >= a;
|
|
11212
|
+
};
|
|
11213
|
+
var preview = (items) => `${items.slice(0, 5).join(", ")}${items.length > 5 ? `, \u2026 (${items.length} in all)` : ""}`;
|
|
11214
|
+
function confirmedAfterBaseline(confirmedAt, recordedAt) {
|
|
11215
|
+
if (confirmedAt === void 0 || recordedAt === void 0)
|
|
11216
|
+
return false;
|
|
11217
|
+
const c = Date.parse(confirmedAt);
|
|
11218
|
+
const r = Date.parse(recordedAt);
|
|
11219
|
+
return Number.isFinite(c) && Number.isFinite(r) && c > r;
|
|
11220
|
+
}
|
|
11221
|
+
async function decisionForbidsImport(ctx) {
|
|
11222
|
+
const decisions = ctx.truth.decisions.filter((d) => isActive(d) && (d.forbids.importedPaths?.length ?? 0) > 0).sort((a, b) => compareUtf8(a.id, b.id));
|
|
11223
|
+
if (decisions.length === 0)
|
|
11224
|
+
return { claims: [], limitations: [] };
|
|
11225
|
+
const matchers = decisions.map((d) => ({ d, match: (d.forbids.importedPaths ?? []).flatMap((p) => compileRepoPattern(p) ?? []) }));
|
|
11226
|
+
const changed = ctx.files.filter((f) => f.kind !== "deleted" && !f.binary && !f.path.startsWith(`${STATE_DIR_NAME}/`) && f.hunks.length > 0);
|
|
11227
|
+
const usable = [];
|
|
11228
|
+
const otherSide = [];
|
|
11229
|
+
for (const f of changed) {
|
|
11230
|
+
if (ctx.to === "WORKTREE") {
|
|
11231
|
+
usable.push(f);
|
|
11232
|
+
continue;
|
|
11233
|
+
}
|
|
11234
|
+
const side = await sideText(ctx, ctx.to, f.path);
|
|
11235
|
+
const current = readSourceFile(ctx.root, f.path).value;
|
|
11236
|
+
if (side !== void 0 && current !== void 0 && side === canonicalSourceText(current))
|
|
11237
|
+
usable.push(f);
|
|
11238
|
+
else
|
|
11239
|
+
otherSide.push(f.path);
|
|
11240
|
+
}
|
|
11241
|
+
const indexed = await readIndexedModuleReferences(ctx.root, usable.map((f) => f.path));
|
|
11242
|
+
const unresolved = [];
|
|
11243
|
+
const ambiguous = [];
|
|
11244
|
+
const unsupported = [];
|
|
11245
|
+
const notIndexedTarget = [];
|
|
11246
|
+
const unavailable2 = [];
|
|
11247
|
+
const groups = /* @__PURE__ */ new Map();
|
|
11248
|
+
usable.forEach((f, i) => {
|
|
11249
|
+
const r = indexed[i];
|
|
11250
|
+
if (r === void 0 || r.status === "unavailable") {
|
|
11251
|
+
unavailable2.push(f.path);
|
|
11252
|
+
return;
|
|
11253
|
+
}
|
|
11254
|
+
if (r.status !== "ok")
|
|
11255
|
+
return;
|
|
11256
|
+
for (const ref of r.references) {
|
|
11257
|
+
const hunks = f.hunks.filter((h) => overlapsLines(ref.location, hunkRange(h))).map((h) => h.evidenceId);
|
|
11258
|
+
if (hunks.length === 0)
|
|
11259
|
+
continue;
|
|
11260
|
+
const where = `${f.path}:${ref.location.startLine ?? "?"}`;
|
|
11261
|
+
const target = ref.target;
|
|
11262
|
+
if (target === void 0) {
|
|
11263
|
+
const s = ref.resolution.status;
|
|
11264
|
+
if (s === "unresolved")
|
|
11265
|
+
unresolved.push(where);
|
|
11266
|
+
else if (s === "ambiguous")
|
|
11267
|
+
ambiguous.push(where);
|
|
11268
|
+
else if (s === "unsupported")
|
|
11269
|
+
unsupported.push(where);
|
|
11270
|
+
else if (s === "resolved")
|
|
11271
|
+
notIndexedTarget.push(where);
|
|
11272
|
+
continue;
|
|
11273
|
+
}
|
|
11274
|
+
for (const { d, match } of matchers) {
|
|
11275
|
+
if (!match.some((m) => m(target)))
|
|
11276
|
+
continue;
|
|
11277
|
+
const key2 = `${d.id}
|
|
11278
|
+
${f.path}
|
|
11279
|
+
${target}`;
|
|
11280
|
+
const g = groups.get(key2) ?? { d, source: f.path, target, sites: [] };
|
|
11281
|
+
g.sites.push({ ref, hunks });
|
|
11282
|
+
groups.set(key2, g);
|
|
11283
|
+
}
|
|
11284
|
+
}
|
|
11285
|
+
});
|
|
11286
|
+
const coverage = ctx.baselineEvaluatedRules?.has("decision-forbids-import") === true;
|
|
11287
|
+
const claims = [];
|
|
11288
|
+
for (const g of [...groups.values()].sort((a, b) => compareUtf8(a.d.id, b.d.id) || compareUtf8(a.source, b.source) || compareUtf8(a.target, b.target))) {
|
|
11289
|
+
const where = g.sites.map((s) => `${g.source}:${s.ref.location.startLine ?? "?"} ${s.ref.kind} "${s.ref.specifier}"${s.ref.typeOnly ? " (type-only)" : ""}`).join("; ");
|
|
11290
|
+
const unverifiable = ctx.baselineKeys !== void 0 && !coverage && !confirmedAfterBaseline(g.d.confirmedAt, ctx.baselineRecordedAt);
|
|
11291
|
+
const c = makeClaim(ctx, {
|
|
11292
|
+
rule: "decision-forbids-import",
|
|
11293
|
+
subject: { kind: "decision", id: g.d.id },
|
|
11294
|
+
key: `import:${g.source}->${g.target}`,
|
|
11295
|
+
alignment: "CONFLICT",
|
|
11296
|
+
reason: "forbidden-import",
|
|
11297
|
+
enforced: g.d.enforcement === "block",
|
|
11298
|
+
expected: `no changed import resolves to ${(g.d.forbids.importedPaths ?? []).join(", ")} (${g.d.id})`,
|
|
11299
|
+
observed: `forbidden import on a changed line: ${where} resolves to ${g.target}`,
|
|
11300
|
+
evidence: [
|
|
11301
|
+
decisionEvidence(ctx, g.d.id),
|
|
11302
|
+
...g.sites.map((s) => moduleReferenceEvidence(ctx.store, ctx.reader, { path: g.source, location: s.ref.location, kind: s.ref.kind, specifier: s.ref.specifier, typeOnly: s.ref.typeOnly, target: g.target }, ctx.toLabel)),
|
|
11303
|
+
...g.sites.flatMap((s) => s.hunks),
|
|
11304
|
+
nodeEvidence(ctx, ctx.graph.getNode(fileRef(g.target)))
|
|
11305
|
+
],
|
|
11306
|
+
violation: { key: violationKey("decision-forbids-import", g.d.id, `import:${nodeId(fileRef(g.source))}->${nodeId(fileRef(g.target))}`), touched: true, ...unverifiable ? { unverifiable: true } : {} }
|
|
11307
|
+
});
|
|
11308
|
+
if (c !== void 0)
|
|
11309
|
+
claims.push(c);
|
|
11310
|
+
}
|
|
11311
|
+
const limitations2 = [];
|
|
11312
|
+
const not = "were not checked against forbids.imported_paths";
|
|
11313
|
+
if (unresolved.length > 0)
|
|
11314
|
+
limitations2.push({ code: "imports-unresolved", message: `${unresolved.length} changed import(s) did not resolve to a repository file and ${not}: ${preview(unresolved)}.` });
|
|
11315
|
+
if (ambiguous.length > 0)
|
|
11316
|
+
limitations2.push({ code: "imports-ambiguous", message: `${ambiguous.length} changed import(s) have more than one candidate file and ${not}: ${preview(ambiguous)}.` });
|
|
11317
|
+
if (unsupported.length > 0)
|
|
11318
|
+
limitations2.push({ code: "imports-resolution-unsupported", message: `${unsupported.length} changed import(s) are in a language without module resolution (imports are recorded as written) and ${not}: ${preview(unsupported)}.` });
|
|
11319
|
+
if (notIndexedTarget.length > 0)
|
|
11320
|
+
limitations2.push({ code: "imports-target-not-indexed", message: `${notIndexedTarget.length} changed import(s) resolve to a file outside the index and ${not}: ${preview(notIndexedTarget)}.` });
|
|
11321
|
+
if (unavailable2.length > 0)
|
|
11322
|
+
limitations2.push({ code: "imports-index-unavailable", message: `The indexed imports of ${unavailable2.length} changed file(s) could not be read and ${not}: ${preview(unavailable2)}.` });
|
|
11323
|
+
if (otherSide.length > 0)
|
|
11324
|
+
limitations2.push({ code: "imports-reviewed-side-not-indexed", message: `${otherSide.length} changed file(s) differ on the reviewed side from the indexed working tree; their imports ${not}: ${preview(otherSide)}.` });
|
|
11325
|
+
return { claims, limitations: limitations2 };
|
|
11326
|
+
}
|
|
10816
11327
|
|
|
10817
11328
|
// packages/director/dist/adoption/head.js
|
|
10818
11329
|
var externals = (s) => s.filter((x) => x.kind === "external");
|
|
@@ -10834,7 +11345,7 @@ async function headBaselineFindings(input) {
|
|
|
10834
11345
|
if (!found.has(key2))
|
|
10835
11346
|
found.set(key2, { key: key2, rule, governing, offending, ...extra });
|
|
10836
11347
|
};
|
|
10837
|
-
const decisions = input.truth.decisions.filter((d) => isActive(d) && d.forbids.paths.length + d.forbids.symbols.length + d.forbids.dependencies.length > 0);
|
|
11348
|
+
const decisions = input.truth.decisions.filter((d) => isActive(d) && d.forbids.paths.length + d.forbids.symbols.length + d.forbids.dependencies.length + (d.forbids.importedPaths?.length ?? 0) > 0);
|
|
10838
11349
|
const declared = input.stateDiagnostics.filter((x) => x.code === "DECLARED_SYMBOL_UNRESOLVED");
|
|
10839
11350
|
const needSymbols = decisions.some((d) => d.forbids.symbols.length > 0) || declared.length > 0 && input.divergent.size > 0;
|
|
10840
11351
|
const symbols = [];
|
|
@@ -10884,6 +11395,20 @@ async function headBaselineFindings(input) {
|
|
|
10884
11395
|
}
|
|
10885
11396
|
}
|
|
10886
11397
|
}
|
|
11398
|
+
const importing = decisions.filter((d) => (d.forbids.importedPaths?.length ?? 0) > 0).map((d) => ({ d, match: (d.forbids.importedPaths ?? []).flatMap((p) => compileRepoPattern(p) ?? []) }));
|
|
11399
|
+
if (importing.length > 0) {
|
|
11400
|
+
for (const f of headFiles.filter((p) => !input.divergent.has(p))) {
|
|
11401
|
+
for (const e of input.graph.adjacentEdges([fileRef(f)], { direction: "outgoing", types: ["IMPORTS"], limit: 1e4 }).edges) {
|
|
11402
|
+
const to = parseNodeId(e.to);
|
|
11403
|
+
if (to?.type !== "file" || !inHead.has(to.path))
|
|
11404
|
+
continue;
|
|
11405
|
+
for (const { d, match } of importing) {
|
|
11406
|
+
if (match.some((m) => m(to.path)))
|
|
11407
|
+
add("decision-forbids-import", d.id, `import:${nodeId(fileRef(f))}->${nodeId(fileRef(to.path))}`, { path: f, enforced: d.enforcement === "block" });
|
|
11408
|
+
}
|
|
11409
|
+
}
|
|
11410
|
+
}
|
|
11411
|
+
}
|
|
10887
11412
|
if (declared.length > 0 && input.divergent.size > 0)
|
|
10888
11413
|
limitations2.add("declared-references-approximated-for-divergent-paths");
|
|
10889
11414
|
for (const diag of declared) {
|
|
@@ -11087,7 +11612,12 @@ async function captureAdoptionBaseline(root, options) {
|
|
|
11087
11612
|
own?.value?.dispose();
|
|
11088
11613
|
}
|
|
11089
11614
|
const bootstrapTruth = await bootstrapTruthOf(root, git.value);
|
|
11090
|
-
const
|
|
11615
|
+
const evaluatedRules = ["decision-forbids", "declared-reference", "external-source-drift", ...wt.dirty ? [] : ["decision-forbids-import"]].sort(compareUtf8);
|
|
11616
|
+
const limitations2 = [
|
|
11617
|
+
...evaluated.limitations,
|
|
11618
|
+
...wt.truncated ? ["working-tree-list-truncated"] : [],
|
|
11619
|
+
...wt.dirty ? ["import-relations-not-evaluated-dirty-working-tree"] : []
|
|
11620
|
+
].sort(compareUtf8);
|
|
11091
11621
|
const body = {
|
|
11092
11622
|
format: "duo.adoption-baseline/2",
|
|
11093
11623
|
project: { name: truth.config.name, rootCommits: repo.value.rootCommitOids },
|
|
@@ -11118,7 +11648,8 @@ async function captureAdoptionBaseline(root, options) {
|
|
|
11118
11648
|
findingsAt: "HEAD",
|
|
11119
11649
|
findings: evaluated.findings,
|
|
11120
11650
|
limitations: limitations2,
|
|
11121
|
-
analysis
|
|
11651
|
+
analysis,
|
|
11652
|
+
evaluatedRules
|
|
11122
11653
|
};
|
|
11123
11654
|
const id3 = historyRecordId("adoption", body);
|
|
11124
11655
|
const existing = loadAdoptionBaseline(root);
|
|
@@ -11290,180 +11821,6 @@ function oneGroup(groups, nodes) {
|
|
|
11290
11821
|
return g !== void 0 && nodes.length > 1 && nodes.every((n) => groups.groups.get(n.id) === g);
|
|
11291
11822
|
}
|
|
11292
11823
|
|
|
11293
|
-
// packages/director/dist/context/policy.js
|
|
11294
|
-
var CONTEXT_POLICY_VERSION = "2";
|
|
11295
|
-
var EDGE_WEIGHTS = {
|
|
11296
|
-
GOVERNS: 1,
|
|
11297
|
-
IMPLEMENTS: 0.9,
|
|
11298
|
-
TRACKED_BY: 0.9,
|
|
11299
|
-
VALIDATED_BY: 0.8,
|
|
11300
|
-
REQUIRES: 0.7,
|
|
11301
|
-
CALLS: 0.7,
|
|
11302
|
-
CONTAINS: 0.6,
|
|
11303
|
-
IMPORTS: 0.5,
|
|
11304
|
-
CHANGED_WITH: 0.3
|
|
11305
|
-
};
|
|
11306
|
-
var SEED_STRENGTH = { id: 1, path: 1, symbol: 1, "symbol-name": 0.9, keyword: 0.6, diff: 1 };
|
|
11307
|
-
var CROSS_LANGUAGE_FACTOR = 0.5;
|
|
11308
|
-
var DEFAULT_LIMITS = {
|
|
11309
|
-
/** Upper bound; project.yaml context.max_depth (1–3, default 2) replaces it. */
|
|
11310
|
-
maxDepth: 2,
|
|
11311
|
-
/** Finalized candidates (05 nodeLimit 200). */
|
|
11312
|
-
nodeLimit: 200,
|
|
11313
|
-
/** Edges read per expanded node. */
|
|
11314
|
-
edgeLimit: 2e3
|
|
11315
|
-
};
|
|
11316
|
-
var MIN_BUDGET = 1e3;
|
|
11317
|
-
var MAX_BUDGET = 1e6;
|
|
11318
|
-
var TASK_TOKEN_LIMIT = 200;
|
|
11319
|
-
var KEYWORD = {
|
|
11320
|
-
/** Keyword seeds kept (05: top 8). */
|
|
11321
|
-
top: 8,
|
|
11322
|
-
/** Keyword matches below this fraction of the best one are not seeds. */
|
|
11323
|
-
minFraction: 0.3,
|
|
11324
|
-
/** BM25 parameters. */
|
|
11325
|
-
k1: 1.2,
|
|
11326
|
-
b: 0.75
|
|
11327
|
-
};
|
|
11328
|
-
var SYMBOL_NAME_MAX_MATCHES = 5;
|
|
11329
|
-
var TIER_ORDER = [
|
|
11330
|
-
"requirement",
|
|
11331
|
-
"decision",
|
|
11332
|
-
"pending",
|
|
11333
|
-
"code-direct",
|
|
11334
|
-
"test",
|
|
11335
|
-
"issue",
|
|
11336
|
-
"code-structural",
|
|
11337
|
-
"code-historical"
|
|
11338
|
-
];
|
|
11339
|
-
var TIER_SHARE = {
|
|
11340
|
-
requirement: 0.25,
|
|
11341
|
-
decision: 0.15,
|
|
11342
|
-
pending: 0.05,
|
|
11343
|
-
"code-direct": 0.3,
|
|
11344
|
-
test: 0.12,
|
|
11345
|
-
issue: 0.05,
|
|
11346
|
-
"code-structural": 0.06,
|
|
11347
|
-
"code-historical": 0.02
|
|
11348
|
-
};
|
|
11349
|
-
var L3_PER_FILE_FIRST_PASS = 2;
|
|
11350
|
-
var LEADING_CONTEXT_LINES = 12;
|
|
11351
|
-
var GENERIC_FILE_WINDOW = { lines: 40, chars: 2e3 };
|
|
11352
|
-
|
|
11353
|
-
// packages/director/dist/context/expand.js
|
|
11354
|
-
var HUBS = /* @__PURE__ */ new Set(["project", "milestone", "decision", "issue"]);
|
|
11355
|
-
function displayRef(id3) {
|
|
11356
|
-
const ref = parseNodeId(id3);
|
|
11357
|
-
if (ref === void 0)
|
|
11358
|
-
return id3;
|
|
11359
|
-
switch (ref.type) {
|
|
11360
|
-
case "project":
|
|
11361
|
-
return "project";
|
|
11362
|
-
case "file":
|
|
11363
|
-
return ref.path;
|
|
11364
|
-
case "symbol":
|
|
11365
|
-
return `${ref.path}#${ref.symbol}`;
|
|
11366
|
-
case "test":
|
|
11367
|
-
return `${ref.path}#${ref.name}`;
|
|
11368
|
-
default:
|
|
11369
|
-
return ref.id;
|
|
11370
|
-
}
|
|
11371
|
-
}
|
|
11372
|
-
function better(a, b) {
|
|
11373
|
-
return b.score - a.score || a.depth - b.depth || compareUtf8(a.node.id, b.node.id);
|
|
11374
|
-
}
|
|
11375
|
-
var Heap = class {
|
|
11376
|
-
items = [];
|
|
11377
|
-
get size() {
|
|
11378
|
-
return this.items.length;
|
|
11379
|
-
}
|
|
11380
|
-
push(c) {
|
|
11381
|
-
const a = this.items;
|
|
11382
|
-
a.push(c);
|
|
11383
|
-
let i = a.length - 1;
|
|
11384
|
-
while (i > 0) {
|
|
11385
|
-
const p = i - 1 >> 1;
|
|
11386
|
-
if (better(a[i], a[p]) >= 0)
|
|
11387
|
-
break;
|
|
11388
|
-
[a[i], a[p]] = [a[p], a[i]];
|
|
11389
|
-
i = p;
|
|
11390
|
-
}
|
|
11391
|
-
}
|
|
11392
|
-
pop() {
|
|
11393
|
-
const a = this.items;
|
|
11394
|
-
const top = a[0];
|
|
11395
|
-
const last = a.pop();
|
|
11396
|
-
if (a.length > 0 && last !== void 0) {
|
|
11397
|
-
a[0] = last;
|
|
11398
|
-
let i = 0;
|
|
11399
|
-
for (; ; ) {
|
|
11400
|
-
const l = 2 * i + 1, r = l + 1;
|
|
11401
|
-
let m = i;
|
|
11402
|
-
if (l < a.length && better(a[l], a[m]) < 0)
|
|
11403
|
-
m = l;
|
|
11404
|
-
if (r < a.length && better(a[r], a[m]) < 0)
|
|
11405
|
-
m = r;
|
|
11406
|
-
if (m === i)
|
|
11407
|
-
break;
|
|
11408
|
-
[a[i], a[m]] = [a[m], a[i]];
|
|
11409
|
-
i = m;
|
|
11410
|
-
}
|
|
11411
|
-
}
|
|
11412
|
-
return top;
|
|
11413
|
-
}
|
|
11414
|
-
};
|
|
11415
|
-
function step(edge) {
|
|
11416
|
-
const provenance = typeof edge.metadata.provenance === "string" ? edge.metadata.provenance : "unknown";
|
|
11417
|
-
return { from: displayRef(edge.from), type: edge.type, to: displayRef(edge.to), provenance };
|
|
11418
|
-
}
|
|
11419
|
-
function expandCandidates(store, seeds, options) {
|
|
11420
|
-
const heap = new Heap();
|
|
11421
|
-
const best = /* @__PURE__ */ new Map();
|
|
11422
|
-
const seedIds = new Set(seeds.map((s) => s.id));
|
|
11423
|
-
for (const s of seeds) {
|
|
11424
|
-
const ref = parseNodeId(s.id);
|
|
11425
|
-
const node = ref === void 0 ? void 0 : store.getNode(ref);
|
|
11426
|
-
if (node === void 0)
|
|
11427
|
-
continue;
|
|
11428
|
-
heap.push({ node, score: s.strength, depth: 0, seed: s.id, steps: [] });
|
|
11429
|
-
best.set(s.id, Math.max(best.get(s.id) ?? 0, s.strength));
|
|
11430
|
-
}
|
|
11431
|
-
const done = /* @__PURE__ */ new Map();
|
|
11432
|
-
let truncated = false;
|
|
11433
|
-
while (heap.size > 0) {
|
|
11434
|
-
const c = heap.pop();
|
|
11435
|
-
if (done.has(c.node.id))
|
|
11436
|
-
continue;
|
|
11437
|
-
if (done.size >= options.nodeLimit) {
|
|
11438
|
-
truncated = true;
|
|
11439
|
-
break;
|
|
11440
|
-
}
|
|
11441
|
-
done.set(c.node.id, c);
|
|
11442
|
-
if (c.depth >= options.maxDepth || HUBS.has(c.node.type) && !(c.depth === 0 && seedIds.has(c.node.id)))
|
|
11443
|
-
continue;
|
|
11444
|
-
const adjacent = store.adjacentEdges([c.node.ref], { direction: "both", limit: options.edgeLimit });
|
|
11445
|
-
if (adjacent.truncated)
|
|
11446
|
-
truncated = true;
|
|
11447
|
-
for (const edge of adjacent.edges) {
|
|
11448
|
-
if (edge.type === "SUPERSEDES")
|
|
11449
|
-
continue;
|
|
11450
|
-
const other = edge.from === c.node.id ? edge.to : edge.from;
|
|
11451
|
-
if (done.has(other) || other.startsWith("project:"))
|
|
11452
|
-
continue;
|
|
11453
|
-
const score = c.score * EDGE_WEIGHTS[edge.type];
|
|
11454
|
-
if (score <= (best.get(other) ?? 0))
|
|
11455
|
-
continue;
|
|
11456
|
-
const ref = parseNodeId(other);
|
|
11457
|
-
const node = ref === void 0 ? void 0 : store.getNode(ref);
|
|
11458
|
-
if (node === void 0)
|
|
11459
|
-
continue;
|
|
11460
|
-
best.set(other, score);
|
|
11461
|
-
heap.push({ node, score, depth: c.depth + 1, seed: c.seed, steps: [...c.steps, step(edge)] });
|
|
11462
|
-
}
|
|
11463
|
-
}
|
|
11464
|
-
return { candidates: [...done.values()].sort(better), truncated };
|
|
11465
|
-
}
|
|
11466
|
-
|
|
11467
11824
|
// packages/director/dist/context/types.js
|
|
11468
11825
|
var SEED_PROVENANCE = {
|
|
11469
11826
|
id: "explicit",
|
|
@@ -11633,6 +11990,7 @@ function planContext(input) {
|
|
|
11633
11990
|
const mil = new Map(truth.milestones.map((m) => [m.id, m]));
|
|
11634
11991
|
const seedIds = new Set(input.seeds.seeds.map((s) => s.id));
|
|
11635
11992
|
const exactSeeds = new Set(input.seeds.seeds.filter((s) => s.match !== "keyword").map((s) => s.id));
|
|
11993
|
+
const idSeeds = new Set(input.seeds.seeds.filter((s) => s.match === "id").map((s) => s.id));
|
|
11636
11994
|
const slice = (loc2) => {
|
|
11637
11995
|
const r = reader.slice(loc2);
|
|
11638
11996
|
diagnostics.push(...r.diagnostics);
|
|
@@ -11665,6 +12023,7 @@ function planContext(input) {
|
|
|
11665
12023
|
score: c.score,
|
|
11666
12024
|
depth: c.depth,
|
|
11667
12025
|
mandatory: exactSeeds.has(c.node.id),
|
|
12026
|
+
...c.depth === 0 && idSeeds.has(c.node.id) ? { primary: true } : {},
|
|
11668
12027
|
levels: levels([["L1", head], ["L2", body === "" ? void 0 : `${head}
|
|
11669
12028
|
${body}`], ["L3", exact === void 0 ? void 0 : `${head}
|
|
11670
12029
|
${fence(exact, "md")}`]]),
|
|
@@ -11773,7 +12132,7 @@ ${fence(exact, "yaml")}`]]),
|
|
|
11773
12132
|
case "issue": {
|
|
11774
12133
|
const i = iss.get(ref.id);
|
|
11775
12134
|
if (i !== void 0)
|
|
11776
|
-
put(issueDraft(i, node, c, slice, via(c)));
|
|
12135
|
+
put(issueDraft(i, node, c, slice, via(c), c.depth === 0 && idSeeds.has(node.id)));
|
|
11777
12136
|
break;
|
|
11778
12137
|
}
|
|
11779
12138
|
case "milestone": {
|
|
@@ -11929,6 +12288,7 @@ ${fence(w.text, langOf(ref.path))}`;
|
|
|
11929
12288
|
tier: d.tier,
|
|
11930
12289
|
rank: i + 1,
|
|
11931
12290
|
mandatory: d.mandatory,
|
|
12291
|
+
...d.primary === true ? { primary: true } : {},
|
|
11932
12292
|
levels: d.levels,
|
|
11933
12293
|
via: d.via,
|
|
11934
12294
|
...d.source === void 0 ? {} : { source: d.source },
|
|
@@ -11965,7 +12325,7 @@ function proposalRefs(p) {
|
|
|
11965
12325
|
out.add(e.id);
|
|
11966
12326
|
return [...out].sort(compareUtf8);
|
|
11967
12327
|
}
|
|
11968
|
-
function issueDraft(i, node, c, slice, v) {
|
|
12328
|
+
function issueDraft(i, node, c, slice, v, primary) {
|
|
11969
12329
|
const head = `${i.id} ${i.title} (${[i.status, i.milestone].filter((x) => x !== null).join(", ")})`;
|
|
11970
12330
|
const ac = i.acceptance.map((a) => `- ${a.id} ${a.text}`);
|
|
11971
12331
|
const commits = Array.isArray(node.payload.commits) ? node.payload.commits.slice(0, 3).map((x) => String(x).slice(0, 12)) : [];
|
|
@@ -11978,6 +12338,7 @@ function issueDraft(i, node, c, slice, v) {
|
|
|
11978
12338
|
score: c.score,
|
|
11979
12339
|
depth: c.depth,
|
|
11980
12340
|
mandatory: c.depth === 0,
|
|
12341
|
+
...primary ? { primary: true } : {},
|
|
11981
12342
|
levels: levels([
|
|
11982
12343
|
["L1", head],
|
|
11983
12344
|
["L2", ac.length === 0 ? void 0 : [head, ...ac].join("\n")],
|
|
@@ -12237,8 +12598,14 @@ var STATIC_LIMITATIONS = [
|
|
|
12237
12598
|
{ code: "files-by-path", message: "Files are listed by path; whole files are never included. Symbols and tests show their own source range; a file without a structural analyzer shows at most its first lines." },
|
|
12238
12599
|
{ code: "no-diff", message: "The working-tree diff is not part of this packet." }
|
|
12239
12600
|
];
|
|
12240
|
-
function limitations(plan, taskTruncated, omitted, summarized) {
|
|
12601
|
+
function limitations(plan, taskTruncated, omitted, summarized, budget, truncatedSeeds) {
|
|
12241
12602
|
const out = [...STATIC_LIMITATIONS];
|
|
12603
|
+
for (const t2 of truncatedSeeds) {
|
|
12604
|
+
out.push({
|
|
12605
|
+
code: "explicit-seed-truncated",
|
|
12606
|
+
message: t2.shown === void 0 ? `Explicit seed ${t2.ref} could not fit within the ${budget}-token budget (its full specification is ${t2.fullTokens} tokens); it is not shown.` : `Explicit seed ${t2.ref} could not fit its full specification within the ${budget}-token budget (${t2.fullTokens} tokens); the highest fitting representation (${t2.shown}) is shown.`
|
|
12607
|
+
});
|
|
12608
|
+
}
|
|
12242
12609
|
if (omitted > 0)
|
|
12243
12610
|
out.push({ code: "omitted", message: `omitted: ${omitted} lower-ranked candidates did not fit the budget (ids in omittedCandidates).` });
|
|
12244
12611
|
if (summarized > 0)
|
|
@@ -12261,6 +12628,11 @@ function packContext(input) {
|
|
|
12261
12628
|
const block = (text4) => meter.count(renderItemBlock(text4) + "\n");
|
|
12262
12629
|
const evidence = (i) => meter.count(renderEvidenceLine(i.ref, i.via) + "\n");
|
|
12263
12630
|
const itemTokens = (i, level) => level < 0 ? 0 : block(i.levels[level]?.text ?? "") + evidence(i);
|
|
12631
|
+
const truncatedSeed = (s, level) => ({
|
|
12632
|
+
ref: s.value.ref,
|
|
12633
|
+
fullTokens: itemTokens(s.value, s.value.levels.length - 1),
|
|
12634
|
+
shown: level < 0 ? void 0 : s.value.levels[level]?.level ?? "L1"
|
|
12635
|
+
});
|
|
12264
12636
|
const pendingTokens = (p, level) => block(p.levels[level]?.text ?? "");
|
|
12265
12637
|
const build = () => {
|
|
12266
12638
|
const chosen = items.filter((s) => s.level >= 0);
|
|
@@ -12298,6 +12670,7 @@ function packContext(input) {
|
|
|
12298
12670
|
const omitted = items.filter((s) => s.level < 0);
|
|
12299
12671
|
const summarized = chosen.filter((s) => s.level < s.value.levels.length - 1).length;
|
|
12300
12672
|
const taskTruncated = request.taskTruncated;
|
|
12673
|
+
const truncatedSeeds = items.filter((s) => s.value.primary === true && s.level < s.value.levels.length - 1).map((s) => truncatedSeed(s, s.level));
|
|
12301
12674
|
const packet2 = {
|
|
12302
12675
|
format: "duo.context-packet/1",
|
|
12303
12676
|
request,
|
|
@@ -12309,7 +12682,7 @@ function packContext(input) {
|
|
|
12309
12682
|
issues: of((i) => i.kind === "issue" || i.kind === "milestone"),
|
|
12310
12683
|
pendingDecisions: pend,
|
|
12311
12684
|
evidence: chosen.map((s) => ({ id: s.value.id, ref: s.value.ref, via: s.value.via })),
|
|
12312
|
-
limitations: limitations(plan, taskTruncated, omitted.length, summarized),
|
|
12685
|
+
limitations: limitations(plan, taskTruncated, omitted.length, summarized, request.budget, truncatedSeeds),
|
|
12313
12686
|
signals: plan.signals,
|
|
12314
12687
|
omittedCandidates: omitted.map((s) => ({ id: s.value.id, ref: s.value.ref, rank: s.value.rank, tier: s.value.tier, reason: "budget" })),
|
|
12315
12688
|
truncated: omitted.length > 0 || plan.traversalTruncated || taskTruncated,
|
|
@@ -12320,7 +12693,7 @@ function packContext(input) {
|
|
|
12320
12693
|
return { packet: packet2, markdown: renderContextMarkdown(packet2) };
|
|
12321
12694
|
};
|
|
12322
12695
|
const frame = build();
|
|
12323
|
-
const frameLimits = limitations(plan, request.taskTruncated, Math.max(1, items.length), Math.max(1, items.length));
|
|
12696
|
+
const frameLimits = limitations(plan, request.taskTruncated, Math.max(1, items.length), Math.max(1, items.length), request.budget, items.filter((s) => s.value.primary === true).map((s) => truncatedSeed(s, 0)));
|
|
12324
12697
|
const reserved = meter.count(renderContextMarkdown({ ...frame.packet, limitations: frameLimits }));
|
|
12325
12698
|
if (reserved > request.budget) {
|
|
12326
12699
|
return failure([createDiagnostic("CONTEXT_REQUEST_INVALID", `budget ${request.budget} is smaller than the packet frame (${reserved} tokens: task, headings, pending decisions, limitations)`)]);
|
|
@@ -12355,6 +12728,12 @@ function packContext(input) {
|
|
|
12355
12728
|
for (const s of items)
|
|
12356
12729
|
if (s.value.mandatory)
|
|
12357
12730
|
raise(s, s.value.tier, void 0);
|
|
12731
|
+
for (const s of items) {
|
|
12732
|
+
if (s.value.primary !== true)
|
|
12733
|
+
continue;
|
|
12734
|
+
while (raise(s, s.value.tier, void 0)) {
|
|
12735
|
+
}
|
|
12736
|
+
}
|
|
12358
12737
|
const promotion = available - spent;
|
|
12359
12738
|
const byTier = (tier) => tier === "pending" ? pending : items.filter((s) => s.value.tier === tier);
|
|
12360
12739
|
for (const capped of [true, false]) {
|
|
@@ -14466,88 +14845,6 @@ async function externalSourceDrift(ctx) {
|
|
|
14466
14845
|
return { claims, limitations: limitations2 };
|
|
14467
14846
|
}
|
|
14468
14847
|
|
|
14469
|
-
// packages/director/dist/review/seeds.js
|
|
14470
|
-
var PROPOSALS2 = `${STATE_DIR_NAME}/decisions/proposals/`;
|
|
14471
|
-
function hunkRange(h) {
|
|
14472
|
-
return h.newLines > 0 ? [h.newStart, h.newStart + h.newLines - 1] : [Math.max(1, h.newStart), Math.max(1, h.newStart)];
|
|
14473
|
-
}
|
|
14474
|
-
function overlaps(loc2, range) {
|
|
14475
|
-
if (loc2?.startLine === void 0)
|
|
14476
|
-
return false;
|
|
14477
|
-
const end2 = loc2.endLine ?? loc2.startLine;
|
|
14478
|
-
return range[0] <= end2 && range[1] >= loc2.startLine;
|
|
14479
|
-
}
|
|
14480
|
-
function owned(graph, path35) {
|
|
14481
|
-
const out = [];
|
|
14482
|
-
let afterId;
|
|
14483
|
-
for (; ; ) {
|
|
14484
|
-
const page = graph.listNodes({ ownerFile: path35, limit: 1e3, ...afterId === void 0 ? {} : { afterId } });
|
|
14485
|
-
out.push(...page);
|
|
14486
|
-
if (page.length < 1e3)
|
|
14487
|
-
return out;
|
|
14488
|
-
afterId = page[page.length - 1]?.id;
|
|
14489
|
-
}
|
|
14490
|
-
}
|
|
14491
|
-
var width = (loc2) => (loc2?.endLine ?? 0) - (loc2?.startLine ?? 0);
|
|
14492
|
-
function truthDefinitions(truth) {
|
|
14493
|
-
return [
|
|
14494
|
-
...truth.requirements.map((d) => ({ ref: definitionRef("requirement", d.id), location: d.location })),
|
|
14495
|
-
...truth.decisions.map((d) => ({ ref: definitionRef("decision", d.id), location: d.location })),
|
|
14496
|
-
...truth.constraints.map((d) => ({ ref: definitionRef("decision", d.id), location: d.location })),
|
|
14497
|
-
...truth.issues.map((d) => ({ ref: definitionRef("issue", d.id), location: d.location })),
|
|
14498
|
-
...truth.milestones.map((d) => ({ ref: definitionRef("milestone", d.id), location: d.location }))
|
|
14499
|
-
];
|
|
14500
|
-
}
|
|
14501
|
-
function diffSeeds(files, graph, truth) {
|
|
14502
|
-
const seeds = /* @__PURE__ */ new Map();
|
|
14503
|
-
const put = (entity, reason, path35, evidenceId2) => {
|
|
14504
|
-
if (graph.getNode(entity) === void 0)
|
|
14505
|
-
return;
|
|
14506
|
-
const id3 = nodeId(entity);
|
|
14507
|
-
const s = seeds.get(id3) ?? { entity, reason, path: path35, evidence: /* @__PURE__ */ new Set() };
|
|
14508
|
-
s.evidence.add(evidenceId2);
|
|
14509
|
-
seeds.set(id3, s);
|
|
14510
|
-
};
|
|
14511
|
-
const defs = truthDefinitions(truth);
|
|
14512
|
-
for (const f of files) {
|
|
14513
|
-
if (f.kind === "deleted")
|
|
14514
|
-
continue;
|
|
14515
|
-
if (f.path.startsWith(`${STATE_DIR_NAME}/`)) {
|
|
14516
|
-
if (f.path.startsWith(PROPOSALS2))
|
|
14517
|
-
continue;
|
|
14518
|
-
for (const h of f.hunks) {
|
|
14519
|
-
for (const d of defs)
|
|
14520
|
-
if (d.location.path === f.path && overlaps(d.location, hunkRange(h)))
|
|
14521
|
-
put(d.ref, "truth-changed", f.path, h.evidenceId);
|
|
14522
|
-
}
|
|
14523
|
-
continue;
|
|
14524
|
-
}
|
|
14525
|
-
const nodes = owned(graph, f.path).filter((n) => n.type === "symbol" || n.type === "test");
|
|
14526
|
-
const ranges = new Map(nodes.map((n) => [n.id, nodeLocations(n)]));
|
|
14527
|
-
let touched = false;
|
|
14528
|
-
for (const h of f.hunks) {
|
|
14529
|
-
const range = hunkRange(h);
|
|
14530
|
-
const hits = nodes.map((n) => ({ n, at: (ranges.get(n.id) ?? []).filter((l) => overlaps(l, range)) })).filter((x) => x.at.length > 0);
|
|
14531
|
-
const inner = hits.filter((x) => !hits.some((o) => o !== x && o.at.some((ol) => x.at.some((xl) => width(ol) < width(xl) && overlaps(xl, [ol.startLine ?? 0, ol.endLine ?? 0])))));
|
|
14532
|
-
for (const { n } of inner) {
|
|
14533
|
-
put(n.ref, "hunk-overlap", f.path, h.evidenceId);
|
|
14534
|
-
touched = true;
|
|
14535
|
-
}
|
|
14536
|
-
}
|
|
14537
|
-
if (!touched)
|
|
14538
|
-
for (const e of f.evidenceIds)
|
|
14539
|
-
put(fileRef(f.path), "file-changed", f.path, e);
|
|
14540
|
-
}
|
|
14541
|
-
return [...seeds.entries()].sort(([a], [b]) => compareUtf8(a, b)).map(([id3, s]) => ({
|
|
14542
|
-
id: id3,
|
|
14543
|
-
ref: displayRef(id3),
|
|
14544
|
-
entity: s.entity,
|
|
14545
|
-
reason: s.reason,
|
|
14546
|
-
path: s.path,
|
|
14547
|
-
evidenceIds: [...s.evidence].sort(compareUtf8)
|
|
14548
|
-
}));
|
|
14549
|
-
}
|
|
14550
|
-
|
|
14551
14848
|
// packages/director/dist/review/semantic.js
|
|
14552
14849
|
var ALIGNMENTS = ["ALIGNED", "PARTIAL", "CONFLICT", "UNKNOWN"];
|
|
14553
14850
|
function semanticSchema(claimIds) {
|
|
@@ -14853,17 +15150,21 @@ async function reviewChanges(root, request, options) {
|
|
|
14853
15150
|
stateDiagnostics: readIndexState(root).state?.diagnostics ?? [],
|
|
14854
15151
|
...request.testResults === void 0 ? {} : { testResults: request.testResults },
|
|
14855
15152
|
...adoption.baseline === void 0 ? {} : { baselineKeys: new Set(adoption.baseline.findings.map((f) => f.key)) },
|
|
14856
|
-
...adoption.baseline === void 0 ? {} : { baselineStructuralLanguages: new Set(adoption.baseline.analysis?.structuralLanguages ?? PRE_T18_STRUCTURAL_LANGUAGES) }
|
|
15153
|
+
...adoption.baseline === void 0 ? {} : { baselineStructuralLanguages: new Set(adoption.baseline.analysis?.structuralLanguages ?? PRE_T18_STRUCTURAL_LANGUAGES) },
|
|
15154
|
+
...adoption.baseline?.evaluatedRules === void 0 ? {} : { baselineEvaluatedRules: new Set(adoption.baseline.evaluatedRules) },
|
|
15155
|
+
...adoption.baseline === void 0 ? {} : { baselineRecordedAt: adoption.baseline.recorded.at }
|
|
14857
15156
|
};
|
|
14858
15157
|
const integrity = await decisionIntegrity(ctx);
|
|
14859
|
-
const
|
|
15158
|
+
const forbids2 = await decisionForbids(ctx);
|
|
15159
|
+
const imports = await decisionForbidsImport(ctx);
|
|
14860
15160
|
const drift = await externalSourceDrift(ctx);
|
|
14861
15161
|
const additions = unlinkedAdditions(ctx);
|
|
14862
|
-
const conflicted = new Set(
|
|
15162
|
+
const conflicted = new Set([...forbids2, ...imports.claims].map((c) => c.subject.id));
|
|
14863
15163
|
const claims = [
|
|
14864
15164
|
...integrity,
|
|
14865
15165
|
...supersedeIntegrity(ctx),
|
|
14866
|
-
...
|
|
15166
|
+
...forbids2,
|
|
15167
|
+
...imports.claims,
|
|
14867
15168
|
...decisionGovernance(ctx, conflicted),
|
|
14868
15169
|
...declaredReferences(ctx),
|
|
14869
15170
|
...requirementImplementation(ctx),
|
|
@@ -14914,6 +15215,7 @@ async function reviewChanges(root, request, options) {
|
|
|
14914
15215
|
...limitationsOf({ identity: diff.value.identity, from: diff.value.from, to: diff.value.to, files }, reviewPacket, task !== "", taskContext?.status === "ready"),
|
|
14915
15216
|
...analysisLimitsOf(files, options.graph),
|
|
14916
15217
|
...drift.limitations,
|
|
15218
|
+
...imports.limitations,
|
|
14917
15219
|
...adoption.status === "incompatible" ? [{ code: "adoption-baseline-unusable", message: "The Adoption Baseline cannot be used (" + (adoption.reason ?? "unreadable") + "); violations are not told apart from pre-existing ones." }] : []
|
|
14918
15220
|
],
|
|
14919
15221
|
semanticAssist: semantic.assist,
|
|
@@ -14934,6 +15236,25 @@ async function reviewChanges(root, request, options) {
|
|
|
14934
15236
|
return success({ result, performance: perf() }, result.diagnostics);
|
|
14935
15237
|
}
|
|
14936
15238
|
|
|
15239
|
+
// packages/director/dist/review/provenance-label.js
|
|
15240
|
+
var PROVENANCE_LABELS = {
|
|
15241
|
+
en: {
|
|
15242
|
+
introduced: "not-in-adoption-baseline",
|
|
15243
|
+
"pre-existing": "in-adoption-baseline",
|
|
15244
|
+
"pre-existing-touched": "in-adoption-baseline, touched",
|
|
15245
|
+
"unverified-at-adoption": "baseline-unverified"
|
|
15246
|
+
},
|
|
15247
|
+
ko: {
|
|
15248
|
+
introduced: "\uCC44\uD0DD \uAE30\uC900\uC120\uC5D0 \uC5C6\uC74C",
|
|
15249
|
+
"pre-existing": "\uCC44\uD0DD \uAE30\uC900\uC120\uC5D0 \uC874\uC7AC",
|
|
15250
|
+
"pre-existing-touched": "\uCC44\uD0DD \uAE30\uC900\uC120\uC5D0 \uC874\uC7AC, \uC774\uBC88 \uBCC0\uACBD\uC5D0\uC11C \uC811\uCD09",
|
|
15251
|
+
"unverified-at-adoption": "\uCC44\uD0DD \uC2DC\uC810 \uAC80\uC99D \uBD88\uAC00"
|
|
15252
|
+
}
|
|
15253
|
+
};
|
|
15254
|
+
function provenanceLabel(value, locale = "en") {
|
|
15255
|
+
return PROVENANCE_LABELS[locale][value];
|
|
15256
|
+
}
|
|
15257
|
+
|
|
14937
15258
|
// packages/director/dist/runtime/metrics.js
|
|
14938
15259
|
import fs23 from "node:fs";
|
|
14939
15260
|
import fsp5 from "node:fs/promises";
|
|
@@ -15918,6 +16239,16 @@ var repoPattern = z4.string().min(1).max(4096).refine((p) => normalizeRepoPatter
|
|
|
15918
16239
|
var definitionId = z4.string().regex(DEFINITION_ID_PATTERN, "a definition ID such as AUTH-03 or D-004");
|
|
15919
16240
|
var endpoint = z4.string().min(1).max(256).refine((v) => !v.startsWith("-") && !/[\u0000-\u001f\u007f\s]/u.test(v), "HEAD, INDEX, WORKTREE or a commit / branch name");
|
|
15920
16241
|
var text2 = (max) => z4.string().trim().min(1).max(max);
|
|
16242
|
+
var decisionId = z4.string().regex(/^D-\d+$/u, "a Decision ID such as D-004");
|
|
16243
|
+
var forbidPattern = z4.string().min(1).max(4096).refine((p) => compileRepoPattern(p) !== void 0, "a repository-relative pattern review can match (no absolute or .. pattern)");
|
|
16244
|
+
var FORBIDS_SYMBOL_MESSAGE = "forbids.symbols matches symbol qualified names only. Do not use a DUO path#symbol reference. Use a qualified-name wildcard such as 'LegacyDb' or '*LegacyDb*'.";
|
|
16245
|
+
var forbidSymbol = text2(500).refine((s) => !/[/\\#]/u.test(s), FORBIDS_SYMBOL_MESSAGE);
|
|
16246
|
+
var forbids = z4.strictObject({
|
|
16247
|
+
paths: z4.array(forbidPattern).max(100).optional().describe("Repository path patterns; a change to a matching file conflicts."),
|
|
16248
|
+
symbols: z4.array(forbidSymbol).max(100).optional().describe("Wildcard patterns over the qualified names of changed symbols ('*' any text, '?' one character), e.g. LegacyDb, LegacyDb.run, *LegacyDb*. Not a DUO path#symbol reference. Catches changes to matching symbols, not new calls or references from other symbols."),
|
|
16249
|
+
dependencies: z4.array(text2(500)).max(100).optional().describe("Package names; adding one to a package.json conflicts."),
|
|
16250
|
+
imported_paths: z4.array(forbidPattern).max(100).optional().describe("Repository path patterns (H-72). Matches changed import/module-reference statements (import, export-from, require, dynamic import, type-only included; Python imports and C++ quoted includes where DUO resolves them) that resolve exactly to a matching repository file. It does not prove the import relation is newly introduced; unresolved, ambiguous and Java/C# imports are not checked. External packages belong in dependencies.")
|
|
16251
|
+
}).refine((f) => (f.paths?.length ?? 0) + (f.symbols?.length ?? 0) + (f.dependencies?.length ?? 0) + (f.imported_paths?.length ?? 0) > 0, "forbids needs at least one path, symbol, dependency or imported path");
|
|
15921
16252
|
var INPUT = {
|
|
15922
16253
|
duo_get_status: z4.strictObject({}),
|
|
15923
16254
|
duo_get_context: z4.strictObject({ task: text2(2e4), budget: z4.number().int().min(MIN_BUDGET).max(MAX_BUDGET).optional(), profile: z4.enum(["default", "review"]).optional() }),
|
|
@@ -15940,6 +16271,10 @@ var INPUT = {
|
|
|
15940
16271
|
answer: text2(2e3),
|
|
15941
16272
|
rationale: text2(4e3).optional(),
|
|
15942
16273
|
governs: z4.strictObject({ requirements: z4.array(definitionId).max(100).optional(), paths: z4.array(repoPattern).max(100).optional(), symbols: z4.array(text2(500)).max(100).optional() }).optional(),
|
|
16274
|
+
// H-71: the proposal may carry what a Decision enforces and what it replaces; it gains no authority until a human confirms it.
|
|
16275
|
+
forbids: forbids.optional(),
|
|
16276
|
+
enforcement: z4.enum(["warn", "block"]).optional(),
|
|
16277
|
+
supersedes: decisionId.optional(),
|
|
15943
16278
|
agent: text2(100).optional()
|
|
15944
16279
|
})
|
|
15945
16280
|
};
|
|
@@ -16058,7 +16393,7 @@ ${advice.join("\n")}`);
|
|
|
16058
16393
|
const r = p;
|
|
16059
16394
|
if (r.status === "index-required")
|
|
16060
16395
|
return "INDEX_REQUIRED: the DUO index is not current. Run duoctl index, then review again.";
|
|
16061
|
-
const lines4 = r.claims.filter((c) => c.alignment !== "ALIGNED").map((c) => `- ${c.alignment} ${c.rule} ${c.subject.id}: ${c.reason}${c.provenance === void 0 ? "" : ` (${c.provenance})`}${c.blockEligible ? " [blocking]" : ""}`);
|
|
16396
|
+
const lines4 = r.claims.filter((c) => c.alignment !== "ALIGNED").map((c) => `- ${c.alignment} ${c.rule} ${c.subject.id}: ${c.reason}${c.provenance === void 0 ? "" : ` (${provenanceLabel(c.provenance)})`}${c.blockEligible ? " [blocking]" : ""}`);
|
|
16062
16397
|
const a = r.semanticAssist;
|
|
16063
16398
|
const semantic = a.status === "not-requested" ? [] : [
|
|
16064
16399
|
`Semantic assistance (supplemental, never blocks): ${a.status}${a.failure === void 0 ? "" : ` (${a.failure})`}`,
|
|
@@ -16111,14 +16446,17 @@ ${advice.join("\n")}`);
|
|
|
16111
16446
|
name: "duo_propose_decision",
|
|
16112
16447
|
title: "Propose a Decision",
|
|
16113
16448
|
readOnly: false,
|
|
16114
|
-
description:
|
|
16449
|
+
description: `Creates a Decision proposal only (decisions/proposals/P-*.yaml). Cannot confirm or reject a decision: a human does that in duoctl or the Web UI. A proposal is not confirmed Project Truth and does not change any review verdict. forbids and enforcement: "block" have no authority until a human confirms the proposal. supersedes names a confirmed Decision to replace; that Decision is not modified until a human confirms this proposal. governs describes what the Decision concerns (for tracing and context); it does not scope forbids. Forbids are repository-wide: they apply to matching changes anywhere in the repository. forbids.symbols are wildcard patterns over the qualified names of changed symbols ('*' any text, '?' one character), e.g. LegacyDb, LegacyDb.run, *LegacyDb*; a DUO path#symbol reference such as src/db/legacy-db.ts#LegacyDb (a display handle in context output) is not a pattern and is refused. A symbol pattern catches changes to matching symbols, not new calls or references from other code. forbids.imported_paths are repository path patterns: they match changed import/module-reference statements that resolve exactly to a matching repository file; this does not prove the import relation is newly introduced, and unresolved, ambiguous or Java/C# imports are not checked. The agent name is an audit label, not an authentication.`,
|
|
16115
16450
|
run: async (args, ctx) => {
|
|
16116
16451
|
const op = await proposeDecision(ctx.root, args.agent ?? ctx.agentName, {
|
|
16117
16452
|
title: args.title,
|
|
16118
16453
|
question: args.question,
|
|
16119
16454
|
answer: args.answer,
|
|
16120
16455
|
...args.rationale === void 0 ? {} : { rationale: args.rationale },
|
|
16121
|
-
...args.governs === void 0 ? {} : { governs: args.governs }
|
|
16456
|
+
...args.governs === void 0 ? {} : { governs: args.governs },
|
|
16457
|
+
...args.forbids === void 0 ? {} : { forbids: args.forbids },
|
|
16458
|
+
...args.enforcement === void 0 ? {} : { enforcement: args.enforcement },
|
|
16459
|
+
...args.supersedes === void 0 ? {} : { supersedes: args.supersedes }
|
|
16122
16460
|
});
|
|
16123
16461
|
return { op, metric: { status: op.kind === "ok" ? "proposed" : op.kind } };
|
|
16124
16462
|
},
|
|
@@ -17304,8 +17642,8 @@ function projectProposals(root) {
|
|
|
17304
17642
|
const service = createDecisionService({ root });
|
|
17305
17643
|
const previews = {};
|
|
17306
17644
|
for (const e of d.proposals.filter((x) => x.status === "pending")) {
|
|
17307
|
-
const
|
|
17308
|
-
previews[e.id] =
|
|
17645
|
+
const preview2 = await service.previewConfirm(e.id);
|
|
17646
|
+
previews[e.id] = preview2.value ?? { error: preview2.diagnostics.map((x) => x.code) };
|
|
17309
17647
|
}
|
|
17310
17648
|
return { kind: "ok", diagnostics: [], payload: { proposals: d.proposals, previews } };
|
|
17311
17649
|
});
|
|
@@ -17462,7 +17800,8 @@ var PROPOSAL_ID = /^P-[A-Za-z0-9-]{1,64}$/u;
|
|
|
17462
17800
|
var SCHEMAS = {
|
|
17463
17801
|
context: z5.strictObject({ task: text3(2e4), budget: z5.number().int().min(MIN_BUDGET).max(MAX_BUDGET).optional() }),
|
|
17464
17802
|
review: z5.strictObject({ task: text3(2e4).optional(), from: endpoint2.optional(), to: endpoint2.optional(), includeSemanticAssist: z5.boolean().optional() }),
|
|
17465
|
-
|
|
17803
|
+
// previewDigest is required (T34.3): a UI confirm is always bound to the preview the person reviewed.
|
|
17804
|
+
confirm: z5.strictObject({ confirmId: z5.string().max(80), previewDigest: z5.string().min(1).max(100) }),
|
|
17466
17805
|
reject: z5.strictObject({ reason: z5.string().max(2e3).optional() }),
|
|
17467
17806
|
graph: z5.strictObject({ node: text3(4096), kind: z5.enum(["trace", "impact"]).default("trace"), depth: z5.coerce.number().int().min(1).max(3).default(1) }),
|
|
17468
17807
|
search: z5.strictObject({ q: text3(200), limit: z5.coerce.number().int().min(1).max(50).default(20) }),
|
|
@@ -17519,7 +17858,7 @@ async function startDuoUiServer(options) {
|
|
|
17519
17858
|
const body2 = parse3(SCHEMAS.confirm, await readJson2(req));
|
|
17520
17859
|
if (body2.confirmId.trim() !== id3)
|
|
17521
17860
|
throw new HttpError(400, "UI_CONFIRM_ID_MISMATCH", "type the proposal ID to confirm it");
|
|
17522
|
-
const r2 = await service.confirm(await actor(), id3);
|
|
17861
|
+
const r2 = await service.confirm(await actor(), id3, { expectedDigest: body2.previewDigest });
|
|
17523
17862
|
await meter("decision", r2.value === void 0 ? "failed" : "confirmed", started);
|
|
17524
17863
|
return { name: "decision", body: r2.value === void 0 ? { status: "failed", diagnostics: r2.diagnostics.map((d) => ({ code: d.code, message: d.message })) } : { status: "confirmed", result: r2.value, warnings: r2.diagnostics.map((d) => ({ code: d.code, message: d.message })) } };
|
|
17525
17864
|
}
|
|
@@ -17802,6 +18141,22 @@ var EN = {
|
|
|
17802
18141
|
"decision.confirmed": "confirmed as {id} \xB7 {path}",
|
|
17803
18142
|
"decision.rejected": "rejected {id}",
|
|
17804
18143
|
"decision.none": "No pending decisions.",
|
|
18144
|
+
"decision.preview.create": "Confirm {id}: this proposal becomes a new confirmed Decision.",
|
|
18145
|
+
"decision.preview.in-place": "Confirm {id}: this proposed Decision becomes confirmed in place.",
|
|
18146
|
+
"decision.preview.lock": "Confirm {id}: this Decision is already confirmed; confirming adds its lock.",
|
|
18147
|
+
"decision.preview.reject": "Reject {id}: the proposal is kept as rejected; nothing becomes a Decision.",
|
|
18148
|
+
"decision.preview.becomes-superseded": "becomes superseded by this Decision",
|
|
18149
|
+
"decision.preview.not-found": "not found; confirm will fail",
|
|
18150
|
+
"decision.preview.stale": "yes, Project Truth changed since this proposal was made{changed}",
|
|
18151
|
+
"decision.preview.fresh": "no",
|
|
18152
|
+
"decision.preview.stale-na": "not applicable (staleness is tracked for proposals only)",
|
|
18153
|
+
"decision.preview.expected": "{id} (expected; the ID in the confirm result is authoritative)",
|
|
18154
|
+
"decision.preview.in-place-id": "{id} (confirmed in place)",
|
|
18155
|
+
"decision.preview.lock-id": "{id} (already confirmed; the lock is added)",
|
|
18156
|
+
"decision.preview.not-set": "(not set)",
|
|
18157
|
+
"decision.preview.none": "(none)",
|
|
18158
|
+
"decision.preview.forbids-scope": "repository-wide (governs does not narrow forbids)",
|
|
18159
|
+
"decision.index-hint": "Run duoctl index so review and context see it.",
|
|
17805
18160
|
"decision.truth-errors": "Not listed: {n} Truth error(s). These files could not be read; confirm and reject refuse to run until they are fixed:",
|
|
17806
18161
|
"stats.none": "No metrics yet.",
|
|
17807
18162
|
"not-initialized": "Not a DUO project (no .duo-project/project.yaml). Run duoctl init.",
|
|
@@ -17996,6 +18351,22 @@ var KO = {
|
|
|
17996
18351
|
"decision.confirmed": "{id}\uB85C \uD655\uC815 \xB7 {path}",
|
|
17997
18352
|
"decision.rejected": "{id} \uAC70\uC808",
|
|
17998
18353
|
"decision.none": "\uB300\uAE30 \uC911 decision\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
18354
|
+
"decision.preview.create": "{id} \uD655\uC815: \uC774 proposal\uC774 \uC0C8 \uD655\uC815 Decision\uC774 \uB429\uB2C8\uB2E4.",
|
|
18355
|
+
"decision.preview.in-place": "{id} \uD655\uC815: \uC774 proposed Decision\uC774 \uADF8 \uC790\uB9AC\uC5D0\uC11C \uD655\uC815\uB429\uB2C8\uB2E4.",
|
|
18356
|
+
"decision.preview.lock": "{id} \uD655\uC815: \uC774\uBBF8 \uD655\uC815\uB41C Decision\uC774\uBA70 lock\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4.",
|
|
18357
|
+
"decision.preview.reject": "{id} \uAC70\uC808: proposal\uC740 rejected\uB85C \uB0A8\uACE0 Decision\uC774 \uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
18358
|
+
"decision.preview.becomes-superseded": "\uC774 Decision\uC5D0 \uC758\uD574 superseded\uAC00 \uB429\uB2C8\uB2E4",
|
|
18359
|
+
"decision.preview.not-found": "\uC5C6\uC74C, \uD655\uC815\uC774 \uC2E4\uD328\uD569\uB2C8\uB2E4",
|
|
18360
|
+
"decision.preview.stale": "\uC608, proposal \uC774\uD6C4 Project Truth\uAC00 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4{changed}",
|
|
18361
|
+
"decision.preview.fresh": "\uC544\uB2C8\uC694",
|
|
18362
|
+
"decision.preview.stale-na": "\uD574\uB2F9 \uC5C6\uC74C(staleness\uB294 proposal\uC5D0\uB9CC \uC801\uC6A9)",
|
|
18363
|
+
"decision.preview.expected": "{id} (\uC608\uC0C1\uAC12, \uC2E4\uC81C ID\uB294 \uD655\uC815 \uACB0\uACFC\uB97C \uB530\uB985\uB2C8\uB2E4)",
|
|
18364
|
+
"decision.preview.in-place-id": "{id} (\uADF8 \uC790\uB9AC\uC5D0\uC11C \uD655\uC815)",
|
|
18365
|
+
"decision.preview.lock-id": "{id} (\uC774\uBBF8 \uD655\uC815\uB428, lock \uCD94\uAC00)",
|
|
18366
|
+
"decision.preview.not-set": "(\uC5C6\uC74C)",
|
|
18367
|
+
"decision.preview.none": "(\uBE44\uC5B4 \uC788\uC74C)",
|
|
18368
|
+
"decision.preview.forbids-scope": "\uC800\uC7A5\uC18C \uC804\uCCB4(governs\uB294 forbids\uC758 \uBC94\uC704\uB97C \uC881\uD788\uC9C0 \uC54A\uC74C)",
|
|
18369
|
+
"decision.index-hint": "review\uC640 context\uC5D0 \uBC18\uC601\uD558\uB824\uBA74 duoctl index\uB97C \uC2E4\uD589\uD558\uC138\uC694.",
|
|
17999
18370
|
"decision.truth-errors": "\uBAA9\uB85D\uC5D0 \uC5C6\uC74C: Truth \uC624\uB958 {n}\uAC1C. \uC544\uB798 \uD30C\uC77C\uC744 \uC77D\uC9C0 \uBABB\uD588\uC73C\uBA70, \uACE0\uCE60 \uB54C\uAE4C\uC9C0 \uD655\uC815\uACFC \uAC70\uC808\uC740 \uC2E4\uD589\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4:",
|
|
18000
18371
|
"stats.none": "\uC9C0\uD45C\uAC00 \uC544\uC9C1 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
18001
18372
|
"not-initialized": "DUO project\uAC00 \uC544\uB2D9\uB2C8\uB2E4(.duo-project/project.yaml \uC5C6\uC74C). duoctl init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
@@ -18258,6 +18629,59 @@ function ambiguityHint(L, a) {
|
|
|
18258
18629
|
}
|
|
18259
18630
|
|
|
18260
18631
|
// apps/cli/dist/commands/decision.js
|
|
18632
|
+
var DECISION_ID2 = /^D-\d+$/u;
|
|
18633
|
+
var LABEL_WIDTH = 13;
|
|
18634
|
+
function show(L, value, nested = false) {
|
|
18635
|
+
if (value === void 0 || value === null)
|
|
18636
|
+
return t(L, "decision.preview.not-set");
|
|
18637
|
+
if (Array.isArray(value))
|
|
18638
|
+
return value.length === 0 ? t(L, "decision.preview.none") : value.map((v) => show(L, v, true)).join(", ");
|
|
18639
|
+
if (typeof value === "object") {
|
|
18640
|
+
const parts = Object.entries(value).filter(([, v]) => v !== void 0 && v !== null && !(Array.isArray(v) && v.length === 0)).map(([k, v]) => `${k}: ${show(L, v, true)}`);
|
|
18641
|
+
if (parts.length === 0)
|
|
18642
|
+
return t(L, "decision.preview.none");
|
|
18643
|
+
return nested ? `{${parts.join("; ")}}` : parts.join("; ");
|
|
18644
|
+
}
|
|
18645
|
+
return String(value).replace(/\r?\n/gu, `
|
|
18646
|
+
${" ".repeat(LABEL_WIDTH + 3)}`);
|
|
18647
|
+
}
|
|
18648
|
+
function hasForbids(forbids2) {
|
|
18649
|
+
return typeof forbids2 === "object" && forbids2 !== null && Object.values(forbids2).some((v) => Array.isArray(v) && v.length > 0);
|
|
18650
|
+
}
|
|
18651
|
+
function previewLines(L, p, op) {
|
|
18652
|
+
const c = p.candidate;
|
|
18653
|
+
const id3 = p.sourceId;
|
|
18654
|
+
const head = op === "reject" ? t(L, "decision.preview.reject", { id: id3 }) : p.action === "create" ? t(L, "decision.preview.create", { id: id3 }) : p.action === "add-lock" ? t(L, "decision.preview.lock", { id: id3 }) : t(L, "decision.preview.in-place", { id: id3 });
|
|
18655
|
+
const row = (label, value) => ` ${label.padEnd(LABEL_WIDTH)} ${value}`;
|
|
18656
|
+
const supersedes = c.supersedes === void 0 ? t(L, "decision.preview.not-set") : p.supersedes === void 0 ? `${show(L, c.supersedes)} (${t(L, "decision.preview.not-found")})` : `${p.supersedes.id} "${p.supersedes.title}" (${p.supersedes.state})${op === "confirm" ? ` \u2192 ${t(L, "decision.preview.becomes-superseded")}` : ""}`;
|
|
18657
|
+
const stale = p.sourceKind === "decision" ? t(L, "decision.preview.stale-na") : p.stale === void 0 ? t(L, "decision.preview.fresh") : t(L, "decision.preview.stale", { changed: p.stale.changedRefs.length > 0 ? ` (changed: ${p.stale.changedRefs.join(", ")})` : "" });
|
|
18658
|
+
const lines4 = [
|
|
18659
|
+
head,
|
|
18660
|
+
row("Title", show(L, c.title)),
|
|
18661
|
+
row("Question", show(L, c.question)),
|
|
18662
|
+
row("Answer", show(L, c.answer)),
|
|
18663
|
+
row("Kind", show(L, c.kind)),
|
|
18664
|
+
row("Rationale", show(L, c.rationale)),
|
|
18665
|
+
row("Governs", show(L, c.governs)),
|
|
18666
|
+
row("Forbids", show(L, c.forbids)),
|
|
18667
|
+
// T40 (N3): governs does not scope forbids; say so wherever forbids are set.
|
|
18668
|
+
...hasForbids(c.forbids) ? [row("Forbids scope", t(L, "decision.preview.forbids-scope"))] : [],
|
|
18669
|
+
row("Enforcement", show(L, c.enforcement)),
|
|
18670
|
+
row("Supersedes", supersedes),
|
|
18671
|
+
// H-71: the proposer as the proposal file records it (an audit label); a YAML Decision has none to show.
|
|
18672
|
+
...p.sourceKind === "proposal" ? [row("Proposed by", p.proposedBy === void 0 ? t(L, "decision.preview.not-set") : `${p.proposedBy}${p.proposedByKind === void 0 ? "" : ` (${p.proposedByKind})`}`)] : [],
|
|
18673
|
+
...c.evidence === void 0 ? [] : [row("Evidence", show(L, c.evidence))],
|
|
18674
|
+
...c.source === void 0 ? [] : [row("Source", show(L, c.source))],
|
|
18675
|
+
...c.extensions === void 0 ? [] : [row("Extensions", show(L, c.extensions))],
|
|
18676
|
+
row("Stale", stale)
|
|
18677
|
+
];
|
|
18678
|
+
if (op === "confirm") {
|
|
18679
|
+
const expected = p.action === "create" ? t(L, "decision.preview.expected", { id: p.expectedDecisionId }) : p.action === "add-lock" ? t(L, "decision.preview.lock-id", { id: p.expectedDecisionId }) : t(L, "decision.preview.in-place-id", { id: p.expectedDecisionId });
|
|
18680
|
+
lines4.push(row("Expected ID", expected));
|
|
18681
|
+
}
|
|
18682
|
+
lines4.push(row("File", p.sourcePath));
|
|
18683
|
+
return lines4;
|
|
18684
|
+
}
|
|
18261
18685
|
async function decisionCommand(env, sub, id3, reason) {
|
|
18262
18686
|
const project2 = requireProject(env, "decision");
|
|
18263
18687
|
if (project2.value === void 0)
|
|
@@ -18281,27 +18705,31 @@ async function decisionCommand(env, sub, id3, reason) {
|
|
|
18281
18705
|
if (!env.io.isTTY || env.nonInteractive) {
|
|
18282
18706
|
return failed4("decision", EXIT.ERROR, [createDiagnostic("CLI_TTY_REQUIRED", t("en", "decision.tty"))], [t(env.locale, "decision.tty")]);
|
|
18283
18707
|
}
|
|
18284
|
-
const
|
|
18285
|
-
const
|
|
18286
|
-
|
|
18287
|
-
|
|
18288
|
-
|
|
18289
|
-
env.
|
|
18290
|
-
|
|
18291
|
-
if (shown.governs.requirements.length > 0)
|
|
18292
|
-
env.io.err(` governs ${shown.governs.requirements.join(", ")}`);
|
|
18708
|
+
const service = createDecisionService({ root: env.root, clock: () => env.io.now() });
|
|
18709
|
+
const previewed = sub === "reject" && DECISION_ID2.test(id3) ? void 0 : await service.previewConfirm(id3);
|
|
18710
|
+
if (previewed !== void 0) {
|
|
18711
|
+
if (previewed.value === void 0)
|
|
18712
|
+
return failed4("decision", EXIT.ERROR, previewed.diagnostics, diagLines(previewed.diagnostics), null, { status: "failed" });
|
|
18713
|
+
for (const line of previewLines(env.locale, previewed.value, sub))
|
|
18714
|
+
env.io.err(line);
|
|
18293
18715
|
}
|
|
18294
18716
|
const typed = (await env.io.prompt(t(env.locale, "decision.retype", { op: sub })))?.trim();
|
|
18295
18717
|
if (typed !== id3)
|
|
18296
18718
|
return failed4("decision", EXIT.ERROR, [], [t(env.locale, "decision.mismatch")]);
|
|
18297
|
-
const service = createDecisionService({ root: env.root, clock: () => env.io.now() });
|
|
18298
18719
|
const actor = await humanActor(env.root);
|
|
18299
18720
|
if (sub === "confirm") {
|
|
18300
|
-
const r2 = await service.confirm(actor, id3);
|
|
18721
|
+
const r2 = await service.confirm(actor, id3, previewed?.value === void 0 ? {} : { expectedDigest: previewed.value.digest });
|
|
18301
18722
|
if (r2.value === void 0)
|
|
18302
18723
|
return failed4("decision", EXIT.ERROR, r2.diagnostics, diagLines(r2.diagnostics), null, { status: "failed" });
|
|
18303
18724
|
const warnings = r2.diagnostics.filter((d) => d.severity === "warning").map((d) => `${d.code}: ${d.message}`);
|
|
18304
|
-
return {
|
|
18725
|
+
return {
|
|
18726
|
+
command: "decision",
|
|
18727
|
+
exitCode: EXIT.OK,
|
|
18728
|
+
diagnostics: r2.diagnostics,
|
|
18729
|
+
result: r2.value,
|
|
18730
|
+
human: [...warnings, t(env.locale, "decision.confirmed", { id: r2.value.decisionId, path: r2.value.path }), t(env.locale, "decision.index-hint")],
|
|
18731
|
+
metric: { status: "confirmed" }
|
|
18732
|
+
};
|
|
18305
18733
|
}
|
|
18306
18734
|
const r = await service.reject(actor, id3, reason);
|
|
18307
18735
|
if (r.value === void 0)
|
|
@@ -18823,7 +19251,7 @@ function renderReview(env, r) {
|
|
|
18823
19251
|
const ev = new Map(r.evidence.map((e) => [e.id, e]));
|
|
18824
19252
|
const shown = r.claims.filter((c) => env.verbose || c.alignment !== "ALIGNED");
|
|
18825
19253
|
for (const c of shown) {
|
|
18826
|
-
const tags = [c.blockEligible ? "blocking" : "", c.provenance
|
|
19254
|
+
const tags = [c.blockEligible ? "blocking" : "", c.provenance === void 0 ? "" : provenanceLabel(c.provenance, L), c.drift ? "drift" : ""].filter((x) => x !== "").join(", ");
|
|
18827
19255
|
out.push(` ${c.alignment.padEnd(9)} ${c.rule.padEnd(26)} ${c.subject.id} \xB7 ${c.reason}${tags === "" ? "" : ` [${tags}]`}`);
|
|
18828
19256
|
const pointers = c.evidenceIds.map((id3) => ev.get(id3)?.pointer).filter((p) => p?.path !== void 0).slice(0, 3).map((p) => `${p?.path ?? ""}${p?.lines === void 0 ? "" : `:${p.lines[0]}-${p.lines[1]}`}`);
|
|
18829
19257
|
if (pointers.length > 0)
|