@dotdotgod/claude-code 0.3.0 → 0.3.2

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.
@@ -14,7 +14,7 @@ User focus, if provided: `$ARGUMENTS`
14
14
 
15
15
  ## CLI Context
16
16
 
17
- Run `dotdotgod config <root> --json` and apply `config.load.documentationSummary.exclude` before building the documentation tree. With focus text, run `dotdotgod query <root> "<focus>" --limit 30 --json`. If the CLI is unavailable, continue from the documentation tree and README indexes.
17
+ Run `dotdotgod map <root> --depth 5 --json` for a broad documentation map. With focus text, run `dotdotgod query <root> "<focus>" --limit 30 --json` and `dotdotgod map <root> --depth 3 --json`. The map command resolves the configured documentation root and exclusions. If the CLI is unavailable, continue from the documentation tree and README indexes.
18
18
 
19
19
  ## Goal
20
20
 
@@ -24,9 +24,10 @@ Load the repository's dotdotgod project memory without modifying maintained file
24
24
 
25
25
  1. Identify the repository root. Expose `Help: dotdotgod --help` as optional CLI discovery guidance without checking or reporting installation status, and do not require Help execution for Load to continue.
26
26
  2. When baseline context is missing, read `AGENTS.md`, the adapter entrypoint, `README.md`, and `docs/README.md` when present.
27
- 3. Build a prefix-compressed tree of `docs/**/*.md` after `load.documentationSummary.exclude`. Count `docs/` as depth 1; without arguments, expand through depth 5. At the boundary, list every direct Markdown file and immediate child directory regardless of item count, then summarize hidden descendants separately under each named child with exact recursive directory and Markdown-file counts.
28
- 4. With arguments, use focused query results to return at most 30 distinct Markdown files and render the tree through depth 3 with the same named boundary-child summaries.
29
- 5. Read bodies selectively from query results, explicit paths, and README indexes. Read relevant active plans only when needed, use `docs/archive/README.md` as the history map, and avoid broad generated, dependency, cache, or secret reads.
27
+ 3. Without arguments, run `dotdotgod map <root> --depth 5 --json` and use its configured prefix-compressed documentation tree. The command owns `documentation.root`, `load.documentationSummary.exclude`, deterministic Markdown discovery, and boundary summaries.
28
+ 4. With arguments, run `dotdotgod query <root> "<focus>" --limit 30 --json` for at most 30 distinct Markdown files and `dotdotgod map <root> --depth 3 --json` for the configured navigation tree.
29
+ 5. If the CLI or shell execution is unavailable, preserve Load behavior by building the same tree from the configured documentation root when known, excluding local memory, and using README routing. At the depth boundary, list every direct Markdown file and immediate child directory, then summarize hidden descendants separately under each named child with exact recursive directory and Markdown-file counts.
30
+ 6. Read bodies selectively from query results, explicit paths, and README indexes. Read relevant active plans only when needed, use `docs/archive/README.md` (or the configured archive `README.md` under an alternate documentation root) as the history map, and avoid broad generated, dependency, cache, or secret reads.
30
31
 
31
32
  ## Output
32
33
 
package/hooks/runtime.mjs CHANGED
@@ -1,3 +1,120 @@
1
1
  #!/usr/bin/env node
