repoctx 0.1.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.
package/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # repoctx
2
+
3
+ LLMs are probabilistic reasoners.
4
+ Repositories are deterministic systems.
5
+
6
+ Deterministic context should be extracted outside the LLM.
7
+
8
+ repoctx precomputes repository context — diffs, summaries, symbol cards —
9
+ so the assistant reasons instead of explores.
10
+
11
+ Fast. Local. Minimal.
12
+ No server. No DB. No embeddings. No magic.
13
+
14
+ ## The problem
15
+
16
+ Every new session, your agent reads the same files again to understand the
17
+ codebase. In large repos this means dozens of tool calls before writing a
18
+ single line. The context is thrown away when the session closes.
19
+
20
+ ## How it works
21
+
22
+ - You (or your agent) run `repoctx save <file> "<summary>"` after working on a file
23
+ - repoctx stores the summary, exported symbols, keywords, and a content hash in `.repoctx/`
24
+ - Next session: `repoctx get --keyword auth` returns all indexed modules tagged with that keyword — **0 file reads**
25
+ - If a file changed since the last save, repoctx flags it as stale
26
+
27
+ The index survives session resets. Context is never lost.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ # From npm (recommended)
33
+ npm install -g repoctx
34
+
35
+ # From source
36
+ git clone https://github.com/Ezequiiel98/repoctx.git
37
+ cd repoctx
38
+ npm install
39
+ npm run build
40
+ npm install -g .
41
+ ```
42
+
43
+ ## Quick start
44
+
45
+ ```bash
46
+ cd your-repo
47
+ repoctx init # creates .repoctx/, adds to .gitignore
48
+ repoctx onboarding >> CLAUDE.md # tells Claude how to use repoctx
49
+ ```
50
+
51
+ That's it. From now on Claude can save context as it works and retrieve it at the start of each session.
52
+
53
+ ## Token savings
54
+
55
+ Measured on a real Node.js service (checkout API, 341 source files):
56
+
57
+ | Scenario | Chars read | Tokens (~) | File reads |
58
+ |---|---|---|---|
59
+ | Without repoctx | 3,731 | ~933 | 4 |
60
+ | repoctx (layer not indexed yet) | 3,396 | ~849 | 2 |
61
+ | repoctx (layer fully indexed) | **1,139** | **~285** | **0** |
62
+
63
+ Tokens estimated using ~4 chars per token heuristic.
64
+
65
+ First session costs the same — you're reading the files anyway to work on them.
66
+ From the second session onwards, you pay ~285 tokens instead of ~933 for the
67
+ same context. The savings compound across sessions and teammates.
68
+
69
+ ## Commands
70
+
71
+ ### `repoctx save <file> "<summary>" [options]`
72
+
73
+ Save context for a file. Run this when a file's public contract changes.
74
+
75
+ ```bash
76
+ repoctx save src/users/dal.js "MongoDB DAL for users. CRUD operations." \
77
+ --symbols "createUser,getUser,deleteUser" \
78
+ --keywords "dal,users,mongodb" \
79
+ --deps "userSchema,sessionSchema" \
80
+ --footguns "deleteUser is soft-delete only" \
81
+ --delta "Added deleteUser (soft delete)"
82
+ ```
83
+
84
+ | Option | Description |
85
+ |---|---|
86
+ | `--symbols` | Comma-separated exported functions/classes |
87
+ | `--keywords` | Tags for filtering with `get --keyword`. **Always include these.** |
88
+ | `--deps` | Dependencies this module relies on |
89
+ | `--footguns` | Things that break if touched wrong |
90
+ | `--delta` | What changed in this save |
91
+ | `--meta` | Virtual entry — no real file (for patterns, glossary, folder maps) |
92
+
93
+ ### `repoctx save-symbol <name> "<purpose>" --file <path> [options]`
94
+
95
+ Save a Symbol Card for a specific function or class.
96
+
97
+ ```bash
98
+ repoctx save-symbol deleteUser "Hard delete a user from MongoDB" \
99
+ --file src/users/dal.js \
100
+ --kind function \
101
+ --signature "({ userSchema }) => async ({ id }) => Promise<DeleteResult>" \
102
+ --related "softDeleteUser:soft-delete variant" \
103
+ --keywords "dal,users,delete"
104
+ ```
105
+
106
+ ### `repoctx get [path] [--keyword k1,k2] [--symbol name]`
107
+
108
+ Print saved context. **Prefer `--keyword` over bare `repoctx get`** — it uses
109
+ a keyword index (O(1)) and returns only what's relevant.
110
+
111
+ ```bash
112
+ repoctx get --keyword auth # modules tagged with "auth"
113
+ repoctx get --keyword dal,users # OR logic across keywords
114
+ repoctx get src/users/ # everything indexed under a path
115
+ repoctx get --symbol deleteUser # look up a specific function
116
+ ```
117
+
118
+ ### `repoctx stale`
119
+
120
+ List all files whose content changed since the last `save`.
121
+
122
+ ```bash
123
+ repoctx stale
124
+ # ⚠ src/users/dal.js (file content changed)
125
+ # → repoctx save src/users/dal.js "<summary>" --delta "what changed"
126
+ ```
127
+
128
+ ### `repoctx checkpoint` / `repoctx diff [--top N]`
129
+
130
+ Save the current git HEAD as a baseline, then show what changed since then —
131
+ formatted for Claude to understand without reading full diffs.
132
+
133
+ ```bash
134
+ repoctx checkpoint
135
+ # ... work for a few days ...
136
+ repoctx diff --top 5
137
+ ```
138
+
139
+ ### `repoctx init`
140
+
141
+ Initialize repoctx in the current repo. Creates `.repoctx/` and adds it to
142
+ `.gitignore`.
143
+
144
+ ### `repoctx onboarding`
145
+
146
+ Print agent instructions ready to paste into `CLAUDE.md` or `AGENTS.md`.
147
+
148
+ ```bash
149
+ repoctx onboarding >> CLAUDE.md
150
+ ```
151
+
152
+ ## When to save
153
+
154
+ **Do save** when the public contract changed:
155
+ - Exported functions, classes, or constants
156
+ - Route handlers or API surface
157
+ - DAL/service patterns (factory signatures, argument shapes)
158
+ - Business logic behavior (soft-delete vs hard-delete, idempotency, etc.)
159
+ - A file is moved, renamed, or becomes a new entrypoint
160
+
161
+ **Don't save** for:
162
+ - Literals, logs, comments, or formatting changes
163
+ - Internal refactors with no public interface change
164
+ - Test-only changes
165
+
166
+ ## Meta entries (patterns, glossary, folder map)
167
+
168
+ For knowledge not tied to a single file:
169
+
170
+ ```bash
171
+ # Codebase conventions
172
+ repoctx save .meta/patterns \
173
+ "DAL pattern: ({ schema }) => async (args) => ... Exports at bottom: fn: fn(models)." \
174
+ --keywords "pattern,convention,dal" --meta
175
+
176
+ # Domain glossary
177
+ repoctx save .meta/glossary \
178
+ "user = registered account. session = active login token." \
179
+ --keywords "glossary,domain" --meta
180
+
181
+ # Folder ownership
182
+ repoctx save .meta/structure \
183
+ "src/users = user domain. src/auth = authentication. src/api = route handlers." \
184
+ --keywords "structure,folders" --meta
185
+ ```
186
+
187
+ ## Sharing context with your team
188
+
189
+ `.repoctx/` is added to `.gitignore` by default (`repoctx init`). Context is
190
+ local per developer.
191
+
192
+ If you want to share context across the team, remove `.repoctx` from
193
+ `.gitignore` and commit it. Anyone who clones the repo gets the full index
194
+ immediately — no re-indexing needed.
195
+
196
+ ## Maintenance
197
+
198
+ Built for my workflow; support is best-effort. PRs welcome.
199
+
200
+ ## License
201
+
202
+ MIT
@@ -0,0 +1,61 @@
1
+ export type ExportEntry = {
2
+ name: string;
3
+ kind: "function" | "class" | "constant" | "type" | "other";
4
+ };
5
+ export type RepoctxFileCache = {
6
+ path: string;
7
+ hash: string;
8
+ publicSurfaceHash?: string | undefined;
9
+ summary: string;
10
+ symbols: string[];
11
+ exports?: ExportEntry[] | undefined;
12
+ keywords?: string[] | undefined;
13
+ dependencies?: string[] | undefined;
14
+ footguns?: string | undefined;
15
+ delta?: string | undefined;
16
+ repoHeadAtSave?: string | undefined;
17
+ updatedAt: string;
18
+ };
19
+ export type RepoctxIndex = {
20
+ version: 1;
21
+ metaVersion?: number | undefined;
22
+ files: Record<string, {
23
+ hash: string;
24
+ ref: string;
25
+ }>;
26
+ keywordIndex: Record<string, string[]>;
27
+ };
28
+ export type SymbolRelation = {
29
+ symbol: string;
30
+ relation: string;
31
+ };
32
+ export type SymbolCard = {
33
+ symbol: string;
34
+ kind: "function" | "class" | "constant" | "type" | "other";
35
+ file: string;
36
+ signature?: string | undefined;
37
+ purpose: string;
38
+ related?: SymbolRelation[] | undefined;
39
+ keywords?: string[] | undefined;
40
+ updatedAt: string;
41
+ };
42
+ export type SymbolsIndex = {
43
+ version: 1;
44
+ symbols: Record<string, string>;
45
+ };
46
+ export declare function repoctxDir(): string;
47
+ export declare function ensureDirs(): Promise<void>;
48
+ export declare function fileHash(file: string): Promise<string>;
49
+ export declare function computePublicSurfaceHash(symbols: string[]): string;
50
+ export declare function loadIndex(): Promise<RepoctxIndex>;
51
+ export declare function saveIndex(idx: RepoctxIndex): Promise<void>;
52
+ export declare function loadFileCache(relativePath: string): Promise<RepoctxFileCache | null>;
53
+ export declare function saveFileCache(relativePath: string, data: RepoctxFileCache): Promise<void>;
54
+ /** O(1) keyword lookup using the index */
55
+ export declare function lookupByKeywords(keywords: string[]): Promise<string[]>;
56
+ export declare function loadSymbolsIndex(): Promise<SymbolsIndex>;
57
+ export declare function saveSymbolCard(data: SymbolCard): Promise<void>;
58
+ export declare function loadAllSymbols(): Promise<SymbolCard[]>;
59
+ export declare function getGitHead(): string | null;
60
+ export declare function getGitBranch(): string | null;
61
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
package/dist/cache.js ADDED
@@ -0,0 +1,155 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import crypto from "node:crypto";
4
+ import { execSync } from "node:child_process";
5
+ // ── Paths ─────────────────────────────────────────────────────────────────────
6
+ function repoRoot() {
7
+ return process.cwd();
8
+ }
9
+ export function repoctxDir() {
10
+ return path.join(repoRoot(), ".repoctx");
11
+ }
12
+ function filesDir() {
13
+ return path.join(repoctxDir(), "files");
14
+ }
15
+ function symbolsDir() {
16
+ return path.join(repoctxDir(), "symbols");
17
+ }
18
+ function indexFile() {
19
+ return path.join(repoctxDir(), "index.json");
20
+ }
21
+ function symbolsIndexFile() {
22
+ return path.join(repoctxDir(), "symbols-index.json");
23
+ }
24
+ export async function ensureDirs() {
25
+ await fs.mkdir(filesDir(), { recursive: true });
26
+ await fs.mkdir(symbolsDir(), { recursive: true });
27
+ }
28
+ // ── Hashing ───────────────────────────────────────────────────────────────────
29
+ export async function fileHash(file) {
30
+ const buf = await fs.readFile(file);
31
+ return crypto.createHash("sha1").update(buf).digest("hex");
32
+ }
33
+ /** Stable short filename derived from the path (not content) */
34
+ function refFromPath(p) {
35
+ return crypto.createHash("sha1").update(p).digest("hex").slice(0, 12) + ".json";
36
+ }
37
+ export function computePublicSurfaceHash(symbols) {
38
+ return crypto.createHash("sha1").update(symbols.slice().sort().join(",")).digest("hex").slice(0, 12);
39
+ }
40
+ // ── Module Card I/O ───────────────────────────────────────────────────────────
41
+ export async function loadIndex() {
42
+ await ensureDirs();
43
+ try {
44
+ const raw = await fs.readFile(indexFile(), "utf8");
45
+ const parsed = JSON.parse(raw);
46
+ // back-compat: old index had no keywordIndex
47
+ if (!parsed.keywordIndex)
48
+ parsed.keywordIndex = {};
49
+ return parsed;
50
+ }
51
+ catch {
52
+ return { version: 1, files: {}, keywordIndex: {} };
53
+ }
54
+ }
55
+ export async function saveIndex(idx) {
56
+ await fs.writeFile(indexFile(), JSON.stringify(idx, null, 2), "utf8");
57
+ }
58
+ export async function loadFileCache(relativePath) {
59
+ const idx = await loadIndex();
60
+ const entry = idx.files[relativePath];
61
+ if (!entry)
62
+ return null;
63
+ try {
64
+ const raw = await fs.readFile(path.join(filesDir(), entry.ref), "utf8");
65
+ return JSON.parse(raw);
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ export async function saveFileCache(relativePath, data) {
72
+ const idx = await loadIndex();
73
+ const ref = refFromPath(relativePath);
74
+ await fs.writeFile(path.join(filesDir(), ref), JSON.stringify(data, null, 2), "utf8");
75
+ // Update file entry
76
+ idx.files[relativePath] = { hash: data.hash, ref };
77
+ // Rebuild keyword index for this path
78
+ const keywords = data.keywords ?? [];
79
+ // Remove this path from all existing keyword entries first
80
+ for (const kw of Object.keys(idx.keywordIndex)) {
81
+ idx.keywordIndex[kw] = (idx.keywordIndex[kw] ?? []).filter((p) => p !== relativePath);
82
+ if ((idx.keywordIndex[kw] ?? []).length === 0)
83
+ delete idx.keywordIndex[kw];
84
+ }
85
+ // Add to new keywords
86
+ for (const kw of keywords) {
87
+ if (!idx.keywordIndex[kw])
88
+ idx.keywordIndex[kw] = [];
89
+ if (!idx.keywordIndex[kw].includes(relativePath)) {
90
+ idx.keywordIndex[kw].push(relativePath);
91
+ }
92
+ }
93
+ await saveIndex(idx);
94
+ }
95
+ /** O(1) keyword lookup using the index */
96
+ export async function lookupByKeywords(keywords) {
97
+ const idx = await loadIndex();
98
+ const sets = keywords.map((k) => new Set(idx.keywordIndex[k] ?? []));
99
+ // OR logic: union of all sets
100
+ const union = new Set();
101
+ for (const s of sets)
102
+ for (const p of s)
103
+ union.add(p);
104
+ return [...union];
105
+ }
106
+ // ── Symbol Card I/O ───────────────────────────────────────────────────────────
107
+ export async function loadSymbolsIndex() {
108
+ await ensureDirs();
109
+ try {
110
+ const raw = await fs.readFile(symbolsIndexFile(), "utf8");
111
+ return JSON.parse(raw);
112
+ }
113
+ catch {
114
+ return { version: 1, symbols: {} };
115
+ }
116
+ }
117
+ export async function saveSymbolCard(data) {
118
+ const idx = await loadSymbolsIndex();
119
+ const fname = refFromPath("symbol:" + data.symbol);
120
+ await fs.writeFile(path.join(symbolsDir(), fname), JSON.stringify(data, null, 2), "utf8");
121
+ idx.symbols[data.symbol] = fname;
122
+ await fs.writeFile(symbolsIndexFile(), JSON.stringify(idx, null, 2), "utf8");
123
+ }
124
+ export async function loadAllSymbols() {
125
+ const idx = await loadSymbolsIndex();
126
+ const results = [];
127
+ for (const fname of Object.values(idx.symbols)) {
128
+ try {
129
+ const raw = await fs.readFile(path.join(symbolsDir(), fname), "utf8");
130
+ results.push(JSON.parse(raw));
131
+ }
132
+ catch {
133
+ // skip corrupt entries
134
+ }
135
+ }
136
+ return results;
137
+ }
138
+ // ── Git helpers ───────────────────────────────────────────────────────────────
139
+ export function getGitHead() {
140
+ try {
141
+ return execSync("git rev-parse HEAD", { encoding: "utf8" }).trim();
142
+ }
143
+ catch {
144
+ return null;
145
+ }
146
+ }
147
+ export function getGitBranch() {
148
+ try {
149
+ return execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
150
+ }
151
+ catch {
152
+ return null;
153
+ }
154
+ }
155
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ type Checkpoint = {
2
+ head: string;
3
+ branch: string;
4
+ at: string;
5
+ };
6
+ export declare function saveCheckpoint(): Promise<Checkpoint>;
7
+ export declare function loadCheckpoint(): Promise<Checkpoint | null>;
8
+ export {};
9
+ //# sourceMappingURL=checkpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint.d.ts","sourceRoot":"","sources":["../src/checkpoint.ts"],"names":[],"mappings":"AAKA,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAWF,wBAAsB,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC,CAc1D;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAQjE"}
@@ -0,0 +1,30 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ import { repoctxDir, ensureDirs } from "./cache.js";
5
+ function stateFile() {
6
+ return path.join(repoctxDir(), "state.json");
7
+ }
8
+ export async function saveCheckpoint() {
9
+ const head = execSync("git rev-parse HEAD", { encoding: "utf8" }).trim();
10
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
11
+ const at = new Date().toISOString();
12
+ const state = {
13
+ repoRoot: process.cwd(),
14
+ lastCheckpoint: { head, branch, at },
15
+ };
16
+ await ensureDirs();
17
+ await fs.writeFile(stateFile(), JSON.stringify(state, null, 2), "utf8");
18
+ return { head, branch, at };
19
+ }
20
+ export async function loadCheckpoint() {
21
+ try {
22
+ const raw = await fs.readFile(stateFile(), "utf8");
23
+ const state = JSON.parse(raw);
24
+ return state.lastCheckpoint ?? null;
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ //# sourceMappingURL=checkpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint.js","sourceRoot":"","sources":["../src/checkpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAapD,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,YAAY,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,oBAAoB,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACzE,MAAM,MAAM,GAAG,QAAQ,CAAC,iCAAiC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxF,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAEpC,MAAM,KAAK,GAAiB;QAC1B,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE;QACvB,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;KACrC,CAAC;IAEF,MAAM,UAAU,EAAE,CAAC;IACnB,MAAM,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAExE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;QACnD,MAAM,KAAK,GAAiB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5C,OAAO,KAAK,CAAC,cAAc,IAAI,IAAI,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
package/dist/diff.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export declare function runDiff({ top }?: {
2
+ top?: number;
3
+ }): Promise<string>;
4
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAoBA,wBAAsB,OAAO,CAAC,EAAE,GAAG,EAAE,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,mBAyE3D"}
package/dist/diff.js ADDED
@@ -0,0 +1,76 @@
1
+ import { execSync } from "node:child_process";
2
+ import { loadCheckpoint } from "./checkpoint.js";
3
+ function parseNumstat(output) {
4
+ const results = [];
5
+ for (const line of output.trim().split("\n").filter(Boolean)) {
6
+ const parts = line.split("\t");
7
+ const file = parts[2];
8
+ if (!file)
9
+ continue;
10
+ results.push({ file, additions: Number(parts[0]), deletions: Number(parts[1]) });
11
+ }
12
+ return results;
13
+ }
14
+ export async function runDiff({ top } = {}) {
15
+ const checkpoint = await loadCheckpoint();
16
+ if (!checkpoint) {
17
+ return "No checkpoint found. Run `repoctx checkpoint` first.";
18
+ }
19
+ const { head, at } = checkpoint;
20
+ const shortHead = head.slice(0, 7);
21
+ let currentHead;
22
+ try {
23
+ currentHead = execSync("git rev-parse HEAD", { encoding: "utf8" }).trim();
24
+ }
25
+ catch {
26
+ return "Not in a git repository.";
27
+ }
28
+ if (currentHead === head) {
29
+ return `No changes since checkpoint (${shortHead}).`;
30
+ }
31
+ let numstatOutput;
32
+ try {
33
+ numstatOutput = execSync(`git diff ${head}..HEAD --numstat`, { encoding: "utf8" });
34
+ }
35
+ catch {
36
+ return `Could not compute diff from ${shortHead}.`;
37
+ }
38
+ let stats = parseNumstat(numstatOutput);
39
+ stats.sort((a, b) => (b.additions + b.deletions) - (a.additions + a.deletions));
40
+ const topStats = top ? stats.slice(0, top) : stats;
41
+ const lines = [];
42
+ lines.push(`# Repoctx Diff (since ${shortHead} — ${at})`);
43
+ lines.push("");
44
+ lines.push(`Files changed (${stats.length}):`);
45
+ lines.push("");
46
+ for (const s of topStats) {
47
+ lines.push(`- ${s.file} (+${s.additions} -${s.deletions})`);
48
+ }
49
+ if (top && stats.length > top) {
50
+ lines.push(`... and ${stats.length - top} more files`);
51
+ }
52
+ lines.push("");
53
+ lines.push("---");
54
+ lines.push("");
55
+ for (const s of topStats) {
56
+ try {
57
+ const fileDiff = execSync(`git diff ${head}..HEAD -- "${s.file}"`, {
58
+ encoding: "utf8",
59
+ });
60
+ if (!fileDiff.trim())
61
+ continue;
62
+ lines.push(`[${s.file}]`);
63
+ const diffLines = fileDiff
64
+ .split("\n")
65
+ .filter((l) => (l.startsWith("+") || l.startsWith("-")) && !l.startsWith("+++") && !l.startsWith("---"))
66
+ .slice(0, 40);
67
+ lines.push(...diffLines);
68
+ lines.push("");
69
+ }
70
+ catch {
71
+ // skip if diff fails for a specific file
72
+ }
73
+ }
74
+ return lines.join("\n");
75
+ }
76
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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"}
package/dist/get.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export declare function getContext({ filterPath, keywords, symbol, }?: {
2
+ filterPath?: string;
3
+ keywords?: string[];
4
+ symbol?: string;
5
+ }): Promise<string>;
6
+ export declare function getStale(): Promise<{
7
+ path: string;
8
+ reason: string;
9
+ }[]>;
10
+ //# sourceMappingURL=get.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../src/get.ts"],"names":[],"mappings":"AASA,wBAAsB,UAAU,CAAC,EAC/B,UAAU,EACV,QAAQ,EACR,MAAM,GACP,GAAE;IACD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACZ,mBAsGL;AAID,wBAAsB,QAAQ,IAAI,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAmB5E"}
package/dist/get.js ADDED
@@ -0,0 +1,119 @@
1
+ import path from "node:path";
2
+ import { loadIndex, loadFileCache, fileHash, loadAllSymbols, lookupByKeywords, } from "./cache.js";
3
+ export async function getContext({ filterPath, keywords, symbol, } = {}) {
4
+ const lines = [];
5
+ // ── Symbol lookup ──────────────────────────────────────────────────────────
6
+ if (symbol) {
7
+ const allSymbols = await loadAllSymbols();
8
+ const match = allSymbols.find((s) => s.symbol.toLowerCase() === symbol.toLowerCase());
9
+ if (!match)
10
+ return `No symbol found: "${symbol}"`;
11
+ lines.push(`[symbol: ${match.symbol}]`);
12
+ lines.push(`Kind: ${match.kind}`);
13
+ lines.push(`File: ${match.file}`);
14
+ lines.push(`Purpose: ${match.purpose}`);
15
+ if (match.signature)
16
+ lines.push(`Signature: ${match.signature}`);
17
+ if (match.related?.length) {
18
+ lines.push(`Related:`);
19
+ for (const r of match.related)
20
+ lines.push(` - ${r.symbol} (${r.relation})`);
21
+ }
22
+ if (match.keywords?.length)
23
+ lines.push(`Keywords: ${match.keywords.join(", ")}`);
24
+ lines.push(`Updated: ${match.updatedAt}`);
25
+ return lines.join("\n");
26
+ }
27
+ // ── Determine which paths to show ─────────────────────────────────────────
28
+ let targetPaths = null; // null = show all
29
+ if (keywords && keywords.length > 0) {
30
+ // O(1) lookup via keyword index
31
+ targetPaths = await lookupByKeywords(keywords);
32
+ }
33
+ // ── Module cards ───────────────────────────────────────────────────────────
34
+ const idx = await loadIndex();
35
+ const relFilter = filterPath
36
+ ? path.relative(process.cwd(), path.resolve(filterPath)).replaceAll("\\", "/")
37
+ : undefined;
38
+ const pathsToShow = targetPaths ?? Object.keys(idx.files);
39
+ for (const rel of pathsToShow) {
40
+ if (relFilter) {
41
+ const isCwd = relFilter === "" || relFilter === ".";
42
+ const isExact = rel === relFilter;
43
+ const isUnder = rel.startsWith(relFilter + "/");
44
+ if (!isCwd && !isExact && !isUnder)
45
+ continue;
46
+ }
47
+ const cache = await loadFileCache(rel);
48
+ if (!cache)
49
+ continue;
50
+ lines.push(`[${rel}]`);
51
+ lines.push(cache.summary);
52
+ if (cache.symbols.length > 0)
53
+ lines.push(`Symbols: ${cache.symbols.join(", ")}`);
54
+ if (cache.exports?.length) {
55
+ lines.push(`Exports: ${cache.exports.map((e) => `${e.name}(${e.kind})`).join(", ")}`);
56
+ }
57
+ if (cache.keywords?.length)
58
+ lines.push(`Keywords: ${cache.keywords.join(", ")}`);
59
+ if (cache.dependencies?.length)
60
+ lines.push(`Deps: ${cache.dependencies.join(", ")}`);
61
+ if (cache.footguns)
62
+ lines.push(`⚠ Footguns: ${cache.footguns}`);
63
+ if (cache.delta)
64
+ lines.push(`Δ Last change: ${cache.delta}`);
65
+ if (cache.hash !== "meta") {
66
+ try {
67
+ const currentHash = await fileHash(path.resolve(rel));
68
+ if (currentHash !== cache.hash) {
69
+ lines.push("⚠ Context outdated (file changed since last save)");
70
+ }
71
+ }
72
+ catch {
73
+ lines.push("⚠ File not found (may have been deleted or moved)");
74
+ }
75
+ }
76
+ lines.push("");
77
+ }
78
+ // ── Symbol cards ───────────────────────────────────────────────────────────
79
+ const allSymbols = await loadAllSymbols();
80
+ let symbolsToShow = allSymbols;
81
+ if (keywords && keywords.length > 0) {
82
+ symbolsToShow = allSymbols.filter((s) => keywords.some((k) => s.keywords?.includes(k)));
83
+ }
84
+ if (symbolsToShow.length > 0 && !symbol) {
85
+ lines.push("── Symbols ──────────────────────────────────────────────");
86
+ for (const s of symbolsToShow) {
87
+ lines.push(`[symbol: ${s.symbol}] (${s.kind}) ${s.file}`);
88
+ lines.push(` ${s.purpose}`);
89
+ if (s.signature)
90
+ lines.push(` Sig: ${s.signature}`);
91
+ if (s.related?.length) {
92
+ lines.push(` Related: ${s.related.map((r) => `${r.symbol} (${r.relation})`).join(", ")}`);
93
+ }
94
+ lines.push("");
95
+ }
96
+ }
97
+ return lines.join("\n");
98
+ }
99
+ // ── Stale check ───────────────────────────────────────────────────────────────
100
+ export async function getStale() {
101
+ const idx = await loadIndex();
102
+ const stale = [];
103
+ for (const rel of Object.keys(idx.files)) {
104
+ const cache = await loadFileCache(rel);
105
+ if (!cache || cache.hash === "meta")
106
+ continue;
107
+ try {
108
+ const currentHash = await fileHash(path.resolve(rel));
109
+ if (currentHash !== cache.hash) {
110
+ stale.push({ path: rel, reason: "file content changed" });
111
+ }
112
+ }
113
+ catch {
114
+ stale.push({ path: rel, reason: "file not found" });
115
+ }
116
+ }
117
+ return stale;
118
+ }
119
+ //# sourceMappingURL=get.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get.js","sourceRoot":"","sources":["../src/get.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { saveManual } from "./save.js";
6
+ import { saveSymbol, parseRelated } from "./saveSymbol.js";
7
+ import { getContext, getStale } from "./get.js";
8
+ import { saveCheckpoint } from "./checkpoint.js";
9
+ import { runDiff } from "./diff.js";
10
+ import { getOnboardingText } from "./onboarding.js";
11
+ import { repoctxDir } from "./cache.js";
12
+ const program = new Command();
13
+ program
14
+ .name("repoctx")
15
+ .description("Structured context layer for AI coding assistants")
16
+ .version("0.1.0");
17
+ // ── repoctx save ──────────────────────────────────────────────────────────────
18
+ program
19
+ .command("save")
20
+ .description("Save context for a file. Always include --keywords so future agents can find this module with repoctx get --keyword <topic>")
21
+ .argument("<file>", "File path (or virtual key with --meta)")
22
+ .argument("<summary>", "What this file does, its role, and conventions")
23
+ .option("--symbols <symbols>", "Comma-separated exported symbols/functions", "")
24
+ .option("--keywords <keywords>", "REQUIRED in practice: comma-separated tags (e.g. dal,payments,http). Used by repoctx get --keyword", "")
25
+ .option("--deps <deps>", "Comma-separated dependencies of this module", "")
26
+ .option("--footguns <text>", "Gotchas: things that break if touched wrong (e.g. soft-delete vs hard-delete)")
27
+ .option("--delta <text>", "What changed in this save — short description of the diff")
28
+ .option("--meta", "Virtual entry — no real file, skips hash check. Use for patterns, glossary, folder maps")
29
+ .action(async (file, summary, opts) => {
30
+ const split = (s) => s ? s.split(",").map((x) => x.trim()).filter(Boolean) : [];
31
+ const rel = await saveManual({
32
+ filePath: file,
33
+ summary,
34
+ symbols: split(opts.symbols),
35
+ keywords: split(opts.keywords),
36
+ dependencies: split(opts.deps),
37
+ footguns: opts.footguns,
38
+ delta: opts.delta,
39
+ meta: !!opts.meta,
40
+ });
41
+ console.log(`✓ Saved context for ${rel}`);
42
+ });
43
+ // ── repoctx save-symbol ───────────────────────────────────────────────────────
44
+ program
45
+ .command("save-symbol")
46
+ .description("Save a Symbol Card for a specific function, class, or constant. Enables repoctx get --symbol <name> lookup")
47
+ .argument("<symbol>", "Symbol name (e.g. removeCharge)")
48
+ .argument("<purpose>", "One-line description of what this symbol does")
49
+ .requiredOption("--file <file>", "File where the symbol lives")
50
+ .option("--kind <kind>", "function | class | constant | type | other", "function")
51
+ .option("--signature <sig>", "Full signature (e.g. '({ chargeSchema }) => async ({ id }) => Promise<Result>')")
52
+ .option("--related <pairs>", "Typed relations: 'symbol:relation,symbol:relation' (e.g. 'deleteCharge:soft-delete variant')", "")
53
+ .option("--keywords <keywords>", "Same tags as the parent module so --keyword lookups include this symbol too", "")
54
+ .action(async (symbol, purpose, opts) => {
55
+ const split = (s) => s ? s.split(",").map((x) => x.trim()).filter(Boolean) : [];
56
+ await saveSymbol({
57
+ symbol,
58
+ purpose,
59
+ file: opts.file,
60
+ kind: opts.kind,
61
+ signature: opts.signature,
62
+ related: opts.related ? parseRelated(opts.related) : [],
63
+ keywords: split(opts.keywords),
64
+ });
65
+ console.log(`✓ Saved symbol card for ${symbol}`);
66
+ });
67
+ // ── repoctx get ───────────────────────────────────────────────────────────────
68
+ program
69
+ .command("get")
70
+ .description("Print saved context. Prefer --keyword over bare get — it's faster, cheaper, and less noisy")
71
+ .argument("[path]", "Filter by file or directory path")
72
+ .option("--keyword <keywords>", "Filter by keyword(s), comma-separated OR logic. e.g. --keyword dal,payments. Use this before reaching for Read on a source file")
73
+ .option("--symbol <name>", "Look up a specific symbol card by name")
74
+ .action(async (filterPath, opts) => {
75
+ const keywords = opts.keyword
76
+ ? opts.keyword.split(",").map((s) => s.trim()).filter(Boolean)
77
+ : undefined;
78
+ const output = await getContext({
79
+ ...(filterPath !== undefined && { filterPath }),
80
+ ...(keywords !== undefined && { keywords }),
81
+ ...(opts.symbol !== undefined && { symbol: opts.symbol }),
82
+ });
83
+ if (!output.trim()) {
84
+ console.log("No context found. Use `repoctx save` to add context.");
85
+ return;
86
+ }
87
+ process.stdout.write(output + "\n");
88
+ });
89
+ // ── repoctx stale ─────────────────────────────────────────────────────────────
90
+ program
91
+ .command("stale")
92
+ .description("List all indexed files whose content changed since last save")
93
+ .action(async () => {
94
+ const results = await getStale();
95
+ if (results.length === 0) {
96
+ console.log("✓ All context is up to date.");
97
+ return;
98
+ }
99
+ console.log(`${results.length} stale file(s):\n`);
100
+ for (const { path: p, reason } of results) {
101
+ console.log(` ⚠ ${p} (${reason})`);
102
+ console.log(` → repoctx save ${p} "<updated summary>" --keywords "..." --delta "what changed"`);
103
+ }
104
+ });
105
+ // ── repoctx checkpoint ────────────────────────────────────────────────────────
106
+ program
107
+ .command("checkpoint")
108
+ .description("Save current git HEAD as baseline for future diffs")
109
+ .action(async () => {
110
+ try {
111
+ const cp = await saveCheckpoint();
112
+ console.log(`✓ Checkpoint saved: ${cp.branch} @ ${cp.head.slice(0, 7)} (${cp.at})`);
113
+ }
114
+ catch (e) {
115
+ console.error(`Error: ${e?.message ?? e}`);
116
+ console.error("Make sure you are inside a git repository.");
117
+ process.exit(1);
118
+ }
119
+ });
120
+ // ── repoctx diff ──────────────────────────────────────────────────────────────
121
+ program
122
+ .command("diff")
123
+ .description("Show changes since last checkpoint, formatted for Claude")
124
+ .option("--top <n>", "Show only top N changed files by lines modified")
125
+ .action(async (opts) => {
126
+ const top = opts.top ? Number(opts.top) : undefined;
127
+ const output = await runDiff(top !== undefined ? { top } : {});
128
+ process.stdout.write(output + "\n");
129
+ });
130
+ // ── repoctx init ──────────────────────────────────────────────────────────────
131
+ program
132
+ .command("init")
133
+ .description("Initialize repoctx in the current repo: creates .repoctx/ and adds it to .gitignore")
134
+ .action(async () => {
135
+ await fs.mkdir(repoctxDir(), { recursive: true });
136
+ // Add .repoctx to .gitignore if not already there
137
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
138
+ let alreadyIgnored = false;
139
+ try {
140
+ const content = await fs.readFile(gitignorePath, "utf8");
141
+ alreadyIgnored = content.split("\n").some((l) => l.trim() === ".repoctx");
142
+ }
143
+ catch {
144
+ // no .gitignore yet, will create
145
+ }
146
+ if (!alreadyIgnored) {
147
+ await fs.appendFile(gitignorePath, "\n# repoctx context index\n.repoctx\n");
148
+ console.log("✓ Added .repoctx to .gitignore");
149
+ }
150
+ else {
151
+ console.log("✓ .repoctx already in .gitignore");
152
+ }
153
+ console.log("✓ repoctx initialized. Run `repoctx onboarding` to get CLAUDE.md instructions.");
154
+ });
155
+ // ── repoctx onboarding ────────────────────────────────────────────────────────
156
+ program
157
+ .command("onboarding")
158
+ .description("Print instructions for AI agents (paste into CLAUDE.md)")
159
+ .action(() => {
160
+ process.stdout.write(getOnboardingText() + "\n");
161
+ });
162
+ program.parseAsync(process.argv).catch((e) => {
163
+ console.error(e?.message ?? e);
164
+ process.exit(1);
165
+ });
166
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,mDAAmD,CAAC;KAChE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,iFAAiF;AACjF,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,6HAA6H,CAAC;KAC1I,QAAQ,CAAC,QAAQ,EAAE,wCAAwC,CAAC;KAC5D,QAAQ,CAAC,WAAW,EAAE,gDAAgD,CAAC;KACvE,MAAM,CAAC,qBAAqB,EAAE,4CAA4C,EAAE,EAAE,CAAC;KAC/E,MAAM,CAAC,uBAAuB,EAAE,oGAAoG,EAAE,EAAE,CAAC;KACzI,MAAM,CAAC,eAAe,EAAE,6CAA6C,EAAE,EAAE,CAAC;KAC1E,MAAM,CAAC,mBAAmB,EAAE,+EAA+E,CAAC;KAC5G,MAAM,CAAC,gBAAgB,EAAE,2DAA2D,CAAC;KACrF,MAAM,CAAC,QAAQ,EAAE,yFAAyF,CAAC;KAC3G,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,OAAe,EAAE,IAAI,EAAE,EAAE;IACpD,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEhG,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC;QAC3B,QAAQ,EAAE,IAAI;QACd,OAAO;QACP,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;QAC5B,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC9B,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAC9B,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI;KAClB,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAC;AAC5C,CAAC,CAAC,CAAC;AAEL,iFAAiF;AACjF,OAAO;KACJ,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,4GAA4G,CAAC;KACzH,QAAQ,CAAC,UAAU,EAAE,iCAAiC,CAAC;KACvD,QAAQ,CAAC,WAAW,EAAE,+CAA+C,CAAC;KACtE,cAAc,CAAC,eAAe,EAAE,6BAA6B,CAAC;KAC9D,MAAM,CAAC,eAAe,EAAE,4CAA4C,EAAE,UAAU,CAAC;KACjF,MAAM,CAAC,mBAAmB,EAAE,iFAAiF,CAAC;KAC9G,MAAM,CAAC,mBAAmB,EAAE,8FAA8F,EAAE,EAAE,CAAC;KAC/H,MAAM,CAAC,uBAAuB,EAAE,6EAA6E,EAAE,EAAE,CAAC;KAClH,MAAM,CAAC,KAAK,EAAE,MAAc,EAAE,OAAe,EAAE,IAAI,EAAE,EAAE;IACtD,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEhG,MAAM,UAAU,CAAC;QACf,MAAM;QACN,OAAO;QACP,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE;QACvD,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;KAC/B,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,CAAC,2BAA2B,MAAM,EAAE,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEL,iFAAiF;AACjF,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,4FAA4F,CAAC;KACzG,QAAQ,CAAC,QAAQ,EAAE,kCAAkC,CAAC;KACtD,MAAM,CAAC,sBAAsB,EAAE,iIAAiI,CAAC;KACjK,MAAM,CAAC,iBAAiB,EAAE,wCAAwC,CAAC;KACnE,MAAM,CAAC,KAAK,EAAE,UAA8B,EAAE,IAAI,EAAE,EAAE;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO;QAC3B,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QACtE,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC;QAC9B,GAAG,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,CAAC;QAC/C,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC3C,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;KAC1D,CAAC,CAAC;IAEH,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;QACpE,OAAO;IACT,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC;AAEL,iFAAiF;AACjF,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,8DAA8D,CAAC;KAC3E,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,OAAO,GAAG,MAAM,QAAQ,EAAE,CAAC;IACjC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;QAC5C,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,mBAAmB,CAAC,CAAC;IAClD,KAAK,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,8DAA8D,CAAC,CAAC;IACrG,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,iFAAiF;AACjF,OAAO;KACJ,OAAO,CAAC,YAAY,CAAC;KACrB,WAAW,CAAC,oDAAoD,CAAC;KACjE,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,cAAc,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC,MAAM,MAAM,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IACtF,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,iFAAiF;AACjF,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,0DAA0D,CAAC;KACvE,MAAM,CAAC,WAAW,EAAE,iDAAiD,CAAC;KACtE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC;AAEL,iFAAiF;AACjF,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,qFAAqF,CAAC;KAClG,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAElD,kDAAkD;IAClD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC;IAC7D,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACzD,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,UAAU,CAAC,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,iCAAiC;IACnC,CAAC;IAED,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE,uCAAuC,CAAC,CAAC;QAC5E,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAChD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAClD,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,gFAAgF,CAAC,CAAC;AAChG,CAAC,CAAC,CAAC;AAEL,iFAAiF;AACjF,OAAO;KACJ,OAAO,CAAC,YAAY,CAAC;KACrB,WAAW,CAAC,yDAAyD,CAAC;KACtE,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,GAAG,IAAI,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IAC3C,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC;IAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function getOnboardingText(): string;
2
+ //# sourceMappingURL=onboarding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,IAAI,MAAM,CAyH1C"}
@@ -0,0 +1,123 @@
1
+ export function getOnboardingText() {
2
+ return `
3
+ # repoctx — Agent Instructions
4
+
5
+ repoctx is a persistent context layer for this repo. It stores module
6
+ summaries, symbol cards, patterns, and domain knowledge in .repoctx/.
7
+
8
+ The index survives session resets — you never lose context when a
9
+ conversation closes and reopens. Always read from repoctx first; it
10
+ prevents hallucinations by grounding your understanding in committed,
11
+ structured facts instead of memory.
12
+
13
+ ## Always start a session with
14
+
15
+ \`\`\`bash
16
+ repoctx get --keyword <topic> # targeted: only what you need
17
+ repoctx diff --top 8 # what changed since the last checkpoint
18
+ \`\`\`
19
+
20
+ Prefer \`--keyword\` over a bare \`repoctx get\` (which dumps everything).
21
+ Use keywords to fetch only the modules relevant to the current task.
22
+ This keeps context small and responses fast.
23
+
24
+ ## Targeted lookups — use these instead of reading source files
25
+
26
+ \`\`\`bash
27
+ repoctx get --keyword dal,users # modules related to a topic
28
+ repoctx get --keyword auth # modules in the auth domain
29
+ repoctx get --symbol deleteUser # look up a specific function
30
+ repoctx get src/users/ # everything indexed under a path
31
+ \`\`\`
32
+
33
+ Keywords are OR-filtered: \`--keyword dal,charge\` returns anything tagged
34
+ with "dal" OR "charge". Each module and symbol has its own keyword list —
35
+ use them to navigate without reading files.
36
+
37
+ ## Speed note
38
+
39
+ Every file you skip reading = one fewer tool call = faster response.
40
+ A \`repoctx get --keyword X\` is one call. Reading three source files is
41
+ three calls. Always check repoctx before reaching for Read.
42
+
43
+ ## When to run \`repoctx save\` (public contract changed)
44
+
45
+ Run it when any of these change:
46
+ - Exported functions, classes, or constants
47
+ - Module.exports / ESM exports
48
+ - Route handlers or API surface
49
+ - DAL/service patterns (factory signatures, argument shapes)
50
+ - Business logic behavior (soft-delete vs hard-delete, idempotency, etc.)
51
+ - A file is moved, renamed, or becomes a new entrypoint
52
+ - A new convention or pattern is introduced
53
+
54
+ Always include \`--keywords\` matching the domain and layer so future
55
+ lookups via \`--keyword\` can find this module:
56
+
57
+ \`\`\`bash
58
+ repoctx save src/users/dal.js \\
59
+ "MongoDB DAL for users. CRUD operations." \\
60
+ --symbols "createUser,getUser,deleteUser" \\
61
+ --keywords "dal,users,mongodb" \\
62
+ --delta "Added deleteUser (soft delete)"
63
+ \`\`\`
64
+
65
+ Use \`--footguns\` to warn future agents about tricky behavior:
66
+
67
+ \`\`\`bash
68
+ --footguns "deleteUser = soft delete. removeUser = hard delete. Don't confuse them."
69
+ \`\`\`
70
+
71
+ ## When NOT to run \`repoctx save\`
72
+
73
+ Skip it for:
74
+ - Literals, log messages, comments, or formatting
75
+ - Internal refactors with no change to the public interface
76
+ - Test-only changes
77
+
78
+ ## Symbol Cards (fine-grained lookup)
79
+
80
+ For key functions worth tracking individually:
81
+
82
+ \`\`\`bash
83
+ repoctx save-symbol deleteUser "Hard delete a user from MongoDB" \\
84
+ --file src/users/dal.js \\
85
+ --kind function \\
86
+ --signature "({ userSchema }) => async ({ id }) => Promise<DeleteResult>" \\
87
+ --related "softDeleteUser:soft-delete variant" \\
88
+ --keywords "dal,users,delete"
89
+ \`\`\`
90
+
91
+ Then look it up with: \`repoctx get --symbol deleteUser\`
92
+
93
+ ## Meta entries (patterns, glossary, folder map)
94
+
95
+ For knowledge not tied to a single file, use \`--meta\`:
96
+
97
+ \`\`\`bash
98
+ repoctx save .meta/patterns "DAL pattern: ({ schema }) => async (args) => ..." \\
99
+ --keywords "pattern,convention,dal" --meta
100
+
101
+ repoctx save .meta/glossary "user = registered account. session = active login token." \\
102
+ --keywords "glossary,domain" --meta
103
+
104
+ repoctx save .meta/structure "src/users = user domain. src/auth = authentication. src/api = route handlers." \\
105
+ --keywords "structure,folders" --meta
106
+ \`\`\`
107
+
108
+ ## Freshness
109
+
110
+ If \`repoctx get\` shows \`⚠ Context outdated\` for a file, read it and
111
+ run \`repoctx save\` with an updated summary before working on it.
112
+ Refresh only the stale file — do not re-index the whole repo.
113
+
114
+ ## Output discipline
115
+
116
+ - Always try \`repoctx get --keyword <topic>\` before reaching for Read.
117
+ - If repoctx can answer your question, stop there.
118
+ - If you must read a file, prefer a targeted slice (10–30 lines) to
119
+ confirm exact syntax, not to understand the module from scratch.
120
+ - After a meaningful change, update repoctx for that module.
121
+ `.trim();
122
+ }
123
+ //# sourceMappingURL=onboarding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding.js","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB;IAC/B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuHR,CAAC,IAAI,EAAE,CAAC;AACT,CAAC"}
package/dist/save.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { type ExportEntry } from "./cache.js";
2
+ export declare function saveManual({ filePath, summary, symbols, exports: exportsArg, keywords, dependencies, footguns, delta, meta, }: {
3
+ filePath: string;
4
+ summary: string;
5
+ symbols: string[];
6
+ exports?: ExportEntry[];
7
+ keywords: string[];
8
+ dependencies?: string[];
9
+ footguns?: string;
10
+ delta?: string;
11
+ meta?: boolean;
12
+ }): Promise<string>;
13
+ //# sourceMappingURL=save.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../src/save.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAqBpB,wBAAsB,UAAU,CAAC,EAC/B,QAAQ,EACR,OAAO,EACP,OAAO,EACP,OAAO,EAAE,UAAU,EACnB,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,IAAY,GACb,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,mBA+BA"}
package/dist/save.js ADDED
@@ -0,0 +1,53 @@
1
+ import path from "node:path";
2
+ import { execSync } from "node:child_process";
3
+ import { fileHash, saveFileCache, getGitHead, computePublicSurfaceHash, } from "./cache.js";
4
+ function findWhereUsed(relPath) {
5
+ try {
6
+ const basename = path.basename(relPath, path.extname(relPath));
7
+ const raw = execSync(`grep -r --include="*.js" --include="*.ts" -l "${basename}" . 2>/dev/null || true`, { encoding: "utf8", cwd: process.cwd() });
8
+ return raw
9
+ .trim()
10
+ .split("\n")
11
+ .filter(Boolean)
12
+ .filter((f) => !f.includes("node_modules") && !f.includes(".repoctx"))
13
+ .filter((f) => f !== `./${relPath}` && f !== relPath)
14
+ .slice(0, 5);
15
+ }
16
+ catch {
17
+ return [];
18
+ }
19
+ }
20
+ export async function saveManual({ filePath, summary, symbols, exports: exportsArg, keywords, dependencies, footguns, delta, meta = false, }) {
21
+ const rel = meta
22
+ ? filePath.replaceAll("\\", "/")
23
+ : path.relative(process.cwd(), path.resolve(filePath)).replaceAll("\\", "/");
24
+ const hash = meta ? "meta" : await fileHash(path.resolve(filePath));
25
+ const publicSurfaceHash = symbols.length > 0 ? computePublicSurfaceHash(symbols) : undefined;
26
+ const head = getGitHead();
27
+ const whereUsed = meta ? [] : findWhereUsed(rel);
28
+ const entry = {
29
+ path: rel,
30
+ hash,
31
+ summary: whereUsed.length > 0
32
+ ? `${summary}\nUsed in: ${whereUsed.join(", ")}`
33
+ : summary,
34
+ symbols,
35
+ keywords,
36
+ updatedAt: new Date().toISOString(),
37
+ };
38
+ if (publicSurfaceHash)
39
+ entry.publicSurfaceHash = publicSurfaceHash;
40
+ if (exportsArg && exportsArg.length > 0)
41
+ entry.exports = exportsArg;
42
+ if (dependencies && dependencies.length > 0)
43
+ entry.dependencies = dependencies;
44
+ if (footguns)
45
+ entry.footguns = footguns;
46
+ if (delta)
47
+ entry.delta = delta;
48
+ if (head)
49
+ entry.repoHeadAtSave = head;
50
+ await saveFileCache(rel, entry);
51
+ return rel;
52
+ }
53
+ //# sourceMappingURL=save.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"save.js","sourceRoot":"","sources":["../src/save.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EACL,QAAQ,EACR,aAAa,EACb,UAAU,EACV,wBAAwB,GAEzB,MAAM,YAAY,CAAC;AAEpB,SAAS,aAAa,CAAC,OAAe;IACpC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/D,MAAM,GAAG,GAAG,QAAQ,CAClB,iDAAiD,QAAQ,yBAAyB,EAClF,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CACzC,CAAC;QACF,OAAO,GAAG;aACP,IAAI,EAAE;aACN,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,OAAO,CAAC;aACf,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;aACrE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE,IAAI,CAAC,KAAK,OAAO,CAAC;aACpD,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,EAC/B,QAAQ,EACR,OAAO,EACP,OAAO,EACP,OAAO,EAAE,UAAU,EACnB,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,IAAI,GAAG,KAAK,GAWb;IACC,MAAM,GAAG,GAAG,IAAI;QACd,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC;QAChC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAE/E,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpE,MAAM,iBAAiB,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7F,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAEjD,MAAM,KAAK,GAAwC;QACjD,IAAI,EAAE,GAAG;QACT,IAAI;QACJ,OAAO,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC;YAC3B,CAAC,CAAC,GAAG,OAAO,cAAc,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAChD,CAAC,CAAC,OAAO;QACX,OAAO;QACP,QAAQ;QACR,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IAEF,IAAI,iBAAiB;QAAE,KAAK,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;IACnE,IAAI,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,OAAO,GAAG,UAAU,CAAC;IACpE,IAAI,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,YAAY,GAAG,YAAY,CAAC;IAC/E,IAAI,QAAQ;QAAE,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxC,IAAI,KAAK;QAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;IAC/B,IAAI,IAAI;QAAE,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC;IAEtC,MAAM,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAEhC,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { type SymbolCard, type SymbolRelation } from "./cache.js";
2
+ export declare function saveSymbol({ symbol, kind, file, signature, purpose, related, keywords, }: {
3
+ symbol: string;
4
+ kind: SymbolCard["kind"];
5
+ file: string;
6
+ signature?: string;
7
+ purpose: string;
8
+ related?: SymbolRelation[];
9
+ keywords?: string[];
10
+ }): Promise<string>;
11
+ /** Parse "symbol:relation,symbol:relation" format from CLI */
12
+ export declare function parseRelated(raw: string): SymbolRelation[];
13
+ //# sourceMappingURL=saveSymbol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"saveSymbol.d.ts","sourceRoot":"","sources":["../src/saveSymbol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAElF,wBAAsB,UAAU,CAAC,EAC/B,MAAM,EACN,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,OAAO,EACP,OAAO,EACP,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,mBAeA;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,EAAE,CAY1D"}
@@ -0,0 +1,33 @@
1
+ import { saveSymbolCard } from "./cache.js";
2
+ export async function saveSymbol({ symbol, kind, file, signature, purpose, related, keywords, }) {
3
+ const card = {
4
+ symbol,
5
+ kind,
6
+ file,
7
+ purpose,
8
+ updatedAt: new Date().toISOString(),
9
+ };
10
+ if (signature)
11
+ card.signature = signature;
12
+ if (related && related.length > 0)
13
+ card.related = related;
14
+ if (keywords && keywords.length > 0)
15
+ card.keywords = keywords;
16
+ await saveSymbolCard(card);
17
+ return symbol;
18
+ }
19
+ /** Parse "symbol:relation,symbol:relation" format from CLI */
20
+ export function parseRelated(raw) {
21
+ return raw
22
+ .split(",")
23
+ .map((s) => s.trim())
24
+ .filter(Boolean)
25
+ .map((s) => {
26
+ const [symbol, ...rest] = s.split(":");
27
+ return {
28
+ symbol: symbol.trim(),
29
+ relation: rest.join(":").trim() || "related",
30
+ };
31
+ });
32
+ }
33
+ //# sourceMappingURL=saveSymbol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"saveSymbol.js","sourceRoot":"","sources":["../src/saveSymbol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAwC,MAAM,YAAY,CAAC;AAElF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,EAC/B,MAAM,EACN,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,OAAO,EACP,OAAO,EACP,QAAQ,GAST;IACC,MAAM,IAAI,GAAe;QACvB,MAAM;QACN,IAAI;QACJ,IAAI;QACJ,OAAO;QACP,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IAEF,IAAI,SAAS;QAAE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC1C,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC1D,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAE9D,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,GAAG;SACP,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvC,OAAO;YACL,MAAM,EAAE,MAAO,CAAC,IAAI,EAAE;YACtB,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS;SAC7C,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "type": "module",
3
+ "name": "repoctx",
4
+ "version": "0.1.0",
5
+ "description": "Structured context layer for AI coding assistants. Reduces token cost and exploration overhead by storing file summaries, symbol cards and git diffs in .repoctx/",
6
+ "bin": { "repoctx": "dist/index.js" },
7
+ "main": "dist/index.js",
8
+ "files": ["dist"],
9
+ "engines": { "node": ">=18" },
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "dev": "tsx src/index.ts",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "ai",
17
+ "claude",
18
+ "llm",
19
+ "context",
20
+ "tokens",
21
+ "developer-tools",
22
+ "cli",
23
+ "codebase",
24
+ "agent"
25
+ ],
26
+ "author": {
27
+ "name": "Ezequiel",
28
+ "url": "https://github.com/Ezequiiel98"
29
+ },
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/Ezequiiel98/repoctx"
34
+ },
35
+ "homepage": "https://github.com/Ezequiiel98/repoctx#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/Ezequiiel98/repoctx/issues"
38
+ },
39
+ "dependencies": {
40
+ "commander": "^14.0.3"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^25.2.0",
44
+ "tsx": "^4.21.0",
45
+ "typescript": "^5.9.3"
46
+ }
47
+ }