@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 +54 -8
- package/dist/{chunk-CDGQGCVN.js → chunk-DT3ZH4AU.js} +90 -25
- package/dist/chunk-DT3ZH4AU.js.map +1 -0
- package/dist/{chunk-HZ4UGA6J.js → chunk-WB7WZKX4.js} +1392 -333
- package/dist/chunk-WB7WZKX4.js.map +1 -0
- package/dist/{cli-router-LO2SWFC4.js → cli-router-DDHH4WQH.js} +3718 -946
- package/dist/cli-router-DDHH4WQH.js.map +1 -0
- package/dist/index.js +273 -274
- package/dist/index.js.map +1 -1
- package/dist/json-index-reader-OXTRTGE6.js +13 -0
- package/llms-full.txt +270 -28
- package/llms.txt +62 -10
- package/package.json +8 -6
- package/pages/visualize-template.html +8 -2
- package/skills/ctxo-review-pr/SKILL.md +2 -0
- package/skills/ctxo-safe-edit/SKILL.md +2 -0
- package/skills/ctxo-understand/SKILL.md +28 -4
- package/dist/chunk-CDGQGCVN.js.map +0 -1
- package/dist/chunk-HZ4UGA6J.js.map +0 -1
- package/dist/cli-router-LO2SWFC4.js.map +0 -1
- package/dist/json-index-reader-UYS74USB.js +0 -9
- /package/dist/{json-index-reader-UYS74USB.js.map → json-index-reader-OXTRTGE6.js.map} +0 -0
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
|
|
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
|
|
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
|
|
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;
|
|
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/)
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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 ===
|
|
243
|
+
if (entry.name === COMMUNITY_HISTORY_DIR) continue;
|
|
186
244
|
files.push(...this.collectJsonFiles(fullPath, visited));
|
|
187
|
-
} else if (entry.name.endsWith(".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-
|
|
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":[]}
|