@dotdotgod/claude-code 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/README.md +11 -1
- package/commands/dd/init.md +6 -1
- package/commands/dd/plan.md +5 -1
- package/hooks/README.md +5 -0
- package/hooks/runtime.mjs +24 -9
- package/mcp/cli.mjs +534 -299
- package/mcp/parsers/TYPESCRIPT_LICENSE +21 -0
- package/mcp/parsers/tree-sitter-tsx.wasm +0 -0
- package/mcp/parsers/tree-sitter-typescript.wasm +0 -0
- package/mcp/server.mjs +245 -86
- package/package.json +2 -2
- package/skills/doc-first-planning/SKILL.md +5 -1
- package/skills/project-initializer/SKILL.md +5 -0
- package/skills/project-initializer/references/agent-docs.md +1 -0
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-
|
|
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
|
|
580
|
-
import { dirname as
|
|
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
|
|
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(
|
|
591
|
-
|
|
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
|
|
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:
|
|
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(
|
|
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
|
|
760
|
-
import { basename, dirname, join as
|
|
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:
|
|
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 =
|
|
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 =
|
|
1754
|
-
if (
|
|
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
|
-
|
|
1763
|
-
|
|
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 =
|
|
1769
|
-
if (
|
|
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
|
-
|
|
1838
|
+
mkdirSync2(target, { recursive: true });
|
|
1778
1839
|
actions.push(action("created", target));
|
|
1779
1840
|
}
|
|
1780
1841
|
function ensureGitignoreEntry(options, entry, actions) {
|
|
1781
|
-
const target =
|
|
1782
|
-
const existed =
|
|
1783
|
-
let current = existed ?
|
|
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
|
-
|
|
1850
|
+
mkdirSync2(options.root, { recursive: true });
|
|
1790
1851
|
if (current.length > 0 && !current.endsWith("\n")) current = `${current}
|
|
1791
1852
|
`;
|
|
1792
|
-
|
|
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 &&
|
|
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.
|
|
2004
|
-
|
|
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
|
|
2037
|
-
import { resolve as
|
|
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] [--
|
|
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.
|
|
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(
|
|
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 =
|
|
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
|
|
2204
|
-
import { basename as basename7, dirname as
|
|
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
|
|
2208
|
-
import { basename as basename2, dirname as
|
|
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 =
|
|
2308
|
-
const absoluteTarget =
|
|
2309
|
-
let link =
|
|
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 (!
|
|
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
|
|
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
|
|
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 =
|
|
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 || !
|
|
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(
|
|
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(
|
|
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) =>
|
|
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 =
|
|
2767
|
+
const content = readFileSync7(file);
|
|
2688
2768
|
return createHash("sha256").update(content).digest("hex");
|
|
2689
2769
|
}
|
|
2690
2770
|
function cacheFile(root) {
|
|
2691
|
-
return
|
|
2771
|
+
return join6(root, CACHE_DIR, MANIFEST_FILE);
|
|
2692
2772
|
}
|
|
2693
2773
|
|
|
2694
2774
|
// packages/cli/src/index/cache.mjs
|
|
2695
|
-
import { existsSync as
|
|
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
|
|
2700
|
-
import { dirname as
|
|
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
|
|
2811
|
+
return join7(root, CACHE_DIR, "graph", kind, `${name}.json`);
|
|
2732
2812
|
}
|
|
2733
2813
|
function jsonSize(file) {
|
|
2734
|
-
return
|
|
2814
|
+
return existsSync8(file) ? statSync3(file).size : 0;
|
|
2735
2815
|
}
|
|
2736
2816
|
function writeJson(file, value) {
|
|
2737
|
-
|
|
2738
|
-
|
|
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
|
|
2802
|
-
import { basename as basename5, dirname as
|
|
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
|
|
2807
|
-
import { basename as basename4, extname as extname3, resolve as
|
|
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 (!
|
|
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 =
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
3002
|
-
import { extname as extname5, join as
|
|
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
|
|
3006
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
3027
|
-
const path =
|
|
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 =
|
|
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 (!
|
|
3099
|
-
const path =
|
|
3100
|
-
return !path.startsWith("..") && !
|
|
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 =
|
|
3105
|
-
cache.set(directory, (0, import_ignore.default)().add(
|
|
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 =
|
|
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(
|
|
3211
|
+
return !/^#![^\n]*\n\/\/ Generated|^\/\/ Generated|@generated/m.test(readFileSync11(file, "utf8").slice(0, 256));
|
|
3132
3212
|
}).map((file) => {
|
|
3133
|
-
const source =
|
|
3134
|
-
return { path:
|
|
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) => [
|
|
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 [
|
|
3228
|
+
for (const path of [dirname8(outlinePath(root)), outlinePath(root)]) {
|
|
3149
3229
|
let stat;
|
|
3150
3230
|
try {
|
|
3151
|
-
stat =
|
|
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(
|
|
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 && !
|
|
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 =
|
|
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
|
-
|
|
3311
|
+
mkdirSync4(dirname8(target), { recursive: true });
|
|
3232
3312
|
const temporary = `${target}.tmp-${process.pid}-${randomUUID()}`;
|
|
3233
|
-
|
|
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
|
|
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(
|
|
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 (!
|
|
3421
|
+
if (!existsSync13(file)) return null;
|
|
3342
3422
|
try {
|
|
3343
|
-
const manifest = JSON.parse(
|
|
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] =
|
|
3348
|
-
for (const [name, path] of Object.entries(shards.edges ?? {})) compact.edges[name] =
|
|
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
|
|
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(
|
|
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) ?
|
|
3517
|
-
const target =
|
|
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(
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 ?
|
|
3687
|
-
if (pathPart && !
|
|
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) ?? (
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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
|
|
3765
|
-
import { join as join14, resolve as
|
|
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 =
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
4155
|
+
const require3 = createRequire2(join17(root, "package.json"));
|
|
3892
4156
|
require3.resolve(EMBEDDING_RUNTIME_PACKAGE);
|
|
3893
|
-
const packagePath =
|
|
3894
|
-
const version = JSON.parse(
|
|
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 =
|
|
3904
|
-
if (
|
|
3905
|
-
|
|
3906
|
-
|
|
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
|
-
|
|
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"
|
|
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 === "--
|
|
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 =
|
|
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
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
const
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
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
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
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
|
-
|
|
4473
|
+
let configured = false;
|
|
4474
|
+
if (search !== "keyword") {
|
|
4255
4475
|
try {
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
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
|
|
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:
|
|
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
|
|
4573
|
+
import { resolve as resolve14 } from "node:path";
|
|
4340
4574
|
|
|
4341
4575
|
// packages/cli/src/memory/documentation-map.mjs
|
|
4342
|
-
import { existsSync as
|
|
4343
|
-
import { join as
|
|
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(
|
|
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 =
|
|
4424
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
|
5856
|
-
import { extname as extname7, resolve as
|
|
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 =
|
|
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(
|
|
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
|
|
5934
|
-
import { extname as extname8, relative as
|
|
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 =
|
|
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 =
|
|
5953
|
-
const absolute =
|
|
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 =
|
|
5957
|
-
const canonicalAbsolute =
|
|
6190
|
+
const canonicalRoot = realpathSync3(rootAbsolute);
|
|
6191
|
+
const canonicalAbsolute = realpathSync3(absolute);
|
|
5958
6192
|
if (!insideRoot(canonicalRoot, canonicalAbsolute)) return normalized;
|
|
5959
|
-
const canonical =
|
|
6193
|
+
const canonical = relative7(canonicalRoot, canonicalAbsolute).replaceAll("\\", "/");
|
|
5960
6194
|
return canonical || normalized;
|
|
5961
6195
|
} catch {
|
|
5962
|
-
return
|
|
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 =
|
|
6218
|
+
const absolute = resolve16(root, path);
|
|
5985
6219
|
if (!insideRoot(root, absolute)) return "";
|
|
5986
|
-
const canonicalAbsolute =
|
|
5987
|
-
if (!insideRoot(
|
|
5988
|
-
const canonicalPath =
|
|
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 =
|
|
6224
|
+
const stats = lstatSync4(canonicalAbsolute);
|
|
5991
6225
|
if (!stats.isFile() || stats.isSymbolicLink() || stats.size > MAX_VECTOR_PROFILE_FILE_BYTES) return "";
|
|
5992
|
-
const buffer =
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
6443
|
-
import { join as
|
|
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 =
|
|
6680
|
+
const docs = join19(root, "docs");
|
|
6446
6681
|
const files = [];
|
|
6447
6682
|
const walk = (dir) => {
|
|
6448
|
-
if (!
|
|
6683
|
+
if (!existsSync19(dir)) return;
|
|
6449
6684
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
6450
|
-
const path =
|
|
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 =
|
|
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 =
|
|
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)
|
|
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
|
|
6517
|
-
import { dirname as
|
|
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 =
|
|
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 =
|
|
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
|
|
6680
|
-
if (!["index.html", "app.css", "app.js"].includes(
|
|
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,
|
|
6919
|
+
return asset(response, join20(ASSET_ROOT, relative8));
|
|
6685
6920
|
});
|
|
6686
|
-
await new Promise((
|
|
6921
|
+
await new Promise((resolve19, reject) => {
|
|
6687
6922
|
server.once("error", reject);
|
|
6688
|
-
server.listen(port, host,
|
|
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;
|