@tricknowtech/context 0.2.0 → 0.3.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 +25 -1
- package/dist/{chunk-6GB2SN2L.js → chunk-BLEGQVXA.js} +228 -102
- package/dist/cli.cjs +287 -154
- package/dist/cli.js +16 -5
- package/dist/index.cjs +260 -137
- package/dist/index.d.cts +49 -8
- package/dist/index.d.ts +49 -8
- package/dist/index.js +1 -1
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -10,7 +10,31 @@ npx @tricknowtech/context init
|
|
|
10
10
|
|
|
11
11
|
## The problem
|
|
12
12
|
|
|
13
|
-
Open the same project on a second machine and
|
|
13
|
+
Open the same project on a second machine and your assistant knows nothing. Instruction files travel with the repo, but the parts that actually accumulate — per-project memory, custom skills and prompts, and the thread of what you were doing — live in your home directory and never leave the machine that built them.
|
|
14
|
+
|
|
15
|
+
## Works with the tool you actually use
|
|
16
|
+
|
|
17
|
+
Assistants are detected automatically, from what's installed and what the repo already contains:
|
|
18
|
+
|
|
19
|
+
| Tool | Project files | User-global |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Claude Code | `CLAUDE.md`, `.claude/**` | memory, skills, agents, plans |
|
|
22
|
+
| OpenAI Codex | `AGENTS.md`, `.codex/**` | `AGENTS.md`, `config.toml`, prompts |
|
|
23
|
+
| Cursor | `.cursorrules`, `.cursor/rules/**` | global rules |
|
|
24
|
+
| GitHub Copilot | `.github/copilot-instructions.md`, instructions, prompts | — |
|
|
25
|
+
| Gemini CLI | `GEMINI.md`, `.gemini/**` | `GEMINI.md`, settings, commands |
|
|
26
|
+
| Windsurf | `.windsurfrules`, `.windsurf/**` | — |
|
|
27
|
+
| Cline | `.clinerules` | — |
|
|
28
|
+
| Aider | `CONVENTIONS.md`, `.aider.conf.yml` | — |
|
|
29
|
+
| Continue | `.continue/**` | — |
|
|
30
|
+
|
|
31
|
+
Each tool's home-directory config is namespaced in the store (`assistants/codex/…`) and restored to the right place on the other machine — `~/.codex`, `~/.gemini`, `~/.claude` — even when that machine's home and project paths are completely different.
|
|
32
|
+
|
|
33
|
+
This also works across a *team* on different tools: a Cursor user's rules sync for a Claude user, because detection keys off what's in the repo, not just what's installed locally. Pin it explicitly if you'd rather:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{ "assistants": ["claude", "codex"] }
|
|
37
|
+
```
|
|
14
38
|
|
|
15
39
|
## What it does
|
|
16
40
|
|
|
@@ -12,8 +12,9 @@ function cwdKey(absPath) {
|
|
|
12
12
|
function makeTemplate(absPath, ctx) {
|
|
13
13
|
const candidates = [
|
|
14
14
|
[ctx.userClaude, "{userClaude}"],
|
|
15
|
-
[ctx.project, "{project}"]
|
|
16
|
-
|
|
15
|
+
[ctx.project, "{project}"],
|
|
16
|
+
[ctx.home, "{home}"]
|
|
17
|
+
].filter(([root]) => Boolean(root)).sort((a, b) => b[0].length - a[0].length);
|
|
17
18
|
let out = absPath;
|
|
18
19
|
for (const [root, token] of candidates) {
|
|
19
20
|
if (absPath === root || absPath.startsWith(root + path.sep)) {
|
|
@@ -24,7 +25,7 @@ function makeTemplate(absPath, ctx) {
|
|
|
24
25
|
return ctx.cwdKey ? out.split(ctx.cwdKey).join("{cwdKey}") : out;
|
|
25
26
|
}
|
|
26
27
|
function resolveTemplate(template, ctx) {
|
|
27
|
-
const expanded = template.split("{userClaude}").join(ctx.userClaude).split("{project}").join(ctx.project).split("{cwdKey}").join(ctx.cwdKey);
|
|
28
|
+
const expanded = template.split("{userClaude}").join(ctx.userClaude).split("{project}").join(ctx.project).split("{home}").join(ctx.home).split("{cwdKey}").join(ctx.cwdKey);
|
|
28
29
|
return path.normalize(expanded);
|
|
29
30
|
}
|
|
30
31
|
function toPosix(p) {
|
|
@@ -181,6 +182,7 @@ function defaultConfig(projectRoot) {
|
|
|
181
182
|
name: path2.basename(projectRoot),
|
|
182
183
|
rootHint: projectRoot,
|
|
183
184
|
tiers: ["core", "handoff"],
|
|
185
|
+
assistants: ["auto"],
|
|
184
186
|
artifactPaths: ["graphify-out"],
|
|
185
187
|
exclude: [...DEFAULT_EXCLUDE],
|
|
186
188
|
remotes: {}
|
|
@@ -209,7 +211,9 @@ function loadConfig(projectRoot) {
|
|
|
209
211
|
...defaultConfig(projectRoot),
|
|
210
212
|
...cfg,
|
|
211
213
|
remotes: cfg.remotes ?? {},
|
|
212
|
-
tiers: cfg.tiers ?? ["core", "handoff"]
|
|
214
|
+
tiers: cfg.tiers ?? ["core", "handoff"],
|
|
215
|
+
// Stores written before multi-assistant support have no `assistants` key.
|
|
216
|
+
assistants: cfg.assistants ?? ["auto"]
|
|
213
217
|
};
|
|
214
218
|
}
|
|
215
219
|
function saveConfig(projectRoot, cfg) {
|
|
@@ -217,31 +221,27 @@ function saveConfig(projectRoot, cfg) {
|
|
|
217
221
|
}
|
|
218
222
|
|
|
219
223
|
// src/collector.ts
|
|
224
|
+
import fs4 from "fs";
|
|
225
|
+
import path4 from "path";
|
|
226
|
+
|
|
227
|
+
// src/assistants.ts
|
|
220
228
|
import fs3 from "fs";
|
|
229
|
+
import os2 from "os";
|
|
221
230
|
import path3 from "path";
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
// `.local.json` variants are gitignored by default, so nothing else carries
|
|
232
|
-
// them — which makes them exactly the kind of file this tool exists for.
|
|
233
|
-
".claude/settings.local.json",
|
|
234
|
-
".claude/memory/",
|
|
235
|
-
".claude/plans/",
|
|
236
|
-
".claude/commands/",
|
|
237
|
-
".claude/agents/",
|
|
238
|
-
".claude/skills/"
|
|
239
|
-
];
|
|
231
|
+
function filesIn(dir, exclude, prefix) {
|
|
232
|
+
return walk(dir, { exclude }).map((abs) => ({
|
|
233
|
+
abs,
|
|
234
|
+
rel: `${prefix}/${toPosix(path3.relative(dir, abs))}`
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
function fileIfExists(abs, rel) {
|
|
238
|
+
return fs3.existsSync(abs) ? [{ abs, rel }] : [];
|
|
239
|
+
}
|
|
240
240
|
function planStem(fileName) {
|
|
241
241
|
return fileName.replace(/\.md$/, "").replace(/-agent-[0-9a-f]+$/i, "");
|
|
242
242
|
}
|
|
243
|
-
function
|
|
244
|
-
const all = walk(
|
|
243
|
+
function relatedByProject(dir, projectRoot, exclude) {
|
|
244
|
+
const all = walk(dir, { exclude });
|
|
245
245
|
if (all.length === 0) return [];
|
|
246
246
|
const projectName = path3.basename(projectRoot);
|
|
247
247
|
const related = /* @__PURE__ */ new Set();
|
|
@@ -263,10 +263,143 @@ function collectPlans(plansDir, projectRoot, exclude) {
|
|
|
263
263
|
}
|
|
264
264
|
return [...related];
|
|
265
265
|
}
|
|
266
|
+
var ADAPTERS = [
|
|
267
|
+
{
|
|
268
|
+
id: "claude",
|
|
269
|
+
name: "Claude Code",
|
|
270
|
+
projectGlobs: [
|
|
271
|
+
"CLAUDE.md",
|
|
272
|
+
"CLAUDE.local.md",
|
|
273
|
+
"**/CLAUDE.md",
|
|
274
|
+
".claude/settings.json",
|
|
275
|
+
".claude/settings.local.json",
|
|
276
|
+
".claude/memory/",
|
|
277
|
+
".claude/plans/",
|
|
278
|
+
".claude/commands/",
|
|
279
|
+
".claude/agents/",
|
|
280
|
+
".claude/skills/"
|
|
281
|
+
],
|
|
282
|
+
userDir: () => userClaudeDir(),
|
|
283
|
+
collectUser: (projectRoot, _home, exclude) => {
|
|
284
|
+
const root = userClaudeDir();
|
|
285
|
+
const out = [];
|
|
286
|
+
const key = cwdKey(projectRoot);
|
|
287
|
+
const projectsDir = path3.join(root, "projects");
|
|
288
|
+
let keys = [];
|
|
289
|
+
try {
|
|
290
|
+
keys = fs3.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && (d.name === key || d.name.startsWith(key + "-"))).map((d) => d.name);
|
|
291
|
+
} catch {
|
|
292
|
+
keys = [];
|
|
293
|
+
}
|
|
294
|
+
for (const k of keys) {
|
|
295
|
+
const memDir = path3.join(projectsDir, k, "memory");
|
|
296
|
+
const prefix = k === key ? "memory" : `memory-sub/${k.slice(key.length + 1)}`;
|
|
297
|
+
out.push(...filesIn(memDir, exclude, prefix));
|
|
298
|
+
}
|
|
299
|
+
out.push(...filesIn(path3.join(root, "skills"), exclude, "skills"));
|
|
300
|
+
out.push(...filesIn(path3.join(root, "agents"), exclude, "agents"));
|
|
301
|
+
for (const abs of relatedByProject(path3.join(root, "plans"), projectRoot, exclude)) {
|
|
302
|
+
out.push({ abs, rel: `plans/${path3.basename(abs)}` });
|
|
303
|
+
}
|
|
304
|
+
out.push(...fileIfExists(path3.join(root, "CLAUDE.md"), "user/CLAUDE.md"));
|
|
305
|
+
out.push(...fileIfExists(path3.join(root, "settings.json"), "user/settings.json"));
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "codex",
|
|
311
|
+
name: "OpenAI Codex",
|
|
312
|
+
projectGlobs: ["AGENTS.md", "**/AGENTS.md", ".codex/"],
|
|
313
|
+
userDir: (home) => path3.join(home, ".codex"),
|
|
314
|
+
collectUser: (projectRoot, home, exclude) => {
|
|
315
|
+
const root = path3.join(home, ".codex");
|
|
316
|
+
const out = [];
|
|
317
|
+
out.push(...fileIfExists(path3.join(root, "AGENTS.md"), "user/AGENTS.md"));
|
|
318
|
+
out.push(...fileIfExists(path3.join(root, "config.toml"), "user/config.toml"));
|
|
319
|
+
out.push(...filesIn(path3.join(root, "prompts"), exclude, "prompts"));
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: "cursor",
|
|
325
|
+
name: "Cursor",
|
|
326
|
+
// `.cursorrules` is the legacy single-file form; `.cursor/rules/*.mdc` is current.
|
|
327
|
+
projectGlobs: [".cursorrules", ".cursor/rules/", ".cursor/"],
|
|
328
|
+
userDir: (home) => path3.join(home, ".cursor"),
|
|
329
|
+
collectUser: (_projectRoot, home, exclude) => filesIn(path3.join(home, ".cursor", "rules"), exclude, "rules")
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: "copilot",
|
|
333
|
+
name: "GitHub Copilot",
|
|
334
|
+
projectGlobs: [
|
|
335
|
+
".github/copilot-instructions.md",
|
|
336
|
+
".github/instructions/",
|
|
337
|
+
".github/prompts/"
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: "windsurf",
|
|
342
|
+
name: "Windsurf",
|
|
343
|
+
projectGlobs: [".windsurfrules", ".windsurf/rules/", ".windsurf/"],
|
|
344
|
+
userDir: (home) => path3.join(home, ".windsurf")
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: "gemini",
|
|
348
|
+
name: "Gemini CLI",
|
|
349
|
+
projectGlobs: ["GEMINI.md", "**/GEMINI.md", ".gemini/"],
|
|
350
|
+
userDir: (home) => path3.join(home, ".gemini"),
|
|
351
|
+
collectUser: (_projectRoot, home, exclude) => {
|
|
352
|
+
const root = path3.join(home, ".gemini");
|
|
353
|
+
const out = [];
|
|
354
|
+
out.push(...fileIfExists(path3.join(root, "GEMINI.md"), "user/GEMINI.md"));
|
|
355
|
+
out.push(...fileIfExists(path3.join(root, "settings.json"), "user/settings.json"));
|
|
356
|
+
out.push(...filesIn(path3.join(root, "commands"), exclude, "commands"));
|
|
357
|
+
return out;
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: "cline",
|
|
362
|
+
name: "Cline",
|
|
363
|
+
projectGlobs: [".clinerules", ".clinerules/"]
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "aider",
|
|
367
|
+
name: "Aider",
|
|
368
|
+
projectGlobs: ["CONVENTIONS.md", ".aider.conf.yml", ".aider.conf.yaml"]
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
id: "continue",
|
|
372
|
+
name: "Continue",
|
|
373
|
+
projectGlobs: [".continue/", ".continuerules"],
|
|
374
|
+
userDir: (home) => path3.join(home, ".continue")
|
|
375
|
+
}
|
|
376
|
+
];
|
|
377
|
+
function adapterById(id) {
|
|
378
|
+
return ADAPTERS.find((a) => a.id === id);
|
|
379
|
+
}
|
|
380
|
+
function homeDir() {
|
|
381
|
+
return os2.homedir();
|
|
382
|
+
}
|
|
383
|
+
function detectAssistants(projectRoot, projectFiles) {
|
|
384
|
+
const home = homeDir();
|
|
385
|
+
return ADAPTERS.filter((a) => {
|
|
386
|
+
if (a.userDir) {
|
|
387
|
+
try {
|
|
388
|
+
if (fs3.existsSync(a.userDir(home))) return true;
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return projectFiles.some((rel) => matchesAny(rel, a.projectGlobs));
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/collector.ts
|
|
266
397
|
function push(out, sourcePath, storePath, tier, sourceRoot, ctx) {
|
|
267
398
|
let size = 0;
|
|
268
399
|
try {
|
|
269
|
-
|
|
400
|
+
const st = fs4.statSync(sourcePath);
|
|
401
|
+
if (!st.isFile()) return;
|
|
402
|
+
size = st.size;
|
|
270
403
|
} catch {
|
|
271
404
|
return;
|
|
272
405
|
}
|
|
@@ -279,76 +412,66 @@ function push(out, sourcePath, storePath, tier, sourceRoot, ctx) {
|
|
|
279
412
|
restoreTemplate: makeTemplate(sourcePath, ctx)
|
|
280
413
|
});
|
|
281
414
|
}
|
|
415
|
+
function templateContextFor(projectRoot) {
|
|
416
|
+
return {
|
|
417
|
+
userClaude: userClaudeDir(),
|
|
418
|
+
project: projectRoot,
|
|
419
|
+
cwdKey: cwdKey(projectRoot),
|
|
420
|
+
home: homeDir()
|
|
421
|
+
};
|
|
422
|
+
}
|
|
282
423
|
function collect(projectRoot, cfg, tiers) {
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
const ctx = { userClaude, project: projectRoot, cwdKey: key };
|
|
424
|
+
const ctx = templateContextFor(projectRoot);
|
|
425
|
+
const home = ctx.home;
|
|
286
426
|
const files = [];
|
|
287
427
|
const skippedTracked = [];
|
|
288
428
|
const exclude = [...HARD_DENY, ...cfg.exclude];
|
|
289
429
|
const tracked = gitTrackedSet(projectRoot);
|
|
430
|
+
const projectRel = walk(projectRoot, { exclude }).map((abs) => ({
|
|
431
|
+
abs,
|
|
432
|
+
rel: toPosix(path4.relative(projectRoot, abs))
|
|
433
|
+
}));
|
|
434
|
+
const configured = cfg.assistants && cfg.assistants.length > 0 && !cfg.assistants.includes("auto");
|
|
435
|
+
const assistants = configured ? cfg.assistants.map(adapterById).filter(Boolean) : detectAssistants(projectRoot, projectRel.map((p) => p.rel));
|
|
290
436
|
if (tiers.includes("core")) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
437
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
438
|
+
for (const adapter of assistants) {
|
|
439
|
+
for (const { abs, rel } of projectRel) {
|
|
440
|
+
if (claimed.has(rel)) continue;
|
|
441
|
+
if (!matchesAny(rel, adapter.projectGlobs)) continue;
|
|
442
|
+
claimed.add(rel);
|
|
443
|
+
if (tracked.has(abs)) {
|
|
444
|
+
skippedTracked.push(rel);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
push(files, abs, `project/${rel}`, "core", "project", ctx);
|
|
297
448
|
}
|
|
298
|
-
push(files, abs, `project/${rel}`, "core", "project", ctx);
|
|
299
449
|
}
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
} catch {
|
|
305
|
-
projectKeys = [];
|
|
306
|
-
}
|
|
307
|
-
for (const pk of projectKeys) {
|
|
308
|
-
const memoryDir = path3.join(projectsDir, pk, "memory");
|
|
309
|
-
for (const abs of walk(memoryDir, { exclude })) {
|
|
310
|
-
const rel = toPosix(path3.relative(memoryDir, abs));
|
|
311
|
-
const storePath = pk === key ? `memory/${rel}` : `memory-sub/${pk.slice(key.length + 1)}/${rel}`;
|
|
312
|
-
push(files, abs, storePath, "core", "user", ctx);
|
|
450
|
+
for (const adapter of assistants) {
|
|
451
|
+
if (!adapter.collectUser) continue;
|
|
452
|
+
for (const { abs, rel } of adapter.collectUser(projectRoot, home, exclude)) {
|
|
453
|
+
push(files, abs, `assistants/${adapter.id}/${rel}`, "core", "user", ctx);
|
|
313
454
|
}
|
|
314
455
|
}
|
|
315
|
-
for (const abs of collectPlans(path3.join(userClaude, "plans"), projectRoot, exclude)) {
|
|
316
|
-
const rel = toPosix(path3.relative(path3.join(userClaude, "plans"), abs));
|
|
317
|
-
push(files, abs, `plans/${rel}`, "core", "user", ctx);
|
|
318
|
-
}
|
|
319
|
-
const skillsDir = path3.join(userClaude, "skills");
|
|
320
|
-
for (const abs of walk(skillsDir, { exclude })) {
|
|
321
|
-
const rel = toPosix(path3.relative(skillsDir, abs));
|
|
322
|
-
push(files, abs, `skills/${rel}`, "core", "user", ctx);
|
|
323
|
-
}
|
|
324
|
-
const agentsDir = path3.join(userClaude, "agents");
|
|
325
|
-
for (const abs of walk(agentsDir, { exclude })) {
|
|
326
|
-
const rel = toPosix(path3.relative(agentsDir, abs));
|
|
327
|
-
push(files, abs, `agents/${rel}`, "core", "user", ctx);
|
|
328
|
-
}
|
|
329
|
-
for (const name of ["CLAUDE.md", "settings.json"]) {
|
|
330
|
-
const abs = path3.join(userClaude, name);
|
|
331
|
-
if (fs3.existsSync(abs)) push(files, abs, `user/${name}`, "core", "user", ctx);
|
|
332
|
-
}
|
|
333
456
|
}
|
|
334
457
|
if (tiers.includes("artifacts")) {
|
|
335
458
|
for (const relDir of cfg.artifactPaths) {
|
|
336
|
-
const absDir =
|
|
459
|
+
const absDir = path4.join(projectRoot, relDir);
|
|
337
460
|
for (const abs of walk(absDir, { exclude })) {
|
|
338
|
-
const rel = toPosix(
|
|
461
|
+
const rel = toPosix(path4.relative(absDir, abs));
|
|
339
462
|
push(files, abs, `artifacts/${relDir}/${rel}`, "artifacts", "project", ctx);
|
|
340
463
|
}
|
|
341
464
|
}
|
|
342
465
|
}
|
|
343
466
|
if (tiers.includes("transcripts")) {
|
|
344
|
-
const projDir =
|
|
467
|
+
const projDir = path4.join(userClaudeDir(), "projects", ctx.cwdKey);
|
|
345
468
|
for (const abs of walk(projDir, { exclude })) {
|
|
346
|
-
const rel = toPosix(
|
|
469
|
+
const rel = toPosix(path4.relative(projDir, abs));
|
|
347
470
|
if (rel.startsWith("memory/")) continue;
|
|
348
|
-
push(files, abs, `transcripts/${rel}`, "transcripts", "user", ctx);
|
|
471
|
+
push(files, abs, `transcripts/claude/${rel}`, "transcripts", "user", ctx);
|
|
349
472
|
}
|
|
350
473
|
}
|
|
351
|
-
return { files, skippedTracked, ctx };
|
|
474
|
+
return { files, skippedTracked, assistants, ctx };
|
|
352
475
|
}
|
|
353
476
|
function describeExcluded(projectRoot, tiers) {
|
|
354
477
|
const userClaude = userClaudeDir();
|
|
@@ -361,15 +484,17 @@ function describeExcluded(projectRoot, tiers) {
|
|
|
361
484
|
if (filter && !filter(abs)) continue;
|
|
362
485
|
files++;
|
|
363
486
|
try {
|
|
364
|
-
bytes +=
|
|
487
|
+
bytes += fs4.statSync(abs).size;
|
|
365
488
|
} catch {
|
|
366
489
|
}
|
|
367
490
|
}
|
|
368
491
|
return { files, bytes };
|
|
369
492
|
};
|
|
370
493
|
if (!tiers.includes("transcripts")) {
|
|
371
|
-
const
|
|
372
|
-
|
|
494
|
+
const m = measure(
|
|
495
|
+
path4.join(userClaude, "projects", key),
|
|
496
|
+
(p) => !p.includes(`${path4.sep}memory${path4.sep}`)
|
|
497
|
+
);
|
|
373
498
|
if (m.files > 0) {
|
|
374
499
|
out.push({
|
|
375
500
|
label: "session transcripts",
|
|
@@ -383,7 +508,7 @@ function describeExcluded(projectRoot, tiers) {
|
|
|
383
508
|
["file-history", "edit-undo snapshots", "transient local undo state, not portable context"],
|
|
384
509
|
["tasks", "task outputs", "session-scoped tool output"]
|
|
385
510
|
]) {
|
|
386
|
-
const m = measure(
|
|
511
|
+
const m = measure(path4.join(userClaude, dir));
|
|
387
512
|
if (m.files > 0) out.push({ label, ...m, reason });
|
|
388
513
|
}
|
|
389
514
|
return out.filter((g) => g.bytes > 0);
|
|
@@ -484,8 +609,8 @@ function isStale(h, newestContentMs) {
|
|
|
484
609
|
}
|
|
485
610
|
|
|
486
611
|
// src/scaffold.ts
|
|
487
|
-
import
|
|
488
|
-
import
|
|
612
|
+
import fs5 from "fs";
|
|
613
|
+
import path5 from "path";
|
|
489
614
|
var SLASH_COMMAND_PATH = ".claude/commands/context.md";
|
|
490
615
|
var SLASH_COMMAND_BODY = `---
|
|
491
616
|
description: Sync this project's LLM context (memory, skills, instructions, handoff)
|
|
@@ -535,26 +660,26 @@ Run the context-sync action requested in: $ARGUMENTS
|
|
|
535
660
|
Run: \`npx @tricknowtech/context status\` and summarize the result.
|
|
536
661
|
`;
|
|
537
662
|
function installSlashCommand(projectRoot) {
|
|
538
|
-
const dest =
|
|
539
|
-
if (
|
|
540
|
-
ensureDir(
|
|
541
|
-
|
|
663
|
+
const dest = path5.join(projectRoot, SLASH_COMMAND_PATH);
|
|
664
|
+
if (fs5.existsSync(dest)) return { path: SLASH_COMMAND_PATH, written: false };
|
|
665
|
+
ensureDir(path5.dirname(dest));
|
|
666
|
+
fs5.writeFileSync(dest, SLASH_COMMAND_BODY, "utf8");
|
|
542
667
|
return { path: SLASH_COMMAND_PATH, written: true };
|
|
543
668
|
}
|
|
544
669
|
function ensureGitignoreEntries(projectRoot, artifactsEnabled) {
|
|
545
|
-
const gitignore =
|
|
670
|
+
const gitignore = path5.join(projectRoot, ".gitignore");
|
|
546
671
|
const wanted = [".contextsync/transcripts/"];
|
|
547
672
|
if (!artifactsEnabled) wanted.push(".contextsync/artifacts/");
|
|
548
673
|
let existing = "";
|
|
549
674
|
try {
|
|
550
|
-
existing =
|
|
675
|
+
existing = fs5.readFileSync(gitignore, "utf8");
|
|
551
676
|
} catch {
|
|
552
677
|
}
|
|
553
678
|
const lines = new Set(existing.split("\n").map((l) => l.trim()));
|
|
554
679
|
const missing = wanted.filter((w) => !lines.has(w));
|
|
555
680
|
if (missing.length === 0) return [];
|
|
556
681
|
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
557
|
-
|
|
682
|
+
fs5.appendFileSync(
|
|
558
683
|
gitignore,
|
|
559
684
|
`${prefix}
|
|
560
685
|
# tricknowtech context-sync \u2014 never commit these tiers
|
|
@@ -566,7 +691,7 @@ ${missing.join("\n")}
|
|
|
566
691
|
}
|
|
567
692
|
|
|
568
693
|
// src/secrets.ts
|
|
569
|
-
import
|
|
694
|
+
import fs6 from "fs";
|
|
570
695
|
var BENIGN_KEY = /(?:^|[_-])(?:input|output|prompt|completion|total|max|min|num|new|cache|cached|remaining|used|count|context|window|budget|estimated?)[_-]?tokens?$|tokens?[_-]?(?:count|used|limit|remaining|in|out|usage|per|budget)$/i;
|
|
571
696
|
function looksLikeCredential(value) {
|
|
572
697
|
if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value)) return false;
|
|
@@ -620,7 +745,7 @@ function scanFiles(files) {
|
|
|
620
745
|
for (const file of files) {
|
|
621
746
|
let buf;
|
|
622
747
|
try {
|
|
623
|
-
buf =
|
|
748
|
+
buf = fs6.readFileSync(file.sourcePath);
|
|
624
749
|
} catch {
|
|
625
750
|
continue;
|
|
626
751
|
}
|
|
@@ -658,8 +783,8 @@ function formatHits(hits) {
|
|
|
658
783
|
}
|
|
659
784
|
|
|
660
785
|
// src/store.ts
|
|
661
|
-
import
|
|
662
|
-
import
|
|
786
|
+
import fs7 from "fs";
|
|
787
|
+
import path6 from "path";
|
|
663
788
|
var MANIFEST_FILE = "manifest.json";
|
|
664
789
|
var LocalStore = class {
|
|
665
790
|
constructor(projectRoot) {
|
|
@@ -670,13 +795,13 @@ var LocalStore = class {
|
|
|
670
795
|
return storeDir(this.projectRoot);
|
|
671
796
|
}
|
|
672
797
|
write(files, projectRoot) {
|
|
673
|
-
for (const name of
|
|
798
|
+
for (const name of fs7.existsSync(this.dir) ? fs7.readdirSync(this.dir) : []) {
|
|
674
799
|
if (name === "config.json" || name === HANDOFF_FILE) continue;
|
|
675
|
-
|
|
800
|
+
fs7.rmSync(path6.join(this.dir, name), { recursive: true, force: true });
|
|
676
801
|
}
|
|
677
802
|
const entries = [];
|
|
678
803
|
for (const file of files) {
|
|
679
|
-
const dest =
|
|
804
|
+
const dest = path6.join(this.dir, file.storePath);
|
|
680
805
|
try {
|
|
681
806
|
copyFile(file.sourcePath, dest);
|
|
682
807
|
} catch {
|
|
@@ -697,11 +822,11 @@ var LocalStore = class {
|
|
|
697
822
|
writtenFrom: projectRoot,
|
|
698
823
|
entries
|
|
699
824
|
};
|
|
700
|
-
writeJson(
|
|
825
|
+
writeJson(path6.join(this.dir, MANIFEST_FILE), manifest);
|
|
701
826
|
return manifest;
|
|
702
827
|
}
|
|
703
828
|
readManifest() {
|
|
704
|
-
return readJson(
|
|
829
|
+
return readJson(path6.join(this.dir, MANIFEST_FILE));
|
|
705
830
|
}
|
|
706
831
|
restore(ctx, opts = {}) {
|
|
707
832
|
const manifest = this.readManifest();
|
|
@@ -709,12 +834,12 @@ var LocalStore = class {
|
|
|
709
834
|
const skipped = [];
|
|
710
835
|
if (!manifest) return { restored, skipped };
|
|
711
836
|
for (const entry of manifest.entries) {
|
|
712
|
-
const src =
|
|
713
|
-
if (!
|
|
837
|
+
const src = path6.join(this.dir, entry.storePath);
|
|
838
|
+
if (!fs7.existsSync(src)) continue;
|
|
714
839
|
const dest = resolveTemplate(entry.restoreTemplate, ctx);
|
|
715
|
-
if (!opts.force &&
|
|
840
|
+
if (!opts.force && fs7.existsSync(dest)) {
|
|
716
841
|
try {
|
|
717
|
-
if (!
|
|
842
|
+
if (!fs7.readFileSync(dest).equals(fs7.readFileSync(src))) {
|
|
718
843
|
skipped.push(entry.storePath);
|
|
719
844
|
continue;
|
|
720
845
|
}
|
|
@@ -724,8 +849,8 @@ var LocalStore = class {
|
|
|
724
849
|
}
|
|
725
850
|
}
|
|
726
851
|
try {
|
|
727
|
-
ensureDir(
|
|
728
|
-
|
|
852
|
+
ensureDir(path6.dirname(dest));
|
|
853
|
+
fs7.copyFileSync(src, dest);
|
|
729
854
|
restored.push(entry.storePath);
|
|
730
855
|
} catch {
|
|
731
856
|
skipped.push(entry.storePath);
|
|
@@ -734,10 +859,10 @@ var LocalStore = class {
|
|
|
734
859
|
return { restored, skipped };
|
|
735
860
|
}
|
|
736
861
|
readHandoff() {
|
|
737
|
-
return readJson(
|
|
862
|
+
return readJson(path6.join(this.dir, HANDOFF_FILE));
|
|
738
863
|
}
|
|
739
864
|
writeHandoff(handoff) {
|
|
740
|
-
writeJson(
|
|
865
|
+
writeJson(path6.join(this.dir, HANDOFF_FILE), handoff);
|
|
741
866
|
}
|
|
742
867
|
};
|
|
743
868
|
|
|
@@ -766,6 +891,7 @@ export {
|
|
|
766
891
|
configPath,
|
|
767
892
|
loadConfig,
|
|
768
893
|
saveConfig,
|
|
894
|
+
templateContextFor,
|
|
769
895
|
collect,
|
|
770
896
|
describeExcluded,
|
|
771
897
|
summarize,
|