@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.
@@ -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 path35 = url.pathname.replace(/\/+$/u, "");
436
- if (path35.includes("//"))
438
+ const path36 = url.pathname.replace(/\/+$/u, "");
439
+ if (path36.includes("//"))
437
440
  return { problem: "empty-path-segment" };
438
- return { value: { baseUrl: `${url.origin}${path35}`, origin: 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(path35) {
541
- return path35.normalize("NFC").toLowerCase();
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(path35) {
586
- return { type: "file", path: path35 };
588
+ function fileRef(path36) {
589
+ return { type: "file", path: path36 };
587
590
  }
588
- function symbolRef(path35, symbol) {
589
- return { type: "symbol", path: path35, symbol };
591
+ function symbolRef(path36, symbol) {
592
+ return { type: "symbol", path: path36, symbol };
590
593
  }
591
- function testRef(path35, name) {
592
- return { type: "test", path: path35, name };
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 path35 = body.includes("#") ? void 0 : decodePath(body);
644
- return path35 === void 0 ? void 0 : fileRef(path35);
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 path35 = decodePath(parts[0] ?? "");
653
+ const path36 = decodePath(parts[0] ?? "");
651
654
  const name = decodeComponent(parts[1] ?? "");
652
- if (path35 === void 0 || name === void 0 || name === "")
655
+ if (path36 === void 0 || name === void 0 || name === "")
653
656
  return void 0;
654
- return type === "symbol" ? symbolRef(path35, name) : testRef(path35, name);
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 path35 = normalized.value;
743
+ const path36 = normalized.value;
741
744
  const prefix = `${STATE_DIR_NAME}/`;
742
745
  if (writeKind === "agent-integration") {
743
- return AGENT_INTEGRATION_FILES.includes(path35) ? success({ path: path35, kind: writeKind, area: path35 }) : failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${path35}" as agent-integration: only ${AGENT_INTEGRATION_FILES.join(", ")}`, { path: path35 })]);
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 (!path35.startsWith(prefix)) {
746
- return failure([createDiagnostic("WRITE_NOT_ALLOWED", `Refusing to write "${path35}": DUO writes only under ${prefix} and never writes project source code`, { path: path35 })]);
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 = path35.slice(prefix.length);
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 "${path35}": not a ${prefix} write area`, { path: path35 })]);
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 "${path35}" as ${writeKind}: ${prefix}${found.area} holds ${found.kind} data`, { path: path35 })]);
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("/") ? path35.startsWith(p) && path35.length > p.length : path35 === p);
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 "${path35}": outside the paths allowed for this caller`, { path: path35 })]);
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: path35, kind: writeKind, area: prefix + found.area });
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(path35, text4) {
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: path35 })]);
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: path35,
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: path35,
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(path35) {
1043
- return SCHEME.test(path35);
1045
+ function isRemoteSourcePath(path36) {
1046
+ return SCHEME.test(path36);
1044
1047
  }
1045
- function externalSourceSlice(path35, rawText, section) {
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: path35 } };
1049
- if (!MARKDOWN.test(path35))
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(path35, text4).value;
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 = (path35, target = "value") => {
1147
- if (target === "key" && path35.length > 0) {
1148
- const parent = path35.length > 1 ? doc.getIn(path35.slice(0, -1), true) : doc.contents;
1149
- const key2 = path35[path35.length - 1];
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 = path35.length === 0 ? doc.contents : doc.getIn(path35, true);
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 = (path35, message2) => ctx.addIssue({ code: "custom", path: [path35], message: message2 });
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, path35) {
1378
+ function valueAt(data, path36) {
1374
1379
  let current = data;
1375
- for (const key2 of path35) {
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(path35) {
1386
- if (path35.length === 0)
1390
+ function describe(path36) {
1391
+ if (path36.length === 0)
1387
1392
  return "(root)";
1388
- return path35.map((k, i) => typeof k === "number" ? `[${k}]` : i === 0 ? k : `.${k}`).join("");
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 = (path35, target = "value") => yaml2.locate(path35, target) ?? fallback;
1400
+ const where = (path36, target = "value") => yaml2.locate(path36, target) ?? fallback;
1396
1401
  for (const issue of result.error.issues) {
1397
- const path35 = issue.path.filter((k) => typeof k === "string" || typeof k === "number");
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([...path35, key2])}". Put custom data under "extensions".`, where([...path35, key2], "key")));
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, path35);
1405
- const parent = valueAt(yaml2.data, path35.slice(0, -1));
1406
- const last = path35[path35.length - 1];
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(path35)}"`, where(path35.slice(0, -1))));
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(path35)}: "${String(value)}" is not a valid ID`, where(path35)));
1411
- } else if (path35.length === 0 && !isRecord(yaml2.data)) {
1412
- diagnostics.push(createDiagnostic("SCHEMA_INVALID_VALUE", "Expected a mapping of properties", where(path35)));
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(path35)}: ${issue.message}`, where(path35)));
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(path35) {
1439
- return this.yaml.locate([...this.base, ...path35]) ?? this.fallback;
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, path35, target, expected, relation) {
1445
- this.references.push({ field, target, expected, relation, location: this.at(path35) });
1449
+ ref(field, path36, target, expected, relation) {
1450
+ this.references.push({ field, target, expected, relation, location: this.at(path36) });
1446
1451
  }
1447
- refs(field, path35, targets, expected, relation) {
1452
+ refs(field, path36, targets, expected, relation) {
1448
1453
  const list3 = targets ?? [];
1449
- list3.forEach((t2, i) => this.ref(field, [...path35, i], t2, expected, relation));
1454
+ list3.forEach((t2, i) => this.ref(field, [...path36, i], t2, expected, relation));
1450
1455
  return list3;
1451
1456
  }
1452
- patterns(path35, values) {
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([...path35, i])));
1464
+ this.diagnostics.push(withSource(d, this.at([...path36, i])));
1460
1465
  });