2
- import { hookMain } from '@dotdotgod/context/hooks';
3
- hookMain(process.argv[2] || '');
2
+ // Generated by scripts/build-adapter-runtime.mjs; do not edit.
3
+
4
+ // packages/context/src/hooks.mjs
5
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
+ import { createHash } from "node:crypto";
7
+ import { dirname, join, resolve } from "node:path";
8
+ function inputText() {
9
+ return readFileSync(0, "utf8");
10
+ }
11
+ function parseInput() {
12
+ const text = inputText().trim();
13
+ return text ? JSON.parse(text) : {};
14
+ }
15
+ function projectRoot(input) {
16
+ return resolve(input.cwd || input.project_dir || process.env.CLAUDE_PROJECT_DIR || process.cwd());
17
+ }
18
+ function sessionId(input) {
19
+ return String(input.session_id || input.sessionId || "default").replace(/[^a-zA-Z0-9._-]/g, "_");
20
+ }
21
+ function statePath(input) {
22
+ return join(projectRoot(input), ".dotdotgod", "context", "runtime", `${sessionId(input)}.json`);
23
+ }
24
+ function readState(input) {
25
+ try {
26
+ return JSON.parse(readFileSync(statePath(input), "utf8"));
27
+ } catch {
28
+ return { loadRequired: false, pending: {} };
29
+ }
30
+ }
31
+ function writeState(input, state) {
32
+ const path = statePath(input);
33
+ mkdirSync(dirname(path), { recursive: true });
34
+ const temp = `${path}.${process.pid}.tmp`;
35
+ writeFileSync(temp, `${JSON.stringify(state, null, 2)}
36
+ `);
37
+ renameSync(temp, path);
38
+ }
39
+ function fileHash(path) {
40
+ try {
41
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
42
+ } catch {
43
+ return "missing";
44
+ }
45
+ }
46
+ function toolName(input) {
47
+ return String(input.tool_name || input.toolName || "");
48
+ }
49
+ function toolInput(input) {
50
+ return input.tool_input || input.toolInput || {};
51
+ }
52
+ function mcpTool(name, suffix) {
53
+ return name === suffix || name.endsWith(`__${suffix}`) || name.endsWith(`_${suffix}`);
54
+ }
55
+ function deny(reason) {
56
+ return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } };
57
+ }
58
+ function commandFrom(input) {
59
+ const value = toolInput(input);
60
+ return String(value.command || value.cmd || "");
61
+ }
62
+ function isSubstantive(name) {
63
+ return /^(Bash|Shell|local_shell|shell|shell_command|exec_command|Edit|Write|apply_patch)$/i.test(name);
64
+ }
65
+ function isImpactGate(name, input) {
66
+ if (!/^(Bash|Shell|local_shell|shell|shell_command|exec_command)$/i.test(name)) return false;
67
+ 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
+ }
69
+ function changedPath(input) {
70
+ const value = toolInput(input);
71
+ const path = value.file_path || value.path;
72
+ return typeof path === "string" ? resolve(projectRoot(input), path) : null;
73
+ }
74
+ function runHook(event) {
75
+ const input = parseInput();
76
+ const name = toolName(input);
77
+ const state = readState(input);
78
+ state.pending ||= {};
79
+ if (event === "sessionstart") {
80
+ state.loadRequired = true;
81
+ writeState(input, state);
82
+ 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
+ }
84
+ if (event === "pretooluse") {
85
+ if (mcpTool(name, "dotdotgod_project_load") || mcpTool(name, "dotdotgod_project_impact")) return {};
86
+ if (state.loadRequired && isSubstantive(name)) return deny("Project memory is required first. Call dotdotgod_project_load with the current project root and a concise task-specific focus, then retry this tool.");
87
+ const pendingPaths = Object.keys(state.pending);
88
+ if (pendingPaths.length && isImpactGate(name, input)) return deny(`Graph impact is pending. Call dotdotgod_project_impact with paths=${JSON.stringify(pendingPaths)} and then retry this tool.`);
89
+ return {};
90
+ }
91
+ if (event === "posttooluse") {
92
+ if (mcpTool(name, "dotdotgod_project_load")) state.loadRequired = false;
93
+ else if (mcpTool(name, "dotdotgod_project_impact")) {
94
+ const checked = toolInput(input).paths;
95
+ if (Array.isArray(checked)) for (const path of checked) {
96
+ const absolute = resolve(projectRoot(input), path);
97
+ if (state.pending[absolute] === fileHash(absolute)) delete state.pending[absolute];
98
+ }
99
+ } else if (/^(Edit|Write|apply_patch)$/i.test(name)) {
100
+ const path = changedPath(input);
101
+ if (path) state.pending[path] = fileHash(path);
102
+ }
103
+ writeState(input, state);
104
+ return {};
105
+ }
106
+ return {};
107
+ }
108
+ function hookMain(event) {
109
+ try {
110
+ process.stdout.write(`${JSON.stringify(runHook(event))}
111
+ `);
112
+ } catch (error) {
113
+ process.stderr.write(`dotdotgod hook error: ${error instanceof Error ? error.message : String(error)}
114
+ `);
115
+ process.stdout.write("{}\n");
116
+ }
117
+ }
118
+
119
+ // packages/claude-code/hooks/runtime-entry.mjs
120
+ hookMain(process.argv[2] || "");