@ctxo/cli 0.12.0 → 0.13.1

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 CHANGED
@@ -20,7 +20,7 @@ ctxo index
20
20
  <img src="https://img.shields.io/badge/Get%20Started-%E2%86%92-06b6d4?style=for-the-badge&labelColor=0f172a" alt="Get Started →">
21
21
  </a>
22
22
 
23
- <sub>Detects your languages, installs the right plugins, wires Ctxo into your AI client, installs git hooks, and builds the first index one command.</sub>
23
+ <sub>Detects your languages, installs the right plugins, wires Ctxo into your AI client, installs git hooks, and builds the first index, all in one command.</sub>
24
24
 
25
25
  </div>
26
26
 
@@ -28,7 +28,7 @@ ctxo index
28
28
 
29
29
  ## The Problem: agents code blind
30
30
 
31
- Drop a modern coding agent into a real repo. It ripgreps a symbol, gets 47 hits, reads five files to find the definition, five more for callers **misses the subclass entirely** (inheritance doesn't show up in text search), **never checks git history** (and confidently reintroduces a bug that was reverted three weeks ago), then runs out of context and starts hallucinating.
31
+ Drop a modern coding agent into a real repo. It ripgreps a symbol, gets 47 hits, reads five files to find the definition, five more for callers, **misses the subclass entirely** (inheritance doesn't show up in text search), **never checks git history** (and confidently reintroduces a bug that was reverted three weeks ago), then runs out of context and starts hallucinating.
32
32
 
33
33
  It's not a skill gap. It's a **sensory gap**. The agent is navigating your codebase with its eyes closed and a phone book.
34
34
 
@@ -36,11 +36,11 @@ It's not a skill gap. It's a **sensory gap**. The agent is navigating your codeb
36
36
 
37
37
  The core shift: your agent stops reacting to files it stumbles into and starts planning from a complete map. **Blast radius before the edit. Git intent before the bug fix. Importer list before the rename.**
38
38
 
39
- Ctxo indexes your repo once kept fresh by file watchers and git hooks into a deterministic graph: every symbol, every edge (imports, calls, extends, implements), every relevant git commit with intent classified, every anti-pattern. All exposed through 14 semantic MCP tools.
39
+ Ctxo indexes your repo once, kept fresh by file watchers and git hooks, into a deterministic graph: every symbol, every edge (imports, calls, extends, implements, uses, and the data edges reads, writes and executes), every relevant git commit with intent classified, every anti-pattern. All exposed through 14 semantic MCP tools.
40
40
 
41
41
  One `get_blast_radius` call replaces an entire ripgrep/read spiral. One `get_pr_impact` replaces a full review session of "wait, what calls this?"
42
42
 
43
- The agent still writes the code. It just stops writing it **blind** so the bug never has to be caught by the compiler, the tests, CI, or a user.
43
+ The agent still writes the code. It just stops writing it **blind**, so the bug never has to be caught by the compiler, the tests, CI, or a user.
44
44
 
45
45
  ## Codebase Dashboard
46
46
 
@@ -71,6 +71,26 @@ The safe-edit guard closes this loop deterministically. When you run `ctxo init`
71
71
 
72
72
  If an agent tries to edit a high-impact symbol without first calling `get_blast_radius`, the hook blocks the edit and tells the agent what to check. Once the agent runs the tool, the edit goes through normally. Fires once per symbol per session, fail-open on any uncertainty.
73
73
 
74
+ With the hook and the skills doing the work, the always-loaded `CLAUDE.md` block shrinks to four lines:
75
+
76
+ ```markdown
77
+ ## ctxo
78
+
79
+ This repo has a ctxo index: dependency graph, git intent, change health. For structural questions (who depends on X, what breaks if X changes, why X looks like this), use the ctxo MCP tools before grepping.
80
+ A pre-edit guard blocks the first edit to a high-impact symbol until `get_blast_radius` has been called for it.
81
+ Skills: `ctxo-understand` (orient at task start), `ctxo-safe-edit` (before changing a symbol), `ctxo-review-pr` (before reviewing a diff).
82
+ ```
83
+
84
+ Every other client, Claude Code without the guard, and a `CLAUDE.md` that another client reads too (an `AGENTS.md` linked to it) get the full tool guide. Re-running `ctxo init` replaces the block in place and keeps the rest of your file. The blocks and skills are written in Prettier's layout and compared with formatting ignored, so a markdown formatter and ctxo never rewrite each other. `ctxo doctor` warns when the block, the skills or the hook drift, and `ctxo doctor --fix` repairs them.
85
+
86
+ **Pick the parts:**
87
+
88
+ ```shell
89
+ ctxo init --only rules,skills,guard # add the guard and the skills; the rules block follows
90
+ ctxo init --skip git-hooks # everything except the git hooks
91
+ ctxo init --no-guard # does not install the guard (CLAUDE.md gets the full block unless a guard is already registered)
92
+ ```
93
+
74
94
  **Tune it:**
75
95
 
76
96
  ```shell
@@ -97,19 +117,45 @@ gate:
97
117
  | Cursor | no (no blocking pre-edit hook) | yes | yes |
98
118
  | Other | no | where supported | yes |
99
119
 
120
+ ## Fresh on every commit
121
+
122
+ A stale index is worse than none: the agent trusts a map that no longer matches the code. `ctxo init` installs a post-commit hook that runs `ctxo index --commit HEAD` once per commit. It re-indexes only the files that commit changed, refreshes the co-change matrix from git history, and records one snapshot per commit for the drift and boundary signals.
123
+
124
+ ```shell
125
+ ctxo index --commit HEAD # what the hook runs
126
+ ctxo index --files src/a.ts src/b.ts # re-index a few files
127
+ ctxo index --cochange-commits 500 --cochange-since 12m # co-change window (or `cochange:` in config.yaml)
128
+ ctxo doctor --fix # upgrades an old hook block in place
129
+ ```
130
+
131
+ The file you forget to touch is usually the one no import points at. Files that keep changing together are coupled anyway: `get_blast_radius` promotes a dependent that co-changes with your target from `potential` to `likely`, and lists the files with no dependency path at all as `coChangedFiles`. `ctxo index --max-history` only controls how many commits each file keeps for `get_why_context`; the co-change window has its own settings.
132
+
133
+ ## Where the boundaries really are
134
+
135
+ Folder names say one thing; imports and commit history often say another. `ctxo index` also writes candidate bounded contexts: files clustered by dependency and co-change edges, with hub files damped, each with a suggested label, a cohesion score, top terms and the edges that cross into other candidates. With `@ctxo/lang-sql` installed, tables, views and procedures join the graph: a table's readers and writers show up in its blast radius, and each candidate lists the tables it shares with other candidates.
136
+
137
+ ```shell
138
+ ctxo contexts # candidates as a table
139
+ ctxo contexts --json # the full contexts-view.json
140
+ ```
141
+
142
+ Agents get the same view from `get_architectural_overlay(mode: "contexts")`, and one candidate in full with `candidate: <id>`.
143
+
100
144
  ## Supported Languages
101
145
 
102
146
  | Language | Plugin | Tier | Notes |
103
147
  |---|---|---|---|
104
- | TypeScript / JavaScript | `@ctxo/lang-typescript` | full | ts-morph; type-aware cross-file resolution |
105
- | Go | `@ctxo/lang-go` | syntax | tree-sitter; exported-symbol analysis |
148
+ | TypeScript / JavaScript | `@ctxo/lang-typescript` | full | ts-morph; type-aware cross-file resolution; decorators as annotations; test blocks as symbols; SQL at known call sites (pg, mysql2, mssql, knex, Prisma, TypeORM, Sequelize, better-sqlite3 raw SQL) |
149
+ | Go | `@ctxo/lang-go` | syntax / full | `ctxo-go-analyzer` full tier (Go 1.22+), tree-sitter fallback; struct tags as annotations; `_test.go` functions as symbols (full tier); SQL at known call sites (`database/sql`, sqlx, pgx, gorm raw; full tier) |
106
150
  | C# | `@ctxo/lang-csharp` | full | Roslyn; .NET SDK 8+ for full tier, tree-sitter fallback |
107
- | Java | `@ctxo/lang-java` | syntax / full | tree-sitter syntax tier built-in; full tier via `@ctxo/lang-java-analyzer` companion (JRE 11+ required, covers Java 8-21); detected by `pom.xml` / `build.gradle` |
151
+ | Java | `@ctxo/lang-java` | syntax / full | tree-sitter syntax tier built-in; full tier via `@ctxo/lang-java-analyzer` companion (JRE 11+ required, covers Java 8-21); detected by `pom.xml` / `build.gradle`; annotations in both tiers; test methods with their annotations; SQL at known call sites (JDBC, Spring `JdbcTemplate`, JPA native queries, MyBatis; full tier) |
152
+ | SQL (T-SQL) | `@ctxo/lang-sql` | syntax | `.sql` tables, views, procedures and functions with `reads` / `writes` / `executes` edges; no database or .NET needed. With it installed, the C# plugin's Roslyn tier also links constant SQL at ADO.NET, Dapper, `SqlBulkCopy` and EF Core call sites to the same tables, and so do the TypeScript, Java and Go plugins at their known call sites |
108
153
 
109
154
  Install via the CLI shortcut:
110
155
 
111
156
  ```bash
112
157
  ctxo install typescript go csharp # syntax or full tier auto-detected
158
+ ctxo install sql # T-SQL plugin (alias tsql)
113
159
  ctxo install java # installs syntax tier; adds full-tier analyzer when JRE 11+ detected
114
160
  ctxo install java --full-tier # force full-tier analyzer install
115
161
  ctxo install java --syntax-only # skip analyzer, syntax tier only
@@ -117,7 +163,7 @@ ctxo install java --syntax-only # skip analyzer, syntax tier only
117
163
 
118
164
  ## Links
119
165
 
120
- * [Docs](https://alperhankendi.github.io/Ctxo/docs/) quick start, MCP tools, CLI reference, integrations
166
+ * [Docs](https://alperhankendi.github.io/Ctxo/docs/): quick start, MCP tools, CLI reference, integrations
121
167
  * [npm](https://www.npmjs.com/package/@ctxo/cli)
122
168
  * [Changelog](CHANGELOG.md)
123
169
  * [LLM Reference](llms-full.txt)
@@ -9,22 +9,8 @@ import { join, relative } from "path";
9
9
 
10
10
  // src/core/types.ts
11
11
  import { z } from "zod";
12
- var SYMBOL_KINDS = [
13
- "function",
14
- "class",
15
- "interface",
16
- "method",
17
- "variable",
18
- "type"
19
- ];
12
+ import { SYMBOL_KINDS, EDGE_KINDS, FACT_KINDS } from "@ctxo/plugin-api";
20
13
  var SymbolKindSchema = z.enum(SYMBOL_KINDS);
21
- var EDGE_KINDS = [
22
- "imports",
23
- "calls",
24
- "extends",
25
- "implements",
26
- "uses"
27
- ];
28
14
  var EdgeKindSchema = z.enum(EDGE_KINDS);
29
15
  var DetailLevelSchema = z.union([
30
16
  z.literal(1),
@@ -52,6 +38,11 @@ var EdgeTargetSchema = z.string().refine(
52
38
  },
53
39
  { message: "edge target must be <file>::<name>::<kind> or <name>::<kind>" }
54
40
  );
41
+ var SymbolAnnotationSchema = z.object({
42
+ name: z.string().min(1),
43
+ args: z.array(z.string()).readonly().optional(),
44
+ named: z.record(z.string(), z.string()).readonly().optional()
45
+ });
55
46
  var SymbolNodeSchema = z.object({
56
47
  symbolId: SymbolIdSchema,
57
48
  name: z.string().min(1),
@@ -59,7 +50,8 @@ var SymbolNodeSchema = z.object({
59
50
  startLine: z.number().int().nonnegative(),
60
51
  endLine: z.number().int().nonnegative(),
61
52
  startOffset: z.number().int().nonnegative().optional(),
62
- endOffset: z.number().int().nonnegative().optional()
53
+ endOffset: z.number().int().nonnegative().optional(),
54
+ annotations: z.array(SymbolAnnotationSchema).readonly().optional()
63
55
  }).refine((node) => node.endLine >= node.startLine, {
64
56
  message: "endLine must be >= startLine"
65
57
  });
@@ -82,6 +74,14 @@ var AntiPatternSchema = z.object({
82
74
  message: z.string(),
83
75
  date: z.string().min(1)
84
76
  });
77
+ var FactKindSchema = z.enum(FACT_KINDS);
78
+ var CodeFactSchema = z.object({
79
+ kind: FactKindSchema,
80
+ symbolId: SymbolIdSchema,
81
+ line: z.number().int().nonnegative(),
82
+ text: z.string(),
83
+ detail: z.record(z.string(), z.string()).readonly().optional()
84
+ });
85
85
  var ComplexityMetricsSchema = z.object({
86
86
  symbolId: z.string().min(1),
87
87
  cyclomatic: z.number().nonnegative()
@@ -94,17 +94,56 @@ var FileIndexSchema = z.object({
94
94
  edges: z.array(GraphEdgeSchema),
95
95
  complexity: z.array(ComplexityMetricsSchema).optional(),
96
96
  intent: z.array(CommitIntentSchema),
97
- antiPatterns: z.array(AntiPatternSchema)
97
+ antiPatterns: z.array(AntiPatternSchema),
98
+ facts: z.array(CodeFactSchema).readonly().optional()
99
+ });
100
+ var CoChangeEntrySchema = z.object({
101
+ file1: z.string().min(1),
102
+ file2: z.string().min(1),
103
+ sharedCommits: z.number().int().min(1),
104
+ frequency: z.number().min(0).max(1)
105
+ });
106
+ var CoChangeWindowMetaSchema = z.object({
107
+ maxCommits: z.union([z.number().int().min(1), z.literal("all")]),
108
+ since: z.string().optional(),
109
+ head: z.string().min(1),
110
+ commitsScanned: z.number().int().min(0),
111
+ commitsUsed: z.number().int().min(0),
112
+ massSkipped: z.number().int().min(0),
113
+ maxFilesPerCommit: z.number().int().min(1),
114
+ minShared: z.number().int().min(1)
115
+ });
116
+ var CoChangeMatrixSchema = z.object({
117
+ version: z.literal(1),
118
+ timestamp: z.number(),
119
+ window: CoChangeWindowMetaSchema.optional(),
120
+ entries: z.array(CoChangeEntrySchema)
98
121
  });
99
122
 
100
123
  // src/adapters/storage/json-index-reader.ts
101
124
  var log = createLogger("ctxo:json-reader");
125
+ var INDEX_ARTIFACT_FILES = [
126
+ "co-changes.json",
127
+ "communities.json",
128
+ "drift-events.json",
129
+ "boundary-violations.json",
130
+ "contexts-view.json"
131
+ ];
132
+ var COMMUNITY_HISTORY_DIR = "communities.history";
102
133
  var JsonIndexReader = class {
103
134
  indexDir;
104
135
  constructor(ctxoRoot) {
105
136
  this.indexDir = join(ctxoRoot, "index");
106
137
  }
107
138
  readAll() {
139
+ return this.readEntries().map((entry) => entry.index);
140
+ }
141
+ /**
142
+ * Every valid index entry together with the path of the JSON file it came from. A caller that wants to
143
+ * delete an entry must use this path: the `file` field inside the JSON says which source file the entry
144
+ * is for, but a corrupt or crafted file can claim any name, such as an artifact's.
145
+ */
146
+ readEntries() {
108
147
  if (!existsSync(this.indexDir)) {
109
148
  return [];
110
149
  }
@@ -113,7 +152,7 @@ var JsonIndexReader = class {
113
152
  for (const filePath of jsonFiles) {
114
153
  const parsed = this.readSingle(filePath);
115
154
  if (parsed) {
116
- results.push(parsed);
155
+ results.push({ path: filePath, index: parsed });
117
156
  }
118
157
  }
119
158
  return results;
@@ -138,17 +177,35 @@ var JsonIndexReader = class {
138
177
  const rawEdges = Array.isArray(raw_data["edges"]) ? raw_data["edges"] : [];
139
178
  const rawIntent = Array.isArray(raw_data["intent"]) ? raw_data["intent"] : [];
140
179
  const rawAntiPatterns = Array.isArray(raw_data["antiPatterns"]) ? raw_data["antiPatterns"] : [];
141
- const validSymbols = rawSymbols.filter((s) => SymbolNodeSchema.safeParse(s).success);
180
+ let droppedAnnotations = 0;
181
+ const validSymbols = [];
182
+ for (const sym of rawSymbols) {
183
+ if (SymbolNodeSchema.safeParse(sym).success) {
184
+ validSymbols.push(sym);
185
+ continue;
186
+ }
187
+ if (sym !== null && typeof sym === "object" && "annotations" in sym) {
188
+ const { annotations: _dropped, ...withoutAnnotations } = sym;
189
+ if (SymbolNodeSchema.safeParse(withoutAnnotations).success) {
190
+ validSymbols.push(withoutAnnotations);
191
+ droppedAnnotations++;
192
+ }
193
+ }
194
+ }
195
+ const factsPresent = raw_data["facts"] !== void 0;
196
+ const rawFacts = Array.isArray(raw_data["facts"]) ? raw_data["facts"] : void 0;
197
+ const validFacts = rawFacts?.filter((f) => CodeFactSchema.safeParse(f).success);
142
198
  const validEdges = rawEdges.filter((e) => GraphEdgeSchema.safeParse(e).success);
143
199
  const validIntent = rawIntent.filter((i) => CommitIntentSchema.safeParse(i).success);
144
200
  const validAntiPatterns = rawAntiPatterns.filter((a) => AntiPatternSchema.safeParse(a).success);
145
201
  const droppedSymbols = rawSymbols.length - validSymbols.length;
202
+ const droppedFacts = rawFacts && validFacts ? rawFacts.length - validFacts.length : factsPresent ? 1 : 0;
146
203
  const droppedEdges = rawEdges.length - validEdges.length;
147
204
  const droppedIntent = rawIntent.length - validIntent.length;
148
205
  const droppedAntiPatterns = rawAntiPatterns.length - validAntiPatterns.length;
149
- if (droppedSymbols > 0 || droppedEdges > 0 || droppedIntent > 0 || droppedAntiPatterns > 0) {
206
+ if (droppedSymbols > 0 || droppedEdges > 0 || droppedIntent > 0 || droppedAntiPatterns > 0 || droppedAnnotations > 0 || droppedFacts > 0) {
150
207
  log.warn(
151
- `${rel}: dropped ${droppedSymbols} invalid symbol(s), ${droppedEdges} invalid edge(s), ${droppedIntent} invalid intent(s), ${droppedAntiPatterns} invalid antiPattern(s) \u2014 kept ${validSymbols.length} symbol(s), ${validEdges.length} edge(s), ${validIntent.length} intent(s), ${validAntiPatterns.length} antiPattern(s)`
208
+ `${rel}: dropped ${droppedSymbols} invalid symbol(s), ${droppedEdges} invalid edge(s), ${droppedIntent} invalid intent(s), ${droppedAntiPatterns} invalid antiPattern(s), ${droppedAnnotations} invalid annotation list(s), ${droppedFacts} invalid fact(s); kept ${validSymbols.length} symbol(s), ${validEdges.length} edge(s), ${validIntent.length} intent(s), ${validAntiPatterns.length} antiPattern(s)`
152
209
  );
153
210
  }
154
211
  const recovered = FileIndexSchema.safeParse({
@@ -156,7 +213,8 @@ var JsonIndexReader = class {
156
213
  symbols: validSymbols,
157
214
  edges: validEdges,
158
215
  intent: validIntent,
159
- antiPatterns: validAntiPatterns
216
+ antiPatterns: validAntiPatterns,
217
+ facts: validFacts
160
218
  });
161
219
  if (!recovered.success) {
162
220
  console.error(
@@ -182,9 +240,9 @@ var JsonIndexReader = class {
182
240
  if (entry.isSymbolicLink()) continue;
183
241
  const fullPath = join(dir, entry.name);
184
242
  if (entry.isDirectory()) {
185
- if (entry.name === "communities.history") continue;
243
+ if (entry.name === COMMUNITY_HISTORY_DIR) continue;
186
244
  files.push(...this.collectJsonFiles(fullPath, visited));
187
- } else if (entry.name.endsWith(".json") && entry.name !== "co-changes.json" && entry.name !== "communities.json" && entry.name !== "drift-events.json" && entry.name !== "boundary-violations.json") {
245
+ } else if (entry.name.endsWith(".json") && !INDEX_ARTIFACT_FILES.includes(entry.name)) {
188
246
  files.push(fullPath);
189
247
  }
190
248
  }
@@ -193,7 +251,14 @@ var JsonIndexReader = class {
193
251
  };
194
252
 
195
253
  export {
254
+ SYMBOL_KINDS,
255
+ EDGE_KINDS,
256
+ SymbolKindSchema,
257
+ EdgeKindSchema,
196
258
  DetailLevelSchema,
259
+ CoChangeMatrixSchema,
260
+ INDEX_ARTIFACT_FILES,
261
+ COMMUNITY_HISTORY_DIR,
197
262
  JsonIndexReader
198
263
  };
199
- //# sourceMappingURL=chunk-CDGQGCVN.js.map
264
+ //# sourceMappingURL=chunk-DT3ZH4AU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/storage/json-index-reader.ts","../src/core/types.ts"],"sourcesContent":["import { readFileSync, readdirSync, existsSync, realpathSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport {\n FileIndexSchema,\n SymbolNodeSchema,\n GraphEdgeSchema,\n CommitIntentSchema,\n AntiPatternSchema,\n CodeFactSchema,\n type FileIndex,\n} from '../../core/types.js';\nimport { createLogger } from '../../core/logger.js';\n\nconst log = createLogger('ctxo:json-reader');\n\n/** JSON files next to the index entries that are products of Ctxo, not entries: the reader skips them. */\nexport const INDEX_ARTIFACT_FILES: readonly string[] = [\n 'co-changes.json',\n 'communities.json',\n 'drift-events.json',\n 'boundary-violations.json',\n 'contexts-view.json',\n];\n\n/** The folder of archived community snapshots inside the index folder: not entries either. */\nexport const COMMUNITY_HISTORY_DIR = 'communities.history';\n\n/** One index entry and the JSON file on disk it was read from. */\nexport interface IndexEntry {\n readonly path: string;\n readonly index: FileIndex;\n}\n\nexport class JsonIndexReader {\n private readonly indexDir: string;\n\n constructor(ctxoRoot: string) {\n this.indexDir = join(ctxoRoot, 'index');\n }\n\n readAll(): FileIndex[] {\n return this.readEntries().map((entry) => entry.index);\n }\n\n /**\n * Every valid index entry together with the path of the JSON file it came from. A caller that wants to\n * delete an entry must use this path: the `file` field inside the JSON says which source file the entry\n * is for, but a corrupt or crafted file can claim any name, such as an artifact's.\n */\n readEntries(): IndexEntry[] {\n if (!existsSync(this.indexDir)) {\n return [];\n }\n\n const jsonFiles = this.collectJsonFiles(this.indexDir);\n const results: IndexEntry[] = [];\n\n for (const filePath of jsonFiles) {\n const parsed = this.readSingle(filePath);\n if (parsed) {\n results.push({ path: filePath, index: parsed });\n }\n }\n\n return results;\n }\n\n readSingle(absolutePath: string): FileIndex | undefined {\n try {\n const raw = readFileSync(absolutePath, 'utf-8');\n const data: unknown = JSON.parse(raw);\n const result = FileIndexSchema.safeParse(data);\n\n if (result.success) {\n return result.data;\n }\n\n // Strict parse failed — attempt resilient recovery: keep valid items, drop invalid ones.\n const rel = relative(this.indexDir, absolutePath);\n const raw_data = data as Record<string, unknown>;\n\n // Must have at least a valid file name and lastModified to be recoverable.\n if (typeof raw_data['file'] !== 'string' || !raw_data['file'] ||\n typeof raw_data['lastModified'] !== 'number') {\n console.error(\n `[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`,\n );\n return undefined;\n }\n\n const rawSymbols = Array.isArray(raw_data['symbols']) ? raw_data['symbols'] : [];\n const rawEdges = Array.isArray(raw_data['edges']) ? raw_data['edges'] : [];\n const rawIntent = Array.isArray(raw_data['intent']) ? raw_data['intent'] : [];\n const rawAntiPatterns = Array.isArray(raw_data['antiPatterns']) ? raw_data['antiPatterns'] : [];\n\n // A symbol that fails only because of its annotations is kept without them: annotations are\n // optional metadata and must not cost the symbol.\n let droppedAnnotations = 0;\n const validSymbols: unknown[] = [];\n for (const sym of rawSymbols) {\n if (SymbolNodeSchema.safeParse(sym).success) {\n validSymbols.push(sym);\n continue;\n }\n if (sym !== null && typeof sym === 'object' && 'annotations' in sym) {\n const { annotations: _dropped, ...withoutAnnotations } = sym as Record<string, unknown>;\n if (SymbolNodeSchema.safeParse(withoutAnnotations).success) {\n validSymbols.push(withoutAnnotations);\n droppedAnnotations++;\n }\n }\n }\n // A facts value that is present but not an array is treated as absent: facts are optional metadata\n // and must not cost the file. It counts as one dropped fact list in the warning.\n const factsPresent = raw_data['facts'] !== undefined;\n const rawFacts = Array.isArray(raw_data['facts']) ? raw_data['facts'] : undefined;\n const validFacts = rawFacts?.filter((f) => CodeFactSchema.safeParse(f).success);\n const validEdges = rawEdges.filter((e) => GraphEdgeSchema.safeParse(e).success);\n const validIntent = rawIntent.filter((i) => CommitIntentSchema.safeParse(i).success);\n const validAntiPatterns = rawAntiPatterns.filter((a) => AntiPatternSchema.safeParse(a).success);\n\n const droppedSymbols = rawSymbols.length - validSymbols.length;\n const droppedFacts = rawFacts && validFacts\n ? rawFacts.length - validFacts.length\n : factsPresent ? 1 : 0;\n const droppedEdges = rawEdges.length - validEdges.length;\n const droppedIntent = rawIntent.length - validIntent.length;\n const droppedAntiPatterns = rawAntiPatterns.length - validAntiPatterns.length;\n\n if (droppedSymbols > 0 || droppedEdges > 0 || droppedIntent > 0 || droppedAntiPatterns > 0 ||\n droppedAnnotations > 0 || droppedFacts > 0) {\n log.warn(\n `${rel}: dropped ${droppedSymbols} invalid symbol(s), ${droppedEdges} invalid edge(s), ${droppedIntent} invalid intent(s), ${droppedAntiPatterns} invalid antiPattern(s), ${droppedAnnotations} invalid annotation list(s), ${droppedFacts} invalid fact(s); kept ${validSymbols.length} symbol(s), ${validEdges.length} edge(s), ${validIntent.length} intent(s), ${validAntiPatterns.length} antiPattern(s)`,\n );\n }\n\n // Re-parse with the filtered arrays to get a fully-typed FileIndex.\n const recovered = FileIndexSchema.safeParse({\n ...raw_data,\n symbols: validSymbols,\n edges: validEdges,\n intent: validIntent,\n antiPatterns: validAntiPatterns,\n facts: validFacts,\n });\n\n if (!recovered.success) {\n console.error(\n `[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`,\n );\n return undefined;\n }\n\n return recovered.data;\n } catch (err) {\n const rel = relative(this.indexDir, absolutePath);\n console.error(\n `[ctxo:json-reader] Failed to read ${rel}: ${(err as Error).message}`,\n );\n return undefined;\n }\n }\n\n private collectJsonFiles(dir: string, visited: Set<string> = new Set()): string[] {\n const realDir = realpathSync(dir);\n if (visited.has(realDir)) return []; // Guard against symlink loops\n visited.add(realDir);\n\n const files: string[] = [];\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.isSymbolicLink()) continue; // Skip symlinks entirely\n\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n // Skip non-FileIndex directories under .ctxo/index/\n if (entry.name === COMMUNITY_HISTORY_DIR) continue;\n files.push(...this.collectJsonFiles(fullPath, visited));\n } else if (entry.name.endsWith('.json') && !INDEX_ARTIFACT_FILES.includes(entry.name)) {\n files.push(fullPath);\n }\n }\n\n return files;\n }\n}\n","import { z } from 'zod';\nimport { SYMBOL_KINDS, EDGE_KINDS, FACT_KINDS } from '@ctxo/plugin-api';\n\n// ── Symbol Kinds ────────────────────────────────────────────────\n\nexport { SYMBOL_KINDS };\n\nexport const SymbolKindSchema = z.enum(SYMBOL_KINDS);\nexport type SymbolKind = z.infer<typeof SymbolKindSchema>;\n\n// ── Edge Kinds ──────────────────────────────────────────────────\n\nexport { EDGE_KINDS };\n\nexport const EdgeKindSchema = z.enum(EDGE_KINDS);\nexport type EdgeKind = z.infer<typeof EdgeKindSchema>;\n\n// ── Detail Levels ───────────────────────────────────────────────\n\nexport const DETAIL_LEVELS = [1, 2, 3, 4] as const;\n\nexport const DetailLevelSchema = z.union([\n z.literal(1),\n z.literal(2),\n z.literal(3),\n z.literal(4),\n]);\nexport type DetailLevel = z.infer<typeof DetailLevelSchema>;\n\n// ── Symbol ID ───────────────────────────────────────────────────\n\n/**\n * Format: \"<relativeFile>::<name>::<kind>\"\n * Example: \"src/foo.ts::myFn::function\"\n */\nexport const SymbolIdSchema = z\n .string()\n .min(1)\n .refine(\n (value) => {\n const parts = value.split('::');\n if (parts.length !== 3) return false;\n const [file, name, kind] = parts;\n if (!file || !name || !kind) return false;\n return SymbolKindSchema.safeParse(kind).success;\n },\n { message: 'Symbol ID must match format \"<file>::<name>::<kind>\"' },\n );\nexport type SymbolId = z.infer<typeof SymbolIdSchema>;\n\n// ── Edge Target ─────────────────────────────────────────────────\n\n/**\n * An edge target: either a full 3-part symbol ID (\"<file>::<name>::<kind>\")\n * or a 2-part name-reference (\"<name>::<kind>\") for cross-file / unresolved\n * targets. The graph builder resolves name-references by name at query time.\n */\nexport const EdgeTargetSchema = z.string().refine(\n (s) => {\n const parts = s.split('::');\n if (parts.length !== 3 && parts.length !== 2) return false;\n if (parts.some((p) => p.length === 0)) return false;\n const kind = parts[parts.length - 1];\n return (SYMBOL_KINDS as readonly string[]).includes(kind as string);\n },\n { message: 'edge target must be <file>::<name>::<kind> or <name>::<kind>' },\n);\nexport type EdgeTarget = z.infer<typeof EdgeTargetSchema>;\n\n// ── Symbol Annotation ───────────────────────────────────────────\n\n/** Host-language attribute or decorator on a symbol (protocol v2). Shape only; plugins enforce caps. */\nexport const SymbolAnnotationSchema = z.object({\n name: z.string().min(1),\n args: z.array(z.string()).readonly().optional(),\n named: z.record(z.string(), z.string()).readonly().optional(),\n});\nexport type SymbolAnnotation = z.infer<typeof SymbolAnnotationSchema>;\n\n// ── Symbol Node ─────────────────────────────────────────────────\n\nexport const SymbolNodeSchema = z\n .object({\n symbolId: SymbolIdSchema,\n name: z.string().min(1),\n kind: SymbolKindSchema,\n startLine: z.number().int().nonnegative(),\n endLine: z.number().int().nonnegative(),\n startOffset: z.number().int().nonnegative().optional(),\n endOffset: z.number().int().nonnegative().optional(),\n annotations: z.array(SymbolAnnotationSchema).readonly().optional(),\n })\n .refine((node) => node.endLine >= node.startLine, {\n message: 'endLine must be >= startLine',\n });\nexport type SymbolNode = z.infer<typeof SymbolNodeSchema>;\n\n// ── Graph Edge ──────────────────────────────────────────────────\n\nexport const GraphEdgeSchema = z.object({\n from: SymbolIdSchema, // strict 3-part: from is always a local, resolved symbol\n to: EdgeTargetSchema, // 3-part OR 2-part name-ref for cross-file/unresolved targets\n kind: EdgeKindSchema,\n typeOnly: z.boolean().optional(),\n});\nexport type GraphEdge = z.infer<typeof GraphEdgeSchema>;\n\n// ── Commit Intent ───────────────────────────────────────────────\n\nexport const CommitIntentSchema = z.object({\n hash: z.string().min(1),\n message: z.string(),\n date: z.string().min(1),\n kind: z.literal('commit'),\n});\nexport type CommitIntent = z.infer<typeof CommitIntentSchema>;\n\n// ── Anti-Pattern ────────────────────────────────────────────────\n\nexport const AntiPatternSchema = z.object({\n hash: z.string().min(1),\n message: z.string(),\n date: z.string().min(1),\n});\nexport type AntiPattern = z.infer<typeof AntiPatternSchema>;\n\n// ── Code Fact ───────────────────────────────────────────────────\n\nexport const FactKindSchema = z.enum(FACT_KINDS);\nexport type FactKind = z.infer<typeof FactKindSchema>;\n\n/** A fact extracted from a symbol body (protocol v2). The line is 0-based, like a symbol's start line. */\nexport const CodeFactSchema = z.object({\n kind: FactKindSchema,\n symbolId: SymbolIdSchema,\n line: z.number().int().nonnegative(),\n text: z.string(),\n detail: z.record(z.string(), z.string()).readonly().optional(),\n});\nexport type CodeFact = z.infer<typeof CodeFactSchema>;\n\n// ── File Index ──────────────────────────────────────────────────\n\nexport const ComplexityMetricsSchema = z.object({\n symbolId: z.string().min(1),\n cyclomatic: z.number().nonnegative(),\n});\n\nexport const FileIndexSchema = z.object({\n file: z.string().min(1),\n lastModified: z.number().nonnegative(),\n contentHash: z.string().optional(),\n symbols: z.array(SymbolNodeSchema),\n edges: z.array(GraphEdgeSchema),\n complexity: z.array(ComplexityMetricsSchema).optional(),\n intent: z.array(CommitIntentSchema),\n antiPatterns: z.array(AntiPatternSchema),\n facts: z.array(CodeFactSchema).readonly().optional(),\n});\nexport type FileIndex = z.infer<typeof FileIndexSchema>;\n\n// ── Logic-Slice Result ──────────────────────────────────────────\n\nexport interface LogicSliceResult {\n readonly root: SymbolNode;\n readonly dependencies: readonly SymbolNode[];\n readonly edges: readonly GraphEdge[];\n}\n\n// ── Formatted Slice ─────────────────────────────────────────────\n\nexport interface TruncationInfo {\n readonly truncated: true;\n readonly reason: 'token_budget_exceeded';\n}\n\nexport interface FormattedSlice {\n readonly root: SymbolNode;\n readonly dependencies: readonly SymbolNode[];\n readonly edges: readonly GraphEdge[];\n readonly level: DetailLevel;\n readonly levelDescription?: string;\n readonly truncation?: TruncationInfo;\n}\n\n// ── Complexity & Churn ──────────────────────────────────────────\n\nexport interface ComplexityMetrics {\n readonly symbolId: string;\n readonly cyclomatic: number;\n}\n\nexport interface ChurnData {\n readonly filePath: string;\n readonly commitCount: number;\n}\n\nexport const SCORE_BANDS = ['low', 'medium', 'high'] as const;\nexport type ScoreBand = (typeof SCORE_BANDS)[number];\n\nexport interface ChangeIntelligenceScore {\n readonly symbolId: string;\n readonly complexity: number;\n readonly churn: number;\n readonly composite: number;\n readonly band: ScoreBand;\n}\n\n// ── Why-Context Result ──────────────────────────────────────────\n\nexport interface WhyContextResult {\n readonly commitHistory: readonly CommitIntent[];\n readonly antiPatternWarnings: readonly AntiPattern[];\n readonly changeIntelligence?: ChangeIntelligenceScore;\n}\n\n// ── Co-Change Analysis ─────────────────────────────────────────\n\nexport interface CoChangeEntry {\n readonly file1: string;\n readonly file2: string;\n readonly sharedCommits: number;\n readonly frequency: number;\n}\n\n/** The window a co-change matrix was built from and the filters that shaped it. */\nexport interface CoChangeWindowMeta {\n /** Configured commit limit: the last N non-merge commits from HEAD, or 'all'. */\n readonly maxCommits: number | 'all';\n /** Configured duration limit such as \"12m\", anchored to HEAD's committer date. */\n readonly since?: string;\n /** Short SHA of HEAD when the matrix was built. */\n readonly head: string;\n /** Commits inside the window. */\n readonly commitsScanned: number;\n /** Window commits that fed the counts. */\n readonly commitsUsed: number;\n /** Window commits skipped for touching more than `maxFilesPerCommit` included files. */\n readonly massSkipped: number;\n readonly maxFilesPerCommit: number;\n readonly minShared: number;\n}\n\nexport interface CoChangeMatrix {\n readonly version: 1;\n readonly timestamp: number;\n /** Absent in matrices written before the window was recorded. */\n readonly window?: CoChangeWindowMeta;\n readonly entries: readonly CoChangeEntry[];\n}\n\nconst CoChangeEntrySchema = z.object({\n file1: z.string().min(1),\n file2: z.string().min(1),\n sharedCommits: z.number().int().min(1),\n frequency: z.number().min(0).max(1),\n});\n\nconst CoChangeWindowMetaSchema = z.object({\n maxCommits: z.union([z.number().int().min(1), z.literal('all')]),\n since: z.string().optional(),\n head: z.string().min(1),\n commitsScanned: z.number().int().min(0),\n commitsUsed: z.number().int().min(0),\n massSkipped: z.number().int().min(0),\n maxFilesPerCommit: z.number().int().min(1),\n minShared: z.number().int().min(1),\n});\n\n/**\n * Validates `co-changes.json`. `entries` is required and `window` optional, so files written before\n * the window was recorded still load. Unknown keys are dropped rather than rejected, so a file from\n * a newer Ctxo stays readable.\n */\nexport const CoChangeMatrixSchema: z.ZodType<CoChangeMatrix> = z.object({\n version: z.literal(1),\n timestamp: z.number(),\n window: CoChangeWindowMetaSchema.optional(),\n entries: z.array(CoChangeEntrySchema),\n});\n\n// ── Commit Record (from git adapter) ────────────────────────────\n\nexport interface CommitRecord {\n readonly hash: string;\n readonly message: string;\n readonly date: string;\n readonly author: string;\n}\n\n// ── Change Log (from git adapter) ───────────────────────────────\n\n/** git name-status letters Ctxo keeps; A added, M modified, D deleted, R renamed, C copied, T type changed. */\nexport type FileChangeStatus = 'A' | 'M' | 'D' | 'R' | 'C' | 'T';\n\nexport interface FileChange {\n readonly status: FileChangeStatus;\n /** Path after the change: repo-relative, forward slashes. */\n readonly path: string;\n /** Path before the change; set only for renames (R) and copies (C). */\n readonly oldPath?: string;\n}\n\n/** One non-merge commit and the files it changed. */\nexport interface CommitChangeSet {\n /** Full commit SHA (git %H). */\n readonly hash: string;\n /** Subject line (git %s). */\n readonly message: string;\n /** Author date, strict ISO 8601 (git %aI). */\n readonly date: string;\n /** Author name (git %an). */\n readonly author: string;\n /** Committer date in unix seconds (git %ct). */\n readonly committedAt: number;\n readonly changes: readonly FileChange[];\n}\n\nexport interface ChangeLog {\n /** Short SHA of HEAD (git %h), which may itself be a merge commit. */\n readonly head: string;\n /** Committer date of HEAD in unix seconds (git %ct). */\n readonly headCommittedAt: number;\n /** Non-merge commits reachable from HEAD, newest first. */\n readonly commits: readonly CommitChangeSet[];\n}\n\n// ── Blame Line (from git adapter) ───────────────────────────────\n\nexport interface BlameLine {\n readonly hash: string;\n readonly lineNumber: number;\n readonly author: string;\n readonly date: string;\n}\n\n// ── Search Types ───────────────────────────────────────────────\n\nexport interface SearchResult {\n symbolId: string;\n name: string;\n kind: string;\n filePath: string;\n /** BM25 relevance score (higher = more relevant) */\n relevanceScore: number;\n /** PageRank importance score (0-1) */\n importanceScore: number;\n /** Final combined score after all boosts */\n combinedScore: number;\n}\n\nexport interface SearchMetrics {\n porterHits: number;\n trigramHits: number;\n phase2Activated: boolean;\n fuzzyApplied: boolean;\n latencyMs: number;\n}\n\nexport interface FuzzyCorrection {\n originalQuery: string;\n correctedQuery: string;\n corrections: Array<{\n original: string;\n corrected: string;\n distance: number;\n }>;\n}\n\nexport interface SearchResponse {\n query: string;\n results: SearchResult[];\n metrics: SearchMetrics;\n fuzzyCorrection?: FuzzyCorrection;\n}\n\n// ── Community Detection ────────────────────────────────────────\n\nexport interface CommunityEntry {\n readonly symbolId: string;\n readonly communityId: number;\n readonly communityLabel: string;\n}\n\nexport interface GodNode {\n readonly symbolId: string;\n readonly bridgedCommunities: readonly number[];\n readonly centralityScore: number;\n}\n\nexport const EDGE_QUALITY = ['full', 'syntax-only', 'mixed'] as const;\nexport type EdgeQuality = (typeof EDGE_QUALITY)[number];\n\nexport interface CommunitySnapshot {\n readonly version: 1;\n readonly computedAt: string;\n readonly commitSha: string;\n readonly modularity: number;\n readonly communities: readonly CommunityEntry[];\n readonly godNodes: readonly GodNode[];\n readonly edgeQuality: EdgeQuality;\n readonly crossClusterEdges: number;\n}\n\nexport interface DriftEvent {\n readonly symbolId: string;\n readonly movedFrom: { readonly id: number; readonly label: string };\n readonly movedTo: { readonly id: number; readonly label: string };\n readonly firstSeenInNewCluster: string;\n}\n\nexport interface BoundaryViolation {\n readonly from: {\n readonly symbolId: string;\n readonly communityId: number;\n readonly label: string;\n };\n readonly to: {\n readonly symbolId: string;\n readonly communityId: number;\n readonly label: string;\n };\n readonly edgeKind: EdgeKind;\n readonly historicalEdgesBetweenClusters: number;\n readonly severity: 'high' | 'medium';\n}\n\n// ── Persisted architectural artifacts ──────────────────────────\n// Written by `ctxo index` next to communities.json so the static\n// HTML visualizer can render Architecture views without re-running\n// detectors in the browser.\n\nexport type ConfidenceLevel = 'high' | 'medium' | 'low';\n\nexport interface DriftEventsArtifact {\n readonly version: 1;\n readonly computedAt: string;\n readonly commitSha: string;\n readonly confidence: ConfidenceLevel;\n /** The number of history entries passed to the detector (kept for compatibility; confidence is built on `distinctCommits`). */\n readonly snapshotsAvailable: number;\n /** Commits other than `commitSha` that the history covers; `confidence` is built on this. */\n readonly distinctCommits: number;\n readonly hint?: string;\n readonly events: readonly DriftEvent[];\n}\n\nexport interface BoundaryViolationsArtifact {\n readonly version: 1;\n readonly computedAt: string;\n readonly commitSha: string;\n readonly confidence: ConfidenceLevel;\n /** The number of history entries passed to the detector (kept for compatibility; confidence is built on `distinctCommits`). */\n readonly snapshotsAvailable: number;\n /** Commits other than `commitSha` that the history covers; `confidence` is built on this. */\n readonly distinctCommits: number;\n readonly hint?: string;\n readonly violations: readonly BoundaryViolation[];\n}\n"],"mappings":"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","names":[]}