brainclaw 1.9.0 → 1.10.0

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.
Files changed (149) hide show
  1. package/README.md +631 -499
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +18 -1
  4. package/dist/commands/code-map.js +129 -0
  5. package/dist/commands/codev.js +7 -0
  6. package/dist/commands/harvest.js +1 -1
  7. package/dist/commands/hooks.js +73 -73
  8. package/dist/commands/init.js +1 -1
  9. package/dist/commands/install-hooks.js +78 -78
  10. package/dist/commands/mcp-read-handlers.js +57 -14
  11. package/dist/commands/mcp.js +200 -13
  12. package/dist/commands/run-profile.js +3 -2
  13. package/dist/commands/switch.js +125 -93
  14. package/dist/commands/version.js +1 -1
  15. package/dist/core/agent-capability.js +19 -4
  16. package/dist/core/agent-files.js +131 -119
  17. package/dist/core/code-map/backend.js +123 -0
  18. package/dist/core/code-map/core.js +81 -0
  19. package/dist/core/code-map/drafts.js +2 -0
  20. package/dist/core/code-map/extractor.js +29 -0
  21. package/dist/core/code-map/finalizer.js +191 -0
  22. package/dist/core/code-map/freshness.js +108 -0
  23. package/dist/core/code-map/ids.js +0 -0
  24. package/dist/core/code-map/importable.js +35 -0
  25. package/dist/core/code-map/indexes.js +197 -0
  26. package/dist/core/code-map/lang/java/imports.scm +17 -0
  27. package/dist/core/code-map/lang/java/index.js +254 -0
  28. package/dist/core/code-map/lang/java/tags.scm +48 -0
  29. package/dist/core/code-map/lang/php/imports.scm +21 -0
  30. package/dist/core/code-map/lang/php/index.js +251 -0
  31. package/dist/core/code-map/lang/php/tags.scm +44 -0
  32. package/dist/core/code-map/lang/provider.js +9 -0
  33. package/dist/core/code-map/lang/providers.js +24 -0
  34. package/dist/core/code-map/lang/python/imports.scm +90 -0
  35. package/dist/core/code-map/lang/python/index.js +364 -0
  36. package/dist/core/code-map/lang/python/tags.scm +81 -0
  37. package/dist/core/code-map/lang/query-runtime.js +374 -0
  38. package/dist/core/code-map/lang/registry.js +125 -0
  39. package/dist/core/code-map/lang/typescript/imports.scm +90 -0
  40. package/dist/core/code-map/lang/typescript/index.js +306 -0
  41. package/dist/core/code-map/lang/typescript/tags.js.scm +106 -0
  42. package/dist/core/code-map/lang/typescript/tags.scm +151 -0
  43. package/dist/core/code-map/lock.js +210 -0
  44. package/dist/core/code-map/materialized.js +51 -0
  45. package/dist/core/code-map/memory-reader.js +59 -0
  46. package/dist/core/code-map/paths.js +53 -0
  47. package/dist/core/code-map/query.js +568 -0
  48. package/dist/core/code-map/refresh.js +0 -0
  49. package/dist/core/code-map/resolve.js +177 -0
  50. package/dist/core/code-map/store.js +206 -0
  51. package/dist/core/code-map/types.js +288 -0
  52. package/dist/core/code-map/vocabulary.js +57 -0
  53. package/dist/core/code-map/wasm-loader.js +294 -0
  54. package/dist/core/code-map/work-section.js +206 -0
  55. package/dist/core/codev-prompts.js +38 -38
  56. package/dist/core/codev-rounds.js +4 -0
  57. package/dist/core/default-profiles/doctor.yaml +11 -11
  58. package/dist/core/default-profiles/janitor.yaml +11 -11
  59. package/dist/core/default-profiles/onboarder.yaml +11 -11
  60. package/dist/core/default-profiles/reviewer.yaml +13 -13
  61. package/dist/core/dispatcher.js +1 -1
  62. package/dist/core/entity-operations.js +29 -3
  63. package/dist/core/execution-adapters.js +11 -10
  64. package/dist/core/execution-profile.js +58 -0
  65. package/dist/core/execution.js +1 -1
  66. package/dist/core/facade-schema.js +9 -0
  67. package/dist/core/instruction-templates.js +2 -0
  68. package/dist/core/loops/verbs.js +0 -1
  69. package/dist/core/mcp-command-resolution.js +3 -1
  70. package/dist/core/messaging.js +2 -2
  71. package/dist/core/protocol-skills.js +164 -164
  72. package/dist/core/runtime-signals.js +1 -1
  73. package/dist/core/search.js +19 -2
  74. package/dist/core/security-guard.js +207 -207
  75. package/dist/core/spawn-check.js +16 -2
  76. package/dist/core/staleness.js +1 -1
  77. package/dist/core/store-resolution.js +67 -11
  78. package/dist/core/worktree.js +18 -18
  79. package/dist/facts.js +9 -5
  80. package/dist/facts.json +8 -4
  81. package/dist/vendor/web-tree-sitter/tree-sitter.js +3980 -0
  82. package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
  83. package/dist/wasm/tree-sitter-java.wasm +0 -0
  84. package/dist/wasm/tree-sitter-javascript.wasm +0 -0
  85. package/dist/wasm/tree-sitter-php.wasm +0 -0
  86. package/dist/wasm/tree-sitter-python.wasm +0 -0
  87. package/dist/wasm/tree-sitter-tsx.wasm +0 -0
  88. package/dist/wasm/tree-sitter-typescript.wasm +0 -0
  89. package/dist/wasm/tree-sitter.wasm +0 -0
  90. package/docs/PROTOCOL.md +1 -1
  91. package/docs/adapters/openclaw.md +43 -43
  92. package/docs/architecture/project-refs.md +328 -328
  93. package/docs/cli.md +2131 -2093
  94. package/docs/code-map.md +198 -0
  95. package/docs/concepts/coordination.md +52 -52
  96. package/docs/concepts/coordinator-runbook.md +129 -129
  97. package/docs/concepts/dispatch-lifecycle.md +245 -245
  98. package/docs/concepts/event-log-store.md +928 -928
  99. package/docs/concepts/ideation-loop.md +317 -317
  100. package/docs/concepts/loop-engine.md +520 -511
  101. package/docs/concepts/mcp-governance.md +268 -268
  102. package/docs/concepts/memory.md +84 -84
  103. package/docs/concepts/multi-agent-workflows.md +167 -167
  104. package/docs/concepts/observer-protocol.md +361 -361
  105. package/docs/concepts/plans-and-claims.md +217 -217
  106. package/docs/concepts/project-md-convention.md +35 -35
  107. package/docs/concepts/runtime-notes.md +38 -38
  108. package/docs/concepts/troubleshooting.md +254 -254
  109. package/docs/concepts/workspace-bootstrapping.md +142 -142
  110. package/docs/context-format-changelog.md +35 -35
  111. package/docs/context-format.md +48 -48
  112. package/docs/index.md +65 -65
  113. package/docs/integrations/agents.md +158 -158
  114. package/docs/integrations/claude-code.md +23 -23
  115. package/docs/integrations/cline.md +77 -77
  116. package/docs/integrations/continue.md +55 -55
  117. package/docs/integrations/copilot.md +68 -68
  118. package/docs/integrations/cursor.md +23 -23
  119. package/docs/integrations/kilocode.md +72 -72
  120. package/docs/integrations/mcp.md +385 -378
  121. package/docs/integrations/mistral-vibe.md +122 -122
  122. package/docs/integrations/openclaw.md +92 -92
  123. package/docs/integrations/opencode.md +84 -84
  124. package/docs/integrations/overview.md +115 -115
  125. package/docs/integrations/roo.md +71 -71
  126. package/docs/integrations/windsurf.md +77 -77
  127. package/docs/mcp-schema-changelog.md +364 -356
  128. package/docs/playbooks/integration/index.md +121 -121
  129. package/docs/playbooks/orchestration.md +37 -0
  130. package/docs/playbooks/productivity/index.md +99 -99
  131. package/docs/playbooks/team/index.md +117 -117
  132. package/docs/product/agent-first-model.md +184 -184
  133. package/docs/product/entity-model-audit.md +462 -462
  134. package/docs/product/positioning.md +86 -86
  135. package/docs/quickstart-existing-project.md +107 -107
  136. package/docs/quickstart.md +183 -183
  137. package/docs/release-maintenance.md +79 -79
  138. package/docs/reputation.md +52 -52
  139. package/docs/review.md +45 -45
  140. package/docs/security.md +212 -212
  141. package/docs/server-operations.md +118 -118
  142. package/docs/storage.md +106 -106
  143. package/package.json +86 -66
  144. package/docs/concepts/event-log-store-critique-A.md +0 -333
  145. package/docs/concepts/event-log-store-critique-B.md +0 -353
  146. package/docs/concepts/event-log-store-phase0-measurements.md +0 -58
  147. package/docs/concepts/event-log-store-proposal-A.md +0 -365
  148. package/docs/concepts/event-log-store-proposal-B.md +0 -404
  149. package/docs/concepts/identity-model-proposal.md +0 -371
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Code Map P1c — whole-project import RESOLUTION pass (cadrage v2; Codex R1).
3
+ *
4
+ * The FIRST cross-file pass in Code Map. P0..langs are strictly per-file: the
5
+ * finalizer turns one file's draft into nodes/edges with no knowledge of sibling
6
+ * files. Resolution is inherently cross-file (`./utils` in `src/a.ts` →
7
+ * `src/utils.ts`), so it runs as a core pass over the whole shard set AFTER
8
+ * extraction + compaction + freshness reclassification, but BEFORE the index /
9
+ * materialized / stats writes (so everything downstream sees the same edge set).
10
+ *
11
+ * For each `module` node (an import) it asks the owning provider's `resolveImport`
12
+ * to map the specifier → a project-internal target FILE path, then emits
13
+ * `module --resolves_to--> <target file node>` (A, file-level). The PROVIDER returns
14
+ * paths only; the CORE mints the edge + target file-node id (dec#108/#109 boundary).
15
+ *
16
+ * P1c-B (symbol-level, additive): for that SAME-RUN resolved target file, each NAMED
17
+ * imported symbol (`mod.imported_names`, excluding `'default'`/`'*'`) is bound to its
18
+ * definition via `module --imports_symbol--> <def symbol node>` — but only when the
19
+ * target file has exactly ONE importable symbol of that name (ambiguous/absent → no
20
+ * edge). "Importable" is the TARGET provider's `isImportableSymbol` (TS: exported &&
21
+ * not a synthetic export; Python: top-level). B's confidence INHERITS the A
22
+ * file-resolution confidence (never higher than the resolution it rides on).
23
+ *
24
+ * Idempotency (Codex R1 #4): edge ids are deterministic by (project, from, to,
25
+ * kind) only, so each shard is rewritten by KEEPING every non-pass-owned node/edge
26
+ * byte-identical (same order), FILTERING old pass-owned edges (BOTH `resolves_to`
27
+ * AND `imports_symbol` — else a renamed/deleted symbol leaves a stale B edge), then
28
+ * appending the freshly computed A+B edges in a single deterministic order. A shard
29
+ * is only written when its edge array actually changed — so a project with no
30
+ * resolver is a pure no-op, and re-runs are byte-identical.
31
+ *
32
+ * Recompute-all (Codex R1): the pass resolves over ALL shards every refresh (even
33
+ * `--changed`) and rewrites only the shards whose pass-owned set changed. That
34
+ * removes the moved/deleted-target stale-edge problem without a reverse-dependency
35
+ * scheduler — the pass already holds the full current file set.
36
+ */
37
+ import { edgeId, fileNodeId } from './ids.js';
38
+ import { writeShard } from './store.js';
39
+ import { buildImportableIndex, lookupImportable } from './importable.js';
40
+ import { EdgeSchema } from './types.js';
41
+ /** Edge kinds this pass OWNS — stale-stripped + recomputed every run (A + B). */
42
+ const PASS_OWNED_EDGE_KINDS = new Set(['resolves_to', 'imports_symbol']);
43
+ function toPosix(p) {
44
+ return p.replace(/\\/g, '/');
45
+ }
46
+ function clampConfidence(c) {
47
+ if (typeof c !== 'number' || !Number.isFinite(c))
48
+ return 1.0;
49
+ return Math.max(0, Math.min(1, c));
50
+ }
51
+ /** Order-and-content equality for an edge array (decides whether a shard rewrite is needed). */
52
+ function edgesArrayEqual(a, b) {
53
+ if (a.length !== b.length)
54
+ return false;
55
+ for (let i = 0; i < a.length; i++) {
56
+ const x = a[i];
57
+ const y = b[i];
58
+ if (x.id !== y.id || x.from !== y.from || x.to !== y.to || x.kind !== y.kind || x.confidence !== y.confidence) {
59
+ return false;
60
+ }
61
+ }
62
+ return true;
63
+ }
64
+ /**
65
+ * Resolve every import in the project to a `resolves_to` edge (file-level v1).
66
+ * Mutates + persists only the shards whose `resolves_to` set changed; returns
67
+ * whether any shard was rewritten so the caller can re-list before index build.
68
+ */
69
+ export async function resolveProjectImports(input) {
70
+ const { projectId, registry, shards } = input;
71
+ const persist = input.persistShard ?? writeShard;
72
+ // Project file-set: indexed path -> lang. Resolution targets MUST be indexed
73
+ // files — this map is the ONLY project knowledge providers get (via ctx).
74
+ const fileLang = new Map();
75
+ for (const s of shards)
76
+ fileLang.set(toPosix(s.path), s.lang);
77
+ const ctx = {
78
+ fileExists: (rel) => fileLang.has(toPosix(rel)),
79
+ langOfFile: (rel) => fileLang.get(toPosix(rel)),
80
+ };
81
+ // Target-file lookups for B: path -> shard, and a memoized importable index
82
+ // (name -> importable symbols) per target file (built lazily, reused across importers).
83
+ const shardByPath = new Map();
84
+ for (const s of shards)
85
+ shardByPath.set(toPosix(s.path), s);
86
+ const importableCache = new Map();
87
+ const importableFor = (targetPath, targetLang) => {
88
+ let idx = importableCache.get(targetPath);
89
+ if (!idx) {
90
+ const targetShard = shardByPath.get(targetPath);
91
+ idx = buildImportableIndex(targetShard?.nodes ?? [], registry.providerForLang(targetLang));
92
+ importableCache.set(targetPath, idx);
93
+ }
94
+ return idx;
95
+ };
96
+ let shardsRewritten = 0;
97
+ let edgesEmitted = 0;
98
+ let symbolEdgesEmitted = 0;
99
+ for (const shard of shards) {
100
+ const provider = registry.providerForLang(shard.lang);
101
+ // Non-pass-owned edges are preserved byte-identical (same order). Filtering BOTH
102
+ // pass-owned kinds also strips any stale A/B edge from a prior run (idempotency).
103
+ const kept = shard.edges.filter((e) => !PASS_OWNED_EDGE_KINDS.has(e.kind));
104
+ // Fresh pass-owned set (A resolves_to + B imports_symbol). Empty when no resolver.
105
+ const fresh = [];
106
+ let freshSymbolCount = 0;
107
+ if (provider?.resolveImport) {
108
+ const seen = new Set();
109
+ for (const mod of shard.nodes) {
110
+ if (mod.kind !== 'module')
111
+ continue;
112
+ const resolutions = await provider.resolveImport({
113
+ source: mod.name,
114
+ fromPath: toPosix(shard.path),
115
+ importedNames: mod.imported_names ?? [],
116
+ span: mod.span ?? undefined,
117
+ }, ctx);
118
+ for (const r of resolutions) {
119
+ if (!r.resolvedPath)
120
+ continue;
121
+ const targetPath = toPosix(r.resolvedPath);
122
+ const targetLang = fileLang.get(targetPath);
123
+ if (!targetLang)
124
+ continue; // target not an indexed file → no edge
125
+ const confidence = clampConfidence(r.confidence);
126
+ if (confidence <= 0)
127
+ continue; // preserves the resolves_to confidence invariant: (0, 1]
128
+ const source = { path: toPosix(shard.path), line: mod.span?.start_line ?? null };
129
+ // A — file-level resolves_to.
130
+ const to = fileNodeId(projectId, targetPath, targetLang);
131
+ const id = edgeId({ projectId, from: mod.id, to, kind: 'resolves_to' });
132
+ if (!seen.has(id)) {
133
+ seen.add(id);
134
+ fresh.push({ id, from: mod.id, to, kind: 'resolves_to', confidence, source });
135
+ }
136
+ // B — symbol-level imports_symbol, off the SAME-RUN resolved target file.
137
+ // Skip default/namespace (no single named symbol). Bind a name only when the
138
+ // target has exactly ONE importable symbol with it (ambiguous/absent → skip).
139
+ const idx = importableFor(targetPath, targetLang);
140
+ for (const name of mod.imported_names ?? []) {
141
+ if (name === 'default' || name === '*')
142
+ continue;
143
+ const target = lookupImportable(idx, name);
144
+ if (!target)
145
+ continue;
146
+ const symId = edgeId({ projectId, from: mod.id, to: target.id, kind: 'imports_symbol' });
147
+ if (seen.has(symId))
148
+ continue; // dedup (duplicate names / re-imports)
149
+ seen.add(symId);
150
+ fresh.push({ id: symId, from: mod.id, to: target.id, kind: 'imports_symbol', confidence, source });
151
+ freshSymbolCount++;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ // Single deterministic order over A+B so re-runs are byte-identical (from, to, kind).
157
+ fresh.sort((a, b) => {
158
+ if (a.from !== b.from)
159
+ return a.from.localeCompare(b.from);
160
+ if (a.to !== b.to)
161
+ return a.to.localeCompare(b.to);
162
+ return a.kind.localeCompare(b.kind);
163
+ });
164
+ const nextEdges = [...kept, ...fresh];
165
+ if (!edgesArrayEqual(shard.edges, nextEdges)) {
166
+ for (const e of fresh)
167
+ EdgeSchema.parse(e); // loud on a malformed pass-owned edge
168
+ shard.edges = nextEdges;
169
+ persist(shard, input.cwd, input.preferredDirName);
170
+ shardsRewritten++;
171
+ }
172
+ edgesEmitted += fresh.length;
173
+ symbolEdgesEmitted += freshSymbolCount;
174
+ }
175
+ return { rewroteAny: shardsRewritten > 0, shardsRewritten, edgesEmitted, symbolEdgesEmitted };
176
+ }
177
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Code Map durable store — init/read/write for manifest, profiler, and shards
3
+ * (spec §4, §5). All writes go through `writeFileAtomic` (io.ts), which is the
4
+ * spec's NTFS-safe atomic temp+rename with EPERM/EBUSY backoff.
5
+ *
6
+ * Queries must be answerable from `files/**` + `indexes/**` alone; the
7
+ * `materialized/` cache is rebuildable and never required (spec §4).
8
+ */
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { writeFileAtomic } from '../io.js';
12
+ import { CODE_MAP_SCHEMA_VERSION, FileShardSchema, ImportsIndexSchema, ManifestSchema, ProfilerSchema, ResolutionIndexSchema, SymbolsIndexSchema, } from './types.js';
13
+ import { codeMapDir, filesDir, importsIndexPath, indexesDir, manifestPath, profilerPath, resolutionIndexPath, shardPath, symbolsIndexPath, } from './paths.js';
14
+ function ensureDir(dir) {
15
+ if (!fs.existsSync(dir))
16
+ fs.mkdirSync(dir, { recursive: true });
17
+ }
18
+ /** Read whole JSON file into memory before parsing (spec §6 — NTFS handle contention). */
19
+ function readJsonFile(filepath) {
20
+ if (!fs.existsSync(filepath))
21
+ return null;
22
+ try {
23
+ const raw = fs.readFileSync(filepath, 'utf-8');
24
+ return JSON.parse(raw);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ /** Create the on-disk skeleton: code/, files/, indexes/ (materialized/ is lazy). */
31
+ export function ensureStoreDirs(cwd, preferredDirName) {
32
+ ensureDir(codeMapDir(cwd, preferredDirName));
33
+ ensureDir(filesDir(cwd, preferredDirName));
34
+ ensureDir(indexesDir(cwd, preferredDirName));
35
+ }
36
+ /**
37
+ * Initialize a fresh Code Map store. A brand-new store starts in
38
+ * `missing_index` freshness (nothing parsed yet — spec §5.1).
39
+ */
40
+ export function initStore(input) {
41
+ ensureStoreDirs(input.cwd, input.preferredDirName);
42
+ const now = new Date().toISOString();
43
+ const manifest = ManifestSchema.parse({
44
+ schema_version: CODE_MAP_SCHEMA_VERSION,
45
+ project_id: input.projectId,
46
+ code_map_enabled: true,
47
+ project_root: input.projectRoot,
48
+ code_map_version: 1,
49
+ store_created_at: now,
50
+ updated_at: now,
51
+ active_backend: 'jsonl',
52
+ extractor_version: input.extractorVersion,
53
+ extractor_config_hash: input.extractorConfigHash,
54
+ engine_glue_hash: input.engineGlueHash ?? null,
55
+ extractor_config: input.extractorConfig,
56
+ languages: input.languages ?? {},
57
+ git: { head: null, branch: null, dirty: false, ...input.git },
58
+ worktree: { worktree_id: null, path: null, ...input.worktree },
59
+ stats: {
60
+ files_indexed: 0,
61
+ nodes: 0,
62
+ edges: 0,
63
+ last_full_refresh_ms: null,
64
+ last_changed_refresh_ms: null,
65
+ },
66
+ freshness: { status: 'missing_index', stale_file_count: 0, partial_reason: null },
67
+ });
68
+ writeManifest(manifest, input.cwd, input.preferredDirName);
69
+ return manifest;
70
+ }
71
+ /**
72
+ * Read the manifest. Tolerates a missing store: returns `null` so callers can
73
+ * surface a `missing_index` freshness badge instead of throwing (spec §6.1).
74
+ */
75
+ export function readManifest(cwd, preferredDirName) {
76
+ const raw = readJsonFile(manifestPath(cwd, preferredDirName));
77
+ if (raw === null)
78
+ return null;
79
+ const parsed = ManifestSchema.safeParse(raw);
80
+ return parsed.success ? parsed.data : null;
81
+ }
82
+ export function writeManifest(manifest, cwd, preferredDirName) {
83
+ ensureDir(codeMapDir(cwd, preferredDirName));
84
+ const next = { ...manifest, updated_at: new Date().toISOString() };
85
+ writeFileAtomic(manifestPath(cwd, preferredDirName), JSON.stringify(next, null, 2));
86
+ }
87
+ export function readProfiler(cwd, preferredDirName) {
88
+ const raw = readJsonFile(profilerPath(cwd, preferredDirName));
89
+ if (raw === null)
90
+ return null;
91
+ const parsed = ProfilerSchema.safeParse(raw);
92
+ return parsed.success ? parsed.data : null;
93
+ }
94
+ export function writeProfiler(profiler, cwd, preferredDirName) {
95
+ ensureDir(codeMapDir(cwd, preferredDirName));
96
+ const next = { ...profiler, updated_at: new Date().toISOString() };
97
+ writeFileAtomic(profilerPath(cwd, preferredDirName), JSON.stringify(next, null, 2));
98
+ }
99
+ /** Read one shard by file id. Returns null on missing/corrupt (readers tolerate). */
100
+ export function readShard(fileIdHash, cwd, preferredDirName) {
101
+ const raw = readJsonFile(shardPath(fileIdHash, cwd, preferredDirName));
102
+ if (raw === null)
103
+ return null;
104
+ const parsed = FileShardSchema.safeParse(raw);
105
+ return parsed.success ? parsed.data : null;
106
+ }
107
+ /** Write one shard. Ensures the hash-prefix dir exists, then atomic write. */
108
+ export function writeShard(shard, cwd, preferredDirName) {
109
+ const target = shardPath(shard.file_id, cwd, preferredDirName);
110
+ ensureDir(path.dirname(target));
111
+ writeFileAtomic(target, JSON.stringify(shard, null, 2));
112
+ }
113
+ export function deleteShard(fileIdHash, cwd, preferredDirName) {
114
+ const target = shardPath(fileIdHash, cwd, preferredDirName);
115
+ try {
116
+ if (fs.existsSync(target))
117
+ fs.unlinkSync(target);
118
+ }
119
+ catch {
120
+ /* best effort */
121
+ }
122
+ }
123
+ /** Enumerate every shard under files/** (used by index rebuild + queries). */
124
+ export function listShards(cwd, preferredDirName) {
125
+ const root = filesDir(cwd, preferredDirName);
126
+ if (!fs.existsSync(root))
127
+ return [];
128
+ const shards = [];
129
+ for (const prefix of fs.readdirSync(root)) {
130
+ const prefixDir = path.join(root, prefix);
131
+ let stat;
132
+ try {
133
+ stat = fs.statSync(prefixDir);
134
+ }
135
+ catch {
136
+ continue;
137
+ }
138
+ if (!stat.isDirectory())
139
+ continue;
140
+ for (const entry of fs.readdirSync(prefixDir)) {
141
+ // Readers never consume a temp file (spec §6 rule 6).
142
+ if (!entry.endsWith('.json') || entry.includes('.tmp'))
143
+ continue;
144
+ const raw = readJsonFile(path.join(prefixDir, entry));
145
+ if (raw === null)
146
+ continue;
147
+ const parsed = FileShardSchema.safeParse(raw);
148
+ if (parsed.success)
149
+ shards.push(parsed.data);
150
+ }
151
+ }
152
+ return shards;
153
+ }
154
+ export function readSymbolsIndex(cwd, preferredDirName) {
155
+ const raw = readJsonFile(symbolsIndexPath(cwd, preferredDirName));
156
+ if (raw === null)
157
+ return null;
158
+ const parsed = SymbolsIndexSchema.safeParse(raw);
159
+ if (!parsed.success)
160
+ return null;
161
+ // Re-home entries onto a null-proto object so a token lookup like
162
+ // entries['constructor'] / entries['toString'] cannot resolve to an inherited
163
+ // Object.prototype member (a non-iterable function) on a JSON-parsed object.
164
+ parsed.data.entries = Object.assign(Object.create(null), parsed.data.entries);
165
+ return parsed.data;
166
+ }
167
+ export function writeSymbolsIndex(index, cwd, preferredDirName) {
168
+ ensureDir(indexesDir(cwd, preferredDirName));
169
+ writeFileAtomic(symbolsIndexPath(cwd, preferredDirName), JSON.stringify(index, null, 2));
170
+ }
171
+ export function readImportsIndex(cwd, preferredDirName) {
172
+ const raw = readJsonFile(importsIndexPath(cwd, preferredDirName));
173
+ if (raw === null)
174
+ return null;
175
+ const parsed = ImportsIndexSchema.safeParse(raw);
176
+ if (!parsed.success)
177
+ return null;
178
+ parsed.data.entries = Object.assign(Object.create(null), parsed.data.entries);
179
+ return parsed.data;
180
+ }
181
+ export function writeImportsIndex(index, cwd, preferredDirName) {
182
+ ensureDir(indexesDir(cwd, preferredDirName));
183
+ writeFileAtomic(importsIndexPath(cwd, preferredDirName), JSON.stringify(index, null, 2));
184
+ }
185
+ export function readResolutionIndex(cwd, preferredDirName) {
186
+ const raw = readJsonFile(resolutionIndexPath(cwd, preferredDirName));
187
+ if (raw === null)
188
+ return null;
189
+ const parsed = ResolutionIndexSchema.safeParse(raw);
190
+ if (!parsed.success)
191
+ return null;
192
+ // Re-home both maps onto null-proto objects so a key like 'constructor' /
193
+ // 'toString' (a path or node id) can't resolve to an inherited prototype member.
194
+ parsed.data.dependents_by_file = Object.assign(Object.create(null), parsed.data.dependents_by_file);
195
+ parsed.data.dependents_by_symbol = Object.assign(Object.create(null), parsed.data.dependents_by_symbol);
196
+ return parsed.data;
197
+ }
198
+ export function writeResolutionIndex(index, cwd, preferredDirName) {
199
+ ensureDir(indexesDir(cwd, preferredDirName));
200
+ writeFileAtomic(resolutionIndexPath(cwd, preferredDirName), JSON.stringify(index, null, 2));
201
+ }
202
+ /** True when a Code Map store has been initialized for this project. */
203
+ export function storeExists(cwd, preferredDirName) {
204
+ return fs.existsSync(manifestPath(cwd, preferredDirName));
205
+ }
206
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Brainclaw Code Map — durable store schemas (spec §5).
3
+ *
4
+ * Every persisted object carries `schema_version`. zod schemas are the runtime
5
+ * source of truth; the exported TS types are inferred from them so the two can
6
+ * never drift.
7
+ */
8
+ import { z } from 'zod';
9
+ import { NAMESPACED_VOCAB_RE, UniversalEdgeKinds, UniversalNodeSubtypes, } from './vocabulary.js';
10
+ /** Current schema version for all Code Map objects in P0. */
11
+ export const CODE_MAP_SCHEMA_VERSION = 1;
12
+ const Sha256Hash = z.string();
13
+ // --- Enums (spec §5) ---
14
+ /** spec §5.3 — per-file shard parse outcome. */
15
+ export const ParseStatusSchema = z.enum([
16
+ 'parsed',
17
+ 'skipped_too_large',
18
+ 'skipped_unsupported',
19
+ 'parse_error',
20
+ ]);
21
+ /** spec §5.4 — node kinds. */
22
+ export const NodeKindSchema = z.enum(['file', 'module', 'symbol']);
23
+ /**
24
+ * spec §5 (dec#108 #4) — symbol subtypes.
25
+ *
26
+ * No longer a closed `z.enum`: a subtype is EITHER a universal value
27
+ * ({@link UniversalNodeSubtypes}) OR a provider-namespaced value matching
28
+ * `^[a-z]+\.[a-z_]+$` (e.g. `rust.trait`). P1a emits only universal values.
29
+ * The TS type is sourced from `vocabulary.ts` so the static + runtime surfaces
30
+ * cannot drift.
31
+ */
32
+ const UNIVERSAL_NODE_SUBTYPES = new Set(UniversalNodeSubtypes);
33
+ export const NodeSubtypeSchema = z.custom((v) => typeof v === 'string' && (UNIVERSAL_NODE_SUBTYPES.has(v) || NAMESPACED_VOCAB_RE.test(v)), { error: 'Invalid node subtype: expected a universal subtype or a namespaced `provider.subtype` value' });
34
+ /**
35
+ * spec §5 (dec#108 #4) — edge kinds. Same constrained-string contract as
36
+ * {@link NodeSubtypeSchema}: a universal value ({@link UniversalEdgeKinds}) or a
37
+ * provider-namespaced value matching `^[a-z]+\.[a-z_]+$`.
38
+ */
39
+ const UNIVERSAL_EDGE_KINDS = new Set(UniversalEdgeKinds);
40
+ export const EdgeKindSchema = z.custom((v) => typeof v === 'string' && (UNIVERSAL_EDGE_KINDS.has(v) || NAMESPACED_VOCAB_RE.test(v)), { error: 'Invalid edge kind: expected a universal edge kind or a namespaced `provider.kind` value' });
41
+ /** spec §5.1 — freshness status enum, shared by manifest + read responses. */
42
+ export const FreshnessStatusSchema = z.enum([
43
+ 'fresh',
44
+ 'stale_changed_files',
45
+ 'stale_extractor',
46
+ 'stale_grammar',
47
+ 'partial',
48
+ 'missing_index',
49
+ ]);
50
+ /** The langs the bundled providers emit today — the fast-path of the validator. */
51
+ const KNOWN_CODE_LANGS = new Set([
52
+ 'javascript',
53
+ 'typescript',
54
+ 'tsx',
55
+ 'jsx',
56
+ 'python',
57
+ 'php',
58
+ 'java',
59
+ ]);
60
+ /** A well-formed (lowercase) language id a future provider could register. */
61
+ const CODE_LANG_RE = /^[a-z][a-z0-9_]*$/;
62
+ export const CodeLangSchema = z.custom((v) => typeof v === 'string' && (KNOWN_CODE_LANGS.has(v) || CODE_LANG_RE.test(v)), { error: 'Invalid code lang: expected a known or well-formed lowercase language id' });
63
+ // --- Node / Edge / Span (spec §5.4, §5.5) ---
64
+ export const SpanSchema = z.object({
65
+ start_line: z.number().int(),
66
+ start_col: z.number().int(),
67
+ end_line: z.number().int(),
68
+ end_col: z.number().int(),
69
+ });
70
+ export const NodeSchema = z.object({
71
+ id: z.string(),
72
+ kind: NodeKindSchema,
73
+ subtype: NodeSubtypeSchema.nullable().optional(),
74
+ lang: CodeLangSchema,
75
+ name: z.string(),
76
+ path: z.string(),
77
+ span: SpanSchema.nullable().optional(),
78
+ exported: z.boolean().default(false),
79
+ confidence: z.number().default(1.0),
80
+ related_memory_ids: z.array(z.string()).default([]),
81
+ /**
82
+ * For `module` nodes: the named bindings pulled from this import/re-export
83
+ * (e.g. ["useEffect","useMemo"]). Default import → ["default"], namespace
84
+ * import → ["*"]. Feeds index.imports.v1 `imported[]` (spec §5.7). Empty/absent
85
+ * for non-module nodes.
86
+ */
87
+ imported_names: z.array(z.string()).default([]),
88
+ });
89
+ export const EdgeSchema = z.object({
90
+ id: z.string(),
91
+ from: z.string(),
92
+ to: z.string(),
93
+ kind: EdgeKindSchema,
94
+ confidence: z.number().default(1.0),
95
+ source: z
96
+ .object({
97
+ path: z.string(),
98
+ line: z.number().int().nullable().optional(),
99
+ })
100
+ .nullable()
101
+ .optional(),
102
+ });
103
+ // --- Per-file shard (spec §5.3) ---
104
+ export const ShardFreshnessSchema = z.object({
105
+ status: FreshnessStatusSchema,
106
+ reason: z.string().nullable().default(null),
107
+ });
108
+ export const FileShardSchema = z.object({
109
+ schema_version: z.number().int().default(CODE_MAP_SCHEMA_VERSION),
110
+ file_id: Sha256Hash,
111
+ project_id: z.string(),
112
+ worktree_id: z.string().nullable().optional(),
113
+ path: z.string(),
114
+ lang: CodeLangSchema,
115
+ file_hash: Sha256Hash,
116
+ mtime_ms: z.number(),
117
+ size_bytes: z.number().int(),
118
+ parse_status: ParseStatusSchema,
119
+ git_head: z.string().nullable().optional(),
120
+ extractor_version: z.string(),
121
+ extractor_config_hash: Sha256Hash,
122
+ grammar_name: z.string().nullable().optional(),
123
+ grammar_version: z.string().nullable().optional(),
124
+ tree_sitter_grammar_hash: Sha256Hash.nullable().optional(),
125
+ freshness: ShardFreshnessSchema,
126
+ nodes: z.array(NodeSchema).default([]),
127
+ edges: z.array(EdgeSchema).default([]),
128
+ diagnostics: z.array(z.unknown()).default([]),
129
+ });
130
+ // --- manifest.json (spec §5.1) ---
131
+ export const ExtractorConfigSchema = z.object({
132
+ included_extensions: z.array(z.string()),
133
+ ignored_patterns_hash: Sha256Hash,
134
+ max_parse_file_bytes: z.number().int(),
135
+ max_query_wait_ms: z.number().int(),
136
+ });
137
+ export const LanguageEntrySchema = z.object({
138
+ enabled: z.boolean(),
139
+ grammar_name: z.string(),
140
+ grammar_version: z.string(),
141
+ tree_sitter_grammar_hash: Sha256Hash,
142
+ });
143
+ export const ManifestGitSchema = z.object({
144
+ head: z.string().nullable(),
145
+ branch: z.string().nullable(),
146
+ dirty: z.boolean(),
147
+ });
148
+ export const ManifestWorktreeSchema = z.object({
149
+ worktree_id: z.string().nullable(),
150
+ path: z.string().nullable(),
151
+ });
152
+ export const ManifestStatsSchema = z.object({
153
+ files_indexed: z.number().int().default(0),
154
+ nodes: z.number().int().default(0),
155
+ edges: z.number().int().default(0),
156
+ last_full_refresh_ms: z.number().nullable().default(null),
157
+ last_changed_refresh_ms: z.number().nullable().default(null),
158
+ });
159
+ export const ManifestFreshnessSchema = z.object({
160
+ status: FreshnessStatusSchema,
161
+ stale_file_count: z.number().int().default(0),
162
+ partial_reason: z.string().nullable().default(null),
163
+ });
164
+ export const ManifestSchema = z.object({
165
+ schema_version: z.number().int().default(CODE_MAP_SCHEMA_VERSION),
166
+ project_id: z.string(),
167
+ code_map_enabled: z.boolean().default(true),
168
+ project_root: z.string(),
169
+ code_map_version: z.number().int().default(1),
170
+ store_created_at: z.string(),
171
+ updated_at: z.string(),
172
+ active_backend: z.string().default('jsonl'),
173
+ extractor_version: z.string(),
174
+ extractor_config_hash: Sha256Hash,
175
+ engine_glue_hash: Sha256Hash.nullable().default(null),
176
+ extractor_config: ExtractorConfigSchema,
177
+ languages: z.record(z.string(), LanguageEntrySchema).default({}),
178
+ git: ManifestGitSchema,
179
+ worktree: ManifestWorktreeSchema,
180
+ stats: ManifestStatsSchema,
181
+ freshness: ManifestFreshnessSchema,
182
+ });
183
+ // --- profiler.json (spec §5.2) ---
184
+ export const ProfilerRootSchema = z.object({
185
+ path: z.string(),
186
+ kind: z.string(),
187
+ manifest_files: z.array(z.string()).default([]),
188
+ source_file_count: z.number().int().default(0),
189
+ estimated_loc: z.number().int().default(0),
190
+ recommended: z.boolean().default(false),
191
+ });
192
+ export const ProfilerSchema = z.object({
193
+ schema_version: z.number().int().default(CODE_MAP_SCHEMA_VERSION),
194
+ project_id: z.string(),
195
+ last_profiled_at: z.string(),
196
+ detection_method: z.string().default('manifest-and-source-scan'),
197
+ updated_at: z.string(),
198
+ roots: z.array(ProfilerRootSchema).default([]),
199
+ ignored: z.object({
200
+ patterns: z.array(z.string()).default([]),
201
+ source: z.array(z.string()).default([]),
202
+ }),
203
+ });
204
+ // --- indexes (spec §5.6, §5.7) ---
205
+ export const SymbolIndexEntrySchema = z.object({
206
+ node_id: z.string(),
207
+ name: z.string(),
208
+ kind: NodeKindSchema,
209
+ subtype: NodeSubtypeSchema.nullable().optional(),
210
+ path: z.string(),
211
+ file_id: Sha256Hash,
212
+ score_hint: z.number().default(1.0),
213
+ });
214
+ export const SymbolsIndexSchema = z.object({
215
+ schema_version: z.number().int().default(CODE_MAP_SCHEMA_VERSION),
216
+ project_id: z.string(),
217
+ updated_at: z.string(),
218
+ extractor_version: z.string(),
219
+ /** Keys are normalized lowercase tokens. */
220
+ entries: z.record(z.string(), z.array(SymbolIndexEntrySchema)).default({}),
221
+ });
222
+ export const ImportIndexEntrySchema = z.object({
223
+ path: z.string(),
224
+ file_id: Sha256Hash,
225
+ imported: z.array(z.string()).default([]),
226
+ });
227
+ export const ImportsIndexSchema = z.object({
228
+ schema_version: z.number().int().default(CODE_MAP_SCHEMA_VERSION),
229
+ project_id: z.string(),
230
+ updated_at: z.string(),
231
+ /** Keys are module specifiers (e.g. "react"). */
232
+ entries: z.record(z.string(), z.array(ImportIndexEntrySchema)).default({}),
233
+ });
234
+ // --- resolution index (P1d) — reverse dependency maps over the P1c graph ---
235
+ /**
236
+ * One DEPENDENT of a target (file or symbol): the importing file + enough metadata
237
+ * to lazy-validate it (file_id) and explain WHY it appears (module specifier the
238
+ * import was written as, source-side imported names, edge confidence).
239
+ */
240
+ export const DependencyIndexEntrySchema = z.object({
241
+ /** Importer file path (POSIX, store identity). */
242
+ path: z.string(),
243
+ /** Importer shard file id (for read-path freshness validation). */
244
+ file_id: Sha256Hash,
245
+ /** Module specifier the importer wrote (e.g. `./b`, `.core`), for reason text. */
246
+ module: z.string().optional(),
247
+ /** Source-side imported names carried on the importing module node. */
248
+ imported: z.array(z.string()).default([]),
249
+ /** Resolution edge confidence (inherited from the A file resolution). */
250
+ confidence: z.number().optional(),
251
+ });
252
+ /**
253
+ * Reverse dependency index (P1d): "who imports this target". Built at refresh from
254
+ * the in-memory shards' `resolves_to` / `imports_symbol` edges (post-pass), so
255
+ * `bclaw_code_brief` can surface a target's dependents (blast radius) without a
256
+ * read-path scan of every shard. Forward deps are read straight from a target's own
257
+ * shard, so they need no index.
258
+ */
259
+ export const ResolutionIndexSchema = z.object({
260
+ schema_version: z.number().int().default(CODE_MAP_SCHEMA_VERSION),
261
+ project_id: z.string(),
262
+ updated_at: z.string(),
263
+ /** Keys are TARGET file paths (reverse `resolves_to`). */
264
+ dependents_by_file: z.record(z.string(), z.array(DependencyIndexEntrySchema)).default({}),
265
+ /** Keys are TARGET symbol node ids (reverse `imports_symbol`). */
266
+ dependents_by_symbol: z.record(z.string(), z.array(DependencyIndexEntrySchema)).default({}),
267
+ });
268
+ // --- .lock (spec §5.8) ---
269
+ export const CodeLockSchema = z.object({
270
+ schema_version: z.number().int().default(CODE_MAP_SCHEMA_VERSION),
271
+ lock_id: z.string(),
272
+ project_id: z.string().nullable().optional(),
273
+ worktree_id: z.string().nullable().optional(),
274
+ owner_agent: z.string().nullable().optional(),
275
+ owner_agent_id: z.string().nullable().optional(),
276
+ pid: z.number().int(),
277
+ operation: z.string(),
278
+ scope: z.string(),
279
+ created_at: z.string(),
280
+ heartbeat_at: z.string(),
281
+ stale_after_ms: z.number().int(),
282
+ });
283
+ /** Freshness badge attached to every agent-facing read response (spec §9). */
284
+ export const FreshnessBadgeSchema = z.object({
285
+ status: FreshnessStatusSchema,
286
+ details: z.record(z.string(), z.unknown()).default({}),
287
+ });
288
+ //# sourceMappingURL=types.js.map