@dotdotgod/claude-code 0.6.4 → 0.6.6
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 +337 -211
- 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";
|
|
@@ -855,6 +860,15 @@ import { homedir } from "node:os";
|
|
|
855
860
|
import { join } from "node:path";
|
|
856
861
|
var DEFAULT_EMBEDDING_PROFILE = Object.freeze({ provider: "local", model: "Xenova/multilingual-e5-small" });
|
|
857
862
|
var EMBEDDING_PROVIDERS = Object.freeze(["local", "openai-compatible", "ollama"]);
|
|
863
|
+
var DEFAULT_INPUT_FORMAT = Object.freeze({ queryPrefix: "query: ", documentPrefix: "passage: " });
|
|
864
|
+
function embeddingInputFormat(profile, label = "embedding") {
|
|
865
|
+
if (profile.inputFormat === void 0) return { ...DEFAULT_INPUT_FORMAT };
|
|
866
|
+
const format = profile.inputFormat;
|
|
867
|
+
if (!format || typeof format !== "object" || Array.isArray(format) || Object.keys(format).some((key) => !Object.hasOwn(DEFAULT_INPUT_FORMAT, key)) || Object.keys(DEFAULT_INPUT_FORMAT).some((key) => typeof format[key] !== "string")) {
|
|
868
|
+
throw new Error(`${label}.inputFormat must contain exactly queryPrefix and documentPrefix strings.`);
|
|
869
|
+
}
|
|
870
|
+
return { queryPrefix: format.queryPrefix, documentPrefix: format.documentPrefix };
|
|
871
|
+
}
|
|
858
872
|
function readJson(path, label) {
|
|
859
873
|
if (!existsSync(path)) return null;
|
|
860
874
|
try {
|
|
@@ -865,7 +879,7 @@ function readJson(path, label) {
|
|
|
865
879
|
}
|
|
866
880
|
function validateEmbeddingProfile(profile, label = "embedding") {
|
|
867
881
|
if (!profile || typeof profile !== "object" || Array.isArray(profile)) throw new Error(`${label} must be an object.`);
|
|
868
|
-
const allowed = /* @__PURE__ */ new Set(["provider", "model", "baseUrl", "apiKey", "apiKeyEnv"]);
|
|
882
|
+
const allowed = /* @__PURE__ */ new Set(["provider", "model", "baseUrl", "apiKey", "apiKeyEnv", "inputFormat"]);
|
|
869
883
|
const unknown = Object.keys(profile).find((key) => !allowed.has(key));
|
|
870
884
|
if (unknown) throw new Error(`${label}.${unknown} is not supported.`);
|
|
871
885
|
if (!EMBEDDING_PROVIDERS.includes(profile.provider)) throw new Error(`${label}.provider must be local, openai-compatible, or ollama.`);
|
|
@@ -883,15 +897,22 @@ function validateEmbeddingProfile(profile, label = "embedding") {
|
|
|
883
897
|
if (!["http:", "https:"].includes(url.protocol)) throw new Error(`${label}.baseUrl must use HTTP or HTTPS.`);
|
|
884
898
|
if (url.username || url.password) throw new Error(`${label}.baseUrl must not contain credentials.`);
|
|
885
899
|
}
|
|
900
|
+
embeddingInputFormat(profile, label);
|
|
886
901
|
return { ...profile, model: profile.model.trim(), ...profile.baseUrl ? { baseUrl: profile.baseUrl.replace(/\/$/, "") } : {} };
|
|
887
902
|
}
|
|
888
903
|
function sanitizeEmbeddingProfile(profile) {
|
|
904
|
+
if (!profile) return null;
|
|
889
905
|
const { apiKey, ...safe } = profile;
|
|
890
906
|
return { ...safe, ...apiKey ? { apiKey: "[redacted]" } : {} };
|
|
891
907
|
}
|
|
892
908
|
function embeddingProfileIdentity(profile) {
|
|
893
|
-
|
|
894
|
-
|
|
909
|
+
return {
|
|
910
|
+
provider: profile.provider,
|
|
911
|
+
model: profile.model,
|
|
912
|
+
...profile.baseUrl !== void 0 ? { baseUrl: profile.baseUrl } : {},
|
|
913
|
+
inputFormatVersion: 1,
|
|
914
|
+
inputFormat: embeddingInputFormat(profile)
|
|
915
|
+
};
|
|
895
916
|
}
|
|
896
917
|
function resolveEmbeddingProfile(root = ".", options = {}) {
|
|
897
918
|
const home = options.home ?? homedir();
|
|
@@ -901,7 +922,7 @@ function resolveEmbeddingProfile(root = ".", options = {}) {
|
|
|
901
922
|
if (projectConfig && Object.hasOwn(projectConfig, "embedding")) return { profile: validateEmbeddingProfile(projectConfig.embedding, "project embedding"), source: "project", path: projectPath };
|
|
902
923
|
const globalConfig = readJson(globalPath, "global config");
|
|
903
924
|
if (globalConfig && Object.hasOwn(globalConfig, "embedding")) return { profile: validateEmbeddingProfile(globalConfig.embedding, "global embedding"), source: "global", path: globalPath };
|
|
904
|
-
return { profile:
|
|
925
|
+
return { profile: null, source: "not-configured", path: null };
|
|
905
926
|
}
|
|
906
927
|
|
|
907
928
|
// packages/cli/src/memory/config.mjs
|
|
@@ -1687,6 +1708,60 @@ function resolveInitializationTemplate(explicitName = null, home = dotdotgodHome
|
|
|
1687
1708
|
` };
|
|
1688
1709
|
}
|
|
1689
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
|
+
|
|
1690
1765
|
// packages/cli/src/init.mjs
|
|
1691
1766
|
function action(status, path, extra = {}) {
|
|
1692
1767
|
return { status, path, ...extra };
|
|
@@ -1702,6 +1777,7 @@ function parseInitOptions(argv) {
|
|
|
1702
1777
|
if (arg === "--json") options.json = true;
|
|
1703
1778
|
else if (arg === "--dotdot-setting") options.dotdotSetting = true;
|
|
1704
1779
|
else if (arg === "--dry-run") options.dryRun = true;
|
|
1780
|
+
else if (arg === "--claude-plans") options.claudePlans = true;
|
|
1705
1781
|
else if (arg === "--project-name" || arg === "--template" || arg === "--documentation-root") {
|
|
1706
1782
|
const value = argv[i + 1];
|
|
1707
1783
|
if (!value || value.startsWith("-")) throw new Error(`${arg} requires a value`);
|
|
@@ -1722,7 +1798,7 @@ function parseInitOptions(argv) {
|
|
|
1722
1798
|
}
|
|
1723
1799
|
if (!options.root) throw new Error("Missing required argument: <root>");
|
|
1724
1800
|
if (!isValidDocumentationRoot(options.documentationRoot)) throw new Error("Invalid --documentation-root: expected a safe repository-relative directory");
|
|
1725
|
-
options.root =
|
|
1801
|
+
options.root = resolve3(options.root);
|
|
1726
1802
|
if (!options.projectName) options.projectName = basename(options.root);
|
|
1727
1803
|
return options;
|
|
1728
1804
|
}
|
|
@@ -1735,8 +1811,8 @@ function initError(options, code, message) {
|
|
|
1735
1811
|
process.exit(2);
|
|
1736
1812
|
}
|
|
1737
1813
|
function writeInitFile(options, relativePath, content, actions) {
|
|
1738
|
-
const target =
|
|
1739
|
-
if (
|
|
1814
|
+
const target = join5(options.root, relativePath);
|
|
1815
|
+
if (existsSync5(target)) {
|
|
1740
1816
|
actions.push(action("skipped", target));
|
|
1741
1817
|
return;
|
|
1742
1818
|
}
|
|
@@ -1744,14 +1820,14 @@ function writeInitFile(options, relativePath, content, actions) {
|
|
|
1744
1820
|
actions.push(action("would_create", target));
|
|
1745
1821
|
return;
|
|
1746
1822
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1823
|
+
mkdirSync2(dirname2(target), { recursive: true });
|
|
1824
|
+
writeFileSync2(target, content.endsWith("\n") ? content : `${content}
|
|
1749
1825
|
`);
|
|
1750
1826
|
actions.push(action("created", target));
|
|
1751
1827
|
}
|
|
1752
1828
|
function ensureInitDirectory(options, relativePath, actions) {
|
|
1753
|
-
const target =
|
|
1754
|
-
if (
|
|
1829
|
+
const target = join5(options.root, relativePath);
|
|
1830
|
+
if (existsSync5(target)) {
|
|
1755
1831
|
actions.push(action("skipped", target));
|
|
1756
1832
|
return;
|
|
1757
1833
|
}
|
|
@@ -1759,22 +1835,22 @@ function ensureInitDirectory(options, relativePath, actions) {
|
|
|
1759
1835
|
actions.push(action("would_create", target));
|
|
1760
1836
|
return;
|
|
1761
1837
|
}
|
|
1762
|
-
|
|
1838
|
+
mkdirSync2(target, { recursive: true });
|
|
1763
1839
|
actions.push(action("created", target));
|
|
1764
1840
|
}
|
|
1765
1841
|
function ensureGitignoreEntry(options, entry, actions) {
|
|
1766
|
-
const target =
|
|
1767
|
-
const existed =
|
|
1768
|
-
let current = existed ?
|
|
1842
|
+
const target = join5(options.root, ".gitignore");
|
|
1843
|
+
const existed = existsSync5(target);
|
|
1844
|
+
let current = existed ? readFileSync5(target, "utf8") : "";
|
|
1769
1845
|
if (current.split(/\r?\n/).includes(entry)) return;
|
|
1770
1846
|
if (options.dryRun) {
|
|
1771
1847
|
actions.push(action(existed ? "would_update" : "would_create", target, { add: entry }));
|
|
1772
1848
|
return;
|
|
1773
1849
|
}
|
|
1774
|
-
|
|
1850
|
+
mkdirSync2(options.root, { recursive: true });
|
|
1775
1851
|
if (current.length > 0 && !current.endsWith("\n")) current = `${current}
|
|
1776
1852
|
`;
|
|
1777
|
-
|
|
1853
|
+
writeFileSync2(target, `${current}${entry}
|
|
1778
1854
|
`);
|
|
1779
1855
|
actions.push(action(existed ? "updated" : "created", target, { add: entry }));
|
|
1780
1856
|
}
|
|
@@ -1978,15 +2054,26 @@ function runInit(argv, usage2) {
|
|
|
1978
2054
|
usage2(error instanceof Error ? error.message : String(error), "init");
|
|
1979
2055
|
return;
|
|
1980
2056
|
}
|
|
1981
|
-
if (!options.dryRun &&
|
|
2057
|
+
if (!options.dryRun && existsSync5(options.root)) {
|
|
1982
2058
|
try {
|
|
1983
2059
|
if (!statSync(options.root).isDirectory()) initError(options, "ROOT_NOT_DIRECTORY", `Project root is not a directory: ${options.root}`);
|
|
1984
2060
|
} catch {
|
|
1985
2061
|
initError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
|
|
1986
2062
|
}
|
|
1987
2063
|
}
|
|
1988
|
-
if (!options.
|
|
1989
|
-
|
|
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"));
|
|
1990
2077
|
if (configExists && options.documentationRootExplicit) {
|
|
1991
2078
|
const existingRoot = readMemoryConfig(options.root).documentation.root;
|
|
1992
2079
|
if (existingRoot !== options.documentationRoot) initError(options, "DOCUMENTATION_ROOT_CONFLICT", `Existing config uses documentation root ${existingRoot}; initialization does not migrate documentation.`);
|
|
@@ -2009,6 +2096,13 @@ function runInit(argv, usage2) {
|
|
|
2009
2096
|
}
|
|
2010
2097
|
writeInitFile(options, "dotdotgod.config.json", template?.text ?? "", actions);
|
|
2011
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
|
+
}
|
|
2012
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 };
|
|
2013
2107
|
if (options.json) console.log(JSON.stringify(payload, null, 2));
|
|
2014
2108
|
else {
|
|
@@ -2018,8 +2112,8 @@ function runInit(argv, usage2) {
|
|
|
2018
2112
|
}
|
|
2019
2113
|
|
|
2020
2114
|
// packages/cli/src/cli/usage.mjs
|
|
2021
|
-
import { readFileSync as
|
|
2022
|
-
import { resolve as
|
|
2115
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
2116
|
+
import { resolve as resolve4 } from "node:path";
|
|
2023
2117
|
var HELP_TOKENS = /* @__PURE__ */ new Set(["help", "--help", "-h"]);
|
|
2024
2118
|
var VERSION_TOKENS = /* @__PURE__ */ new Set(["version", "--version", "-v"]);
|
|
2025
2119
|
function commandUsage(command = "root") {
|
|
@@ -2029,9 +2123,10 @@ function commandUsage(command = "root") {
|
|
|
2029
2123
|
dotdotgod validate <root> [--include-local-memory] [--check-index] [--max-lines n] [--max-chars n] [--no-link-check] [--json]`;
|
|
2030
2124
|
case "init":
|
|
2031
2125
|
return `Usage:
|
|
2032
|
-
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]
|
|
2033
2127
|
|
|
2034
|
-
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.`;
|
|
2035
2130
|
case "index":
|
|
2036
2131
|
return `Usage:
|
|
2037
2132
|
dotdotgod index <root> [--json]`;
|
|
@@ -2051,9 +2146,9 @@ Create dotdotgod.config.json from a bundled or user template. Without --template
|
|
|
2051
2146
|
dotdotgod status <root> [--json]`;
|
|
2052
2147
|
case "query":
|
|
2053
2148
|
return `Usage:
|
|
2054
|
-
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]
|
|
2055
2150
|
|
|
2056
|
-
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.`;
|
|
2057
2152
|
case "embedding":
|
|
2058
2153
|
return `Usage:
|
|
2059
2154
|
dotdotgod embedding status [<root>] [--json]
|
|
@@ -2158,7 +2253,7 @@ function isVersionToken(value) {
|
|
|
2158
2253
|
function printVersion() {
|
|
2159
2254
|
let version = "unknown";
|
|
2160
2255
|
try {
|
|
2161
|
-
const data = JSON.parse(
|
|
2256
|
+
const data = JSON.parse(readFileSync6(new URL("../../package.json", import.meta.url), "utf8"));
|
|
2162
2257
|
if (typeof data.version === "string") version = data.version;
|
|
2163
2258
|
} catch {
|
|
2164
2259
|
}
|
|
@@ -2180,17 +2275,17 @@ function parseCommon(argv) {
|
|
|
2180
2275
|
if (arg === "--json") options.json = true;
|
|
2181
2276
|
else if (!arg.startsWith("-") && options.root === ".") options.root = arg;
|
|
2182
2277
|
}
|
|
2183
|
-
options.root =
|
|
2278
|
+
options.root = resolve4(options.root);
|
|
2184
2279
|
return options;
|
|
2185
2280
|
}
|
|
2186
2281
|
|
|
2187
2282
|
// packages/cli/src/validate/run.mjs
|
|
2188
|
-
import { existsSync as
|
|
2189
|
-
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";
|
|
2190
2285
|
|
|
2191
2286
|
// packages/cli/src/docs/traceability.mjs
|
|
2192
|
-
import { existsSync as
|
|
2193
|
-
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";
|
|
2194
2289
|
function isSecretIndexPath(path) {
|
|
2195
2290
|
return /(^|\/)(\.env|\.npmrc|\.pypirc|id_rsa|id_dsa|id_ed25519|credentials|secrets?)(\.|\/|$)/i.test(path);
|
|
2196
2291
|
}
|
|
@@ -2289,9 +2384,9 @@ function stripTraceabilityLinksRegion(content) {
|
|
|
2289
2384
|
return stripped.replace(/(?:^[ \t]*\r?\n)*^##[ \t]+Traceability[ \t]*\r?(?:\n(?:[ \t]*\r?\n)*|$)/gm, "");
|
|
2290
2385
|
}
|
|
2291
2386
|
function markdownLinkPath(root, file, target) {
|
|
2292
|
-
const fromDir =
|
|
2293
|
-
const absoluteTarget =
|
|
2294
|
-
let link =
|
|
2387
|
+
const fromDir = dirname3(file);
|
|
2388
|
+
const absoluteTarget = resolve5(root, target);
|
|
2389
|
+
let link = relative3(fromDir, absoluteTarget).split("\\").join("/");
|
|
2295
2390
|
if (!link.startsWith(".") && !link.startsWith("/")) link = link || basename2(target);
|
|
2296
2391
|
return link;
|
|
2297
2392
|
}
|
|
@@ -2390,7 +2485,7 @@ function validateTraceabilityPathArray(data, field2, add, root, config, { requir
|
|
|
2390
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.`);
|
|
2391
2486
|
continue;
|
|
2392
2487
|
}
|
|
2393
|
-
if (!
|
|
2488
|
+
if (!existsSync6(resolve5(root, value))) add("TRACEABILITY_MISSING_TARGET", displayField, `target does not exist: ${value}.`);
|
|
2394
2489
|
}
|
|
2395
2490
|
}
|
|
2396
2491
|
function validateTraceabilityCommandArray(data, field2, add, { required = true, displayField = field2 } = {}) {
|
|
@@ -2458,9 +2553,9 @@ function validateTraceabilityBlock(data, root, file, line = null, config = defau
|
|
|
2458
2553
|
|
|
2459
2554
|
// packages/cli/src/index/files.mjs
|
|
2460
2555
|
import { createHash } from "node:crypto";
|
|
2461
|
-
import { existsSync as
|
|
2556
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync, statSync as statSync2 } from "node:fs";
|
|
2462
2557
|
import { spawnSync } from "node:child_process";
|
|
2463
|
-
import { basename as basename3, extname as extname2, join as
|
|
2558
|
+
import { basename as basename3, extname as extname2, join as join6 } from "node:path";
|
|
2464
2559
|
|
|
2465
2560
|
// packages/cli/src/index/constants.mjs
|
|
2466
2561
|
var CACHE_VERSION = 15;
|
|
@@ -2621,7 +2716,7 @@ function walkIndexCandidates(root, config = readMemoryConfig(root)) {
|
|
|
2621
2716
|
const files = [];
|
|
2622
2717
|
const walk = (dir) => {
|
|
2623
2718
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
2624
|
-
const path =
|
|
2719
|
+
const path = join6(dir, entry.name);
|
|
2625
2720
|
const pathRel = rel(root, path);
|
|
2626
2721
|
if (entry.isDirectory()) {
|
|
2627
2722
|
const area2 = resolveMemoryArea(pathRel, config);
|
|
@@ -2635,7 +2730,7 @@ function walkIndexCandidates(root, config = readMemoryConfig(root)) {
|
|
|
2635
2730
|
function addConfiguredLocalMemoryCandidates(root, candidates, config, limit = 5e3) {
|
|
2636
2731
|
let added = 0;
|
|
2637
2732
|
const addPath = (path) => {
|
|
2638
|
-
if (added >= limit || !
|
|
2733
|
+
if (added >= limit || !existsSync7(path)) return;
|
|
2639
2734
|
let stat;
|
|
2640
2735
|
try {
|
|
2641
2736
|
stat = statSync2(path);
|
|
@@ -2651,7 +2746,7 @@ function addConfiguredLocalMemoryCandidates(root, candidates, config, limit = 5e
|
|
|
2651
2746
|
if (!stat.isDirectory() || isExcludedIndexDir(pathRel)) return;
|
|
2652
2747
|
for (const entry of readdirSync(path, { withFileTypes: true })) {
|
|
2653
2748
|
if (added >= limit) return;
|
|
2654
|
-
addPath(
|
|
2749
|
+
addPath(join6(path, entry.name));
|
|
2655
2750
|
}
|
|
2656
2751
|
};
|
|
2657
2752
|
for (const area2 of config.areas ?? []) {
|
|
@@ -2659,30 +2754,30 @@ function addConfiguredLocalMemoryCandidates(root, candidates, config, limit = 5e
|
|
|
2659
2754
|
for (const pattern of area2.paths ?? []) {
|
|
2660
2755
|
if (pattern.startsWith("**/")) continue;
|
|
2661
2756
|
const base = pattern.endsWith("/**") ? pattern.slice(0, -3) : pattern;
|
|
2662
|
-
addPath(
|
|
2757
|
+
addPath(join6(root, base));
|
|
2663
2758
|
}
|
|
2664
2759
|
}
|
|
2665
2760
|
}
|
|
2666
2761
|
function collectIndexFiles(root, config = readMemoryConfig(root)) {
|
|
2667
2762
|
const candidates = new Set(gitIndexCandidates(root) ?? walkIndexCandidates(root, config));
|
|
2668
2763
|
addConfiguredLocalMemoryCandidates(root, candidates, config);
|
|
2669
|
-
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();
|
|
2670
2765
|
}
|
|
2671
2766
|
function fingerprint(file) {
|
|
2672
|
-
const content =
|
|
2767
|
+
const content = readFileSync7(file);
|
|
2673
2768
|
return createHash("sha256").update(content).digest("hex");
|
|
2674
2769
|
}
|
|
2675
2770
|
function cacheFile(root) {
|
|
2676
|
-
return
|
|
2771
|
+
return join6(root, CACHE_DIR, MANIFEST_FILE);
|
|
2677
2772
|
}
|
|
2678
2773
|
|
|
2679
2774
|
// packages/cli/src/index/cache.mjs
|
|
2680
|
-
import { existsSync as
|
|
2775
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
|
|
2681
2776
|
import { basename as basename6, join as join12 } from "node:path";
|
|
2682
2777
|
|
|
2683
2778
|
// packages/cli/src/graph/store.mjs
|
|
2684
|
-
import { existsSync as
|
|
2685
|
-
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";
|
|
2686
2781
|
var GRAPH_NODE_SHARDS = ["docs", "packages", "source"];
|
|
2687
2782
|
var GRAPH_EDGE_SHARDS = ["docs-links", "packages", "other"];
|
|
2688
2783
|
function graphNodeShard(node2) {
|
|
@@ -2713,14 +2808,14 @@ function expandEdge(row) {
|
|
|
2713
2808
|
return { source, target, relation, ...data };
|
|
2714
2809
|
}
|
|
2715
2810
|
function shardFile(root, kind, name) {
|
|
2716
|
-
return
|
|
2811
|
+
return join7(root, CACHE_DIR, "graph", kind, `${name}.json`);
|
|
2717
2812
|
}
|
|
2718
2813
|
function jsonSize(file) {
|
|
2719
|
-
return
|
|
2814
|
+
return existsSync8(file) ? statSync3(file).size : 0;
|
|
2720
2815
|
}
|
|
2721
2816
|
function writeJson(file, value) {
|
|
2722
|
-
|
|
2723
|
-
|
|
2817
|
+
mkdirSync3(dirname4(file), { recursive: true });
|
|
2818
|
+
writeFileSync3(file, `${JSON.stringify(value)}
|
|
2724
2819
|
`);
|
|
2725
2820
|
}
|
|
2726
2821
|
function compactGraph(graph) {
|
|
@@ -2783,13 +2878,13 @@ function addEdge(graph, source, target, relation, data = {}) {
|
|
|
2783
2878
|
}
|
|
2784
2879
|
|
|
2785
2880
|
// packages/cli/src/graph/extract.mjs
|
|
2786
|
-
import { readFileSync as
|
|
2787
|
-
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";
|
|
2788
2883
|
|
|
2789
2884
|
// packages/cli/src/graph/metadata.mjs
|
|
2790
2885
|
import { createHash as createHash2 } from "node:crypto";
|
|
2791
|
-
import { existsSync as
|
|
2792
|
-
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";
|
|
2793
2888
|
function isReadmeIndexPath(path = "") {
|
|
2794
2889
|
return basename4(path) === "README.md";
|
|
2795
2890
|
}
|
|
@@ -2848,7 +2943,7 @@ function addReferenceEvidence(graph, sourceId, reference) {
|
|
|
2848
2943
|
function addTraceabilityTarget(graph, sourceId, root, relation, targetPath, data = {}, config = defaultMemoryConfig()) {
|
|
2849
2944
|
if (!isLocalRelativeTraceabilityPath(targetPath)) return;
|
|
2850
2945
|
addReferenceEvidence(graph, sourceId, { targetPath, href: targetPath, relation, data: { confidence: "CURATED_TRACEABILITY", ...data } });
|
|
2851
|
-
if (!
|
|
2946
|
+
if (!existsSync9(resolve6(root, targetPath))) return;
|
|
2852
2947
|
const targetId = `file:${targetPath}`;
|
|
2853
2948
|
addNode(graph, targetId, "file", fileNodeMetadata(targetPath, null, config));
|
|
2854
2949
|
addEdge(graph, sourceId, targetId, relation, { confidence: "CURATED_TRACEABILITY", ...data });
|
|
@@ -2900,7 +2995,7 @@ function headingId(path, title) {
|
|
|
2900
2995
|
// packages/cli/src/graph/extract.mjs
|
|
2901
2996
|
function extractMarkdownGraph(root, file, graph, config = defaultMemoryConfig()) {
|
|
2902
2997
|
const path = rel(root, file);
|
|
2903
|
-
const content =
|
|
2998
|
+
const content = readFileSync8(file, "utf8");
|
|
2904
2999
|
const fileId = `file:${path}`;
|
|
2905
3000
|
const headingRe = /^(#{1,6})\s+(.+)$/gm;
|
|
2906
3001
|
let match;
|
|
@@ -2914,7 +3009,7 @@ function extractMarkdownGraph(root, file, graph, config = defaultMemoryConfig())
|
|
|
2914
3009
|
const hashIndex = href.indexOf("#");
|
|
2915
3010
|
const pathPart = hashIndex < 0 ? href : href.slice(0, hashIndex);
|
|
2916
3011
|
const anchor = hashIndex < 0 ? "" : href.slice(hashIndex + 1);
|
|
2917
|
-
const targetPath = pathPart ? rel(root,
|
|
3012
|
+
const targetPath = pathPart ? rel(root, resolve7(dirname5(file), pathPart)) : path;
|
|
2918
3013
|
const data = { line, confidence: "EXTRACTED" };
|
|
2919
3014
|
addReferenceEvidence(graph, fileId, { targetPath, href, anchor, relation: "links_to", data });
|
|
2920
3015
|
if (pathPart && isReadmeIndexPath(path)) addReferenceEvidence(graph, fileId, { targetPath, href, anchor, relation: "routes_to", data: { line, confidence: "CURATED_INDEX", sourceRole: "readme-index" } });
|
|
@@ -2931,7 +3026,7 @@ function extractPackageGraph(root, file, graph) {
|
|
|
2931
3026
|
const fileId = `file:${path}`;
|
|
2932
3027
|
let pkg;
|
|
2933
3028
|
try {
|
|
2934
|
-
pkg = JSON.parse(
|
|
3029
|
+
pkg = JSON.parse(readFileSync8(file, "utf8"));
|
|
2935
3030
|
} catch {
|
|
2936
3031
|
return;
|
|
2937
3032
|
}
|
|
@@ -2977,18 +3072,18 @@ function buildGraph(root, files, config = defaultMemoryConfig()) {
|
|
|
2977
3072
|
}
|
|
2978
3073
|
|
|
2979
3074
|
// packages/cli/src/symbols/store.mjs
|
|
2980
|
-
import { existsSync as
|
|
3075
|
+
import { existsSync as existsSync12, lstatSync as lstatSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync12, renameSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2981
3076
|
import { randomUUID } from "node:crypto";
|
|
2982
|
-
import { dirname as
|
|
3077
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
2983
3078
|
|
|
2984
3079
|
// packages/cli/src/symbols/discovery.mjs
|
|
2985
3080
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
2986
|
-
import { existsSync as
|
|
2987
|
-
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";
|
|
2988
3083
|
|
|
2989
3084
|
// packages/cli/src/query/chunks.mjs
|
|
2990
|
-
import { readFileSync as
|
|
2991
|
-
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";
|
|
2992
3087
|
import { createHash as createHash3 } from "node:crypto";
|
|
2993
3088
|
var MAX_CHUNK_CHARS = 1600;
|
|
2994
3089
|
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".dotdotgod"]);
|
|
@@ -2996,7 +3091,7 @@ function textFingerprint(text) {
|
|
|
2996
3091
|
return createHash3("sha256").update(text).digest("hex");
|
|
2997
3092
|
}
|
|
2998
3093
|
function collectDocumentationMarkdown(root, exclude = ["docs/plan", "docs/archive"], documentationRoot = "docs") {
|
|
2999
|
-
const docsRoot =
|
|
3094
|
+
const docsRoot = join8(root, documentationRoot);
|
|
3000
3095
|
const files = [];
|
|
3001
3096
|
const walk = (directory) => {
|
|
3002
3097
|
let entries;
|
|
@@ -3008,8 +3103,8 @@ function collectDocumentationMarkdown(root, exclude = ["docs/plan", "docs/archiv
|
|
|
3008
3103
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
3009
3104
|
for (const entry of entries) {
|
|
3010
3105
|
if (entry.name.startsWith(".")) continue;
|
|
3011
|
-
const absolute =
|
|
3012
|
-
const path =
|
|
3106
|
+
const absolute = join8(directory, entry.name);
|
|
3107
|
+
const path = relative4(root, absolute).replaceAll("\\", "/");
|
|
3013
3108
|
if (isSecretLikePathPattern(path) || exclude.some((pattern) => matchPathPattern(path, pattern) || path === pattern || path.startsWith(`${pattern}/`))) continue;
|
|
3014
3109
|
if (entry.isDirectory()) {
|
|
3015
3110
|
if (!SKIPPED_DIRECTORIES.has(entry.name)) walk(absolute);
|
|
@@ -3071,7 +3166,7 @@ ${piece}`;
|
|
|
3071
3166
|
}
|
|
3072
3167
|
function collectDocumentationChunks(root, exclude, documentationRoot = "docs") {
|
|
3073
3168
|
return collectDocumentationMarkdown(root, exclude, documentationRoot).flatMap((path) => {
|
|
3074
|
-
const content =
|
|
3169
|
+
const content = readFileSync9(join8(root, path), "utf8");
|
|
3075
3170
|
return chunkMarkdown(path, content);
|
|
3076
3171
|
});
|
|
3077
3172
|
}
|
|
@@ -3080,14 +3175,14 @@ function collectDocumentationChunks(root, exclude, documentationRoot = "docs") {
|
|
|
3080
3175
|
init_languages();
|
|
3081
3176
|
init_runtime();
|
|
3082
3177
|
function inside(root, file) {
|
|
3083
|
-
if (!
|
|
3084
|
-
const path =
|
|
3085
|
-
return !path.startsWith("..") && !
|
|
3178
|
+
if (!lstatSync2(file).isFile()) return false;
|
|
3179
|
+
const path = relative5(realpathSync2(root), realpathSync2(file));
|
|
3180
|
+
return !path.startsWith("..") && !isAbsolute2(path);
|
|
3086
3181
|
}
|
|
3087
3182
|
function ignoreRules(root, directory, cache) {
|
|
3088
3183
|
if (!cache.has(directory)) {
|
|
3089
|
-
const file =
|
|
3090
|
-
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") : ""));
|
|
3091
3186
|
}
|
|
3092
3187
|
return cache.get(directory);
|
|
3093
3188
|
}
|
|
@@ -3110,18 +3205,18 @@ function ignored(root, path, cache) {
|
|
|
3110
3205
|
function sourceFiles(root) {
|
|
3111
3206
|
const cache = /* @__PURE__ */ new Map();
|
|
3112
3207
|
return collectIndexFiles(root).filter((file) => {
|
|
3113
|
-
const path =
|
|
3208
|
+
const path = relative5(root, file).replaceAll("\\", "/");
|
|
3114
3209
|
if (!SOURCE_EXTENSIONS.has(extname5(path)) || path.split("/").some((part) => part.startsWith(".")) || isSecretLikePathPattern(path)) return false;
|
|
3115
3210
|
if (!inside(root, file) || ignored(root, path, cache)) return false;
|
|
3116
|
-
return !/^#![^\n]*\n\/\/ Generated|^\/\/ Generated|@generated/m.test(
|
|
3211
|
+
return !/^#![^\n]*\n\/\/ Generated|^\/\/ Generated|@generated/m.test(readFileSync11(file, "utf8").slice(0, 256));
|
|
3117
3212
|
}).map((file) => {
|
|
3118
|
-
const source =
|
|
3119
|
-
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 };
|
|
3120
3215
|
});
|
|
3121
3216
|
}
|
|
3122
3217
|
function outlinePolicy(root) {
|
|
3123
3218
|
const config = readMemoryConfig(root);
|
|
3124
|
-
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)]);
|
|
3125
3220
|
return textFingerprint(JSON.stringify({ areas: config.areas, ignores, extractor: EXTRACTOR_ID, declarations: DECLARATIONS, extensions: LANGUAGE_EXTENSIONS, parser: parserIdentity() }));
|
|
3126
3221
|
}
|
|
3127
3222
|
|
|
@@ -3130,10 +3225,10 @@ init_languages();
|
|
|
3130
3225
|
var OUTLINE_SCHEMA = 1;
|
|
3131
3226
|
var outlinePath = (root) => join11(root, ".dotdotgod", "outlines.json");
|
|
3132
3227
|
function assertCachePath(root) {
|
|
3133
|
-
for (const path of [
|
|
3228
|
+
for (const path of [dirname8(outlinePath(root)), outlinePath(root)]) {
|
|
3134
3229
|
let stat;
|
|
3135
3230
|
try {
|
|
3136
|
-
stat =
|
|
3231
|
+
stat = lstatSync3(path);
|
|
3137
3232
|
} catch (error) {
|
|
3138
3233
|
if (error.code !== "ENOENT") throw error;
|
|
3139
3234
|
}
|
|
@@ -3143,7 +3238,7 @@ function assertCachePath(root) {
|
|
|
3143
3238
|
function readOutlines(root) {
|
|
3144
3239
|
try {
|
|
3145
3240
|
assertCachePath(root);
|
|
3146
|
-
const index = JSON.parse(
|
|
3241
|
+
const index = JSON.parse(readFileSync12(outlinePath(root), "utf8"));
|
|
3147
3242
|
if (index.schema !== OUTLINE_SCHEMA || index.extractor !== EXTRACTOR_ID || !Array.isArray(index.files)) return null;
|
|
3148
3243
|
const paths = /* @__PURE__ */ new Set();
|
|
3149
3244
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -3176,7 +3271,7 @@ function outlineStatus(root) {
|
|
|
3176
3271
|
assertCachePath(root);
|
|
3177
3272
|
const files = sourceFiles(root);
|
|
3178
3273
|
const index = readOutlines(root);
|
|
3179
|
-
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) };
|
|
3180
3275
|
const old = new Map(index.files.map((file) => [file.path, file]));
|
|
3181
3276
|
const current = new Set(files.map((file) => file.path));
|
|
3182
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);
|
|
@@ -3205,7 +3300,7 @@ async function buildOutlines(root) {
|
|
|
3205
3300
|
continue;
|
|
3206
3301
|
}
|
|
3207
3302
|
const { extractSymbols: extractSymbols2 } = await Promise.resolve().then(() => (init_parser(), parser_exports));
|
|
3208
|
-
const source =
|
|
3303
|
+
const source = readFileSync12(join11(root, file.path), "utf8");
|
|
3209
3304
|
const extracted = await extractSymbols2(file.path, source, file.language);
|
|
3210
3305
|
if (fingerprint(join11(root, file.path)) !== file.hash) throw new Error(`Source changed during outline extraction: ${file.path}`);
|
|
3211
3306
|
records.push({ ...file, ...extracted });
|
|
@@ -3213,9 +3308,9 @@ async function buildOutlines(root) {
|
|
|
3213
3308
|
}
|
|
3214
3309
|
const index = { schema: OUTLINE_SCHEMA, extractor: EXTRACTOR_ID, policy: outlinePolicy(root), files: records, refresh: { parsed, reused } };
|
|
3215
3310
|
const target = outlinePath(root);
|
|
3216
|
-
|
|
3311
|
+
mkdirSync4(dirname8(target), { recursive: true });
|
|
3217
3312
|
const temporary = `${target}.tmp-${process.pid}-${randomUUID()}`;
|
|
3218
|
-
|
|
3313
|
+
writeFileSync4(temporary, JSON.stringify(index), { flag: "wx" });
|
|
3219
3314
|
renameSync(temporary, target);
|
|
3220
3315
|
return index;
|
|
3221
3316
|
}
|
|
@@ -3233,10 +3328,10 @@ ${text}`;
|
|
|
3233
3328
|
}
|
|
3234
3329
|
|
|
3235
3330
|
// packages/cli/src/symbols/graph.mjs
|
|
3236
|
-
import { dirname as
|
|
3331
|
+
import { dirname as dirname9, posix } from "node:path";
|
|
3237
3332
|
function importTarget(file, specifier, paths) {
|
|
3238
3333
|
if (!specifier.startsWith(".")) return null;
|
|
3239
|
-
const base = posix.normalize(posix.join(
|
|
3334
|
+
const base = posix.normalize(posix.join(dirname9(file).replaceAll("\\", "/"), specifier));
|
|
3240
3335
|
if (base.startsWith("../")) return null;
|
|
3241
3336
|
const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
|
|
3242
3337
|
const candidates = [base, ...[".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"].map((extension) => `${stem}${extension}`), ...[".ts", ".js", ".py"].map((extension) => `${base}/index${extension}`)];
|
|
@@ -3323,14 +3418,14 @@ function writeIndex(root, index) {
|
|
|
3323
3418
|
}
|
|
3324
3419
|
function readIndex(root) {
|
|
3325
3420
|
const file = cacheFile(root);
|
|
3326
|
-
if (!
|
|
3421
|
+
if (!existsSync13(file)) return null;
|
|
3327
3422
|
try {
|
|
3328
|
-
const manifest = JSON.parse(
|
|
3423
|
+
const manifest = JSON.parse(readFileSync13(file, "utf8"));
|
|
3329
3424
|
const shards = manifest.graph?.shards;
|
|
3330
3425
|
if (!shards) return manifest;
|
|
3331
3426
|
const compact = { nodes: {}, edges: {} };
|
|
3332
|
-
for (const [name, path] of Object.entries(shards.nodes ?? {})) compact.nodes[name] =
|
|
3333
|
-
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")) : [];
|
|
3334
3429
|
return { ...manifest, graph: expandGraph(compact) };
|
|
3335
3430
|
} catch {
|
|
3336
3431
|
return null;
|
|
@@ -3386,7 +3481,7 @@ function graphSummary(index) {
|
|
|
3386
3481
|
|
|
3387
3482
|
// packages/cli/src/validate/references.mjs
|
|
3388
3483
|
import { statSync as statSync5 } from "node:fs";
|
|
3389
|
-
import { dirname as
|
|
3484
|
+
import { dirname as dirname10, relative as relative6, resolve as resolve8 } from "node:path";
|
|
3390
3485
|
var blank = (text) => text.replace(/[^\n\r]/g, " ");
|
|
3391
3486
|
var unescape = (text) => text.replace(/\\([!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~])/g, "$1");
|
|
3392
3487
|
var labelKey = (text) => unescape(text).trim().replace(/\s+/g, " ").toLowerCase();
|
|
@@ -3486,7 +3581,7 @@ function extractMemoryReferences(content) {
|
|
|
3486
3581
|
return references;
|
|
3487
3582
|
}
|
|
3488
3583
|
function sharedLocalReferences(content, root, file, config) {
|
|
3489
|
-
const source = resolveMemoryArea(
|
|
3584
|
+
const source = resolveMemoryArea(relative6(root, file).split("\\").join("/"), config);
|
|
3490
3585
|
if (source?.scope !== "shared") return [];
|
|
3491
3586
|
const errors = [];
|
|
3492
3587
|
for (const item of extractMemoryReferences(content)) {
|
|
@@ -3498,14 +3593,14 @@ function sharedLocalReferences(content, root, file, config) {
|
|
|
3498
3593
|
continue;
|
|
3499
3594
|
}
|
|
3500
3595
|
if (!path) continue;
|
|
3501
|
-
const base = item.kind === "markdown-link" || /^\.\.?\//.test(path) ?
|
|
3502
|
-
const target =
|
|
3596
|
+
const base = item.kind === "markdown-link" || /^\.\.?\//.test(path) ? dirname10(file) : root;
|
|
3597
|
+
const target = relative6(root, resolve8(base, path)).split("\\").join("/");
|
|
3503
3598
|
if (target === ".." || target.startsWith("../")) continue;
|
|
3504
3599
|
if (item.kind === "inline-code") {
|
|
3505
3600
|
const configuredDirectory = (config.areas ?? []).some((area3) => (area3.paths ?? []).some((pattern) => pattern === `${target}/**`));
|
|
3506
3601
|
let directory = configuredDirectory;
|
|
3507
3602
|
try {
|
|
3508
|
-
directory ||= statSync5(
|
|
3603
|
+
directory ||= statSync5(resolve8(root, target)).isDirectory();
|
|
3509
3604
|
} catch {
|
|
3510
3605
|
}
|
|
3511
3606
|
if (directory) continue;
|
|
@@ -3530,7 +3625,7 @@ function runValidate(argv) {
|
|
|
3530
3625
|
else if (!arg.startsWith("-")) options.root = arg;
|
|
3531
3626
|
else usage(`Unknown option: ${arg}`, "validate");
|
|
3532
3627
|
}
|
|
3533
|
-
const root =
|
|
3628
|
+
const root = resolve9(options.root);
|
|
3534
3629
|
const memoryConfig = readMemoryConfig(root);
|
|
3535
3630
|
const documentationRoot = memoryConfig.documentation?.root ?? "docs";
|
|
3536
3631
|
const docs = join13(root, documentationRoot);
|
|
@@ -3551,7 +3646,7 @@ Fix: ${fix}` : message, ...prompt ? { prompt } : {} });
|
|
|
3551
3646
|
return false;
|
|
3552
3647
|
};
|
|
3553
3648
|
const walk = (dir) => {
|
|
3554
|
-
if (!
|
|
3649
|
+
if (!existsSync14(dir) || shouldSkipDir(dir)) return;
|
|
3555
3650
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
3556
3651
|
const path = join13(dir, entry.name);
|
|
3557
3652
|
if (entry.isDirectory()) {
|
|
@@ -3563,7 +3658,7 @@ Fix: ${fix}` : message, ...prompt ? { prompt } : {} });
|
|
|
3563
3658
|
} else if (entry.isFile() && entry.name.endsWith(".md")) markdownFiles.push(path);
|
|
3564
3659
|
}
|
|
3565
3660
|
};
|
|
3566
|
-
if (!
|
|
3661
|
+
if (!existsSync14(docs)) usage(`docs directory not found: ${docs}`);
|
|
3567
3662
|
const validationPolicy = cloneValidationPolicy(memoryConfig.validation ?? DEFAULT_VALIDATION_POLICY);
|
|
3568
3663
|
const maxLines = options.maxLines ?? validationPolicy.markdown.maxLines;
|
|
3569
3664
|
const maxChars = options.maxChars ?? validationPolicy.markdown.maxChars;
|
|
@@ -3594,7 +3689,7 @@ README.md directory indexes, clear HTTP-status filenames such as RESPONSE_200_OK
|
|
|
3594
3689
|
walk(docs);
|
|
3595
3690
|
const dirSiblingMap = /* @__PURE__ */ new Map();
|
|
3596
3691
|
for (const f of markdownFiles) {
|
|
3597
|
-
const dir =
|
|
3692
|
+
const dir = dirname11(f);
|
|
3598
3693
|
if (!dirSiblingMap.has(dir)) dirSiblingMap.set(dir, []);
|
|
3599
3694
|
dirSiblingMap.get(dir).push(basename7(f));
|
|
3600
3695
|
}
|
|
@@ -3603,14 +3698,14 @@ README.md directory indexes, clear HTTP-status filenames such as RESPONSE_200_OK
|
|
|
3603
3698
|
const docsRel = rel(docs, file);
|
|
3604
3699
|
const rootRel = rel(root, file);
|
|
3605
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.");
|
|
3606
|
-
const content =
|
|
3701
|
+
const content = readFileSync14(file, "utf8");
|
|
3607
3702
|
fileCache.set(file, content);
|
|
3608
3703
|
const docArea = docsRel?.split("/")[0];
|
|
3609
3704
|
if (name !== "README.md" && isUpperSnakeMarkdown(name) && FILENAME_QUALITY_AREAS.has(docArea)) {
|
|
3610
3705
|
const fp = validationPolicy.markdown?.filename ?? {};
|
|
3611
3706
|
const allow = fp.allow ?? [];
|
|
3612
3707
|
if (!allow.some((p) => matchPathPattern(rootRel, p)) && (fp.warnNumberedSeries ?? true)) {
|
|
3613
|
-
const siblings = dirSiblingMap.get(
|
|
3708
|
+
const siblings = dirSiblingMap.get(dirname11(file)) ?? [];
|
|
3614
3709
|
if (isNumberedSeriesFilename(name, siblings)) {
|
|
3615
3710
|
const heading = extractFirstHeading(content);
|
|
3616
3711
|
const suggestion = suggestFilenameFromHeading(heading);
|
|
@@ -3652,7 +3747,7 @@ ${traceabilityExample()}`, block.line);
|
|
|
3652
3747
|
}
|
|
3653
3748
|
}
|
|
3654
3749
|
const byDir = /* @__PURE__ */ new Map();
|
|
3655
|
-
for (const file of markdownFiles) byDir.set(
|
|
3750
|
+
for (const file of markdownFiles) byDir.set(dirname11(file), [...byDir.get(dirname11(file)) ?? [], file]);
|
|
3656
3751
|
for (const [dir, files] of byDir) {
|
|
3657
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.");
|
|
3658
3753
|
}
|
|
@@ -3663,18 +3758,18 @@ ${traceabilityExample()}`, block.line);
|
|
|
3663
3758
|
}
|
|
3664
3759
|
if (options.linkCheck) {
|
|
3665
3760
|
for (const [file, content] of fileCache) {
|
|
3666
|
-
const fileDir =
|
|
3761
|
+
const fileDir = dirname11(file);
|
|
3667
3762
|
for (const { href, line } of extractLinks(content)) {
|
|
3668
3763
|
const hashIndex = href.indexOf("#");
|
|
3669
3764
|
const pathPart = hashIndex === -1 ? href : href.slice(0, hashIndex);
|
|
3670
3765
|
const anchor = hashIndex === -1 ? "" : href.slice(hashIndex + 1);
|
|
3671
|
-
const target = pathPart ?
|
|
3672
|
-
if (pathPart && !
|
|
3766
|
+
const target = pathPart ? resolve9(fileDir, pathPart) : file;
|
|
3767
|
+
if (pathPart && !existsSync14(target)) {
|
|
3673
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.");
|
|
3674
3769
|
continue;
|
|
3675
3770
|
}
|
|
3676
3771
|
if (anchor && extname6(target) === ".md") {
|
|
3677
|
-
const targetContent = fileCache.get(target) ?? (
|
|
3772
|
+
const targetContent = fileCache.get(target) ?? (existsSync14(target) ? readFileSync14(target, "utf8") : "");
|
|
3678
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.");
|
|
3679
3774
|
}
|
|
3680
3775
|
}
|
|
@@ -3709,19 +3804,19 @@ ${traceabilityExample()}`, block.line);
|
|
|
3709
3804
|
if (options.includeLocalMemory) {
|
|
3710
3805
|
for (const area2 of ["plan", "archive/plan", "archive/report"]) {
|
|
3711
3806
|
const areaRoot = join13(docs, area2);
|
|
3712
|
-
if (!
|
|
3807
|
+
if (!existsSync14(areaRoot)) continue;
|
|
3713
3808
|
for (const entry of readdirSync3(areaRoot, { withFileTypes: true })) {
|
|
3714
3809
|
if (!entry.isDirectory()) continue;
|
|
3715
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.");
|
|
3716
3811
|
const readme = join13(areaRoot, entry.name, "README.md");
|
|
3717
|
-
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.");
|
|
3718
3813
|
}
|
|
3719
3814
|
}
|
|
3720
3815
|
}
|
|
3721
3816
|
const gitignore = join13(root, ".gitignore");
|
|
3722
|
-
if (!
|
|
3817
|
+
if (!existsSync14(gitignore)) addError(gitignore, "MISSING_GITIGNORE", "Expected .gitignore", null, `create .gitignore and include ${documentationRoot}/plan, ${documentationRoot}/archive, and .dotdotgod entries.`);
|
|
3723
3818
|
else {
|
|
3724
|
-
const content =
|
|
3819
|
+
const content = readFileSync14(gitignore, "utf8").split("\n").map((line) => line.trim());
|
|
3725
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.`);
|
|
3726
3821
|
}
|
|
3727
3822
|
if (options.json) console.log(JSON.stringify({ ok: errors.length === 0, errors, warnings }, null, 2));
|
|
@@ -3746,8 +3841,8 @@ Prompt: ${error.prompt}` : ""}`);
|
|
|
3746
3841
|
}
|
|
3747
3842
|
|
|
3748
3843
|
// packages/cli/src/commands/config.mjs
|
|
3749
|
-
import { existsSync as
|
|
3750
|
-
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";
|
|
3751
3846
|
function parseConfigOptions(argv, usageKey = "config") {
|
|
3752
3847
|
const options = { root: ".", json: false, template: null };
|
|
3753
3848
|
let rootSet = false;
|
|
@@ -3765,7 +3860,7 @@ function parseConfigOptions(argv, usageKey = "config") {
|
|
|
3765
3860
|
} else if (!arg.startsWith("-")) usage(`Unexpected argument: ${arg}`, usageKey);
|
|
3766
3861
|
else usage(`Unknown option: ${arg}`, usageKey);
|
|
3767
3862
|
}
|
|
3768
|
-
options.root =
|
|
3863
|
+
options.root = resolve10(options.root);
|
|
3769
3864
|
return options;
|
|
3770
3865
|
}
|
|
3771
3866
|
function configInitError(options, code, message, path = null) {
|
|
@@ -3783,7 +3878,7 @@ function formatConfigOutput(payload) {
|
|
|
3783
3878
|
lines.push(`- path: ${payload.path ?? "none"}`);
|
|
3784
3879
|
lines.push(`- documentation root: ${payload.config.documentation.root}`);
|
|
3785
3880
|
lines.push(`- memory areas: ${payload.config.areas.length}`);
|
|
3786
|
-
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})`);
|
|
3787
3882
|
lines.push(`- traceability required: ${(payload.config.traceability.required ?? []).join(", ") || "none"}`);
|
|
3788
3883
|
lines.push(`- traceability exclude: ${(payload.config.traceability.exclude ?? []).join(", ") || "none"}`);
|
|
3789
3884
|
lines.push("- traceability keys:");
|
|
@@ -3807,17 +3902,17 @@ function runConfig(argv) {
|
|
|
3807
3902
|
const isInit = argv[0] === "init";
|
|
3808
3903
|
const options = parseConfigOptions(isInit ? argv.slice(1) : argv, isInit ? "config init" : "config");
|
|
3809
3904
|
if (isInit) {
|
|
3810
|
-
if (!
|
|
3905
|
+
if (!existsSync15(options.root)) configInitError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
|
|
3811
3906
|
try {
|
|
3812
3907
|
if (!statSync6(options.root).isDirectory()) configInitError(options, "ROOT_NOT_DIRECTORY", `Project root is not a directory: ${options.root}`);
|
|
3813
3908
|
} catch {
|
|
3814
3909
|
configInitError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
|
|
3815
3910
|
}
|
|
3816
3911
|
const target = join14(options.root, "dotdotgod.config.json");
|
|
3817
|
-
if (
|
|
3912
|
+
if (existsSync15(target)) configInitError(options, "CONFIG_EXISTS", "dotdotgod.config.json already exists.", target);
|
|
3818
3913
|
const template = resolveInitializationTemplate(options.template);
|
|
3819
3914
|
if (!template.ok) configInitError(options, template.error.code, template.error.message, template.error.path ?? target);
|
|
3820
|
-
|
|
3915
|
+
writeFileSync5(target, template.text);
|
|
3821
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 } };
|
|
3822
3917
|
if (options.json) console.log(JSON.stringify(payload2, null, 2));
|
|
3823
3918
|
else console.log(`dotdotgod config init: created ${target} from ${template.source}:${template.name} (${template.selectedBy})`);
|
|
@@ -3831,7 +3926,7 @@ function runConfig(argv) {
|
|
|
3831
3926
|
embedding = { source: resolved.source, path: resolved.path, profile: sanitizeEmbeddingProfile(resolved.profile) };
|
|
3832
3927
|
} catch (error) {
|
|
3833
3928
|
errors.push({ file: "embedding config", code: "EMBEDDING_CONFIG_INVALID", message: error instanceof Error ? error.message : String(error) });
|
|
3834
|
-
embedding = { source: "invalid", path: null, profile:
|
|
3929
|
+
embedding = { source: "invalid", path: null, profile: null };
|
|
3835
3930
|
}
|
|
3836
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 };
|
|
3837
3932
|
if (options.json) console.log(JSON.stringify(payload, null, 2));
|
|
@@ -3840,10 +3935,10 @@ function runConfig(argv) {
|
|
|
3840
3935
|
}
|
|
3841
3936
|
|
|
3842
3937
|
// packages/cli/src/commands/query.mjs
|
|
3843
|
-
import { resolve as
|
|
3938
|
+
import { resolve as resolve11 } from "node:path";
|
|
3844
3939
|
|
|
3845
3940
|
// packages/cli/src/query/embedding-runtime.mjs
|
|
3846
|
-
import { existsSync as
|
|
3941
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync15, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
3847
3942
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
3848
3943
|
import { createRequire as createRequire2 } from "node:module";
|
|
3849
3944
|
import { homedir as homedir3 } from "node:os";
|
|
@@ -3876,7 +3971,7 @@ function embeddingRuntimeStatus(options = {}) {
|
|
|
3876
3971
|
const require3 = createRequire2(join15(root, "package.json"));
|
|
3877
3972
|
require3.resolve(EMBEDDING_RUNTIME_PACKAGE);
|
|
3878
3973
|
const packagePath = join15(root, "node_modules", ...EMBEDDING_RUNTIME_PACKAGE.split("/"), "package.json");
|
|
3879
|
-
const version = JSON.parse(
|
|
3974
|
+
const version = JSON.parse(readFileSync15(packagePath, "utf8")).version;
|
|
3880
3975
|
return { ok: true, installed: true, installAvailable: true, package: EMBEDDING_RUNTIME_PACKAGE, requiredVersion: EMBEDDING_RUNTIME_VERSION, packageVersion: version, location: root };
|
|
3881
3976
|
} catch {
|
|
3882
3977
|
return { ok: true, installed: false, installAvailable: true, package: EMBEDDING_RUNTIME_PACKAGE, requiredVersion: EMBEDDING_RUNTIME_VERSION, packageVersion: null, location: root };
|
|
@@ -3886,9 +3981,9 @@ function installEmbeddingRuntime(options = {}) {
|
|
|
3886
3981
|
if (options.confirm !== true) throw new Error("Embedding runtime installation requires explicit confirmation.");
|
|
3887
3982
|
const root = embeddingRuntimeRoot(options);
|
|
3888
3983
|
const lock = join15(root, ".installing");
|
|
3889
|
-
if (
|
|
3890
|
-
|
|
3891
|
-
|
|
3984
|
+
if (existsSync16(lock)) throw new Error("Embedding runtime installation is already in progress.");
|
|
3985
|
+
mkdirSync5(root, { recursive: true });
|
|
3986
|
+
writeFileSync6(lock, `${process.pid}
|
|
3892
3987
|
`, { flag: "wx" });
|
|
3893
3988
|
try {
|
|
3894
3989
|
const spawn = options.spawnImpl ?? spawnSync2;
|
|
@@ -3944,7 +4039,12 @@ async function postJson(url, body, profile) {
|
|
|
3944
4039
|
const response = await fetch(url, { method: "POST", headers: { "content-type": "application/json", ...key ? { authorization: `Bearer ${key}` } : {} }, body: JSON.stringify(body), signal: controller.signal });
|
|
3945
4040
|
if (!response.ok) {
|
|
3946
4041
|
const category = response.status === 401 || response.status === 403 ? "authentication" : response.status === 429 ? "rate limit" : `HTTP ${response.status}`;
|
|
3947
|
-
|
|
4042
|
+
const error = new Error(`Remote embedding ${category} failure.`);
|
|
4043
|
+
if (response.status === 400 || response.status === 413) {
|
|
4044
|
+
const body2 = (await response.text()).slice(0, 8192);
|
|
4045
|
+
error.tokenLimit = /(?:exceed\w*[\s\S]{0,120}tokens?|tokens?[\s\S]{0,120}(?:exceed|limit|too (?:long|many))|maximum context length)/i.test(body2);
|
|
4046
|
+
}
|
|
4047
|
+
throw error;
|
|
3948
4048
|
}
|
|
3949
4049
|
try {
|
|
3950
4050
|
return await response.json();
|
|
@@ -3971,25 +4071,41 @@ function remoteEmbedder(profile) {
|
|
|
3971
4071
|
return ordered.map(normalize);
|
|
3972
4072
|
};
|
|
3973
4073
|
}
|
|
4074
|
+
function withInputFormat(embed, profile) {
|
|
4075
|
+
const format = embeddingInputFormat(profile);
|
|
4076
|
+
return (texts, role) => {
|
|
4077
|
+
if (role !== void 0 && role !== "query" && role !== "document") throw new Error("Embedding role must be query or document.");
|
|
4078
|
+
return embed(role === void 0 ? texts : texts.map((text) => format[`${role}Prefix`] + text));
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
3974
4081
|
async function createEmbedder(profile) {
|
|
3975
4082
|
const embed = profile.provider === "local" ? await localEmbedder(profile) : remoteEmbedder(profile);
|
|
3976
|
-
|
|
4083
|
+
async function embedBatch(texts) {
|
|
3977
4084
|
if (texts.length === 0) return [];
|
|
3978
|
-
|
|
4085
|
+
let rows;
|
|
4086
|
+
try {
|
|
4087
|
+
rows = await embed(texts);
|
|
4088
|
+
} catch (error) {
|
|
4089
|
+
if (!error.tokenLimit || texts.length === 1) throw error;
|
|
4090
|
+
const middle = Math.ceil(texts.length / 2);
|
|
4091
|
+
rows = [...await embedBatch(texts.slice(0, middle)), ...await embedBatch(texts.slice(middle))];
|
|
4092
|
+
}
|
|
3979
4093
|
if (!Array.isArray(rows) || rows.length !== texts.length) throw new Error("Embedding provider returned an unexpected vector count.");
|
|
3980
4094
|
const dimensions = rows[0]?.length;
|
|
3981
4095
|
if (!dimensions || rows.some((row) => row.length !== dimensions)) throw new Error("Embedding provider returned inconsistent vector dimensions.");
|
|
3982
4096
|
return rows;
|
|
3983
|
-
}
|
|
4097
|
+
}
|
|
4098
|
+
return withInputFormat(embedBatch, profile);
|
|
3984
4099
|
}
|
|
3985
4100
|
async function resolveEmbedder(root, options = {}) {
|
|
3986
|
-
const resolved = options.profile ? { profile: options.profile, source: options.source ?? "injected", path: null } : resolveEmbeddingProfile(root, options);
|
|
3987
|
-
|
|
4101
|
+
const resolved = options.profile || options.embed ? { profile: options.profile ?? DEFAULT_EMBEDDING_PROFILE, source: options.source ?? "injected", path: null } : resolveEmbeddingProfile(root, options);
|
|
4102
|
+
if (!resolved.profile) throw new Error("Embedding is not configured. Set embedding in project or global configuration to use vector search.");
|
|
4103
|
+
return { ...resolved, identity: embeddingProfileIdentity(resolved.profile), embed: options.embed ? withInputFormat(options.embed, resolved.profile) : await createEmbedder(resolved.profile) };
|
|
3988
4104
|
}
|
|
3989
4105
|
|
|
3990
4106
|
// packages/cli/src/query/store.mjs
|
|
3991
4107
|
import { createHash as createHash6 } from "node:crypto";
|
|
3992
|
-
import { existsSync as
|
|
4108
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync6, readFileSync as readFileSync16, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
3993
4109
|
import { join as join16 } from "node:path";
|
|
3994
4110
|
var VECTOR_SCHEMA_VERSION = 2;
|
|
3995
4111
|
var VECTOR_DIMENSIONS = 384;
|
|
@@ -4015,13 +4131,13 @@ function vectorCachePaths(root) {
|
|
|
4015
4131
|
}
|
|
4016
4132
|
function readVectorCache(root, identity2 = null) {
|
|
4017
4133
|
const paths = vectorCachePaths(root);
|
|
4018
|
-
if (!
|
|
4134
|
+
if (!existsSync17(paths.manifest) || !existsSync17(paths.chunks) || !existsSync17(paths.embeddings)) return null;
|
|
4019
4135
|
try {
|
|
4020
|
-
const manifest = JSON.parse(
|
|
4136
|
+
const manifest = JSON.parse(readFileSync16(paths.manifest, "utf8"));
|
|
4021
4137
|
if (manifest.schemaVersion !== VECTOR_SCHEMA_VERSION || !Number.isInteger(manifest.dimensions) || manifest.dimensions < 1) return null;
|
|
4022
4138
|
if (identity2 && manifest.profileFingerprint !== profileFingerprint(identity2)) return null;
|
|
4023
|
-
const chunks =
|
|
4024
|
-
const buffer =
|
|
4139
|
+
const chunks = readFileSync16(paths.chunks, "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
4140
|
+
const buffer = readFileSync16(paths.embeddings);
|
|
4025
4141
|
if (buffer.byteLength !== chunks.length * manifest.dimensions * 4) return null;
|
|
4026
4142
|
const vectors = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4);
|
|
4027
4143
|
if (!isValidNormalizedVectorValues(vectors, manifest.dimensions)) return null;
|
|
@@ -4032,14 +4148,14 @@ function readVectorCache(root, identity2 = null) {
|
|
|
4032
4148
|
}
|
|
4033
4149
|
function atomicWrite(path, data) {
|
|
4034
4150
|
const temporary = `${path}.tmp-${process.pid}`;
|
|
4035
|
-
|
|
4151
|
+
writeFileSync7(temporary, data);
|
|
4036
4152
|
renameSync2(temporary, path);
|
|
4037
4153
|
}
|
|
4038
4154
|
function writeVectorCache(root, chunks, vectors, metadata = {}) {
|
|
4039
4155
|
const paths = vectorCachePaths(root);
|
|
4040
4156
|
const dimensions = metadata.dimensions;
|
|
4041
4157
|
if (!Number.isInteger(dimensions) || dimensions < 1) throw new Error("Vector dimensions must be a positive integer.");
|
|
4042
|
-
|
|
4158
|
+
mkdirSync6(paths.directory, { recursive: true });
|
|
4043
4159
|
if (vectors.length !== chunks.length * dimensions) throw new Error("Vector count does not match chunk metadata.");
|
|
4044
4160
|
if (!isValidNormalizedVectorValues(vectors, dimensions)) throw new Error("Vectors must contain finite normalized values.");
|
|
4045
4161
|
const manifest = { schemaVersion: VECTOR_SCHEMA_VERSION, chunks: chunks.length, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...metadata };
|
|
@@ -4098,11 +4214,6 @@ async function projectCorpus(root, scope) {
|
|
|
4098
4214
|
const outlines = scope === "docs" ? null : await buildOutlines(root);
|
|
4099
4215
|
return { chunks: [...docs, ...outlines ? outlineChunks(outlines) : []], outlines };
|
|
4100
4216
|
}
|
|
4101
|
-
function assertCodeEmbeddingConsent(root, options, chunks) {
|
|
4102
|
-
if (!chunks.some((chunk) => chunk.kind === "symbol")) return;
|
|
4103
|
-
const profile = options.profile ?? resolveEmbeddingProfile(root).profile;
|
|
4104
|
-
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.");
|
|
4105
|
-
}
|
|
4106
4217
|
function attachRelationships(results, outlines) {
|
|
4107
4218
|
if (!outlines) return results;
|
|
4108
4219
|
const graph = addOutlineGraph({ nodes: [], edges: [] }, outlines);
|
|
@@ -4117,7 +4228,7 @@ var DEFAULT_LIMIT = 30;
|
|
|
4117
4228
|
var MAX_LIMIT = 100;
|
|
4118
4229
|
var EMBED_BATCH_SIZE = 16;
|
|
4119
4230
|
function parseQueryOptions(argv) {
|
|
4120
|
-
const options = { root: ".", query: "", limit: DEFAULT_LIMIT, json: false, scope: "all", search: "hybrid"
|
|
4231
|
+
const options = { root: ".", query: "", limit: DEFAULT_LIMIT, json: false, scope: "all", search: "hybrid" };
|
|
4121
4232
|
let rootSet = false;
|
|
4122
4233
|
const queryParts = [];
|
|
4123
4234
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -4129,14 +4240,13 @@ function parseQueryOptions(argv) {
|
|
|
4129
4240
|
} else if (arg === "--search") {
|
|
4130
4241
|
options.search = argv[++i];
|
|
4131
4242
|
if (!["hybrid", "keyword", "vector"].includes(options.search)) usage("--search must be hybrid, keyword or vector.", "query");
|
|
4132
|
-
} else if (arg === "--
|
|
4133
|
-
else if (arg === "--limit") {
|
|
4243
|
+
} else if (arg === "--limit") {
|
|
4134
4244
|
const value = Number(argv[++i]);
|
|
4135
4245
|
if (!Number.isInteger(value) || value < 1 || value > MAX_LIMIT) usage(`--limit must be an integer from 1 to ${MAX_LIMIT}.`, "query");
|
|
4136
4246
|
options.limit = value;
|
|
4137
4247
|
} else if (arg.startsWith("-")) usage(`Unknown option: ${arg}`, "query");
|
|
4138
4248
|
else if (!rootSet) {
|
|
4139
|
-
options.root =
|
|
4249
|
+
options.root = resolve11(arg);
|
|
4140
4250
|
rootSet = true;
|
|
4141
4251
|
} else queryParts.push(arg);
|
|
4142
4252
|
}
|
|
@@ -4190,7 +4300,7 @@ async function buildVectorIndex(root, embedOrOptions = {}) {
|
|
|
4190
4300
|
});
|
|
4191
4301
|
for (let offset = 0; offset < missing.length; offset += EMBED_BATCH_SIZE) {
|
|
4192
4302
|
const batch = missing.slice(offset, offset + EMBED_BATCH_SIZE);
|
|
4193
|
-
const embedded = await resolved.embed(batch.map(({ chunk }) =>
|
|
4303
|
+
const embedded = await resolved.embed(batch.map(({ chunk }) => chunk.passage), "document");
|
|
4194
4304
|
embedded.forEach((vector, index) => {
|
|
4195
4305
|
rows[batch[index].index] = vector;
|
|
4196
4306
|
});
|
|
@@ -4216,11 +4326,20 @@ async function queryProject(root, query, options = {}) {
|
|
|
4216
4326
|
let vector = [];
|
|
4217
4327
|
let index;
|
|
4218
4328
|
let vectorError;
|
|
4219
|
-
|
|
4329
|
+
let configured = false;
|
|
4330
|
+
if (search !== "keyword") {
|
|
4331
|
+
try {
|
|
4332
|
+
configured = Boolean(options.profile || options.embed || resolveEmbeddingProfile(root, options).profile);
|
|
4333
|
+
} catch (error) {
|
|
4334
|
+
if (search === "vector") throw error;
|
|
4335
|
+
vectorError = error.message;
|
|
4336
|
+
}
|
|
4337
|
+
if (!configured && !vectorError && search === "vector") throw new Error("Embedding is not configured. Set embedding in project or global configuration to use vector search.");
|
|
4338
|
+
}
|
|
4339
|
+
if (search !== "keyword" && configured && corpus.chunks.length) {
|
|
4220
4340
|
try {
|
|
4221
|
-
assertCodeEmbeddingConsent(root, options, corpus.chunks);
|
|
4222
4341
|
index = await buildVectorIndex(root, { ...options, chunks: corpus.chunks });
|
|
4223
|
-
const [queryVector] = await index.embedding.embed([
|
|
4342
|
+
const [queryVector] = await index.embedding.embed([query], "query");
|
|
4224
4343
|
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));
|
|
4225
4344
|
} catch (error) {
|
|
4226
4345
|
if (search === "vector") throw error;
|
|
@@ -4269,7 +4388,7 @@ async function runQuery(argv) {
|
|
|
4269
4388
|
}
|
|
4270
4389
|
|
|
4271
4390
|
// packages/cli/src/commands/embedding.mjs
|
|
4272
|
-
import { resolve as
|
|
4391
|
+
import { resolve as resolve12 } from "node:path";
|
|
4273
4392
|
function parse(argv) {
|
|
4274
4393
|
const [action2 = "status", ...rest] = argv;
|
|
4275
4394
|
let root = ".";
|
|
@@ -4281,12 +4400,18 @@ function parse(argv) {
|
|
|
4281
4400
|
else if (!arg.startsWith("-") && root === ".") root = arg;
|
|
4282
4401
|
else usage(`Unknown embedding option: ${arg}`, `embedding ${action2}`);
|
|
4283
4402
|
}
|
|
4284
|
-
return { action: action2, root:
|
|
4403
|
+
return { action: action2, root: resolve12(root), json: json2, confirm };
|
|
4285
4404
|
}
|
|
4286
4405
|
function runEmbedding(argv, options = {}) {
|
|
4287
4406
|
const parsed = parse(argv);
|
|
4288
4407
|
if (!["status", "install"].includes(parsed.action)) usage(`Unknown embedding command: ${parsed.action}`, "embedding");
|
|
4289
4408
|
const resolved = resolveEmbeddingProfile(parsed.root, options);
|
|
4409
|
+
if (!resolved.profile) {
|
|
4410
|
+
if (parsed.action === "install") throw new Error("Configure an explicit local embedding profile before installing its runtime.");
|
|
4411
|
+
const result2 = { ok: true, command: "embedding status", provider: null, required: false, installed: false, installAvailable: false, source: "not-configured" };
|
|
4412
|
+
console.log(parsed.json ? JSON.stringify(result2, null, 2) : "Embedding is not configured; keyword search is used.");
|
|
4413
|
+
return result2;
|
|
4414
|
+
}
|
|
4290
4415
|
if (resolved.profile.provider !== "local") {
|
|
4291
4416
|
const result2 = { ok: true, command: `embedding ${parsed.action}`, provider: resolved.profile.provider, required: false, installed: false, installAvailable: false };
|
|
4292
4417
|
console.log(parsed.json ? JSON.stringify(result2, null, 2) : "Local embedding runtime is not required for the configured remote provider.");
|
|
@@ -4301,11 +4426,11 @@ function runEmbedding(argv, options = {}) {
|
|
|
4301
4426
|
}
|
|
4302
4427
|
|
|
4303
4428
|
// packages/cli/src/commands/map.mjs
|
|
4304
|
-
import { resolve as
|
|
4429
|
+
import { resolve as resolve14 } from "node:path";
|
|
4305
4430
|
|
|
4306
4431
|
// packages/cli/src/memory/documentation-map.mjs
|
|
4307
|
-
import { existsSync as
|
|
4308
|
-
import { join as join17, resolve as
|
|
4432
|
+
import { existsSync as existsSync18, readdirSync as readdirSync4, statSync as statSync7 } from "node:fs";
|
|
4433
|
+
import { join as join17, resolve as resolve13 } from "node:path";
|
|
4309
4434
|
var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".dotdotgod"]);
|
|
4310
4435
|
function node() {
|
|
4311
4436
|
return { directories: /* @__PURE__ */ new Map(), files: [] };
|
|
@@ -4385,8 +4510,8 @@ function discoverMarkdown(root, documentationRoot, exclude) {
|
|
|
4385
4510
|
return paths.sort();
|
|
4386
4511
|
}
|
|
4387
4512
|
function buildDocumentationMap(projectRoot = ".", { depth = 5 } = {}) {
|
|
4388
|
-
const root =
|
|
4389
|
-
if (!
|
|
4513
|
+
const root = resolve13(projectRoot);
|
|
4514
|
+
if (!existsSync18(root)) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
|
|
4390
4515
|
try {
|
|
4391
4516
|
if (!statSync7(root).isDirectory()) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
|
|
4392
4517
|
} catch {
|
|
@@ -4434,7 +4559,7 @@ function parseMapOptions(argv) {
|
|
|
4434
4559
|
} else if (!arg.startsWith("-")) fail(options.json, "UNEXPECTED_ARGUMENT", `Unexpected argument: ${arg}`);
|
|
4435
4560
|
else fail(options.json, "UNKNOWN_OPTION", `Unknown option: ${arg}`);
|
|
4436
4561
|
}
|
|
4437
|
-
options.root =
|
|
4562
|
+
options.root = resolve14(options.root);
|
|
4438
4563
|
return options;
|
|
4439
4564
|
}
|
|
4440
4565
|
function runMap(argv) {
|
|
@@ -4446,7 +4571,7 @@ function runMap(argv) {
|
|
|
4446
4571
|
}
|
|
4447
4572
|
|
|
4448
4573
|
// packages/cli/src/reference/resolve.mjs
|
|
4449
|
-
import { resolve as
|
|
4574
|
+
import { resolve as resolve17 } from "node:path";
|
|
4450
4575
|
|
|
4451
4576
|
// node_modules/.pnpm/leiden-ts@0.1.0/node_modules/leiden-ts/dist/index.js
|
|
4452
4577
|
var GraphValidationError = class extends Error {
|
|
@@ -5817,8 +5942,8 @@ function compareImpactItems(seeds) {
|
|
|
5817
5942
|
}
|
|
5818
5943
|
|
|
5819
5944
|
// packages/cli/src/impact/references.mjs
|
|
5820
|
-
import { readFileSync as
|
|
5821
|
-
import { extname as extname7, resolve as
|
|
5945
|
+
import { readFileSync as readFileSync17, statSync as statSync8 } from "node:fs";
|
|
5946
|
+
import { extname as extname7, resolve as resolve15 } from "node:path";
|
|
5822
5947
|
var WARNING_LIMIT = 20;
|
|
5823
5948
|
var edgeKey = (source, target, relation) => `${source}\0${target}\0${relation}`;
|
|
5824
5949
|
function prepareImpactReferences(index, root, changedPaths) {
|
|
@@ -5830,10 +5955,10 @@ function prepareImpactReferences(index, root, changedPaths) {
|
|
|
5830
5955
|
if (checks.has(path)) return checks.get(path);
|
|
5831
5956
|
let result;
|
|
5832
5957
|
try {
|
|
5833
|
-
const absolute =
|
|
5958
|
+
const absolute = resolve15(root, path);
|
|
5834
5959
|
const stat = statSync8(absolute);
|
|
5835
5960
|
result = { status: "exists" };
|
|
5836
|
-
if (stat.isFile() && extname7(absolute) === ".md") result.anchors = extractAnchors(
|
|
5961
|
+
if (stat.isFile() && extname7(absolute) === ".md") result.anchors = extractAnchors(readFileSync17(absolute, "utf8"));
|
|
5837
5962
|
} catch (error) {
|
|
5838
5963
|
result = { status: ["ENOENT", "ENOTDIR"].includes(error.code) ? "missing" : "unavailable" };
|
|
5839
5964
|
}
|
|
@@ -5895,15 +6020,15 @@ function prepareImpactReferences(index, root, changedPaths) {
|
|
|
5895
6020
|
}
|
|
5896
6021
|
|
|
5897
6022
|
// packages/cli/src/impact/vector-profile.mjs
|
|
5898
|
-
import { lstatSync as
|
|
5899
|
-
import { extname as extname8, relative as
|
|
6023
|
+
import { lstatSync as lstatSync4, readFileSync as readFileSync18, realpathSync as realpathSync3 } from "node:fs";
|
|
6024
|
+
import { extname as extname8, relative as relative7, resolve as resolve16, sep } from "node:path";
|
|
5900
6025
|
var MAX_VECTOR_PROFILE_FILE_BYTES = 64 * 1024;
|
|
5901
6026
|
var MAX_VECTOR_PROFILE_CHARS = 4e3;
|
|
5902
6027
|
var MAX_VECTOR_PROFILE_METADATA_ITEMS = 20;
|
|
5903
6028
|
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"]);
|
|
5904
6029
|
var GENERATED_SEGMENTS = /* @__PURE__ */ new Set([".dotdotgod", "build", "coverage", "dist", "node_modules"]);
|
|
5905
6030
|
function insideRoot(root, absolute) {
|
|
5906
|
-
const normalizedRoot =
|
|
6031
|
+
const normalizedRoot = resolve16(root);
|
|
5907
6032
|
return absolute === normalizedRoot || absolute.startsWith(`${normalizedRoot}${sep}`);
|
|
5908
6033
|
}
|
|
5909
6034
|
function normalizeChangedPath(path) {
|
|
@@ -5914,17 +6039,17 @@ function normalizeChangedPath(path) {
|
|
|
5914
6039
|
function canonicalizeChangedPath(root, path) {
|
|
5915
6040
|
const normalized = normalizeChangedPath(path);
|
|
5916
6041
|
if (!normalized) return null;
|
|
5917
|
-
const rootAbsolute =
|
|
5918
|
-
const absolute =
|
|
6042
|
+
const rootAbsolute = resolve16(root);
|
|
6043
|
+
const absolute = resolve16(rootAbsolute, normalized);
|
|
5919
6044
|
if (!insideRoot(rootAbsolute, absolute)) return normalized;
|
|
5920
6045
|
try {
|
|
5921
|
-
const canonicalRoot =
|
|
5922
|
-
const canonicalAbsolute =
|
|
6046
|
+
const canonicalRoot = realpathSync3(rootAbsolute);
|
|
6047
|
+
const canonicalAbsolute = realpathSync3(absolute);
|
|
5923
6048
|
if (!insideRoot(canonicalRoot, canonicalAbsolute)) return normalized;
|
|
5924
|
-
const canonical =
|
|
6049
|
+
const canonical = relative7(canonicalRoot, canonicalAbsolute).replaceAll("\\", "/");
|
|
5925
6050
|
return canonical || normalized;
|
|
5926
6051
|
} catch {
|
|
5927
|
-
return
|
|
6052
|
+
return relative7(rootAbsolute, absolute).replaceAll("\\", "/") || normalized;
|
|
5928
6053
|
}
|
|
5929
6054
|
}
|
|
5930
6055
|
function unsafeProfilePath(path) {
|
|
@@ -5946,15 +6071,15 @@ function graphMetadata(graph, seedId) {
|
|
|
5946
6071
|
function safeTextPrefix(root, path) {
|
|
5947
6072
|
if (unsafeProfilePath(path) || !TEXT_EXTENSIONS.has(extname8(path).toLowerCase())) return "";
|
|
5948
6073
|
try {
|
|
5949
|
-
const absolute =
|
|
6074
|
+
const absolute = resolve16(root, path);
|
|
5950
6075
|
if (!insideRoot(root, absolute)) return "";
|
|
5951
|
-
const canonicalAbsolute =
|
|
5952
|
-
if (!insideRoot(
|
|
5953
|
-
const canonicalPath =
|
|
6076
|
+
const canonicalAbsolute = realpathSync3(absolute);
|
|
6077
|
+
if (!insideRoot(realpathSync3(root), canonicalAbsolute)) return "";
|
|
6078
|
+
const canonicalPath = relative7(realpathSync3(root), canonicalAbsolute).replaceAll("\\", "/");
|
|
5954
6079
|
if (canonicalPath !== path || unsafeProfilePath(canonicalPath) || !TEXT_EXTENSIONS.has(extname8(canonicalPath).toLowerCase())) return "";
|
|
5955
|
-
const stats =
|
|
6080
|
+
const stats = lstatSync4(canonicalAbsolute);
|
|
5956
6081
|
if (!stats.isFile() || stats.isSymbolicLink() || stats.size > MAX_VECTOR_PROFILE_FILE_BYTES) return "";
|
|
5957
|
-
const buffer =
|
|
6082
|
+
const buffer = readFileSync18(canonicalAbsolute);
|
|
5958
6083
|
if (buffer.includes(0)) return "";
|
|
5959
6084
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
|
5960
6085
|
return text.slice(0, MAX_VECTOR_PROFILE_CHARS);
|
|
@@ -5988,6 +6113,7 @@ async function buildVectorImpactOverlay(root, index, changedPaths, options = {})
|
|
|
5988
6113
|
const threshold = semantic.threshold ?? 0.5;
|
|
5989
6114
|
const topK = semantic.topKPerFile ?? 5;
|
|
5990
6115
|
try {
|
|
6116
|
+
if (!options.profile && !options.embed && !options.buildIndex && !resolveEmbeddingProfile(root, options).profile) return { status: "disabled", edges: [], diagnostics: { enabled: false, reason: "Embedding is not configured." } };
|
|
5991
6117
|
const vectorIndex = await (options.buildIndex ?? buildVectorIndex)(root, options);
|
|
5992
6118
|
const edges = [];
|
|
5993
6119
|
const normalizedChangedPaths = [...new Set(changedPaths.map((path) => canonicalizeChangedPath(root, path)).filter(Boolean))];
|
|
@@ -5996,7 +6122,7 @@ async function buildVectorImpactOverlay(root, index, changedPaths, options = {})
|
|
|
5996
6122
|
if (!profile) continue;
|
|
5997
6123
|
const embed = vectorIndex.embedding?.embed ?? options.embed;
|
|
5998
6124
|
if (!embed) throw new Error("Vector index did not provide an embedder.");
|
|
5999
|
-
const [queryVector] = await embed([
|
|
6125
|
+
const [queryVector] = await embed([profile.text], "query");
|
|
6000
6126
|
if (!queryVector) throw new Error("Changed-file embedding was empty.");
|
|
6001
6127
|
const matches = rankVectorFiles(queryVector, vectorIndex).filter((match) => match.path !== changed && match.vectorScore >= threshold).slice(0, topK);
|
|
6002
6128
|
for (const match of matches) {
|
|
@@ -6174,7 +6300,7 @@ function buildCompactImpactReport(impact, limits = {}) {
|
|
|
6174
6300
|
}
|
|
6175
6301
|
|
|
6176
6302
|
// packages/cli/src/reference/extract.mjs
|
|
6177
|
-
import { basename as basename8, dirname as
|
|
6303
|
+
import { basename as basename8, dirname as dirname12, extname as extname9 } from "node:path";
|
|
6178
6304
|
var DEFAULT_REFERENCE_LIMIT = 5;
|
|
6179
6305
|
function extractBracketReferences(prompt = "") {
|
|
6180
6306
|
const refs = [];
|
|
@@ -6225,7 +6351,7 @@ function aliasEntriesForPath(path = "") {
|
|
|
6225
6351
|
const parts = withoutMd.split("/").filter(Boolean);
|
|
6226
6352
|
for (let i = 0; i < parts.length; i += 1) entries.push({ alias: parts.slice(i).join("/"), kind: "path-suffix" });
|
|
6227
6353
|
if (base === "README.md") {
|
|
6228
|
-
const dir =
|
|
6354
|
+
const dir = dirname12(path).replace(/\\/g, "/");
|
|
6229
6355
|
entries.push({ alias: basename8(dir), kind: "path" });
|
|
6230
6356
|
}
|
|
6231
6357
|
for (const alias of [path, withoutMd, base, stem]) entries.push({ alias, kind: "path" });
|
|
@@ -6355,7 +6481,7 @@ function parseReferenceOptions(argv, command) {
|
|
|
6355
6481
|
else filtered.push(arg);
|
|
6356
6482
|
}
|
|
6357
6483
|
const operands = filtered.filter((arg) => !arg.startsWith("-"));
|
|
6358
|
-
return { ...options, root:
|
|
6484
|
+
return { ...options, root: resolve17(operands[0] ?? "."), json: filtered.includes("--json"), rootArgv: operands.slice(1) };
|
|
6359
6485
|
}
|
|
6360
6486
|
function formatReferenceOutput(payload) {
|
|
6361
6487
|
const refreshNote = payload.metadata.cacheRefreshed ? ", refreshed" : "";
|
|
@@ -6404,13 +6530,13 @@ function runExpand(argv) {
|
|
|
6404
6530
|
}
|
|
6405
6531
|
|
|
6406
6532
|
// packages/cli/src/commands/traceability.mjs
|
|
6407
|
-
import { existsSync as
|
|
6408
|
-
import { join as join18, resolve as
|
|
6533
|
+
import { existsSync as existsSync19, readFileSync as readFileSync19, readdirSync as readdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
6534
|
+
import { join as join18, resolve as resolve18 } from "node:path";
|
|
6409
6535
|
function collectDocsMarkdownFiles(root) {
|
|
6410
6536
|
const docs = join18(root, "docs");
|
|
6411
6537
|
const files = [];
|
|
6412
6538
|
const walk = (dir) => {
|
|
6413
|
-
if (!
|
|
6539
|
+
if (!existsSync19(dir)) return;
|
|
6414
6540
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
6415
6541
|
const path = join18(dir, entry.name);
|
|
6416
6542
|
if (entry.isDirectory()) walk(path);
|
|
@@ -6437,7 +6563,7 @@ function parseTraceabilityOptions(argv) {
|
|
|
6437
6563
|
}
|
|
6438
6564
|
if (options.check && options.write) usage("Choose only one traceability links mode: --check or --write.", "traceability links");
|
|
6439
6565
|
options.check = options.check || !options.write;
|
|
6440
|
-
options.root =
|
|
6566
|
+
options.root = resolve18(options.root);
|
|
6441
6567
|
return options;
|
|
6442
6568
|
}
|
|
6443
6569
|
function runTraceability(argv) {
|
|
@@ -6448,7 +6574,7 @@ function runTraceability(argv) {
|
|
|
6448
6574
|
const errors = [...config.errors ?? []];
|
|
6449
6575
|
for (const file of files) {
|
|
6450
6576
|
if (options.write && errors.length > 0) break;
|
|
6451
|
-
const content =
|
|
6577
|
+
const content = readFileSync19(file, "utf8");
|
|
6452
6578
|
for (const error of validateTraceabilityLinksRegion(content, options.root, file)) errors.push(error);
|
|
6453
6579
|
const blocks = extractDotdotgodTraceabilityBlocks(content).filter((block) => !block.error);
|
|
6454
6580
|
if (blocks.length === 0) continue;
|
|
@@ -6457,7 +6583,7 @@ function runTraceability(argv) {
|
|
|
6457
6583
|
for (const error of result.errors ?? []) errors.push(error);
|
|
6458
6584
|
continue;
|
|
6459
6585
|
}
|
|
6460
|
-
if (options.write && result.changed)
|
|
6586
|
+
if (options.write && result.changed) writeFileSync8(file, result.content);
|
|
6461
6587
|
if (result.changed) results.push({ file: rel(options.root, file), changed: true });
|
|
6462
6588
|
}
|
|
6463
6589
|
const ok = errors.length === 0 && (!options.check || results.length === 0);
|
|
@@ -6478,8 +6604,8 @@ function runTraceability(argv) {
|
|
|
6478
6604
|
|
|
6479
6605
|
// packages/cli/src/graph-view/server.mjs
|
|
6480
6606
|
import { createServer } from "node:http";
|
|
6481
|
-
import { readFileSync as
|
|
6482
|
-
import { dirname as
|
|
6607
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
6608
|
+
import { dirname as dirname13, extname as extname10, join as join19 } from "node:path";
|
|
6483
6609
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6484
6610
|
|
|
6485
6611
|
// packages/cli/src/graph-view/payload.mjs
|
|
@@ -6591,7 +6717,7 @@ function buildImpactGraphPayload(index, impact) {
|
|
|
6591
6717
|
}
|
|
6592
6718
|
|
|
6593
6719
|
// packages/cli/src/graph-view/server.mjs
|
|
6594
|
-
var ASSET_ROOT = join19(
|
|
6720
|
+
var ASSET_ROOT = join19(dirname13(fileURLToPath2(import.meta.url)), "assets");
|
|
6595
6721
|
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" };
|
|
6596
6722
|
var VENDOR_FILES = {
|
|
6597
6723
|
"/vendor/sigma.js": new URL("../../node_modules/sigma/dist/sigma.min.js", import.meta.url),
|
|
@@ -6604,7 +6730,7 @@ function json(response, status, value) {
|
|
|
6604
6730
|
}
|
|
6605
6731
|
function asset(response, file) {
|
|
6606
6732
|
try {
|
|
6607
|
-
const body =
|
|
6733
|
+
const body = readFileSync20(file);
|
|
6608
6734
|
response.writeHead(200, { "content-type": CONTENT_TYPES[extname10(file)] ?? "application/octet-stream", "cache-control": "no-store" });
|
|
6609
6735
|
response.end(body);
|
|
6610
6736
|
} catch {
|
|
@@ -6641,16 +6767,16 @@ async function startImpactGraphServer({ root, changed, host = "127.0.0.1", port
|
|
|
6641
6767
|
}
|
|
6642
6768
|
}
|
|
6643
6769
|
if (VENDOR_FILES[url.pathname]) return asset(response, fileURLToPath2(VENDOR_FILES[url.pathname]));
|
|
6644
|
-
const
|
|
6645
|
-
if (!["index.html", "app.css", "app.js"].includes(
|
|
6770
|
+
const relative8 = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
6771
|
+
if (!["index.html", "app.css", "app.js"].includes(relative8)) {
|
|
6646
6772
|
response.writeHead(404);
|
|
6647
6773
|
return response.end("Not found");
|
|
6648
6774
|
}
|
|
6649
|
-
return asset(response, join19(ASSET_ROOT,
|
|
6775
|
+
return asset(response, join19(ASSET_ROOT, relative8));
|
|
6650
6776
|
});
|
|
6651
|
-
await new Promise((
|
|
6777
|
+
await new Promise((resolve19, reject) => {
|
|
6652
6778
|
server.once("error", reject);
|
|
6653
|
-
server.listen(port, host,
|
|
6779
|
+
server.listen(port, host, resolve19);
|
|
6654
6780
|
});
|
|
6655
6781
|
const address = server.address();
|
|
6656
6782
|
const actualPort = typeof address === "object" && address ? address.port : port;
|