@duo-director/cli 0.3.0-rc.3 → 0.3.0-rc.5
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-DJGVC2WO.js → cli-3KHF55G7.js} +931 -565
- 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
|
@@ -161,6 +161,8 @@ var DIAGNOSTIC_SEVERITY = {
|
|
|
161
161
|
DECISION_CONFIRM_PREVIEW_CHANGED: "error",
|
|
162
162
|
/** A confirmed Decision's lock.digest does not match its content fields (a detection aid, not a signature). */
|
|
163
163
|
DECISION_LOCK_MISMATCH: "warning",
|
|
164
|
+
/** T44 (F-07): a confirm session refused a proposal whose result depends on an earlier proposal of the same session. */
|
|
165
|
+
DECISION_SESSION_CONFLICT: "error",
|
|
164
166
|
/** T40 (N1): an authoritative operation refused partial Project Truth; the loader errors follow it. */
|
|
165
167
|
PROJECT_TRUTH_INVALID: "error",
|
|
166
168
|
/** A SourceLocation that does not address its canonical source text (a producer bug, T09.1). */
|
|
@@ -303,6 +305,7 @@ var DIAGNOSTIC_PERSISTENCE = {
|
|
|
303
305
|
DECISION_SUPERSEDE_TARGET_INVALID: T,
|
|
304
306
|
DECISION_LOCK_BUSY: T,
|
|
305
307
|
DECISION_CONFIRM_PREVIEW_CHANGED: T,
|
|
308
|
+
DECISION_SESSION_CONFLICT: T,
|
|
306
309
|
PROJECT_TRUTH_INVALID: T,
|
|
307
310
|
DECISION_LOCK_MISMATCH: P,
|
|
308
311
|
SOURCE_LOCATION_INVALID: P,
|
|
@@ -432,10 +435,10 @@ function parseCompatibleBaseUrl(raw) {
|
|
|
432
435
|
return { problem: "insecure-http" };
|
|
433
436
|
if (isOfficialOpenAIHost(url.hostname))
|
|
434
437
|
return { problem: "official-openai" };
|
|
435
|
-
const
|
|
436
|
-
if (
|
|
438
|
+
const path36 = url.pathname.replace(/\/+$/u, "");
|
|
439
|
+
if (path36.includes("//"))
|
|
437
440
|
return { problem: "empty-path-segment" };
|
|
438
|
-
return { value: { baseUrl: `${url.origin}${
|
|
441
|
+
return { value: { baseUrl: `${url.origin}${path36}`, origin: url.origin } };
|
|
439
442
|
}
|
|
440
443
|
var COMPATIBLE_BASE_URL_PROBLEMS = {
|
|
441
444
|
"not-absolute": "must be an absolute URL (https://host/path)",
|
|
@@ -537,8 +540,8 @@ function compileRepoPattern(pattern) {
|
|
|
537
540
|
const regex = new RegExp(`${source}$`, "u");
|
|
538
541
|
return (p) => regex.test(p);
|
|
539
542
|
}
|
|
540
|
-
function portablePathKey(
|
|
541
|
-
return
|
|
543
|
+
function portablePathKey(path36) {
|
|
544
|
+
return path36.normalize("NFC").toLowerCase();
|
|
542
545
|
}
|
|
543
546
|
function findPathPortabilityCollisions(paths) {
|
|
544
547
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -582,14 +585,14 @@ function isDefinitionId(value) {
|
|
|
582
585
|
function definitionRef(type, id3) {
|
|
583
586
|
return { type, id: id3 };
|
|
584
587
|
}
|
|
585
|
-
function fileRef(
|
|
586
|
-
return { type: "file", path:
|
|
588
|
+
function fileRef(path36) {
|
|
589
|
+
return { type: "file", path: path36 };
|
|
587
590
|
}
|
|
588
|
-
function symbolRef(
|
|
589
|
-
return { type: "symbol", path:
|
|
591
|
+
function symbolRef(path36, symbol) {
|
|
592
|
+
return { type: "symbol", path: path36, symbol };
|
|
590
593
|
}
|
|
591
|
-
function testRef(
|
|
592
|
-
return { type: "test", path:
|
|
594
|
+
function testRef(path36, name) {
|
|
595
|
+
return { type: "test", path: path36, name };
|
|
593
596
|
}
|
|
594
597
|
var ESCAPED = /[%#\u0000-\u001F\u007F]/g;
|
|
595
598
|
var ESCAPED_ONE = /^[%#\u0000-\u001F\u007F]$/;
|
|
@@ -640,18 +643,18 @@ function parseNodeId(value) {
|
|
|
640
643
|
if (type === "project")
|
|
641
644
|
return body === "root" ? PROJECT_REF : void 0;
|
|
642
645
|
if (type === "file") {
|
|
643
|
-
const
|
|
644
|
-
return
|
|
646
|
+
const path36 = body.includes("#") ? void 0 : decodePath(body);
|
|
647
|
+
return path36 === void 0 ? void 0 : fileRef(path36);
|
|
645
648
|
}
|
|
646
649
|
if (type === "symbol" || type === "test") {
|
|
647
650
|
const parts = body.split("#");
|
|
648
651
|
if (parts.length !== 2)
|
|
649
652
|
return void 0;
|
|
650
|
-
const
|
|
653
|
+
const path36 = decodePath(parts[0] ?? "");
|
|
651
654
|
const name = decodeComponent(parts[1] ?? "");
|
|
652
|
-
if (
|
|
655
|
+
if (path36 === void 0 || name === void 0 || name === "")
|
|
653
656
|
return void 0;
|
|
654
|
-
return type === "symbol" ? symbolRef(
|
|
657
|
+
return type === "symbol" ? symbolRef(path36, name) : testRef(path36, name);
|
|
655
658
|
}
|
|
656
659
|
const id3 = body.includes("#") ? void 0 : decodeComponent(body);
|
|
657
660
|
return id3 !== void 0 && isDefinitionId(id3) ? definitionRef(type, id3) : void 0;
|
|
@@ -737,33 +740,33 @@ function checkWriteBoundary(repositoryRoot, targetPath, writeKind, options = {})
|
|
|
737
740
|
const outside = normalized.diagnostics.some((d) => d.code === "PATH_OUTSIDE_REPOSITORY");
|
|
738
741
|
return failure(outside ? [createDiagnostic("WRITE_OUTSIDE_REPOSITORY", `Refusing to write "${targetPath}": it is outside the repository`)] : normalized.diagnostics);
|
|
739
742
|
}
|
|
740
|
-
const
|
|
743
|
+
const path36 = normalized.value;
|
|
741
744
|
const prefix = `${STATE_DIR_NAME}/`;
|
|
742
745
|
if (writeKind === "agent-integration") {
|
|
743
|
-
return AGENT_INTEGRATION_FILES.includes(
|
|
746
|
+
return AGENT_INTEGRATION_FILES.includes(path36) ? success({ path: path36, kind: writeKind, area: path36 }) : failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${path36}" as agent-integration: only ${AGENT_INTEGRATION_FILES.join(", ")}`, { path: path36 })]);
|
|
744
747
|
}
|
|
745
|
-
if (!
|
|
746
|
-
return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${
|
|
748
|
+
if (!path36.startsWith(prefix)) {
|
|
749
|
+
return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${path36}": DUO writes only under ${prefix} and never writes project source code`, { path: path36 })]);
|
|
747
750
|
}
|
|
748
|
-
const inner =
|
|
751
|
+
const inner = path36.slice(prefix.length);
|
|
749
752
|
const kinds = Object.keys(WRITE_AREAS);
|
|
750
753
|
const found = kinds.flatMap((kind) => WRITE_AREAS[kind].filter((a) => matches(inner, a)).map((area) => ({ kind, area })))[0];
|
|
751
754
|
if (found === void 0) {
|
|
752
|
-
return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${
|
|
755
|
+
return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${path36}": not a ${prefix} write area`, { path: path36 })]);
|
|
753
756
|
}
|
|
754
757
|
if (found.kind !== writeKind) {
|
|
755
|
-
return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${
|
|
758
|
+
return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${path36}" as ${writeKind}: ${prefix}${found.area} holds ${found.kind} data`, { path: path36 })]);
|
|
756
759
|
}
|
|
757
760
|
if (options.restrictTo !== void 0) {
|
|
758
761
|
const allowed = options.restrictTo.some((r) => {
|
|
759
762
|
const p = normalizeRepoPattern(r).value;
|
|
760
|
-
return p !== void 0 && (p.endsWith("/") ?
|
|
763
|
+
return p !== void 0 && (p.endsWith("/") ? path36.startsWith(p) && path36.length > p.length : path36 === p);
|
|
761
764
|
});
|
|
762
765
|
if (!allowed) {
|
|
763
|
-
return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${
|
|
766
|
+
return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${path36}": outside the paths allowed for this caller`, { path: path36 })]);
|
|
764
767
|
}
|
|
765
768
|
}
|
|
766
|
-
return success({ path:
|
|
769
|
+
return success({ path: path36, kind: writeKind, area: prefix + found.area });
|
|
767
770
|
}
|
|
768
771
|
|
|
769
772
|
// packages/core/dist/fs-guard.js
|
|
@@ -898,12 +901,12 @@ function paragraphLines(p) {
|
|
|
898
901
|
visit2(child);
|
|
899
902
|
return { detect: detect.split("\n"), display: display.split("\n") };
|
|
900
903
|
}
|
|
901
|
-
function parseMarkdown(
|
|
904
|
+
function parseMarkdown(path36, text4) {
|
|
902
905
|
let tree;
|
|
903
906
|
try {
|
|
904
907
|
tree = fromMarkdown(text4, { extensions: [frontmatter(["yaml"])], mdastExtensions: [frontmatterFromMarkdown(["yaml"])] });
|
|
905
908
|
} catch (error) {
|
|
906
|
-
return failure([createDiagnostic("MARKDOWN_PARSE_ERROR", String(error), { path:
|
|
909
|
+
return failure([createDiagnostic("MARKDOWN_PARSE_ERROR", String(error), { path: path36 })]);
|
|
907
910
|
}
|
|
908
911
|
const lineStarts3 = [0];
|
|
909
912
|
for (let i = 0; i < text4.length; i++)
|
|
@@ -926,7 +929,7 @@ function parseMarkdown(path35, text4) {
|
|
|
926
929
|
const stop = Math.max(start2, end2);
|
|
927
930
|
const el = lineAt(stop);
|
|
928
931
|
return {
|
|
929
|
-
path:
|
|
932
|
+
path: path36,
|
|
930
933
|
startLine: sl,
|
|
931
934
|
startColumn: start2 - (lineStarts3[sl - 1] ?? 0) + 1,
|
|
932
935
|
endLine: el,
|
|
@@ -1022,7 +1025,7 @@ function parseMarkdown(path35, text4) {
|
|
|
1022
1025
|
};
|
|
1023
1026
|
walk3(tree);
|
|
1024
1027
|
return success({
|
|
1025
|
-
path:
|
|
1028
|
+
path: path36,
|
|
1026
1029
|
length: text4.length,
|
|
1027
1030
|
lineCount: lineStarts3.length,
|
|
1028
1031
|
frontmatter: front,
|
|
@@ -1039,16 +1042,16 @@ function parseMarkdown(path35, text4) {
|
|
|
1039
1042
|
// packages/core/dist/provenance.js
|
|
1040
1043
|
var MARKDOWN = /\.(?:md|markdown|mdx)$/iu;
|
|
1041
1044
|
var SCHEME = /^(?:[A-Za-z][A-Za-z0-9+.-]+:|\/\/)/u;
|
|
1042
|
-
function isRemoteSourcePath(
|
|
1043
|
-
return SCHEME.test(
|
|
1045
|
+
function isRemoteSourcePath(path36) {
|
|
1046
|
+
return SCHEME.test(path36);
|
|
1044
1047
|
}
|
|
1045
|
-
function externalSourceSlice(
|
|
1048
|
+
function externalSourceSlice(path36, rawText, section) {
|
|
1046
1049
|
const text4 = canonicalSourceText(rawText);
|
|
1047
1050
|
if (section === void 0)
|
|
1048
|
-
return { status: "ok", text: text4, hash: sha256Text(text4), location: { path:
|
|
1049
|
-
if (!MARKDOWN.test(
|
|
1051
|
+
return { status: "ok", text: text4, hash: sha256Text(text4), location: { path: path36 } };
|
|
1052
|
+
if (!MARKDOWN.test(path36))
|
|
1050
1053
|
return { status: "section-unsupported" };
|
|
1051
|
-
const doc = parseMarkdown(
|
|
1054
|
+
const doc = parseMarkdown(path36, text4).value;
|
|
1052
1055
|
if (doc === void 0)
|
|
1053
1056
|
return { status: "section-missing" };
|
|
1054
1057
|
const headings = doc.blocks.flatMap((b) => b.kind === "heading" ? [b] : []);
|
|
@@ -1143,16 +1146,16 @@ function parseYaml(input) {
|
|
|
1143
1146
|
} catch (error) {
|
|
1144
1147
|
return failure([...diagnostics, createDiagnostic("YAML_SYNTAX_ERROR", String(error), at(0))]);
|
|
1145
1148
|
}
|
|
1146
|
-
const locate = (
|
|
1147
|
-
if (target === "key" &&
|
|
1148
|
-
const parent =
|
|
1149
|
-
const key2 =
|
|
1149
|
+
const locate = (path36, target = "value") => {
|
|
1150
|
+
if (target === "key" && path36.length > 0) {
|
|
1151
|
+
const parent = path36.length > 1 ? doc.getIn(path36.slice(0, -1), true) : doc.contents;
|
|
1152
|
+
const key2 = path36[path36.length - 1];
|
|
1150
1153
|
if (!isMap(parent))
|
|
1151
1154
|
return void 0;
|
|
1152
1155
|
const pair = parent.items.find((item) => (isScalar(item.key) ? item.key.value : item.key) === key2);
|
|
1153
1156
|
return pair && isNode(pair.key) ? rangeOf(pair.key) : void 0;
|
|
1154
1157
|
}
|
|
1155
|
-
const node =
|
|
1158
|
+
const node = path36.length === 0 ? doc.contents : doc.getIn(path36, true);
|
|
1156
1159
|
return isNode(node) ? rangeOf(node) : void 0;
|
|
1157
1160
|
};
|
|
1158
1161
|
return success({ data, locate }, diagnostics);
|
|
@@ -1230,7 +1233,7 @@ var ProjectConfigSchema = z.strictObject({
|
|
|
1230
1233
|
extensions
|
|
1231
1234
|
});
|
|
1232
1235
|
function checkLlmConfig(llm, ctx) {
|
|
1233
|
-
const issue = (
|
|
1236
|
+
const issue = (path36, message2) => ctx.addIssue({ code: "custom", path: [path36], message: message2 });
|
|
1234
1237
|
if (llm.provider === "openai-compatible") {
|
|
1235
1238
|
const required = "is required for provider openai-compatible (no default)";
|
|
1236
1239
|
if (llm.model === void 0 || llm.model === null || llm.model.trim() === "")
|
|
@@ -1299,6 +1302,8 @@ var issueShape = {
|
|
|
1299
1302
|
requirements: ids().optional(),
|
|
1300
1303
|
decisions: ids().optional(),
|
|
1301
1304
|
depends_on: ids().optional(),
|
|
1305
|
+
/** H-78 (T45): the repository path scope this Issue declares it implements (relevance / traceability only). Paths only. */
|
|
1306
|
+
implements: z.strictObject({ paths: texts().optional() }).optional(),
|
|
1302
1307
|
extensions
|
|
1303
1308
|
};
|
|
1304
1309
|
var IssueBlockSchema = z.strictObject({ type: z.literal("issue"), ...issueShape });
|
|
@@ -1370,9 +1375,9 @@ var MilestoneFileSchema = z.strictObject({
|
|
|
1370
1375
|
function isRecord(value) {
|
|
1371
1376
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1372
1377
|
}
|
|
1373
|
-
function valueAt(data,
|
|
1378
|
+
function valueAt(data, path36) {
|
|
1374
1379
|
let current = data;
|
|
1375
|
-
for (const key2 of
|
|
1380
|
+
for (const key2 of path36) {
|
|
1376
1381
|
if (Array.isArray(current) && typeof key2 === "number")
|
|
1377
1382
|
current = current[key2];
|
|
1378
1383
|
else if (isRecord(current) && typeof key2 === "string")
|
|
@@ -1382,36 +1387,36 @@ function valueAt(data, path35) {
|
|
|
1382
1387
|
}
|
|
1383
1388
|
return current;
|
|
1384
1389
|
}
|
|
1385
|
-
function describe(
|
|
1386
|
-
if (
|
|
1390
|
+
function describe(path36) {
|
|
1391
|
+
if (path36.length === 0)
|
|
1387
1392
|
return "(root)";
|
|
1388
|
-
return
|
|
1393
|
+
return path36.map((k, i) => typeof k === "number" ? `[${k}]` : i === 0 ? k : `.${k}`).join("");
|
|
1389
1394
|
}
|
|
1390
1395
|
function validateData(schema, yaml2, fallback) {
|
|
1391
1396
|
const result = schema.safeParse(yaml2.data);
|
|
1392
1397
|
if (result.success)
|
|
1393
1398
|
return success(result.data);
|
|
1394
1399
|
const diagnostics = [];
|
|
1395
|
-
const where = (
|
|
1400
|
+
const where = (path36, target = "value") => yaml2.locate(path36, target) ?? fallback;
|
|
1396
1401
|
for (const issue of result.error.issues) {
|
|
1397
|
-
const
|
|
1402
|
+
const path36 = issue.path.filter((k) => typeof k === "string" || typeof k === "number");
|
|
1398
1403
|
if (issue.code === "unrecognized_keys") {
|
|
1399
1404
|
for (const key2 of issue.keys) {
|
|
1400
|
-
diagnostics.push(createDiagnostic("SCHEMA_UNKNOWN_PROPERTY", `Unknown property "${describe([...
|
|
1405
|
+
diagnostics.push(createDiagnostic("SCHEMA_UNKNOWN_PROPERTY", `Unknown property "${describe([...path36, key2])}". Put custom data under "extensions".`, where([...path36, key2], "key")));
|
|
1401
1406
|
}
|
|
1402
1407
|
continue;
|
|
1403
1408
|
}
|
|
1404
|
-
const value = valueAt(yaml2.data,
|
|
1405
|
-
const parent = valueAt(yaml2.data,
|
|
1406
|
-
const last =
|
|
1409
|
+
const value = valueAt(yaml2.data, path36);
|
|
1410
|
+
const parent = valueAt(yaml2.data, path36.slice(0, -1));
|
|
1411
|
+
const last = path36[path36.length - 1];
|
|
1407
1412
|
if (issue.code === "invalid_type" && value === void 0 && typeof last === "string" && isRecord(parent) && !(last in parent)) {
|
|
1408
|
-
diagnostics.push(createDiagnostic("SCHEMA_MISSING_PROPERTY", `Missing required property "${describe(
|
|
1413
|
+
diagnostics.push(createDiagnostic("SCHEMA_MISSING_PROPERTY", `Missing required property "${describe(path36)}"`, where(path36.slice(0, -1))));
|
|
1409
1414
|
} else if (issue.message === INVALID_ID_MESSAGE) {
|
|
1410
|
-
diagnostics.push(createDiagnostic("INVALID_ID", `${describe(
|
|
1411
|
-
} else if (
|
|
1412
|
-
diagnostics.push(createDiagnostic("SCHEMA_INVALID_VALUE", "Expected a mapping of properties", where(
|
|
1415
|
+
diagnostics.push(createDiagnostic("INVALID_ID", `${describe(path36)}: "${String(value)}" is not a valid ID`, where(path36)));
|
|
1416
|
+
} else if (path36.length === 0 && !isRecord(yaml2.data)) {
|
|
1417
|
+
diagnostics.push(createDiagnostic("SCHEMA_INVALID_VALUE", "Expected a mapping of properties", where(path36)));
|
|
1413
1418
|
} else {
|
|
1414
|
-
diagnostics.push(createDiagnostic("SCHEMA_INVALID_VALUE", `${describe(
|
|
1419
|
+
diagnostics.push(createDiagnostic("SCHEMA_INVALID_VALUE", `${describe(path36)}: ${issue.message}`, where(path36)));
|
|
1415
1420
|
}
|
|
1416
1421
|
}
|
|
1417
1422
|
return failure(diagnostics);
|
|
@@ -1435,32 +1440,32 @@ var MapContext = class _MapContext {
|
|
|
1435
1440
|
this.diagnostics = diagnostics;
|
|
1436
1441
|
this.base = base;
|
|
1437
1442
|
}
|
|
1438
|
-
at(
|
|
1439
|
-
return this.yaml.locate([...this.base, ...
|
|
1443
|
+
at(path36) {
|
|
1444
|
+
return this.yaml.locate([...this.base, ...path36]) ?? this.fallback;
|
|
1440
1445
|
}
|
|
1441
1446
|
child(base) {
|
|
1442
1447
|
return new _MapContext(this.yaml, this.at(base), this.diagnostics, [...this.base, ...base]);
|
|
1443
1448
|
}
|
|
1444
|
-
ref(field,
|
|
1445
|
-
this.references.push({ field, target, expected, relation, location: this.at(
|
|
1449
|
+
ref(field, path36, target, expected, relation) {
|
|
1450
|
+
this.references.push({ field, target, expected, relation, location: this.at(path36) });
|
|
1446
1451
|
}
|
|
1447
|
-
refs(field,
|
|
1452
|
+
refs(field, path36, targets, expected, relation) {
|
|
1448
1453
|
const list3 = targets ?? [];
|
|
1449
|
-
list3.forEach((t2, i) => this.ref(field, [...
|
|
1454
|
+
list3.forEach((t2, i) => this.ref(field, [...path36, i], t2, expected, relation));
|
|
1450
1455
|
return list3;
|
|
1451
1456
|
}
|
|
1452
|
-
patterns(
|
|
1457
|
+
patterns(path36, values) {
|
|
1453
1458
|
const out = [];
|
|
1454
1459
|
(values ?? []).forEach((v, i) => {
|
|
1455
1460
|
const r = normalizeRepoPattern(v);
|
|
1456
1461
|
if (r.value !== void 0)
|
|
1457
1462
|
out.push(r.value);
|
|
1458
1463
|
for (const d of r.diagnostics)
|
|
1459
|
-
this.diagnostics.push(withSource(d, this.at([...
|
|
1464
|
+
this.diagnostics.push(withSource(d, this.at([...path36, i])));
|
|
1460
1465
|
});
|
|
1461
1466
|
return out;
|
|
1462
1467
|
}
|
|
1463
|
-
sources(
|
|
1468
|
+
sources(path36, values) {
|
|
1464
1469
|
const out = [];
|
|
1465
1470
|
(values ?? []).forEach((v, i) => {
|
|
1466
1471
|
if (typeof v === "string") {
|
|
@@ -1469,20 +1474,20 @@ var MapContext = class _MapContext {
|
|
|
1469
1474
|
}
|
|
1470
1475
|
const r = normalizeRepoPath(v.path);
|
|
1471
1476
|
for (const d of r.diagnostics)
|
|
1472
|
-
this.diagnostics.push(withSource(d, this.at([...
|
|
1477
|
+
this.diagnostics.push(withSource(d, this.at([...path36, i, "path"])));
|
|
1473
1478
|
if (r.value !== void 0)
|
|
1474
1479
|
out.push({ kind: "external", path: r.value, hash: v.hash, section: v.section });
|
|
1475
1480
|
});
|
|
1476
1481
|
return out;
|
|
1477
1482
|
}
|
|
1478
|
-
evidence(
|
|
1483
|
+
evidence(path36, values) {
|
|
1479
1484
|
const out = [];
|
|
1480
1485
|
(values ?? []).forEach((v, i) => {
|
|
1481
1486
|
let repoPath3;
|
|
1482
1487
|
if (v.path !== void 0) {
|
|
1483
1488
|
const r = normalizeRepoPath(v.path);
|
|
1484
1489
|
for (const d of r.diagnostics)
|
|
1485
|
-
this.diagnostics.push(withSource(d, this.at([...
|
|
1490
|
+
this.diagnostics.push(withSource(d, this.at([...path36, i, "path"])));
|
|
1486
1491
|
repoPath3 = r.value;
|
|
1487
1492
|
}
|
|
1488
1493
|
out.push({
|
|
@@ -1576,6 +1581,7 @@ function mapIssue(id3, title, description, location4, d, acceptance, ctx) {
|
|
|
1576
1581
|
requirements: ctx.refs("requirements", ["requirements"], d.requirements, "requirement", { type: "TRACKED_BY", direction: "incoming" }),
|
|
1577
1582
|
decisions: ctx.refs("decisions", ["decisions"], d.decisions, "decision", { type: "GOVERNS", direction: "incoming" }),
|
|
1578
1583
|
dependsOn: ctx.refs("depends_on", ["depends_on"], d.depends_on, "issue", { type: "REQUIRES", direction: "outgoing" }),
|
|
1584
|
+
implements: { paths: ctx.patterns(["implements", "paths"], d.implements?.paths) },
|
|
1579
1585
|
references: ctx.references,
|
|
1580
1586
|
extensions: d.extensions ?? {}
|
|
1581
1587
|
};
|
|
@@ -1817,8 +1823,8 @@ function parseDefinitionDocument(doc) {
|
|
|
1817
1823
|
out.gaps.push(...declaredGapsOf(doc, sections));
|
|
1818
1824
|
return { value: out, diagnostics };
|
|
1819
1825
|
}
|
|
1820
|
-
function parseDefinitionMarkdown(
|
|
1821
|
-
const md = parseMarkdown(
|
|
1826
|
+
function parseDefinitionMarkdown(path36, text4) {
|
|
1827
|
+
const md = parseMarkdown(path36, text4);
|
|
1822
1828
|
if (md.value === void 0)
|
|
1823
1829
|
return { diagnostics: md.diagnostics };
|
|
1824
1830
|
const defs = parseDefinitionDocument(md.value);
|
|
@@ -1829,16 +1835,16 @@ function parseDefinitionMarkdown(path35, text4) {
|
|
|
1829
1835
|
function isRecord3(value) {
|
|
1830
1836
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1831
1837
|
}
|
|
1832
|
-
function parseFile(
|
|
1833
|
-
const parsed = parseYaml({ path:
|
|
1838
|
+
function parseFile(path36, text4) {
|
|
1839
|
+
const parsed = parseYaml({ path: path36, text: text4 });
|
|
1834
1840
|
const diagnostics = [...parsed.diagnostics];
|
|
1835
1841
|
if (parsed.value === void 0)
|
|
1836
|
-
return { diagnostics, whole: { path:
|
|
1837
|
-
const whole = parsed.value.locate([]) ?? { path:
|
|
1842
|
+
return { diagnostics, whole: { path: path36, startLine: 1 } };
|
|
1843
|
+
const whole = parsed.value.locate([]) ?? { path: path36, startLine: 1 };
|
|
1838
1844
|
return { yaml: parsed.value, diagnostics, whole };
|
|
1839
1845
|
}
|
|
1840
|
-
function parseProjectConfig(
|
|
1841
|
-
const { yaml: yaml2, diagnostics, whole } = parseFile(
|
|
1846
|
+
function parseProjectConfig(path36, text4) {
|
|
1847
|
+
const { yaml: yaml2, diagnostics, whole } = parseFile(path36, text4);
|
|
1842
1848
|
if (yaml2 === void 0)
|
|
1843
1849
|
return failure(diagnostics);
|
|
1844
1850
|
const version = isRecord3(yaml2.data) ? yaml2.data["schema_version"] : void 0;
|
|
@@ -1853,8 +1859,8 @@ function parseProjectConfig(path35, text4) {
|
|
|
1853
1859
|
const config = mapProjectConfig(valid.value, new MapContext(yaml2, whole, diagnostics));
|
|
1854
1860
|
return { value: config, diagnostics };
|
|
1855
1861
|
}
|
|
1856
|
-
function parseDecisionFile(
|
|
1857
|
-
const { yaml: yaml2, diagnostics, whole } = parseFile(
|
|
1862
|
+
function parseDecisionFile(path36, text4) {
|
|
1863
|
+
const { yaml: yaml2, diagnostics, whole } = parseFile(path36, text4);
|
|
1858
1864
|
if (yaml2 === void 0)
|
|
1859
1865
|
return failure(diagnostics);
|
|
1860
1866
|
const valid = validateData(DecisionSchema, yaml2, whole);
|
|
@@ -1863,8 +1869,8 @@ function parseDecisionFile(path35, text4) {
|
|
|
1863
1869
|
return failure(diagnostics);
|
|
1864
1870
|
return { value: mapDecision(valid.value, whole, new MapContext(yaml2, whole, diagnostics)), diagnostics };
|
|
1865
1871
|
}
|
|
1866
|
-
function parseProposalFile(
|
|
1867
|
-
const { yaml: yaml2, diagnostics, whole } = parseFile(
|
|
1872
|
+
function parseProposalFile(path36, text4) {
|
|
1873
|
+
const { yaml: yaml2, diagnostics, whole } = parseFile(path36, text4);
|
|
1868
1874
|
if (yaml2 === void 0)
|
|
1869
1875
|
return failure(diagnostics);
|
|
1870
1876
|
const valid = validateData(ProposalSchema, yaml2, whole);
|
|
@@ -1873,8 +1879,8 @@ function parseProposalFile(path35, text4) {
|
|
|
1873
1879
|
return failure(diagnostics);
|
|
1874
1880
|
return { value: mapProposal(valid.value, whole, new MapContext(yaml2, whole, diagnostics)), diagnostics };
|
|
1875
1881
|
}
|
|
1876
|
-
function parseConstraintsFile(
|
|
1877
|
-
const { yaml: yaml2, diagnostics, whole } = parseFile(
|
|
1882
|
+
function parseConstraintsFile(path36, text4) {
|
|
1883
|
+
const { yaml: yaml2, diagnostics, whole } = parseFile(path36, text4);
|
|
1878
1884
|
if (yaml2 === void 0)
|
|
1879
1885
|
return failure(diagnostics);
|
|
1880
1886
|
const valid = validateData(ConstraintsFileSchema, yaml2, whole);
|
|
@@ -1885,8 +1891,8 @@ function parseConstraintsFile(path35, text4) {
|
|
|
1885
1891
|
const constraints = valid.value.constraints.map((c, i) => mapConstraint(c, root.child(["constraints", i])));
|
|
1886
1892
|
return { value: constraints, diagnostics };
|
|
1887
1893
|
}
|
|
1888
|
-
function parseMilestoneFile(
|
|
1889
|
-
const { yaml: yaml2, diagnostics, whole } = parseFile(
|
|
1894
|
+
function parseMilestoneFile(path36, text4) {
|
|
1895
|
+
const { yaml: yaml2, diagnostics, whole } = parseFile(path36, text4);
|
|
1890
1896
|
if (yaml2 === void 0)
|
|
1891
1897
|
return failure(diagnostics);
|
|
1892
1898
|
const valid = validateData(MilestoneFileSchema, yaml2, whole);
|
|
@@ -1896,17 +1902,17 @@ function parseMilestoneFile(path35, text4) {
|
|
|
1896
1902
|
const d = valid.value;
|
|
1897
1903
|
return { value: mapMilestone(d.id, d.title, d.state, d.issues, whole, d.extensions, new MapContext(yaml2, whole, diagnostics)), diagnostics };
|
|
1898
1904
|
}
|
|
1899
|
-
function parseVisionFile(
|
|
1900
|
-
const md = parseMarkdown(
|
|
1905
|
+
function parseVisionFile(path36, text4) {
|
|
1906
|
+
const md = parseMarkdown(path36, text4);
|
|
1901
1907
|
const diagnostics = [...md.diagnostics];
|
|
1902
1908
|
const doc = md.value;
|
|
1903
1909
|
if (doc === void 0)
|
|
1904
1910
|
return failure(diagnostics);
|
|
1905
1911
|
if (doc.frontmatter === void 0) {
|
|
1906
|
-
diagnostics.push(createDiagnostic("SCHEMA_MISSING_PROPERTY", 'vision.md needs YAML frontmatter with "status"', { path:
|
|
1912
|
+
diagnostics.push(createDiagnostic("SCHEMA_MISSING_PROPERTY", 'vision.md needs YAML frontmatter with "status"', { path: path36, startLine: 1 }));
|
|
1907
1913
|
return failure(diagnostics);
|
|
1908
1914
|
}
|
|
1909
|
-
const parsed = parseYaml({ path:
|
|
1915
|
+
const parsed = parseYaml({ path: path36, text: doc.frontmatter.value, startLine: doc.frontmatter.contentStartLine });
|
|
1910
1916
|
diagnostics.push(...parsed.diagnostics);
|
|
1911
1917
|
if (parsed.value === void 0)
|
|
1912
1918
|
return failure(diagnostics);
|
|
@@ -2408,7 +2414,7 @@ function alive(pid) {
|
|
|
2408
2414
|
}
|
|
2409
2415
|
function createDecisionService(options) {
|
|
2410
2416
|
const root = options.root;
|
|
2411
|
-
const
|
|
2417
|
+
const fs29 = options.fs ?? nodeDecisionFileSystem;
|
|
2412
2418
|
const clock = options.clock ?? (() => /* @__PURE__ */ new Date());
|
|
2413
2419
|
const now = () => clock().toISOString();
|
|
2414
2420
|
const forbidden = (actor, op) => {
|
|
@@ -2425,18 +2431,18 @@ function createDecisionService(options) {
|
|
|
2425
2431
|
const file = lock.value.absolute;
|
|
2426
2432
|
const deadline = Date.now() + (options.lockTimeoutMs ?? 5e3);
|
|
2427
2433
|
for (; ; ) {
|
|
2428
|
-
if (await
|
|
2434
|
+
if (await fs29.createExclusive(file, JSON.stringify({ pid: process.pid, host: os.hostname(), acquiredAt: now() })))
|
|
2429
2435
|
break;
|
|
2430
2436
|
let owner;
|
|
2431
2437
|
try {
|
|
2432
|
-
owner = JSON.parse(await
|
|
2438
|
+
owner = JSON.parse(await fs29.readText(file) ?? "null");
|
|
2433
2439
|
} catch {
|
|
2434
2440
|
owner = void 0;
|
|
2435
2441
|
}
|
|
2436
2442
|
if (owner === null)
|
|
2437
2443
|
continue;
|
|
2438
2444
|
if (typeof owner?.pid === "number" && owner.host === os.hostname() && !alive(owner.pid)) {
|
|
2439
|
-
await
|
|
2445
|
+
await fs29.remove(file);
|
|
2440
2446
|
continue;
|
|
2441
2447
|
}
|
|
2442
2448
|
if (Date.now() >= deadline)
|
|
@@ -2446,7 +2452,7 @@ function createDecisionService(options) {
|
|
|
2446
2452
|
try {
|
|
2447
2453
|
return await fn();
|
|
2448
2454
|
} finally {
|
|
2449
|
-
await
|
|
2455
|
+
await fs29.remove(file);
|
|
2450
2456
|
}
|
|
2451
2457
|
}
|
|
2452
2458
|
function load(complete = true) {
|
|
@@ -2455,17 +2461,17 @@ function createDecisionService(options) {
|
|
|
2455
2461
|
return loaded.value === void 0 ? failure(loaded.diagnostics) : success(loaded.value.truth, loaded.diagnostics);
|
|
2456
2462
|
}
|
|
2457
2463
|
const isServiceFile = (d) => d.location.path === decisionPath(d.id);
|
|
2458
|
-
function traceErrors(truth, id3,
|
|
2459
|
-
const decisions = change.decision === void 0 ? truth.decisions : [...truth.decisions.filter((d) => d.location.path !==
|
|
2460
|
-
const proposals = change.proposal === void 0 ? truth.proposals : [...truth.proposals.filter((p) => p.location.path !==
|
|
2464
|
+
function traceErrors(truth, id3, path36, change) {
|
|
2465
|
+
const decisions = change.decision === void 0 ? truth.decisions : [...truth.decisions.filter((d) => d.location.path !== path36), change.decision];
|
|
2466
|
+
const proposals = change.proposal === void 0 ? truth.proposals : [...truth.proposals.filter((p) => p.location.path !== path36), change.proposal];
|
|
2461
2467
|
const { diagnostics } = analyzeTrace({ ...truth, decisions, proposals, references: truth.config.references });
|
|
2462
|
-
return diagnostics.filter((d) => d.severity === "error" && TRACE_ERRORS.has(d.code) && (d.source?.path ===
|
|
2468
|
+
return diagnostics.filter((d) => d.severity === "error" && TRACE_ERRORS.has(d.code) && (d.source?.path === path36 || idTokens(d.message).includes(id3)));
|
|
2463
2469
|
}
|
|
2464
2470
|
async function updateInPlace(actor, repoPath3, target, allowed, values) {
|
|
2465
2471
|
const g = guard(actor, repoPath3, target);
|
|
2466
2472
|
if (g.value === void 0)
|
|
2467
2473
|
return [...g.diagnostics];
|
|
2468
|
-
const text4 = await
|
|
2474
|
+
const text4 = await fs29.readText(g.value.absolute);
|
|
2469
2475
|
if (text4 === void 0)
|
|
2470
2476
|
return [createDiagnostic("FILE_READ_ERROR", `${repoPath3} is missing`, { path: repoPath3 })];
|
|
2471
2477
|
for (const key2 of Object.keys(values)) {
|
|
@@ -2477,7 +2483,7 @@ function createDecisionService(options) {
|
|
|
2477
2483
|
const errors = parsed.diagnostics.filter((d) => d.severity === "error");
|
|
2478
2484
|
if (errors.length > 0)
|
|
2479
2485
|
return errors;
|
|
2480
|
-
await
|
|
2486
|
+
await fs29.writeAtomic(g.value.absolute, next2);
|
|
2481
2487
|
return [];
|
|
2482
2488
|
}
|
|
2483
2489
|
async function repairWith(truth) {
|
|
@@ -2490,7 +2496,7 @@ function createDecisionService(options) {
|
|
|
2490
2496
|
if (pending !== void 0) {
|
|
2491
2497
|
const g = guard({ kind: "human", name: d.confirmedBy ?? "duo" }, pending.location.path, "proposal");
|
|
2492
2498
|
if (g.value !== void 0) {
|
|
2493
|
-
await
|
|
2499
|
+
await fs29.remove(g.value.absolute);
|
|
2494
2500
|
repaired.push(`removed ${pending.location.path} (confirmed as ${d.id})`);
|
|
2495
2501
|
}
|
|
2496
2502
|
}
|
|
@@ -2540,7 +2546,7 @@ function createDecisionService(options) {
|
|
|
2540
2546
|
return truthChanged || changedRefs.length > 0 ? { truthChanged, changedRefs } : void 0;
|
|
2541
2547
|
}
|
|
2542
2548
|
async function allocate(prefix, truth) {
|
|
2543
|
-
const names = [...await
|
|
2549
|
+
const names = [...await fs29.list(`${root}/${DECISIONS_DIR}`), ...await fs29.list(`${root}/${PROPOSALS_DIR}`)].map((n) => n.replace(/\.ya?ml$/u, ""));
|
|
2544
2550
|
if (prefix === "D")
|
|
2545
2551
|
return nextDecisionId([...truth.decisions.map((d) => d.id), ...names]);
|
|
2546
2552
|
return nextProposalId([...truth.proposals.map((p) => p.id), ...truth.decisions.flatMap((d) => d.proposalId === void 0 ? [] : [d.proposalId]), ...names]);
|
|
@@ -2556,7 +2562,7 @@ function createDecisionService(options) {
|
|
|
2556
2562
|
return failure(g.diagnostics);
|
|
2557
2563
|
let created;
|
|
2558
2564
|
try {
|
|
2559
|
-
created = await
|
|
2565
|
+
created = await fs29.createExclusive(g.value.absolute, render(chosen));
|
|
2560
2566
|
} catch (error) {
|
|
2561
2567
|
return failure([createDiagnostic("FILE_WRITE_ERROR", `Cannot create ${g.value.path}: ${error.message}; nothing was confirmed`, { path: g.value.path })]);
|
|
2562
2568
|
}
|
|
@@ -2762,7 +2768,7 @@ function createDecisionService(options) {
|
|
|
2762
2768
|
}));
|
|
2763
2769
|
}
|
|
2764
2770
|
async function rawData(repoPath3) {
|
|
2765
|
-
const text4 = await
|
|
2771
|
+
const text4 = await fs29.readText(`${root}/${repoPath3}`);
|
|
2766
2772
|
if (text4 === void 0)
|
|
2767
2773
|
return void 0;
|
|
2768
2774
|
return parseYaml({ path: repoPath3, text: text4 }).value?.data ?? {};
|
|
@@ -2846,7 +2852,7 @@ function createDecisionService(options) {
|
|
|
2846
2852
|
}
|
|
2847
2853
|
const g = guard(actor, proposal.location.path, "proposal");
|
|
2848
2854
|
if (g.value !== void 0)
|
|
2849
|
-
await
|
|
2855
|
+
await fs29.remove(g.value.absolute);
|
|
2850
2856
|
} catch (error) {
|
|
2851
2857
|
after.push(createDiagnostic("FILE_WRITE_ERROR", `${created.value.id} is confirmed; finishing failed and is repaired by the next operation: ${error.message}`));
|
|
2852
2858
|
}
|
|
@@ -2893,15 +2899,78 @@ function createDecisionService(options) {
|
|
|
2893
2899
|
}
|
|
2894
2900
|
}
|
|
2895
2901
|
|
|
2902
|
+
// packages/core/dist/decisions/session.js
|
|
2903
|
+
import fs5 from "node:fs";
|
|
2904
|
+
import os2 from "node:os";
|
|
2905
|
+
import path7 from "node:path";
|
|
2906
|
+
var REGENERABLE = /* @__PURE__ */ new Set(["generated", "cache", "runtime"]);
|
|
2907
|
+
var PLAN_ACTOR = { kind: "human", name: "confirm-session-plan" };
|
|
2908
|
+
var conflict = (id3, reason, more = []) => failure([
|
|
2909
|
+
createDiagnostic("DECISION_SESSION_CONFLICT", `${id3} cannot be confirmed in this session after the proposals before it: ${reason}. Nothing was confirmed; confirm it on its own.`),
|
|
2910
|
+
...more
|
|
2911
|
+
]);
|
|
2912
|
+
function sessionOrder(ids2) {
|
|
2913
|
+
return [...ids2].sort(compareUtf8);
|
|
2914
|
+
}
|
|
2915
|
+
async function planConfirmSession(options, reviewed) {
|
|
2916
|
+
const ids2 = reviewed.map((r) => r.id);
|
|
2917
|
+
if (ids2.length === 0)
|
|
2918
|
+
return failure([createDiagnostic("INVALID_ID", "No proposal was selected")]);
|
|
2919
|
+
if (new Set(ids2).size !== ids2.length)
|
|
2920
|
+
return failure([createDiagnostic("INVALID_ID", "A proposal was selected twice")]);
|
|
2921
|
+
const bad = ids2.find((id3) => !PROPOSAL_ID_PATTERN.test(id3));
|
|
2922
|
+
if (bad !== void 0)
|
|
2923
|
+
return failure([createDiagnostic("INVALID_ID", `"${bad}" is not a proposal ID; a session confirms proposals only`)]);
|
|
2924
|
+
const digestOf = new Map(reviewed.map((r) => [r.id, r.digest]));
|
|
2925
|
+
const order = sessionOrder(ids2);
|
|
2926
|
+
const live = createDecisionService({ root: options.root });
|
|
2927
|
+
for (const id3 of order) {
|
|
2928
|
+
const p = await live.previewConfirm(id3);
|
|
2929
|
+
if (p.value === void 0)
|
|
2930
|
+
return failure(p.diagnostics);
|
|
2931
|
+
if (p.value.sourceKind !== "proposal")
|
|
2932
|
+
return failure([createDiagnostic("INVALID_ID", `${id3} is not a proposal`)]);
|
|
2933
|
+
if (p.value.digest !== digestOf.get(id3)) {
|
|
2934
|
+
return failure([createDiagnostic("DECISION_CONFIRM_PREVIEW_CHANGED", "The Decision changed after you reviewed it. Review the current contents and confirm again.", { path: p.value.sourcePath })]);
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
const tmp = fs5.mkdtempSync(path7.join(os2.tmpdir(), "duo-confirm-plan-"));
|
|
2938
|
+
try {
|
|
2939
|
+
fs5.cpSync(path7.join(options.root, STATE_DIR_NAME), path7.join(tmp, STATE_DIR_NAME), {
|
|
2940
|
+
recursive: true,
|
|
2941
|
+
filter: (src) => !REGENERABLE.has(path7.relative(path7.join(options.root, STATE_DIR_NAME), src).split(path7.sep)[0] ?? "")
|
|
2942
|
+
});
|
|
2943
|
+
const sim = createDecisionService({ root: tmp, clock: () => /* @__PURE__ */ new Date(0) });
|
|
2944
|
+
const created = /* @__PURE__ */ new Set();
|
|
2945
|
+
const items = [];
|
|
2946
|
+
for (const id3 of order) {
|
|
2947
|
+
const p = await sim.previewConfirm(id3);
|
|
2948
|
+
if (p.value === void 0)
|
|
2949
|
+
return conflict(id3, "its candidate cannot be read", p.diagnostics);
|
|
2950
|
+
const target = p.value.candidate.supersedes;
|
|
2951
|
+
if (typeof target === "string" && created.has(target))
|
|
2952
|
+
return conflict(id3, `it supersedes ${target}, which this session creates`);
|
|
2953
|
+
const c = await sim.confirm(PLAN_ACTOR, id3, { expectedDigest: p.value.digest });
|
|
2954
|
+
if (c.value === void 0)
|
|
2955
|
+
return conflict(id3, "its confirm would fail", c.diagnostics);
|
|
2956
|
+
created.add(c.value.decisionId);
|
|
2957
|
+
items.push({ id: id3, preview: p.value, changedFromReview: p.value.digest !== digestOf.get(id3) });
|
|
2958
|
+
}
|
|
2959
|
+
return success({ items });
|
|
2960
|
+
} finally {
|
|
2961
|
+
fs5.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2896
2965
|
// packages/core/dist/index.js
|
|
2897
2966
|
var packageInfo = { name: "@duo-director/core", dependsOn: [] };
|
|
2898
2967
|
|
|
2899
2968
|
// packages/analyzer/dist/scan/scanner.js
|
|
2900
|
-
import
|
|
2901
|
-
import
|
|
2969
|
+
import fs6 from "node:fs";
|
|
2970
|
+
import path9 from "node:path";
|
|
2902
2971
|
|
|
2903
2972
|
// packages/analyzer/dist/git/work-tree.js
|
|
2904
|
-
import
|
|
2973
|
+
import path8 from "node:path";
|
|
2905
2974
|
|
|
2906
2975
|
// packages/analyzer/dist/git/exec.js
|
|
2907
2976
|
import { execFile } from "node:child_process";
|
|
@@ -2973,7 +3042,7 @@ async function workTreePrefix(rootDir) {
|
|
|
2973
3042
|
return result.ok ? { ok: true, value: result.value.replace(/\r?\n$/u, "") } : result;
|
|
2974
3043
|
}
|
|
2975
3044
|
function probeWorkTree(root) {
|
|
2976
|
-
const rootDir =
|
|
3045
|
+
const rootDir = path8.resolve(root);
|
|
2977
3046
|
let asked;
|
|
2978
3047
|
return { rootDir, prefix: () => asked ??= workTreePrefix(rootDir) };
|
|
2979
3048
|
}
|
|
@@ -2994,8 +3063,8 @@ async function listIndexEntries(root) {
|
|
|
2994
3063
|
const m = STAGE_RECORD.exec(record);
|
|
2995
3064
|
if (m === null)
|
|
2996
3065
|
return { ok: false, message: `unexpected git ls-files record: ${JSON.stringify(record)}` };
|
|
2997
|
-
const [, mode = "", oid = "", stage = "0",
|
|
2998
|
-
byPath.set(
|
|
3066
|
+
const [, mode = "", oid = "", stage = "0", path36 = ""] = m;
|
|
3067
|
+
byPath.set(path36, stage === "0" ? { path: path36, mode, oid } : { path: path36, mode });
|
|
2999
3068
|
}
|
|
3000
3069
|
return { ok: true, value: [...byPath.values()] };
|
|
3001
3070
|
}
|
|
@@ -3019,8 +3088,8 @@ function compileAll(patterns) {
|
|
|
3019
3088
|
});
|
|
3020
3089
|
}
|
|
3021
3090
|
var secretMatchers = compileAll(SECRET_FILE_PATTERNS);
|
|
3022
|
-
function isSecretFileName(
|
|
3023
|
-
const name =
|
|
3091
|
+
function isSecretFileName(path36) {
|
|
3092
|
+
const name = path36.slice(path36.lastIndexOf("/") + 1).toLowerCase();
|
|
3024
3093
|
return secretMatchers.some((m) => m(name));
|
|
3025
3094
|
}
|
|
3026
3095
|
function createPathPolicy(include = [], exclude = []) {
|
|
@@ -3029,20 +3098,20 @@ function createPathPolicy(include = [], exclude = []) {
|
|
|
3029
3098
|
const invalidPatterns = [...include, ...exclude].filter((p) => compileRepoPattern(p) === void 0);
|
|
3030
3099
|
return {
|
|
3031
3100
|
invalidPatterns,
|
|
3032
|
-
exclusionOf(
|
|
3033
|
-
if (
|
|
3101
|
+
exclusionOf(path36) {
|
|
3102
|
+
if (path36.split("/").some((segment) => segment.toLowerCase() === ".git"))
|
|
3034
3103
|
return "git-internal";
|
|
3035
|
-
if (DUO_REGENERABLE_PREFIXES.some((prefix) =>
|
|
3104
|
+
if (DUO_REGENERABLE_PREFIXES.some((prefix) => path36.startsWith(prefix)))
|
|
3036
3105
|
return "duo-regenerable";
|
|
3037
|
-
if (DUO_HISTORY_PREFIXES.some((prefix) =>
|
|
3106
|
+
if (DUO_HISTORY_PREFIXES.some((prefix) => path36.startsWith(prefix)))
|
|
3038
3107
|
return "duo-history";
|
|
3039
|
-
if (AGENT_INTEGRATION_FILES.includes(
|
|
3108
|
+
if (AGENT_INTEGRATION_FILES.includes(path36))
|
|
3040
3109
|
return "duo-agent-integration";
|
|
3041
|
-
if (isSecretFileName(
|
|
3110
|
+
if (isSecretFileName(path36))
|
|
3042
3111
|
return "secret";
|
|
3043
|
-
if (excludeMatchers.some((m) => m(
|
|
3112
|
+
if (excludeMatchers.some((m) => m(path36)))
|
|
3044
3113
|
return "index-exclude";
|
|
3045
|
-
if (include.length > 0 && !includeMatchers.some((m) => m(
|
|
3114
|
+
if (include.length > 0 && !includeMatchers.some((m) => m(path36)))
|
|
3046
3115
|
return "not-included";
|
|
3047
3116
|
return void 0;
|
|
3048
3117
|
}
|
|
@@ -3077,8 +3146,8 @@ function buildOutputMatcher(paths) {
|
|
|
3077
3146
|
cache.set(dir, v);
|
|
3078
3147
|
return v;
|
|
3079
3148
|
};
|
|
3080
|
-
return (
|
|
3081
|
-
const segments =
|
|
3149
|
+
return (path36) => {
|
|
3150
|
+
const segments = path36.split("/");
|
|
3082
3151
|
for (let i = 1; i < segments.length; i++)
|
|
3083
3152
|
if (isOutput(segments.slice(0, i).join("/")))
|
|
3084
3153
|
return true;
|
|
@@ -3093,7 +3162,7 @@ var REGULAR_MODES = /* @__PURE__ */ new Set(["100644", "100755"]);
|
|
|
3093
3162
|
var MAX_LINK_TEXT = 4096;
|
|
3094
3163
|
function entryKind(absolute) {
|
|
3095
3164
|
try {
|
|
3096
|
-
const stat =
|
|
3165
|
+
const stat = fs6.lstatSync(absolute);
|
|
3097
3166
|
if (stat.isSymbolicLink())
|
|
3098
3167
|
return "symlink";
|
|
3099
3168
|
if (stat.isFile())
|
|
@@ -3106,16 +3175,16 @@ function entryKind(absolute) {
|
|
|
3106
3175
|
function linkText(absolute, kind) {
|
|
3107
3176
|
try {
|
|
3108
3177
|
if (kind === "symlink")
|
|
3109
|
-
return
|
|
3178
|
+
return fs6.readlinkSync(absolute, "utf8");
|
|
3110
3179
|
if (kind !== "file")
|
|
3111
3180
|
return void 0;
|
|
3112
|
-
const fd =
|
|
3181
|
+
const fd = fs6.openSync(absolute, "r");
|
|
3113
3182
|
try {
|
|
3114
3183
|
const buffer = Buffer.alloc(MAX_LINK_TEXT);
|
|
3115
|
-
const n =
|
|
3184
|
+
const n = fs6.readSync(fd, buffer, 0, MAX_LINK_TEXT, 0);
|
|
3116
3185
|
return buffer.subarray(0, n).toString("utf8");
|
|
3117
3186
|
} finally {
|
|
3118
|
-
|
|
3187
|
+
fs6.closeSync(fd);
|
|
3119
3188
|
}
|
|
3120
3189
|
} catch {
|
|
3121
3190
|
return void 0;
|
|
@@ -3124,15 +3193,15 @@ function linkText(absolute, kind) {
|
|
|
3124
3193
|
function pointsOutside(root, link, target) {
|
|
3125
3194
|
if (target === void 0)
|
|
3126
3195
|
return true;
|
|
3127
|
-
const resolved =
|
|
3128
|
-
const relative =
|
|
3129
|
-
return relative === ".." || relative.startsWith(`..${
|
|
3196
|
+
const resolved = path9.resolve(root, path9.dirname(link), target);
|
|
3197
|
+
const relative = path9.relative(root, resolved);
|
|
3198
|
+
return relative === ".." || relative.startsWith(`..${path9.sep}`) || path9.isAbsolute(relative);
|
|
3130
3199
|
}
|
|
3131
3200
|
function symlinkDiagnostic(root, link, target) {
|
|
3132
3201
|
return pointsOutside(root, link, target) ? createDiagnostic("SYMLINK_OUTSIDE_REPOSITORY", `Symlink "${link}" points outside the repository; its target is not read`, { path: link }) : createDiagnostic("SYMLINK_SKIPPED", `Symlink "${link}" is not followed`, { path: link });
|
|
3133
3202
|
}
|
|
3134
3203
|
async function scanRepository(root, options = {}) {
|
|
3135
|
-
const rootDir =
|
|
3204
|
+
const rootDir = path9.resolve(root);
|
|
3136
3205
|
const fail = (d) => ({ files: [], excluded: [], typeChanges: [], diagnostics: [d] });
|
|
3137
3206
|
const prefix = await prefixFor(rootDir, options.workTree);
|
|
3138
3207
|
if (!prefix.ok) {
|
|
@@ -3190,12 +3259,12 @@ async function scanRepository(root, options = {}) {
|
|
|
3190
3259
|
if (reportedSymlinks.has(link))
|
|
3191
3260
|
return;
|
|
3192
3261
|
reportedSymlinks.add(link);
|
|
3193
|
-
diagnostics.push(symlinkDiagnostic(rootDir, link, linkText(
|
|
3262
|
+
diagnostics.push(symlinkDiagnostic(rootDir, link, linkText(path9.join(rootDir, link), kind)));
|
|
3194
3263
|
};
|
|
3195
3264
|
const directoryKind = (dir) => {
|
|
3196
3265
|
let kind = directories.get(dir);
|
|
3197
3266
|
if (kind === void 0) {
|
|
3198
|
-
kind = entryKind(
|
|
3267
|
+
kind = entryKind(path9.join(rootDir, dir));
|
|
3199
3268
|
directories.set(dir, kind);
|
|
3200
3269
|
}
|
|
3201
3270
|
return kind;
|
|
@@ -3224,7 +3293,7 @@ async function scanRepository(root, options = {}) {
|
|
|
3224
3293
|
}
|
|
3225
3294
|
continue;
|
|
3226
3295
|
}
|
|
3227
|
-
const absolute =
|
|
3296
|
+
const absolute = path9.join(rootDir, candidate.path);
|
|
3228
3297
|
const kind = entryKind(absolute);
|
|
3229
3298
|
const indexMode = candidate.index?.mode;
|
|
3230
3299
|
const symlinkReplacedByFile = indexMode === MODE_SYMLINK && kind === "file" && gitTypeChanged.has(candidate.path);
|
|
@@ -3391,8 +3460,8 @@ var NORMALIZED_TEXT_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
|
3391
3460
|
"vagrantfile",
|
|
3392
3461
|
"jenkinsfile"
|
|
3393
3462
|
]);
|
|
3394
|
-
function fingerprintModeOf(
|
|
3395
|
-
const name =
|
|
3463
|
+
function fingerprintModeOf(path36) {
|
|
3464
|
+
const name = path36.slice(path36.lastIndexOf("/") + 1).toLowerCase();
|
|
3396
3465
|
if (NORMALIZED_TEXT_FILE_NAMES.has(name))
|
|
3397
3466
|
return "normalized-text";
|
|
3398
3467
|
const dot = name.lastIndexOf(".");
|
|
@@ -3426,18 +3495,18 @@ function computeContentHash(bytes, mode) {
|
|
|
3426
3495
|
}
|
|
3427
3496
|
|
|
3428
3497
|
// packages/analyzer/dist/fingerprint/fingerprint.js
|
|
3429
|
-
import
|
|
3430
|
-
import
|
|
3498
|
+
import fs7 from "node:fs/promises";
|
|
3499
|
+
import path10 from "node:path";
|
|
3431
3500
|
async function fingerprintOne(root, file) {
|
|
3432
|
-
const absolute =
|
|
3501
|
+
const absolute = path10.join(root, file.path);
|
|
3433
3502
|
try {
|
|
3434
|
-
const stat = await
|
|
3503
|
+
const stat = await fs7.lstat(absolute);
|
|
3435
3504
|
if (stat.isSymbolicLink())
|
|
3436
3505
|
return createDiagnostic("SYMLINK_SKIPPED", `Symlink "${file.path}" is not followed`, { path: file.path });
|
|
3437
3506
|
if (!stat.isFile())
|
|
3438
3507
|
return createDiagnostic("SCAN_ENTRY_SKIPPED", `"${file.path}" is not a regular file`, { path: file.path });
|
|
3439
3508
|
const fingerprintMode = fingerprintModeOf(file.path);
|
|
3440
|
-
const { contentHash, size } = computeContentHash(await
|
|
3509
|
+
const { contentHash, size } = computeContentHash(await fs7.readFile(absolute), fingerprintMode);
|
|
3441
3510
|
const base = { path: file.path, state: file.state, fingerprintMode, contentHash, size };
|
|
3442
3511
|
return file.gitBlobOid === void 0 ? base : { ...base, gitBlobOid: file.gitBlobOid };
|
|
3443
3512
|
} catch (error) {
|
|
@@ -3445,7 +3514,7 @@ async function fingerprintOne(root, file) {
|
|
|
3445
3514
|
}
|
|
3446
3515
|
}
|
|
3447
3516
|
async function fingerprintRepositoryFiles(root, files, options = {}) {
|
|
3448
|
-
const rootDir =
|
|
3517
|
+
const rootDir = path10.resolve(root);
|
|
3449
3518
|
const results = new Array(files.length);
|
|
3450
3519
|
let next2 = 0;
|
|
3451
3520
|
const worker = async () => {
|
|
@@ -3471,8 +3540,8 @@ async function fingerprintRepositoryFiles(root, files, options = {}) {
|
|
|
3471
3540
|
}
|
|
3472
3541
|
|
|
3473
3542
|
// packages/analyzer/dist/fingerprint/store.js
|
|
3474
|
-
import
|
|
3475
|
-
import
|
|
3543
|
+
import fs8 from "node:fs";
|
|
3544
|
+
import path11 from "node:path";
|
|
3476
3545
|
var FINGERPRINT_FILE_PATH = `${STATE_DIR_NAME}/generated/fingerprints.json`;
|
|
3477
3546
|
var FINGERPRINT_FORMAT = "duo-fingerprints";
|
|
3478
3547
|
var FINGERPRINT_FORMAT_VERSION = 3;
|
|
@@ -3549,7 +3618,7 @@ function symlinkOnPath2(root, repoPath3) {
|
|
|
3549
3618
|
for (let i = 1; i <= segments.length; i++) {
|
|
3550
3619
|
const partial = segments.slice(0, i).join("/");
|
|
3551
3620
|
try {
|
|
3552
|
-
if (
|
|
3621
|
+
if (fs8.lstatSync(path11.join(root, partial)).isSymbolicLink())
|
|
3553
3622
|
return partial;
|
|
3554
3623
|
} catch {
|
|
3555
3624
|
return void 0;
|
|
@@ -3561,12 +3630,12 @@ function asEmpty(result) {
|
|
|
3561
3630
|
return success([], result.diagnostics);
|
|
3562
3631
|
}
|
|
3563
3632
|
function readFingerprintFile(root) {
|
|
3564
|
-
const rootDir =
|
|
3633
|
+
const rootDir = path11.resolve(root);
|
|
3565
3634
|
if (symlinkOnPath2(rootDir, FINGERPRINT_FILE_PATH) !== void 0)
|
|
3566
3635
|
return asEmpty(invalid("the path contains a symlink"));
|
|
3567
3636
|
let text4;
|
|
3568
3637
|
try {
|
|
3569
|
-
text4 =
|
|
3638
|
+
text4 = fs8.readFileSync(path11.join(rootDir, FINGERPRINT_FILE_PATH), "utf8");
|
|
3570
3639
|
} catch (error) {
|
|
3571
3640
|
if (error.code === "ENOENT")
|
|
3572
3641
|
return success([]);
|
|
@@ -3576,7 +3645,7 @@ function readFingerprintFile(root) {
|
|
|
3576
3645
|
return parsed.value === void 0 ? asEmpty(parsed) : parsed;
|
|
3577
3646
|
}
|
|
3578
3647
|
function writeFingerprintFile(root, fingerprints) {
|
|
3579
|
-
const rootDir =
|
|
3648
|
+
const rootDir = path11.resolve(root);
|
|
3580
3649
|
const allowed = checkWriteBoundary(rootDir, FINGERPRINT_FILE_PATH, "regenerable");
|
|
3581
3650
|
if (allowed.value === void 0)
|
|
3582
3651
|
return failure(allowed.diagnostics);
|
|
@@ -3585,14 +3654,14 @@ function writeFingerprintFile(root, fingerprints) {
|
|
|
3585
3654
|
if (link !== void 0) {
|
|
3586
3655
|
return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${target}": "${link}" is a symlink`, { path: target })]);
|
|
3587
3656
|
}
|
|
3588
|
-
const absolute =
|
|
3657
|
+
const absolute = path11.join(rootDir, target);
|
|
3589
3658
|
const temp = `${absolute}.${process.pid}.tmp`;
|
|
3590
3659
|
try {
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3660
|
+
fs8.mkdirSync(path11.dirname(absolute), { recursive: true });
|
|
3661
|
+
fs8.writeFileSync(temp, serializeFingerprints(fingerprints), "utf8");
|
|
3662
|
+
fs8.renameSync(temp, absolute);
|
|
3594
3663
|
} catch (error) {
|
|
3595
|
-
|
|
3664
|
+
fs8.rmSync(temp, { force: true });
|
|
3596
3665
|
return failure([createDiagnostic("FILE_WRITE_ERROR", `Cannot write "${target}": ${error.message}`, { path: target })]);
|
|
3597
3666
|
}
|
|
3598
3667
|
return success(target);
|
|
@@ -3620,8 +3689,8 @@ function compareFingerprints(previous, current) {
|
|
|
3620
3689
|
}
|
|
3621
3690
|
|
|
3622
3691
|
// packages/analyzer/dist/git/provider.js
|
|
3623
|
-
import
|
|
3624
|
-
import
|
|
3692
|
+
import fs9 from "node:fs";
|
|
3693
|
+
import path12 from "node:path";
|
|
3625
3694
|
|
|
3626
3695
|
// packages/analyzer/dist/git/parse.js
|
|
3627
3696
|
var GitOutputError = class extends Error {
|
|
@@ -3652,19 +3721,19 @@ var ORDINARY = /^1 (.)(.) (\S{4}) (\d{6}) (\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+
|
|
|
3652
3721
|
var RENAMED = /^2 (.)(.) (\S{4}) (\d{6}) (\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) ([RC])(\d+) (.*)$/su;
|
|
3653
3722
|
var UNMERGED = /^u (..) (\S{4}) (\d{6}) (\d{6}) (\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) ([0-9a-f]+) (.*)$/su;
|
|
3654
3723
|
function parseStatusV2(out, toPath) {
|
|
3655
|
-
const
|
|
3724
|
+
const tokens3 = out.split("\0");
|
|
3656
3725
|
let oid;
|
|
3657
3726
|
let head;
|
|
3658
3727
|
const changes = [];
|
|
3659
3728
|
const skipped2 = [];
|
|
3660
|
-
const
|
|
3729
|
+
const path36 = (raw) => {
|
|
3661
3730
|
const p = toPath(raw);
|
|
3662
3731
|
if (p === void 0)
|
|
3663
3732
|
skipped2.push(raw);
|
|
3664
3733
|
return p;
|
|
3665
3734
|
};
|
|
3666
|
-
for (let i = 0; i <
|
|
3667
|
-
const t2 =
|
|
3735
|
+
for (let i = 0; i < tokens3.length; i++) {
|
|
3736
|
+
const t2 = tokens3[i] ?? "";
|
|
3668
3737
|
if (t2 === "")
|
|
3669
3738
|
continue;
|
|
3670
3739
|
if (t2.startsWith("# branch.oid ")) {
|
|
@@ -3684,7 +3753,7 @@ function parseStatusV2(out, toPath) {
|
|
|
3684
3753
|
const nested = raw.endsWith("/");
|
|
3685
3754
|
if (nested)
|
|
3686
3755
|
raw = raw.slice(0, -1);
|
|
3687
|
-
const p =
|
|
3756
|
+
const p = path36(raw);
|
|
3688
3757
|
if (p !== void 0)
|
|
3689
3758
|
changes.push({ path: p, unstaged: "untracked", submodule: nested });
|
|
3690
3759
|
continue;
|
|
@@ -3692,7 +3761,7 @@ function parseStatusV2(out, toPath) {
|
|
|
3692
3761
|
let m = ORDINARY.exec(t2);
|
|
3693
3762
|
if (m !== null) {
|
|
3694
3763
|
const [, x = ".", y = ".", sub = "N...", mH = "", mI = "", mW = "", hH = "", hI = "", raw = ""] = m;
|
|
3695
|
-
const p =
|
|
3764
|
+
const p = path36(raw);
|
|
3696
3765
|
if (p !== void 0) {
|
|
3697
3766
|
changes.push({
|
|
3698
3767
|
path: p,
|
|
@@ -3711,11 +3780,11 @@ function parseStatusV2(out, toPath) {
|
|
|
3711
3780
|
m = RENAMED.exec(t2);
|
|
3712
3781
|
if (m !== null) {
|
|
3713
3782
|
const [, x = ".", y = ".", sub = "N...", mH = "", mI = "", mW = "", hH = "", hI = "", , score = "0", raw = ""] = m;
|
|
3714
|
-
const origRaw =
|
|
3783
|
+
const origRaw = tokens3[++i];
|
|
3715
3784
|
if (origRaw === void 0)
|
|
3716
3785
|
throw new GitOutputError("rename record without original path");
|
|
3717
|
-
const p =
|
|
3718
|
-
const orig =
|
|
3786
|
+
const p = path36(raw);
|
|
3787
|
+
const orig = path36(origRaw);
|
|
3719
3788
|
if (p !== void 0 && orig !== void 0) {
|
|
3720
3789
|
changes.push({
|
|
3721
3790
|
path: p,
|
|
@@ -3736,7 +3805,7 @@ function parseStatusV2(out, toPath) {
|
|
|
3736
3805
|
m = UNMERGED.exec(t2);
|
|
3737
3806
|
if (m !== null) {
|
|
3738
3807
|
const [, xy = "UU", sub = "N...", , , , mW = "", , , , raw = ""] = m;
|
|
3739
|
-
const p =
|
|
3808
|
+
const p = path36(raw);
|
|
3740
3809
|
if (p !== void 0) {
|
|
3741
3810
|
changes.push({ path: p, staged: "unmerged", unstaged: "unmerged", conflict: xy, submodule: sub.startsWith("S"), ...optional("worktreeMode", modeOf(mW)) });
|
|
3742
3811
|
}
|
|
@@ -3810,8 +3879,8 @@ function parsePatchBlock(block) {
|
|
|
3810
3879
|
for (const line of lines4) {
|
|
3811
3880
|
const h = HUNK.exec(line);
|
|
3812
3881
|
if (h !== null) {
|
|
3813
|
-
const [,
|
|
3814
|
-
current = { oldStart: Number(
|
|
3882
|
+
const [, os3 = "0", ol, ns = "0", nl, section = ""] = h;
|
|
3883
|
+
current = { oldStart: Number(os3), oldLines: ol === void 0 ? 1 : Number(ol), newStart: Number(ns), newLines: nl === void 0 ? 1 : Number(nl), section, lines: [] };
|
|
3815
3884
|
hunks.push(current);
|
|
3816
3885
|
continue;
|
|
3817
3886
|
}
|
|
@@ -3857,18 +3926,18 @@ function parseLsFilesStage(out, toPath) {
|
|
|
3857
3926
|
return result;
|
|
3858
3927
|
}
|
|
3859
3928
|
function parseLogMessages(out) {
|
|
3860
|
-
const
|
|
3861
|
-
if (
|
|
3862
|
-
|
|
3863
|
-
if (
|
|
3929
|
+
const tokens3 = out.split("\0");
|
|
3930
|
+
if (tokens3.at(-1) === "")
|
|
3931
|
+
tokens3.pop();
|
|
3932
|
+
if (tokens3.length % 3 !== 0)
|
|
3864
3933
|
throw new GitOutputError("unexpected git log record count");
|
|
3865
3934
|
const commits = [];
|
|
3866
|
-
for (let i = 0; i <
|
|
3867
|
-
const oid =
|
|
3935
|
+
for (let i = 0; i < tokens3.length; i += 3) {
|
|
3936
|
+
const oid = tokens3[i] ?? "";
|
|
3868
3937
|
if (!/^[0-9a-f]+$/u.test(oid))
|
|
3869
3938
|
throw new GitOutputError(`unexpected commit id ${JSON.stringify(oid)}`);
|
|
3870
|
-
const parents = (
|
|
3871
|
-
commits.push({ oid, parents, message: (
|
|
3939
|
+
const parents = (tokens3[i + 1] ?? "").split(" ").filter((p) => p !== "");
|
|
3940
|
+
commits.push({ oid, parents, message: (tokens3[i + 2] ?? "").replace(/\n$/u, "") });
|
|
3872
3941
|
}
|
|
3873
3942
|
return commits;
|
|
3874
3943
|
}
|
|
@@ -4096,7 +4165,7 @@ var Provider = class {
|
|
|
4096
4165
|
let newSize = d.newOid === void 0 ? void 0 : sizes.get(d.newOid);
|
|
4097
4166
|
if (newSize === void 0 && to === "WORKTREE" && d.kind !== "deleted") {
|
|
4098
4167
|
try {
|
|
4099
|
-
newSize =
|
|
4168
|
+
newSize = fs9.lstatSync(path12.join(this.root, d.path)).size;
|
|
4100
4169
|
} catch {
|
|
4101
4170
|
newSize = void 0;
|
|
4102
4171
|
}
|
|
@@ -4171,7 +4240,7 @@ var Provider = class {
|
|
|
4171
4240
|
}
|
|
4172
4241
|
};
|
|
4173
4242
|
async function openGitProvider(root, options = {}) {
|
|
4174
|
-
const rootDir =
|
|
4243
|
+
const rootDir = path12.resolve(root);
|
|
4175
4244
|
const r = await prefixFor(rootDir, options.workTree);
|
|
4176
4245
|
if (!r.ok) {
|
|
4177
4246
|
return failure([r.gitMissing === true ? createDiagnostic("GIT_COMMAND_FAILED", r.message) : createDiagnostic("GIT_REPOSITORY_REQUIRED", `"${rootDir}" is not a Git work tree; DUO requires a Git repository: ${r.message}`)]);
|
|
@@ -4183,20 +4252,20 @@ async function openGitProvider(root, options = {}) {
|
|
|
4183
4252
|
}
|
|
4184
4253
|
|
|
4185
4254
|
// packages/analyzer/dist/git/identity.js
|
|
4186
|
-
import
|
|
4255
|
+
import path13 from "node:path";
|
|
4187
4256
|
async function readGitUserName(root) {
|
|
4188
|
-
const r = await runGit(
|
|
4257
|
+
const r = await runGit(path13.resolve(root), ["config", "--get", "user.name"]);
|
|
4189
4258
|
if (!r.ok)
|
|
4190
4259
|
return void 0;
|
|
4191
4260
|
const name = r.value.trim();
|
|
4192
4261
|
return name === "" ? void 0 : name;
|
|
4193
4262
|
}
|
|
4194
4263
|
async function findGitTopLevel(dir) {
|
|
4195
|
-
const r = await runGit(
|
|
4264
|
+
const r = await runGit(path13.resolve(dir), ["rev-parse", "--show-toplevel"]);
|
|
4196
4265
|
if (!r.ok)
|
|
4197
4266
|
return void 0;
|
|
4198
4267
|
const top = r.value.replace(/\r?\n$/u, "");
|
|
4199
|
-
return top === "" ? void 0 :
|
|
4268
|
+
return top === "" ? void 0 : path13.resolve(top);
|
|
4200
4269
|
}
|
|
4201
4270
|
|
|
4202
4271
|
// packages/analyzer/dist/git/history.js
|
|
@@ -4249,12 +4318,12 @@ import { createHash as createHash5 } from "node:crypto";
|
|
|
4249
4318
|
var ANALYZER_SELECTION_VERSION = "1";
|
|
4250
4319
|
var CPP_SOURCES = /* @__PURE__ */ new Set(["cpp", "cc", "cxx"]);
|
|
4251
4320
|
var CPP_FILES = /* @__PURE__ */ new Set(["cpp", "cc", "cxx", "hpp", "hh", "hxx"]);
|
|
4252
|
-
function extensionOf(
|
|
4253
|
-
const name =
|
|
4321
|
+
function extensionOf(path36) {
|
|
4322
|
+
const name = path36.slice(path36.lastIndexOf("/") + 1);
|
|
4254
4323
|
const dot = name.lastIndexOf(".");
|
|
4255
4324
|
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
4256
4325
|
}
|
|
4257
|
-
var stemOf = (
|
|
4326
|
+
var stemOf = (path36) => path36.slice(0, path36.length - extensionOf(path36).length - 1);
|
|
4258
4327
|
function headerContext(paths) {
|
|
4259
4328
|
const exts = paths.map(extensionOf);
|
|
4260
4329
|
return {
|
|
@@ -4263,8 +4332,8 @@ function headerContext(paths) {
|
|
|
4263
4332
|
cppStems: new Set(paths.filter((p) => CPP_SOURCES.has(extensionOf(p))).map(stemOf))
|
|
4264
4333
|
};
|
|
4265
4334
|
}
|
|
4266
|
-
function isCppHeader(
|
|
4267
|
-
return extensionOf(
|
|
4335
|
+
function isCppHeader(path36, context) {
|
|
4336
|
+
return extensionOf(path36) === "h" && (context.uproject || context.cppProject || context.cppStems.has(stemOf(path36)));
|
|
4268
4337
|
}
|
|
4269
4338
|
function createAnalyzerRegistry(analyzers) {
|
|
4270
4339
|
const ids2 = /* @__PURE__ */ new Set();
|
|
@@ -4274,7 +4343,7 @@ function createAnalyzerRegistry(analyzers) {
|
|
|
4274
4343
|
ids2.add(a.id);
|
|
4275
4344
|
}
|
|
4276
4345
|
const list3 = [...analyzers];
|
|
4277
|
-
const analyzerFor = (
|
|
4346
|
+
const analyzerFor = (path36) => list3.find((a) => a.supports(path36));
|
|
4278
4347
|
const identities = Object.fromEntries(list3.map((a) => [a.id, a.identity]).sort(([x], [y]) => x < y ? -1 : 1));
|
|
4279
4348
|
const digest = `sha256:${createHash5("sha256").update([`selection=${ANALYZER_SELECTION_VERSION}`, ...Object.entries(identities).map(([k, v]) => `${k}=${v}`)].join("\n")).digest("hex")}`;
|
|
4280
4349
|
return {
|
|
@@ -4282,21 +4351,21 @@ function createAnalyzerRegistry(analyzers) {
|
|
|
4282
4351
|
analyzerFor,
|
|
4283
4352
|
scope(paths) {
|
|
4284
4353
|
const context = headerContext(paths);
|
|
4285
|
-
const selectFor = (
|
|
4286
|
-
const own = analyzerFor(
|
|
4354
|
+
const selectFor = (path36) => {
|
|
4355
|
+
const own = analyzerFor(path36);
|
|
4287
4356
|
if (own !== void 0)
|
|
4288
4357
|
return own;
|
|
4289
|
-
const ext = extensionOf(
|
|
4358
|
+
const ext = extensionOf(path36);
|
|
4290
4359
|
const contextual = list3.find((a) => a.contextualExtensions?.includes(ext));
|
|
4291
4360
|
if (contextual === void 0 || ext !== "h")
|
|
4292
4361
|
return void 0;
|
|
4293
|
-
return isCppHeader(
|
|
4362
|
+
return isCppHeader(path36, context) ? contextual : void 0;
|
|
4294
4363
|
};
|
|
4295
4364
|
return {
|
|
4296
4365
|
analyzerFor: selectFor,
|
|
4297
|
-
capabilitiesFor: (
|
|
4298
|
-
languageFor(
|
|
4299
|
-
const a = selectFor(
|
|
4366
|
+
capabilitiesFor: (path36) => selectFor(path36)?.capabilities ?? FILE_ONLY_CAPABILITIES,
|
|
4367
|
+
languageFor(path36) {
|
|
4368
|
+
const a = selectFor(path36);
|
|
4300
4369
|
if (a === void 0)
|
|
4301
4370
|
return void 0;
|
|
4302
4371
|
return a.languages.length === 1 ? a.languages[0] : void 0;
|
|
@@ -4325,7 +4394,7 @@ import { createHash as createHash7 } from "node:crypto";
|
|
|
4325
4394
|
// packages/analyzer/dist/language/tree-sitter/runtime.js
|
|
4326
4395
|
import { createRequire } from "node:module";
|
|
4327
4396
|
import { createHash as createHash6 } from "node:crypto";
|
|
4328
|
-
import
|
|
4397
|
+
import fs10 from "node:fs";
|
|
4329
4398
|
import { fileURLToPath } from "node:url";
|
|
4330
4399
|
import { Language, LANGUAGE_VERSION, MIN_COMPATIBLE_VERSION, Parser } from "web-tree-sitter";
|
|
4331
4400
|
var GRAMMAR_FILES = {
|
|
@@ -4341,7 +4410,7 @@ var requireFromHere = createRequire(import.meta.url);
|
|
|
4341
4410
|
var fileName = (id3) => GRAMMAR_FILES[id3].slice(GRAMMAR_FILES[id3].lastIndexOf("/") + 1);
|
|
4342
4411
|
var defaultGrammarLocator = (id3) => {
|
|
4343
4412
|
const packaged = fileURLToPath(new URL(`./grammars/${fileName(id3)}`, import.meta.url));
|
|
4344
|
-
if (
|
|
4413
|
+
if (fs10.existsSync(packaged))
|
|
4345
4414
|
return packaged;
|
|
4346
4415
|
try {
|
|
4347
4416
|
return requireFromHere.resolve(GRAMMAR_FILES[id3]);
|
|
@@ -4371,7 +4440,7 @@ async function loadGrammarSet(ids2, locate = defaultGrammarLocator) {
|
|
|
4371
4440
|
let source;
|
|
4372
4441
|
try {
|
|
4373
4442
|
const located = locate(id3);
|
|
4374
|
-
source = typeof located === "string" ? new Uint8Array(
|
|
4443
|
+
source = typeof located === "string" ? new Uint8Array(fs10.readFileSync(located)) : located;
|
|
4375
4444
|
} catch (error) {
|
|
4376
4445
|
diagnostics.push(createDiagnostic("ANALYZER_INIT_FAILED", `Grammar "${id3}" (${GRAMMAR_FILES[id3]}) not found: ${message(error)}`));
|
|
4377
4446
|
continue;
|
|
@@ -4400,8 +4469,8 @@ function createParser(language) {
|
|
|
4400
4469
|
// packages/analyzer/dist/language/tree-sitter/analyzer-base.js
|
|
4401
4470
|
var DEFAULT_PARSE_TIMEOUT_MS = 2e3;
|
|
4402
4471
|
var decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
4403
|
-
function extensionOf2(
|
|
4404
|
-
const name =
|
|
4472
|
+
function extensionOf2(path36) {
|
|
4473
|
+
const name = path36.slice(path36.lastIndexOf("/") + 1);
|
|
4405
4474
|
const dot = name.lastIndexOf(".");
|
|
4406
4475
|
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
4407
4476
|
}
|
|
@@ -4447,25 +4516,25 @@ var TreeSitterAnalyzer = class {
|
|
|
4447
4516
|
this.capabilities = spec2.capabilities;
|
|
4448
4517
|
this.callResolution = spec2.callResolution;
|
|
4449
4518
|
}
|
|
4450
|
-
grammarOf(
|
|
4451
|
-
const ext = extensionOf2(
|
|
4519
|
+
grammarOf(path36) {
|
|
4520
|
+
const ext = extensionOf2(path36);
|
|
4452
4521
|
if (Object.hasOwn(this.spec.extensions, ext))
|
|
4453
4522
|
return this.spec.extensions[ext];
|
|
4454
4523
|
return this.spec.contextual !== void 0 && Object.hasOwn(this.spec.contextual, ext) ? this.spec.contextual[ext] : void 0;
|
|
4455
4524
|
}
|
|
4456
|
-
supports(
|
|
4457
|
-
return Object.hasOwn(this.spec.extensions, extensionOf2(
|
|
4525
|
+
supports(path36) {
|
|
4526
|
+
return Object.hasOwn(this.spec.extensions, extensionOf2(path36));
|
|
4458
4527
|
}
|
|
4459
4528
|
analyze(input) {
|
|
4460
4529
|
if (this.disposed)
|
|
4461
4530
|
throw new Error(`LanguageAnalyzer "${this.id}" was disposed`);
|
|
4462
|
-
const { path:
|
|
4463
|
-
const grammar = this.grammarOf(
|
|
4531
|
+
const { path: path36 } = input;
|
|
4532
|
+
const grammar = this.grammarOf(path36);
|
|
4464
4533
|
const parser = grammar === void 0 ? void 0 : this.parsers.get(grammar);
|
|
4465
4534
|
if (grammar === void 0 || parser === void 0) {
|
|
4466
|
-
return failure([createDiagnostic("LANGUAGE_UNSUPPORTED", `"${
|
|
4535
|
+
return failure([createDiagnostic("LANGUAGE_UNSUPPORTED", `"${path36}" is not a ${this.id} source file`, { path: path36 })]);
|
|
4467
4536
|
}
|
|
4468
|
-
const mode = fingerprintModeOf(
|
|
4537
|
+
const mode = fingerprintModeOf(path36);
|
|
4469
4538
|
const { contentHash } = computeContentHash(input.content, mode);
|
|
4470
4539
|
let text4;
|
|
4471
4540
|
try {
|
|
@@ -4473,7 +4542,7 @@ var TreeSitterAnalyzer = class {
|
|
|
4473
4542
|
if (text4.charCodeAt(0) === 65279)
|
|
4474
4543
|
text4 = text4.slice(1);
|
|
4475
4544
|
} catch {
|
|
4476
|
-
return failure([createDiagnostic("SOURCE_DECODE_ERROR", `"${
|
|
4545
|
+
return failure([createDiagnostic("SOURCE_DECODE_ERROR", `"${path36}" is not valid UTF-8`, { path: path36 })]);
|
|
4477
4546
|
}
|
|
4478
4547
|
const parsed = this.spec.prepare === void 0 ? text4 : this.spec.prepare(text4);
|
|
4479
4548
|
if (parsed.length !== text4.length)
|
|
@@ -4482,12 +4551,12 @@ var TreeSitterAnalyzer = class {
|
|
|
4482
4551
|
const tree = parser.parse(parsed, null, { progressCallback: () => performance.now() > deadline });
|
|
4483
4552
|
if (tree === null) {
|
|
4484
4553
|
parser.reset();
|
|
4485
|
-
return failure([createDiagnostic("AST_PARSE_TIMEOUT", `Parsing "${
|
|
4554
|
+
return failure([createDiagnostic("AST_PARSE_TIMEOUT", `Parsing "${path36}" took longer than ${this.parseTimeoutMs} ms`, { path: path36 })]);
|
|
4486
4555
|
}
|
|
4487
4556
|
try {
|
|
4488
|
-
const x = this.spec.extract(
|
|
4557
|
+
const x = this.spec.extract(path36, tree, text4);
|
|
4489
4558
|
return success({
|
|
4490
|
-
path:
|
|
4559
|
+
path: path36,
|
|
4491
4560
|
language: grammar,
|
|
4492
4561
|
contentHash,
|
|
4493
4562
|
parseStatus: x.partial ? "partial" : "complete",
|
|
@@ -4520,7 +4589,7 @@ async function createTreeSitterAnalyzer(spec2, options = {}) {
|
|
|
4520
4589
|
}
|
|
4521
4590
|
|
|
4522
4591
|
// packages/analyzer/dist/language/annotations.js
|
|
4523
|
-
function parseDuoAnnotations(
|
|
4592
|
+
function parseDuoAnnotations(path36, comment, startLine, startColumn) {
|
|
4524
4593
|
const annotations = [];
|
|
4525
4594
|
const diagnostics = [];
|
|
4526
4595
|
const block = comment.startsWith("/*");
|
|
@@ -4550,10 +4619,10 @@ function parseDuoAnnotations(path35, comment, startLine, startColumn) {
|
|
|
4550
4619
|
text4 = text4.trimEnd();
|
|
4551
4620
|
const index = offset2 + (body.length - content.length);
|
|
4552
4621
|
const column = k === 0 ? startColumn + index : index + 1;
|
|
4553
|
-
const location4 = { path:
|
|
4554
|
-
const
|
|
4555
|
-
const end2 =
|
|
4556
|
-
const ids2 = [...new Set(end2 === -1 ?
|
|
4622
|
+
const location4 = { path: path36, startLine: startLine + k, startColumn: column, endLine: startLine + k, endColumn: column + text4.length };
|
|
4623
|
+
const tokens3 = text4.slice("duo:".length).split(/[\s,]+/).filter((t2) => t2 !== "");
|
|
4624
|
+
const end2 = tokens3.findIndex((t2) => !isDefinitionId(t2));
|
|
4625
|
+
const ids2 = [...new Set(end2 === -1 ? tokens3 : tokens3.slice(0, end2))];
|
|
4557
4626
|
if (ids2.length === 0) {
|
|
4558
4627
|
diagnostics.push(createDiagnostic("DUO_ANNOTATION_INVALID", `"duo:" comment does not start with a definition ID: ${JSON.stringify(text4)}`, location4));
|
|
4559
4628
|
return;
|
|
@@ -4565,9 +4634,9 @@ function parseDuoAnnotations(path35, comment, startLine, startColumn) {
|
|
|
4565
4634
|
|
|
4566
4635
|
// packages/analyzer/dist/language/tree-sitter/syntax.js
|
|
4567
4636
|
var MAX_PARSE_DIAGNOSTICS = 20;
|
|
4568
|
-
function location(
|
|
4637
|
+
function location(path36, node) {
|
|
4569
4638
|
return {
|
|
4570
|
-
path:
|
|
4639
|
+
path: path36,
|
|
4571
4640
|
startLine: node.startPosition.row + 1,
|
|
4572
4641
|
startColumn: node.startPosition.column + 1,
|
|
4573
4642
|
endLine: node.endPosition.row + 1,
|
|
@@ -4595,9 +4664,9 @@ function parseErrors(tree) {
|
|
|
4595
4664
|
visit2(tree.rootNode);
|
|
4596
4665
|
return out;
|
|
4597
4666
|
}
|
|
4598
|
-
function reportParseErrors(
|
|
4667
|
+
function reportParseErrors(path36, tree, errors) {
|
|
4599
4668
|
const diagnostics = [];
|
|
4600
|
-
const sorted = errors.map((e) => ({ ...e, location: location(
|
|
4669
|
+
const sorted = errors.map((e) => ({ ...e, location: location(path36, e.node) })).sort((a, b) => compareSourceLocations(a.location, b.location));
|
|
4601
4670
|
for (const e of sorted.slice(0, MAX_PARSE_DIAGNOSTICS)) {
|
|
4602
4671
|
diagnostics.push(createDiagnostic("AST_PARSE_ERROR", e.missing ? `Syntax error: missing "${e.node.type}"` : "Syntax error", e.location));
|
|
4603
4672
|
}
|
|
@@ -4605,7 +4674,7 @@ function reportParseErrors(path35, tree, errors) {
|
|
|
4605
4674
|
diagnostics.push(createDiagnostic("AST_PARSE_ERROR", `${sorted.length - MAX_PARSE_DIAGNOSTICS} more syntax errors`, sorted[MAX_PARSE_DIAGNOSTICS]?.location));
|
|
4606
4675
|
}
|
|
4607
4676
|
if (sorted.length === 0 && tree.rootNode.hasError)
|
|
4608
|
-
diagnostics.push(createDiagnostic("AST_PARSE_ERROR", "Syntax error", { path:
|
|
4677
|
+
diagnostics.push(createDiagnostic("AST_PARSE_ERROR", "Syntax error", { path: path36 }));
|
|
4609
4678
|
return diagnostics;
|
|
4610
4679
|
}
|
|
4611
4680
|
function sortExtraction(x, partial) {
|
|
@@ -4635,8 +4704,8 @@ var StructuralCollector = class {
|
|
|
4635
4704
|
annotations = [];
|
|
4636
4705
|
tests = [];
|
|
4637
4706
|
diagnostics = [];
|
|
4638
|
-
constructor(
|
|
4639
|
-
this.path =
|
|
4707
|
+
constructor(path36) {
|
|
4708
|
+
this.path = path36;
|
|
4640
4709
|
}
|
|
4641
4710
|
/** Adds a declaration; returns the symbol ref calls inside it belong to. */
|
|
4642
4711
|
symbol(d) {
|
|
@@ -4857,12 +4926,12 @@ function parameterName(declarator) {
|
|
|
4857
4926
|
function parameterText(p) {
|
|
4858
4927
|
const name = parameterName(p.childForFieldName("declarator"));
|
|
4859
4928
|
const defaultValue = p.childForFieldName("default_value");
|
|
4860
|
-
const
|
|
4929
|
+
const tokens3 = [];
|
|
4861
4930
|
const visit2 = (n) => {
|
|
4862
4931
|
if (n.type === "comment" || same(n, name))
|
|
4863
4932
|
return;
|
|
4864
4933
|
if (n.childCount === 0) {
|
|
4865
|
-
|
|
4934
|
+
tokens3.push(n.text);
|
|
4866
4935
|
return;
|
|
4867
4936
|
}
|
|
4868
4937
|
for (const c of n.children)
|
|
@@ -4874,7 +4943,7 @@ function parameterText(p) {
|
|
|
4874
4943
|
continue;
|
|
4875
4944
|
visit2(c);
|
|
4876
4945
|
}
|
|
4877
|
-
return
|
|
4946
|
+
return tokens3.join(" ").replace(/\s*([*&,()[\]<>:])\s*/gu, "$1").replace(/\s+/gu, " ").trim();
|
|
4878
4947
|
}
|
|
4879
4948
|
var PARAMETER = /* @__PURE__ */ new Set(["parameter_declaration", "optional_parameter_declaration"]);
|
|
4880
4949
|
var LIST_PUNCTUATION = /* @__PURE__ */ new Set(["(", ")", ",", "comment"]);
|
|
@@ -4929,10 +4998,10 @@ var CppExtractor = class {
|
|
|
4929
4998
|
c;
|
|
4930
4999
|
includes = [];
|
|
4931
5000
|
testPath;
|
|
4932
|
-
constructor(
|
|
5001
|
+
constructor(path36, tree) {
|
|
4933
5002
|
this.tree = tree;
|
|
4934
|
-
this.c = new StructuralCollector(
|
|
4935
|
-
this.testPath = TEST_PATH.test(
|
|
5003
|
+
this.c = new StructuralCollector(path36);
|
|
5004
|
+
this.testPath = TEST_PATH.test(path36);
|
|
4936
5005
|
}
|
|
4937
5006
|
run() {
|
|
4938
5007
|
for (const n of this.tree.rootNode.descendantsOfType("preproc_include"))
|
|
@@ -5122,9 +5191,9 @@ var CppExtractor = class {
|
|
|
5122
5191
|
if (n.type === "call_expression") {
|
|
5123
5192
|
const fn = n.childForFieldName("function");
|
|
5124
5193
|
if (fn !== null) {
|
|
5125
|
-
const
|
|
5194
|
+
const path36 = namePath(fn);
|
|
5126
5195
|
const bare = fn.type === "identifier";
|
|
5127
|
-
this.c.call(n, bare ? "identifier" : "member", fn.text,
|
|
5196
|
+
this.c.call(n, bare ? "identifier" : "member", fn.text, path36, enclosing, bare && locals.has(fn.text), bare && member);
|
|
5128
5197
|
}
|
|
5129
5198
|
} else if (n.type === "new_expression") {
|
|
5130
5199
|
const type = n.childForFieldName("type");
|
|
@@ -5135,8 +5204,8 @@ var CppExtractor = class {
|
|
|
5135
5204
|
});
|
|
5136
5205
|
}
|
|
5137
5206
|
};
|
|
5138
|
-
function extractCpp(
|
|
5139
|
-
return new CppExtractor(
|
|
5207
|
+
function extractCpp(path36, tree) {
|
|
5208
|
+
return new CppExtractor(path36, tree).run();
|
|
5140
5209
|
}
|
|
5141
5210
|
|
|
5142
5211
|
// packages/analyzer/dist/language/tree-sitter/csharp-extract.js
|
|
@@ -5182,9 +5251,9 @@ var CSharpExtractor = class {
|
|
|
5182
5251
|
tree;
|
|
5183
5252
|
c;
|
|
5184
5253
|
usings = /* @__PURE__ */ new Set();
|
|
5185
|
-
constructor(
|
|
5254
|
+
constructor(path36, tree) {
|
|
5186
5255
|
this.tree = tree;
|
|
5187
|
-
this.c = new StructuralCollector(
|
|
5256
|
+
this.c = new StructuralCollector(path36);
|
|
5188
5257
|
}
|
|
5189
5258
|
run() {
|
|
5190
5259
|
let namespace = [];
|
|
@@ -5207,10 +5276,10 @@ var CSharpExtractor = class {
|
|
|
5207
5276
|
const target = node.namedChildren.filter((c) => c !== null && c !== node.childForFieldName("name") && (c.type === "qualified_name" || c.type === "identifier" || c.type === "generic_name")).at(-1);
|
|
5208
5277
|
if (target === void 0 || target === null)
|
|
5209
5278
|
return;
|
|
5210
|
-
const
|
|
5279
|
+
const tokens3 = node.children.filter((c) => c !== null && !c.isNamed).map((c) => c?.type);
|
|
5211
5280
|
this.c.module(node, target.text, "using", alias === void 0 ? [] : [{ local: alias, imported: target.text, typeOnly: false }], {
|
|
5212
|
-
...
|
|
5213
|
-
...
|
|
5281
|
+
...tokens3.includes("static") ? { static: true } : {},
|
|
5282
|
+
...tokens3.includes("global") ? { global: true } : {},
|
|
5214
5283
|
...alias === void 0 ? {} : { alias }
|
|
5215
5284
|
});
|
|
5216
5285
|
if (alias === void 0)
|
|
@@ -5298,8 +5367,8 @@ var CSharpExtractor = class {
|
|
|
5298
5367
|
return true;
|
|
5299
5368
|
}
|
|
5300
5369
|
};
|
|
5301
|
-
function extractCSharp(
|
|
5302
|
-
return new CSharpExtractor(
|
|
5370
|
+
function extractCSharp(path36, tree) {
|
|
5371
|
+
return new CSharpExtractor(path36, tree).run();
|
|
5303
5372
|
}
|
|
5304
5373
|
|
|
5305
5374
|
// packages/analyzer/dist/language/tree-sitter/java-extract.js
|
|
@@ -5337,10 +5406,10 @@ var JavaExtractor = class {
|
|
|
5337
5406
|
/** Imported names of test annotations → framework, and wildcard-imported packages. */
|
|
5338
5407
|
annotationImports = /* @__PURE__ */ new Map();
|
|
5339
5408
|
wildcardImports = /* @__PURE__ */ new Set();
|
|
5340
|
-
constructor(
|
|
5341
|
-
this.path =
|
|
5409
|
+
constructor(path36, tree) {
|
|
5410
|
+
this.path = path36;
|
|
5342
5411
|
this.tree = tree;
|
|
5343
|
-
this.c = new StructuralCollector(
|
|
5412
|
+
this.c = new StructuralCollector(path36);
|
|
5344
5413
|
}
|
|
5345
5414
|
run() {
|
|
5346
5415
|
for (const n of this.tree.rootNode.namedChildren)
|
|
@@ -5463,8 +5532,8 @@ var JavaExtractor = class {
|
|
|
5463
5532
|
return true;
|
|
5464
5533
|
}
|
|
5465
5534
|
};
|
|
5466
|
-
function extractJava(
|
|
5467
|
-
return new JavaExtractor(
|
|
5535
|
+
function extractJava(path36, tree) {
|
|
5536
|
+
return new JavaExtractor(path36, tree).run();
|
|
5468
5537
|
}
|
|
5469
5538
|
|
|
5470
5539
|
// packages/analyzer/dist/language/tree-sitter/python-extract.js
|
|
@@ -5537,10 +5606,10 @@ var PythonExtractor = class {
|
|
|
5537
5606
|
/** Module-level names bound by something other than an import (def, class, assignment, del, ...). */
|
|
5538
5607
|
moduleOtherBound = /* @__PURE__ */ new Set();
|
|
5539
5608
|
definitions = [];
|
|
5540
|
-
constructor(
|
|
5609
|
+
constructor(path36, tree) {
|
|
5541
5610
|
this.tree = tree;
|
|
5542
|
-
this.c = new StructuralCollector(
|
|
5543
|
-
this.testFile = PYTEST_FILE.test(
|
|
5611
|
+
this.c = new StructuralCollector(path36);
|
|
5612
|
+
this.testFile = PYTEST_FILE.test(path36);
|
|
5544
5613
|
}
|
|
5545
5614
|
run() {
|
|
5546
5615
|
const defs = /* @__PURE__ */ new Map();
|
|
@@ -5633,12 +5702,12 @@ var PythonExtractor = class {
|
|
|
5633
5702
|
}
|
|
5634
5703
|
isUnittestCase(cls) {
|
|
5635
5704
|
for (const base of cls.childForFieldName("superclasses")?.namedChildren ?? []) {
|
|
5636
|
-
const
|
|
5637
|
-
if (
|
|
5705
|
+
const path36 = base === null ? void 0 : namePath4(base);
|
|
5706
|
+
if (path36 === void 0)
|
|
5638
5707
|
continue;
|
|
5639
|
-
if (
|
|
5708
|
+
if (path36.length === 1 && this.testCaseNames.has(path36[0]))
|
|
5640
5709
|
return true;
|
|
5641
|
-
if (
|
|
5710
|
+
if (path36.length === 2 && this.unittestModules.has(path36[0]) && path36[1] === "TestCase")
|
|
5642
5711
|
return true;
|
|
5643
5712
|
}
|
|
5644
5713
|
return false;
|
|
@@ -5832,8 +5901,8 @@ var PythonExtractor = class {
|
|
|
5832
5901
|
}
|
|
5833
5902
|
}
|
|
5834
5903
|
};
|
|
5835
|
-
function extractPython(
|
|
5836
|
-
return new PythonExtractor(
|
|
5904
|
+
function extractPython(path36, tree) {
|
|
5905
|
+
return new PythonExtractor(path36, tree).run();
|
|
5837
5906
|
}
|
|
5838
5907
|
|
|
5839
5908
|
// packages/analyzer/dist/language/tree-sitter/languages.js
|
|
@@ -5912,9 +5981,9 @@ var TEST_FRAMEWORKS = {
|
|
|
5912
5981
|
var GLOBAL_TEST_FUNCTIONS = { test: "test", it: "test", describe: "suite", suite: "suite" };
|
|
5913
5982
|
var TEST_MODIFIERS = /* @__PURE__ */ new Set(["skip", "only", "todo"]);
|
|
5914
5983
|
var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/i;
|
|
5915
|
-
function location2(
|
|
5984
|
+
function location2(path36, node) {
|
|
5916
5985
|
return {
|
|
5917
|
-
path:
|
|
5986
|
+
path: path36,
|
|
5918
5987
|
startLine: node.startPosition.row + 1,
|
|
5919
5988
|
startColumn: node.startPosition.column + 1,
|
|
5920
5989
|
endLine: node.endPosition.row + 1,
|
|
@@ -6000,10 +6069,10 @@ var Extractor = class {
|
|
|
6000
6069
|
/** Local names bound by imports or declarations: a global test function name bound here is not a test function. */
|
|
6001
6070
|
localNames = /* @__PURE__ */ new Set();
|
|
6002
6071
|
testFile;
|
|
6003
|
-
constructor(
|
|
6004
|
-
this.path =
|
|
6072
|
+
constructor(path36, tree) {
|
|
6073
|
+
this.path = path36;
|
|
6005
6074
|
this.tree = tree;
|
|
6006
|
-
this.testFile = TEST_FILE.test(
|
|
6075
|
+
this.testFile = TEST_FILE.test(path36);
|
|
6007
6076
|
}
|
|
6008
6077
|
run() {
|
|
6009
6078
|
for (const statement of this.tree.rootNode.namedChildren)
|
|
@@ -6606,8 +6675,8 @@ var Extractor = class {
|
|
|
6606
6675
|
}
|
|
6607
6676
|
}
|
|
6608
6677
|
};
|
|
6609
|
-
function extractTypeScriptJavaScript(
|
|
6610
|
-
return new Extractor(
|
|
6678
|
+
function extractTypeScriptJavaScript(path36, tree) {
|
|
6679
|
+
return new Extractor(path36, tree).run();
|
|
6611
6680
|
}
|
|
6612
6681
|
|
|
6613
6682
|
// packages/analyzer/dist/language/tree-sitter/ts-js-analyzer.js
|
|
@@ -6688,30 +6757,30 @@ var GraphStoreError = class extends Error {
|
|
|
6688
6757
|
};
|
|
6689
6758
|
|
|
6690
6759
|
// packages/graph/dist/store/node-sqlite/node-sqlite-graph-store.js
|
|
6691
|
-
import
|
|
6760
|
+
import fs11 from "node:fs";
|
|
6692
6761
|
import { DatabaseSync } from "node:sqlite";
|
|
6693
6762
|
|
|
6694
6763
|
// packages/graph/dist/store/json.js
|
|
6695
|
-
function canonical2(value,
|
|
6764
|
+
function canonical2(value, path36) {
|
|
6696
6765
|
if (value === null || typeof value === "boolean" || typeof value === "string")
|
|
6697
6766
|
return value;
|
|
6698
6767
|
if (typeof value === "number") {
|
|
6699
6768
|
if (!Number.isFinite(value))
|
|
6700
|
-
throw new GraphStoreError("INVALID_INPUT", `${
|
|
6769
|
+
throw new GraphStoreError("INVALID_INPUT", `${path36}: ${value} is not a finite JSON number`);
|
|
6701
6770
|
return value;
|
|
6702
6771
|
}
|
|
6703
6772
|
if (Array.isArray(value))
|
|
6704
|
-
return value.map((v, i) => canonical2(v, `${
|
|
6773
|
+
return value.map((v, i) => canonical2(v, `${path36}[${i}]`));
|
|
6705
6774
|
if (typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
6706
6775
|
const out = {};
|
|
6707
6776
|
for (const key2 of Object.keys(value).sort(compareUtf8))
|
|
6708
|
-
out[key2] = canonical2(value[key2], `${
|
|
6777
|
+
out[key2] = canonical2(value[key2], `${path36}.${key2}`);
|
|
6709
6778
|
return out;
|
|
6710
6779
|
}
|
|
6711
|
-
throw new GraphStoreError("INVALID_INPUT", `${
|
|
6780
|
+
throw new GraphStoreError("INVALID_INPUT", `${path36}: ${typeof value} is not a JSON value`);
|
|
6712
6781
|
}
|
|
6713
|
-
function canonicalJson(value,
|
|
6714
|
-
return JSON.stringify(canonical2(value ?? {},
|
|
6782
|
+
function canonicalJson(value, path36) {
|
|
6783
|
+
return JSON.stringify(canonical2(value ?? {}, path36));
|
|
6715
6784
|
}
|
|
6716
6785
|
|
|
6717
6786
|
// packages/graph/dist/store/node-sqlite/schema.js
|
|
@@ -7031,9 +7100,9 @@ function inspectSchema(db) {
|
|
|
7031
7100
|
return { kind: "unsupported", found: "missing", duoGraph: true };
|
|
7032
7101
|
return row.value === String(GRAPH_SCHEMA_VERSION) ? { kind: "ok" } : { kind: "unsupported", found: row.value, duoGraph: true };
|
|
7033
7102
|
}
|
|
7034
|
-
function removeDatabaseFiles(
|
|
7103
|
+
function removeDatabaseFiles(path36) {
|
|
7035
7104
|
for (const suffix of ["", "-wal", "-shm", "-journal"])
|
|
7036
|
-
|
|
7105
|
+
fs11.rmSync(path36 + suffix, { force: true });
|
|
7037
7106
|
}
|
|
7038
7107
|
function openNodeSqliteGraphStore(options) {
|
|
7039
7108
|
const source = { path: options.path };
|
|
@@ -7154,7 +7223,7 @@ function traverse(store, seeds, options) {
|
|
|
7154
7223
|
var EDGE_ENDPOINTS = {
|
|
7155
7224
|
CONTAINS: [["project", "milestone"], ["project", "file"], ["file", "symbol"], ["symbol", "symbol"], ["file", "test"]],
|
|
7156
7225
|
REQUIRES: [["milestone", "requirement"], ["requirement", "requirement"], ["issue", "issue"]],
|
|
7157
|
-
IMPLEMENTS: [["symbol", "requirement"], ["file", "requirement"]],
|
|
7226
|
+
IMPLEMENTS: [["symbol", "requirement"], ["file", "requirement"], ["file", "issue"]],
|
|
7158
7227
|
CALLS: [["symbol", "symbol"]],
|
|
7159
7228
|
IMPORTS: [["file", "file"]],
|
|
7160
7229
|
GOVERNS: [["decision", "requirement"], ["decision", "issue"], ["decision", "file"], ["decision", "symbol"]],
|
|
@@ -7647,7 +7716,7 @@ function readDeclarationLinks(graph) {
|
|
|
7647
7716
|
var TEST_FILE2 = /\.(?:test|spec)\.[cm]?[jt]sx?$/iu;
|
|
7648
7717
|
var STATE_PREFIX = `${STATE_DIR_NAME}/`;
|
|
7649
7718
|
var CALL_RESOLUTION_VERSION = 1;
|
|
7650
|
-
var RELATION_RULES_VERSION =
|
|
7719
|
+
var RELATION_RULES_VERSION = 2;
|
|
7651
7720
|
var json = (value) => JSON.parse(JSON.stringify(value));
|
|
7652
7721
|
var clean = (value) => json(Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0)));
|
|
7653
7722
|
var PROVENANCE_RANK = { declared: 3, static: 2, git: 1, heuristic: 0 };
|
|
@@ -7945,7 +8014,7 @@ var Builder = class {
|
|
|
7945
8014
|
bindings.set(binding.local, { ref, binding });
|
|
7946
8015
|
this.contexts.set(analysis.path, { analysis, analyzerVersion, bindings, byIdentity, topLevel, tests, strategy });
|
|
7947
8016
|
}
|
|
7948
|
-
// ---- declared code relations (Requirement.implements / tests, Decision.governs) ----
|
|
8017
|
+
// ---- declared code relations (Requirement.implements / tests, Issue.implements, Decision.governs) ----
|
|
7949
8018
|
symbolsNamed(qualifiedName, paths) {
|
|
7950
8019
|
const matchers = paths.flatMap((p) => compileRepoPattern(p) ?? []);
|
|
7951
8020
|
const out = [];
|
|
@@ -8053,6 +8122,13 @@ var Builder = class {
|
|
|
8053
8122
|
}
|
|
8054
8123
|
}
|
|
8055
8124
|
}
|
|
8125
|
+
for (const i of truth.issues) {
|
|
8126
|
+
const iss = definitionRef("issue", i.id);
|
|
8127
|
+
if (!this.has(iss))
|
|
8128
|
+
continue;
|
|
8129
|
+
for (const f of this.filesMatching(i.implements.paths))
|
|
8130
|
+
this.addEdge(fileRef(f), "IMPLEMENTS", iss, "project-truth", { provenance: "declared", basis: ["implements.paths"] });
|
|
8131
|
+
}
|
|
8056
8132
|
for (const d of truth.decisions) {
|
|
8057
8133
|
const dec = definitionRef("decision", d.id);
|
|
8058
8134
|
if (!this.has(dec))
|
|
@@ -8075,43 +8151,43 @@ var Builder = class {
|
|
|
8075
8151
|
}
|
|
8076
8152
|
/** Module results for every analyzed file: stored results when the Indexer allows it, else TypeScript resolution. */
|
|
8077
8153
|
resolveModules() {
|
|
8078
|
-
for (const [
|
|
8154
|
+
for (const [path36, ctx] of [...this.contexts].sort(([a], [b]) => compareUtf8(a, b))) {
|
|
8079
8155
|
const refs = ctx.analysis.moduleReferences;
|
|
8080
|
-
const previous = this.memo?.previous.get(
|
|
8156
|
+
const previous = this.memo?.previous.get(path36);
|
|
8081
8157
|
let results;
|
|
8082
8158
|
let configFiles;
|
|
8083
|
-
if (previous !== void 0 && this.memo?.reusableModules.has(
|
|
8159
|
+
if (previous !== void 0 && this.memo?.reusableModules.has(path36) === true && previous.modules.length === refs.length) {
|
|
8084
8160
|
results = previous.modules;
|
|
8085
8161
|
configFiles = previous.configFiles;
|
|
8086
8162
|
this.work.modulesReused += refs.length;
|
|
8087
8163
|
} else {
|
|
8088
|
-
results = refs.map((ref) => this.input.moduleResolver.resolve(this.request(
|
|
8089
|
-
configFiles = this.input.moduleResolver.configFiles(
|
|
8164
|
+
results = refs.map((ref) => this.input.moduleResolver.resolve(this.request(path36, ref)));
|
|
8165
|
+
configFiles = this.input.moduleResolver.configFiles(path36);
|
|
8090
8166
|
this.work.modulesRecomputed += refs.length;
|
|
8091
8167
|
this.work.filesModulesRecomputed++;
|
|
8092
8168
|
}
|
|
8093
|
-
this.moduleResults.set(
|
|
8094
|
-
this.resolution.set(
|
|
8169
|
+
this.moduleResults.set(path36, new Map(refs.map((ref, i) => [ref, results[i]])));
|
|
8170
|
+
this.resolution.set(path36, { modules: results, calls: [], exportDependencies: [], configFiles });
|
|
8095
8171
|
}
|
|
8096
8172
|
}
|
|
8097
|
-
sameModules(
|
|
8098
|
-
const previous = this.memo?.previous.get(
|
|
8099
|
-
const current = this.resolution.get(
|
|
8173
|
+
sameModules(path36) {
|
|
8174
|
+
const previous = this.memo?.previous.get(path36);
|
|
8175
|
+
const current = this.resolution.get(path36);
|
|
8100
8176
|
return previous !== void 0 && current !== void 0 && canonicalJson(json({ m: previous.modules }), "modules") === canonicalJson(json({ m: current.modules }), "modules");
|
|
8101
8177
|
}
|
|
8102
8178
|
/** A file whose export surface a stored call result may have read has changed. */
|
|
8103
|
-
surfaceChanged(
|
|
8104
|
-
return this.memo === void 0 || this.memo.changedFiles.has(
|
|
8179
|
+
surfaceChanged(path36) {
|
|
8180
|
+
return this.memo === void 0 || this.memo.changedFiles.has(path36) || !this.sameModules(path36);
|
|
8105
8181
|
}
|
|
8106
|
-
callFreshnessOf(
|
|
8107
|
-
const previous = this.memo?.previous.get(
|
|
8182
|
+
callFreshnessOf(path36, ctx) {
|
|
8183
|
+
const previous = this.memo?.previous.get(path36);
|
|
8108
8184
|
if (this.memo === void 0 || previous === void 0)
|
|
8109
8185
|
return "missing";
|
|
8110
8186
|
if (this.memo.callVersionChanged)
|
|
8111
8187
|
return "stale-version";
|
|
8112
|
-
if (this.memo.changedFiles.has(
|
|
8188
|
+
if (this.memo.changedFiles.has(path36))
|
|
8113
8189
|
return "stale-source";
|
|
8114
|
-
if (!this.sameModules(
|
|
8190
|
+
if (!this.sameModules(path36))
|
|
8115
8191
|
return "stale-modules";
|
|
8116
8192
|
if (previous.exportDependencies.some((d) => this.surfaceChanged(d)))
|
|
8117
8193
|
return "stale-dependency";
|
|
@@ -8135,12 +8211,12 @@ var Builder = class {
|
|
|
8135
8211
|
modulesAndCalls() {
|
|
8136
8212
|
this.resolveModules();
|
|
8137
8213
|
this.reportConfigDiagnostics();
|
|
8138
|
-
for (const [
|
|
8139
|
-
const file = fileRef(
|
|
8214
|
+
for (const [path36, ctx] of [...this.contexts].sort(([a], [b]) => compareUtf8(a, b))) {
|
|
8215
|
+
const file = fileRef(path36);
|
|
8140
8216
|
for (const ref of ctx.analysis.moduleReferences) {
|
|
8141
|
-
const result = this.moduleResult(
|
|
8142
|
-
this.moduleResolutions.push({ from:
|
|
8143
|
-
if (result.status === "resolved" && result.path !==
|
|
8217
|
+
const result = this.moduleResult(path36, ref);
|
|
8218
|
+
this.moduleResolutions.push({ from: path36, specifier: ref.specifier, kind: ref.kind, location: ref.location, result });
|
|
8219
|
+
if (result.status === "resolved" && result.path !== path36 && this.filePaths.has(result.path)) {
|
|
8144
8220
|
const kinds = [ref.kind];
|
|
8145
8221
|
this.addEdge(file, "IMPORTS", fileRef(result.path), "module-resolution", json({ provenance: "static", resolution: result.claim, kinds, typeOnly: ref.typeOnly, declarationOnly: result.declarationOnly, extensionSubstituted: result.extensionSubstituted }), (old) => json({
|
|
8146
8222
|
...old,
|
|
@@ -8154,29 +8230,29 @@ var Builder = class {
|
|
|
8154
8230
|
}
|
|
8155
8231
|
}
|
|
8156
8232
|
}
|
|
8157
|
-
for (const [
|
|
8233
|
+
for (const [path36, ctx] of [...this.contexts].sort(([a], [b]) => compareUtf8(a, b))) {
|
|
8158
8234
|
const sites = ctx.analysis.callSites;
|
|
8159
|
-
let freshness = this.callFreshnessOf(
|
|
8160
|
-
const previous = this.memo?.previous.get(
|
|
8235
|
+
let freshness = this.callFreshnessOf(path36, ctx);
|
|
8236
|
+
const previous = this.memo?.previous.get(path36);
|
|
8161
8237
|
let outcomes = freshness === "fresh" && previous !== void 0 ? this.rehydrate(previous.calls) : void 0;
|
|
8162
8238
|
if (freshness === "fresh" && outcomes === void 0)
|
|
8163
8239
|
freshness = "stale-dependency";
|
|
8164
8240
|
let exportDependencies = previous?.exportDependencies ?? [];
|
|
8165
8241
|
if (outcomes === void 0) {
|
|
8166
8242
|
const deps = /* @__PURE__ */ new Set();
|
|
8167
|
-
outcomes = sites.map((call) => this.resolveByStrategy(
|
|
8168
|
-
deps.delete(
|
|
8243
|
+
outcomes = sites.map((call) => this.resolveByStrategy(path36, ctx, call, deps));
|
|
8244
|
+
deps.delete(path36);
|
|
8169
8245
|
exportDependencies = [...deps].sort(compareUtf8);
|
|
8170
8246
|
this.work.callsRecomputed += sites.length;
|
|
8171
8247
|
this.work.filesCallsRecomputed++;
|
|
8172
8248
|
} else {
|
|
8173
8249
|
this.work.callsReused += sites.length;
|
|
8174
8250
|
}
|
|
8175
|
-
this.callFreshness.set(
|
|
8251
|
+
this.callFreshness.set(path36, freshness);
|
|
8176
8252
|
const resolved = outcomes;
|
|
8177
|
-
const current = this.resolution.get(
|
|
8253
|
+
const current = this.resolution.get(path36);
|
|
8178
8254
|
if (current !== void 0) {
|
|
8179
|
-
this.resolution.set(
|
|
8255
|
+
this.resolution.set(path36, {
|
|
8180
8256
|
...current,
|
|
8181
8257
|
calls: resolved.map((o) => ({
|
|
8182
8258
|
status: o.status,
|
|
@@ -8192,7 +8268,7 @@ var Builder = class {
|
|
|
8192
8268
|
this.calls[outcome.status]++;
|
|
8193
8269
|
const target = outcome.target;
|
|
8194
8270
|
this.callResolutions.push({
|
|
8195
|
-
path:
|
|
8271
|
+
path: path36,
|
|
8196
8272
|
calleeText: call.calleeText,
|
|
8197
8273
|
location: call.location,
|
|
8198
8274
|
status: outcome.status,
|
|
@@ -8213,7 +8289,7 @@ var Builder = class {
|
|
|
8213
8289
|
const declarationOnly = /\.d\.[cm]?ts$/u.test(target.file);
|
|
8214
8290
|
this.addEdge(call.enclosingSymbol, "CALLS", target.symbol.ref, "call-resolution", json({ provenance: "static", resolution: "exact", callSites: 1, declarationOnly }), (old) => json({ ...old, callSites: (typeof old.callSites === "number" ? old.callSites : 0) + 1 }));
|
|
8215
8291
|
});
|
|
8216
|
-
this.validatedByCalls(
|
|
8292
|
+
this.validatedByCalls(path36, ctx, exactTargets);
|
|
8217
8293
|
}
|
|
8218
8294
|
}
|
|
8219
8295
|
/**
|
|
@@ -8234,7 +8310,7 @@ var Builder = class {
|
|
|
8234
8310
|
}
|
|
8235
8311
|
}
|
|
8236
8312
|
/** VALIDATED_BY Symbol → Test: exact calls inside the test's range to code outside test files. */
|
|
8237
|
-
validatedByCalls(
|
|
8313
|
+
validatedByCalls(path36, ctx, exact) {
|
|
8238
8314
|
for (const { call, target } of exact) {
|
|
8239
8315
|
const test = this.innermostTest(ctx.tests, call.location);
|
|
8240
8316
|
if (test === void 0)
|
|
@@ -8242,7 +8318,7 @@ var Builder = class {
|
|
|
8242
8318
|
const targetCtx = this.contexts.get(target.file);
|
|
8243
8319
|
if (TEST_FILE2.test(target.file) || (targetCtx?.analysis.tests.length ?? 0) > 0)
|
|
8244
8320
|
continue;
|
|
8245
|
-
this.addEdge(target.symbol.ref, "VALIDATED_BY", testRef(
|
|
8321
|
+
this.addEdge(target.symbol.ref, "VALIDATED_BY", testRef(path36, test.fullName), "test", { provenance: "static", basis: ["exact-call"] });
|
|
8246
8322
|
}
|
|
8247
8323
|
}
|
|
8248
8324
|
innermostTest(tests, at) {
|
|
@@ -8268,11 +8344,11 @@ var Builder = class {
|
|
|
8268
8344
|
deps.add(d);
|
|
8269
8345
|
return r.result;
|
|
8270
8346
|
}
|
|
8271
|
-
viaImport(
|
|
8347
|
+
viaImport(path36, entry, member, kind, deps) {
|
|
8272
8348
|
const { ref, binding } = entry;
|
|
8273
8349
|
if (binding.typeOnly)
|
|
8274
8350
|
return { status: "unresolved", reason: "type-only-binding" };
|
|
8275
|
-
const module = this.moduleResult(
|
|
8351
|
+
const module = this.moduleResult(path36, ref);
|
|
8276
8352
|
if (module.status !== "resolved")
|
|
8277
8353
|
return { status: "unresolved", reason: module.status === "external" ? "external-module" : "module-unresolved" };
|
|
8278
8354
|
if (binding.imported === "*") {
|
|
@@ -8292,19 +8368,19 @@ var Builder = class {
|
|
|
8292
8368
|
return symbol?.kind === "method" ? { status: "exact", target: { file, symbol } } : { status: "unresolved", reason: "static-member-not-found" };
|
|
8293
8369
|
}
|
|
8294
8370
|
/** Exact resolution from syntax facts only (no type checker). Anything else is unresolved or ambiguous. */
|
|
8295
|
-
resolveByStrategy(
|
|
8371
|
+
resolveByStrategy(path36, ctx, call, deps) {
|
|
8296
8372
|
if (ctx.strategy === "module-bindings")
|
|
8297
|
-
return this.resolveCall(
|
|
8373
|
+
return this.resolveCall(path36, ctx, call, deps);
|
|
8298
8374
|
if (ctx.strategy === "none")
|
|
8299
8375
|
return { status: "unresolved", reason: "call-resolution-unavailable" };
|
|
8300
|
-
return this.sameFileFunction(
|
|
8376
|
+
return this.sameFileFunction(path36, ctx, call);
|
|
8301
8377
|
}
|
|
8302
8378
|
/**
|
|
8303
8379
|
* same-file-functions (T18.0): a bare-name call, not bound locally and not possibly a class member,
|
|
8304
8380
|
* to the only function of that name at module or namespace scope in the same file. Everything else
|
|
8305
8381
|
* stays unresolved: another file, a member, an overload set or a macro is not guessed.
|
|
8306
8382
|
*/
|
|
8307
|
-
sameFileFunction(
|
|
8383
|
+
sameFileFunction(path36, ctx, call) {
|
|
8308
8384
|
const names = call.calleePath;
|
|
8309
8385
|
if (call.kind !== "identifier" || names === void 0 || names.length !== 1)
|
|
8310
8386
|
return { status: "unresolved", reason: "receiver-type-unknown" };
|
|
@@ -8323,9 +8399,9 @@ var Builder = class {
|
|
|
8323
8399
|
return { status: "unresolved", reason: all.length > 0 ? "target-not-callable" : "no-candidate" };
|
|
8324
8400
|
if (all.length > 1)
|
|
8325
8401
|
return { status: "ambiguous", reason: "same-file-candidates" };
|
|
8326
|
-
return { status: "exact", target: { file:
|
|
8402
|
+
return { status: "exact", target: { file: path36, symbol: only } };
|
|
8327
8403
|
}
|
|
8328
|
-
resolveCall(
|
|
8404
|
+
resolveCall(path36, ctx, call, deps) {
|
|
8329
8405
|
const names = call.calleePath;
|
|
8330
8406
|
if (names === void 0 || names.length === 0)
|
|
8331
8407
|
return { status: "unresolved", reason: "computed-callee" };
|
|
@@ -8343,18 +8419,18 @@ var Builder = class {
|
|
|
8343
8419
|
if (enclosing?.memberScope !== "instance" || enclosing.parent === void 0)
|
|
8344
8420
|
return { status: "unresolved", reason: "this-receiver-unknown" };
|
|
8345
8421
|
const target = ctx.byIdentity.get(`${enclosing.parent}.${second}`);
|
|
8346
|
-
return target?.kind === "method" ? { status: "exact", target: { file:
|
|
8422
|
+
return target?.kind === "method" ? { status: "exact", target: { file: path36, symbol: target } } : { status: "unresolved", reason: "member-not-found" };
|
|
8347
8423
|
}
|
|
8348
8424
|
const imported = ctx.bindings.get(root);
|
|
8349
8425
|
if (imported !== void 0)
|
|
8350
|
-
return this.viaImport(
|
|
8426
|
+
return this.viaImport(path36, imported, second, call.kind, deps);
|
|
8351
8427
|
const local = ctx.topLevel.get(root) ?? [];
|
|
8352
8428
|
if (second === void 0) {
|
|
8353
8429
|
const callable = local.filter((s) => CALLABLE_BY_KIND[call.kind].has(s.kind));
|
|
8354
8430
|
if (callable.length > 1)
|
|
8355
8431
|
return { status: "ambiguous", reason: "same-file-candidates" };
|
|
8356
8432
|
const only = callable[0];
|
|
8357
|
-
return only === void 0 ? { status: "unresolved", reason: local.length > 0 ? "target-not-callable" : "no-candidate" } : { status: "exact", target: { file:
|
|
8433
|
+
return only === void 0 ? { status: "unresolved", reason: local.length > 0 ? "target-not-callable" : "no-candidate" } : { status: "exact", target: { file: path36, symbol: only } };
|
|
8358
8434
|
}
|
|
8359
8435
|
const classes = local.filter((s) => s.kind === "class");
|
|
8360
8436
|
if (classes.length > 1)
|
|
@@ -8362,18 +8438,18 @@ var Builder = class {
|
|
|
8362
8438
|
const cls = classes[0];
|
|
8363
8439
|
if (cls === void 0 || local.length !== 1)
|
|
8364
8440
|
return { status: "unresolved", reason: "receiver-type-unknown" };
|
|
8365
|
-
return this.staticMember(
|
|
8441
|
+
return this.staticMember(path36, cls, second);
|
|
8366
8442
|
}
|
|
8367
8443
|
// ---- annotations ----
|
|
8368
8444
|
annotationRelations() {
|
|
8369
|
-
for (const [
|
|
8445
|
+
for (const [path36, ctx] of [...this.contexts].sort(([a], [b]) => compareUtf8(a, b))) {
|
|
8370
8446
|
if (ctx.analysis.annotations.length === 0)
|
|
8371
8447
|
continue;
|
|
8372
8448
|
const candidates3 = [
|
|
8373
8449
|
...ctx.analysis.symbols.flatMap((s) => [s.location, ...s.additionalLocations ?? []].map((location4) => ({ value: { kind: "symbol", ref: s.ref }, location: location4 }))),
|
|
8374
8450
|
...ctx.tests.map((t2) => ({ value: { kind: "test", fullName: t2.fullName }, location: t2.location }))
|
|
8375
8451
|
];
|
|
8376
|
-
const text4 = this.input.sourceText(
|
|
8452
|
+
const text4 = this.input.sourceText(path36);
|
|
8377
8453
|
for (const annotation of ctx.analysis.annotations) {
|
|
8378
8454
|
const attached = attachAnnotation(annotation.location, candidates3, text4);
|
|
8379
8455
|
const target = attached.kind === "file" ? { kind: "file" } : attached.value;
|
|
@@ -8393,9 +8469,9 @@ var Builder = class {
|
|
|
8393
8469
|
if (target.kind === "symbol")
|
|
8394
8470
|
this.addEdge(target.ref, "IMPLEMENTS", entity.ref, "annotation", metadata);
|
|
8395
8471
|
else if (target.kind === "test")
|
|
8396
|
-
this.addEdge(entity.ref, "VALIDATED_BY", testRef(
|
|
8472
|
+
this.addEdge(entity.ref, "VALIDATED_BY", testRef(path36, target.fullName), "annotation", metadata);
|
|
8397
8473
|
else
|
|
8398
|
-
this.addEdge(fileRef(
|
|
8474
|
+
this.addEdge(fileRef(path36), "IMPLEMENTS", entity.ref, "annotation", metadata);
|
|
8399
8475
|
this.annotations[target.kind]++;
|
|
8400
8476
|
}
|
|
8401
8477
|
}
|
|
@@ -8478,8 +8554,8 @@ var TRUTH_SCOPE = "truth";
|
|
|
8478
8554
|
function codeFileOf(ref) {
|
|
8479
8555
|
return ref.type === "file" || ref.type === "symbol" || ref.type === "test" ? ref.path : void 0;
|
|
8480
8556
|
}
|
|
8481
|
-
function fileScope(
|
|
8482
|
-
return `file:${
|
|
8557
|
+
function fileScope(path36) {
|
|
8558
|
+
return `file:${path36}`;
|
|
8483
8559
|
}
|
|
8484
8560
|
function nodeScope(ref) {
|
|
8485
8561
|
const file = codeFileOf(ref);
|
|
@@ -8618,7 +8694,7 @@ function resolvePython(request, files, roots = discoverPythonRoots(files)) {
|
|
|
8618
8694
|
return { status: "ambiguous", candidates: found.sort(compareUtf8) };
|
|
8619
8695
|
return { status: "resolved", path: found[0], claim: "python-local", declarationOnly: false, extensionSubstituted: false };
|
|
8620
8696
|
}
|
|
8621
|
-
var isPythonMetadataFile = (
|
|
8697
|
+
var isPythonMetadataFile = (path36) => METADATA_FILE.test(baseOf(path36));
|
|
8622
8698
|
|
|
8623
8699
|
// packages/graph/dist/build/resolve/languages.js
|
|
8624
8700
|
var PYTHON_RESOLUTION_VERSION = "2";
|
|
@@ -8664,21 +8740,21 @@ function createLanguageModuleResolver(options) {
|
|
|
8664
8740
|
}
|
|
8665
8741
|
|
|
8666
8742
|
// packages/graph/dist/incremental/indexer.js
|
|
8667
|
-
import
|
|
8668
|
-
import
|
|
8743
|
+
import fs14 from "node:fs";
|
|
8744
|
+
import path16 from "node:path";
|
|
8669
8745
|
|
|
8670
8746
|
// packages/graph/dist/incremental/analysis-cache.js
|
|
8671
8747
|
import { createHash as createHash10 } from "node:crypto";
|
|
8672
8748
|
|
|
8673
8749
|
// packages/graph/dist/incremental/files.js
|
|
8674
|
-
import
|
|
8675
|
-
import
|
|
8750
|
+
import fs12 from "node:fs";
|
|
8751
|
+
import path14 from "node:path";
|
|
8676
8752
|
function symlinkOnPath3(root, repoPath3) {
|
|
8677
8753
|
const segments = repoPath3.split("/");
|
|
8678
8754
|
for (let i = 1; i <= segments.length; i++) {
|
|
8679
8755
|
const partial = segments.slice(0, i).join("/");
|
|
8680
8756
|
try {
|
|
8681
|
-
if (
|
|
8757
|
+
if (fs12.lstatSync(path14.join(root, partial)).isSymbolicLink())
|
|
8682
8758
|
return partial;
|
|
8683
8759
|
} catch {
|
|
8684
8760
|
return void 0;
|
|
@@ -8690,7 +8766,7 @@ function readRegenerable(root, repoPath3) {
|
|
|
8690
8766
|
if (symlinkOnPath3(root, repoPath3) !== void 0)
|
|
8691
8767
|
return { error: "the path contains a symlink" };
|
|
8692
8768
|
try {
|
|
8693
|
-
return { text:
|
|
8769
|
+
return { text: fs12.readFileSync(path14.join(root, repoPath3), "utf8") };
|
|
8694
8770
|
} catch (error) {
|
|
8695
8771
|
return error.code === "ENOENT" ? {} : { error: error.message };
|
|
8696
8772
|
}
|
|
@@ -8700,7 +8776,7 @@ async function symlinkOnPathAsync(root, repoPath3) {
|
|
|
8700
8776
|
for (let i = 1; i <= segments.length; i++) {
|
|
8701
8777
|
const partial = segments.slice(0, i).join("/");
|
|
8702
8778
|
try {
|
|
8703
|
-
if ((await
|
|
8779
|
+
if ((await fs12.promises.lstat(path14.join(root, partial))).isSymbolicLink())
|
|
8704
8780
|
return partial;
|
|
8705
8781
|
} catch {
|
|
8706
8782
|
return void 0;
|
|
@@ -8712,7 +8788,7 @@ async function readRegenerableAsync(root, repoPath3) {
|
|
|
8712
8788
|
if (await symlinkOnPathAsync(root, repoPath3) !== void 0)
|
|
8713
8789
|
return { error: "the path contains a symlink" };
|
|
8714
8790
|
try {
|
|
8715
|
-
return { text: await
|
|
8791
|
+
return { text: await fs12.promises.readFile(path14.join(root, repoPath3), "utf8") };
|
|
8716
8792
|
} catch (error) {
|
|
8717
8793
|
return error.code === "ENOENT" ? {} : { error: error.message };
|
|
8718
8794
|
}
|
|
@@ -8725,14 +8801,14 @@ function writeRegenerable(root, repoPath3, text4) {
|
|
|
8725
8801
|
const link = symlinkOnPath3(root, target);
|
|
8726
8802
|
if (link !== void 0)
|
|
8727
8803
|
return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${target}": "${link}" is a symlink`, { path: target })]);
|
|
8728
|
-
const absolute =
|
|
8804
|
+
const absolute = path14.join(root, target);
|
|
8729
8805
|
const temp = `${absolute}.${process.pid}.tmp`;
|
|
8730
8806
|
try {
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8807
|
+
fs12.mkdirSync(path14.dirname(absolute), { recursive: true });
|
|
8808
|
+
fs12.writeFileSync(temp, text4, "utf8");
|
|
8809
|
+
fs12.renameSync(temp, absolute);
|
|
8734
8810
|
} catch (error) {
|
|
8735
|
-
|
|
8811
|
+
fs12.rmSync(temp, { force: true });
|
|
8736
8812
|
return failure([createDiagnostic("FILE_WRITE_ERROR", `Cannot write "${target}": ${error.message}`, { path: target })]);
|
|
8737
8813
|
}
|
|
8738
8814
|
return success(target);
|
|
@@ -8743,14 +8819,14 @@ function pruneRegenerableDirectory(root, repoDir, keep) {
|
|
|
8743
8819
|
let removed = 0;
|
|
8744
8820
|
let entries;
|
|
8745
8821
|
try {
|
|
8746
|
-
entries =
|
|
8822
|
+
entries = fs12.readdirSync(path14.join(root, repoDir), { withFileTypes: true });
|
|
8747
8823
|
} catch {
|
|
8748
8824
|
return 0;
|
|
8749
8825
|
}
|
|
8750
8826
|
for (const entry of entries) {
|
|
8751
8827
|
if (!entry.isFile() || keep.has(entry.name))
|
|
8752
8828
|
continue;
|
|
8753
|
-
|
|
8829
|
+
fs12.rmSync(path14.join(root, repoDir, entry.name), { force: true });
|
|
8754
8830
|
removed++;
|
|
8755
8831
|
}
|
|
8756
8832
|
return removed;
|
|
@@ -8806,8 +8882,8 @@ function pruneAnalysisCache(root, keep) {
|
|
|
8806
8882
|
}
|
|
8807
8883
|
|
|
8808
8884
|
// packages/graph/dist/incremental/assess.js
|
|
8809
|
-
import
|
|
8810
|
-
import
|
|
8885
|
+
import fs13 from "node:fs";
|
|
8886
|
+
import path15 from "node:path";
|
|
8811
8887
|
|
|
8812
8888
|
// packages/graph/dist/incremental/state.js
|
|
8813
8889
|
import { createHash as createHash11 } from "node:crypto";
|
|
@@ -8969,7 +9045,7 @@ function isResolutionConfigFile(repoPath3) {
|
|
|
8969
9045
|
}
|
|
8970
9046
|
function hashOnDisk(root, repoPath3) {
|
|
8971
9047
|
try {
|
|
8972
|
-
return computeContentHash(
|
|
9048
|
+
return computeContentHash(fs13.readFileSync(path15.join(root, repoPath3)), fingerprintModeOf(repoPath3)).contentHash;
|
|
8973
9049
|
} catch {
|
|
8974
9050
|
return void 0;
|
|
8975
9051
|
}
|
|
@@ -9026,8 +9102,8 @@ function readScope(store, scope) {
|
|
|
9026
9102
|
if (scope === TRUTH_SCOPE)
|
|
9027
9103
|
nodes = TRUTH_TYPES.flatMap((type) => listAll(store, { type }));
|
|
9028
9104
|
else {
|
|
9029
|
-
const
|
|
9030
|
-
nodes = [...store.getNodes([fileRef(
|
|
9105
|
+
const path36 = scope.slice("file:".length);
|
|
9106
|
+
nodes = [...store.getNodes([fileRef(path36)]), ...listAll(store, { ownerFile: path36 })];
|
|
9031
9107
|
}
|
|
9032
9108
|
const edges = /* @__PURE__ */ new Map();
|
|
9033
9109
|
for (let i = 0; i < nodes.length; i += REF_CHUNK) {
|
|
@@ -9106,17 +9182,17 @@ function applyGraphDiff(tx, diff) {
|
|
|
9106
9182
|
// packages/graph/dist/incremental/indexer.js
|
|
9107
9183
|
var GRAPH_DB_FILE_PATH = `${STATE_DIR_NAME}/generated/graph.db`;
|
|
9108
9184
|
function openProjectGraphStore(root) {
|
|
9109
|
-
const rootDir =
|
|
9185
|
+
const rootDir = path16.resolve(root);
|
|
9110
9186
|
const allowed = checkWriteBoundary(rootDir, GRAPH_DB_FILE_PATH, "regenerable");
|
|
9111
9187
|
if (allowed.value === void 0)
|
|
9112
9188
|
return { diagnostics: allowed.diagnostics, regenerable: false };
|
|
9113
|
-
const file =
|
|
9114
|
-
|
|
9189
|
+
const file = path16.join(rootDir, allowed.value.path);
|
|
9190
|
+
fs14.mkdirSync(path16.dirname(file), { recursive: true });
|
|
9115
9191
|
return openNodeSqliteGraphStore({ path: file, onUnsupportedSchema: "recreate" });
|
|
9116
9192
|
}
|
|
9117
9193
|
function openProjectGraphReader(root) {
|
|
9118
|
-
const file =
|
|
9119
|
-
if (!
|
|
9194
|
+
const file = path16.join(path16.resolve(root), GRAPH_DB_FILE_PATH);
|
|
9195
|
+
if (!fs14.existsSync(file))
|
|
9120
9196
|
return openNodeSqliteGraphStore({ path: ":memory:" });
|
|
9121
9197
|
return openNodeSqliteGraphStore({ path: file, readOnly: true });
|
|
9122
9198
|
}
|
|
@@ -9128,7 +9204,7 @@ async function indexRepository(root, options) {
|
|
|
9128
9204
|
options.onPhase?.(name, now - phaseStart);
|
|
9129
9205
|
phaseStart = now;
|
|
9130
9206
|
};
|
|
9131
|
-
const rootDir =
|
|
9207
|
+
const rootDir = path16.resolve(root);
|
|
9132
9208
|
const { store } = options;
|
|
9133
9209
|
const window = options.historyWindow ?? HISTORY_WINDOW;
|
|
9134
9210
|
const diagnostics = [];
|
|
@@ -9218,7 +9294,7 @@ async function indexRepository(root, options) {
|
|
|
9218
9294
|
continue;
|
|
9219
9295
|
}
|
|
9220
9296
|
changedFiles.add(f.path);
|
|
9221
|
-
const content2 =
|
|
9297
|
+
const content2 = fs14.readFileSync(path16.join(rootDir, f.path));
|
|
9222
9298
|
analyzed++;
|
|
9223
9299
|
const started = performance.now();
|
|
9224
9300
|
const r = analyzer.analyze({ path: f.path, content: content2 });
|
|
@@ -9295,7 +9371,7 @@ async function indexRepository(root, options) {
|
|
|
9295
9371
|
if (known !== void 0)
|
|
9296
9372
|
return known;
|
|
9297
9373
|
try {
|
|
9298
|
-
return
|
|
9374
|
+
return fs14.readFileSync(path16.join(rootDir, p), "utf8");
|
|
9299
9375
|
} catch {
|
|
9300
9376
|
return void 0;
|
|
9301
9377
|
}
|
|
@@ -9464,7 +9540,7 @@ function graphChanged(diff) {
|
|
|
9464
9540
|
}
|
|
9465
9541
|
|
|
9466
9542
|
// packages/graph/dist/incremental/inspect.js
|
|
9467
|
-
import
|
|
9543
|
+
import path17 from "node:path";
|
|
9468
9544
|
async function inspectIndex(root, options) {
|
|
9469
9545
|
let phaseStart = performance.now();
|
|
9470
9546
|
const phase = (name) => {
|
|
@@ -9472,7 +9548,7 @@ async function inspectIndex(root, options) {
|
|
|
9472
9548
|
options.onPhase?.(name, now - phaseStart);
|
|
9473
9549
|
phaseStart = now;
|
|
9474
9550
|
};
|
|
9475
|
-
const rootDir =
|
|
9551
|
+
const rootDir = path17.resolve(root);
|
|
9476
9552
|
const window = options.historyWindow ?? HISTORY_WINDOW;
|
|
9477
9553
|
const diagnostics = [];
|
|
9478
9554
|
const loaded = loadProjectTruth(rootDir);
|
|
@@ -9635,35 +9711,35 @@ function sameFingerprint(a, b) {
|
|
|
9635
9711
|
async function readIndexedModuleReferences(root, paths) {
|
|
9636
9712
|
const read2 = readIndexState(root);
|
|
9637
9713
|
if (read2.state === void 0)
|
|
9638
|
-
return paths.map((
|
|
9714
|
+
return paths.map((path36) => ({ status: "unavailable", path: path36, reason: read2.problem ?? "index state" }));
|
|
9639
9715
|
const files = new Map(read2.state.files.map((f) => [f.path, f]));
|
|
9640
|
-
const keys = paths.map((
|
|
9641
|
-
const f = files.get(
|
|
9642
|
-
return f?.analysis === void 0 || f.analysis.status !== "ok" || f.resolution === void 0 ? void 0 : { path:
|
|
9716
|
+
const keys = paths.map((path36) => {
|
|
9717
|
+
const f = files.get(path36);
|
|
9718
|
+
return f?.analysis === void 0 || f.analysis.status !== "ok" || f.resolution === void 0 ? void 0 : { path: path36, contentHash: f.contentHash, analyzer: f.analysis.analyzer, analyzerIdentity: f.analysis.identity };
|
|
9643
9719
|
});
|
|
9644
9720
|
const present = keys.filter((k) => k !== void 0);
|
|
9645
9721
|
const cached = await readCachedAnalyses(root, present);
|
|
9646
9722
|
const byPath = new Map(present.map((k, i) => [k.path, cached[i]]));
|
|
9647
|
-
return paths.map((
|
|
9648
|
-
const f = files.get(
|
|
9723
|
+
return paths.map((path36, i) => {
|
|
9724
|
+
const f = files.get(path36);
|
|
9649
9725
|
if (f === void 0)
|
|
9650
|
-
return { status: "not-indexed", path:
|
|
9726
|
+
return { status: "not-indexed", path: path36 };
|
|
9651
9727
|
if (keys[i] === void 0)
|
|
9652
|
-
return { status: "not-analyzed", path:
|
|
9653
|
-
const entry = byPath.get(
|
|
9728
|
+
return { status: "not-analyzed", path: path36 };
|
|
9729
|
+
const entry = byPath.get(path36);
|
|
9654
9730
|
const modules = f.resolution?.modules ?? [];
|
|
9655
9731
|
if (entry === void 0)
|
|
9656
|
-
return { status: "unavailable", path:
|
|
9732
|
+
return { status: "unavailable", path: path36, reason: "analysis cache entry missing" };
|
|
9657
9733
|
const refs = entry.analysis.moduleReferences;
|
|
9658
9734
|
if (refs.length !== modules.length)
|
|
9659
|
-
return { status: "unavailable", path:
|
|
9735
|
+
return { status: "unavailable", path: path36, reason: "analysis and resolution do not match" };
|
|
9660
9736
|
return {
|
|
9661
9737
|
status: "ok",
|
|
9662
|
-
path:
|
|
9738
|
+
path: path36,
|
|
9663
9739
|
language: entry.analysis.language,
|
|
9664
9740
|
references: refs.map((r, j) => {
|
|
9665
9741
|
const resolution = modules[j];
|
|
9666
|
-
const target = resolution.status === "resolved" && resolution.path !==
|
|
9742
|
+
const target = resolution.status === "resolved" && resolution.path !== path36 && files.has(resolution.path) ? resolution.path : void 0;
|
|
9667
9743
|
return { kind: r.kind, specifier: r.specifier, typeOnly: r.typeOnly, location: r.location, resolution, ...target === void 0 ? {} : { target } };
|
|
9668
9744
|
})
|
|
9669
9745
|
};
|
|
@@ -9676,13 +9752,14 @@ var STEPS = {
|
|
|
9676
9752
|
symbol: [
|
|
9677
9753
|
{ edge: "CALLS", direction: "incoming", relation: "direct" },
|
|
9678
9754
|
{ edge: "VALIDATED_BY", direction: "outgoing", relation: "direct" },
|
|
9679
|
-
|
|
9755
|
+
// IMPLEMENTS also links Issues (H-78); impact reports Requirements here, as before.
|
|
9756
|
+
{ edge: "IMPLEMENTS", direction: "outgoing", relation: "direct", to: "requirement" },
|
|
9680
9757
|
{ edge: "GOVERNS", direction: "incoming", relation: "direct" },
|
|
9681
9758
|
{ edge: "CONTAINS", direction: "incoming", relation: "structural" }
|
|
9682
9759
|
],
|
|
9683
9760
|
file: [
|
|
9684
9761
|
{ edge: "IMPORTS", direction: "incoming", relation: "direct" },
|
|
9685
|
-
{ edge: "IMPLEMENTS", direction: "outgoing", relation: "direct" },
|
|
9762
|
+
{ edge: "IMPLEMENTS", direction: "outgoing", relation: "direct", to: "requirement" },
|
|
9686
9763
|
{ edge: "GOVERNS", direction: "incoming", relation: "direct" },
|
|
9687
9764
|
{ edge: "CONTAINS", direction: "outgoing", relation: "structural", seedOnly: true },
|
|
9688
9765
|
{ edge: "CHANGED_WITH", direction: "outgoing", relation: "historical" }
|
|
@@ -9729,6 +9806,8 @@ function impact(store, seeds, options = {}) {
|
|
|
9729
9806
|
const other = step2.direction === "incoming" ? e.from : e.to;
|
|
9730
9807
|
if (seedSet.has(other))
|
|
9731
9808
|
continue;
|
|
9809
|
+
if (step2.to !== void 0 && refOf3(other).type !== step2.to)
|
|
9810
|
+
continue;
|
|
9732
9811
|
const relation = RANK[step2.relation] > RANK[current.relation] ? step2.relation : current.relation;
|
|
9733
9812
|
const item = { id: other, ref: refOf3(other), depth: current.depth + 1, relation, via: { from: current.id, edge: step2.edge, direction: step2.direction } };
|
|
9734
9813
|
const known = best.get(other);
|
|
@@ -9755,13 +9834,13 @@ function trace(store, seed, options = {}) {
|
|
|
9755
9834
|
|
|
9756
9835
|
// packages/graph/dist/query/locations.js
|
|
9757
9836
|
var KEYS = ["startLine", "startColumn", "endLine", "endColumn"];
|
|
9758
|
-
function asLocation(v,
|
|
9837
|
+
function asLocation(v, path36) {
|
|
9759
9838
|
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
9760
9839
|
return void 0;
|
|
9761
9840
|
const o = v;
|
|
9762
|
-
if (o.path !==
|
|
9841
|
+
if (o.path !== path36)
|
|
9763
9842
|
return void 0;
|
|
9764
|
-
const out = { path:
|
|
9843
|
+
const out = { path: path36 };
|
|
9765
9844
|
for (const k of KEYS) {
|
|
9766
9845
|
const n = o[k];
|
|
9767
9846
|
if (n === void 0)
|
|
@@ -9798,12 +9877,12 @@ var packageInfo3 = {
|
|
|
9798
9877
|
|
|
9799
9878
|
// packages/director/dist/adoption/baseline.js
|
|
9800
9879
|
import { createHash as createHash12 } from "node:crypto";
|
|
9801
|
-
import
|
|
9802
|
-
import
|
|
9880
|
+
import fs15 from "node:fs";
|
|
9881
|
+
import path19 from "node:path";
|
|
9803
9882
|
|
|
9804
9883
|
// packages/director/dist/review/record.js
|
|
9805
9884
|
import fsp3 from "node:fs/promises";
|
|
9806
|
-
import
|
|
9885
|
+
import path18 from "node:path";
|
|
9807
9886
|
var REVIEW_RECORD_FORMAT = "duo.review-record/1";
|
|
9808
9887
|
var REVIEW_ASSIST_FORMAT = "duo.review-assist/1";
|
|
9809
9888
|
var ADOPTION_BASELINE_FORMAT = "duo.adoption-baseline/2";
|
|
@@ -9897,15 +9976,15 @@ function assistBody(reviewId, result) {
|
|
|
9897
9976
|
skippedChecks: a.skippedChecks
|
|
9898
9977
|
};
|
|
9899
9978
|
}
|
|
9900
|
-
function verifyReviewRecord(text4,
|
|
9979
|
+
function verifyReviewRecord(text4, path36 = "record") {
|
|
9901
9980
|
let parsed;
|
|
9902
9981
|
try {
|
|
9903
9982
|
parsed = JSON.parse(text4);
|
|
9904
9983
|
} catch (error) {
|
|
9905
|
-
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", `${
|
|
9984
|
+
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", `${path36} is not valid JSON: ${error.message}`, { path: path36 })]);
|
|
9906
9985
|
}
|
|
9907
9986
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
9908
|
-
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", `${
|
|
9987
|
+
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", `${path36} is not a record object`, { path: path36 })]);
|
|
9909
9988
|
const body = { ...parsed };
|
|
9910
9989
|
const id3 = body.id;
|
|
9911
9990
|
delete body.id;
|
|
@@ -9913,29 +9992,29 @@ function verifyReviewRecord(text4, path35 = "record") {
|
|
|
9913
9992
|
const format = body.format;
|
|
9914
9993
|
const prefix = typeof format === "string" ? ID_PREFIX[format] : void 0;
|
|
9915
9994
|
if (prefix === void 0)
|
|
9916
|
-
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", `${
|
|
9995
|
+
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", `${path36} has format ${String(format)}`, { path: path36 })]);
|
|
9917
9996
|
const expected = hexId(prefix, body);
|
|
9918
9997
|
if (id3 !== expected)
|
|
9919
|
-
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", `${
|
|
9998
|
+
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", `${path36}: id ${String(id3)} does not match its content (${expected})`, { path: path36 })]);
|
|
9920
9999
|
return success({ id: expected, body });
|
|
9921
10000
|
}
|
|
9922
|
-
async function writeHistoryRecord(root,
|
|
9923
|
-
const guarded2 = guardWrite(root,
|
|
10001
|
+
async function writeHistoryRecord(root, path36, file, id3, body) {
|
|
10002
|
+
const guarded2 = guardWrite(root, path36, "human-history", { restrictTo: [`${REVIEWS_DIR}/`] });
|
|
9924
10003
|
if (guarded2.value === void 0)
|
|
9925
10004
|
return failure(guarded2.diagnostics);
|
|
9926
10005
|
try {
|
|
9927
10006
|
if (await createFileExclusive(guarded2.value.absolute, `${JSON.stringify(file, null, 2)}
|
|
9928
10007
|
`))
|
|
9929
|
-
return success({ id: id3, path:
|
|
9930
|
-
const existing = verifyReviewRecord(await fsp3.readFile(guarded2.value.absolute, "utf8"),
|
|
10008
|
+
return success({ id: id3, path: path36, status: "created" });
|
|
10009
|
+
const existing = verifyReviewRecord(await fsp3.readFile(guarded2.value.absolute, "utf8"), path36);
|
|
9931
10010
|
if (existing.value === void 0)
|
|
9932
10011
|
return failure(existing.diagnostics);
|
|
9933
10012
|
if (stableJson(existing.value.body) !== stableJson(body)) {
|
|
9934
|
-
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", `${
|
|
10013
|
+
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", `${path36} exists with other content under the same ID; it is not overwritten`, { path: path36 })]);
|
|
9935
10014
|
}
|
|
9936
|
-
return success({ id: id3, path:
|
|
10015
|
+
return success({ id: id3, path: path36, status: "unchanged" });
|
|
9937
10016
|
} catch (error) {
|
|
9938
|
-
return failure([createDiagnostic("FILE_WRITE_ERROR", `Cannot record ${
|
|
10017
|
+
return failure([createDiagnostic("FILE_WRITE_ERROR", `Cannot record ${path36}: ${error.message}`, { path: path36 })]);
|
|
9939
10018
|
}
|
|
9940
10019
|
}
|
|
9941
10020
|
async function recordReview(result, options) {
|
|
@@ -9959,10 +10038,10 @@ async function recordReview(result, options) {
|
|
|
9959
10038
|
return assist.value === void 0 ? failure(diagnostics) : success({ ...main.value, assist: assist.value }, diagnostics);
|
|
9960
10039
|
}
|
|
9961
10040
|
async function listReviewRecords(root) {
|
|
9962
|
-
const dir =
|
|
10041
|
+
const dir = path18.join(root, REVIEWS_DIR);
|
|
9963
10042
|
let names;
|
|
9964
10043
|
try {
|
|
9965
|
-
for (const part of [
|
|
10044
|
+
for (const part of [path18.dirname(dir), dir]) {
|
|
9966
10045
|
const stat = await fsp3.lstat(part);
|
|
9967
10046
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
9968
10047
|
return failure([createDiagnostic("REVIEW_RECORD_INTEGRITY", "Review history directory is not a real project directory")]);
|
|
@@ -9976,10 +10055,10 @@ async function listReviewRecords(root) {
|
|
|
9976
10055
|
const read2 = async (name) => {
|
|
9977
10056
|
const rel = `${REVIEWS_DIR}/${name}`;
|
|
9978
10057
|
try {
|
|
9979
|
-
const stat = await fsp3.lstat(
|
|
10058
|
+
const stat = await fsp3.lstat(path18.join(dir, name));
|
|
9980
10059
|
if (!stat.isFile())
|
|
9981
10060
|
return void 0;
|
|
9982
|
-
const text4 = await fsp3.readFile(
|
|
10061
|
+
const text4 = await fsp3.readFile(path18.join(dir, name), "utf8");
|
|
9983
10062
|
const verified = verifyReviewRecord(text4, rel);
|
|
9984
10063
|
diagnostics.push(...verified.diagnostics);
|
|
9985
10064
|
if (verified.value === void 0)
|
|
@@ -10358,6 +10437,16 @@ function seedEvidence(ctx, seed) {
|
|
|
10358
10437
|
function isActive(d) {
|
|
10359
10438
|
return d.state === "confirmed" && d.supersededBy === null;
|
|
10360
10439
|
}
|
|
10440
|
+
function withDecisionAuthority(truth, claims) {
|
|
10441
|
+
const decisions = new Map(truth.decisions.map((d) => [d.id, d]));
|
|
10442
|
+
return claims.map((c) => {
|
|
10443
|
+
const d = c.subject.kind === "decision" ? decisions.get(c.subject.id) : void 0;
|
|
10444
|
+
if (d === void 0)
|
|
10445
|
+
return c;
|
|
10446
|
+
const decisionAuthority = { state: d.state, active: isActive(d), supersedes: d.supersedes, supersededBy: d.supersededBy };
|
|
10447
|
+
return { ...c, decisionAuthority };
|
|
10448
|
+
});
|
|
10449
|
+
}
|
|
10361
10450
|
|
|
10362
10451
|
// packages/director/dist/review/scope.js
|
|
10363
10452
|
var TEST_NAME = /(?:\.|^)(?:test|spec|e2e|stories|story|bench)\.[cm]?[jt]sx?$/u;
|
|
@@ -10388,10 +10477,10 @@ var GENERATED_DIRS = /* @__PURE__ */ new Set([
|
|
|
10388
10477
|
]);
|
|
10389
10478
|
var GENERATED_NAME = /\.(?:generated|gen|min)\.[cm]?[jt]sx?$|\.(?:g|g\.i|designer)\.cs$|\.generated\.h$|_pb2(?:_grpc)?\.py$|\.pb\.(?:h|cc)$/iu;
|
|
10390
10479
|
var DOC_DIRS = /* @__PURE__ */ new Set(["docs", "doc", "examples", "example"]);
|
|
10391
|
-
function nonApplicationReason(
|
|
10392
|
-
if (
|
|
10480
|
+
function nonApplicationReason(path36) {
|
|
10481
|
+
if (path36 === STATE_DIR_NAME || path36.startsWith(`${STATE_DIR_NAME}/`))
|
|
10393
10482
|
return "project-truth";
|
|
10394
|
-
const segments =
|
|
10483
|
+
const segments = path36.split("/");
|
|
10395
10484
|
const name = segments[segments.length - 1] ?? "";
|
|
10396
10485
|
const dirs = segments.slice(0, -1).map((s) => s.toLowerCase());
|
|
10397
10486
|
if (TEST_NAME.test(name) || OTHER_TEST_NAME.test(name) || SETUP_NAME.test(name) || dirs.some((d) => TEST_DIRS.has(d)))
|
|
@@ -10427,7 +10516,7 @@ function unlinkedAdditions(ctx) {
|
|
|
10427
10516
|
continue;
|
|
10428
10517
|
const owned2 = ctx.graph.listNodes({ ownerFile: f.path, limit: 1e4 }).map((n) => n.ref);
|
|
10429
10518
|
const from = [fileRef(f.path), ...seeds.map((s) => s.entity), ...owned2];
|
|
10430
|
-
if (ctx.graph.adjacentEdges(from, { direction: "outgoing", types: ["IMPLEMENTS"], limit:
|
|
10519
|
+
if (ctx.graph.adjacentEdges(from, { direction: "outgoing", types: ["IMPLEMENTS"], limit: 1e4 }).edges.some((e) => e.to.startsWith("req:")))
|
|
10431
10520
|
continue;
|
|
10432
10521
|
const c = makeClaim(ctx, {
|
|
10433
10522
|
rule: "unlinked-addition",
|
|
@@ -10455,14 +10544,14 @@ ${governing}
|
|
|
10455
10544
|
${offending}`).slice(7, 23)}`;
|
|
10456
10545
|
}
|
|
10457
10546
|
var dependencyOffending = (manifest, name) => `dep:${manifest}#${name}`;
|
|
10458
|
-
var sourceOffending = (
|
|
10547
|
+
var sourceOffending = (path36, section) => `src:${path36}#${section ?? ""}`;
|
|
10459
10548
|
function declaredReferenceParts(message2) {
|
|
10460
10549
|
const m = /^(\S+) (\S+) "(.*)" matches /u.exec(message2);
|
|
10461
10550
|
return m === null ? void 0 : { governing: m[1] ?? "", offending: `ref:${m[2] ?? ""}:${m[3] ?? ""}` };
|
|
10462
10551
|
}
|
|
10463
10552
|
|
|
10464
10553
|
// packages/director/dist/context/policy.js
|
|
10465
|
-
var CONTEXT_POLICY_VERSION = "
|
|
10554
|
+
var CONTEXT_POLICY_VERSION = "8";
|
|
10466
10555
|
var EDGE_WEIGHTS = {
|
|
10467
10556
|
GOVERNS: 1,
|
|
10468
10557
|
IMPLEMENTS: 0.9,
|
|
@@ -10646,11 +10735,11 @@ function overlaps(loc2, range) {
|
|
|
10646
10735
|
const end2 = loc2.endLine ?? loc2.startLine;
|
|
10647
10736
|
return range[0] <= end2 && range[1] >= loc2.startLine;
|
|
10648
10737
|
}
|
|
10649
|
-
function owned(graph,
|
|
10738
|
+
function owned(graph, path36) {
|
|
10650
10739
|
const out = [];
|
|
10651
10740
|
let afterId;
|
|
10652
10741
|
for (; ; ) {
|
|
10653
|
-
const page = graph.listNodes({ ownerFile:
|
|
10742
|
+
const page = graph.listNodes({ ownerFile: path36, limit: 1e3, ...afterId === void 0 ? {} : { afterId } });
|
|
10654
10743
|
out.push(...page);
|
|
10655
10744
|
if (page.length < 1e3)
|
|
10656
10745
|
return out;
|
|
@@ -10669,11 +10758,11 @@ function truthDefinitions(truth) {
|
|
|
10669
10758
|
}
|
|
10670
10759
|
function diffSeeds(files, graph, truth) {
|
|
10671
10760
|
const seeds = /* @__PURE__ */ new Map();
|
|
10672
|
-
const put = (entity, reason,
|
|
10761
|
+
const put = (entity, reason, path36, evidenceId2) => {
|
|
10673
10762
|
if (graph.getNode(entity) === void 0)
|
|
10674
10763
|
return;
|
|
10675
10764
|
const id3 = nodeId(entity);
|
|
10676
|
-
const s = seeds.get(id3) ?? { entity, reason, path:
|
|
10765
|
+
const s = seeds.get(id3) ?? { entity, reason, path: path36, evidence: /* @__PURE__ */ new Set() };
|
|
10677
10766
|
s.evidence.add(evidenceId2);
|
|
10678
10767
|
seeds.set(id3, s);
|
|
10679
10768
|
};
|
|
@@ -10951,7 +11040,7 @@ async function decisionForbids(ctx) {
|
|
|
10951
11040
|
function implemented(ctx) {
|
|
10952
11041
|
const out = /* @__PURE__ */ new Map();
|
|
10953
11042
|
for (const s of codeSeeds(ctx)) {
|
|
10954
|
-
const edges = ctx.graph.adjacentEdges([s.entity], { direction: "outgoing", types: ["IMPLEMENTS"], limit: 1e3 }).edges;
|
|
11043
|
+
const edges = ctx.graph.adjacentEdges([s.entity], { direction: "outgoing", types: ["IMPLEMENTS"], limit: 1e3 }).edges.filter((e) => e.to.startsWith("req:"));
|
|
10955
11044
|
for (const e of edges) {
|
|
10956
11045
|
const id3 = e.to.slice("req:".length);
|
|
10957
11046
|
out.set(id3, [...out.get(id3) ?? [], s]);
|
|
@@ -11491,10 +11580,10 @@ async function observeWorkingTree(git, cap = 1e3) {
|
|
|
11491
11580
|
var ADOPTION_FILE = /^adoption-[0-9a-f]{16}\.json$/u;
|
|
11492
11581
|
function fileHash(root, p) {
|
|
11493
11582
|
try {
|
|
11494
|
-
const abs =
|
|
11495
|
-
if (!
|
|
11583
|
+
const abs = path19.join(root, p);
|
|
11584
|
+
if (!fs15.lstatSync(abs).isFile())
|
|
11496
11585
|
return void 0;
|
|
11497
|
-
return `sha256:${createHash12("sha256").update(
|
|
11586
|
+
return `sha256:${createHash12("sha256").update(fs15.readFileSync(abs)).digest("hex")}`;
|
|
11498
11587
|
} catch {
|
|
11499
11588
|
return void 0;
|
|
11500
11589
|
}
|
|
@@ -11513,10 +11602,10 @@ async function bootstrapTruthOf(root, git) {
|
|
|
11513
11602
|
return out.sort((a, b) => compareUtf8(a.path, b.path));
|
|
11514
11603
|
}
|
|
11515
11604
|
function loadAdoptionBaseline(root) {
|
|
11516
|
-
const dir =
|
|
11605
|
+
const dir = path19.join(root, REVIEWS_DIR);
|
|
11517
11606
|
let names;
|
|
11518
11607
|
try {
|
|
11519
|
-
names =
|
|
11608
|
+
names = fs15.readdirSync(dir).filter((n) => ADOPTION_FILE.test(n)).sort(compareUtf8);
|
|
11520
11609
|
} catch {
|
|
11521
11610
|
return { status: "missing" };
|
|
11522
11611
|
}
|
|
@@ -11527,7 +11616,7 @@ function loadAdoptionBaseline(root) {
|
|
|
11527
11616
|
const rel = `${REVIEWS_DIR}/${names[0] ?? ""}`;
|
|
11528
11617
|
let text4;
|
|
11529
11618
|
try {
|
|
11530
|
-
text4 =
|
|
11619
|
+
text4 = fs15.readFileSync(path19.join(root, rel), "utf8");
|
|
11531
11620
|
} catch (error) {
|
|
11532
11621
|
return { status: "incompatible", path: rel, reason: error.message };
|
|
11533
11622
|
}
|
|
@@ -11716,15 +11805,15 @@ function codePoints(text4) {
|
|
|
11716
11805
|
return n;
|
|
11717
11806
|
}
|
|
11718
11807
|
function truncateToTokens(text4, maxTokens) {
|
|
11719
|
-
const
|
|
11720
|
-
if (
|
|
11808
|
+
const tokens3 = encode(text4, PLAIN_TEXT);
|
|
11809
|
+
if (tokens3.length <= maxTokens)
|
|
11721
11810
|
return { text: text4, truncated: false };
|
|
11722
|
-
return { text: decode(
|
|
11811
|
+
return { text: decode(tokens3.slice(0, Math.max(0, maxTokens))).replace(/\uFFFD+$/u, ""), truncated: true };
|
|
11723
11812
|
}
|
|
11724
11813
|
|
|
11725
11814
|
// packages/director/dist/context/cache.js
|
|
11726
|
-
import
|
|
11727
|
-
import
|
|
11815
|
+
import fs16 from "node:fs";
|
|
11816
|
+
import path20 from "node:path";
|
|
11728
11817
|
var PACKET_CACHE_DIR = `${STATE_DIR_NAME}/cache/packets`;
|
|
11729
11818
|
var FORMAT2 = "duo.packet-cache/1";
|
|
11730
11819
|
function fileOf(digest) {
|
|
@@ -11734,10 +11823,10 @@ function readCachedPacket(root, digest) {
|
|
|
11734
11823
|
if (!/^sha256:[0-9a-f]{64}$/u.test(digest))
|
|
11735
11824
|
return void 0;
|
|
11736
11825
|
try {
|
|
11737
|
-
const target =
|
|
11738
|
-
if (
|
|
11826
|
+
const target = path20.join(root, fileOf(digest));
|
|
11827
|
+
if (fs16.lstatSync(target).isSymbolicLink())
|
|
11739
11828
|
return void 0;
|
|
11740
|
-
const entry = JSON.parse(
|
|
11829
|
+
const entry = JSON.parse(fs16.readFileSync(target, "utf8"));
|
|
11741
11830
|
if (entry.format !== FORMAT2 || entry.digest !== digest || entry.packet?.dependencyDigest !== digest || entry.packet.format !== "duo.context-packet/1")
|
|
11742
11831
|
return void 0;
|
|
11743
11832
|
return entry.packet;
|
|
@@ -11749,15 +11838,15 @@ function writeCachedPacket(root, packet) {
|
|
|
11749
11838
|
const allowed = checkWriteBoundary(root, fileOf(packet.dependencyDigest), "regenerable");
|
|
11750
11839
|
if (allowed.value === void 0)
|
|
11751
11840
|
return false;
|
|
11752
|
-
const target =
|
|
11841
|
+
const target = path20.join(root, allowed.value.path);
|
|
11753
11842
|
const temp = `${target}.${process.pid}.tmp`;
|
|
11754
11843
|
try {
|
|
11755
|
-
|
|
11756
|
-
|
|
11757
|
-
|
|
11844
|
+
fs16.mkdirSync(path20.dirname(target), { recursive: true });
|
|
11845
|
+
fs16.writeFileSync(temp, JSON.stringify({ format: FORMAT2, digest: packet.dependencyDigest, packet }));
|
|
11846
|
+
fs16.renameSync(temp, target);
|
|
11758
11847
|
return true;
|
|
11759
11848
|
} catch {
|
|
11760
|
-
|
|
11849
|
+
fs16.rmSync(temp, { force: true });
|
|
11761
11850
|
return false;
|
|
11762
11851
|
}
|
|
11763
11852
|
}
|
|
@@ -11845,11 +11934,11 @@ var EXT_LANG = [
|
|
|
11845
11934
|
[/\.(?:cpp|cc|cxx|hpp|hh|hxx|h)$/u, "cpp"],
|
|
11846
11935
|
[/\.py$/u, "python"]
|
|
11847
11936
|
];
|
|
11848
|
-
function langOf(
|
|
11849
|
-
return EXT_LANG.find(([re]) => re.test(
|
|
11937
|
+
function langOf(path36) {
|
|
11938
|
+
return EXT_LANG.find(([re]) => re.test(path36))?.[1] ?? "";
|
|
11850
11939
|
}
|
|
11851
|
-
var extensionOf3 = (
|
|
11852
|
-
const name =
|
|
11940
|
+
var extensionOf3 = (path36) => {
|
|
11941
|
+
const name = path36.slice(path36.lastIndexOf("/") + 1);
|
|
11853
11942
|
const dot = name.lastIndexOf(".");
|
|
11854
11943
|
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
11855
11944
|
};
|
|
@@ -11965,7 +12054,7 @@ function candidateScope(expansion, seeds, task) {
|
|
|
11965
12054
|
taskText: task,
|
|
11966
12055
|
entries: expansion.candidates.map((x) => {
|
|
11967
12056
|
const ref = x.node.ref;
|
|
11968
|
-
const
|
|
12057
|
+
const path36 = ref.type === "symbol" || ref.type === "file" ? ref.path : void 0;
|
|
11969
12058
|
const qn = x.node.payload.qualifiedName;
|
|
11970
12059
|
return {
|
|
11971
12060
|
id: x.node.id,
|
|
@@ -11974,7 +12063,7 @@ function candidateScope(expansion, seeds, task) {
|
|
|
11974
12063
|
hops: x.depth,
|
|
11975
12064
|
...bySeed.has(x.node.id) ? { seed: SEED_PROVENANCE[bySeed.get(x.node.id)?.match ?? "keyword"] } : {},
|
|
11976
12065
|
...bySeed.has(x.seed) ? { origin: SEED_PROVENANCE[bySeed.get(x.seed)?.match ?? "keyword"], originRef: displayRef(x.seed) } : {},
|
|
11977
|
-
...
|
|
12066
|
+
...path36 === void 0 ? {} : { path: path36 },
|
|
11978
12067
|
...typeof qn === "string" ? { qualifiedName: qn } : {}
|
|
11979
12068
|
};
|
|
11980
12069
|
})
|
|
@@ -12007,9 +12096,9 @@ function planContext(input) {
|
|
|
12007
12096
|
};
|
|
12008
12097
|
const via = (c, extra = []) => ({ seed: displayRef(c.seed), steps: [...c.steps, ...extra] });
|
|
12009
12098
|
const fileLanguages = /* @__PURE__ */ new Map();
|
|
12010
|
-
const analyzedPath = (
|
|
12011
|
-
if (!fileLanguages.has(
|
|
12012
|
-
fileLanguages.set(
|
|
12099
|
+
const analyzedPath = (path36) => {
|
|
12100
|
+
if (!fileLanguages.has(path36))
|
|
12101
|
+
fileLanguages.set(path36, DEFAULT_EXTENSION_LANGUAGES[extensionOf3(path36)]);
|
|
12013
12102
|
};
|
|
12014
12103
|
const requirementItem = (r, c) => {
|
|
12015
12104
|
const head = `${r.id} ${r.title} (${[r.status, r.milestone, r.priority].filter((x) => x !== null && x !== void 0).join(", ")})`;
|
|
@@ -12474,16 +12563,16 @@ var TokenMeter = class {
|
|
|
12474
12563
|
};
|
|
12475
12564
|
|
|
12476
12565
|
// packages/director/dist/context/metrics.js
|
|
12477
|
-
import
|
|
12478
|
-
import
|
|
12566
|
+
import fs17 from "node:fs";
|
|
12567
|
+
import path21 from "node:path";
|
|
12479
12568
|
var TOKEN_COUNT_CACHE_PATH = `${STATE_DIR_NAME}/cache/token-counts.json`;
|
|
12480
12569
|
var FORMAT3 = "duo.token-counts/1";
|
|
12481
12570
|
function readCounts(root) {
|
|
12482
12571
|
try {
|
|
12483
|
-
const entry = JSON.parse(
|
|
12572
|
+
const entry = JSON.parse(fs17.readFileSync(path21.join(root, TOKEN_COUNT_CACHE_PATH), "utf8"));
|
|
12484
12573
|
if (entry.format !== FORMAT3 || entry.estimator !== TOKEN_ESTIMATOR_ID || entry.counts === void 0)
|
|
12485
12574
|
return /* @__PURE__ */ new Map();
|
|
12486
|
-
return new Map(Object.entries(entry.counts).map(([k, [
|
|
12575
|
+
return new Map(Object.entries(entry.counts).map(([k, [tokens3, chars]]) => [k, { tokens: tokens3, chars }]));
|
|
12487
12576
|
} catch {
|
|
12488
12577
|
return /* @__PURE__ */ new Map();
|
|
12489
12578
|
}
|
|
@@ -12492,15 +12581,15 @@ function writeCounts(root, counts) {
|
|
|
12492
12581
|
const allowed = checkWriteBoundary(root, TOKEN_COUNT_CACHE_PATH, "regenerable");
|
|
12493
12582
|
if (allowed.value === void 0)
|
|
12494
12583
|
return;
|
|
12495
|
-
const target =
|
|
12584
|
+
const target = path21.join(root, allowed.value.path);
|
|
12496
12585
|
const temp = `${target}.${process.pid}.tmp`;
|
|
12497
12586
|
const sorted = Object.fromEntries([...counts].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => [k, [v.tokens, v.chars]]));
|
|
12498
12587
|
try {
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12588
|
+
fs17.mkdirSync(path21.dirname(target), { recursive: true });
|
|
12589
|
+
fs17.writeFileSync(temp, JSON.stringify({ format: FORMAT3, estimator: TOKEN_ESTIMATOR_ID, counts: sorted }));
|
|
12590
|
+
fs17.renameSync(temp, target);
|
|
12502
12591
|
} catch {
|
|
12503
|
-
|
|
12592
|
+
fs17.rmSync(temp, { force: true });
|
|
12504
12593
|
}
|
|
12505
12594
|
}
|
|
12506
12595
|
function repositoryTokens(root, meter, useCache, memo) {
|
|
@@ -12508,7 +12597,7 @@ function repositoryTokens(root, meter, useCache, memo) {
|
|
|
12508
12597
|
const cached = useCache ? readCounts(root) : /* @__PURE__ */ new Map();
|
|
12509
12598
|
const next2 = /* @__PURE__ */ new Map();
|
|
12510
12599
|
const perFile = /* @__PURE__ */ new Map();
|
|
12511
|
-
let
|
|
12600
|
+
let tokens3 = 0, bytes = 0, chars = 0, binaryFiles = 0;
|
|
12512
12601
|
for (const f of fingerprints) {
|
|
12513
12602
|
bytes += f.size;
|
|
12514
12603
|
if (f.fingerprintMode !== "normalized-text") {
|
|
@@ -12524,7 +12613,7 @@ function repositoryTokens(root, meter, useCache, memo) {
|
|
|
12524
12613
|
}
|
|
12525
12614
|
next2.set(f.contentHash, v);
|
|
12526
12615
|
perFile.set(f.path, v.tokens);
|
|
12527
|
-
|
|
12616
|
+
tokens3 += v.tokens;
|
|
12528
12617
|
chars += v.chars;
|
|
12529
12618
|
}
|
|
12530
12619
|
if (useCache && (next2.size !== cached.size || [...next2.keys()].some((k) => !cached.has(k))))
|
|
@@ -12534,7 +12623,7 @@ function repositoryTokens(root, meter, useCache, memo) {
|
|
|
12534
12623
|
for (const [k, v] of next2)
|
|
12535
12624
|
memo.set(k, v);
|
|
12536
12625
|
}
|
|
12537
|
-
return { files: fingerprints.length, binaryFiles, tokens:
|
|
12626
|
+
return { files: fingerprints.length, binaryFiles, tokens: tokens3, bytes, chars, perFile };
|
|
12538
12627
|
}
|
|
12539
12628
|
function reduction(selected, base) {
|
|
12540
12629
|
return base <= 0 ? null : Math.round((1 - selected / base) * 1e4) / 100;
|
|
@@ -12821,12 +12910,12 @@ var SourceReader = class {
|
|
|
12821
12910
|
constructor(root) {
|
|
12822
12911
|
this.root = root;
|
|
12823
12912
|
}
|
|
12824
|
-
text(
|
|
12913
|
+
text(path36) {
|
|
12825
12914
|
const t0 = performance3.now();
|
|
12826
|
-
let r = this.files.get(
|
|
12915
|
+
let r = this.files.get(path36);
|
|
12827
12916
|
if (r === void 0) {
|
|
12828
|
-
r = readSourceFile(this.root,
|
|
12829
|
-
this.files.set(
|
|
12917
|
+
r = readSourceFile(this.root, path36);
|
|
12918
|
+
this.files.set(path36, r);
|
|
12830
12919
|
}
|
|
12831
12920
|
this.ms += performance3.now() - t0;
|
|
12832
12921
|
return r;
|
|
@@ -12862,8 +12951,8 @@ var SourceReader = class {
|
|
|
12862
12951
|
return r.value === void 0 ? { diagnostics: r.diagnostics } : success({ text: r.value, startLine: start2 });
|
|
12863
12952
|
}
|
|
12864
12953
|
/** The first whole lines of a file within both bounds (T18.0 generic files); undefined when it cannot be read as text. */
|
|
12865
|
-
headWindow(
|
|
12866
|
-
const text4 = this.text(
|
|
12954
|
+
headWindow(path36, bounds) {
|
|
12955
|
+
const text4 = this.text(path36).value;
|
|
12867
12956
|
if (text4 === void 0 || text4.trim() === "")
|
|
12868
12957
|
return void 0;
|
|
12869
12958
|
const all = text4.split("\n");
|
|
@@ -12899,18 +12988,40 @@ function refOfNode(node) {
|
|
|
12899
12988
|
return ref.id;
|
|
12900
12989
|
}
|
|
12901
12990
|
}
|
|
12991
|
+
function keywordQueryTokens(tokens3, idTokens2, exactTokens) {
|
|
12992
|
+
return tokens3.filter((t2) => !idTokens2.has(t2) && !exactTokens.has(t2));
|
|
12993
|
+
}
|
|
12994
|
+
var SEPARATORS = /[\s,;:!?()[\]{}<>"'`]+/u;
|
|
12902
12995
|
function rawTokens(task) {
|
|
12903
|
-
return task
|
|
12996
|
+
return taskWords(task).map((w) => w.token);
|
|
12997
|
+
}
|
|
12998
|
+
function taskWords(task) {
|
|
12999
|
+
return task.split(SEPARATORS).map((w) => {
|
|
13000
|
+
const token = w.replace(/^[.]+|[.]+$/gu, "");
|
|
13001
|
+
const trimmed = w.replace(/[.]+$/u, "");
|
|
13002
|
+
return { token, pathText: trimmed };
|
|
13003
|
+
}).filter((w) => w.token.length > 0);
|
|
13004
|
+
}
|
|
13005
|
+
function pathOf(pathText) {
|
|
13006
|
+
if (!pathText.includes("/") && !pathText.includes("\\"))
|
|
13007
|
+
return void 0;
|
|
13008
|
+
return normalizeRepoPath(pathText).value;
|
|
13009
|
+
}
|
|
13010
|
+
function rootFileOf(pathText) {
|
|
13011
|
+
if (pathText.includes("/") || pathText.includes("\\") || !pathText.includes("."))
|
|
13012
|
+
return void 0;
|
|
13013
|
+
const p = normalizeRepoPath(pathText).value;
|
|
13014
|
+
return p === void 0 || p !== pathText ? void 0 : { path: p, leadingDot: pathText.startsWith(".") };
|
|
12904
13015
|
}
|
|
12905
13016
|
var NAME_TOKEN = /^[A-Za-z_$][\w$]*(?:\.#?[A-Za-z_$][\w$]*)*$/u;
|
|
12906
|
-
function isPathSignal(
|
|
12907
|
-
const
|
|
12908
|
-
return
|
|
13017
|
+
function isPathSignal(path36) {
|
|
13018
|
+
const words = taskWords(path36);
|
|
13019
|
+
return words.length === 1 && words[0]?.pathText === path36 && pathOf(path36) !== void 0;
|
|
12909
13020
|
}
|
|
12910
13021
|
function isNameSignal(name) {
|
|
12911
|
-
const
|
|
13022
|
+
const tokens3 = rawTokens(name);
|
|
12912
13023
|
const upper = name.toUpperCase();
|
|
12913
|
-
return
|
|
13024
|
+
return tokens3.length === 1 && tokens3[0] === name && NAME_TOKEN.test(name) && name.length >= 3 && !isDefinitionId(upper) && !PROPOSAL_ID_PATTERN.test(upper);
|
|
12914
13025
|
}
|
|
12915
13026
|
function listAll2(store, type) {
|
|
12916
13027
|
const out = [];
|
|
@@ -12999,9 +13110,10 @@ function resolveSeeds(task, truth, store, explicit = [], groups = NO_CALLABLE_GR
|
|
|
12999
13110
|
exact++;
|
|
13000
13111
|
}
|
|
13001
13112
|
}
|
|
13002
|
-
const
|
|
13113
|
+
const words = taskWords(task);
|
|
13114
|
+
const tokens3 = words.map((w) => w.token);
|
|
13003
13115
|
const idTokens2 = /* @__PURE__ */ new Set();
|
|
13004
|
-
for (const token of
|
|
13116
|
+
for (const token of tokens3) {
|
|
13005
13117
|
const upper = token.toUpperCase();
|
|
13006
13118
|
if (PROPOSAL_ID_PATTERN.test(upper)) {
|
|
13007
13119
|
proposalIds.add(upper);
|
|
@@ -13021,25 +13133,43 @@ function resolveSeeds(task, truth, store, explicit = [], groups = NO_CALLABLE_GR
|
|
|
13021
13133
|
}
|
|
13022
13134
|
const symbols = listAll2(store, "symbol");
|
|
13023
13135
|
const files = listAll2(store, "file");
|
|
13024
|
-
|
|
13136
|
+
const exactTokens = /* @__PURE__ */ new Set();
|
|
13137
|
+
for (const { token, pathText } of words) {
|
|
13025
13138
|
if (idTokens2.has(token))
|
|
13026
13139
|
continue;
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13140
|
+
const p = pathOf(pathText);
|
|
13141
|
+
const node = p === void 0 ? void 0 : store.getNode(fileRef(p));
|
|
13142
|
+
if (node !== void 0) {
|
|
13143
|
+
add(node, "path", pathText, SEED_STRENGTH.path);
|
|
13144
|
+
exact++;
|
|
13145
|
+
exactTokens.add(token);
|
|
13146
|
+
continue;
|
|
13147
|
+
}
|
|
13148
|
+
const root = rootFileOf(pathText);
|
|
13149
|
+
const rootNode = root === void 0 ? void 0 : store.getNode(fileRef(root.path));
|
|
13150
|
+
const asRootFile = () => {
|
|
13151
|
+
if (rootNode !== void 0) {
|
|
13152
|
+
add(rootNode, "path", pathText, SEED_STRENGTH.path);
|
|
13032
13153
|
exact++;
|
|
13154
|
+
exactTokens.add(token);
|
|
13033
13155
|
}
|
|
13156
|
+
};
|
|
13157
|
+
if (rootNode !== void 0 && root?.leadingDot === true) {
|
|
13158
|
+
asRootFile();
|
|
13034
13159
|
continue;
|
|
13035
13160
|
}
|
|
13161
|
+
if (token.includes("/"))
|
|
13162
|
+
continue;
|
|
13036
13163
|
const name = token.replace(/\(\)$/u, "");
|
|
13037
|
-
if (!NAME_TOKEN.test(name) || name.length < 3)
|
|
13164
|
+
if (!NAME_TOKEN.test(name) || name.length < 3) {
|
|
13165
|
+
asRootFile();
|
|
13038
13166
|
continue;
|
|
13167
|
+
}
|
|
13039
13168
|
const qualified = symbols.filter((s) => s.payload.qualifiedName === name);
|
|
13040
13169
|
if (qualified.length === 1 && qualified[0] !== void 0) {
|
|
13041
13170
|
add(qualified[0], "symbol", token, SEED_STRENGTH.symbol);
|
|
13042
13171
|
exact++;
|
|
13172
|
+
exactTokens.add(token);
|
|
13043
13173
|
continue;
|
|
13044
13174
|
}
|
|
13045
13175
|
if (qualified.length > 1 && name.includes(".")) {
|
|
@@ -13047,6 +13177,7 @@ function resolveSeeds(task, truth, store, explicit = [], groups = NO_CALLABLE_GR
|
|
|
13047
13177
|
for (const n of qualified)
|
|
13048
13178
|
add(n, "symbol", token, SEED_STRENGTH.symbol);
|
|
13049
13179
|
exact++;
|
|
13180
|
+
exactTokens.add(token);
|
|
13050
13181
|
continue;
|
|
13051
13182
|
}
|
|
13052
13183
|
if (qualified.length <= SYMBOL_NAME_MAX_MATCHES)
|
|
@@ -13054,17 +13185,23 @@ function resolveSeeds(task, truth, store, explicit = [], groups = NO_CALLABLE_GR
|
|
|
13054
13185
|
continue;
|
|
13055
13186
|
}
|
|
13056
13187
|
const named = qualified.length > 1 ? qualified : symbols.filter((s) => s.payload.name === name);
|
|
13188
|
+
if (qualified.length === 0 && named.length === 0) {
|
|
13189
|
+
asRootFile();
|
|
13190
|
+
continue;
|
|
13191
|
+
}
|
|
13057
13192
|
if (named.length === 1 && named[0] !== void 0) {
|
|
13058
13193
|
add(named[0], "symbol-name", token, SEED_STRENGTH["symbol-name"]);
|
|
13059
13194
|
exact++;
|
|
13195
|
+
exactTokens.add(token);
|
|
13060
13196
|
} else if (oneGroup(groups, named)) {
|
|
13061
13197
|
for (const n of named)
|
|
13062
13198
|
add(n, "symbol-name", token, SEED_STRENGTH["symbol-name"]);
|
|
13063
13199
|
exact++;
|
|
13200
|
+
exactTokens.add(token);
|
|
13064
13201
|
} else if (named.length > 1 && named.length <= SYMBOL_NAME_MAX_MATCHES)
|
|
13065
13202
|
ambiguities.push({ term: name, reason: "symbol-name", options: named.map(option) });
|
|
13066
13203
|
}
|
|
13067
|
-
const ranked = bm25(keywordDocs(truth, store, symbols, files), searchTerms(
|
|
13204
|
+
const ranked = bm25(keywordDocs(truth, store, symbols, files), searchTerms(keywordQueryTokens(tokens3, idTokens2, exactTokens).join(" ")));
|
|
13068
13205
|
const best = ranked[0]?.score ?? 0;
|
|
13069
13206
|
const keyword = ranked.filter((r) => r.score >= best * KEYWORD.minFraction).slice(0, KEYWORD.top);
|
|
13070
13207
|
if (exact === 0 && ambiguities.length === 0) {
|
|
@@ -13580,8 +13717,8 @@ function renderGapQuestions(assessment, options = {}) {
|
|
|
13580
13717
|
}
|
|
13581
13718
|
|
|
13582
13719
|
// packages/director/dist/init/documents.js
|
|
13583
|
-
import
|
|
13584
|
-
import
|
|
13720
|
+
import fs18 from "node:fs";
|
|
13721
|
+
import path22 from "node:path";
|
|
13585
13722
|
var DOC_EXT = /\.(?:md|markdown|mdx|rst|adoc|txt)$/iu;
|
|
13586
13723
|
var KEYWORDS = ["architecture", "design", "spec", "specification", "specs", "requirements", "requirement", "prd", "rfc", "adr", "decisions", "decision", "roadmap", "vision", "overview", "plan", "\uAE30\uD68D", "\uC124\uACC4", "\uC694\uAD6C\uC0AC\uD56D"];
|
|
13587
13724
|
var LEGAL = /^(?:license|licence|copying|notice|code_of_conduct|code-of-conduct|security|changelog|changes|history)(?:\.|$)/iu;
|
|
@@ -13644,7 +13781,7 @@ function rank2(p) {
|
|
|
13644
13781
|
}
|
|
13645
13782
|
function readBounded(root, p, maxBytes) {
|
|
13646
13783
|
try {
|
|
13647
|
-
if (
|
|
13784
|
+
if (fs18.statSync(path22.join(root, p)).size > maxBytes)
|
|
13648
13785
|
return void 0;
|
|
13649
13786
|
} catch {
|
|
13650
13787
|
return void 0;
|
|
@@ -13701,21 +13838,21 @@ function discoverDocuments(root, files, rootDescription, options = {}) {
|
|
|
13701
13838
|
|
|
13702
13839
|
// packages/director/dist/init/inspect.js
|
|
13703
13840
|
import { createHash as createHash15 } from "node:crypto";
|
|
13704
|
-
import
|
|
13705
|
-
import
|
|
13706
|
-
var
|
|
13841
|
+
import fs19 from "node:fs";
|
|
13842
|
+
import path23 from "node:path";
|
|
13843
|
+
var REGENERABLE2 = new Set(WRITE_AREAS.regenerable.map((a) => a.replace(/\/$/u, "")));
|
|
13707
13844
|
var PROJECT_FILE = `${STATE_DIR_NAME}/${PROJECT_FILE_NAME}`;
|
|
13708
13845
|
function typeOf(stat) {
|
|
13709
13846
|
return stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : stat.isFile() ? "file" : "other";
|
|
13710
13847
|
}
|
|
13711
13848
|
function walk2(root, dir, out) {
|
|
13712
|
-
for (const name of
|
|
13849
|
+
for (const name of fs19.readdirSync(path23.join(root, dir)).sort(compareUtf8)) {
|
|
13713
13850
|
const rel = `${dir}/${name}`;
|
|
13714
|
-
if (dir === STATE_DIR_NAME &&
|
|
13851
|
+
if (dir === STATE_DIR_NAME && REGENERABLE2.has(name))
|
|
13715
13852
|
continue;
|
|
13716
|
-
const type = typeOf(
|
|
13853
|
+
const type = typeOf(fs19.lstatSync(path23.join(root, rel)));
|
|
13717
13854
|
if (type === "file") {
|
|
13718
|
-
out.push({ path: rel, type, hash: `sha256:${createHash15("sha256").update(
|
|
13855
|
+
out.push({ path: rel, type, hash: `sha256:${createHash15("sha256").update(fs19.readFileSync(path23.join(root, rel))).digest("hex")}` });
|
|
13719
13856
|
} else {
|
|
13720
13857
|
out.push({ path: rel, type });
|
|
13721
13858
|
if (type === "directory")
|
|
@@ -13727,7 +13864,7 @@ function inspectStateDirectory(root) {
|
|
|
13727
13864
|
const entries = [];
|
|
13728
13865
|
let top;
|
|
13729
13866
|
try {
|
|
13730
|
-
top = typeOf(
|
|
13867
|
+
top = typeOf(fs19.lstatSync(path23.join(root, STATE_DIR_NAME)));
|
|
13731
13868
|
} catch {
|
|
13732
13869
|
top = void 0;
|
|
13733
13870
|
}
|
|
@@ -13793,12 +13930,12 @@ var INIT_DIRECTORIES = [
|
|
|
13793
13930
|
var GITIGNORE_TEXT = "generated/\ncache/\nruntime/\n";
|
|
13794
13931
|
|
|
13795
13932
|
// packages/director/dist/init/observe.js
|
|
13796
|
-
import
|
|
13797
|
-
import
|
|
13933
|
+
import fs21 from "node:fs";
|
|
13934
|
+
import path25 from "node:path";
|
|
13798
13935
|
|
|
13799
13936
|
// packages/director/dist/init/stack.js
|
|
13800
|
-
import
|
|
13801
|
-
import
|
|
13937
|
+
import fs20 from "node:fs";
|
|
13938
|
+
import path24 from "node:path";
|
|
13802
13939
|
var MAX_MANIFEST_BYTES = 1024 * 1024;
|
|
13803
13940
|
var MAX_DEPTH = 4;
|
|
13804
13941
|
var CAP = 20;
|
|
@@ -13825,7 +13962,7 @@ function stackManifestKind(p) {
|
|
|
13825
13962
|
}
|
|
13826
13963
|
function read(root, file) {
|
|
13827
13964
|
try {
|
|
13828
|
-
if (
|
|
13965
|
+
if (fs20.statSync(path24.join(root, file)).size > MAX_MANIFEST_BYTES)
|
|
13829
13966
|
return void 0;
|
|
13830
13967
|
} catch {
|
|
13831
13968
|
return void 0;
|
|
@@ -13975,7 +14112,7 @@ var extensionOf4 = (p) => {
|
|
|
13975
14112
|
var baseName2 = (p) => p.slice(p.lastIndexOf("/") + 1);
|
|
13976
14113
|
function readJson(root, file) {
|
|
13977
14114
|
try {
|
|
13978
|
-
if (
|
|
14115
|
+
if (fs21.statSync(path25.join(root, file)).size > MAX_MANIFEST_BYTES2)
|
|
13979
14116
|
return void 0;
|
|
13980
14117
|
} catch {
|
|
13981
14118
|
return void 0;
|
|
@@ -14085,7 +14222,7 @@ function observeRepository(root, scan, git, workingTree) {
|
|
|
14085
14222
|
technical.push({ manifest: "package.json", field: "packageManager", value: declaredPm });
|
|
14086
14223
|
const pkgName = str(rootPkg?.name);
|
|
14087
14224
|
const pkgDescription = str(rootPkg?.description);
|
|
14088
|
-
const name = pkgName !== void 0 ? { value: pkgName, source: { path: "package.json", field: "name", kind: "manifest" } } : { value:
|
|
14225
|
+
const name = pkgName !== void 0 ? { value: pkgName, source: { path: "package.json", field: "name", kind: "manifest" } } : { value: path25.basename(path25.resolve(root)), source: { kind: "directory" } };
|
|
14089
14226
|
return {
|
|
14090
14227
|
provenance: "observed",
|
|
14091
14228
|
name,
|
|
@@ -14220,9 +14357,9 @@ async function planInit(root, options = {}) {
|
|
|
14220
14357
|
|
|
14221
14358
|
// packages/director/dist/init/apply.js
|
|
14222
14359
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
14223
|
-
import
|
|
14360
|
+
import fs22 from "node:fs";
|
|
14224
14361
|
import fsp4 from "node:fs/promises";
|
|
14225
|
-
import
|
|
14362
|
+
import path26 from "node:path";
|
|
14226
14363
|
|
|
14227
14364
|
// packages/director/dist/init/render.js
|
|
14228
14365
|
var OPEN_TEXT = {
|
|
@@ -14278,13 +14415,13 @@ var nodeInitFileSystem = {
|
|
|
14278
14415
|
await fsp4.mkdir(absolute);
|
|
14279
14416
|
return true;
|
|
14280
14417
|
} catch (error) {
|
|
14281
|
-
if (error.code === "EEXIST" &&
|
|
14418
|
+
if (error.code === "EEXIST" && fs22.lstatSync(absolute).isDirectory())
|
|
14282
14419
|
return false;
|
|
14283
14420
|
throw error;
|
|
14284
14421
|
}
|
|
14285
14422
|
},
|
|
14286
14423
|
writeFile: (absolute, text4) => fsp4.writeFile(absolute, text4, { encoding: "utf8", flag: "wx" }),
|
|
14287
|
-
copyFile: (from, to) => fsp4.copyFile(from, to,
|
|
14424
|
+
copyFile: (from, to) => fsp4.copyFile(from, to, fs22.constants.COPYFILE_EXCL),
|
|
14288
14425
|
async place(staged, target) {
|
|
14289
14426
|
try {
|
|
14290
14427
|
await fsp4.link(staged, target);
|
|
@@ -14292,7 +14429,7 @@ var nodeInitFileSystem = {
|
|
|
14292
14429
|
const code = error.code;
|
|
14293
14430
|
if (code !== "EPERM" && code !== "ENOTSUP" && code !== "ENOSYS" && code !== "EXDEV")
|
|
14294
14431
|
throw error;
|
|
14295
|
-
await fsp4.copyFile(staged, target,
|
|
14432
|
+
await fsp4.copyFile(staged, target, fs22.constants.COPYFILE_EXCL);
|
|
14296
14433
|
}
|
|
14297
14434
|
},
|
|
14298
14435
|
removeFile: (absolute) => fsp4.rm(absolute, { force: true }),
|
|
@@ -14394,7 +14531,7 @@ async function applyInitPlan(root, plan, answers, options = {}) {
|
|
|
14394
14531
|
const createdFiles = [];
|
|
14395
14532
|
const runtimeRel = `${STATE_DIR_NAME}/runtime`;
|
|
14396
14533
|
const stagingRel = `${runtimeRel}/init-${randomBytes2(6).toString("hex")}`;
|
|
14397
|
-
const stagingAbs =
|
|
14534
|
+
const stagingAbs = path26.join(root, stagingRel);
|
|
14398
14535
|
let stagingMade = false;
|
|
14399
14536
|
const rollback = async () => {
|
|
14400
14537
|
for (const f of [...createdFiles].reverse())
|
|
@@ -14424,13 +14561,13 @@ async function applyInitPlan(root, plan, answers, options = {}) {
|
|
|
14424
14561
|
}
|
|
14425
14562
|
await io.mkdir(stagingAbs);
|
|
14426
14563
|
stagingMade = true;
|
|
14427
|
-
const stageAbs = (p) =>
|
|
14564
|
+
const stageAbs = (p) => path26.join(stagingAbs, ...p.split("/"));
|
|
14428
14565
|
const ensureStageDir = async (p) => {
|
|
14429
|
-
await fsp4.mkdir(
|
|
14566
|
+
await fsp4.mkdir(path26.dirname(stageAbs(p)), { recursive: true });
|
|
14430
14567
|
};
|
|
14431
14568
|
for (const p of inspection.existing.filter(isTruthFile)) {
|
|
14432
14569
|
await ensureStageDir(p);
|
|
14433
|
-
await io.copyFile(
|
|
14570
|
+
await io.copyFile(path26.join(root, p), stageAbs(p));
|
|
14434
14571
|
}
|
|
14435
14572
|
for (const [p, f] of files) {
|
|
14436
14573
|
await ensureStageDir(p);
|
|
@@ -14445,10 +14582,10 @@ async function applyInitPlan(root, plan, answers, options = {}) {
|
|
|
14445
14582
|
return failure([createDiagnostic("INIT_VALIDATION_FAILED", `The planned Project Truth does not load cleanly: ${(ours.length > 0 ? ours : errors).map((d) => `${d.code} ${d.source?.path ?? ""}`.trim()).join("; ")}`), ...ours.length > 0 ? ours : errors]);
|
|
14446
14583
|
}
|
|
14447
14584
|
for (const d of plan.directories)
|
|
14448
|
-
await mkdir(
|
|
14585
|
+
await mkdir(path26.join(root, d));
|
|
14449
14586
|
const order = [...files].sort(([a], [b]) => a === INIT_FILES.project ? 1 : b === INIT_FILES.project ? -1 : compareUtf8(a, b));
|
|
14450
14587
|
for (const [p] of order) {
|
|
14451
|
-
const target =
|
|
14588
|
+
const target = path26.join(root, p);
|
|
14452
14589
|
await io.place(stageAbs(p), target);
|
|
14453
14590
|
createdFiles.push(target);
|
|
14454
14591
|
}
|
|
@@ -14476,8 +14613,8 @@ async function applyInitPlan(root, plan, answers, options = {}) {
|
|
|
14476
14613
|
import { performance as performance5 } from "node:perf_hooks";
|
|
14477
14614
|
|
|
14478
14615
|
// packages/director/dist/llm/contract/cache.js
|
|
14479
|
-
import
|
|
14480
|
-
import
|
|
14616
|
+
import fs23 from "node:fs";
|
|
14617
|
+
import path27 from "node:path";
|
|
14481
14618
|
var LLM_CACHE_DIR = `${STATE_DIR_NAME}/cache/llm`;
|
|
14482
14619
|
var FORMAT4 = "duo.llm-cache/1";
|
|
14483
14620
|
function responseCacheKey(identity, request) {
|
|
@@ -14494,10 +14631,10 @@ function responseCacheKey(identity, request) {
|
|
|
14494
14631
|
var fileOf2 = (key2) => `${LLM_CACHE_DIR}/${key2.replace(/^sha256:/u, "")}.json`;
|
|
14495
14632
|
function readCachedResponse(root, key2) {
|
|
14496
14633
|
try {
|
|
14497
|
-
const target =
|
|
14498
|
-
if (
|
|
14634
|
+
const target = path27.join(root, fileOf2(key2));
|
|
14635
|
+
if (fs23.lstatSync(target).isSymbolicLink())
|
|
14499
14636
|
return void 0;
|
|
14500
|
-
const entry = JSON.parse(
|
|
14637
|
+
const entry = JSON.parse(fs23.readFileSync(target, "utf8"));
|
|
14501
14638
|
return entry.format === FORMAT4 && entry.key === key2 && entry.response?.status === "success" ? entry.response : void 0;
|
|
14502
14639
|
} catch {
|
|
14503
14640
|
return void 0;
|
|
@@ -14507,14 +14644,14 @@ function writeCachedResponse(root, key2, response) {
|
|
|
14507
14644
|
const allowed = checkWriteBoundary(root, fileOf2(key2), "regenerable");
|
|
14508
14645
|
if (allowed.value === void 0)
|
|
14509
14646
|
return;
|
|
14510
|
-
const target =
|
|
14647
|
+
const target = path27.join(root, allowed.value.path);
|
|
14511
14648
|
const temp = `${target}.${process.pid}.tmp`;
|
|
14512
14649
|
try {
|
|
14513
|
-
|
|
14514
|
-
|
|
14515
|
-
|
|
14650
|
+
fs23.mkdirSync(path27.dirname(target), { recursive: true });
|
|
14651
|
+
fs23.writeFileSync(temp, JSON.stringify({ format: FORMAT4, key: key2, response }));
|
|
14652
|
+
fs23.renameSync(temp, target);
|
|
14516
14653
|
} catch {
|
|
14517
|
-
|
|
14654
|
+
fs23.rmSync(temp, { force: true });
|
|
14518
14655
|
}
|
|
14519
14656
|
}
|
|
14520
14657
|
|
|
@@ -15199,10 +15336,11 @@ async function reviewChanges(root, request, options) {
|
|
|
15199
15336
|
baseline,
|
|
15200
15337
|
freshness,
|
|
15201
15338
|
diff: { identity: diff.value.identity, from: diff.value.from, to: diff.value.to, files: allFiles },
|
|
15339
|
+
// C241: lifecycle facts are added after the verdict and the semantic step, so neither sees them.
|
|
15202
15340
|
seeds,
|
|
15203
15341
|
verdict: verdict.verdict,
|
|
15204
15342
|
verdictBasis: verdict.basis,
|
|
15205
|
-
claims,
|
|
15343
|
+
claims: withDecisionAuthority(truth, claims),
|
|
15206
15344
|
evidence,
|
|
15207
15345
|
...gaps === void 0 ? {} : { gaps },
|
|
15208
15346
|
context: {
|
|
@@ -15255,10 +15393,27 @@ function provenanceLabel(value, locale = "en") {
|
|
|
15255
15393
|
return PROVENANCE_LABELS[locale][value];
|
|
15256
15394
|
}
|
|
15257
15395
|
|
|
15396
|
+
// packages/director/dist/review/authority-label.js
|
|
15397
|
+
var AUTHORITY_LABELS = {
|
|
15398
|
+
en: { current: "current authority", superseded: "superseded", inactive: "not current authority ({state})", supersedes: "supersedes {id}", supersededBy: "superseded by {id}" },
|
|
15399
|
+
ko: { current: "\uD604\uC7AC authority", superseded: "\uB300\uCCB4\uB428", inactive: "\uD604\uC7AC authority \uC544\uB2D8({state})", supersedes: "\uB300\uCCB4 \uB300\uC0C1 {id}", supersededBy: "\uB300\uCCB4\uD55C Decision {id}" }
|
|
15400
|
+
};
|
|
15401
|
+
function decisionAuthorityParts(a, locale = "en") {
|
|
15402
|
+
const l = AUTHORITY_LABELS[locale];
|
|
15403
|
+
return [
|
|
15404
|
+
a.active ? l.current : a.state === "superseded" ? l.superseded : l.inactive.replace("{state}", a.state),
|
|
15405
|
+
...a.supersedes === null ? [] : [l.supersedes.replace("{id}", a.supersedes)],
|
|
15406
|
+
...a.supersededBy === null ? [] : [l.supersededBy.replace("{id}", a.supersededBy)]
|
|
15407
|
+
];
|
|
15408
|
+
}
|
|
15409
|
+
function decisionAuthorityLabel(a, locale = "en") {
|
|
15410
|
+
return decisionAuthorityParts(a, locale).join(" \xB7 ");
|
|
15411
|
+
}
|
|
15412
|
+
|
|
15258
15413
|
// packages/director/dist/runtime/metrics.js
|
|
15259
|
-
import
|
|
15414
|
+
import fs24 from "node:fs";
|
|
15260
15415
|
import fsp5 from "node:fs/promises";
|
|
15261
|
-
import
|
|
15416
|
+
import path28 from "node:path";
|
|
15262
15417
|
var METRICS_PATH = `${STATE_DIR_NAME}/runtime/metrics.jsonl`;
|
|
15263
15418
|
function reviewLlmMetric(r) {
|
|
15264
15419
|
const a = r.semanticAssist;
|
|
@@ -15273,13 +15428,13 @@ function reviewLlmMetric(r) {
|
|
|
15273
15428
|
};
|
|
15274
15429
|
}
|
|
15275
15430
|
async function appendRuntimeMetric(root, metric) {
|
|
15276
|
-
if (!
|
|
15431
|
+
if (!fs24.existsSync(path28.join(root, STATE_DIR_NAME, PROJECT_FILE_NAME)))
|
|
15277
15432
|
return success("skipped");
|
|
15278
15433
|
const guarded2 = guardWrite(root, METRICS_PATH, "regenerable");
|
|
15279
15434
|
if (guarded2.value === void 0)
|
|
15280
15435
|
return failure(guarded2.diagnostics);
|
|
15281
15436
|
try {
|
|
15282
|
-
await fsp5.mkdir(
|
|
15437
|
+
await fsp5.mkdir(path28.dirname(guarded2.value.absolute), { recursive: true });
|
|
15283
15438
|
await fsp5.appendFile(guarded2.value.absolute, `${JSON.stringify(metric)}
|
|
15284
15439
|
`, "utf8");
|
|
15285
15440
|
return success("written");
|
|
@@ -15290,7 +15445,7 @@ async function appendRuntimeMetric(root, metric) {
|
|
|
15290
15445
|
function readRuntimeMetrics(root, options = {}) {
|
|
15291
15446
|
let text4;
|
|
15292
15447
|
try {
|
|
15293
|
-
text4 =
|
|
15448
|
+
text4 = fs24.readFileSync(path28.join(root, METRICS_PATH), "utf8");
|
|
15294
15449
|
} catch {
|
|
15295
15450
|
return [];
|
|
15296
15451
|
}
|
|
@@ -16070,8 +16225,8 @@ function getDecision(root, id3) {
|
|
|
16070
16225
|
}
|
|
16071
16226
|
|
|
16072
16227
|
// packages/integration/dist/operations/search.js
|
|
16073
|
-
import
|
|
16074
|
-
import
|
|
16228
|
+
import fs25 from "node:fs";
|
|
16229
|
+
import path29 from "node:path";
|
|
16075
16230
|
var EVIDENCE_SEARCH_FORMAT = "duo.evidence-search/1";
|
|
16076
16231
|
var SEARCH_NOTICE = "Evidence candidates found by exact or lexical match. They are not confirmed intent, Decisions or Requirements.";
|
|
16077
16232
|
function searchEvidence(root, query, limit) {
|
|
@@ -16123,12 +16278,12 @@ function searchEvidence(root, query, limit) {
|
|
|
16123
16278
|
}
|
|
16124
16279
|
}
|
|
16125
16280
|
});
|
|
16126
|
-
const dir =
|
|
16127
|
-
const names =
|
|
16281
|
+
const dir = path29.join(root, REVIEWS_DIR);
|
|
16282
|
+
const names = fs25.existsSync(dir) ? fs25.readdirSync(dir).filter((n) => /^review-[0-9a-f]{16}\.json$/u.test(n)).sort(compareUtf8) : [];
|
|
16128
16283
|
for (const name of names) {
|
|
16129
16284
|
let record;
|
|
16130
16285
|
try {
|
|
16131
|
-
record = JSON.parse(
|
|
16286
|
+
record = JSON.parse(fs25.readFileSync(path29.join(dir, name), "utf8"));
|
|
16132
16287
|
} catch {
|
|
16133
16288
|
continue;
|
|
16134
16289
|
}
|
|
@@ -16181,13 +16336,13 @@ function proposeDecision(root, agentName, input) {
|
|
|
16181
16336
|
}
|
|
16182
16337
|
|
|
16183
16338
|
// packages/integration/dist/operations/doctor.js
|
|
16184
|
-
import
|
|
16339
|
+
import path33 from "node:path";
|
|
16185
16340
|
|
|
16186
16341
|
// packages/integration/dist/agents/integration.js
|
|
16187
16342
|
import { createHash as createHash16 } from "node:crypto";
|
|
16188
|
-
import
|
|
16343
|
+
import fs27 from "node:fs";
|
|
16189
16344
|
import fsp6 from "node:fs/promises";
|
|
16190
|
-
import
|
|
16345
|
+
import path32 from "node:path";
|
|
16191
16346
|
|
|
16192
16347
|
// packages/integration/dist/mcp/probe.js
|
|
16193
16348
|
import { Client } from "@modelcontextprotocol/client";
|
|
@@ -16350,7 +16505,7 @@ var TOOLS = {
|
|
|
16350
16505
|
name: "duo_get_context",
|
|
16351
16506
|
title: "Project direction context for a task",
|
|
16352
16507
|
readOnly: true,
|
|
16353
|
-
description:
|
|
16508
|
+
description: 'Returns project direction context for a task: confirmed intent, relevant code and tests, pending decisions and Knowledge Gaps. Does not modify or index the repository. If the index is stale, returns status index-required (run duoctl index). Surfaced gaps are open questions, not instructions; ask the human only when gaps.requiresHumanInput is true. Pending proposals are not confirmed decisions. If the task names only an Issue ID and you know which files you will change, include their repository paths in the task (for example "TASK-015 apps/desktop/src/main.ts").',
|
|
16354
16509
|
run: async (args, ctx) => {
|
|
16355
16510
|
const op = await projectContext(ctx.root, { task: args.task, ...args.budget === void 0 ? {} : { budget: args.budget }, ...args.profile === void 0 ? {} : { profile: args.profile } }, {
|
|
16356
16511
|
signal: ctx.signal,
|
|
@@ -16393,7 +16548,8 @@ ${advice.join("\n")}`);
|
|
|
16393
16548
|
const r = p;
|
|
16394
16549
|
if (r.status === "index-required")
|
|
16395
16550
|
return "INDEX_REQUIRED: the DUO index is not current. Run duoctl index, then review again.";
|
|
16396
|
-
const
|
|
16551
|
+
const tags = (c) => [...c.blockEligible ? ["blocking"] : [], ...c.decisionAuthority === void 0 ? [] : decisionAuthorityParts(c.decisionAuthority)];
|
|
16552
|
+
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)})`}${tags(c).length === 0 ? "" : ` [${tags(c).join("; ")}]`}`);
|
|
16397
16553
|
const a = r.semanticAssist;
|
|
16398
16554
|
const semantic = a.status === "not-requested" ? [] : [
|
|
16399
16555
|
`Semantic assistance (supplemental, never blocks): ${a.status}${a.failure === void 0 ? "" : ` (${a.failure})`}`,
|
|
@@ -16465,7 +16621,7 @@ ${advice.join("\n")}`);
|
|
|
16465
16621
|
};
|
|
16466
16622
|
|
|
16467
16623
|
// packages/integration/dist/agents/adapter.js
|
|
16468
|
-
import
|
|
16624
|
+
import path30 from "node:path";
|
|
16469
16625
|
var SERVER_NAME = "duo-director";
|
|
16470
16626
|
function isDuoLaunch(entry) {
|
|
16471
16627
|
if (entry === null || typeof entry !== "object")
|
|
@@ -16474,7 +16630,7 @@ function isDuoLaunch(entry) {
|
|
|
16474
16630
|
if (typeof e.command !== "string" || !Array.isArray(e.args))
|
|
16475
16631
|
return false;
|
|
16476
16632
|
const args = e.args.filter((a) => typeof a === "string");
|
|
16477
|
-
const base =
|
|
16633
|
+
const base = path30.basename(e.command.replaceAll("\\", "/")).toLowerCase().replace(/\.(exe|cmd|bat|ps1)$/u, "");
|
|
16478
16634
|
return args.includes("mcp") && (base === "duoctl" || args.includes("duoctl"));
|
|
16479
16635
|
}
|
|
16480
16636
|
var sameLaunch = (a, b) => a.command === b.command && a.args.length === b.args.length && a.args.every((x, i) => x === b.args[i]);
|
|
@@ -16734,8 +16890,8 @@ var codexAdapter = {
|
|
|
16734
16890
|
};
|
|
16735
16891
|
|
|
16736
16892
|
// packages/integration/dist/agents/launcher.js
|
|
16737
|
-
import
|
|
16738
|
-
import
|
|
16893
|
+
import fs26 from "node:fs";
|
|
16894
|
+
import path31 from "node:path";
|
|
16739
16895
|
var PATH_LAUNCHER = { kind: "path", command: "duoctl", argsPrefix: [] };
|
|
16740
16896
|
var NPX_LAUNCHER = { kind: "npx", command: "npx", argsPrefix: ["--no-install", "duoctl"] };
|
|
16741
16897
|
var currentHost = () => ({ env: process.env, platform: process.platform });
|
|
@@ -16749,21 +16905,21 @@ function pathValue(host2) {
|
|
|
16749
16905
|
}
|
|
16750
16906
|
function candidates2(dir, name, host2) {
|
|
16751
16907
|
if (host2.platform !== "win32")
|
|
16752
|
-
return [
|
|
16908
|
+
return [path31.join(dir, name)];
|
|
16753
16909
|
const exts = (host2.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter((e) => e !== "").map((e) => e.toLowerCase());
|
|
16754
|
-
return
|
|
16910
|
+
return path31.extname(name) !== "" ? [path31.join(dir, name)] : exts.map((e) => path31.join(dir, name + e));
|
|
16755
16911
|
}
|
|
16756
16912
|
function findOnPath(name, host2 = currentHost()) {
|
|
16757
16913
|
for (const dir of pathValue(host2).split(host2.platform === "win32" ? ";" : ":")) {
|
|
16758
|
-
if (dir === "" || !
|
|
16914
|
+
if (dir === "" || !path31.isAbsolute(dir))
|
|
16759
16915
|
continue;
|
|
16760
16916
|
for (const file of candidates2(dir, name, host2)) {
|
|
16761
16917
|
try {
|
|
16762
|
-
const st =
|
|
16918
|
+
const st = fs26.statSync(file);
|
|
16763
16919
|
if (!st.isFile())
|
|
16764
16920
|
continue;
|
|
16765
16921
|
if (host2.platform !== "win32")
|
|
16766
|
-
|
|
16922
|
+
fs26.accessSync(file, fs26.constants.X_OK);
|
|
16767
16923
|
return file;
|
|
16768
16924
|
} catch {
|
|
16769
16925
|
}
|
|
@@ -16774,7 +16930,7 @@ function findOnPath(name, host2 = currentHost()) {
|
|
|
16774
16930
|
function shadowing(root, command2) {
|
|
16775
16931
|
let names;
|
|
16776
16932
|
try {
|
|
16777
|
-
names =
|
|
16933
|
+
names = fs26.readdirSync(root);
|
|
16778
16934
|
} catch {
|
|
16779
16935
|
return [];
|
|
16780
16936
|
}
|
|
@@ -16788,12 +16944,12 @@ function checkLauncher(root, launcher, host2 = currentHost()) {
|
|
|
16788
16944
|
const diagnostics = [];
|
|
16789
16945
|
const warnings = [];
|
|
16790
16946
|
const { command: command2 } = launcher;
|
|
16791
|
-
if (!BARE.test(command2) && !
|
|
16947
|
+
if (!BARE.test(command2) && !path31.isAbsolute(command2)) {
|
|
16792
16948
|
return { available: false, warnings, diagnostics: [createDiagnostic("AGENT_LAUNCHER_UNAVAILABLE", `launcher command "${command2}" must be a bare command name on PATH or an absolute path; relative paths are refused`)] };
|
|
16793
16949
|
}
|
|
16794
|
-
if (
|
|
16950
|
+
if (path31.isAbsolute(command2)) {
|
|
16795
16951
|
warnings.push(`launcher "${command2}" is an absolute path: the configuration will not work on other machines or after a move`);
|
|
16796
|
-
const ok =
|
|
16952
|
+
const ok = fs26.existsSync(command2);
|
|
16797
16953
|
if (!ok)
|
|
16798
16954
|
diagnostics.push(createDiagnostic("AGENT_LAUNCHER_UNAVAILABLE", `launcher "${command2}" does not exist`));
|
|
16799
16955
|
return { available: ok, ...ok ? { resolved: command2 } : {}, diagnostics, warnings };
|
|
@@ -16808,8 +16964,8 @@ function checkLauncher(root, launcher, host2 = currentHost()) {
|
|
|
16808
16964
|
}
|
|
16809
16965
|
let localBin;
|
|
16810
16966
|
if (launcher.kind === "npx") {
|
|
16811
|
-
const bin = ["duoctl", "duoctl.cmd"].map((n) =>
|
|
16812
|
-
localBin = bin.find((b) =>
|
|
16967
|
+
const bin = ["duoctl", "duoctl.cmd"].map((n) => path31.join(root, "node_modules", ".bin", n));
|
|
16968
|
+
localBin = bin.find((b) => fs26.existsSync(b));
|
|
16813
16969
|
if (localBin === void 0)
|
|
16814
16970
|
diagnostics.push(createDiagnostic("AGENT_LAUNCHER_UNAVAILABLE", "node_modules/.bin/duoctl is missing: the npx launcher needs @duo-director/cli installed in this project (npx --no-install never downloads it)"));
|
|
16815
16971
|
}
|
|
@@ -16828,13 +16984,13 @@ var EXPECTED_MCP_TOOLS = Object.keys(TOOLS).sort();
|
|
|
16828
16984
|
var BACKUP_DIR = `${STATE_DIR_NAME}/runtime/backup`;
|
|
16829
16985
|
function readTarget(root, rel) {
|
|
16830
16986
|
const guarded2 = guardWrite(root, rel, "agent-integration");
|
|
16831
|
-
const abs =
|
|
16987
|
+
const abs = path32.join(root, rel);
|
|
16832
16988
|
let bytes;
|
|
16833
16989
|
try {
|
|
16834
|
-
const st =
|
|
16990
|
+
const st = fs27.lstatSync(abs);
|
|
16835
16991
|
if (!st.isFile())
|
|
16836
16992
|
return { hash: "not-a-file", problem: `${rel} is not a regular file` };
|
|
16837
|
-
bytes =
|
|
16993
|
+
bytes = fs27.readFileSync(abs);
|
|
16838
16994
|
} catch {
|
|
16839
16995
|
return guarded2.value === void 0 ? { hash: null, problem: guarded2.diagnostics.map((d) => d.message).join("; ") } : { hash: null };
|
|
16840
16996
|
}
|
|
@@ -16843,7 +16999,7 @@ function readTarget(root, rel) {
|
|
|
16843
16999
|
return { hash: hash2, problem: guarded2.diagnostics.map((d) => d.message).join("; ") };
|
|
16844
17000
|
return { text: bytes.toString("utf8"), hash: hash2 };
|
|
16845
17001
|
}
|
|
16846
|
-
var initialized = (root) =>
|
|
17002
|
+
var initialized = (root) => fs27.existsSync(path32.join(root, STATE_DIR_NAME, PROJECT_FILE_NAME));
|
|
16847
17003
|
function launcherOf(entry) {
|
|
16848
17004
|
if (!isDuoLaunch(entry))
|
|
16849
17005
|
return void 0;
|
|
@@ -17058,8 +17214,8 @@ async function applyAgentIntegration(root, plan, options = {}) {
|
|
|
17058
17214
|
if (text4 === null) {
|
|
17059
17215
|
await fsp6.rm(g.value.absolute, { force: true });
|
|
17060
17216
|
changed.push({ path: rel, action: "delete" });
|
|
17061
|
-
const dir =
|
|
17062
|
-
if (dir !== root &&
|
|
17217
|
+
const dir = path32.dirname(g.value.absolute);
|
|
17218
|
+
if (dir !== root && fs27.readdirSync(dir).length === 0)
|
|
17063
17219
|
await fsp6.rmdir(dir);
|
|
17064
17220
|
} else {
|
|
17065
17221
|
await writeFileAtomic(g.value.absolute, text4);
|
|
@@ -17069,7 +17225,7 @@ async function applyAgentIntegration(root, plan, options = {}) {
|
|
|
17069
17225
|
} catch (error) {
|
|
17070
17226
|
for (const c of changed) {
|
|
17071
17227
|
const original = originals.get(c.path);
|
|
17072
|
-
const abs =
|
|
17228
|
+
const abs = path32.join(root, c.path);
|
|
17073
17229
|
if (original === void 0)
|
|
17074
17230
|
await fsp6.rm(abs, { force: true }).catch(() => void 0);
|
|
17075
17231
|
else
|
|
@@ -17175,7 +17331,7 @@ function scrub(value) {
|
|
|
17175
17331
|
return value;
|
|
17176
17332
|
}
|
|
17177
17333
|
async function projectDoctor(rootArg, options) {
|
|
17178
|
-
const root =
|
|
17334
|
+
const root = path33.resolve(rootArg);
|
|
17179
17335
|
const host2 = options.host ?? currentHost();
|
|
17180
17336
|
const results = /* @__PURE__ */ new Map();
|
|
17181
17337
|
const set = (id3, status, reason, facts = {}, actions = []) => {
|
|
@@ -17465,8 +17621,8 @@ function createDuoMcpServer(options) {
|
|
|
17465
17621
|
return { isError: true, content: [{ type: "text", text: run2.op.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n") || "operation failed" }] };
|
|
17466
17622
|
}
|
|
17467
17623
|
const payload = run2.op.kind === "ok" ? run2.op.payload : { format: NOT_INITIALIZED_FORMAT, status: "not-initialized", message: "Not a DUO project yet (no .duo-project/project.yaml). A human runs duoctl init." };
|
|
17468
|
-
const
|
|
17469
|
-
return { content: [{ type: "text", text:
|
|
17624
|
+
const summary2 = run2.op.kind === "ok" ? tool.summarize(payload) : String(payload.message);
|
|
17625
|
+
return { content: [{ type: "text", text: summary2 }], structuredContent: payload };
|
|
17470
17626
|
});
|
|
17471
17627
|
}
|
|
17472
17628
|
return server;
|
|
@@ -17506,7 +17662,7 @@ async function serveDuoMcp(options) {
|
|
|
17506
17662
|
}
|
|
17507
17663
|
|
|
17508
17664
|
// packages/integration/dist/mcp/root.js
|
|
17509
|
-
import
|
|
17665
|
+
import path34 from "node:path";
|
|
17510
17666
|
var ROOT_FROM_GIT_CWD = "git-cwd";
|
|
17511
17667
|
var ROOT_FROM_ENV_PREFIX = "env:";
|
|
17512
17668
|
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/u;
|
|
@@ -17517,7 +17673,7 @@ async function resolveMcpRoot(spec2, host2) {
|
|
|
17517
17673
|
if (spec2.root !== void 0 && spec2.rootFrom !== void 0)
|
|
17518
17674
|
return failure([createDiagnostic("CLI_USAGE_INVALID", "--root and --root-from cannot be combined")]);
|
|
17519
17675
|
if (spec2.rootFrom === void 0)
|
|
17520
|
-
return success(
|
|
17676
|
+
return success(path34.resolve(host2.cwd, spec2.root ?? "."));
|
|
17521
17677
|
if (!isRootFrom(spec2.rootFrom))
|
|
17522
17678
|
return failure([createDiagnostic("CLI_USAGE_INVALID", `--root-from must be ${ROOT_FROM_GIT_CWD} or ${ROOT_FROM_ENV_PREFIX}<NAME>`)]);
|
|
17523
17679
|
if (spec2.rootFrom === ROOT_FROM_GIT_CWD) {
|
|
@@ -17528,16 +17684,16 @@ async function resolveMcpRoot(spec2, host2) {
|
|
|
17528
17684
|
const value = host2.env[name];
|
|
17529
17685
|
if (value === void 0 || value === "")
|
|
17530
17686
|
return failure([createDiagnostic("MCP_ROOT_UNRESOLVED", `environment variable ${name} is not set (--root-from ${spec2.rootFrom})`)]);
|
|
17531
|
-
if (!
|
|
17687
|
+
if (!path34.isAbsolute(value))
|
|
17532
17688
|
return failure([createDiagnostic("MCP_ROOT_UNRESOLVED", `environment variable ${name} is not an absolute path (--root-from ${spec2.rootFrom})`)]);
|
|
17533
|
-
return success(
|
|
17689
|
+
return success(path34.resolve(value));
|
|
17534
17690
|
}
|
|
17535
17691
|
|
|
17536
17692
|
// packages/integration/dist/http/server.js
|
|
17537
17693
|
import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
17538
|
-
import
|
|
17694
|
+
import fs28 from "node:fs";
|
|
17539
17695
|
import http from "node:http";
|
|
17540
|
-
import
|
|
17696
|
+
import path35 from "node:path";
|
|
17541
17697
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
17542
17698
|
import { z as z5 } from "zod";
|
|
17543
17699
|
|
|
@@ -17735,16 +17891,16 @@ var TYPES = {
|
|
|
17735
17891
|
var PAGES = /^\/(?:|overview|direction|graph|context|review|reviews(?:\/[\w.-]+)?|decisions|coverage|search|entity\/.+)$/u;
|
|
17736
17892
|
function locateUiAssets() {
|
|
17737
17893
|
const candidates3 = [new URL("./ui/", import.meta.url), new URL("../../../ui/dist/app/", import.meta.url)].map((u) => fileURLToPath2(u));
|
|
17738
|
-
return candidates3.find((d) =>
|
|
17894
|
+
return candidates3.find((d) => fs28.existsSync(path35.join(d, "index.html")));
|
|
17739
17895
|
}
|
|
17740
17896
|
function loadAssets(dir) {
|
|
17741
17897
|
const out = /* @__PURE__ */ new Map();
|
|
17742
|
-
for (const name of
|
|
17743
|
-
const type = TYPES[
|
|
17744
|
-
const file =
|
|
17745
|
-
if (type === void 0 || !
|
|
17898
|
+
for (const name of fs28.readdirSync(dir)) {
|
|
17899
|
+
const type = TYPES[path35.extname(name).toLowerCase()];
|
|
17900
|
+
const file = path35.join(dir, name);
|
|
17901
|
+
if (type === void 0 || !fs28.lstatSync(file).isFile() || name.endsWith(".map"))
|
|
17746
17902
|
continue;
|
|
17747
|
-
out.set(name, { type, body:
|
|
17903
|
+
out.set(name, { type, body: fs28.readFileSync(file) });
|
|
17748
17904
|
}
|
|
17749
17905
|
return out;
|
|
17750
17906
|
}
|
|
@@ -17886,7 +18042,7 @@ async function startDuoUiServer(options) {
|
|
|
17886
18042
|
switch (route) {
|
|
17887
18043
|
case "session":
|
|
17888
18044
|
only("GET");
|
|
17889
|
-
return { name: "session", body: { csrf, version: options.version, apiVersion: UI_API_VERSION, root:
|
|
18045
|
+
return { name: "session", body: { csrf, version: options.version, apiVersion: UI_API_VERSION, root: path35.basename(root) } };
|
|
17890
18046
|
case "overview":
|
|
17891
18047
|
only("GET");
|
|
17892
18048
|
return { name: "overview", body: data(await projectOverview(root, shared)) };
|
|
@@ -18127,6 +18283,7 @@ var EN = {
|
|
|
18127
18283
|
"review.semantic-verdict": " with semantic assistance: {verdict}",
|
|
18128
18284
|
"review.pass": "PASS means: no project-direction violation found in the available evidence (not: no bugs).",
|
|
18129
18285
|
"review.aligned": "{n} aligned claims (--verbose to list)",
|
|
18286
|
+
"review.authority": "authority: {label}",
|
|
18130
18287
|
"review.bootstrap": "{n} Truth files exactly as init wrote them (adoption-bootstrap, not reviewed until changed)",
|
|
18131
18288
|
"review.gaps": "Knowledge gaps:",
|
|
18132
18289
|
"review.limitations": "Limitations:",
|
|
@@ -18157,6 +18314,27 @@ var EN = {
|
|
|
18157
18314
|
"decision.preview.none": "(none)",
|
|
18158
18315
|
"decision.preview.forbids-scope": "repository-wide (governs does not narrow forbids)",
|
|
18159
18316
|
"decision.index-hint": "Run duoctl index so review and context see it.",
|
|
18317
|
+
"decision.session.header": "Review: {n} pending proposal(s) in ID order: {ids}. Nothing is confirmed until you choose and type the IDs.",
|
|
18318
|
+
"decision.session.item": "[{i}/{n}]",
|
|
18319
|
+
"decision.session.select": "Confirm which proposals? Type their IDs (e.g. {example}), 'all', 'one' to decide one by one, or press Enter to cancel: ",
|
|
18320
|
+
"decision.session.cancelled": "Nothing confirmed. The proposals stay pending.",
|
|
18321
|
+
"decision.session.select-invalid": "{reason}; nothing confirmed.",
|
|
18322
|
+
"decision.session.unknown-id": "{id} is not a pending proposal of this review",
|
|
18323
|
+
"decision.session.duplicate-id": "{id} is listed twice",
|
|
18324
|
+
"decision.session.mixed": "'all', 'one' and 'cancel' cannot be combined with IDs",
|
|
18325
|
+
"decision.session.plan": "Confirm plan, in ID order (each confirm is bound to the candidate shown):",
|
|
18326
|
+
"decision.session.plan-item": " {id} \u2192 {decision} (expected)",
|
|
18327
|
+
"decision.session.changed": "{id} at the moment of its confirm (the proposals before it in this session are confirmed first):",
|
|
18328
|
+
"decision.session.final": "Type the IDs to confirm, exactly as listed ({ids}), or press Enter to cancel: ",
|
|
18329
|
+
"decision.session.final-mismatch": "The IDs do not match; nothing confirmed.",
|
|
18330
|
+
"decision.session.nothing": "Nothing confirmed.",
|
|
18331
|
+
"decision.session.one-prompt": "Type {id} to confirm it (expected {expected}), 'skip' to leave it pending, or press Enter to stop: ",
|
|
18332
|
+
"decision.session.one-changed": "{id} changed since the review above (an earlier confirm in this session); these rows are now:",
|
|
18333
|
+
"decision.session.skipped": "{id} skipped, still pending",
|
|
18334
|
+
"decision.session.failed-item": "{id} was not confirmed:",
|
|
18335
|
+
"decision.session.confirmed-item": "{from} confirmed as {id} \xB7 {path}",
|
|
18336
|
+
"decision.session.summary": "Confirmed {done} of {selected}. Still pending: {pending}.",
|
|
18337
|
+
"decision.session.stopped": "Stopped; the remaining proposals stay pending.",
|
|
18160
18338
|
"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:",
|
|
18161
18339
|
"stats.none": "No metrics yet.",
|
|
18162
18340
|
"not-initialized": "Not a DUO project (no .duo-project/project.yaml). Run duoctl init.",
|
|
@@ -18337,6 +18515,7 @@ var KO = {
|
|
|
18337
18515
|
"review.semantic-verdict": " \uC758\uBBF8 \uBCF4\uC870 \uD310\uC815 \uBC18\uC601 \uC2DC: {verdict}",
|
|
18338
18516
|
"review.pass": "PASS\uB294 \uD604\uC7AC Evidence \uBC94\uC704\uC5D0\uC11C \uD504\uB85C\uC81D\uD2B8 \uBC29\uD5A5 \uC704\uBC18\uC744 \uCC3E\uC9C0 \uBABB\uD588\uB2E4\uB294 \uB73B\uC785\uB2C8\uB2E4(\uBC84\uADF8 \uC5C6\uC74C\uC774 \uC544\uB2D8).",
|
|
18339
18517
|
"review.aligned": "ALIGNED claim {n}\uAC1C(--verbose\uB85C \uBAA9\uB85D)",
|
|
18518
|
+
"review.authority": "Decision \uC0C1\uD0DC: {label}",
|
|
18340
18519
|
"review.bootstrap": "init\uC774 \uB9CC\uB4E0 \uADF8\uB300\uB85C\uC758 Truth \uD30C\uC77C {n}\uAC1C(adoption-bootstrap, \uBC14\uB00C\uAE30 \uC804\uC5D0\uB294 \uAC80\uC218\uD558\uC9C0 \uC54A\uC74C)",
|
|
18341
18520
|
"review.gaps": "Knowledge gap:",
|
|
18342
18521
|
"review.limitations": "\uD55C\uACC4:",
|
|
@@ -18367,6 +18546,27 @@ var KO = {
|
|
|
18367
18546
|
"decision.preview.none": "(\uBE44\uC5B4 \uC788\uC74C)",
|
|
18368
18547
|
"decision.preview.forbids-scope": "\uC800\uC7A5\uC18C \uC804\uCCB4(governs\uB294 forbids\uC758 \uBC94\uC704\uB97C \uC881\uD788\uC9C0 \uC54A\uC74C)",
|
|
18369
18548
|
"decision.index-hint": "review\uC640 context\uC5D0 \uBC18\uC601\uD558\uB824\uBA74 duoctl index\uB97C \uC2E4\uD589\uD558\uC138\uC694.",
|
|
18549
|
+
"decision.session.header": "\uAC80\uD1A0: pending proposal {n}\uAC1C, ID \uC21C\uC11C: {ids}. \uC120\uD0DD\uD558\uACE0 ID\uB97C \uC785\uB825\uD558\uAE30 \uC804\uC5D0\uB294 \uC544\uBB34\uAC83\uB3C4 \uD655\uC815\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
18550
|
+
"decision.session.item": "[{i}/{n}]",
|
|
18551
|
+
"decision.session.select": "\uC5B4\uB5A4 proposal\uC744 \uD655\uC815\uD560\uAE4C\uC694? ID(\uC608: {example}), 'all', \uD558\uB098\uC529 \uACB0\uC815\uD558\uB824\uBA74 'one'\uC744 \uC785\uB825\uD558\uAC70\uB098 Enter\uB85C \uCDE8\uC18C\uD558\uC138\uC694: ",
|
|
18552
|
+
"decision.session.cancelled": "\uD655\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. proposal\uC740 pending\uC73C\uB85C \uB0A8\uC2B5\uB2C8\uB2E4.",
|
|
18553
|
+
"decision.session.select-invalid": "{reason}. \uD655\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
|
|
18554
|
+
"decision.session.unknown-id": "{id}\uB294 \uC774 \uAC80\uD1A0\uC758 pending proposal\uC774 \uC544\uB2D9\uB2C8\uB2E4",
|
|
18555
|
+
"decision.session.duplicate-id": "{id}\uAC00 \uB450 \uBC88 \uC788\uC2B5\uB2C8\uB2E4",
|
|
18556
|
+
"decision.session.mixed": "'all', 'one', 'cancel'\uC740 ID\uC640 \uD568\uAED8 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
18557
|
+
"decision.session.plan": "\uD655\uC815 \uACC4\uD68D(ID \uC21C\uC11C, \uAC01 \uD655\uC815\uC740 \uBCF4\uC778 candidate\uC5D0 \uBB36\uC785\uB2C8\uB2E4):",
|
|
18558
|
+
"decision.session.plan-item": " {id} \u2192 {decision} (\uC608\uC0C1)",
|
|
18559
|
+
"decision.session.changed": "{id}\uC758 \uD655\uC815 \uC2DC\uC810 \uB0B4\uC6A9(\uC774 session\uC5D0\uC11C \uC55E\uC758 proposal\uC774 \uBA3C\uC800 \uD655\uC815\uB429\uB2C8\uB2E4):",
|
|
18560
|
+
"decision.session.final": "\uD655\uC815\uD560 ID\uB97C \uBCF4\uC778 \uADF8\uB300\uB85C \uC785\uB825\uD558\uC138\uC694({ids}). Enter\uB294 \uCDE8\uC18C: ",
|
|
18561
|
+
"decision.session.final-mismatch": "ID\uAC00 \uB2E4\uB985\uB2C8\uB2E4. \uD655\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
|
|
18562
|
+
"decision.session.nothing": "\uD655\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
|
|
18563
|
+
"decision.session.one-prompt": "\uD655\uC815\uD558\uB824\uBA74 {id}(\uC608\uC0C1 {expected}), pending\uC73C\uB85C \uB450\uB824\uBA74 'skip', \uBA48\uCD94\uB824\uBA74 Enter: ",
|
|
18564
|
+
"decision.session.one-changed": "{id}\uB294 \uC704 \uAC80\uD1A0 \uB4A4 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4(\uC774 session\uC758 \uC55E\uC120 \uD655\uC815). \uC9C0\uAE08 \uAC12:",
|
|
18565
|
+
"decision.session.skipped": "{id} \uAC74\uB108\uB700, pending \uC720\uC9C0",
|
|
18566
|
+
"decision.session.failed-item": "{id}\uB294 \uD655\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4:",
|
|
18567
|
+
"decision.session.confirmed-item": "{from} \u2192 {id}\uB85C \uD655\uC815 \xB7 {path}",
|
|
18568
|
+
"decision.session.summary": "{selected}\uAC1C \uC911 {done}\uAC1C \uD655\uC815. \uB0A8\uC740 pending: {pending}.",
|
|
18569
|
+
"decision.session.stopped": "\uBA48\uCDC4\uC2B5\uB2C8\uB2E4. \uB0A8\uC740 proposal\uC740 pending\uC73C\uB85C \uB0A8\uC2B5\uB2C8\uB2E4.",
|
|
18370
18570
|
"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:",
|
|
18371
18571
|
"stats.none": "\uC9C0\uD45C\uAC00 \uC544\uC9C1 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
18372
18572
|
"not-initialized": "DUO project\uAC00 \uC544\uB2D9\uB2C8\uB2E4(.duo-project/project.yaml \uC5C6\uC74C). duoctl init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
@@ -18628,8 +18828,7 @@ function ambiguityHint(L, a) {
|
|
|
18628
18828
|
return [t(L, key2, { term: a.term }), ...r.definitionIdAlso ? [t(L, "context.ambiguous.id-also")] : []].map((line) => redactSecrets(line).text);
|
|
18629
18829
|
}
|
|
18630
18830
|
|
|
18631
|
-
// apps/cli/dist/commands/decision.js
|
|
18632
|
-
var DECISION_ID2 = /^D-\d+$/u;
|
|
18831
|
+
// apps/cli/dist/commands/decision-preview.js
|
|
18633
18832
|
var LABEL_WIDTH = 13;
|
|
18634
18833
|
function show(L, value, nested = false) {
|
|
18635
18834
|
if (value === void 0 || value === null)
|
|
@@ -18682,10 +18881,175 @@ function previewLines(L, p, op) {
|
|
|
18682
18881
|
lines4.push(row("File", p.sourcePath));
|
|
18683
18882
|
return lines4;
|
|
18684
18883
|
}
|
|
18884
|
+
|
|
18885
|
+
// apps/cli/dist/commands/decision-session.js
|
|
18886
|
+
var tokens2 = (input) => input.split(/[\s,]+/u).filter((x) => x !== "").map((x) => /^p-/iu.test(x) ? x.toUpperCase() : x.toLowerCase());
|
|
18887
|
+
function parseSelection(input, pending) {
|
|
18888
|
+
const list3 = tokens2(input ?? "");
|
|
18889
|
+
if (list3.length === 0)
|
|
18890
|
+
return { kind: "cancel" };
|
|
18891
|
+
if (list3.length === 1 && (list3[0] === "all" || list3[0] === "one" || list3[0] === "cancel"))
|
|
18892
|
+
return { kind: list3[0] };
|
|
18893
|
+
if (list3.some((x) => x === "all" || x === "one" || x === "cancel"))
|
|
18894
|
+
return { kind: "error", reason: "mixed" };
|
|
18895
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18896
|
+
for (const id3 of list3) {
|
|
18897
|
+
if (seen.has(id3))
|
|
18898
|
+
return { kind: "error", reason: "duplicate-id", id: id3 };
|
|
18899
|
+
seen.add(id3);
|
|
18900
|
+
if (!pending.includes(id3))
|
|
18901
|
+
return { kind: "error", reason: "unknown-id", id: id3 };
|
|
18902
|
+
}
|
|
18903
|
+
return { kind: "ids", ids: sessionOrder(list3) };
|
|
18904
|
+
}
|
|
18905
|
+
function matchesSelection(input, selected) {
|
|
18906
|
+
const list3 = tokens2(input ?? "");
|
|
18907
|
+
return list3.length === selected.length && list3.every((x, i) => x === selected[i]);
|
|
18908
|
+
}
|
|
18909
|
+
function changedRows(before, after) {
|
|
18910
|
+
const key2 = (line) => line.startsWith(" ") ? line.slice(2, 15).trim() : "";
|
|
18911
|
+
const rows = (lines4) => new Map(lines4.slice(1).map((l) => [key2(l), l]).filter(([k]) => k !== "" && k !== "Expected ID"));
|
|
18912
|
+
const a = rows(before);
|
|
18913
|
+
const b = rows(after);
|
|
18914
|
+
const out = [];
|
|
18915
|
+
for (const [k, line] of b) {
|
|
18916
|
+
const old = a.get(k);
|
|
18917
|
+
if (old !== line)
|
|
18918
|
+
out.push(...old === void 0 ? [] : [" - " + old.trim()], " + " + line.trim());
|
|
18919
|
+
}
|
|
18920
|
+
for (const [k, line] of a)
|
|
18921
|
+
if (!b.has(k))
|
|
18922
|
+
out.push(" - " + line.trim());
|
|
18923
|
+
return out;
|
|
18924
|
+
}
|
|
18925
|
+
async function reviewPendingCommand(env, project2) {
|
|
18926
|
+
const L = env.locale;
|
|
18927
|
+
if (!env.io.isTTY || env.nonInteractive) {
|
|
18928
|
+
return failed4("decision", EXIT.ERROR, [createDiagnostic("CLI_TTY_REQUIRED", t("en", "decision.tty"))], [t(L, "decision.tty")]);
|
|
18929
|
+
}
|
|
18930
|
+
const pendingIds = listDecisionProposals(project2.truth).filter((e) => e.status === "pending").map((e) => e.id);
|
|
18931
|
+
const service = createDecisionService({ root: env.root, clock: () => env.io.now() });
|
|
18932
|
+
const res = { mode: "cancel", reviewed: [], selected: [], confirmed: [], skipped: [], failed: [], pending: [] };
|
|
18933
|
+
const outcome = (exitCode, human, diagnostics = [], status) => {
|
|
18934
|
+
const confirmedIds = new Set(res.confirmed.map((c) => c.proposalId));
|
|
18935
|
+
res.pending = res.reviewed.filter((id3) => !confirmedIds.has(id3));
|
|
18936
|
+
const st = status ?? (res.failed.length > 0 ? "partial" : res.confirmed.length > 0 ? "confirmed" : "cancelled");
|
|
18937
|
+
return { command: "decision", exitCode, diagnostics, human, result: { status: st, session: res }, metric: { status: st } };
|
|
18938
|
+
};
|
|
18939
|
+
if (pendingIds.length === 0)
|
|
18940
|
+
return outcome(EXIT.OK, [t(L, "decision.none")], [], "none");
|
|
18941
|
+
const reviewed = /* @__PURE__ */ new Map();
|
|
18942
|
+
for (const id3 of pendingIds) {
|
|
18943
|
+
const p = await service.previewConfirm(id3);
|
|
18944
|
+
if (p.value === void 0)
|
|
18945
|
+
return failed4("decision", EXIT.ERROR, p.diagnostics, diagLines(p.diagnostics), null, { status: "failed" });
|
|
18946
|
+
reviewed.set(id3, p.value);
|
|
18947
|
+
}
|
|
18948
|
+
res.reviewed = [...pendingIds];
|
|
18949
|
+
env.io.err(t(L, "decision.session.header", { n: pendingIds.length, ids: pendingIds.join(", ") }));
|
|
18950
|
+
pendingIds.forEach((id3, i) => {
|
|
18951
|
+
env.io.err("");
|
|
18952
|
+
env.io.err(t(L, "decision.session.item", { i: i + 1, n: pendingIds.length }));
|
|
18953
|
+
for (const line of previewLines(L, reviewed.get(id3), "confirm"))
|
|
18954
|
+
env.io.err(line);
|
|
18955
|
+
});
|
|
18956
|
+
env.io.err("");
|
|
18957
|
+
const sel = parseSelection(await env.io.prompt(t(L, "decision.session.select", { example: pendingIds.slice(0, 2).join(" ") })), pendingIds);
|
|
18958
|
+
if (sel.kind === "cancel")
|
|
18959
|
+
return outcome(EXIT.OK, [t(L, "decision.session.cancelled")]);
|
|
18960
|
+
if (sel.kind === "error") {
|
|
18961
|
+
const reason = sel.reason === "mixed" ? t(L, "decision.session.mixed") : t(L, sel.reason === "unknown-id" ? "decision.session.unknown-id" : "decision.session.duplicate-id", { id: sel.id ?? "" });
|
|
18962
|
+
return outcome(EXIT.ERROR, [t(L, "decision.session.select-invalid", { reason })], [], "failed");
|
|
18963
|
+
}
|
|
18964
|
+
const actor = await humanActor(env.root);
|
|
18965
|
+
if (sel.kind === "one") {
|
|
18966
|
+
res.mode = "one";
|
|
18967
|
+
res.selected = [...pendingIds];
|
|
18968
|
+
for (const id3 of pendingIds) {
|
|
18969
|
+
const p = await service.previewConfirm(id3);
|
|
18970
|
+
if (p.value === void 0) {
|
|
18971
|
+
res.failed.push({ proposalId: id3, diagnostics: p.diagnostics.map((d) => d.code) });
|
|
18972
|
+
return outcome(EXIT.ERROR, [...diagLines(p.diagnostics), t(L, "decision.session.stopped")], p.diagnostics);
|
|
18973
|
+
}
|
|
18974
|
+
const rows = changedRows(previewLines(L, reviewed.get(id3), "confirm"), previewLines(L, p.value, "confirm"));
|
|
18975
|
+
if (p.value.digest !== reviewed.get(id3)?.digest) {
|
|
18976
|
+
env.io.err(t(L, "decision.session.one-changed", { id: id3 }));
|
|
18977
|
+
for (const r2 of rows)
|
|
18978
|
+
env.io.err(r2);
|
|
18979
|
+
}
|
|
18980
|
+
const answer = (await env.io.prompt(t(L, "decision.session.one-prompt", { id: id3, expected: p.value.expectedDecisionId })))?.trim() ?? "";
|
|
18981
|
+
if (answer === "")
|
|
18982
|
+
return outcome(res.failed.length > 0 ? EXIT.ERROR : EXIT.OK, [...summary(L, res), t(L, "decision.session.stopped")]);
|
|
18983
|
+
if (answer.toLowerCase() === "skip") {
|
|
18984
|
+
res.skipped.push(id3);
|
|
18985
|
+
env.io.err(t(L, "decision.session.skipped", { id: id3 }));
|
|
18986
|
+
continue;
|
|
18987
|
+
}
|
|
18988
|
+
if (answer.toUpperCase() !== id3)
|
|
18989
|
+
return outcome(EXIT.ERROR, [...summary(L, res), t(L, "decision.mismatch"), t(L, "decision.session.stopped")]);
|
|
18990
|
+
const r = await service.confirm(actor, id3, { expectedDigest: p.value.digest });
|
|
18991
|
+
if (r.value === void 0) {
|
|
18992
|
+
res.failed.push({ proposalId: id3, diagnostics: r.diagnostics.map((d) => d.code) });
|
|
18993
|
+
for (const line of [t(L, "decision.session.failed-item", { id: id3 }), ...diagLines(r.diagnostics)])
|
|
18994
|
+
env.io.err(line);
|
|
18995
|
+
continue;
|
|
18996
|
+
}
|
|
18997
|
+
res.confirmed.push({ proposalId: id3, decisionId: r.value.decisionId, path: r.value.path });
|
|
18998
|
+
env.io.err(t(L, "decision.confirmed", { id: r.value.decisionId, path: r.value.path }));
|
|
18999
|
+
}
|
|
19000
|
+
return outcome(res.failed.length > 0 ? EXIT.ERROR : EXIT.OK, summary(L, res));
|
|
19001
|
+
}
|
|
19002
|
+
res.mode = sel.kind;
|
|
19003
|
+
const selected = sel.kind === "all" ? [...pendingIds] : [...sel.ids];
|
|
19004
|
+
res.selected = selected;
|
|
19005
|
+
const plan = await planConfirmSession({ root: env.root }, selected.map((id3) => ({ id: id3, digest: reviewed.get(id3).digest })));
|
|
19006
|
+
if (plan.value === void 0)
|
|
19007
|
+
return outcome(EXIT.ERROR, [...diagLines(plan.diagnostics), t(L, "decision.session.nothing")], plan.diagnostics, "failed");
|
|
19008
|
+
env.io.err(t(L, "decision.session.plan"));
|
|
19009
|
+
for (const item of plan.value.items)
|
|
19010
|
+
env.io.err(t(L, "decision.session.plan-item", { id: item.id, decision: item.preview.expectedDecisionId }));
|
|
19011
|
+
for (const item of plan.value.items.filter((x) => x.changedFromReview)) {
|
|
19012
|
+
env.io.err("");
|
|
19013
|
+
env.io.err(t(L, "decision.session.changed", { id: item.id }));
|
|
19014
|
+
for (const r of changedRows(previewLines(L, reviewed.get(item.id), "confirm"), previewLines(L, item.preview, "confirm")))
|
|
19015
|
+
env.io.err(r);
|
|
19016
|
+
}
|
|
19017
|
+
env.io.err("");
|
|
19018
|
+
const typed = await env.io.prompt(t(L, "decision.session.final", { ids: selected.join(" ") }));
|
|
19019
|
+
if ((typed ?? "").trim() === "")
|
|
19020
|
+
return outcome(EXIT.OK, [t(L, "decision.session.cancelled")]);
|
|
19021
|
+
if (!matchesSelection(typed, selected))
|
|
19022
|
+
return outcome(EXIT.ERROR, [t(L, "decision.session.final-mismatch")], [], "failed");
|
|
19023
|
+
for (const item of plan.value.items) {
|
|
19024
|
+
const r = await service.confirm(actor, item.id, { expectedDigest: item.preview.digest });
|
|
19025
|
+
if (r.value === void 0) {
|
|
19026
|
+
res.failed.push({ proposalId: item.id, diagnostics: r.diagnostics.map((d) => d.code) });
|
|
19027
|
+
for (const line of [t(L, "decision.session.failed-item", { id: item.id }), ...diagLines(r.diagnostics)])
|
|
19028
|
+
env.io.err(line);
|
|
19029
|
+
continue;
|
|
19030
|
+
}
|
|
19031
|
+
res.confirmed.push({ proposalId: item.id, decisionId: r.value.decisionId, path: r.value.path });
|
|
19032
|
+
}
|
|
19033
|
+
return outcome(res.failed.length > 0 ? EXIT.ERROR : EXIT.OK, summary(L, res));
|
|
19034
|
+
}
|
|
19035
|
+
function summary(L, res) {
|
|
19036
|
+
const done = new Set(res.confirmed.map((c) => c.proposalId));
|
|
19037
|
+
const pending = res.reviewed.filter((id3) => !done.has(id3));
|
|
19038
|
+
return [
|
|
19039
|
+
...res.confirmed.map((c) => t(L, "decision.session.confirmed-item", { from: c.proposalId, id: c.decisionId, path: c.path })),
|
|
19040
|
+
t(L, "decision.session.summary", { done: res.confirmed.length, selected: res.selected.length, pending: pending.length === 0 ? t(L, "decision.preview.none") : pending.join(", ") }),
|
|
19041
|
+
...res.confirmed.length > 0 ? [t(L, "decision.index-hint")] : []
|
|
19042
|
+
];
|
|
19043
|
+
}
|
|
19044
|
+
|
|
19045
|
+
// apps/cli/dist/commands/decision.js
|
|
19046
|
+
var DECISION_ID2 = /^D-\d+$/u;
|
|
18685
19047
|
async function decisionCommand(env, sub, id3, reason) {
|
|
18686
19048
|
const project2 = requireProject(env, "decision");
|
|
18687
19049
|
if (project2.value === void 0)
|
|
18688
19050
|
return project2.outcome;
|
|
19051
|
+
if (sub === "review-pending")
|
|
19052
|
+
return reviewPendingCommand(env, project2.value);
|
|
18689
19053
|
const truth = project2.value.truth;
|
|
18690
19054
|
const entries = listDecisionProposals(truth);
|
|
18691
19055
|
if (sub === "list" || sub === void 0) {
|
|
@@ -18699,7 +19063,7 @@ async function decisionCommand(env, sub, id3, reason) {
|
|
|
18699
19063
|
return { command: "decision", exitCode: EXIT.OK, diagnostics: unread, human, result: { pending: pending.map((e) => ({ id: e.id, title: e.proposal.title, question: e.proposal.question, answer: e.proposal.answer })), proposedDecisions: proposedDecisions.map((d) => ({ id: d.id, title: d.title })) } };
|
|
18700
19064
|
}
|
|
18701
19065
|
if (sub !== "confirm" && sub !== "reject")
|
|
18702
|
-
return usage("decision", "usage: duoctl decision list | confirm <id> | reject <id> [--reason <text>]");
|
|
19066
|
+
return usage("decision", "usage: duoctl decision list | confirm <id> | reject <id> [--reason <text>] | review-pending");
|
|
18703
19067
|
if (id3 === void 0)
|
|
18704
19068
|
return usage("decision", `duoctl decision ${sub} needs an ID`);
|
|
18705
19069
|
if (!env.io.isTTY || env.nonInteractive) {
|
|
@@ -19253,6 +19617,8 @@ function renderReview(env, r) {
|
|
|
19253
19617
|
for (const c of shown) {
|
|
19254
19618
|
const tags = [c.blockEligible ? "blocking" : "", c.provenance === void 0 ? "" : provenanceLabel(c.provenance, L), c.drift ? "drift" : ""].filter((x) => x !== "").join(", ");
|
|
19255
19619
|
out.push(` ${c.alignment.padEnd(9)} ${c.rule.padEnd(26)} ${c.subject.id} \xB7 ${c.reason}${tags === "" ? "" : ` [${tags}]`}`);
|
|
19620
|
+
if (c.decisionAuthority !== void 0)
|
|
19621
|
+
out.push(` ${t(L, "review.authority", { label: decisionAuthorityLabel(c.decisionAuthority, L) })}`);
|
|
19256
19622
|
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]}`}`);
|
|
19257
19623
|
if (pointers.length > 0)
|
|
19258
19624
|
out.push(` evidence: ${pointers.join(", ")}`);
|
|
@@ -19490,7 +19856,7 @@ var HELP = [
|
|
|
19490
19856
|
" review review changes (--staged, --from, --to, --files, --task, --budget, --refresh, --record, --fail-on block|ask|warn, --strict = --fail-on warn, --semantic: optional LLM assistance)",
|
|
19491
19857
|
" trace trace a node (--depth 1-3)",
|
|
19492
19858
|
" impact Graph-recorded impact of a node (--depth 1-3)",
|
|
19493
|
-
" decision list | confirm <id> | reject <id> [--reason <text>] (terminal only)",
|
|
19859
|
+
" decision list | confirm <id> | reject <id> [--reason <text>] | review-pending (terminal only, a human: review-pending previews every pending proposal, then confirms the ones you choose)",
|
|
19494
19860
|
" stats runtime metrics summary (--last n)",
|
|
19495
19861
|
" mcp serve the duo-director MCP server over stdio (--root <path> | --root-from git-cwd|env:<NAME>, --agent <label>)",
|
|
19496
19862
|
" install connect an agent: install codex|claude-code [--launcher path|npx] [--yes] \xB7 install status [agent] \xB7 install remove <agent>",
|