@dotdotgod/hermes 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mcp/cli.mjs CHANGED
@@ -538,7 +538,7 @@ var init_languages = __esm({
538
538
  ".zsh",
539
539
  ".fish"
540
540
  ]);
541
- EXTRACTOR_ID = "outline-v3:vscode-tree-sitter-wasm-0.3.1";
541
+ EXTRACTOR_ID = "outline-v4:vscode-0.3.1:typescript-import-types-1";
542
542
  DECLARATIONS = {
543
543
  function_declaration: "function",
544
544
  generator_function_declaration: "function",
@@ -576,19 +576,24 @@ var init_languages = __esm({
576
576
 
577
577
  // packages/cli/src/symbols/runtime.mjs
578
578
  import { createRequire } from "node:module";
579
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
580
- import { dirname as dirname5, join as join8 } from "node:path";
579
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
580
+ import { dirname as dirname6, join as join9 } from "node:path";
581
581
  import { fileURLToPath } from "node:url";
582
582
  import { createHash as createHash4 } from "node:crypto";
583
+ function languageAsset(language) {
584
+ const patched = fileURLToPath(new URL(`./parsers/tree-sitter-${language}.wasm`, import.meta.url));
585
+ return existsSync10(patched) ? patched : join9(dirname6(parserEntry()), `tree-sitter-${language}.wasm`);
586
+ }
583
587
  function parserEntry() {
584
588
  const bundled = fileURLToPath(new URL("./parsers/tree-sitter.cjs", import.meta.url));
585
- return existsSync9(bundled) ? bundled : require2.resolve("@vscode/tree-sitter-wasm");
589
+ return existsSync10(bundled) ? bundled : require2.resolve("@vscode/tree-sitter-wasm");
586
590
  }
587
591
  function parserIdentity() {
588
592
  if (!identity) {
589
593
  const entry = parserEntry();
590
- const hash2 = createHash4("sha256").update(readFileSync9(entry));
591
- for (const name of ["tree-sitter.wasm", ...Object.keys(LANGUAGE_EXTENSIONS).map((language) => `tree-sitter-${language}.wasm`)]) hash2.update(name).update(readFileSync9(join8(dirname5(entry), name)));
594
+ const hash2 = createHash4("sha256").update(readFileSync10(entry));
595
+ hash2.update(readFileSync10(join9(dirname6(entry), "tree-sitter.wasm")));
596
+ for (const language of Object.keys(LANGUAGE_EXTENSIONS)) hash2.update(language).update(readFileSync10(languageAsset(language)));
592
597
  identity = hash2.digest("hex");
593
598
  }
594
599
  return identity;
@@ -609,17 +614,17 @@ var parser_exports = {};
609
614
  __export(parser_exports, {
610
615
  extractSymbols: () => extractSymbols
611
616
  });
612
- import { dirname as dirname6, join as join10 } from "node:path";
617
+ import { dirname as dirname7 } from "node:path";
613
618
  import { createHash as createHash5 } from "node:crypto";
614
619
  async function parserFor(language) {
615
620
  if (!runtime) runtime = (async () => {
616
621
  const entry = parserEntry();
617
622
  const api2 = loadParserApi();
618
623
  await api2.Parser.init();
619
- return { api: api2, directory: dirname6(entry) };
624
+ return { api: api2, directory: dirname7(entry) };
620
625
  })();
621
626
  const { api, directory } = await runtime;
622
- if (!languages.has(language)) languages.set(language, api.Language.load(join10(directory, `tree-sitter-${language}.wasm`)));
627
+ if (!languages.has(language)) languages.set(language, api.Language.load(languageAsset(language)));
623
628
  const loaded = await languages.get(language);
624
629
  const parser = new api.Parser();
625
630
  parser.setLanguage(loaded);
@@ -756,8 +761,8 @@ var init_parser = __esm({
756
761
  });
757
762
 
758
763
  // packages/cli/src/init.mjs
759
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, statSync, writeFileSync } from "node:fs";
760
- import { basename, dirname, join as join4, resolve as resolve2 } from "node:path";
764
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync2 } from "node:fs";
765
+ import { basename, dirname as dirname2, join as join5, resolve as resolve3 } from "node:path";
761
766
 
762
767
  // packages/cli/src/config/templates.mjs
763
768
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
@@ -896,6 +901,7 @@ function validateEmbeddingProfile(profile, label = "embedding") {
896
901
  return { ...profile, model: profile.model.trim(), ...profile.baseUrl ? { baseUrl: profile.baseUrl.replace(/\/$/, "") } : {} };
897
902
  }
898
903
  function sanitizeEmbeddingProfile(profile) {
904
+ if (!profile) return null;
899
905
  const { apiKey, ...safe } = profile;
900
906
  return { ...safe, ...apiKey ? { apiKey: "[redacted]" } : {} };
901
907
  }
@@ -916,7 +922,7 @@ function resolveEmbeddingProfile(root = ".", options = {}) {
916
922
  if (projectConfig && Object.hasOwn(projectConfig, "embedding")) return { profile: validateEmbeddingProfile(projectConfig.embedding, "project embedding"), source: "project", path: projectPath };
917
923
  const globalConfig = readJson(globalPath, "global config");
918
924
  if (globalConfig && Object.hasOwn(globalConfig, "embedding")) return { profile: validateEmbeddingProfile(globalConfig.embedding, "global embedding"), source: "global", path: globalPath };
919
- return { profile: { ...DEFAULT_EMBEDDING_PROFILE }, source: "default", path: null };
925
+ return { profile: null, source: "not-configured", path: null };
920
926
  }
921
927
 
922
928
  // packages/cli/src/memory/config.mjs
@@ -1702,6 +1708,60 @@ function resolveInitializationTemplate(explicitName = null, home = dotdotgodHome
1702
1708
  ` };
1703
1709
  }
1704
1710
 
1711
+ // packages/cli/src/claude-plans.mjs
1712
+ import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync4, realpathSync, writeFileSync, mkdirSync } from "node:fs";
1713
+ import { dirname, join as join4, relative as relative2, resolve as resolve2, isAbsolute } from "node:path";
1714
+ function prepareClaudePlans(root, documentationRoot) {
1715
+ const path = join4(root, ".claude", "settings.json");
1716
+ for (const target of [join4(root, ".claude"), path]) {
1717
+ try {
1718
+ if (lstatSync(target).isSymbolicLink()) throw new Error(`Refusing symlink: ${target}`);
1719
+ } catch (error) {
1720
+ if (error.code !== "ENOENT") throw error;
1721
+ }
1722
+ }
1723
+ const base = resolve2(root);
1724
+ if (existsSync4(dirname(path))) {
1725
+ const rel2 = relative2(realpathSync(base), realpathSync(dirname(path)));
1726
+ if (rel2.startsWith("..") || isAbsolute(rel2)) throw new Error("Claude settings must stay inside the project.");
1727
+ }
1728
+ const current = existsSync4(path) ? readFileSync4(path, "utf8") : null;
1729
+ const settings = current === null ? {} : JSON.parse(current);
1730
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error("Claude settings must be a JSON object.");
1731
+ const value = `./${documentationRoot}/plan`;
1732
+ if (Object.hasOwn(settings, "plansDirectory")) return {
1733
+ status: "skipped",
1734
+ path,
1735
+ reason: "Existing plansDirectory preserved.",
1736
+ value: settings.plansDirectory
1737
+ };
1738
+ return {
1739
+ status: current === null ? "created" : "updated",
1740
+ path,
1741
+ value,
1742
+ current,
1743
+ content: `${JSON.stringify({ ...settings, plansDirectory: value }, null, 2)}
1744
+ `
1745
+ };
1746
+ }
1747
+ function applyClaudePlans(prepared, dryRun) {
1748
+ const { content, current, ...result } = prepared;
1749
+ if (!content) return result;
1750
+ if (dryRun) return { ...result, status: `would_${result.status === "created" ? "create" : "update"}` };
1751
+ for (const target of [dirname(prepared.path), prepared.path]) {
1752
+ try {
1753
+ if (lstatSync(target).isSymbolicLink()) throw new Error(`Refusing symlink: ${target}`);
1754
+ } catch (error) {
1755
+ if (error.code !== "ENOENT") throw error;
1756
+ }
1757
+ }
1758
+ const latest = existsSync4(prepared.path) ? readFileSync4(prepared.path, "utf8") : null;
1759
+ if (latest !== current) throw new Error("Claude settings changed during initialization; retry without overwriting them.");
1760
+ mkdirSync(dirname(prepared.path), { recursive: true });
1761
+ writeFileSync(prepared.path, content, { flag: current === null ? "wx" : "w" });
1762
+ return result;
1763
+ }
1764
+
1705
1765
  // packages/cli/src/init.mjs
1706
1766
  function action(status, path, extra = {}) {
1707
1767
  return { status, path, ...extra };
@@ -1717,6 +1777,7 @@ function parseInitOptions(argv) {
1717
1777
  if (arg === "--json") options.json = true;
1718
1778
  else if (arg === "--dotdot-setting") options.dotdotSetting = true;
1719
1779
  else if (arg === "--dry-run") options.dryRun = true;
1780
+ else if (arg === "--claude-plans") options.claudePlans = true;
1720
1781
  else if (arg === "--project-name" || arg === "--template" || arg === "--documentation-root") {
1721
1782
  const value = argv[i + 1];
1722
1783
  if (!value || value.startsWith("-")) throw new Error(`${arg} requires a value`);
@@ -1737,7 +1798,7 @@ function parseInitOptions(argv) {
1737
1798
  }
1738
1799
  if (!options.root) throw new Error("Missing required argument: <root>");
1739
1800
  if (!isValidDocumentationRoot(options.documentationRoot)) throw new Error("Invalid --documentation-root: expected a safe repository-relative directory");
1740
- options.root = resolve2(options.root);
1801
+ options.root = resolve3(options.root);
1741
1802
  if (!options.projectName) options.projectName = basename(options.root);
1742
1803
  return options;
1743
1804
  }
@@ -1750,8 +1811,8 @@ function initError(options, code, message) {
1750
1811
  process.exit(2);
1751
1812
  }
1752
1813
  function writeInitFile(options, relativePath, content, actions) {
1753
- const target = join4(options.root, relativePath);
1754
- if (existsSync4(target)) {
1814
+ const target = join5(options.root, relativePath);
1815
+ if (existsSync5(target)) {
1755
1816
  actions.push(action("skipped", target));
1756
1817
  return;
1757
1818
  }
@@ -1759,14 +1820,14 @@ function writeInitFile(options, relativePath, content, actions) {
1759
1820
  actions.push(action("would_create", target));
1760
1821
  return;
1761
1822
  }
1762
- mkdirSync(dirname(target), { recursive: true });
1763
- writeFileSync(target, content.endsWith("\n") ? content : `${content}
1823
+ mkdirSync2(dirname2(target), { recursive: true });
1824
+ writeFileSync2(target, content.endsWith("\n") ? content : `${content}
1764
1825
  `);
1765
1826
  actions.push(action("created", target));
1766
1827
  }
1767
1828
  function ensureInitDirectory(options, relativePath, actions) {
1768
- const target = join4(options.root, relativePath);
1769
- if (existsSync4(target)) {
1829
+ const target = join5(options.root, relativePath);
1830
+ if (existsSync5(target)) {
1770
1831
  actions.push(action("skipped", target));
1771
1832
  return;
1772
1833
  }
@@ -1774,22 +1835,22 @@ function ensureInitDirectory(options, relativePath, actions) {
1774
1835
  actions.push(action("would_create", target));
1775
1836
  return;
1776
1837
  }
1777
- mkdirSync(target, { recursive: true });
1838
+ mkdirSync2(target, { recursive: true });
1778
1839
  actions.push(action("created", target));
1779
1840
  }
1780
1841
  function ensureGitignoreEntry(options, entry, actions) {
1781
- const target = join4(options.root, ".gitignore");
1782
- const existed = existsSync4(target);
1783
- let current = existed ? readFileSync4(target, "utf8") : "";
1842
+ const target = join5(options.root, ".gitignore");
1843
+ const existed = existsSync5(target);
1844
+ let current = existed ? readFileSync5(target, "utf8") : "";
1784
1845
  if (current.split(/\r?\n/).includes(entry)) return;
1785
1846
  if (options.dryRun) {
1786
1847
  actions.push(action(existed ? "would_update" : "would_create", target, { add: entry }));
1787
1848
  return;
1788
1849
  }
1789
- mkdirSync(options.root, { recursive: true });
1850
+ mkdirSync2(options.root, { recursive: true });
1790
1851
  if (current.length > 0 && !current.endsWith("\n")) current = `${current}
1791
1852
  `;
1792
- writeFileSync(target, `${current}${entry}
1853
+ writeFileSync2(target, `${current}${entry}
1793
1854
  `);
1794
1855
  actions.push(action(existed ? "updated" : "created", target, { add: entry }));
1795
1856
  }
@@ -1993,15 +2054,26 @@ function runInit(argv, usage2) {
1993
2054
  usage2(error instanceof Error ? error.message : String(error), "init");
1994
2055
  return;
1995
2056
  }
1996
- if (!options.dryRun && existsSync4(options.root)) {
2057
+ if (!options.dryRun && existsSync5(options.root)) {
1997
2058
  try {
1998
2059
  if (!statSync(options.root).isDirectory()) initError(options, "ROOT_NOT_DIRECTORY", `Project root is not a directory: ${options.root}`);
1999
2060
  } catch {
2000
2061
  initError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
2001
2062
  }
2002
2063
  }
2003
- if (!options.dryRun) mkdirSync(options.root, { recursive: true });
2004
- const configExists = existsSync4(join4(options.root, "dotdotgod.config.json"));
2064
+ if (options.claudePlans && !options.documentationRootExplicit && existsSync5(join5(options.root, "dotdotgod.config.json"))) {
2065
+ options.documentationRoot = readMemoryConfig(options.root).documentation.root;
2066
+ }
2067
+ let claudePlans;
2068
+ if (options.claudePlans) {
2069
+ try {
2070
+ claudePlans = prepareClaudePlans(options.root, options.documentationRoot);
2071
+ } catch (error) {
2072
+ initError(options, "CLAUDE_SETTINGS_INVALID", error.message);
2073
+ }
2074
+ }
2075
+ if (!options.dryRun) mkdirSync2(options.root, { recursive: true });
2076
+ const configExists = existsSync5(join5(options.root, "dotdotgod.config.json"));
2005
2077
  if (configExists && options.documentationRootExplicit) {
2006
2078
  const existingRoot = readMemoryConfig(options.root).documentation.root;
2007
2079
  if (existingRoot !== options.documentationRoot) initError(options, "DOCUMENTATION_ROOT_CONFLICT", `Existing config uses documentation root ${existingRoot}; initialization does not migrate documentation.`);
@@ -2024,6 +2096,13 @@ function runInit(argv, usage2) {
2024
2096
  }
2025
2097
  writeInitFile(options, "dotdotgod.config.json", template?.text ?? "", actions);
2026
2098
  for (const entry of [`${options.documentationRoot}/plan`, `${options.documentationRoot}/archive`, ".dotdotgod"]) ensureGitignoreEntry(options, entry, actions);
2099
+ if (claudePlans) {
2100
+ try {
2101
+ actions.push(applyClaudePlans(claudePlans, options.dryRun));
2102
+ } catch (error) {
2103
+ initError(options, "CLAUDE_SETTINGS_CHANGED", error.message);
2104
+ }
2105
+ }
2027
2106
  const payload = { ok: true, command: "init", root: options.root, projectName: options.projectName, documentationRoot: options.documentationRoot, dryRun: options.dryRun, dotdotSetting: options.dotdotSetting, template: template ? { name: template.name, source: template.source, selectedBy: template.selectedBy } : null, actions };
2028
2107
  if (options.json) console.log(JSON.stringify(payload, null, 2));
2029
2108
  else {
@@ -2033,8 +2112,8 @@ function runInit(argv, usage2) {
2033
2112
  }
2034
2113
 
2035
2114
  // packages/cli/src/cli/usage.mjs
2036
- import { readFileSync as readFileSync5 } from "node:fs";
2037
- import { resolve as resolve3 } from "node:path";
2115
+ import { readFileSync as readFileSync6 } from "node:fs";
2116
+ import { resolve as resolve4 } from "node:path";
2038
2117
  var HELP_TOKENS = /* @__PURE__ */ new Set(["help", "--help", "-h"]);
2039
2118
  var VERSION_TOKENS = /* @__PURE__ */ new Set(["version", "--version", "-v"]);
2040
2119
  function commandUsage(command = "root") {
@@ -2044,9 +2123,10 @@ function commandUsage(command = "root") {
2044
2123
  dotdotgod validate <root> [--include-local-memory] [--check-index] [--max-lines n] [--max-chars n] [--no-link-check] [--json]`;
2045
2124
  case "init":
2046
2125
  return `Usage:
2047
- dotdotgod init <root> [--project-name NAME] [--template NAME] [--documentation-root PATH] [--dotdot-setting] [--dry-run] [--json]
2126
+ dotdotgod init <root> [--project-name NAME] [--template NAME] [--documentation-root PATH] [--dotdot-setting] [--claude-plans] [--dry-run] [--json]
2048
2127
 
2049
- Create AGENTS.md, agent entrypoints, docs indexes, a project config from an initialization template, and local memory gitignore entries.`;
2128
+ Create AGENTS.md, agent entrypoints, docs indexes, a project config from an initialization template, and local memory gitignore entries.
2129
+ Use --claude-plans only after opting into project-local native plan storage; existing Claude settings are preserved.`;
2050
2130
  case "index":
2051
2131
  return `Usage:
2052
2132
  dotdotgod index <root> [--json]`;
@@ -2066,9 +2146,9 @@ Create dotdotgod.config.json from a bundled or user template. Without --template
2066
2146
  dotdotgod status <root> [--json]`;
2067
2147
  case "query":
2068
2148
  return `Usage:
2069
- dotdotgod query <root> <query> [--limit n] [--scope docs|code|all] [--search hybrid|keyword|vector] [--allow-code-embedding] [--json]
2149
+ dotdotgod query <root> <query> [--limit n] [--scope docs|code|all] [--search hybrid|keyword|vector] [--json]
2070
2150
 
2071
- Search full documentation and code outlines/comments. Default: hybrid docs+code. Remote code metadata requires explicit consent.`;
2151
+ Search full documentation and code outlines/comments. Default: hybrid docs+code. Selecting a remote embedding profile authorizes code metadata transmission.`;
2072
2152
  case "embedding":
2073
2153
  return `Usage:
2074
2154
  dotdotgod embedding status [<root>] [--json]
@@ -2173,7 +2253,7 @@ function isVersionToken(value) {
2173
2253
  function printVersion() {
2174
2254
  let version = "unknown";
2175
2255
  try {
2176
- const data = JSON.parse(readFileSync5(new URL("../../package.json", import.meta.url), "utf8"));
2256
+ const data = JSON.parse(readFileSync6(new URL("../../package.json", import.meta.url), "utf8"));
2177
2257
  if (typeof data.version === "string") version = data.version;
2178
2258
  } catch {
2179
2259
  }
@@ -2195,17 +2275,17 @@ function parseCommon(argv) {
2195
2275
  if (arg === "--json") options.json = true;
2196
2276
  else if (!arg.startsWith("-") && options.root === ".") options.root = arg;
2197
2277
  }
2198
- options.root = resolve3(options.root);
2278
+ options.root = resolve4(options.root);
2199
2279
  return options;
2200
2280
  }
2201
2281
 
2202
2282
  // packages/cli/src/validate/run.mjs
2203
- import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync3 } from "node:fs";
2204
- import { basename as basename7, dirname as dirname10, extname as extname6, join as join13, resolve as resolve8 } from "node:path";
2283
+ import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync as readdirSync3 } from "node:fs";
2284
+ import { basename as basename7, dirname as dirname11, extname as extname6, join as join13, resolve as resolve9 } from "node:path";
2205
2285
 
2206
2286
  // packages/cli/src/docs/traceability.mjs
2207
- import { existsSync as existsSync5 } from "node:fs";
2208
- import { basename as basename2, dirname as dirname2, extname, relative as relative2, resolve as resolve4 } from "node:path";
2287
+ import { existsSync as existsSync6 } from "node:fs";
2288
+ import { basename as basename2, dirname as dirname3, extname, relative as relative3, resolve as resolve5 } from "node:path";
2209
2289
  function isSecretIndexPath(path) {
2210
2290
  return /(^|\/)(\.env|\.npmrc|\.pypirc|id_rsa|id_dsa|id_ed25519|credentials|secrets?)(\.|\/|$)/i.test(path);
2211
2291
  }
@@ -2304,9 +2384,9 @@ function stripTraceabilityLinksRegion(content) {
2304
2384
  return stripped.replace(/(?:^[ \t]*\r?\n)*^##[ \t]+Traceability[ \t]*\r?(?:\n(?:[ \t]*\r?\n)*|$)/gm, "");
2305
2385
  }
2306
2386
  function markdownLinkPath(root, file, target) {
2307
- const fromDir = dirname2(file);
2308
- const absoluteTarget = resolve4(root, target);
2309
- let link = relative2(fromDir, absoluteTarget).split("\\").join("/");
2387
+ const fromDir = dirname3(file);
2388
+ const absoluteTarget = resolve5(root, target);
2389
+ let link = relative3(fromDir, absoluteTarget).split("\\").join("/");
2310
2390
  if (!link.startsWith(".") && !link.startsWith("/")) link = link || basename2(target);
2311
2391
  return link;
2312
2392
  }
@@ -2405,7 +2485,7 @@ function validateTraceabilityPathArray(data, field2, add, root, config, { requir
2405
2485
  add("TRACEABILITY_LOCAL_MEMORY_TARGET", displayField, `target points to local memory area "${area2.id}": ${value}. Traceability must point to shared durable files, not active plans or archive memory.`);
2406
2486
  continue;
2407
2487
  }
2408
- if (!existsSync5(resolve4(root, value))) add("TRACEABILITY_MISSING_TARGET", displayField, `target does not exist: ${value}.`);
2488
+ if (!existsSync6(resolve5(root, value))) add("TRACEABILITY_MISSING_TARGET", displayField, `target does not exist: ${value}.`);
2409
2489
  }
2410
2490
  }
2411
2491
  function validateTraceabilityCommandArray(data, field2, add, { required = true, displayField = field2 } = {}) {
@@ -2473,9 +2553,9 @@ function validateTraceabilityBlock(data, root, file, line = null, config = defau
2473
2553
 
2474
2554
  // packages/cli/src/index/files.mjs
2475
2555
  import { createHash } from "node:crypto";
2476
- import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync, statSync as statSync2 } from "node:fs";
2556
+ import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync, statSync as statSync2 } from "node:fs";
2477
2557
  import { spawnSync } from "node:child_process";
2478
- import { basename as basename3, extname as extname2, join as join5 } from "node:path";
2558
+ import { basename as basename3, extname as extname2, join as join6 } from "node:path";
2479
2559
 
2480
2560
  // packages/cli/src/index/constants.mjs
2481
2561
  var CACHE_VERSION = 15;
@@ -2636,7 +2716,7 @@ function walkIndexCandidates(root, config = readMemoryConfig(root)) {
2636
2716
  const files = [];
2637
2717
  const walk = (dir) => {
2638
2718
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
2639
- const path = join5(dir, entry.name);
2719
+ const path = join6(dir, entry.name);
2640
2720
  const pathRel = rel(root, path);
2641
2721
  if (entry.isDirectory()) {
2642
2722
  const area2 = resolveMemoryArea(pathRel, config);
@@ -2650,7 +2730,7 @@ function walkIndexCandidates(root, config = readMemoryConfig(root)) {
2650
2730
  function addConfiguredLocalMemoryCandidates(root, candidates, config, limit = 5e3) {
2651
2731
  let added = 0;
2652
2732
  const addPath = (path) => {
2653
- if (added >= limit || !existsSync6(path)) return;
2733
+ if (added >= limit || !existsSync7(path)) return;
2654
2734
  let stat;
2655
2735
  try {
2656
2736
  stat = statSync2(path);
@@ -2666,7 +2746,7 @@ function addConfiguredLocalMemoryCandidates(root, candidates, config, limit = 5e
2666
2746
  if (!stat.isDirectory() || isExcludedIndexDir(pathRel)) return;
2667
2747
  for (const entry of readdirSync(path, { withFileTypes: true })) {
2668
2748
  if (added >= limit) return;
2669
- addPath(join5(path, entry.name));
2749
+ addPath(join6(path, entry.name));
2670
2750
  }
2671
2751
  };
2672
2752
  for (const area2 of config.areas ?? []) {
@@ -2674,30 +2754,30 @@ function addConfiguredLocalMemoryCandidates(root, candidates, config, limit = 5e
2674
2754
  for (const pattern of area2.paths ?? []) {
2675
2755
  if (pattern.startsWith("**/")) continue;
2676
2756
  const base = pattern.endsWith("/**") ? pattern.slice(0, -3) : pattern;
2677
- addPath(join5(root, base));
2757
+ addPath(join6(root, base));
2678
2758
  }
2679
2759
  }
2680
2760
  }
2681
2761
  function collectIndexFiles(root, config = readMemoryConfig(root)) {
2682
2762
  const candidates = new Set(gitIndexCandidates(root) ?? walkIndexCandidates(root, config));
2683
2763
  addConfiguredLocalMemoryCandidates(root, candidates, config);
2684
- return [...candidates].filter((path) => shouldIndexPath(path, config)).map((path) => join5(root, path)).filter(existsSync6).sort();
2764
+ return [...candidates].filter((path) => shouldIndexPath(path, config)).map((path) => join6(root, path)).filter(existsSync7).sort();
2685
2765
  }
2686
2766
  function fingerprint(file) {
2687
- const content = readFileSync6(file);
2767
+ const content = readFileSync7(file);
2688
2768
  return createHash("sha256").update(content).digest("hex");
2689
2769
  }
2690
2770
  function cacheFile(root) {
2691
- return join5(root, CACHE_DIR, MANIFEST_FILE);
2771
+ return join6(root, CACHE_DIR, MANIFEST_FILE);
2692
2772
  }
2693
2773
 
2694
2774
  // packages/cli/src/index/cache.mjs
2695
- import { existsSync as existsSync12, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
2775
+ import { existsSync as existsSync13, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
2696
2776
  import { basename as basename6, join as join12 } from "node:path";
2697
2777
 
2698
2778
  // packages/cli/src/graph/store.mjs
2699
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
2700
- import { dirname as dirname3, join as join6 } from "node:path";
2779
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
2780
+ import { dirname as dirname4, join as join7 } from "node:path";
2701
2781
  var GRAPH_NODE_SHARDS = ["docs", "packages", "source"];
2702
2782
  var GRAPH_EDGE_SHARDS = ["docs-links", "packages", "other"];
2703
2783
  function graphNodeShard(node2) {
@@ -2728,14 +2808,14 @@ function expandEdge(row) {
2728
2808
  return { source, target, relation, ...data };
2729
2809
  }
2730
2810
  function shardFile(root, kind, name) {
2731
- return join6(root, CACHE_DIR, "graph", kind, `${name}.json`);
2811
+ return join7(root, CACHE_DIR, "graph", kind, `${name}.json`);
2732
2812
  }
2733
2813
  function jsonSize(file) {
2734
- return existsSync7(file) ? statSync3(file).size : 0;
2814
+ return existsSync8(file) ? statSync3(file).size : 0;
2735
2815
  }
2736
2816
  function writeJson(file, value) {
2737
- mkdirSync2(dirname3(file), { recursive: true });
2738
- writeFileSync2(file, `${JSON.stringify(value)}
2817
+ mkdirSync3(dirname4(file), { recursive: true });
2818
+ writeFileSync3(file, `${JSON.stringify(value)}
2739
2819
  `);
2740
2820
  }
2741
2821
  function compactGraph(graph) {
@@ -2798,13 +2878,13 @@ function addEdge(graph, source, target, relation, data = {}) {
2798
2878
  }
2799
2879
 
2800
2880
  // packages/cli/src/graph/extract.mjs
2801
- import { readFileSync as readFileSync7 } from "node:fs";
2802
- import { basename as basename5, dirname as dirname4, resolve as resolve6 } from "node:path";
2881
+ import { readFileSync as readFileSync8 } from "node:fs";
2882
+ import { basename as basename5, dirname as dirname5, resolve as resolve7 } from "node:path";
2803
2883
 
2804
2884
  // packages/cli/src/graph/metadata.mjs
2805
2885
  import { createHash as createHash2 } from "node:crypto";
2806
- import { existsSync as existsSync8 } from "node:fs";
2807
- import { basename as basename4, extname as extname3, resolve as resolve5 } from "node:path";
2886
+ import { existsSync as existsSync9 } from "node:fs";
2887
+ import { basename as basename4, extname as extname3, resolve as resolve6 } from "node:path";
2808
2888
  function isReadmeIndexPath(path = "") {
2809
2889
  return basename4(path) === "README.md";
2810
2890
  }
@@ -2863,7 +2943,7 @@ function addReferenceEvidence(graph, sourceId, reference) {
2863
2943
  function addTraceabilityTarget(graph, sourceId, root, relation, targetPath, data = {}, config = defaultMemoryConfig()) {
2864
2944
  if (!isLocalRelativeTraceabilityPath(targetPath)) return;
2865
2945
  addReferenceEvidence(graph, sourceId, { targetPath, href: targetPath, relation, data: { confidence: "CURATED_TRACEABILITY", ...data } });
2866
- if (!existsSync8(resolve5(root, targetPath))) return;
2946
+ if (!existsSync9(resolve6(root, targetPath))) return;
2867
2947
  const targetId = `file:${targetPath}`;
2868
2948
  addNode(graph, targetId, "file", fileNodeMetadata(targetPath, null, config));
2869
2949
  addEdge(graph, sourceId, targetId, relation, { confidence: "CURATED_TRACEABILITY", ...data });
@@ -2915,7 +2995,7 @@ function headingId(path, title) {
2915
2995
  // packages/cli/src/graph/extract.mjs
2916
2996
  function extractMarkdownGraph(root, file, graph, config = defaultMemoryConfig()) {
2917
2997
  const path = rel(root, file);
2918
- const content = readFileSync7(file, "utf8");
2998
+ const content = readFileSync8(file, "utf8");
2919
2999
  const fileId = `file:${path}`;
2920
3000
  const headingRe = /^(#{1,6})\s+(.+)$/gm;
2921
3001
  let match;
@@ -2929,7 +3009,7 @@ function extractMarkdownGraph(root, file, graph, config = defaultMemoryConfig())
2929
3009
  const hashIndex = href.indexOf("#");
2930
3010
  const pathPart = hashIndex < 0 ? href : href.slice(0, hashIndex);
2931
3011
  const anchor = hashIndex < 0 ? "" : href.slice(hashIndex + 1);
2932
- const targetPath = pathPart ? rel(root, resolve6(dirname4(file), pathPart)) : path;
3012
+ const targetPath = pathPart ? rel(root, resolve7(dirname5(file), pathPart)) : path;
2933
3013
  const data = { line, confidence: "EXTRACTED" };
2934
3014
  addReferenceEvidence(graph, fileId, { targetPath, href, anchor, relation: "links_to", data });
2935
3015
  if (pathPart && isReadmeIndexPath(path)) addReferenceEvidence(graph, fileId, { targetPath, href, anchor, relation: "routes_to", data: { line, confidence: "CURATED_INDEX", sourceRole: "readme-index" } });
@@ -2946,7 +3026,7 @@ function extractPackageGraph(root, file, graph) {
2946
3026
  const fileId = `file:${path}`;
2947
3027
  let pkg;
2948
3028
  try {
2949
- pkg = JSON.parse(readFileSync7(file, "utf8"));
3029
+ pkg = JSON.parse(readFileSync8(file, "utf8"));
2950
3030
  } catch {
2951
3031
  return;
2952
3032
  }
@@ -2992,18 +3072,18 @@ function buildGraph(root, files, config = defaultMemoryConfig()) {
2992
3072
  }
2993
3073
 
2994
3074
  // packages/cli/src/symbols/store.mjs
2995
- import { existsSync as existsSync11, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync11, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
3075
+ import { existsSync as existsSync12, lstatSync as lstatSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync12, renameSync, writeFileSync as writeFileSync4 } from "node:fs";
2996
3076
  import { randomUUID } from "node:crypto";
2997
- import { dirname as dirname7, join as join11 } from "node:path";
3077
+ import { dirname as dirname8, join as join11 } from "node:path";
2998
3078
 
2999
3079
  // packages/cli/src/symbols/discovery.mjs
3000
3080
  var import_ignore = __toESM(require_ignore(), 1);
3001
- import { existsSync as existsSync10, lstatSync, readFileSync as readFileSync10, realpathSync } from "node:fs";
3002
- import { extname as extname5, join as join9, relative as relative4, isAbsolute } from "node:path";
3081
+ import { existsSync as existsSync11, lstatSync as lstatSync2, readFileSync as readFileSync11, realpathSync as realpathSync2 } from "node:fs";
3082
+ import { extname as extname5, join as join10, relative as relative5, isAbsolute as isAbsolute2 } from "node:path";
3003
3083
 
3004
3084
  // packages/cli/src/query/chunks.mjs
3005
- import { readFileSync as readFileSync8, readdirSync as readdirSync2 } from "node:fs";
3006
- import { join as join7, relative as relative3 } from "node:path";
3085
+ import { readFileSync as readFileSync9, readdirSync as readdirSync2 } from "node:fs";
3086
+ import { join as join8, relative as relative4 } from "node:path";
3007
3087
  import { createHash as createHash3 } from "node:crypto";
3008
3088
  var MAX_CHUNK_CHARS = 1600;
3009
3089
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".dotdotgod"]);
@@ -3011,7 +3091,7 @@ function textFingerprint(text) {
3011
3091
  return createHash3("sha256").update(text).digest("hex");
3012
3092
  }
3013
3093
  function collectDocumentationMarkdown(root, exclude = ["docs/plan", "docs/archive"], documentationRoot = "docs") {
3014
- const docsRoot = join7(root, documentationRoot);
3094
+ const docsRoot = join8(root, documentationRoot);
3015
3095
  const files = [];
3016
3096
  const walk = (directory) => {
3017
3097
  let entries;
@@ -3023,8 +3103,8 @@ function collectDocumentationMarkdown(root, exclude = ["docs/plan", "docs/archiv
3023
3103
  entries.sort((a, b) => a.name.localeCompare(b.name));
3024
3104
  for (const entry of entries) {
3025
3105
  if (entry.name.startsWith(".")) continue;
3026
- const absolute = join7(directory, entry.name);
3027
- const path = relative3(root, absolute).replaceAll("\\", "/");
3106
+ const absolute = join8(directory, entry.name);
3107
+ const path = relative4(root, absolute).replaceAll("\\", "/");
3028
3108
  if (isSecretLikePathPattern(path) || exclude.some((pattern) => matchPathPattern(path, pattern) || path === pattern || path.startsWith(`${pattern}/`))) continue;
3029
3109
  if (entry.isDirectory()) {
3030
3110
  if (!SKIPPED_DIRECTORIES.has(entry.name)) walk(absolute);
@@ -3086,7 +3166,7 @@ ${piece}`;
3086
3166
  }
3087
3167
  function collectDocumentationChunks(root, exclude, documentationRoot = "docs") {
3088
3168
  return collectDocumentationMarkdown(root, exclude, documentationRoot).flatMap((path) => {
3089
- const content = readFileSync8(join7(root, path), "utf8");
3169
+ const content = readFileSync9(join8(root, path), "utf8");
3090
3170
  return chunkMarkdown(path, content);
3091
3171
  });
3092
3172
  }
@@ -3095,14 +3175,14 @@ function collectDocumentationChunks(root, exclude, documentationRoot = "docs") {
3095
3175
  init_languages();
3096
3176
  init_runtime();
3097
3177
  function inside(root, file) {
3098
- if (!lstatSync(file).isFile()) return false;
3099
- const path = relative4(realpathSync(root), realpathSync(file));
3100
- return !path.startsWith("..") && !isAbsolute(path);
3178
+ if (!lstatSync2(file).isFile()) return false;
3179
+ const path = relative5(realpathSync2(root), realpathSync2(file));
3180
+ return !path.startsWith("..") && !isAbsolute2(path);
3101
3181
  }
3102
3182
  function ignoreRules(root, directory, cache) {
3103
3183
  if (!cache.has(directory)) {
3104
- const file = join9(root, directory, ".gitignore");
3105
- cache.set(directory, (0, import_ignore.default)().add(existsSync10(file) && inside(root, file) ? readFileSync10(file, "utf8") : ""));
3184
+ const file = join10(root, directory, ".gitignore");
3185
+ cache.set(directory, (0, import_ignore.default)().add(existsSync11(file) && inside(root, file) ? readFileSync11(file, "utf8") : ""));
3106
3186
  }
3107
3187
  return cache.get(directory);
3108
3188
  }
@@ -3125,18 +3205,18 @@ function ignored(root, path, cache) {
3125
3205
  function sourceFiles(root) {
3126
3206
  const cache = /* @__PURE__ */ new Map();
3127
3207
  return collectIndexFiles(root).filter((file) => {
3128
- const path = relative4(root, file).replaceAll("\\", "/");
3208
+ const path = relative5(root, file).replaceAll("\\", "/");
3129
3209
  if (!SOURCE_EXTENSIONS.has(extname5(path)) || path.split("/").some((part) => part.startsWith(".")) || isSecretLikePathPattern(path)) return false;
3130
3210
  if (!inside(root, file) || ignored(root, path, cache)) return false;
3131
- return !/^#![^\n]*\n\/\/ Generated|^\/\/ Generated|@generated/m.test(readFileSync10(file, "utf8").slice(0, 256));
3211
+ return !/^#![^\n]*\n\/\/ Generated|^\/\/ Generated|@generated/m.test(readFileSync11(file, "utf8").slice(0, 256));
3132
3212
  }).map((file) => {
3133
- const source = readFileSync10(file, "utf8");
3134
- return { path: relative4(root, file).replaceAll("\\", "/"), hash: fingerprint(file), language: languageForPath(file), sourceLength: source.length, lineCount: source.split("\n").length };
3213
+ const source = readFileSync11(file, "utf8");
3214
+ return { path: relative5(root, file).replaceAll("\\", "/"), hash: fingerprint(file), language: languageForPath(file), sourceLength: source.length, lineCount: source.split("\n").length };
3135
3215
  });
3136
3216
  }
3137
3217
  function outlinePolicy(root) {
3138
3218
  const config = readMemoryConfig(root);
3139
- const ignores = collectIndexFiles(root).filter((file) => file.endsWith("/.gitignore") && inside(root, file)).map((file) => [relative4(root, file), fingerprint(file)]);
3219
+ const ignores = collectIndexFiles(root).filter((file) => file.endsWith("/.gitignore") && inside(root, file)).map((file) => [relative5(root, file), fingerprint(file)]);
3140
3220
  return textFingerprint(JSON.stringify({ areas: config.areas, ignores, extractor: EXTRACTOR_ID, declarations: DECLARATIONS, extensions: LANGUAGE_EXTENSIONS, parser: parserIdentity() }));
3141
3221
  }
3142
3222
 
@@ -3145,10 +3225,10 @@ init_languages();
3145
3225
  var OUTLINE_SCHEMA = 1;
3146
3226
  var outlinePath = (root) => join11(root, ".dotdotgod", "outlines.json");
3147
3227
  function assertCachePath(root) {
3148
- for (const path of [dirname7(outlinePath(root)), outlinePath(root)]) {
3228
+ for (const path of [dirname8(outlinePath(root)), outlinePath(root)]) {
3149
3229
  let stat;
3150
3230
  try {
3151
- stat = lstatSync2(path);
3231
+ stat = lstatSync3(path);
3152
3232
  } catch (error) {
3153
3233
  if (error.code !== "ENOENT") throw error;
3154
3234
  }
@@ -3158,7 +3238,7 @@ function assertCachePath(root) {
3158
3238
  function readOutlines(root) {
3159
3239
  try {
3160
3240
  assertCachePath(root);
3161
- const index = JSON.parse(readFileSync11(outlinePath(root), "utf8"));
3241
+ const index = JSON.parse(readFileSync12(outlinePath(root), "utf8"));
3162
3242
  if (index.schema !== OUTLINE_SCHEMA || index.extractor !== EXTRACTOR_ID || !Array.isArray(index.files)) return null;
3163
3243
  const paths = /* @__PURE__ */ new Set();
3164
3244
  const ids = /* @__PURE__ */ new Set();
@@ -3191,7 +3271,7 @@ function outlineStatus(root) {
3191
3271
  assertCachePath(root);
3192
3272
  const files = sourceFiles(root);
3193
3273
  const index = readOutlines(root);
3194
- if (!index) return { ok: files.length === 0 && !existsSync11(outlinePath(root)), reason: existsSync11(outlinePath(root)) ? "missing-or-invalid" : "missing", paths: files.map((file) => file.path) };
3274
+ if (!index) return { ok: files.length === 0 && !existsSync12(outlinePath(root)), reason: existsSync12(outlinePath(root)) ? "missing-or-invalid" : "missing", paths: files.map((file) => file.path) };
3195
3275
  const old = new Map(index.files.map((file) => [file.path, file]));
3196
3276
  const current = new Set(files.map((file) => file.path));
3197
3277
  const changed = files.filter((file) => old.get(file.path)?.hash !== file.hash || old.get(file.path)?.sourceLength !== file.sourceLength || old.get(file.path)?.lineCount !== file.lineCount).map((file) => file.path);
@@ -3220,7 +3300,7 @@ async function buildOutlines(root) {
3220
3300
  continue;
3221
3301
  }
3222
3302
  const { extractSymbols: extractSymbols2 } = await Promise.resolve().then(() => (init_parser(), parser_exports));
3223
- const source = readFileSync11(join11(root, file.path), "utf8");
3303
+ const source = readFileSync12(join11(root, file.path), "utf8");
3224
3304
  const extracted = await extractSymbols2(file.path, source, file.language);
3225
3305
  if (fingerprint(join11(root, file.path)) !== file.hash) throw new Error(`Source changed during outline extraction: ${file.path}`);
3226
3306
  records.push({ ...file, ...extracted });
@@ -3228,9 +3308,9 @@ async function buildOutlines(root) {
3228
3308
  }
3229
3309
  const index = { schema: OUTLINE_SCHEMA, extractor: EXTRACTOR_ID, policy: outlinePolicy(root), files: records, refresh: { parsed, reused } };
3230
3310
  const target = outlinePath(root);
3231
- mkdirSync3(dirname7(target), { recursive: true });
3311
+ mkdirSync4(dirname8(target), { recursive: true });
3232
3312
  const temporary = `${target}.tmp-${process.pid}-${randomUUID()}`;
3233
- writeFileSync3(temporary, JSON.stringify(index), { flag: "wx" });
3313
+ writeFileSync4(temporary, JSON.stringify(index), { flag: "wx" });
3234
3314
  renameSync(temporary, target);
3235
3315
  return index;
3236
3316
  }
@@ -3248,10 +3328,10 @@ ${text}`;
3248
3328
  }
3249
3329
 
3250
3330
  // packages/cli/src/symbols/graph.mjs
3251
- import { dirname as dirname8, posix } from "node:path";
3331
+ import { dirname as dirname9, posix } from "node:path";
3252
3332
  function importTarget(file, specifier, paths) {
3253
3333
  if (!specifier.startsWith(".")) return null;
3254
- const base = posix.normalize(posix.join(dirname8(file).replaceAll("\\", "/"), specifier));
3334
+ const base = posix.normalize(posix.join(dirname9(file).replaceAll("\\", "/"), specifier));
3255
3335
  if (base.startsWith("../")) return null;
3256
3336
  const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
3257
3337
  const candidates = [base, ...[".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"].map((extension) => `${stem}${extension}`), ...[".ts", ".js", ".py"].map((extension) => `${base}/index${extension}`)];
@@ -3338,14 +3418,14 @@ function writeIndex(root, index) {
3338
3418
  }
3339
3419
  function readIndex(root) {
3340
3420
  const file = cacheFile(root);
3341
- if (!existsSync12(file)) return null;
3421
+ if (!existsSync13(file)) return null;
3342
3422
  try {
3343
- const manifest = JSON.parse(readFileSync12(file, "utf8"));
3423
+ const manifest = JSON.parse(readFileSync13(file, "utf8"));
3344
3424
  const shards = manifest.graph?.shards;
3345
3425
  if (!shards) return manifest;
3346
3426
  const compact = { nodes: {}, edges: {} };
3347
- for (const [name, path] of Object.entries(shards.nodes ?? {})) compact.nodes[name] = existsSync12(join12(root, path)) ? JSON.parse(readFileSync12(join12(root, path), "utf8")) : [];
3348
- for (const [name, path] of Object.entries(shards.edges ?? {})) compact.edges[name] = existsSync12(join12(root, path)) ? JSON.parse(readFileSync12(join12(root, path), "utf8")) : [];
3427
+ for (const [name, path] of Object.entries(shards.nodes ?? {})) compact.nodes[name] = existsSync13(join12(root, path)) ? JSON.parse(readFileSync13(join12(root, path), "utf8")) : [];
3428
+ for (const [name, path] of Object.entries(shards.edges ?? {})) compact.edges[name] = existsSync13(join12(root, path)) ? JSON.parse(readFileSync13(join12(root, path), "utf8")) : [];
3349
3429
  return { ...manifest, graph: expandGraph(compact) };
3350
3430
  } catch {
3351
3431
  return null;
@@ -3401,7 +3481,7 @@ function graphSummary(index) {
3401
3481
 
3402
3482
  // packages/cli/src/validate/references.mjs
3403
3483
  import { statSync as statSync5 } from "node:fs";
3404
- import { dirname as dirname9, relative as relative5, resolve as resolve7 } from "node:path";
3484
+ import { dirname as dirname10, relative as relative6, resolve as resolve8 } from "node:path";
3405
3485
  var blank = (text) => text.replace(/[^\n\r]/g, " ");
3406
3486
  var unescape = (text) => text.replace(/\\([!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~])/g, "$1");
3407
3487
  var labelKey = (text) => unescape(text).trim().replace(/\s+/g, " ").toLowerCase();
@@ -3501,7 +3581,7 @@ function extractMemoryReferences(content) {
3501
3581
  return references;
3502
3582
  }
3503
3583
  function sharedLocalReferences(content, root, file, config) {
3504
- const source = resolveMemoryArea(relative5(root, file).split("\\").join("/"), config);
3584
+ const source = resolveMemoryArea(relative6(root, file).split("\\").join("/"), config);
3505
3585
  if (source?.scope !== "shared") return [];
3506
3586
  const errors = [];
3507
3587
  for (const item of extractMemoryReferences(content)) {
@@ -3513,14 +3593,14 @@ function sharedLocalReferences(content, root, file, config) {
3513
3593
  continue;
3514
3594
  }
3515
3595
  if (!path) continue;
3516
- const base = item.kind === "markdown-link" || /^\.\.?\//.test(path) ? dirname9(file) : root;
3517
- const target = relative5(root, resolve7(base, path)).split("\\").join("/");
3596
+ const base = item.kind === "markdown-link" || /^\.\.?\//.test(path) ? dirname10(file) : root;
3597
+ const target = relative6(root, resolve8(base, path)).split("\\").join("/");
3518
3598
  if (target === ".." || target.startsWith("../")) continue;
3519
3599
  if (item.kind === "inline-code") {
3520
3600
  const configuredDirectory = (config.areas ?? []).some((area3) => (area3.paths ?? []).some((pattern) => pattern === `${target}/**`));
3521
3601
  let directory = configuredDirectory;
3522
3602
  try {
3523
- directory ||= statSync5(resolve7(root, target)).isDirectory();
3603
+ directory ||= statSync5(resolve8(root, target)).isDirectory();
3524
3604
  } catch {
3525
3605
  }
3526
3606
  if (directory) continue;
@@ -3545,7 +3625,7 @@ function runValidate(argv) {
3545
3625
  else if (!arg.startsWith("-")) options.root = arg;
3546
3626
  else usage(`Unknown option: ${arg}`, "validate");
3547
3627
  }
3548
- const root = resolve8(options.root);
3628
+ const root = resolve9(options.root);
3549
3629
  const memoryConfig = readMemoryConfig(root);
3550
3630
  const documentationRoot = memoryConfig.documentation?.root ?? "docs";
3551
3631
  const docs = join13(root, documentationRoot);
@@ -3566,7 +3646,7 @@ Fix: ${fix}` : message, ...prompt ? { prompt } : {} });
3566
3646
  return false;
3567
3647
  };
3568
3648
  const walk = (dir) => {
3569
- if (!existsSync13(dir) || shouldSkipDir(dir)) return;
3649
+ if (!existsSync14(dir) || shouldSkipDir(dir)) return;
3570
3650
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
3571
3651
  const path = join13(dir, entry.name);
3572
3652
  if (entry.isDirectory()) {
@@ -3578,7 +3658,7 @@ Fix: ${fix}` : message, ...prompt ? { prompt } : {} });
3578
3658
  } else if (entry.isFile() && entry.name.endsWith(".md")) markdownFiles.push(path);
3579
3659
  }
3580
3660
  };
3581
- if (!existsSync13(docs)) usage(`docs directory not found: ${docs}`);
3661
+ if (!existsSync14(docs)) usage(`docs directory not found: ${docs}`);
3582
3662
  const validationPolicy = cloneValidationPolicy(memoryConfig.validation ?? DEFAULT_VALIDATION_POLICY);
3583
3663
  const maxLines = options.maxLines ?? validationPolicy.markdown.maxLines;
3584
3664
  const maxChars = options.maxChars ?? validationPolicy.markdown.maxChars;
@@ -3609,7 +3689,7 @@ README.md directory indexes, clear HTTP-status filenames such as RESPONSE_200_OK
3609
3689
  walk(docs);
3610
3690
  const dirSiblingMap = /* @__PURE__ */ new Map();
3611
3691
  for (const f of markdownFiles) {
3612
- const dir = dirname10(f);
3692
+ const dir = dirname11(f);
3613
3693
  if (!dirSiblingMap.has(dir)) dirSiblingMap.set(dir, []);
3614
3694
  dirSiblingMap.get(dir).push(basename7(f));
3615
3695
  }
@@ -3618,14 +3698,14 @@ README.md directory indexes, clear HTTP-status filenames such as RESPONSE_200_OK
3618
3698
  const docsRel = rel(docs, file);
3619
3699
  const rootRel = rel(root, file);
3620
3700
  if (docsRel && !docsRel.startsWith("..") && !isUpperSnakeMarkdown(name)) addError(file, "FILE_NAMING", `Markdown file must be UPPER_SNAKE_CASE.md or README.md: ${name}`, null, "rename the markdown file to UPPER_SNAKE_CASE.md or README.md and update any links that reference it.");
3621
- const content = readFileSync13(file, "utf8");
3701
+ const content = readFileSync14(file, "utf8");
3622
3702
  fileCache.set(file, content);
3623
3703
  const docArea = docsRel?.split("/")[0];
3624
3704
  if (name !== "README.md" && isUpperSnakeMarkdown(name) && FILENAME_QUALITY_AREAS.has(docArea)) {
3625
3705
  const fp = validationPolicy.markdown?.filename ?? {};
3626
3706
  const allow = fp.allow ?? [];
3627
3707
  if (!allow.some((p) => matchPathPattern(rootRel, p)) && (fp.warnNumberedSeries ?? true)) {
3628
- const siblings = dirSiblingMap.get(dirname10(file)) ?? [];
3708
+ const siblings = dirSiblingMap.get(dirname11(file)) ?? [];
3629
3709
  if (isNumberedSeriesFilename(name, siblings)) {
3630
3710
  const heading = extractFirstHeading(content);
3631
3711
  const suggestion = suggestFilenameFromHeading(heading);
@@ -3667,7 +3747,7 @@ ${traceabilityExample()}`, block.line);
3667
3747
  }
3668
3748
  }
3669
3749
  const byDir = /* @__PURE__ */ new Map();
3670
- for (const file of markdownFiles) byDir.set(dirname10(file), [...byDir.get(dirname10(file)) ?? [], file]);
3750
+ for (const file of markdownFiles) byDir.set(dirname11(file), [...byDir.get(dirname11(file)) ?? [], file]);
3671
3751
  for (const [dir, files] of byDir) {
3672
3752
  if (files.length > 1 && !files.some((file) => basename7(file) === "README.md")) addError(dir, "MISSING_README", "Directory with multiple markdown files must include README.md", null, "add a README.md in this directory that indexes the important markdown files and their purpose.");
3673
3753
  }
@@ -3678,18 +3758,18 @@ ${traceabilityExample()}`, block.line);
3678
3758
  }
3679
3759
  if (options.linkCheck) {
3680
3760
  for (const [file, content] of fileCache) {
3681
- const fileDir = dirname10(file);
3761
+ const fileDir = dirname11(file);
3682
3762
  for (const { href, line } of extractLinks(content)) {
3683
3763
  const hashIndex = href.indexOf("#");
3684
3764
  const pathPart = hashIndex === -1 ? href : href.slice(0, hashIndex);
3685
3765
  const anchor = hashIndex === -1 ? "" : href.slice(hashIndex + 1);
3686
- const target = pathPart ? resolve8(fileDir, pathPart) : file;
3687
- if (pathPart && !existsSync13(target)) {
3766
+ const target = pathPart ? resolve9(fileDir, pathPart) : file;
3767
+ if (pathPart && !existsSync14(target)) {
3688
3768
  addError(file, "BROKEN_LINK", `Local link target does not exist: ${pathPart}`, line, "update the link target to an existing local file, create the intended file, or remove the stale link.");
3689
3769
  continue;
3690
3770
  }
3691
3771
  if (anchor && extname6(target) === ".md") {
3692
- const targetContent = fileCache.get(target) ?? (existsSync13(target) ? readFileSync13(target, "utf8") : "");
3772
+ const targetContent = fileCache.get(target) ?? (existsSync14(target) ? readFileSync14(target, "utf8") : "");
3693
3773
  if (targetContent && !extractAnchors(targetContent).has(decodeURIComponent(anchor))) addError(file, "BROKEN_ANCHOR", `Local anchor target does not exist: ${href}`, line, "update the fragment to a heading that exists in the target markdown file, or add the missing heading.");
3694
3774
  }
3695
3775
  }
@@ -3724,19 +3804,19 @@ ${traceabilityExample()}`, block.line);
3724
3804
  if (options.includeLocalMemory) {
3725
3805
  for (const area2 of ["plan", "archive/plan", "archive/report"]) {
3726
3806
  const areaRoot = join13(docs, area2);
3727
- if (!existsSync13(areaRoot)) continue;
3807
+ if (!existsSync14(areaRoot)) continue;
3728
3808
  for (const entry of readdirSync3(areaRoot, { withFileTypes: true })) {
3729
3809
  if (!entry.isDirectory()) continue;
3730
3810
  if (!isKebabCase(entry.name)) addError(join13(areaRoot, entry.name), "SLUG_NAMING", `Archive/plan/report slug must be kebab-case: ${entry.name}`, null, "rename the task/report directory to kebab-case and update any README index links that reference it.");
3731
3811
  const readme = join13(areaRoot, entry.name, "README.md");
3732
- if (!existsSync13(readme)) addError(readme, "MISSING_README", `Expected README.md in ${documentationRoot}/${area2}/${entry.name}/`, null, "add a README.md that summarizes the task/report and links any supporting files.");
3812
+ if (!existsSync14(readme)) addError(readme, "MISSING_README", `Expected README.md in ${documentationRoot}/${area2}/${entry.name}/`, null, "add a README.md that summarizes the task/report and links any supporting files.");
3733
3813
  }
3734
3814
  }
3735
3815
  }
3736
3816
  const gitignore = join13(root, ".gitignore");
3737
- if (!existsSync13(gitignore)) addError(gitignore, "MISSING_GITIGNORE", "Expected .gitignore", null, `create .gitignore and include ${documentationRoot}/plan, ${documentationRoot}/archive, and .dotdotgod entries.`);
3817
+ if (!existsSync14(gitignore)) addError(gitignore, "MISSING_GITIGNORE", "Expected .gitignore", null, `create .gitignore and include ${documentationRoot}/plan, ${documentationRoot}/archive, and .dotdotgod entries.`);
3738
3818
  else {
3739
- const content = readFileSync13(gitignore, "utf8").split("\n").map((line) => line.trim());
3819
+ const content = readFileSync14(gitignore, "utf8").split("\n").map((line) => line.trim());
3740
3820
  for (const required of [`${documentationRoot}/plan`, `${documentationRoot}/archive`, CACHE_DIR]) if (!content.includes(required)) addError(gitignore, "MISSING_GITIGNORE_ENTRY", `Expected .gitignore entry: ${required}`, null, `add ${required} to .gitignore so local plans, archives, and cache files stay untracked.`);
3741
3821
  }
3742
3822
  if (options.json) console.log(JSON.stringify({ ok: errors.length === 0, errors, warnings }, null, 2));
@@ -3761,8 +3841,8 @@ Prompt: ${error.prompt}` : ""}`);
3761
3841
  }
3762
3842
 
3763
3843
  // packages/cli/src/commands/config.mjs
3764
- import { existsSync as existsSync14, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
3765
- import { join as join14, resolve as resolve9 } from "node:path";
3844
+ import { existsSync as existsSync15, statSync as statSync6, writeFileSync as writeFileSync5 } from "node:fs";
3845
+ import { join as join14, resolve as resolve10 } from "node:path";
3766
3846
  function parseConfigOptions(argv, usageKey = "config") {
3767
3847
  const options = { root: ".", json: false, template: null };
3768
3848
  let rootSet = false;
@@ -3780,7 +3860,7 @@ function parseConfigOptions(argv, usageKey = "config") {
3780
3860
  } else if (!arg.startsWith("-")) usage(`Unexpected argument: ${arg}`, usageKey);
3781
3861
  else usage(`Unknown option: ${arg}`, usageKey);
3782
3862
  }
3783
- options.root = resolve9(options.root);
3863
+ options.root = resolve10(options.root);
3784
3864
  return options;
3785
3865
  }
3786
3866
  function configInitError(options, code, message, path = null) {
@@ -3798,7 +3878,7 @@ function formatConfigOutput(payload) {
3798
3878
  lines.push(`- path: ${payload.path ?? "none"}`);
3799
3879
  lines.push(`- documentation root: ${payload.config.documentation.root}`);
3800
3880
  lines.push(`- memory areas: ${payload.config.areas.length}`);
3801
- lines.push(`- embedding: ${payload.embedding.profile.provider}/${payload.embedding.profile.model} (${payload.embedding.source})`);
3881
+ lines.push(`- embedding: ${payload.embedding.profile ? `${payload.embedding.profile.provider}/${payload.embedding.profile.model}` : "not configured; keyword search"} (${payload.embedding.source})`);
3802
3882
  lines.push(`- traceability required: ${(payload.config.traceability.required ?? []).join(", ") || "none"}`);
3803
3883
  lines.push(`- traceability exclude: ${(payload.config.traceability.exclude ?? []).join(", ") || "none"}`);
3804
3884
  lines.push("- traceability keys:");
@@ -3822,17 +3902,17 @@ function runConfig(argv) {
3822
3902
  const isInit = argv[0] === "init";
3823
3903
  const options = parseConfigOptions(isInit ? argv.slice(1) : argv, isInit ? "config init" : "config");
3824
3904
  if (isInit) {
3825
- if (!existsSync14(options.root)) configInitError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
3905
+ if (!existsSync15(options.root)) configInitError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
3826
3906
  try {
3827
3907
  if (!statSync6(options.root).isDirectory()) configInitError(options, "ROOT_NOT_DIRECTORY", `Project root is not a directory: ${options.root}`);
3828
3908
  } catch {
3829
3909
  configInitError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
3830
3910
  }
3831
3911
  const target = join14(options.root, "dotdotgod.config.json");
3832
- if (existsSync14(target)) configInitError(options, "CONFIG_EXISTS", "dotdotgod.config.json already exists.", target);
3912
+ if (existsSync15(target)) configInitError(options, "CONFIG_EXISTS", "dotdotgod.config.json already exists.", target);
3833
3913
  const template = resolveInitializationTemplate(options.template);
3834
3914
  if (!template.ok) configInitError(options, template.error.code, template.error.message, template.error.path ?? target);
3835
- writeFileSync4(target, template.text);
3915
+ writeFileSync5(target, template.text);
3836
3916
  const payload2 = { ok: true, command: "config init", root: options.root, path: target, created: true, template: { name: template.name, source: template.source, selectedBy: template.selectedBy } };
3837
3917
  if (options.json) console.log(JSON.stringify(payload2, null, 2));
3838
3918
  else console.log(`dotdotgod config init: created ${target} from ${template.source}:${template.name} (${template.selectedBy})`);
@@ -3846,7 +3926,7 @@ function runConfig(argv) {
3846
3926
  embedding = { source: resolved.source, path: resolved.path, profile: sanitizeEmbeddingProfile(resolved.profile) };
3847
3927
  } catch (error) {
3848
3928
  errors.push({ file: "embedding config", code: "EMBEDDING_CONFIG_INVALID", message: error instanceof Error ? error.message : String(error) });
3849
- embedding = { source: "invalid", path: null, profile: sanitizeEmbeddingProfile({ provider: "local", model: "Xenova/multilingual-e5-small" }) };
3929
+ embedding = { source: "invalid", path: null, profile: null };
3850
3930
  }
3851
3931
  const payload = { ok: errors.length === 0, command: "config", root: options.root, source: config.source ?? "default", path: config.source && config.source !== "default" ? join14(options.root, config.source) : null, embedding, config: memoryConfigSummary(config), errors };
3852
3932
  if (options.json) console.log(JSON.stringify(payload, null, 2));
@@ -3855,14 +3935,198 @@ function runConfig(argv) {
3855
3935
  }
3856
3936
 
3857
3937
  // packages/cli/src/commands/query.mjs
3858
- import { resolve as resolve10 } from "node:path";
3938
+ import { resolve as resolve11 } from "node:path";
3939
+
3940
+ // packages/cli/src/query/cache-session.mjs
3941
+ import { randomUUID as randomUUID2 } from "node:crypto";
3942
+ import { mkdirSync as mkdirSync6, openSync, closeSync, readFileSync as readFileSync16, writeFileSync as writeFileSync7, renameSync as renameSync3, rmSync as rmSync2 } from "node:fs";
3943
+ import { join as join16 } from "node:path";
3944
+ import { setTimeout as sleep } from "node:timers/promises";
3945
+
3946
+ // packages/cli/src/query/store.mjs
3947
+ import { createHash as createHash6 } from "node:crypto";
3948
+ import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
3949
+ import { join as join15 } from "node:path";
3950
+ var VECTOR_SCHEMA_VERSION = 2;
3951
+ var VECTOR_DIMENSIONS = 384;
3952
+ function profileFingerprint(identity2) {
3953
+ return createHash6("sha256").update(JSON.stringify(identity2)).digest("hex");
3954
+ }
3955
+ function isValidNormalizedVectorValues(values, dimensions = VECTOR_DIMENSIONS) {
3956
+ if (!Number.isInteger(dimensions) || dimensions < 1 || !values || values.length % dimensions !== 0) return false;
3957
+ for (let offset = 0; offset < values.length; offset += dimensions) {
3958
+ let squaredNorm = 0;
3959
+ for (let index = 0; index < dimensions; index += 1) {
3960
+ const value = values[offset + index];
3961
+ if (!Number.isFinite(value) || value < -1 || value > 1) return false;
3962
+ squaredNorm += value * value;
3963
+ }
3964
+ if (Math.abs(Math.sqrt(squaredNorm) - 1) > 0.01) return false;
3965
+ }
3966
+ return true;
3967
+ }
3968
+ function vectorCachePaths(root) {
3969
+ const directory = join15(root, ".dotdotgod", "vectors");
3970
+ return { directory, manifest: join15(directory, "manifest.json"), chunks: join15(directory, "chunks.jsonl"), embeddings: join15(directory, "embeddings.f32") };
3971
+ }
3972
+ function readVectorCache(root, identity2 = null, diagnostics = {}) {
3973
+ diagnostics.reason = "missing";
3974
+ const paths = vectorCachePaths(root);
3975
+ if (!existsSync16(paths.manifest) || !existsSync16(paths.chunks) || !existsSync16(paths.embeddings)) return null;
3976
+ try {
3977
+ const manifest = JSON.parse(readFileSync15(paths.manifest, "utf8"));
3978
+ diagnostics.reason = "schema-mismatch";
3979
+ if (manifest.schemaVersion !== VECTOR_SCHEMA_VERSION || !Number.isInteger(manifest.dimensions) || manifest.dimensions < 1) return null;
3980
+ diagnostics.reason = "profile-mismatch";
3981
+ if (identity2 && manifest.profileFingerprint !== profileFingerprint(identity2)) return null;
3982
+ diagnostics.reason = "corrupt";
3983
+ const chunks = readFileSync15(paths.chunks, "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
3984
+ const buffer = readFileSync15(paths.embeddings);
3985
+ if (buffer.byteLength !== chunks.length * manifest.dimensions * 4) return null;
3986
+ const vectors = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4);
3987
+ if (!isValidNormalizedVectorValues(vectors, manifest.dimensions)) return null;
3988
+ diagnostics.reason = "valid";
3989
+ return { manifest, chunks, vectors: new Float32Array(vectors) };
3990
+ } catch {
3991
+ diagnostics.reason = "corrupt";
3992
+ return null;
3993
+ }
3994
+ }
3995
+ function atomicWrite(path, data) {
3996
+ const temporary = `${path}.tmp-${process.pid}`;
3997
+ writeFileSync6(temporary, data);
3998
+ renameSync2(temporary, path);
3999
+ }
4000
+ function writeVectorCache(root, chunks, vectors, metadata = {}) {
4001
+ const paths = vectorCachePaths(root);
4002
+ const dimensions = metadata.dimensions;
4003
+ if (!Number.isInteger(dimensions) || dimensions < 1) throw new Error("Vector dimensions must be a positive integer.");
4004
+ mkdirSync5(paths.directory, { recursive: true });
4005
+ if (vectors.length !== chunks.length * dimensions) throw new Error("Vector count does not match chunk metadata.");
4006
+ if (!isValidNormalizedVectorValues(vectors, dimensions)) throw new Error("Vectors must contain finite normalized values.");
4007
+ const manifest = { schemaVersion: VECTOR_SCHEMA_VERSION, chunks: chunks.length, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...metadata };
4008
+ atomicWrite(paths.chunks, `${chunks.map((chunk) => JSON.stringify(chunk)).join("\n")}
4009
+ `);
4010
+ atomicWrite(paths.embeddings, Buffer.from(vectors.buffer, vectors.byteOffset, vectors.byteLength));
4011
+ atomicWrite(paths.manifest, `${JSON.stringify(manifest, null, 2)}
4012
+ `);
4013
+ return manifest;
4014
+ }
4015
+
4016
+ // packages/cli/src/query/cache-session.mjs
4017
+ async function vectorCacheSession(root, identity2, action2) {
4018
+ const directory = join16(root, ".dotdotgod", "vectors");
4019
+ mkdirSync6(directory, { recursive: true });
4020
+ const lock = join16(directory, "build.lock");
4021
+ const token = randomUUID2();
4022
+ const started = Date.now();
4023
+ let waited = false;
4024
+ while (true) {
4025
+ try {
4026
+ const fd = openSync(lock, "wx");
4027
+ try {
4028
+ writeFileSync7(fd, JSON.stringify({ pid: process.pid, token }));
4029
+ } finally {
4030
+ closeSync(fd);
4031
+ }
4032
+ break;
4033
+ } catch (error) {
4034
+ if (error.code !== "EEXIST") throw error;
4035
+ waited = true;
4036
+ if (Date.now() - started >= 3e4) throw new Error("Vector cache lock timed out; another build may be running or the lock needs inspection.");
4037
+ try {
4038
+ const owner = JSON.parse(readFileSync16(lock, "utf8"));
4039
+ if (!Number.isInteger(owner.pid) || owner.pid < 1) throw new Error("Invalid lock owner.");
4040
+ try {
4041
+ process.kill(owner.pid, 0);
4042
+ } catch (probe) {
4043
+ if (probe.code !== "ESRCH") throw probe;
4044
+ const recovery = `${lock}.recovery`;
4045
+ try {
4046
+ mkdirSync6(recovery);
4047
+ } catch (claim) {
4048
+ if (claim.code === "EEXIST") {
4049
+ await sleep(50);
4050
+ continue;
4051
+ }
4052
+ throw claim;
4053
+ }
4054
+ try {
4055
+ const current = JSON.parse(readFileSync16(lock, "utf8"));
4056
+ if (current.token === owner.token && current.pid === owner.pid) rmSync2(lock);
4057
+ } finally {
4058
+ rmSync2(recovery, { recursive: true, force: true });
4059
+ }
4060
+ continue;
4061
+ }
4062
+ } catch (probe) {
4063
+ if (probe.code === "ENOENT") continue;
4064
+ }
4065
+ await sleep(50);
4066
+ }
4067
+ }
4068
+ try {
4069
+ const path = join16(directory, "snapshots.json");
4070
+ const fingerprint2 = profileFingerprint(identity2);
4071
+ let state;
4072
+ let reason = "missing";
4073
+ try {
4074
+ state = JSON.parse(readFileSync16(path, "utf8"));
4075
+ if (state.schema !== 1) {
4076
+ reason = "schema-mismatch";
4077
+ state = null;
4078
+ } else if (state.profile !== fingerprint2) {
4079
+ reason = "profile-mismatch";
4080
+ state = null;
4081
+ } else {
4082
+ if (state.dimensions !== null && (!Number.isInteger(state.dimensions) || state.dimensions < 1)) throw new Error("Invalid cache dimensions.");
4083
+ for (const rows of [state.docs, state.code, state.pending]) {
4084
+ if (!Array.isArray(rows) || rows.some((row) => typeof row.chunk?.fingerprint !== "string" || !Array.isArray(row.vector) || row.vector.length !== state.dimensions || !isValidNormalizedVectorValues(row.vector, state.dimensions))) throw new Error("Invalid cache rows.");
4085
+ }
4086
+ reason = "valid";
4087
+ }
4088
+ } catch (error) {
4089
+ state = null;
4090
+ reason = error.code === "ENOENT" ? "missing" : "corrupt";
4091
+ }
4092
+ if (!state) {
4093
+ state = { schema: 1, profile: fingerprint2, dimensions: null, docs: [], code: [], pending: [] };
4094
+ if (reason === "missing") {
4095
+ const legacyDiagnostics = {};
4096
+ const legacy = readVectorCache(root, identity2, legacyDiagnostics);
4097
+ reason = legacyDiagnostics.reason;
4098
+ if (legacy) {
4099
+ state.dimensions = legacy.manifest.dimensions;
4100
+ legacy.chunks.forEach((chunk, i) => state[chunk.kind === "symbol" ? "code" : "docs"].push({ chunk, vector: Array.from(legacy.vectors.subarray(i * state.dimensions, (i + 1) * state.dimensions)) }));
4101
+ reason = "legacy-migrated";
4102
+ }
4103
+ }
4104
+ }
4105
+ const save = () => {
4106
+ const temporary = `${path}.tmp-${token}`;
4107
+ try {
4108
+ writeFileSync7(temporary, JSON.stringify(state));
4109
+ renameSync3(temporary, path);
4110
+ } finally {
4111
+ rmSync2(temporary, { force: true });
4112
+ }
4113
+ };
4114
+ return await action2(state, save, { reason, lockWaitMs: waited ? Date.now() - started : 0 });
4115
+ } finally {
4116
+ try {
4117
+ if (JSON.parse(readFileSync16(lock, "utf8")).token === token) rmSync2(lock);
4118
+ } catch (error) {
4119
+ if (error.code !== "ENOENT") throw error;
4120
+ }
4121
+ }
4122
+ }
3859
4123
 
3860
4124
  // packages/cli/src/query/embedding-runtime.mjs
3861
- import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync14, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
4125
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync17, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
3862
4126
  import { spawnSync as spawnSync2 } from "node:child_process";
3863
4127
  import { createRequire as createRequire2 } from "node:module";
3864
4128
  import { homedir as homedir3 } from "node:os";
3865
- import { join as join15 } from "node:path";
4129
+ import { join as join17 } from "node:path";
3866
4130
  import { pathToFileURL } from "node:url";
3867
4131
  var EMBEDDING_RUNTIME_PACKAGE = "@huggingface/transformers";
3868
4132
  var EMBEDDING_RUNTIME_VERSION = "4.2.0";
@@ -3874,12 +4138,12 @@ var EmbeddingRuntimeMissingError = class extends Error {
3874
4138
  }
3875
4139
  };
3876
4140
  function embeddingRuntimeRoot(options = {}) {
3877
- return join15(options.home ?? homedir3(), ".dotdotgod", "runtime", "embedding");
4141
+ return join17(options.home ?? homedir3(), ".dotdotgod", "runtime", "embedding");
3878
4142
  }
3879
4143
  function resolvePersistentTransformers(options = {}) {
3880
4144
  const root = embeddingRuntimeRoot(options);
3881
4145
  try {
3882
- const require3 = createRequire2(join15(root, "package.json"));
4146
+ const require3 = createRequire2(join17(root, "package.json"));
3883
4147
  return require3.resolve(EMBEDDING_RUNTIME_PACKAGE);
3884
4148
  } catch {
3885
4149
  throw new EmbeddingRuntimeMissingError();
@@ -3888,10 +4152,10 @@ function resolvePersistentTransformers(options = {}) {
3888
4152
  function embeddingRuntimeStatus(options = {}) {
3889
4153
  const root = embeddingRuntimeRoot(options);
3890
4154
  try {
3891
- const require3 = createRequire2(join15(root, "package.json"));
4155
+ const require3 = createRequire2(join17(root, "package.json"));
3892
4156
  require3.resolve(EMBEDDING_RUNTIME_PACKAGE);
3893
- const packagePath = join15(root, "node_modules", ...EMBEDDING_RUNTIME_PACKAGE.split("/"), "package.json");
3894
- const version = JSON.parse(readFileSync14(packagePath, "utf8")).version;
4157
+ const packagePath = join17(root, "node_modules", ...EMBEDDING_RUNTIME_PACKAGE.split("/"), "package.json");
4158
+ const version = JSON.parse(readFileSync17(packagePath, "utf8")).version;
3895
4159
  return { ok: true, installed: true, installAvailable: true, package: EMBEDDING_RUNTIME_PACKAGE, requiredVersion: EMBEDDING_RUNTIME_VERSION, packageVersion: version, location: root };
3896
4160
  } catch {
3897
4161
  return { ok: true, installed: false, installAvailable: true, package: EMBEDDING_RUNTIME_PACKAGE, requiredVersion: EMBEDDING_RUNTIME_VERSION, packageVersion: null, location: root };
@@ -3900,10 +4164,10 @@ function embeddingRuntimeStatus(options = {}) {
3900
4164
  function installEmbeddingRuntime(options = {}) {
3901
4165
  if (options.confirm !== true) throw new Error("Embedding runtime installation requires explicit confirmation.");
3902
4166
  const root = embeddingRuntimeRoot(options);
3903
- const lock = join15(root, ".installing");
3904
- if (existsSync15(lock)) throw new Error("Embedding runtime installation is already in progress.");
3905
- mkdirSync4(root, { recursive: true });
3906
- writeFileSync5(lock, `${process.pid}
4167
+ const lock = join17(root, ".installing");
4168
+ if (existsSync17(lock)) throw new Error("Embedding runtime installation is already in progress.");
4169
+ mkdirSync7(root, { recursive: true });
4170
+ writeFileSync8(lock, `${process.pid}
3907
4171
  `, { flag: "wx" });
3908
4172
  try {
3909
4173
  const spawn = options.spawnImpl ?? spawnSync2;
@@ -3913,7 +4177,7 @@ function installEmbeddingRuntime(options = {}) {
3913
4177
  if (!status.installed) throw new Error("Embedding runtime installation could not be verified.");
3914
4178
  return { ...status, command: "embedding install", modelDownloadPending: true };
3915
4179
  } finally {
3916
- rmSync(lock, { force: true });
4180
+ rmSync3(lock, { force: true });
3917
4181
  }
3918
4182
  }
3919
4183
  async function importTransformers(options = {}) {
@@ -4018,74 +4282,11 @@ async function createEmbedder(profile) {
4018
4282
  return withInputFormat(embedBatch, profile);
4019
4283
  }
4020
4284
  async function resolveEmbedder(root, options = {}) {
4021
- const resolved = options.profile ? { profile: options.profile, source: options.source ?? "injected", path: null } : resolveEmbeddingProfile(root, options);
4285
+ const resolved = options.profile || options.embed ? { profile: options.profile ?? DEFAULT_EMBEDDING_PROFILE, source: options.source ?? "injected", path: null } : resolveEmbeddingProfile(root, options);
4286
+ if (!resolved.profile) throw new Error("Embedding is not configured. Set embedding in project or global configuration to use vector search.");
4022
4287
  return { ...resolved, identity: embeddingProfileIdentity(resolved.profile), embed: options.embed ? withInputFormat(options.embed, resolved.profile) : await createEmbedder(resolved.profile) };
4023
4288
  }
4024
4289
 
4025
- // packages/cli/src/query/store.mjs
4026
- import { createHash as createHash6 } from "node:crypto";
4027
- import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
4028
- import { join as join16 } from "node:path";
4029
- var VECTOR_SCHEMA_VERSION = 2;
4030
- var VECTOR_DIMENSIONS = 384;
4031
- function profileFingerprint(identity2) {
4032
- return createHash6("sha256").update(JSON.stringify(identity2)).digest("hex");
4033
- }
4034
- function isValidNormalizedVectorValues(values, dimensions = VECTOR_DIMENSIONS) {
4035
- if (!Number.isInteger(dimensions) || dimensions < 1 || !values || values.length % dimensions !== 0) return false;
4036
- for (let offset = 0; offset < values.length; offset += dimensions) {
4037
- let squaredNorm = 0;
4038
- for (let index = 0; index < dimensions; index += 1) {
4039
- const value = values[offset + index];
4040
- if (!Number.isFinite(value) || value < -1 || value > 1) return false;
4041
- squaredNorm += value * value;
4042
- }
4043
- if (Math.abs(Math.sqrt(squaredNorm) - 1) > 0.01) return false;
4044
- }
4045
- return true;
4046
- }
4047
- function vectorCachePaths(root) {
4048
- const directory = join16(root, ".dotdotgod", "vectors");
4049
- return { directory, manifest: join16(directory, "manifest.json"), chunks: join16(directory, "chunks.jsonl"), embeddings: join16(directory, "embeddings.f32") };
4050
- }
4051
- function readVectorCache(root, identity2 = null) {
4052
- const paths = vectorCachePaths(root);
4053
- if (!existsSync16(paths.manifest) || !existsSync16(paths.chunks) || !existsSync16(paths.embeddings)) return null;
4054
- try {
4055
- const manifest = JSON.parse(readFileSync15(paths.manifest, "utf8"));
4056
- if (manifest.schemaVersion !== VECTOR_SCHEMA_VERSION || !Number.isInteger(manifest.dimensions) || manifest.dimensions < 1) return null;
4057
- if (identity2 && manifest.profileFingerprint !== profileFingerprint(identity2)) return null;
4058
- const chunks = readFileSync15(paths.chunks, "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
4059
- const buffer = readFileSync15(paths.embeddings);
4060
- if (buffer.byteLength !== chunks.length * manifest.dimensions * 4) return null;
4061
- const vectors = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4);
4062
- if (!isValidNormalizedVectorValues(vectors, manifest.dimensions)) return null;
4063
- return { manifest, chunks, vectors: new Float32Array(vectors) };
4064
- } catch {
4065
- return null;
4066
- }
4067
- }
4068
- function atomicWrite(path, data) {
4069
- const temporary = `${path}.tmp-${process.pid}`;
4070
- writeFileSync6(temporary, data);
4071
- renameSync2(temporary, path);
4072
- }
4073
- function writeVectorCache(root, chunks, vectors, metadata = {}) {
4074
- const paths = vectorCachePaths(root);
4075
- const dimensions = metadata.dimensions;
4076
- if (!Number.isInteger(dimensions) || dimensions < 1) throw new Error("Vector dimensions must be a positive integer.");
4077
- mkdirSync5(paths.directory, { recursive: true });
4078
- if (vectors.length !== chunks.length * dimensions) throw new Error("Vector count does not match chunk metadata.");
4079
- if (!isValidNormalizedVectorValues(vectors, dimensions)) throw new Error("Vectors must contain finite normalized values.");
4080
- const manifest = { schemaVersion: VECTOR_SCHEMA_VERSION, chunks: chunks.length, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...metadata };
4081
- atomicWrite(paths.chunks, `${chunks.map((chunk) => JSON.stringify(chunk)).join("\n")}
4082
- `);
4083
- atomicWrite(paths.embeddings, Buffer.from(vectors.buffer, vectors.byteOffset, vectors.byteLength));
4084
- atomicWrite(paths.manifest, `${JSON.stringify(manifest, null, 2)}
4085
- `);
4086
- return manifest;
4087
- }
4088
-
4089
4290
  // packages/cli/src/query/hybrid.mjs
4090
4291
  var resultKey = (chunk) => chunk.kind === "symbol" ? chunk.id : chunk.path;
4091
4292
  var terms = (text) => [...new Set(text.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean))];
@@ -4133,11 +4334,6 @@ async function projectCorpus(root, scope) {
4133
4334
  const outlines = scope === "docs" ? null : await buildOutlines(root);
4134
4335
  return { chunks: [...docs, ...outlines ? outlineChunks(outlines) : []], outlines };
4135
4336
  }
4136
- function assertCodeEmbeddingConsent(root, options, chunks) {
4137
- if (!chunks.some((chunk) => chunk.kind === "symbol")) return;
4138
- const profile = options.profile ?? resolveEmbeddingProfile(root).profile;
4139
- if (profile.provider !== "local" && options.allowCodeEmbedding !== true) throw new Error("Remote code-metadata embedding requires --allow-code-embedding; use --search keyword or --scope docs instead.");
4140
- }
4141
4337
  function attachRelationships(results, outlines) {
4142
4338
  if (!outlines) return results;
4143
4339
  const graph = addOutlineGraph({ nodes: [], edges: [] }, outlines);
@@ -4152,7 +4348,7 @@ var DEFAULT_LIMIT = 30;
4152
4348
  var MAX_LIMIT = 100;
4153
4349
  var EMBED_BATCH_SIZE = 16;
4154
4350
  function parseQueryOptions(argv) {
4155
- const options = { root: ".", query: "", limit: DEFAULT_LIMIT, json: false, scope: "all", search: "hybrid", allowCodeEmbedding: false };
4351
+ const options = { root: ".", query: "", limit: DEFAULT_LIMIT, json: false, scope: "all", search: "hybrid" };
4156
4352
  let rootSet = false;
4157
4353
  const queryParts = [];
4158
4354
  for (let i = 0; i < argv.length; i += 1) {
@@ -4164,14 +4360,13 @@ function parseQueryOptions(argv) {
4164
4360
  } else if (arg === "--search") {
4165
4361
  options.search = argv[++i];
4166
4362
  if (!["hybrid", "keyword", "vector"].includes(options.search)) usage("--search must be hybrid, keyword or vector.", "query");
4167
- } else if (arg === "--allow-code-embedding") options.allowCodeEmbedding = true;
4168
- else if (arg === "--limit") {
4363
+ } else if (arg === "--limit") {
4169
4364
  const value = Number(argv[++i]);
4170
4365
  if (!Number.isInteger(value) || value < 1 || value > MAX_LIMIT) usage(`--limit must be an integer from 1 to ${MAX_LIMIT}.`, "query");
4171
4366
  options.limit = value;
4172
4367
  } else if (arg.startsWith("-")) usage(`Unknown option: ${arg}`, "query");
4173
4368
  else if (!rootSet) {
4174
- options.root = resolve10(arg);
4369
+ options.root = resolve11(arg);
4175
4370
  rootSet = true;
4176
4371
  } else queryParts.push(arg);
4177
4372
  }
@@ -4209,38 +4404,62 @@ async function buildVectorIndex(root, embedOrOptions = {}) {
4209
4404
  const config = readMemoryConfig(root);
4210
4405
  const exclude = config.load?.documentationSummary?.exclude ?? ["docs/plan", "docs/archive"];
4211
4406
  const chunks = options.chunks ?? collectDocumentationChunks(root, exclude, config.documentation?.root ?? "docs");
4212
- const cached = readVectorCache(root, resolved.identity);
4213
- const cachedOffsets = new Map((cached?.chunks ?? []).map((chunk, index) => [chunk.fingerprint, index]));
4214
- const rows = new Array(chunks.length);
4215
- const missing = [];
4216
- let reused = 0;
4217
- chunks.forEach((chunk, index) => {
4218
- const oldIndex = cachedOffsets.get(chunk.fingerprint);
4219
- if (oldIndex === void 0 || !cached) missing.push({ chunk, index });
4220
- else {
4221
- const d = cached.manifest.dimensions;
4222
- rows[index] = Array.from(cached.vectors.subarray(oldIndex * d, (oldIndex + 1) * d));
4223
- reused += 1;
4224
- }
4225
- });
4226
- for (let offset = 0; offset < missing.length; offset += EMBED_BATCH_SIZE) {
4227
- const batch = missing.slice(offset, offset + EMBED_BATCH_SIZE);
4228
- const embedded = await resolved.embed(batch.map(({ chunk }) => chunk.passage), "document");
4229
- embedded.forEach((vector, index) => {
4230
- rows[batch[index].index] = vector;
4407
+ return vectorCacheSession(root, resolved.identity, async (state, save, diagnostics) => {
4408
+ const selectedFingerprints = new Set(chunks.map((chunk) => chunk.fingerprint));
4409
+ state.pending = state.pending.filter((row) => selectedFingerprints.has(row.chunk.fingerprint));
4410
+ const recovered = new Set(state.pending.map((row) => row.chunk.fingerprint));
4411
+ const cacheRows = [...state.docs, ...state.code, ...state.pending];
4412
+ const cached = state.dimensions ? { chunks: cacheRows.map((row) => row.chunk), vectors: Float32Array.from(cacheRows.flatMap((row) => row.vector)), manifest: { dimensions: state.dimensions } } : null;
4413
+ const cachedOffsets = new Map((cached?.chunks ?? []).map((chunk, index) => [chunk.fingerprint, index]));
4414
+ const rows = new Array(chunks.length);
4415
+ const missing = [];
4416
+ let reused = 0;
4417
+ chunks.forEach((chunk, index) => {
4418
+ const oldIndex = cachedOffsets.get(chunk.fingerprint);
4419
+ if (oldIndex === void 0 || !cached) missing.push({ chunk, index });
4420
+ else {
4421
+ const d = cached.manifest.dimensions;
4422
+ rows[index] = Array.from(cached.vectors.subarray(oldIndex * d, (oldIndex + 1) * d));
4423
+ reused += 1;
4424
+ }
4231
4425
  });
4232
- }
4233
- const dimensions = rows[0]?.length ?? cached?.manifest?.dimensions;
4234
- if (!dimensions) throw new Error("No documentation chunks were available to establish vector dimensions.");
4235
- if (rows.some((row) => !row || row.length !== dimensions)) throw new Error("Embedder returned inconsistent passage-vector dimensions.");
4236
- const vectors = new Float32Array(chunks.length * dimensions);
4237
- rows.forEach((row, index) => {
4238
- assertVector(row, dimensions, "Passage vector");
4239
- vectors.set(row, index * dimensions);
4426
+ for (let offset = 0; offset < missing.length; offset += EMBED_BATCH_SIZE) {
4427
+ const batch = missing.slice(offset, offset + EMBED_BATCH_SIZE);
4428
+ const embedded = await resolved.embed(batch.map(({ chunk }) => chunk.passage), "document");
4429
+ if (!Array.isArray(embedded) || embedded.length !== batch.length) throw new Error("Embedder returned an unexpected vector count.");
4430
+ embedded.forEach((vector, index) => {
4431
+ const dimensions2 = state.dimensions ?? vector.length;
4432
+ assertVector(vector, dimensions2, "Passage vector");
4433
+ state.dimensions = dimensions2;
4434
+ rows[batch[index].index] = vector;
4435
+ const { passage, ...chunk } = batch[index].chunk;
4436
+ state.pending.push({ chunk, vector: Array.from(vector) });
4437
+ });
4438
+ save();
4439
+ }
4440
+ const dimensions = rows[0]?.length ?? cached?.manifest?.dimensions;
4441
+ if (!dimensions && chunks.length) throw new Error("No documentation chunks were available to establish vector dimensions.");
4442
+ if (rows.some((row) => !row || row.length !== dimensions)) throw new Error("Embedder returned inconsistent passage-vector dimensions.");
4443
+ const vectors = new Float32Array(chunks.length * dimensions);
4444
+ rows.forEach((row, index) => {
4445
+ assertVector(row, dimensions, "Passage vector");
4446
+ vectors.set(row, index * dimensions);
4447
+ });
4448
+ const storedChunks = chunks.map(({ passage, ...chunk }) => chunk);
4449
+ const refreshed = options.corpora ?? (options.chunks ? [...new Set(chunks.map((chunk) => chunk.kind === "symbol" ? "code" : "docs"))] : ["docs"]);
4450
+ const counters = {};
4451
+ for (const corpus of refreshed) {
4452
+ const positions = chunks.map((chunk, index) => ({ chunk, index })).filter(({ chunk }) => (chunk.kind === "symbol" ? "code" : "docs") === corpus);
4453
+ state[corpus] = positions.map(({ index }) => ({ chunk: storedChunks[index], vector: Array.from(rows[index]) }));
4454
+ counters[corpus] = { chunks: positions.length, embedded: missing.filter(({ chunk }) => (chunk.kind === "symbol" ? "code" : "docs") === corpus).length, recovered: positions.filter(({ chunk }) => recovered.has(chunk.fingerprint)).length };
4455
+ counters[corpus].reused = positions.length - counters[corpus].embedded;
4456
+ }
4457
+ state.pending = [];
4458
+ save();
4459
+ const metadata = { provider: resolved.profile.provider, model: resolved.profile.model, dimensions, profileFingerprint: profileFingerprint(resolved.identity), excluded: exclude, embedded: missing.length, reused, diagnostics: { ...diagnostics, corpora: counters } };
4460
+ const manifest = dimensions ? writeVectorCache(root, storedChunks, vectors, metadata) : { chunks: 0, ...metadata };
4461
+ return { chunks: storedChunks, vectors, manifest, embedding: resolved };
4240
4462
  });
4241
- const storedChunks = chunks.map(({ passage, ...chunk }) => chunk);
4242
- const manifest = writeVectorCache(root, storedChunks, vectors, { provider: resolved.profile.provider, model: resolved.profile.model, dimensions, profileFingerprint: profileFingerprint(resolved.identity), excluded: exclude, embedded: missing.length, reused });
4243
- return { chunks: storedChunks, vectors, manifest, embedding: resolved };
4244
4463
  }
4245
4464
  async function queryProject(root, query, options = {}) {
4246
4465
  const scope = options.scope ?? "all";
@@ -4251,11 +4470,20 @@ async function queryProject(root, query, options = {}) {
4251
4470
  let vector = [];
4252
4471
  let index;
4253
4472
  let vectorError;
4254
- if (search !== "keyword" && corpus.chunks.length) {
4473
+ let configured = false;
4474
+ if (search !== "keyword") {
4255
4475
  try {
4256
- assertCodeEmbeddingConsent(root, options, corpus.chunks);
4257
- index = await buildVectorIndex(root, { ...options, chunks: corpus.chunks });
4258
- const [queryVector] = await index.embedding.embed([query], "query");
4476
+ configured = Boolean(options.profile || options.embed || resolveEmbeddingProfile(root, options).profile);
4477
+ } catch (error) {
4478
+ if (search === "vector") throw error;
4479
+ vectorError = error.message;
4480
+ }
4481
+ if (!configured && !vectorError && search === "vector") throw new Error("Embedding is not configured. Set embedding in project or global configuration to use vector search.");
4482
+ }
4483
+ if (search !== "keyword" && configured) {
4484
+ try {
4485
+ index = await buildVectorIndex(root, { ...options, chunks: corpus.chunks, corpora: scope === "all" ? ["docs", "code"] : [scope] });
4486
+ const [queryVector] = index.chunks.length ? await index.embedding.embed([query], "query") : [];
4259
4487
  vector = index.chunks.map((chunk, position) => ({ ...chunk, vectorScore: cosineScore(queryVector, index.vectors, position * index.manifest.dimensions, index.manifest.dimensions) })).sort((a, b) => b.vectorScore - a.vectorScore || a.id.localeCompare(b.id));
4260
4488
  } catch (error) {
4261
4489
  if (search === "vector") throw error;
@@ -4304,7 +4532,7 @@ async function runQuery(argv) {
4304
4532
  }
4305
4533
 
4306
4534
  // packages/cli/src/commands/embedding.mjs
4307
- import { resolve as resolve11 } from "node:path";
4535
+ import { resolve as resolve12 } from "node:path";
4308
4536
  function parse(argv) {
4309
4537
  const [action2 = "status", ...rest] = argv;
4310
4538
  let root = ".";
@@ -4316,12 +4544,18 @@ function parse(argv) {
4316
4544
  else if (!arg.startsWith("-") && root === ".") root = arg;
4317
4545
  else usage(`Unknown embedding option: ${arg}`, `embedding ${action2}`);
4318
4546
  }
4319
- return { action: action2, root: resolve11(root), json: json2, confirm };
4547
+ return { action: action2, root: resolve12(root), json: json2, confirm };
4320
4548
  }
4321
4549
  function runEmbedding(argv, options = {}) {
4322
4550
  const parsed = parse(argv);
4323
4551
  if (!["status", "install"].includes(parsed.action)) usage(`Unknown embedding command: ${parsed.action}`, "embedding");
4324
4552
  const resolved = resolveEmbeddingProfile(parsed.root, options);
4553
+ if (!resolved.profile) {
4554
+ if (parsed.action === "install") throw new Error("Configure an explicit local embedding profile before installing its runtime.");
4555
+ const result2 = { ok: true, command: "embedding status", provider: null, required: false, installed: false, installAvailable: false, source: "not-configured" };
4556
+ console.log(parsed.json ? JSON.stringify(result2, null, 2) : "Embedding is not configured; keyword search is used.");
4557
+ return result2;
4558
+ }
4325
4559
  if (resolved.profile.provider !== "local") {
4326
4560
  const result2 = { ok: true, command: `embedding ${parsed.action}`, provider: resolved.profile.provider, required: false, installed: false, installAvailable: false };
4327
4561
  console.log(parsed.json ? JSON.stringify(result2, null, 2) : "Local embedding runtime is not required for the configured remote provider.");
@@ -4336,11 +4570,11 @@ function runEmbedding(argv, options = {}) {
4336
4570
  }
4337
4571
 
4338
4572
  // packages/cli/src/commands/map.mjs
4339
- import { resolve as resolve13 } from "node:path";
4573
+ import { resolve as resolve14 } from "node:path";
4340
4574
 
4341
4575
  // packages/cli/src/memory/documentation-map.mjs
4342
- import { existsSync as existsSync17, readdirSync as readdirSync4, statSync as statSync7 } from "node:fs";
4343
- import { join as join17, resolve as resolve12 } from "node:path";
4576
+ import { existsSync as existsSync18, readdirSync as readdirSync4, statSync as statSync7 } from "node:fs";
4577
+ import { join as join18, resolve as resolve13 } from "node:path";
4344
4578
  var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".dotdotgod"]);
4345
4579
  function node() {
4346
4580
  return { directories: /* @__PURE__ */ new Map(), files: [] };
@@ -4403,7 +4637,7 @@ function discoverMarkdown(root, documentationRoot, exclude) {
4403
4637
  const walk = (relativeDirectory) => {
4404
4638
  let entries;
4405
4639
  try {
4406
- entries = readdirSync4(join17(root, relativeDirectory), { withFileTypes: true, encoding: "utf8" });
4640
+ entries = readdirSync4(join18(root, relativeDirectory), { withFileTypes: true, encoding: "utf8" });
4407
4641
  } catch {
4408
4642
  return;
4409
4643
  }
@@ -4420,8 +4654,8 @@ function discoverMarkdown(root, documentationRoot, exclude) {
4420
4654
  return paths.sort();
4421
4655
  }
4422
4656
  function buildDocumentationMap(projectRoot = ".", { depth = 5 } = {}) {
4423
- const root = resolve12(projectRoot);
4424
- if (!existsSync17(root)) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
4657
+ const root = resolve13(projectRoot);
4658
+ if (!existsSync18(root)) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
4425
4659
  try {
4426
4660
  if (!statSync7(root).isDirectory()) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
4427
4661
  } catch {
@@ -4469,7 +4703,7 @@ function parseMapOptions(argv) {
4469
4703
  } else if (!arg.startsWith("-")) fail(options.json, "UNEXPECTED_ARGUMENT", `Unexpected argument: ${arg}`);
4470
4704
  else fail(options.json, "UNKNOWN_OPTION", `Unknown option: ${arg}`);
4471
4705
  }
4472
- options.root = resolve13(options.root);
4706
+ options.root = resolve14(options.root);
4473
4707
  return options;
4474
4708
  }
4475
4709
  function runMap(argv) {
@@ -4481,7 +4715,7 @@ function runMap(argv) {
4481
4715
  }
4482
4716
 
4483
4717
  // packages/cli/src/reference/resolve.mjs
4484
- import { resolve as resolve16 } from "node:path";
4718
+ import { resolve as resolve17 } from "node:path";
4485
4719
 
4486
4720
  // node_modules/.pnpm/leiden-ts@0.1.0/node_modules/leiden-ts/dist/index.js
4487
4721
  var GraphValidationError = class extends Error {
@@ -5852,8 +6086,8 @@ function compareImpactItems(seeds) {
5852
6086
  }
5853
6087
 
5854
6088
  // packages/cli/src/impact/references.mjs
5855
- import { readFileSync as readFileSync16, statSync as statSync8 } from "node:fs";
5856
- import { extname as extname7, resolve as resolve14 } from "node:path";
6089
+ import { readFileSync as readFileSync18, statSync as statSync8 } from "node:fs";
6090
+ import { extname as extname7, resolve as resolve15 } from "node:path";
5857
6091
  var WARNING_LIMIT = 20;
5858
6092
  var edgeKey = (source, target, relation) => `${source}\0${target}\0${relation}`;
5859
6093
  function prepareImpactReferences(index, root, changedPaths) {
@@ -5865,10 +6099,10 @@ function prepareImpactReferences(index, root, changedPaths) {
5865
6099
  if (checks.has(path)) return checks.get(path);
5866
6100
  let result;
5867
6101
  try {
5868
- const absolute = resolve14(root, path);
6102
+ const absolute = resolve15(root, path);
5869
6103
  const stat = statSync8(absolute);
5870
6104
  result = { status: "exists" };
5871
- if (stat.isFile() && extname7(absolute) === ".md") result.anchors = extractAnchors(readFileSync16(absolute, "utf8"));
6105
+ if (stat.isFile() && extname7(absolute) === ".md") result.anchors = extractAnchors(readFileSync18(absolute, "utf8"));
5872
6106
  } catch (error) {
5873
6107
  result = { status: ["ENOENT", "ENOTDIR"].includes(error.code) ? "missing" : "unavailable" };
5874
6108
  }
@@ -5930,15 +6164,15 @@ function prepareImpactReferences(index, root, changedPaths) {
5930
6164
  }
5931
6165
 
5932
6166
  // packages/cli/src/impact/vector-profile.mjs
5933
- import { lstatSync as lstatSync3, readFileSync as readFileSync17, realpathSync as realpathSync2 } from "node:fs";
5934
- import { extname as extname8, relative as relative6, resolve as resolve15, sep } from "node:path";
6167
+ import { lstatSync as lstatSync4, readFileSync as readFileSync19, realpathSync as realpathSync3 } from "node:fs";
6168
+ import { extname as extname8, relative as relative7, resolve as resolve16, sep } from "node:path";
5935
6169
  var MAX_VECTOR_PROFILE_FILE_BYTES = 64 * 1024;
5936
6170
  var MAX_VECTOR_PROFILE_CHARS = 4e3;
5937
6171
  var MAX_VECTOR_PROFILE_METADATA_ITEMS = 20;
5938
6172
  var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".c", ".cc", ".cpp", ".css", ".go", ".h", ".hpp", ".html", ".java", ".js", ".json", ".jsx", ".md", ".mjs", ".py", ".rb", ".rs", ".sh", ".ts", ".tsx", ".txt", ".yaml", ".yml"]);
5939
6173
  var GENERATED_SEGMENTS = /* @__PURE__ */ new Set([".dotdotgod", "build", "coverage", "dist", "node_modules"]);
5940
6174
  function insideRoot(root, absolute) {
5941
- const normalizedRoot = resolve15(root);
6175
+ const normalizedRoot = resolve16(root);
5942
6176
  return absolute === normalizedRoot || absolute.startsWith(`${normalizedRoot}${sep}`);
5943
6177
  }
5944
6178
  function normalizeChangedPath(path) {
@@ -5949,17 +6183,17 @@ function normalizeChangedPath(path) {
5949
6183
  function canonicalizeChangedPath(root, path) {
5950
6184
  const normalized = normalizeChangedPath(path);
5951
6185
  if (!normalized) return null;
5952
- const rootAbsolute = resolve15(root);
5953
- const absolute = resolve15(rootAbsolute, normalized);
6186
+ const rootAbsolute = resolve16(root);
6187
+ const absolute = resolve16(rootAbsolute, normalized);
5954
6188
  if (!insideRoot(rootAbsolute, absolute)) return normalized;
5955
6189
  try {
5956
- const canonicalRoot = realpathSync2(rootAbsolute);
5957
- const canonicalAbsolute = realpathSync2(absolute);
6190
+ const canonicalRoot = realpathSync3(rootAbsolute);
6191
+ const canonicalAbsolute = realpathSync3(absolute);
5958
6192
  if (!insideRoot(canonicalRoot, canonicalAbsolute)) return normalized;
5959
- const canonical = relative6(canonicalRoot, canonicalAbsolute).replaceAll("\\", "/");
6193
+ const canonical = relative7(canonicalRoot, canonicalAbsolute).replaceAll("\\", "/");
5960
6194
  return canonical || normalized;
5961
6195
  } catch {
5962
- return relative6(rootAbsolute, absolute).replaceAll("\\", "/") || normalized;
6196
+ return relative7(rootAbsolute, absolute).replaceAll("\\", "/") || normalized;
5963
6197
  }
5964
6198
  }
5965
6199
  function unsafeProfilePath(path) {
@@ -5981,15 +6215,15 @@ function graphMetadata(graph, seedId) {
5981
6215
  function safeTextPrefix(root, path) {
5982
6216
  if (unsafeProfilePath(path) || !TEXT_EXTENSIONS.has(extname8(path).toLowerCase())) return "";
5983
6217
  try {
5984
- const absolute = resolve15(root, path);
6218
+ const absolute = resolve16(root, path);
5985
6219
  if (!insideRoot(root, absolute)) return "";
5986
- const canonicalAbsolute = realpathSync2(absolute);
5987
- if (!insideRoot(realpathSync2(root), canonicalAbsolute)) return "";
5988
- const canonicalPath = relative6(realpathSync2(root), canonicalAbsolute).replaceAll("\\", "/");
6220
+ const canonicalAbsolute = realpathSync3(absolute);
6221
+ if (!insideRoot(realpathSync3(root), canonicalAbsolute)) return "";
6222
+ const canonicalPath = relative7(realpathSync3(root), canonicalAbsolute).replaceAll("\\", "/");
5989
6223
  if (canonicalPath !== path || unsafeProfilePath(canonicalPath) || !TEXT_EXTENSIONS.has(extname8(canonicalPath).toLowerCase())) return "";
5990
- const stats = lstatSync3(canonicalAbsolute);
6224
+ const stats = lstatSync4(canonicalAbsolute);
5991
6225
  if (!stats.isFile() || stats.isSymbolicLink() || stats.size > MAX_VECTOR_PROFILE_FILE_BYTES) return "";
5992
- const buffer = readFileSync17(canonicalAbsolute);
6226
+ const buffer = readFileSync19(canonicalAbsolute);
5993
6227
  if (buffer.includes(0)) return "";
5994
6228
  const text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
5995
6229
  return text.slice(0, MAX_VECTOR_PROFILE_CHARS);
@@ -6023,6 +6257,7 @@ async function buildVectorImpactOverlay(root, index, changedPaths, options = {})
6023
6257
  const threshold = semantic.threshold ?? 0.5;
6024
6258
  const topK = semantic.topKPerFile ?? 5;
6025
6259
  try {
6260
+ if (!options.profile && !options.embed && !options.buildIndex && !resolveEmbeddingProfile(root, options).profile) return { status: "disabled", edges: [], diagnostics: { enabled: false, reason: "Embedding is not configured." } };
6026
6261
  const vectorIndex = await (options.buildIndex ?? buildVectorIndex)(root, options);
6027
6262
  const edges = [];
6028
6263
  const normalizedChangedPaths = [...new Set(changedPaths.map((path) => canonicalizeChangedPath(root, path)).filter(Boolean))];
@@ -6209,7 +6444,7 @@ function buildCompactImpactReport(impact, limits = {}) {
6209
6444
  }
6210
6445
 
6211
6446
  // packages/cli/src/reference/extract.mjs
6212
- import { basename as basename8, dirname as dirname11, extname as extname9 } from "node:path";
6447
+ import { basename as basename8, dirname as dirname12, extname as extname9 } from "node:path";
6213
6448
  var DEFAULT_REFERENCE_LIMIT = 5;
6214
6449
  function extractBracketReferences(prompt = "") {
6215
6450
  const refs = [];
@@ -6260,7 +6495,7 @@ function aliasEntriesForPath(path = "") {
6260
6495
  const parts = withoutMd.split("/").filter(Boolean);
6261
6496
  for (let i = 0; i < parts.length; i += 1) entries.push({ alias: parts.slice(i).join("/"), kind: "path-suffix" });
6262
6497
  if (base === "README.md") {
6263
- const dir = dirname11(path).replace(/\\/g, "/");
6498
+ const dir = dirname12(path).replace(/\\/g, "/");
6264
6499
  entries.push({ alias: basename8(dir), kind: "path" });
6265
6500
  }
6266
6501
  for (const alias of [path, withoutMd, base, stem]) entries.push({ alias, kind: "path" });
@@ -6390,7 +6625,7 @@ function parseReferenceOptions(argv, command) {
6390
6625
  else filtered.push(arg);
6391
6626
  }
6392
6627
  const operands = filtered.filter((arg) => !arg.startsWith("-"));
6393
- return { ...options, root: resolve16(operands[0] ?? "."), json: filtered.includes("--json"), rootArgv: operands.slice(1) };
6628
+ return { ...options, root: resolve17(operands[0] ?? "."), json: filtered.includes("--json"), rootArgv: operands.slice(1) };
6394
6629
  }
6395
6630
  function formatReferenceOutput(payload) {
6396
6631
  const refreshNote = payload.metadata.cacheRefreshed ? ", refreshed" : "";
@@ -6439,15 +6674,15 @@ function runExpand(argv) {
6439
6674
  }
6440
6675
 
6441
6676
  // packages/cli/src/commands/traceability.mjs
6442
- import { existsSync as existsSync18, readFileSync as readFileSync18, readdirSync as readdirSync5, writeFileSync as writeFileSync7 } from "node:fs";
6443
- import { join as join18, resolve as resolve17 } from "node:path";
6677
+ import { existsSync as existsSync19, readFileSync as readFileSync20, readdirSync as readdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
6678
+ import { join as join19, resolve as resolve18 } from "node:path";
6444
6679
  function collectDocsMarkdownFiles(root) {
6445
- const docs = join18(root, "docs");
6680
+ const docs = join19(root, "docs");
6446
6681
  const files = [];
6447
6682
  const walk = (dir) => {
6448
- if (!existsSync18(dir)) return;
6683
+ if (!existsSync19(dir)) return;
6449
6684
  for (const entry of readdirSync5(dir, { withFileTypes: true })) {
6450
- const path = join18(dir, entry.name);
6685
+ const path = join19(dir, entry.name);
6451
6686
  if (entry.isDirectory()) walk(path);
6452
6687
  else if (entry.isFile() && entry.name.endsWith(".md")) files.push(path);
6453
6688
  }
@@ -6472,7 +6707,7 @@ function parseTraceabilityOptions(argv) {
6472
6707
  }
6473
6708
  if (options.check && options.write) usage("Choose only one traceability links mode: --check or --write.", "traceability links");
6474
6709
  options.check = options.check || !options.write;
6475
- options.root = resolve17(options.root);
6710
+ options.root = resolve18(options.root);
6476
6711
  return options;
6477
6712
  }
6478
6713
  function runTraceability(argv) {
@@ -6483,7 +6718,7 @@ function runTraceability(argv) {
6483
6718
  const errors = [...config.errors ?? []];
6484
6719
  for (const file of files) {
6485
6720
  if (options.write && errors.length > 0) break;
6486
- const content = readFileSync18(file, "utf8");
6721
+ const content = readFileSync20(file, "utf8");
6487
6722
  for (const error of validateTraceabilityLinksRegion(content, options.root, file)) errors.push(error);
6488
6723
  const blocks = extractDotdotgodTraceabilityBlocks(content).filter((block) => !block.error);
6489
6724
  if (blocks.length === 0) continue;
@@ -6492,7 +6727,7 @@ function runTraceability(argv) {
6492
6727
  for (const error of result.errors ?? []) errors.push(error);
6493
6728
  continue;
6494
6729
  }
6495
- if (options.write && result.changed) writeFileSync7(file, result.content);
6730
+ if (options.write && result.changed) writeFileSync9(file, result.content);
6496
6731
  if (result.changed) results.push({ file: rel(options.root, file), changed: true });
6497
6732
  }
6498
6733
  const ok = errors.length === 0 && (!options.check || results.length === 0);
@@ -6513,8 +6748,8 @@ function runTraceability(argv) {
6513
6748
 
6514
6749
  // packages/cli/src/graph-view/server.mjs
6515
6750
  import { createServer } from "node:http";
6516
- import { readFileSync as readFileSync19 } from "node:fs";
6517
- import { dirname as dirname12, extname as extname10, join as join19 } from "node:path";
6751
+ import { readFileSync as readFileSync21 } from "node:fs";
6752
+ import { dirname as dirname13, extname as extname10, join as join20 } from "node:path";
6518
6753
  import { fileURLToPath as fileURLToPath2 } from "node:url";
6519
6754
 
6520
6755
  // packages/cli/src/graph-view/payload.mjs
@@ -6626,7 +6861,7 @@ function buildImpactGraphPayload(index, impact) {
6626
6861
  }
6627
6862
 
6628
6863
  // packages/cli/src/graph-view/server.mjs
6629
- var ASSET_ROOT = join19(dirname12(fileURLToPath2(import.meta.url)), "assets");
6864
+ var ASSET_ROOT = join20(dirname13(fileURLToPath2(import.meta.url)), "assets");
6630
6865
  var CONTENT_TYPES = { ".css": "text/css; charset=utf-8", ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".map": "application/json; charset=utf-8" };
6631
6866
  var VENDOR_FILES = {
6632
6867
  "/vendor/sigma.js": new URL("../../node_modules/sigma/dist/sigma.min.js", import.meta.url),
@@ -6639,7 +6874,7 @@ function json(response, status, value) {
6639
6874
  }
6640
6875
  function asset(response, file) {
6641
6876
  try {
6642
- const body = readFileSync19(file);
6877
+ const body = readFileSync21(file);
6643
6878
  response.writeHead(200, { "content-type": CONTENT_TYPES[extname10(file)] ?? "application/octet-stream", "cache-control": "no-store" });
6644
6879
  response.end(body);
6645
6880
  } catch {
@@ -6676,16 +6911,16 @@ async function startImpactGraphServer({ root, changed, host = "127.0.0.1", port
6676
6911
  }
6677
6912
  }
6678
6913
  if (VENDOR_FILES[url.pathname]) return asset(response, fileURLToPath2(VENDOR_FILES[url.pathname]));
6679
- const relative7 = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
6680
- if (!["index.html", "app.css", "app.js"].includes(relative7)) {
6914
+ const relative8 = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
6915
+ if (!["index.html", "app.css", "app.js"].includes(relative8)) {
6681
6916
  response.writeHead(404);
6682
6917
  return response.end("Not found");
6683
6918
  }
6684
- return asset(response, join19(ASSET_ROOT, relative7));
6919
+ return asset(response, join20(ASSET_ROOT, relative8));
6685
6920
  });
6686
- await new Promise((resolve18, reject) => {
6921
+ await new Promise((resolve19, reject) => {
6687
6922
  server.once("error", reject);
6688
- server.listen(port, host, resolve18);
6923
+ server.listen(port, host, resolve19);
6689
6924
  });
6690
6925
  const address = server.address();
6691
6926
  const actualPort = typeof address === "object" && address ? address.port : port;