@dotdotgod/claude-code 0.3.2 → 0.3.3
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 +3 -1
- package/hooks/runtime.mjs +41 -17
- package/mcp/cli.mjs +152 -35
- package/mcp/server.mjs +55 -9
- package/package.json +2 -5
package/README.md
CHANGED
|
@@ -34,6 +34,8 @@ Then use the bundled `/dd:*` commands in your repository:
|
|
|
34
34
|
/dd:impact
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
After an update, restart Claude Code and confirm the active plugin points to the new installed cache entry. Source changes do not rewrite an existing `~/.claude/plugins/cache/.../<hash>/`; reinstall the plugin through the supported flow if Claude continues using an older cache hash.
|
|
38
|
+
|
|
37
39
|
For local development from a source checkout, load the plugin directory directly instead of installing:
|
|
38
40
|
|
|
39
41
|
```bash
|
|
@@ -45,7 +47,7 @@ claude --plugin-dir /path/to/dotdotgod/packages/claude-code
|
|
|
45
47
|
| Command or skill | Use it for | Result |
|
|
46
48
|
| --- | --- | --- |
|
|
47
49
|
| `/dd:init` | Start a repository with dotdotgod conventions. | Creates or normalizes shared agent files, docs indexes, local-memory areas, and the complete default project config. |
|
|
48
|
-
| `/dd:load` | Load project memory without changing maintained project files. | Renders the shared Markdown tree and uses `dotdotgod query` when focus text is provided;
|
|
50
|
+
| `/dd:load` | Load project memory without changing maintained project files. | Renders the shared Markdown tree and uses `dotdotgod query` when focus text is provided; unavailable optional semantic routing falls back to the map with bounded evidence. |
|
|
49
51
|
| `/dd:plan` | Plan before implementation. | Writes or updates durable task intent in `docs/plan/<task-slug>/README.md`. |
|
|
50
52
|
| `/dd:impact` | Review changed files before verification or handoff. | Uses `dotdotgod graph impact` to identify likely related docs, tests, commands, and source files. |
|
|
51
53
|
| `document-clarify` | Improve docs wording without changing behavior contracts. | Clarifies README/spec/test/arch/plan/archive docs using memory-area roles. |
|
package/hooks/runtime.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// packages/context/src/hooks.mjs
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
-
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { dirname, join, parse, resolve } from "node:path";
|
|
8
8
|
function inputText() {
|
|
9
9
|
return readFileSync(0, "utf8");
|
|
10
10
|
}
|
|
@@ -12,24 +12,45 @@ function parseInput() {
|
|
|
12
12
|
const text = inputText().trim();
|
|
13
13
|
return text ? JSON.parse(text) : {};
|
|
14
14
|
}
|
|
15
|
-
function projectRoot(input) {
|
|
16
|
-
return resolve(input.cwd || input.project_dir || process.env.CLAUDE_PROJECT_DIR || process.cwd());
|
|
17
|
-
}
|
|
18
15
|
function sessionId(input) {
|
|
19
16
|
return String(input.session_id || input.sessionId || "default").replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
20
17
|
}
|
|
21
|
-
function
|
|
22
|
-
return join(
|
|
18
|
+
function runtimeStatePath(root, id) {
|
|
19
|
+
return join(root, ".dotdotgod", "context", "runtime", `${id}.json`);
|
|
20
|
+
}
|
|
21
|
+
function declaredProjectRoot(input) {
|
|
22
|
+
const value = input.project_dir || process.env.CLAUDE_PROJECT_DIR;
|
|
23
|
+
return typeof value === "string" && value.trim() ? resolve(value) : null;
|
|
24
|
+
}
|
|
25
|
+
function findExistingStateRoot(cwd, id) {
|
|
26
|
+
let current = resolve(cwd);
|
|
27
|
+
const filesystemRoot = parse(current).root;
|
|
28
|
+
while (true) {
|
|
29
|
+
if (existsSync(runtimeStatePath(current, id))) return current;
|
|
30
|
+
if (current === filesystemRoot) return null;
|
|
31
|
+
current = dirname(current);
|
|
32
|
+
}
|
|
23
33
|
}
|
|
24
|
-
function
|
|
34
|
+
function resolveHookContext(input, event) {
|
|
35
|
+
const id = sessionId(input);
|
|
36
|
+
const declared = declaredProjectRoot(input);
|
|
37
|
+
if (declared) return { root: declared, path: runtimeStatePath(declared, id) };
|
|
38
|
+
if (typeof input.cwd !== "string" || !input.cwd.trim()) return null;
|
|
39
|
+
const existing = findExistingStateRoot(input.cwd, id);
|
|
40
|
+
if (existing) return { root: existing, path: runtimeStatePath(existing, id) };
|
|
41
|
+
if (event !== "sessionstart") return null;
|
|
42
|
+
const root = resolve(input.cwd);
|
|
43
|
+
return { root, path: runtimeStatePath(root, id) };
|
|
44
|
+
}
|
|
45
|
+
function readState(path) {
|
|
25
46
|
try {
|
|
26
|
-
return JSON.parse(readFileSync(
|
|
47
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
27
48
|
} catch {
|
|
28
49
|
return { loadRequired: false, pending: {} };
|
|
29
50
|
}
|
|
30
51
|
}
|
|
31
|
-
function writeState(
|
|
32
|
-
|
|
52
|
+
function writeState(path, state) {
|
|
53
|
+
if (!path) return;
|
|
33
54
|
mkdirSync(dirname(path), { recursive: true });
|
|
34
55
|
const temp = `${path}.${process.pid}.tmp`;
|
|
35
56
|
writeFileSync(temp, `${JSON.stringify(state, null, 2)}
|
|
@@ -66,19 +87,21 @@ function isImpactGate(name, input) {
|
|
|
66
87
|
if (!/^(Bash|Shell|local_shell|shell|shell_command|exec_command)$/i.test(name)) return false;
|
|
67
88
|
return /(^|\s)(test|build|lint|verify|commit|push|publish|deploy)(\s|:|$)|\b(git\s+(commit|push)|npm\s+(test|publish)|pnpm\s+[^\n]*(test|build|lint|verify|publish))\b/i.test(commandFrom(input));
|
|
68
89
|
}
|
|
69
|
-
function changedPath(input) {
|
|
90
|
+
function changedPath(input, root) {
|
|
70
91
|
const value = toolInput(input);
|
|
71
92
|
const path = value.file_path || value.path;
|
|
72
|
-
return typeof path === "string" ? resolve(
|
|
93
|
+
return root && typeof path === "string" ? resolve(root, path) : null;
|
|
73
94
|
}
|
|
74
95
|
function runHook(event) {
|
|
75
96
|
const input = parseInput();
|
|
76
97
|
const name = toolName(input);
|
|
77
|
-
const
|
|
98
|
+
const context = resolveHookContext(input, event);
|
|
99
|
+
const state = readState(context?.path);
|
|
78
100
|
state.pending ||= {};
|
|
79
101
|
if (event === "sessionstart") {
|
|
102
|
+
if (!context) return {};
|
|
80
103
|
state.loadRequired = true;
|
|
81
|
-
writeState(
|
|
104
|
+
writeState(context.path, state);
|
|
82
105
|
return { hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: "Dotdotgod project memory has not been loaded for this session. Before substantive shell or write work, call dotdotgod_project_load once with a concise task-specific focus." } };
|
|
83
106
|
}
|
|
84
107
|
if (event === "pretooluse") {
|
|
@@ -89,18 +112,19 @@ function runHook(event) {
|
|
|
89
112
|
return {};
|
|
90
113
|
}
|
|
91
114
|
if (event === "posttooluse") {
|
|
115
|
+
if (!context) return {};
|
|
92
116
|
if (mcpTool(name, "dotdotgod_project_load")) state.loadRequired = false;
|
|
93
117
|
else if (mcpTool(name, "dotdotgod_project_impact")) {
|
|
94
118
|
const checked = toolInput(input).paths;
|
|
95
119
|
if (Array.isArray(checked)) for (const path of checked) {
|
|
96
|
-
const absolute = resolve(
|
|
120
|
+
const absolute = resolve(context.root, path);
|
|
97
121
|
if (state.pending[absolute] === fileHash(absolute)) delete state.pending[absolute];
|
|
98
122
|
}
|
|
99
123
|
} else if (/^(Edit|Write|apply_patch)$/i.test(name)) {
|
|
100
|
-
const path = changedPath(input);
|
|
124
|
+
const path = changedPath(input, context.root);
|
|
101
125
|
if (path) state.pending[path] = fileHash(path);
|
|
102
126
|
}
|
|
103
|
-
writeState(
|
|
127
|
+
writeState(context.path, state);
|
|
104
128
|
return {};
|
|
105
129
|
}
|
|
106
130
|
return {};
|
package/mcp/cli.mjs
CHANGED
|
@@ -1309,6 +1309,20 @@ Create dotdotgod.config.json from a bundled or user template. Without --template
|
|
|
1309
1309
|
dotdotgod query <root> <query> [--limit n] [--json]
|
|
1310
1310
|
|
|
1311
1311
|
Search shared project documentation with the resolved local or remote embedding provider.`;
|
|
1312
|
+
case "embedding":
|
|
1313
|
+
return `Usage:
|
|
1314
|
+
dotdotgod embedding status [<root>] [--json]
|
|
1315
|
+
dotdotgod embedding install [<root>] --confirm [--json]`;
|
|
1316
|
+
case "embedding status":
|
|
1317
|
+
return `Usage:
|
|
1318
|
+
dotdotgod embedding status [<root>] [--json]
|
|
1319
|
+
|
|
1320
|
+
Inspect the optional persistent local embedding runtime without network access.`;
|
|
1321
|
+
case "embedding install":
|
|
1322
|
+
return `Usage:
|
|
1323
|
+
dotdotgod embedding install [<root>] --confirm [--json]
|
|
1324
|
+
|
|
1325
|
+
After user approval, install the fixed optional runtime persistently. Uses network access and dependency install scripts; the model may download on first query.`;
|
|
1312
1326
|
case "map":
|
|
1313
1327
|
return `Usage:
|
|
1314
1328
|
dotdotgod map <root> [--depth <positive-integer>] [--json]
|
|
@@ -1351,6 +1365,7 @@ Commands:
|
|
|
1351
1365
|
init Initialize project memory files.
|
|
1352
1366
|
config Inspect or initialize project configuration.
|
|
1353
1367
|
query Search project documentation.
|
|
1368
|
+
embedding Inspect or install the optional local embedding runtime.
|
|
1354
1369
|
map Render the project documentation map.
|
|
1355
1370
|
resolve Resolve a project reference.
|
|
1356
1371
|
expand Expand references in a prompt.
|
|
@@ -1403,6 +1418,7 @@ function helpCommandFromArgs(args) {
|
|
|
1403
1418
|
if (nonHelp[0] === "graph" && nonHelp[1]) return `graph ${nonHelp[1]}`;
|
|
1404
1419
|
if (nonHelp[0] === "traceability" && nonHelp[1]) return `traceability ${nonHelp[1]}`;
|
|
1405
1420
|
if (nonHelp[0] === "config" && nonHelp[1] === "init") return "config init";
|
|
1421
|
+
if (nonHelp[0] === "embedding" && nonHelp[1]) return `embedding ${nonHelp[1]}`;
|
|
1406
1422
|
return nonHelp[0] ?? "root";
|
|
1407
1423
|
}
|
|
1408
1424
|
function parseCommon(argv) {
|
|
@@ -2709,6 +2725,74 @@ function collectDocumentationChunks(root, exclude, documentationRoot = "docs") {
|
|
|
2709
2725
|
});
|
|
2710
2726
|
}
|
|
2711
2727
|
|
|
2728
|
+
// packages/cli/src/query/embedding-runtime.mjs
|
|
2729
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync11, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2730
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2731
|
+
import { createRequire } from "node:module";
|
|
2732
|
+
import { homedir as homedir3 } from "node:os";
|
|
2733
|
+
import { join as join11 } from "node:path";
|
|
2734
|
+
import { pathToFileURL } from "node:url";
|
|
2735
|
+
var EMBEDDING_RUNTIME_PACKAGE = "@huggingface/transformers";
|
|
2736
|
+
var EMBEDDING_RUNTIME_VERSION = "4.2.0";
|
|
2737
|
+
var EmbeddingRuntimeMissingError = class extends Error {
|
|
2738
|
+
constructor() {
|
|
2739
|
+
super("Optional local embedding runtime is not installed.");
|
|
2740
|
+
this.name = "EmbeddingRuntimeMissingError";
|
|
2741
|
+
this.code = "EMBEDDING_RUNTIME_MISSING";
|
|
2742
|
+
}
|
|
2743
|
+
};
|
|
2744
|
+
function embeddingRuntimeRoot(options = {}) {
|
|
2745
|
+
return join11(options.home ?? homedir3(), ".dotdotgod", "runtime", "embedding");
|
|
2746
|
+
}
|
|
2747
|
+
function resolvePersistentTransformers(options = {}) {
|
|
2748
|
+
const root = embeddingRuntimeRoot(options);
|
|
2749
|
+
try {
|
|
2750
|
+
const require2 = createRequire(join11(root, "package.json"));
|
|
2751
|
+
return require2.resolve(EMBEDDING_RUNTIME_PACKAGE);
|
|
2752
|
+
} catch {
|
|
2753
|
+
throw new EmbeddingRuntimeMissingError();
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
function embeddingRuntimeStatus(options = {}) {
|
|
2757
|
+
const root = embeddingRuntimeRoot(options);
|
|
2758
|
+
try {
|
|
2759
|
+
const require2 = createRequire(join11(root, "package.json"));
|
|
2760
|
+
const packagePath = require2.resolve(`${EMBEDDING_RUNTIME_PACKAGE}/package.json`);
|
|
2761
|
+
const version = JSON.parse(readFileSync11(packagePath, "utf8")).version;
|
|
2762
|
+
return { ok: true, installed: true, installAvailable: true, package: EMBEDDING_RUNTIME_PACKAGE, requiredVersion: EMBEDDING_RUNTIME_VERSION, packageVersion: version, location: root };
|
|
2763
|
+
} catch {
|
|
2764
|
+
return { ok: true, installed: false, installAvailable: true, package: EMBEDDING_RUNTIME_PACKAGE, requiredVersion: EMBEDDING_RUNTIME_VERSION, packageVersion: null, location: root };
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
function installEmbeddingRuntime(options = {}) {
|
|
2768
|
+
if (options.confirm !== true) throw new Error("Embedding runtime installation requires explicit confirmation.");
|
|
2769
|
+
const root = embeddingRuntimeRoot(options);
|
|
2770
|
+
const lock = join11(root, ".installing");
|
|
2771
|
+
if (existsSync12(lock)) throw new Error("Embedding runtime installation is already in progress.");
|
|
2772
|
+
mkdirSync3(root, { recursive: true });
|
|
2773
|
+
writeFileSync4(lock, `${process.pid}
|
|
2774
|
+
`, { flag: "wx" });
|
|
2775
|
+
try {
|
|
2776
|
+
const spawn = options.spawnImpl ?? spawnSync2;
|
|
2777
|
+
const result = spawn(options.npmCommand ?? "npm", ["install", "--prefix", root, "--save-exact", "--omit=dev", "--no-audit", "--no-fund", `${EMBEDDING_RUNTIME_PACKAGE}@${EMBEDDING_RUNTIME_VERSION}`], { encoding: "utf8", timeout: options.timeoutMs ?? 3e5, shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
2778
|
+
if (result.error || result.status !== 0) throw new Error(result.error?.code === "ENOENT" ? "npm is unavailable." : "Embedding runtime installation failed.");
|
|
2779
|
+
const status = embeddingRuntimeStatus({ home: options.home });
|
|
2780
|
+
if (!status.installed) throw new Error("Embedding runtime installation could not be verified.");
|
|
2781
|
+
return { ...status, command: "embedding install", modelDownloadPending: true };
|
|
2782
|
+
} finally {
|
|
2783
|
+
rmSync(lock, { force: true });
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
async function importTransformers(options = {}) {
|
|
2787
|
+
try {
|
|
2788
|
+
return await import(EMBEDDING_RUNTIME_PACKAGE);
|
|
2789
|
+
} catch (error) {
|
|
2790
|
+
if (error?.code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
2791
|
+
const path = resolvePersistentTransformers(options);
|
|
2792
|
+
return import(pathToFileURL(path).href);
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2712
2796
|
// packages/cli/src/query/embedder.mjs
|
|
2713
2797
|
var localPipelines = /* @__PURE__ */ new Map();
|
|
2714
2798
|
function normalize(row) {
|
|
@@ -2719,7 +2803,7 @@ function normalize(row) {
|
|
|
2719
2803
|
}
|
|
2720
2804
|
async function localEmbedder(profile) {
|
|
2721
2805
|
if (!localPipelines.has(profile.model)) {
|
|
2722
|
-
localPipelines.set(profile.model,
|
|
2806
|
+
localPipelines.set(profile.model, importTransformers().then(async ({ env, pipeline }) => {
|
|
2723
2807
|
env.allowLocalModels = true;
|
|
2724
2808
|
return pipeline("feature-extraction", profile.model);
|
|
2725
2809
|
}));
|
|
@@ -2787,8 +2871,8 @@ async function resolveEmbedder(root, options = {}) {
|
|
|
2787
2871
|
|
|
2788
2872
|
// packages/cli/src/query/store.mjs
|
|
2789
2873
|
import { createHash as createHash4 } from "node:crypto";
|
|
2790
|
-
import { existsSync as
|
|
2791
|
-
import { join as
|
|
2874
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync12, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2875
|
+
import { join as join12 } from "node:path";
|
|
2792
2876
|
var VECTOR_SCHEMA_VERSION = 2;
|
|
2793
2877
|
var VECTOR_DIMENSIONS = 384;
|
|
2794
2878
|
function profileFingerprint(identity) {
|
|
@@ -2808,18 +2892,18 @@ function isValidNormalizedVectorValues(values, dimensions = VECTOR_DIMENSIONS) {
|
|
|
2808
2892
|
return true;
|
|
2809
2893
|
}
|
|
2810
2894
|
function vectorCachePaths(root) {
|
|
2811
|
-
const directory =
|
|
2812
|
-
return { directory, manifest:
|
|
2895
|
+
const directory = join12(root, ".dotdotgod", "vectors");
|
|
2896
|
+
return { directory, manifest: join12(directory, "manifest.json"), chunks: join12(directory, "chunks.jsonl"), embeddings: join12(directory, "embeddings.f32") };
|
|
2813
2897
|
}
|
|
2814
2898
|
function readVectorCache(root, identity = null) {
|
|
2815
2899
|
const paths = vectorCachePaths(root);
|
|
2816
|
-
if (!
|
|
2900
|
+
if (!existsSync13(paths.manifest) || !existsSync13(paths.chunks) || !existsSync13(paths.embeddings)) return null;
|
|
2817
2901
|
try {
|
|
2818
|
-
const manifest = JSON.parse(
|
|
2902
|
+
const manifest = JSON.parse(readFileSync12(paths.manifest, "utf8"));
|
|
2819
2903
|
if (manifest.schemaVersion !== VECTOR_SCHEMA_VERSION || !Number.isInteger(manifest.dimensions) || manifest.dimensions < 1) return null;
|
|
2820
2904
|
if (identity && manifest.profileFingerprint !== profileFingerprint(identity)) return null;
|
|
2821
|
-
const chunks =
|
|
2822
|
-
const buffer =
|
|
2905
|
+
const chunks = readFileSync12(paths.chunks, "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
2906
|
+
const buffer = readFileSync12(paths.embeddings);
|
|
2823
2907
|
if (buffer.byteLength !== chunks.length * manifest.dimensions * 4) return null;
|
|
2824
2908
|
const vectors = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4);
|
|
2825
2909
|
if (!isValidNormalizedVectorValues(vectors, manifest.dimensions)) return null;
|
|
@@ -2830,14 +2914,14 @@ function readVectorCache(root, identity = null) {
|
|
|
2830
2914
|
}
|
|
2831
2915
|
function atomicWrite(path, data) {
|
|
2832
2916
|
const temporary = `${path}.tmp-${process.pid}`;
|
|
2833
|
-
|
|
2917
|
+
writeFileSync5(temporary, data);
|
|
2834
2918
|
renameSync(temporary, path);
|
|
2835
2919
|
}
|
|
2836
2920
|
function writeVectorCache(root, chunks, vectors, metadata = {}) {
|
|
2837
2921
|
const paths = vectorCachePaths(root);
|
|
2838
2922
|
const dimensions = metadata.dimensions;
|
|
2839
2923
|
if (!Number.isInteger(dimensions) || dimensions < 1) throw new Error("Vector dimensions must be a positive integer.");
|
|
2840
|
-
|
|
2924
|
+
mkdirSync4(paths.directory, { recursive: true });
|
|
2841
2925
|
if (vectors.length !== chunks.length * dimensions) throw new Error("Vector count does not match chunk metadata.");
|
|
2842
2926
|
if (!isValidNormalizedVectorValues(vectors, dimensions)) throw new Error("Vectors must contain finite normalized values.");
|
|
2843
2927
|
const manifest = { schemaVersion: VECTOR_SCHEMA_VERSION, chunks: chunks.length, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...metadata };
|
|
@@ -2976,12 +3060,44 @@ async function runQuery(argv) {
|
|
|
2976
3060
|
}
|
|
2977
3061
|
}
|
|
2978
3062
|
|
|
3063
|
+
// packages/cli/src/commands/embedding.mjs
|
|
3064
|
+
import { resolve as resolve10 } from "node:path";
|
|
3065
|
+
function parse(argv) {
|
|
3066
|
+
const [action2 = "status", ...rest] = argv;
|
|
3067
|
+
let root = ".";
|
|
3068
|
+
let json = false;
|
|
3069
|
+
let confirm = false;
|
|
3070
|
+
for (const arg of rest) {
|
|
3071
|
+
if (arg === "--json") json = true;
|
|
3072
|
+
else if (arg === "--confirm") confirm = true;
|
|
3073
|
+
else if (!arg.startsWith("-") && root === ".") root = arg;
|
|
3074
|
+
else usage(`Unknown embedding option: ${arg}`, `embedding ${action2}`);
|
|
3075
|
+
}
|
|
3076
|
+
return { action: action2, root: resolve10(root), json, confirm };
|
|
3077
|
+
}
|
|
3078
|
+
function runEmbedding(argv, options = {}) {
|
|
3079
|
+
const parsed = parse(argv);
|
|
3080
|
+
if (!["status", "install"].includes(parsed.action)) usage(`Unknown embedding command: ${parsed.action}`, "embedding");
|
|
3081
|
+
const resolved = resolveEmbeddingProfile(parsed.root, options);
|
|
3082
|
+
if (resolved.profile.provider !== "local") {
|
|
3083
|
+
const result2 = { ok: true, command: `embedding ${parsed.action}`, provider: resolved.profile.provider, required: false, installed: false, installAvailable: false };
|
|
3084
|
+
console.log(parsed.json ? JSON.stringify(result2, null, 2) : "Local embedding runtime is not required for the configured remote provider.");
|
|
3085
|
+
return result2;
|
|
3086
|
+
}
|
|
3087
|
+
if (parsed.action === "install" && !parsed.confirm) usage("embedding install requires --confirm after user approval.", "embedding install");
|
|
3088
|
+
const runtimeOptions = { home: options.home };
|
|
3089
|
+
const result = parsed.action === "status" ? embeddingRuntimeStatus(runtimeOptions) : installEmbeddingRuntime({ ...runtimeOptions, confirm: true, spawnImpl: options.spawnImpl, npmCommand: options.npmCommand });
|
|
3090
|
+
const payload = { ...result, command: `embedding ${parsed.action}`, provider: "local", required: true, ...parsed.action === "install" ? { disclosure: "Installation uses network access and dependency install scripts; the configured model may download on first query." } : {} };
|
|
3091
|
+
console.log(parsed.json ? JSON.stringify(payload, null, 2) : payload.installed ? `Embedding runtime installed (${payload.packageVersion}).` : "Embedding runtime is not installed.");
|
|
3092
|
+
return payload;
|
|
3093
|
+
}
|
|
3094
|
+
|
|
2979
3095
|
// packages/cli/src/commands/map.mjs
|
|
2980
|
-
import { resolve as
|
|
3096
|
+
import { resolve as resolve12 } from "node:path";
|
|
2981
3097
|
|
|
2982
3098
|
// packages/cli/src/memory/documentation-map.mjs
|
|
2983
|
-
import { existsSync as
|
|
2984
|
-
import { join as
|
|
3099
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4, statSync as statSync6 } from "node:fs";
|
|
3100
|
+
import { join as join13, resolve as resolve11 } from "node:path";
|
|
2985
3101
|
var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".dotdotgod"]);
|
|
2986
3102
|
function node() {
|
|
2987
3103
|
return { directories: /* @__PURE__ */ new Map(), files: [] };
|
|
@@ -3044,7 +3160,7 @@ function discoverMarkdown(root, documentationRoot, exclude) {
|
|
|
3044
3160
|
const walk = (relativeDirectory) => {
|
|
3045
3161
|
let entries;
|
|
3046
3162
|
try {
|
|
3047
|
-
entries = readdirSync4(
|
|
3163
|
+
entries = readdirSync4(join13(root, relativeDirectory), { withFileTypes: true, encoding: "utf8" });
|
|
3048
3164
|
} catch {
|
|
3049
3165
|
return;
|
|
3050
3166
|
}
|
|
@@ -3061,8 +3177,8 @@ function discoverMarkdown(root, documentationRoot, exclude) {
|
|
|
3061
3177
|
return paths.sort();
|
|
3062
3178
|
}
|
|
3063
3179
|
function buildDocumentationMap(projectRoot = ".", { depth = 5 } = {}) {
|
|
3064
|
-
const root =
|
|
3065
|
-
if (!
|
|
3180
|
+
const root = resolve11(projectRoot);
|
|
3181
|
+
if (!existsSync14(root)) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
|
|
3066
3182
|
try {
|
|
3067
3183
|
if (!statSync6(root).isDirectory()) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
|
|
3068
3184
|
} catch {
|
|
@@ -3110,7 +3226,7 @@ function parseMapOptions(argv) {
|
|
|
3110
3226
|
} else if (!arg.startsWith("-")) fail(options.json, "UNEXPECTED_ARGUMENT", `Unexpected argument: ${arg}`);
|
|
3111
3227
|
else fail(options.json, "UNKNOWN_OPTION", `Unknown option: ${arg}`);
|
|
3112
3228
|
}
|
|
3113
|
-
options.root =
|
|
3229
|
+
options.root = resolve12(options.root);
|
|
3114
3230
|
return options;
|
|
3115
3231
|
}
|
|
3116
3232
|
function runMap(argv) {
|
|
@@ -3122,7 +3238,7 @@ function runMap(argv) {
|
|
|
3122
3238
|
}
|
|
3123
3239
|
|
|
3124
3240
|
// packages/cli/src/reference/resolve.mjs
|
|
3125
|
-
import { resolve as
|
|
3241
|
+
import { resolve as resolve14 } from "node:path";
|
|
3126
3242
|
|
|
3127
3243
|
// node_modules/.pnpm/leiden-ts@0.1.0/node_modules/leiden-ts/dist/index.js
|
|
3128
3244
|
var GraphValidationError = class extends Error {
|
|
@@ -4493,15 +4609,15 @@ function compareImpactItems(seeds) {
|
|
|
4493
4609
|
}
|
|
4494
4610
|
|
|
4495
4611
|
// packages/cli/src/impact/vector-profile.mjs
|
|
4496
|
-
import { lstatSync, readFileSync as
|
|
4497
|
-
import { extname as extname5, relative as relative4, resolve as
|
|
4612
|
+
import { lstatSync, readFileSync as readFileSync13, realpathSync } from "node:fs";
|
|
4613
|
+
import { extname as extname5, relative as relative4, resolve as resolve13, sep } from "node:path";
|
|
4498
4614
|
var MAX_VECTOR_PROFILE_FILE_BYTES = 64 * 1024;
|
|
4499
4615
|
var MAX_VECTOR_PROFILE_CHARS = 4e3;
|
|
4500
4616
|
var MAX_VECTOR_PROFILE_METADATA_ITEMS = 20;
|
|
4501
4617
|
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"]);
|
|
4502
4618
|
var GENERATED_SEGMENTS = /* @__PURE__ */ new Set([".dotdotgod", "build", "coverage", "dist", "node_modules"]);
|
|
4503
4619
|
function insideRoot(root, absolute) {
|
|
4504
|
-
const normalizedRoot =
|
|
4620
|
+
const normalizedRoot = resolve13(root);
|
|
4505
4621
|
return absolute === normalizedRoot || absolute.startsWith(`${normalizedRoot}${sep}`);
|
|
4506
4622
|
}
|
|
4507
4623
|
function normalizeChangedPath(path) {
|
|
@@ -4512,8 +4628,8 @@ function normalizeChangedPath(path) {
|
|
|
4512
4628
|
function canonicalizeChangedPath(root, path) {
|
|
4513
4629
|
const normalized = normalizeChangedPath(path);
|
|
4514
4630
|
if (!normalized) return null;
|
|
4515
|
-
const rootAbsolute =
|
|
4516
|
-
const absolute =
|
|
4631
|
+
const rootAbsolute = resolve13(root);
|
|
4632
|
+
const absolute = resolve13(rootAbsolute, normalized);
|
|
4517
4633
|
if (!insideRoot(rootAbsolute, absolute)) return normalized;
|
|
4518
4634
|
try {
|
|
4519
4635
|
const canonicalRoot = realpathSync(rootAbsolute);
|
|
@@ -4544,7 +4660,7 @@ function graphMetadata(graph, seedId) {
|
|
|
4544
4660
|
function safeTextPrefix(root, path) {
|
|
4545
4661
|
if (unsafeProfilePath(path) || !TEXT_EXTENSIONS.has(extname5(path).toLowerCase())) return "";
|
|
4546
4662
|
try {
|
|
4547
|
-
const absolute =
|
|
4663
|
+
const absolute = resolve13(root, path);
|
|
4548
4664
|
if (!insideRoot(root, absolute)) return "";
|
|
4549
4665
|
const canonicalAbsolute = realpathSync(absolute);
|
|
4550
4666
|
if (!insideRoot(realpathSync(root), canonicalAbsolute)) return "";
|
|
@@ -4552,7 +4668,7 @@ function safeTextPrefix(root, path) {
|
|
|
4552
4668
|
if (canonicalPath !== path || unsafeProfilePath(canonicalPath) || !TEXT_EXTENSIONS.has(extname5(canonicalPath).toLowerCase())) return "";
|
|
4553
4669
|
const stats = lstatSync(canonicalAbsolute);
|
|
4554
4670
|
if (!stats.isFile() || stats.isSymbolicLink() || stats.size > MAX_VECTOR_PROFILE_FILE_BYTES) return "";
|
|
4555
|
-
const buffer =
|
|
4671
|
+
const buffer = readFileSync13(canonicalAbsolute);
|
|
4556
4672
|
if (buffer.includes(0)) return "";
|
|
4557
4673
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
|
4558
4674
|
return text.slice(0, MAX_VECTOR_PROFILE_CHARS);
|
|
@@ -4945,7 +5061,7 @@ function parseReferenceOptions(argv, command) {
|
|
|
4945
5061
|
else filtered.push(arg);
|
|
4946
5062
|
}
|
|
4947
5063
|
const operands = filtered.filter((arg) => !arg.startsWith("-"));
|
|
4948
|
-
return { ...options, root:
|
|
5064
|
+
return { ...options, root: resolve14(operands[0] ?? "."), json: filtered.includes("--json"), rootArgv: operands.slice(1) };
|
|
4949
5065
|
}
|
|
4950
5066
|
function formatReferenceOutput(payload) {
|
|
4951
5067
|
const refreshNote = payload.metadata.cacheRefreshed ? ", refreshed" : "";
|
|
@@ -4994,15 +5110,15 @@ function runExpand(argv) {
|
|
|
4994
5110
|
}
|
|
4995
5111
|
|
|
4996
5112
|
// packages/cli/src/commands/traceability.mjs
|
|
4997
|
-
import { existsSync as
|
|
4998
|
-
import { join as
|
|
5113
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
5114
|
+
import { join as join14, resolve as resolve15 } from "node:path";
|
|
4999
5115
|
function collectDocsMarkdownFiles(root) {
|
|
5000
|
-
const docs =
|
|
5116
|
+
const docs = join14(root, "docs");
|
|
5001
5117
|
const files = [];
|
|
5002
5118
|
const walk = (dir) => {
|
|
5003
|
-
if (!
|
|
5119
|
+
if (!existsSync15(dir)) return;
|
|
5004
5120
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
5005
|
-
const path =
|
|
5121
|
+
const path = join14(dir, entry.name);
|
|
5006
5122
|
if (entry.isDirectory()) walk(path);
|
|
5007
5123
|
else if (entry.isFile() && entry.name.endsWith(".md")) files.push(path);
|
|
5008
5124
|
}
|
|
@@ -5027,7 +5143,7 @@ function parseTraceabilityOptions(argv) {
|
|
|
5027
5143
|
}
|
|
5028
5144
|
if (options.check && options.write) usage("Choose only one traceability links mode: --check or --write.", "traceability links");
|
|
5029
5145
|
options.check = options.check || !options.write;
|
|
5030
|
-
options.root =
|
|
5146
|
+
options.root = resolve15(options.root);
|
|
5031
5147
|
return options;
|
|
5032
5148
|
}
|
|
5033
5149
|
function runTraceability(argv) {
|
|
@@ -5038,7 +5154,7 @@ function runTraceability(argv) {
|
|
|
5038
5154
|
const errors = [...config.errors ?? []];
|
|
5039
5155
|
for (const file of files) {
|
|
5040
5156
|
if (options.write && errors.length > 0) break;
|
|
5041
|
-
const content =
|
|
5157
|
+
const content = readFileSync14(file, "utf8");
|
|
5042
5158
|
for (const error of validateTraceabilityLinksRegion(content, options.root, file)) errors.push(error);
|
|
5043
5159
|
const blocks = extractDotdotgodTraceabilityBlocks(content).filter((block) => !block.error);
|
|
5044
5160
|
if (blocks.length === 0) continue;
|
|
@@ -5047,7 +5163,7 @@ function runTraceability(argv) {
|
|
|
5047
5163
|
for (const error of result.errors ?? []) errors.push(error);
|
|
5048
5164
|
continue;
|
|
5049
5165
|
}
|
|
5050
|
-
if (options.write && result.changed)
|
|
5166
|
+
if (options.write && result.changed) writeFileSync6(file, result.content);
|
|
5051
5167
|
if (result.changed) results.push({ file: rel(options.root, file), changed: true });
|
|
5052
5168
|
}
|
|
5053
5169
|
const ok = errors.length === 0 && (!options.check || results.length === 0);
|
|
@@ -5253,6 +5369,7 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
5253
5369
|
else if (command === "config") runConfig(args);
|
|
5254
5370
|
else if (command === "status") runStatus(args);
|
|
5255
5371
|
else if (command === "query") await runQuery(args);
|
|
5372
|
+
else if (command === "embedding") runEmbedding(args);
|
|
5256
5373
|
else if (command === "map") runMap(args);
|
|
5257
5374
|
else if (command === "resolve") runResolve(args);
|
|
5258
5375
|
else if (command === "expand") runExpand(args);
|
package/mcp/server.mjs
CHANGED
|
@@ -23179,10 +23179,37 @@ async function cliJson(args, cwd) {
|
|
|
23179
23179
|
}
|
|
23180
23180
|
async function projectLoad(input) {
|
|
23181
23181
|
const root2 = resolve5(input.root || process.cwd());
|
|
23182
|
-
const
|
|
23182
|
+
const focus = input.focus?.trim() ?? "";
|
|
23183
|
+
const tree = markdownTree(root2, input.maxDepth ?? (focus ? 3 : 5));
|
|
23183
23184
|
let query = null;
|
|
23184
|
-
|
|
23185
|
-
|
|
23185
|
+
let queryUnavailable;
|
|
23186
|
+
if (focus) {
|
|
23187
|
+
try {
|
|
23188
|
+
query = await cliJson(["query", root2, focus, "--limit", String(Math.min(input.limit ?? 30, 30)), "--json"], root2);
|
|
23189
|
+
} catch (error2) {
|
|
23190
|
+
const missingRuntime = String(error2?.message ?? error2).includes("Optional local embedding runtime is not installed");
|
|
23191
|
+
queryUnavailable = missingRuntime ? {
|
|
23192
|
+
code: "EMBEDDING_RUNTIME_MISSING",
|
|
23193
|
+
message: "Local semantic search requires an optional embedding runtime; continue with the documentation map or ask the user before installation.",
|
|
23194
|
+
recovery: { kind: "embedding-runtime-install", requiresConfirmation: true, statusTool: "dotdotgod_embedding_status", installTool: "dotdotgod_embedding_install", cliCommand: "dotdotgod embedding install --confirm" }
|
|
23195
|
+
} : {
|
|
23196
|
+
code: "QUERY_UNAVAILABLE",
|
|
23197
|
+
message: "Semantic project query is unavailable; continue with the documentation map and targeted reads."
|
|
23198
|
+
};
|
|
23199
|
+
}
|
|
23200
|
+
}
|
|
23201
|
+
return { ok: true, root: root2, focus, documentationTree: tree, query, ...queryUnavailable ? { queryUnavailable } : {} };
|
|
23202
|
+
}
|
|
23203
|
+
async function projectEmbeddingStatus(input = {}) {
|
|
23204
|
+
const root2 = resolve5(input.root || process.cwd());
|
|
23205
|
+
const result = await cliJson(["embedding", "status", root2, "--json"], root2);
|
|
23206
|
+
return { ...result, ...result.location ? { location: "~/.dotdotgod/runtime/embedding" } : {} };
|
|
23207
|
+
}
|
|
23208
|
+
async function projectEmbeddingInstall(input = {}) {
|
|
23209
|
+
if (input.confirm !== true) throw new Error("confirm: true is required after explicit user approval.");
|
|
23210
|
+
const root2 = resolve5(input.root || process.cwd());
|
|
23211
|
+
const result = await cliJson(["embedding", "install", root2, "--confirm", "--json"], root2);
|
|
23212
|
+
return { ...result, ...result.location ? { location: "~/.dotdotgod/runtime/embedding" } : {} };
|
|
23186
23213
|
}
|
|
23187
23214
|
async function projectImpact(input) {
|
|
23188
23215
|
const root2 = resolve5(input.root || process.cwd());
|
|
@@ -23216,10 +23243,13 @@ var IngestionJobRunner = class {
|
|
|
23216
23243
|
this.renderer = renderer;
|
|
23217
23244
|
this.running = false;
|
|
23218
23245
|
this.scheduled = false;
|
|
23246
|
+
this.closed = false;
|
|
23247
|
+
this.pumpPromise = void 0;
|
|
23219
23248
|
this.controllers = /* @__PURE__ */ new Map();
|
|
23220
23249
|
this.schedule();
|
|
23221
23250
|
}
|
|
23222
23251
|
enqueue(kind, input) {
|
|
23252
|
+
if (this.closed) throw new Error("Ingestion job runner is closed.");
|
|
23223
23253
|
if (!["index", "fetch"].includes(kind)) throw new Error("Unsupported ingestion job kind.");
|
|
23224
23254
|
const job = this.store.createJob({ kind, input, sessionId: this.sessionId ?? null });
|
|
23225
23255
|
this.schedule();
|
|
@@ -23233,18 +23263,31 @@ var IngestionJobRunner = class {
|
|
|
23233
23263
|
return this.store.cancelJob(id);
|
|
23234
23264
|
}
|
|
23235
23265
|
schedule() {
|
|
23236
|
-
if (this.scheduled || this.running) return;
|
|
23266
|
+
if (this.closed || this.scheduled || this.running) return;
|
|
23237
23267
|
this.scheduled = true;
|
|
23238
23268
|
queueMicrotask(() => {
|
|
23239
23269
|
this.scheduled = false;
|
|
23240
|
-
|
|
23270
|
+
if (this.closed) return;
|
|
23271
|
+
const pending = this.pump();
|
|
23272
|
+
this.pumpPromise = pending;
|
|
23273
|
+
const clearPending = () => {
|
|
23274
|
+
if (this.pumpPromise === pending) this.pumpPromise = void 0;
|
|
23275
|
+
};
|
|
23276
|
+
void pending.then(clearPending, clearPending);
|
|
23241
23277
|
});
|
|
23242
23278
|
}
|
|
23279
|
+
async close() {
|
|
23280
|
+
this.closed = true;
|
|
23281
|
+
for (const controller of this.controllers.values()) controller.abort();
|
|
23282
|
+
if (this.scheduled) await Promise.resolve();
|
|
23283
|
+
await this.pumpPromise;
|
|
23284
|
+
}
|
|
23243
23285
|
async pump() {
|
|
23244
|
-
if (this.running) return;
|
|
23286
|
+
if (this.closed || this.running) return;
|
|
23245
23287
|
this.running = true;
|
|
23246
23288
|
try {
|
|
23247
23289
|
for (; ; ) {
|
|
23290
|
+
if (this.closed) break;
|
|
23248
23291
|
const job = this.store.claimNextJob();
|
|
23249
23292
|
if (!job) break;
|
|
23250
23293
|
const controller = new AbortController();
|
|
@@ -23259,7 +23302,7 @@ var IngestionJobRunner = class {
|
|
|
23259
23302
|
} finally {
|
|
23260
23303
|
this.controllers.delete(job.id);
|
|
23261
23304
|
}
|
|
23262
|
-
await new Promise((resolve6) => setImmediate(resolve6));
|
|
23305
|
+
if (!this.closed) await new Promise((resolve6) => setImmediate(resolve6));
|
|
23263
23306
|
}
|
|
23264
23307
|
} finally {
|
|
23265
23308
|
this.running = false;
|
|
@@ -23427,10 +23470,11 @@ register("session_resume", "Use an explicit opaque session ID for subsequent con
|
|
|
23427
23470
|
register("ingestion_job_start", "Queue one durable bounded background index or strict-fetch ingestion job.", phase3Shape("ingestion_job_start"), (input) => ({ ok: true, job: getJobs().enqueue(input.kind, input.input) }));
|
|
23428
23471
|
register("ingestion_job_status", "Return bounded status for one background ingestion job.", phase3Shape("ingestion_job_status"), (input) => ({ ok: true, job: getJobs().status(input.id) }), { readOnlyHint: true });
|
|
23429
23472
|
register("ingestion_job_cancel", "Cancel one queued or running background ingestion job.", phase3Shape("ingestion_job_cancel"), (input) => ({ ok: true, ...getJobs().cancel(input.id) }), { destructiveHint: true });
|
|
23430
|
-
register("context_heal", "Explicitly back up and migrate only a recognized recoverable context database.", phase3Shape("context_heal"), () => {
|
|
23473
|
+
register("context_heal", "Explicitly back up and migrate only a recognized recoverable context database.", phase3Shape("context_heal"), async () => {
|
|
23474
|
+
await jobRunner?.close();
|
|
23475
|
+
jobRunner = void 0;
|
|
23431
23476
|
contextStore?.close();
|
|
23432
23477
|
contextStore = void 0;
|
|
23433
|
-
jobRunner = void 0;
|
|
23434
23478
|
return healContextDatabase(root);
|
|
23435
23479
|
}, { destructiveHint: true });
|
|
23436
23480
|
register("stats", "Report local context store counts and location.", {}, () => ({ ok: true, sessionId, ...getStore().stats() }), { readOnlyHint: true });
|
|
@@ -23447,6 +23491,8 @@ register("dotdotgod_project_load", "Load a bounded documentation map and optiona
|
|
|
23447
23491
|
limit: external_exports.number().int().min(1).max(30).optional(),
|
|
23448
23492
|
maxDepth: external_exports.number().int().min(1).max(5).optional()
|
|
23449
23493
|
}, (input) => projectLoad(projectInput(input)), { readOnlyHint: true });
|
|
23494
|
+
register("dotdotgod_embedding_status", "Inspect the optional persistent embedding runtime without installing or contacting a registry.", { root: external_exports.string().optional() }, (input) => projectEmbeddingStatus(projectInput(input)), { readOnlyHint: true });
|
|
23495
|
+
register("dotdotgod_embedding_install", "After explicit user approval, install the fixed optional embedding runtime persistently. Uses network and dependency install scripts.", { root: external_exports.string().optional(), confirm: external_exports.literal(true) }, (input) => projectEmbeddingInstall(projectInput(input)), { destructiveHint: true, openWorldHint: true });
|
|
23450
23496
|
register("dotdotgod_project_impact", "Run bounded graph impact for up to 20 changed paths.", {
|
|
23451
23497
|
root: external_exports.string().optional(),
|
|
23452
23498
|
paths: external_exports.array(external_exports.string()).min(1).max(20)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotdotgod/claude-code",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Claude Code adapter for dotdotgod project memory workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Elastic-2.0",
|
|
@@ -23,12 +23,9 @@
|
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=22.5.0"
|
|
25
25
|
},
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"@huggingface/transformers": "^4.2.0"
|
|
28
|
-
},
|
|
29
26
|
"devDependencies": {
|
|
30
27
|
"esbuild": "0.27.3",
|
|
31
|
-
"@dotdotgod/context": "0.3.
|
|
28
|
+
"@dotdotgod/context": "0.3.3"
|
|
32
29
|
},
|
|
33
30
|
"files": [
|
|
34
31
|
".claude-plugin",
|