1461
1466
  return out;
1462
1467
  }
1463
- sources(path35, values) {
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([...path35, i, "path"])));
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(path35, values) {
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([...path35, i, "path"])));
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(path35, text4) {
1821
- const md = parseMarkdown(path35, text4);
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(path35, text4) {
1833
- const parsed = parseYaml({ path: path35, text: text4 });
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: path35, startLine: 1 } };
1837
- const whole = parsed.value.locate([]) ?? { path: path35, startLine: 1 };
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(path35, text4) {
1841
- const { yaml: yaml2, diagnostics, whole } = parseFile(path35, text4);
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(path35, text4) {
1857
- const { yaml: yaml2, diagnostics, whole } = parseFile(path35, text4);
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(path35, text4) {
1867
- const { yaml: yaml2, diagnostics, whole } = parseFile(path35, text4);
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(path35, text4) {
1877
- const { yaml: yaml2, diagnostics, whole } = parseFile(path35, text4);
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(path35, text4) {
1889
- const { yaml: yaml2, diagnostics, whole } = parseFile(path35, text4);
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(path35, text4) {
1900
- const md = parseMarkdown(path35, text4);
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: path35, startLine: 1 }));
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: path35, text: doc.frontmatter.value, startLine: doc.frontmatter.contentStartLine });
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 fs28 = options.fs ?? nodeDecisionFileSystem;
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 fs28.createExclusive(file, JSON.stringify({ pid: process.pid, host: os.hostname(), acquiredAt: now() })))
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 fs28.readText(file) ?? "null");
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 fs28.remove(file);
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 fs28.remove(file);
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, path35, change) {
2459
- const decisions = change.decision === void 0 ? truth.decisions : [...truth.decisions.filter((d) => d.location.path !== path35), change.decision];
2460
- const proposals = change.proposal === void 0 ? truth.proposals : [...truth.proposals.filter((p) => p.location.path !== path35), change.proposal];
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 === path35 || idTokens(d.message).includes(id3)));
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 fs28.readText(g.value.absolute);
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 fs28.writeAtomic(g.value.absolute, next2);
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 fs28.remove(g.value.absolute);
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 fs28.list(`${root}/${DECISIONS_DIR}`), ...await fs28.list(`${root}/${PROPOSALS_DIR}`)].map((n) => n.replace(/\.ya?ml$/u, ""));
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 fs28.createExclusive(g.value.absolute, render(chosen));
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 fs28.readText(`${root}/${repoPath3}`);
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 fs28.remove(g.value.absolute);
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 fs5 from "node:fs";
2901
- import path8 from "node:path";
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 path7 from "node:path";
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 = path7.resolve(root);
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", path35 = ""] = m;
2998
- byPath.set(path35, stage === "0" ? { path: path35, mode, oid } : { path: path35, mode });
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(path35) {
3023
- const name = path35.slice(path35.lastIndexOf("/") + 1).toLowerCase();
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(path35) {
3033
- if (path35.split("/").some((segment) => segment.toLowerCase() === ".git"))
3101
+ exclusionOf(path36) {
3102
+ if (path36.split("/").some((segment) => segment.toLowerCase() === ".git"))
3034
3103
  return "git-internal";
3035
- if (DUO_REGENERABLE_PREFIXES.some((prefix) => path35.startsWith(prefix)))
3104
+ if (DUO_REGENERABLE_PREFIXES.some((prefix) => path36.startsWith(prefix)))
3036
3105
  return "duo-regenerable";
3037
- if (DUO_HISTORY_PREFIXES.some((prefix) => path35.startsWith(prefix)))
3106
+ if (DUO_HISTORY_PREFIXES.some((prefix) => path36.startsWith(prefix)))
3038
3107
  return "duo-history";
3039
- if (AGENT_INTEGRATION_FILES.includes(path35))
3108
+ if (AGENT_INTEGRATION_FILES.includes(path36))
3040
3109
  return "duo-agent-integration";
3041
- if (isSecretFileName(path35))
3110
+ if (isSecretFileName(path36))
3042
3111
  return "secret";
3043
- if (excludeMatchers.some((m) => m(path35)))
3112
+ if (excludeMatchers.some((m) => m(path36)))
3044
3113
  return "index-exclude";
3045
- if (include.length > 0 && !includeMatchers.some((m) => m(path35)))
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 (path35) => {
3081
- const segments = path35.split("/");
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 = fs5.lstatSync(absolute);
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 fs5.readlinkSync(absolute, "utf8");
3178
+ return fs6.readlinkSync(absolute, "utf8");
3110
3179
  if (kind !== "file")
3111
3180
  return void 0;
3112
- const fd = fs5.openSync(absolute, "r");
3181
+ const fd = fs6.openSync(absolute, "r");
3113
3182
  try {
3114
3183
  const buffer = Buffer.alloc(MAX_LINK_TEXT);
3115
- const n = fs5.readSync(fd, buffer, 0, MAX_LINK_TEXT, 0);
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
- fs5.closeSync(fd);
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 = path8.resolve(root, path8.dirname(link), target);
3128
- const relative = path8.relative(root, resolved);
3129
- return relative === ".." || relative.startsWith(`..${path8.sep}`) || path8.isAbsolute(relative);
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 = path8.resolve(root);
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(path8.join(rootDir, link), kind)));
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(path8.join(rootDir, dir));
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 = path8.join(rootDir, candidate.path);
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(path35) {
3395
- const name = path35.slice(path35.lastIndexOf("/") + 1).toLowerCase();
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 fs6 from "node:fs/promises";
3430
- import path9 from "node:path";
3498
+ import fs7 from "node:fs/promises";
3499
+ import path10 from "node:path";
3431
3500
  async function fingerprintOne(root, file) {
3432
- const absolute = path9.join(root, file.path);
3501
+ const absolute = path10.join(root, file.path);
3433
3502
  try {
3434
- const stat = await fs6.lstat(absolute);
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 fs6.readFile(absolute), fingerprintMode);
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 = path9.resolve(root);
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 fs7 from "node:fs";
3475
- import path10 from "node:path";
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 (fs7.lstatSync(path10.join(root, partial)).isSymbolicLink())
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 = path10.resolve(root);
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 = fs7.readFileSync(path10.join(rootDir, FINGERPRINT_FILE_PATH), "utf8");
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 = path10.resolve(root);
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 = path10.join(rootDir, target);
3657
+ const absolute = path11.join(rootDir, target);
3589
3658
  const temp = `${absolute}.${process.pid}.tmp`;
3590
3659
  try {
3591
- fs7.mkdirSync(path10.dirname(absolute), { recursive: true });
3592
- fs7.writeFileSync(temp, serializeFingerprints(fingerprints), "utf8");
3593
- fs7.renameSync(temp, absolute);
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
- fs7.rmSync(temp, { force: true });
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 fs8 from "node:fs";
3624
- import path11 from "node:path";
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 tokens2 = out.split("\0");
3724
+ const tokens3 = out.split("\0");
3656
3725
  let oid;
3657
3726
  let head;
3658
3727
  const changes = [];
3659
3728
  const skipped2 = [];
3660
- const path35 = (raw) => {
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 < tokens2.length; i++) {
3667
- const t2 = tokens2[i] ?? "";
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 = path35(raw);
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 = path35(raw);
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 = tokens2[++i];
3783
+ const origRaw = tokens3[++i];
3715
3784
  if (origRaw === void 0)
3716
3785
  throw new GitOutputError("rename record without original path");
3717
- const p = path35(raw);
3718
- const orig = path35(origRaw);
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 = path35(raw);
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 [, os2 = "0", ol, ns = "0", nl, section = ""] = h;
3814
- current = { oldStart: Number(os2), oldLines: ol === void 0 ? 1 : Number(ol), newStart: Number(ns), newLines: nl === void 0 ? 1 : Number(nl), section, lines: [] };
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 tokens2 = out.split("\0");
3861
- if (tokens2.at(-1) === "")
3862
- tokens2.pop();
3863
- if (tokens2.length % 3 !== 0)
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 < tokens2.length; i += 3) {
3867
- const oid = tokens2[i] ?? "";
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 = (tokens2[i + 1] ?? "").split(" ").filter((p) => p !== "");
3871
- commits.push({ oid, parents, message: (tokens2[i + 2] ?? "").replace(/\n$/u, "") });
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 = fs8.lstatSync(path11.join(this.root, d.path)).size;
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 = path11.resolve(root);
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 path12 from "node:path";
4255
+ import path13 from "node:path";
4187
4256
  async function readGitUserName(root) {
4188
- const r = await runGit(path12.resolve(root), ["config", "--get", "user.name"]);
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(path12.resolve(dir), ["rev-parse", "--show-toplevel"]);
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 : path12.resolve(top);
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(path35) {
4253
- const name = path35.slice(path35.lastIndexOf("/") + 1);
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 = (path35) => path35.slice(0, path35.length - extensionOf(path35).length - 1);
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(path35, context) {
4267
- return extensionOf(path35) === "h" && (context.uproject || context.cppProject || context.cppStems.has(stemOf(path35)));
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 = (path35) => list3.find((a) => a.supports(path35));
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 = (path35) => {
4286
- const own = analyzerFor(path35);
4354
+ const selectFor = (path36) => {
4355
+ const own = analyzerFor(path36);
4287
4356
  if (own !== void 0)
4288
4357
  return own;
4289
- const ext = extensionOf(path35);
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(path35, context) ? contextual : void 0;
4362
+ return isCppHeader(path36, context) ? contextual : void 0;
4294
4363
  };
4295
4364
  return {
4296
4365
  analyzerFor: selectFor,
4297
- capabilitiesFor: (path35) => selectFor(path35)?.capabilities ?? FILE_ONLY_CAPABILITIES,
4298
- languageFor(path35) {
4299
- const a = selectFor(path35);
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 fs9 from "node:fs";
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 (fs9.existsSync(packaged))
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(fs9.readFileSync(located)) : located;
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(path35) {
4404
- const name = path35.slice(path35.lastIndexOf("/") + 1);
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(path35) {
4451
- const ext = extensionOf2(path35);
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(path35) {
4457
- return Object.hasOwn(this.spec.extensions, extensionOf2(path35));
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: path35 } = input;
4463
- const grammar = this.grammarOf(path35);
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", `"${path35}" is not a ${this.id} source file`, { path: path35 })]);
4535
+ return failure([createDiagnostic("LANGUAGE_UNSUPPORTED", `"${path36}" is not a ${this.id} source file`, { path: path36 })]);
4467
4536
  }
4468
- const mode = fingerprintModeOf(path35);
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", `"${path35}" is not valid UTF-8`, { path: path35 })]);
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 "${path35}" took longer than ${this.parseTimeoutMs} ms`, { path: path35 })]);
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(path35, tree, text4);
4557
+ const x = this.spec.extract(path36, tree, text4);
4489
4558
  return success({
4490
- path: path35,
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(path35, comment, startLine, startColumn) {
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: path35, startLine: startLine + k, startColumn: column, endLine: startLine + k, endColumn: column + text4.length };
4554
- const tokens2 = text4.slice("duo:".length).split(/[\s,]+/).filter((t2) => t2 !== "");
4555
- const end2 = tokens2.findIndex((t2) => !isDefinitionId(t2));
4556
- const ids2 = [...new Set(end2 === -1 ? tokens2 : tokens2.slice(0, end2))];
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(path35, node) {
4637
+ function location(path36, node) {
4569
4638
  return {
4570
- path: path35,
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(path35, tree, errors) {
4667
+ function reportParseErrors(path36, tree, errors) {
4599
4668
  const diagnostics = [];
4600
- const sorted = errors.map((e) => ({ ...e, location: location(path35, e.node) })).sort((a, b) => compareSourceLocations(a.location, b.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: path35 }));
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(path35) {
4639
- this.path = path35;
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 tokens2 = [];
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
- tokens2.push(n.text);
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 tokens2.join(" ").replace(/\s*([*&,()[\]<>:])\s*/gu, "$1").replace(/\s+/gu, " ").trim();
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(path35, tree) {
5001
+ constructor(path36, tree) {
4933
5002
  this.tree = tree;
4934
- this.c = new StructuralCollector(path35);
4935
- this.testPath = TEST_PATH.test(path35);
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 path35 = namePath(fn);
5194
+ const path36 = namePath(fn);
5126
5195
  const bare = fn.type === "identifier";
5127
- this.c.call(n, bare ? "identifier" : "member", fn.text, path35, enclosing, bare && locals.has(fn.text), bare && member);
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(path35, tree) {
5139
- return new CppExtractor(path35, tree).run();
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(path35, tree) {
5254
+ constructor(path36, tree) {
5186
5255
  this.tree = tree;
5187
- this.c = new StructuralCollector(path35);
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 tokens2 = node.children.filter((c) => c !== null && !c.isNamed).map((c) => c?.type);
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
- ...tokens2.includes("static") ? { static: true } : {},
5213
- ...tokens2.includes("global") ? { global: true } : {},
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(path35, tree) {
5302
- return new CSharpExtractor(path35, tree).run();
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(path35, tree) {
5341
- this.path = path35;
5409
+ constructor(path36, tree) {
5410
+ this.path = path36;
5342
5411
  this.tree = tree;
5343
- this.c = new StructuralCollector(path35);
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(path35, tree) {
5467
- return new JavaExtractor(path35, tree).run();
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(path35, tree) {
5609
+ constructor(path36, tree) {
5541
5610
  this.tree = tree;
5542
- this.c = new StructuralCollector(path35);
5543
- this.testFile = PYTEST_FILE.test(path35);
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 path35 = base === null ? void 0 : namePath4(base);
5637
- if (path35 === void 0)
5705
+ const path36 = base === null ? void 0 : namePath4(base);
5706
+ if (path36 === void 0)
5638
5707
  continue;
5639
- if (path35.length === 1 && this.testCaseNames.has(path35[0]))
5708
+ if (path36.length === 1 && this.testCaseNames.has(path36[0]))
5640
5709
  return true;
5641
- if (path35.length === 2 && this.unittestModules.has(path35[0]) && path35[1] === "TestCase")
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(path35, tree) {
5836
- return new PythonExtractor(path35, tree).run();
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(path35, node) {
5984
+ function location2(path36, node) {
5916
5985
  return {
5917
- path: path35,
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(path35, tree) {
6004
- this.path = path35;
6072
+ constructor(path36, tree) {
6073
+ this.path = path36;
6005
6074
  this.tree = tree;
6006
- this.testFile = TEST_FILE.test(path35);
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(path35, tree) {
6610
- return new Extractor(path35, tree).run();
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 fs10 from "node:fs";
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, path35) {
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", `${path35}: ${value} is not a finite JSON number`);
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, `${path35}[${i}]`));
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], `${path35}.${key2}`);
6777
+ out[key2] = canonical2(value[key2], `${path36}.${key2}`);
6709
6778
  return out;
6710
6779
  }
6711
- throw new GraphStoreError("INVALID_INPUT", `${path35}: ${typeof value} is not a JSON value`);
6780
+ throw new GraphStoreError("INVALID_INPUT", `${path36}: ${typeof value} is not a JSON value`);
6712
6781
  }
6713
- function canonicalJson(value, path35) {
6714
- return JSON.stringify(canonical2(value ?? {}, path35));
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(path35) {
7103
+ function removeDatabaseFiles(path36) {
7035
7104
  for (const suffix of ["", "-wal", "-shm", "-journal"])
7036
- fs10.rmSync(path35 + suffix, { force: true });
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 = 1;
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 [path35, ctx] of [...this.contexts].sort(([a], [b]) => compareUtf8(a, b))) {
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(path35);
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(path35) === true && previous.modules.length === refs.length) {
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(path35, ref)));
8089
- configFiles = this.input.moduleResolver.configFiles(path35);
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(path35, new Map(refs.map((ref, i) => [ref, results[i]])));
8094
- this.resolution.set(path35, { modules: results, calls: [], exportDependencies: [], configFiles });
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(path35) {
8098
- const previous = this.memo?.previous.get(path35);
8099
- const current = this.resolution.get(path35);
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(path35) {
8104
- return this.memo === void 0 || this.memo.changedFiles.has(path35) || !this.sameModules(path35);
8179
+ surfaceChanged(path36) {
8180
+ return this.memo === void 0 || this.memo.changedFiles.has(path36) || !this.sameModules(path36);
8105
8181
  }
8106
- callFreshnessOf(path35, ctx) {
8107
- const previous = this.memo?.previous.get(path35);
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(path35))
8188
+ if (this.memo.changedFiles.has(path36))
8113
8189
  return "stale-source";
8114
- if (!this.sameModules(path35))
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 [path35, ctx] of [...this.contexts].sort(([a], [b]) => compareUtf8(a, b))) {
8139
- const file = fileRef(path35);
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(path35, ref);
8142
- this.moduleResolutions.push({ from: path35, specifier: ref.specifier, kind: ref.kind, location: ref.location, result });
8143
- if (result.status === "resolved" && result.path !== path35 && this.filePaths.has(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 [path35, ctx] of [...this.contexts].sort(([a], [b]) => compareUtf8(a, b))) {
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(path35, ctx);
8160
- const previous = this.memo?.previous.get(path35);
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(path35, ctx, call, deps));
8168
- deps.delete(path35);
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(path35, freshness);
8251
+ this.callFreshness.set(path36, freshness);
8176
8252
  const resolved = outcomes;
8177
- const current = this.resolution.get(path35);
8253
+ const current = this.resolution.get(path36);
8178
8254
  if (current !== void 0) {
8179
- this.resolution.set(path35, {
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: path35,
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(path35, ctx, exactTargets);
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(path35, ctx, exact) {
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(path35, test.fullName), "test", { provenance: "static", basis: ["exact-call"] });
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(path35, entry, member, kind, deps) {
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(path35, ref);
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(path35, ctx, call, deps) {
8371
+ resolveByStrategy(path36, ctx, call, deps) {
8296
8372
  if (ctx.strategy === "module-bindings")
8297
- return this.resolveCall(path35, ctx, call, deps);
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(path35, ctx, call);
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(path35, ctx, call) {
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: path35, symbol: only } };
8402
+ return { status: "exact", target: { file: path36, symbol: only } };
8327
8403
  }
8328
- resolveCall(path35, ctx, call, deps) {
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: path35, symbol: target } } : { status: "unresolved", reason: "member-not-found" };
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(path35, imported, second, call.kind, deps);
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: path35, symbol: only } };
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(path35, cls, second);
8441
+ return this.staticMember(path36, cls, second);
8366
8442
  }
8367
8443
  // ---- annotations ----
8368
8444
  annotationRelations() {
8369
- for (const [path35, ctx] of [...this.contexts].sort(([a], [b]) => compareUtf8(a, b))) {
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(path35);
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(path35, target.fullName), "annotation", metadata);
8472
+ this.addEdge(entity.ref, "VALIDATED_BY", testRef(path36, target.fullName), "annotation", metadata);
8397
8473
  else
8398
- this.addEdge(fileRef(path35), "IMPLEMENTS", entity.ref, "annotation", metadata);
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(path35) {
8482
- return `file:${path35}`;
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 = (path35) => METADATA_FILE.test(baseOf(path35));
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 fs13 from "node:fs";
8668
- import path15 from "node:path";
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 fs11 from "node:fs";
8675
- import path13 from "node:path";
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 (fs11.lstatSync(path13.join(root, partial)).isSymbolicLink())
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: fs11.readFileSync(path13.join(root, repoPath3), "utf8") };
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 fs11.promises.lstat(path13.join(root, partial))).isSymbolicLink())
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 fs11.promises.readFile(path13.join(root, repoPath3), "utf8") };
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 = path13.join(root, target);
8804
+ const absolute = path14.join(root, target);
8729
8805
  const temp = `${absolute}.${process.pid}.tmp`;
8730
8806
  try {
8731
- fs11.mkdirSync(path13.dirname(absolute), { recursive: true });
8732
- fs11.writeFileSync(temp, text4, "utf8");
8733
- fs11.renameSync(temp, absolute);
8807
+ fs12.mkdirSync(path14.dirname(absolute), { recursive: true });
8808
+ fs12.writeFileSync(temp, text4, "utf8");
8809
+ fs12.renameSync(temp, absolute);
8734
8810
  } catch (error) {
8735
- fs11.rmSync(temp, { force: true });
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 = fs11.readdirSync(path13.join(root, repoDir), { withFileTypes: true });
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
- fs11.rmSync(path13.join(root, repoDir, entry.name), { force: true });
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 fs12 from "node:fs";
8810
- import path14 from "node:path";
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(fs12.readFileSync(path14.join(root, repoPath3)), fingerprintModeOf(repoPath3)).contentHash;
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 path35 = scope.slice("file:".length);
9030
- nodes = [...store.getNodes([fileRef(path35)]), ...listAll(store, { ownerFile: path35 })];
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 = path15.resolve(root);
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 = path15.join(rootDir, allowed.value.path);
9114
- fs13.mkdirSync(path15.dirname(file), { recursive: true });
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 = path15.join(path15.resolve(root), GRAPH_DB_FILE_PATH);
9119
- if (!fs13.existsSync(file))
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 = path15.resolve(root);
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 = fs13.readFileSync(path15.join(rootDir, f.path));
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 fs13.readFileSync(path15.join(rootDir, p), "utf8");
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 path16 from "node:path";
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 = path16.resolve(root);
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((path35) => ({ status: "unavailable", path: path35, reason: read2.problem ?? "index state" }));
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((path35) => {
9641
- const f = files.get(path35);
9642
- return f?.analysis === void 0 || f.analysis.status !== "ok" || f.resolution === void 0 ? void 0 : { path: path35, contentHash: f.contentHash, analyzer: f.analysis.analyzer, analyzerIdentity: f.analysis.identity };
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((path35, i) => {
9648
- const f = files.get(path35);
9723
+ return paths.map((path36, i) => {
9724
+ const f = files.get(path36);
9649
9725
  if (f === void 0)
9650
- return { status: "not-indexed", path: path35 };
9726
+ return { status: "not-indexed", path: path36 };
9651
9727
  if (keys[i] === void 0)
9652
- return { status: "not-analyzed", path: path35 };
9653
- const entry = byPath.get(path35);
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: path35, reason: "analysis cache entry missing" };
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: path35, reason: "analysis and resolution do not match" };
9735
+ return { status: "unavailable", path: path36, reason: "analysis and resolution do not match" };
9660
9736
  return {
9661
9737
  status: "ok",
9662
- path: path35,
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 !== path35 && files.has(resolution.path) ? resolution.path : void 0;
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
- { edge: "IMPLEMENTS", direction: "outgoing", relation: "direct" },
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, path35) {
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 !== path35)
9841
+ if (o.path !== path36)
9763
9842
  return void 0;
9764
- const out = { path: path35 };
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 fs14 from "node:fs";
9802
- import path18 from "node:path";
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 path17 from "node:path";
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, path35 = "record") {
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", `${path35} is not valid JSON: ${error.message}`, { path: path35 })]);
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", `${path35} is not a record object`, { path: path35 })]);
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", `${path35} has format ${String(format)}`, { path: path35 })]);
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", `${path35}: id ${String(id3)} does not match its content (${expected})`, { path: path35 })]);
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, path35, file, id3, body) {
9923
- const guarded2 = guardWrite(root, path35, "human-history", { restrictTo: [`${REVIEWS_DIR}/`] });
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: path35, status: "created" });
9930
- const existing = verifyReviewRecord(await fsp3.readFile(guarded2.value.absolute, "utf8"), path35);
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", `${path35} exists with other content under the same ID; it is not overwritten`, { path: path35 })]);
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: path35, status: "unchanged" });
10015
+ return success({ id: id3, path: path36, status: "unchanged" });
9937
10016
  } catch (error) {
9938
- return failure([createDiagnostic("FILE_WRITE_ERROR", `Cannot record ${path35}: ${error.message}`, { path: path35 })]);
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 = path17.join(root, REVIEWS_DIR);
10041
+ const dir = path18.join(root, REVIEWS_DIR);
9963
10042
  let names;
9964
10043
  try {
9965
- for (const part of [path17.dirname(dir), dir]) {
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(path17.join(dir, name));
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(path17.join(dir, name), "utf8");
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(path35) {
10392
- if (path35 === STATE_DIR_NAME || path35.startsWith(`${STATE_DIR_NAME}/`))
10480
+ function nonApplicationReason(path36) {
10481
+ if (path36 === STATE_DIR_NAME || path36.startsWith(`${STATE_DIR_NAME}/`))
10393
10482
  return "project-truth";
10394
- const segments = path35.split("/");
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: 1 }).edges.length > 0)
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 = (path35, section) => `src:${path35}#${section ?? ""}`;
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 = "3";
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, path35) {
10738
+ function owned(graph, path36) {
10650
10739
  const out = [];
10651
10740
  let afterId;
10652
10741
  for (; ; ) {
10653
- const page = graph.listNodes({ ownerFile: path35, limit: 1e3, ...afterId === void 0 ? {} : { afterId } });
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, path35, evidenceId2) => {
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: path35, evidence: /* @__PURE__ */ new Set() };
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 = path18.join(root, p);
11495
- if (!fs14.lstatSync(abs).isFile())
11583
+ const abs = path19.join(root, p);
11584
+ if (!fs15.lstatSync(abs).isFile())
11496
11585
  return void 0;
11497
- return `sha256:${createHash12("sha256").update(fs14.readFileSync(abs)).digest("hex")}`;
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 = path18.join(root, REVIEWS_DIR);
11605
+ const dir = path19.join(root, REVIEWS_DIR);
11517
11606
  let names;
11518
11607
  try {
11519
- names = fs14.readdirSync(dir).filter((n) => ADOPTION_FILE.test(n)).sort(compareUtf8);
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 = fs14.readFileSync(path18.join(root, rel), "utf8");
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 tokens2 = encode(text4, PLAIN_TEXT);
11720
- if (tokens2.length <= maxTokens)
11808
+ const tokens3 = encode(text4, PLAIN_TEXT);
11809
+ if (tokens3.length <= maxTokens)
11721
11810
  return { text: text4, truncated: false };
11722
- return { text: decode(tokens2.slice(0, Math.max(0, maxTokens))).replace(/\uFFFD+$/u, ""), truncated: true };
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 fs15 from "node:fs";
11727
- import path19 from "node:path";
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 = path19.join(root, fileOf(digest));
11738
- if (fs15.lstatSync(target).isSymbolicLink())
11826
+ const target = path20.join(root, fileOf(digest));
11827
+ if (fs16.lstatSync(target).isSymbolicLink())
11739
11828
  return void 0;
11740
- const entry = JSON.parse(fs15.readFileSync(target, "utf8"));
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 = path19.join(root, allowed.value.path);
11841
+ const target = path20.join(root, allowed.value.path);
11753
11842
  const temp = `${target}.${process.pid}.tmp`;
11754
11843
  try {
11755
- fs15.mkdirSync(path19.dirname(target), { recursive: true });
11756
- fs15.writeFileSync(temp, JSON.stringify({ format: FORMAT2, digest: packet.dependencyDigest, packet }));
11757
- fs15.renameSync(temp, target);
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
- fs15.rmSync(temp, { force: true });
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(path35) {
11849
- return EXT_LANG.find(([re]) => re.test(path35))?.[1] ?? "";
11937
+ function langOf(path36) {
11938
+ return EXT_LANG.find(([re]) => re.test(path36))?.[1] ?? "";
11850
11939
  }
11851
- var extensionOf3 = (path35) => {
11852
- const name = path35.slice(path35.lastIndexOf("/") + 1);
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 path35 = ref.type === "symbol" || ref.type === "file" ? ref.path : void 0;
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
- ...path35 === void 0 ? {} : { path: path35 },
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 = (path35) => {
12011
- if (!fileLanguages.has(path35))
12012
- fileLanguages.set(path35, DEFAULT_EXTENSION_LANGUAGES[extensionOf3(path35)]);
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 fs16 from "node:fs";
12478
- import path20 from "node:path";
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(fs16.readFileSync(path20.join(root, TOKEN_COUNT_CACHE_PATH), "utf8"));
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, [tokens2, chars]]) => [k, { tokens: tokens2, chars }]));
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 = path20.join(root, allowed.value.path);
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
- fs16.mkdirSync(path20.dirname(target), { recursive: true });
12500
- fs16.writeFileSync(temp, JSON.stringify({ format: FORMAT3, estimator: TOKEN_ESTIMATOR_ID, counts: sorted }));
12501
- fs16.renameSync(temp, target);
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
- fs16.rmSync(temp, { force: true });
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 tokens2 = 0, bytes = 0, chars = 0, binaryFiles = 0;
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
- tokens2 += v.tokens;
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: tokens2, bytes, chars, perFile };
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(path35) {
12913
+ text(path36) {
12825
12914
  const t0 = performance3.now();
12826
- let r = this.files.get(path35);
12915
+ let r = this.files.get(path36);
12827
12916
  if (r === void 0) {
12828
- r = readSourceFile(this.root, path35);
12829
- this.files.set(path35, r);
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(path35, bounds) {
12866
- const text4 = this.text(path35).value;
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.split(/[\s,;:!?()[\]{}<>"'`]+/u).map((t2) => t2.replace(/^[.]+|[.]+$/gu, "")).filter((t2) => t2.length > 0);
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(path35) {
12907
- const tokens2 = rawTokens(path35);
12908
- return tokens2.length === 1 && tokens2[0] === path35 && path35.includes("/") && normalizeRepoPath(path35).value === path35;
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 tokens2 = rawTokens(name);
13022
+ const tokens3 = rawTokens(name);
12912
13023
  const upper = name.toUpperCase();
12913
- return tokens2.length === 1 && tokens2[0] === name && NAME_TOKEN.test(name) && name.length >= 3 && !isDefinitionId(upper) && !PROPOSAL_ID_PATTERN.test(upper);
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 tokens2 = rawTokens(task);
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 tokens2) {
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
- for (const token of tokens2) {
13136
+ const exactTokens = /* @__PURE__ */ new Set();
13137
+ for (const { token, pathText } of words) {
13025
13138
  if (idTokens2.has(token))
13026
13139
  continue;
13027
- if (token.includes("/")) {
13028
- const p = normalizeRepoPath(token.replace(/^\.\//u, "")).value;
13029
- const node = p === void 0 ? void 0 : store.getNode(fileRef(p));
13030
- if (node !== void 0) {
13031
- add(node, "path", token, SEED_STRENGTH.path);
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(tokens2.filter((t2) => !idTokens2.has(t2)).join(" ")));
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 fs17 from "node:fs";
13584
- import path21 from "node:path";
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 (fs17.statSync(path21.join(root, p)).size > maxBytes)
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 fs18 from "node:fs";
13705
- import path22 from "node:path";
13706
- var REGENERABLE = new Set(WRITE_AREAS.regenerable.map((a) => a.replace(/\/$/u, "")));
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 fs18.readdirSync(path22.join(root, dir)).sort(compareUtf8)) {
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 && REGENERABLE.has(name))
13851
+ if (dir === STATE_DIR_NAME && REGENERABLE2.has(name))
13715
13852
  continue;
13716
- const type = typeOf(fs18.lstatSync(path22.join(root, rel)));
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(fs18.readFileSync(path22.join(root, rel))).digest("hex")}` });
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(fs18.lstatSync(path22.join(root, STATE_DIR_NAME)));
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 fs20 from "node:fs";
13797
- import path24 from "node:path";
13933
+ import fs21 from "node:fs";
13934
+ import path25 from "node:path";
13798
13935
 
13799
13936
  // packages/director/dist/init/stack.js
13800
- import fs19 from "node:fs";
13801
- import path23 from "node:path";
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 (fs19.statSync(path23.join(root, file)).size > MAX_MANIFEST_BYTES)
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 (fs20.statSync(path24.join(root, file)).size > MAX_MANIFEST_BYTES2)
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: path24.basename(path24.resolve(root)), source: { kind: "directory" } };
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 fs21 from "node:fs";
14360
+ import fs22 from "node:fs";
14224
14361
  import fsp4 from "node:fs/promises";
14225
- import path25 from "node:path";
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" && fs21.lstatSync(absolute).isDirectory())
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, fs21.constants.COPYFILE_EXCL),
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, fs21.constants.COPYFILE_EXCL);
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 = path25.join(root, stagingRel);
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) => path25.join(stagingAbs, ...p.split("/"));
14564
+ const stageAbs = (p) => path26.join(stagingAbs, ...p.split("/"));
14428
14565
  const ensureStageDir = async (p) => {
14429
- await fsp4.mkdir(path25.dirname(stageAbs(p)), { recursive: true });
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(path25.join(root, p), stageAbs(p));
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(path25.join(root, d));
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 = path25.join(root, p);
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 fs22 from "node:fs";
14480
- import path26 from "node:path";
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 = path26.join(root, fileOf2(key2));
14498
- if (fs22.lstatSync(target).isSymbolicLink())
14634
+ const target = path27.join(root, fileOf2(key2));
14635
+ if (fs23.lstatSync(target).isSymbolicLink())
14499
14636
  return void 0;
14500
- const entry = JSON.parse(fs22.readFileSync(target, "utf8"));
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 = path26.join(root, allowed.value.path);
14647
+ const target = path27.join(root, allowed.value.path);
14511
14648
  const temp = `${target}.${process.pid}.tmp`;
14512
14649
  try {
14513
- fs22.mkdirSync(path26.dirname(target), { recursive: true });
14514
- fs22.writeFileSync(temp, JSON.stringify({ format: FORMAT4, key: key2, response }));
14515
- fs22.renameSync(temp, target);
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
- fs22.rmSync(temp, { force: true });
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 fs23 from "node:fs";
15414
+ import fs24 from "node:fs";
15260
15415
  import fsp5 from "node:fs/promises";
15261
- import path27 from "node:path";
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 (!fs23.existsSync(path27.join(root, STATE_DIR_NAME, PROJECT_FILE_NAME)))
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(path27.dirname(guarded2.value.absolute), { recursive: true });
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 = fs23.readFileSync(path27.join(root, METRICS_PATH), "utf8");
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 fs24 from "node:fs";
16074
- import path28 from "node:path";
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 = path28.join(root, REVIEWS_DIR);
16127
- const names = fs24.existsSync(dir) ? fs24.readdirSync(dir).filter((n) => /^review-[0-9a-f]{16}\.json$/u.test(n)).sort(compareUtf8) : [];
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(fs24.readFileSync(path28.join(dir, name), "utf8"));
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 path32 from "node:path";
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 fs26 from "node:fs";
16343
+ import fs27 from "node:fs";
16189
16344
  import fsp6 from "node:fs/promises";
16190
- import path31 from "node:path";
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: "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.",
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 lines4 = r.claims.filter((c) => c.alignment !== "ALIGNED").map((c) => `- ${c.alignment} ${c.rule} ${c.subject.id}: ${c.reason}${c.provenance === void 0 ? "" : ` (${provenanceLabel(c.provenance)})`}${c.blockEligible ? " [blocking]" : ""}`);
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 path29 from "node:path";
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 = path29.basename(e.command.replaceAll("\\", "/")).toLowerCase().replace(/\.(exe|cmd|bat|ps1)$/u, "");
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 fs25 from "node:fs";
16738
- import path30 from "node:path";
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 [path30.join(dir, name)];
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 path30.extname(name) !== "" ? [path30.join(dir, name)] : exts.map((e) => path30.join(dir, name + e));
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 === "" || !path30.isAbsolute(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 = fs25.statSync(file);
16918
+ const st = fs26.statSync(file);
16763
16919
  if (!st.isFile())
16764
16920
  continue;
16765
16921
  if (host2.platform !== "win32")
16766
- fs25.accessSync(file, fs25.constants.X_OK);
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 = fs25.readdirSync(root);
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) && !path30.isAbsolute(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 (path30.isAbsolute(command2)) {
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 = fs25.existsSync(command2);
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) => path30.join(root, "node_modules", ".bin", n));
16812
- localBin = bin.find((b) => fs25.existsSync(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 = path31.join(root, rel);
16987
+ const abs = path32.join(root, rel);
16832
16988
  let bytes;
16833
16989
  try {
16834
- const st = fs26.lstatSync(abs);
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 = fs26.readFileSync(abs);
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) => fs26.existsSync(path31.join(root, STATE_DIR_NAME, PROJECT_FILE_NAME));
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 = path31.dirname(g.value.absolute);
17062
- if (dir !== root && fs26.readdirSync(dir).length === 0)
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 = path31.join(root, c.path);
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 = path32.resolve(rootArg);
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 summary = run2.op.kind === "ok" ? tool.summarize(payload) : String(payload.message);
17469
- return { content: [{ type: "text", text: summary }], structuredContent: payload };
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 path33 from "node:path";
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(path33.resolve(host2.cwd, spec2.root ?? "."));
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 (!path33.isAbsolute(value))
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(path33.resolve(value));
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 fs27 from "node:fs";
17694
+ import fs28 from "node:fs";
17539
17695
  import http from "node:http";
17540
- import path34 from "node:path";
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) => fs27.existsSync(path34.join(d, "index.html")));
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 fs27.readdirSync(dir)) {
17743
- const type = TYPES[path34.extname(name).toLowerCase()];
17744
- const file = path34.join(dir, name);
17745
- if (type === void 0 || !fs27.lstatSync(file).isFile() || name.endsWith(".map"))
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: fs27.readFileSync(file) });
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: path34.basename(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>",