@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/dist/cli.cjs
CHANGED
|
@@ -37,16 +37,16 @@ module.exports = __toCommonJS(cli_exports);
|
|
|
37
37
|
|
|
38
38
|
// src/commands.ts
|
|
39
39
|
var import_node_child_process2 = require("child_process");
|
|
40
|
-
var
|
|
41
|
-
var
|
|
40
|
+
var import_node_fs8 = __toESM(require("fs"), 1);
|
|
41
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
42
42
|
|
|
43
43
|
// src/collector.ts
|
|
44
|
-
var
|
|
45
|
-
var
|
|
44
|
+
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
45
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
46
46
|
|
|
47
|
-
// src/
|
|
48
|
-
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
47
|
+
// src/assistants.ts
|
|
49
48
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
49
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
50
50
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
51
51
|
|
|
52
52
|
// src/fsutil.ts
|
|
@@ -63,8 +63,9 @@ function cwdKey(absPath) {
|
|
|
63
63
|
function makeTemplate(absPath, ctx) {
|
|
64
64
|
const candidates = [
|
|
65
65
|
[ctx.userClaude, "{userClaude}"],
|
|
66
|
-
[ctx.project, "{project}"]
|
|
67
|
-
|
|
66
|
+
[ctx.project, "{project}"],
|
|
67
|
+
[ctx.home, "{home}"]
|
|
68
|
+
].filter(([root]) => Boolean(root)).sort((a, b) => b[0].length - a[0].length);
|
|
68
69
|
let out2 = absPath;
|
|
69
70
|
for (const [root, token] of candidates) {
|
|
70
71
|
if (absPath === root || absPath.startsWith(root + import_node_path.default.sep)) {
|
|
@@ -75,7 +76,7 @@ function makeTemplate(absPath, ctx) {
|
|
|
75
76
|
return ctx.cwdKey ? out2.split(ctx.cwdKey).join("{cwdKey}") : out2;
|
|
76
77
|
}
|
|
77
78
|
function resolveTemplate(template, ctx) {
|
|
78
|
-
const expanded = template.split("{userClaude}").join(ctx.userClaude).split("{project}").join(ctx.project).split("{cwdKey}").join(ctx.cwdKey);
|
|
79
|
+
const expanded = template.split("{userClaude}").join(ctx.userClaude).split("{project}").join(ctx.project).split("{home}").join(ctx.home).split("{cwdKey}").join(ctx.cwdKey);
|
|
79
80
|
return import_node_path.default.normalize(expanded);
|
|
80
81
|
}
|
|
81
82
|
function toPosix(p) {
|
|
@@ -197,7 +198,176 @@ function formatBytes(n) {
|
|
|
197
198
|
return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
// src/assistants.ts
|
|
202
|
+
function filesIn(dir, exclude, prefix) {
|
|
203
|
+
return walk(dir, { exclude }).map((abs) => ({
|
|
204
|
+
abs,
|
|
205
|
+
rel: `${prefix}/${toPosix(import_node_path2.default.relative(dir, abs))}`
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
function fileIfExists(abs, rel) {
|
|
209
|
+
return import_node_fs2.default.existsSync(abs) ? [{ abs, rel }] : [];
|
|
210
|
+
}
|
|
211
|
+
function planStem(fileName) {
|
|
212
|
+
return fileName.replace(/\.md$/, "").replace(/-agent-[0-9a-f]+$/i, "");
|
|
213
|
+
}
|
|
214
|
+
function relatedByProject(dir, projectRoot, exclude) {
|
|
215
|
+
const all = walk(dir, { exclude });
|
|
216
|
+
if (all.length === 0) return [];
|
|
217
|
+
const projectName = import_node_path2.default.basename(projectRoot);
|
|
218
|
+
const related = /* @__PURE__ */ new Set();
|
|
219
|
+
const stems = /* @__PURE__ */ new Set();
|
|
220
|
+
for (const abs of all) {
|
|
221
|
+
let text = "";
|
|
222
|
+
try {
|
|
223
|
+
text = import_node_fs2.default.readFileSync(abs, "utf8");
|
|
224
|
+
} catch {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (text.includes(projectRoot) || text.includes(projectName)) {
|
|
228
|
+
related.add(abs);
|
|
229
|
+
stems.add(planStem(import_node_path2.default.basename(abs)));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
for (const abs of all) {
|
|
233
|
+
if (stems.has(planStem(import_node_path2.default.basename(abs)))) related.add(abs);
|
|
234
|
+
}
|
|
235
|
+
return [...related];
|
|
236
|
+
}
|
|
237
|
+
var ADAPTERS = [
|
|
238
|
+
{
|
|
239
|
+
id: "claude",
|
|
240
|
+
name: "Claude Code",
|
|
241
|
+
projectGlobs: [
|
|
242
|
+
"CLAUDE.md",
|
|
243
|
+
"CLAUDE.local.md",
|
|
244
|
+
"**/CLAUDE.md",
|
|
245
|
+
".claude/settings.json",
|
|
246
|
+
".claude/settings.local.json",
|
|
247
|
+
".claude/memory/",
|
|
248
|
+
".claude/plans/",
|
|
249
|
+
".claude/commands/",
|
|
250
|
+
".claude/agents/",
|
|
251
|
+
".claude/skills/"
|
|
252
|
+
],
|
|
253
|
+
userDir: () => userClaudeDir(),
|
|
254
|
+
collectUser: (projectRoot, _home, exclude) => {
|
|
255
|
+
const root = userClaudeDir();
|
|
256
|
+
const out2 = [];
|
|
257
|
+
const key = cwdKey(projectRoot);
|
|
258
|
+
const projectsDir = import_node_path2.default.join(root, "projects");
|
|
259
|
+
let keys = [];
|
|
260
|
+
try {
|
|
261
|
+
keys = import_node_fs2.default.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && (d.name === key || d.name.startsWith(key + "-"))).map((d) => d.name);
|
|
262
|
+
} catch {
|
|
263
|
+
keys = [];
|
|
264
|
+
}
|
|
265
|
+
for (const k of keys) {
|
|
266
|
+
const memDir = import_node_path2.default.join(projectsDir, k, "memory");
|
|
267
|
+
const prefix = k === key ? "memory" : `memory-sub/${k.slice(key.length + 1)}`;
|
|
268
|
+
out2.push(...filesIn(memDir, exclude, prefix));
|
|
269
|
+
}
|
|
270
|
+
out2.push(...filesIn(import_node_path2.default.join(root, "skills"), exclude, "skills"));
|
|
271
|
+
out2.push(...filesIn(import_node_path2.default.join(root, "agents"), exclude, "agents"));
|
|
272
|
+
for (const abs of relatedByProject(import_node_path2.default.join(root, "plans"), projectRoot, exclude)) {
|
|
273
|
+
out2.push({ abs, rel: `plans/${import_node_path2.default.basename(abs)}` });
|
|
274
|
+
}
|
|
275
|
+
out2.push(...fileIfExists(import_node_path2.default.join(root, "CLAUDE.md"), "user/CLAUDE.md"));
|
|
276
|
+
out2.push(...fileIfExists(import_node_path2.default.join(root, "settings.json"), "user/settings.json"));
|
|
277
|
+
return out2;
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: "codex",
|
|
282
|
+
name: "OpenAI Codex",
|
|
283
|
+
projectGlobs: ["AGENTS.md", "**/AGENTS.md", ".codex/"],
|
|
284
|
+
userDir: (home) => import_node_path2.default.join(home, ".codex"),
|
|
285
|
+
collectUser: (projectRoot, home, exclude) => {
|
|
286
|
+
const root = import_node_path2.default.join(home, ".codex");
|
|
287
|
+
const out2 = [];
|
|
288
|
+
out2.push(...fileIfExists(import_node_path2.default.join(root, "AGENTS.md"), "user/AGENTS.md"));
|
|
289
|
+
out2.push(...fileIfExists(import_node_path2.default.join(root, "config.toml"), "user/config.toml"));
|
|
290
|
+
out2.push(...filesIn(import_node_path2.default.join(root, "prompts"), exclude, "prompts"));
|
|
291
|
+
return out2;
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: "cursor",
|
|
296
|
+
name: "Cursor",
|
|
297
|
+
// `.cursorrules` is the legacy single-file form; `.cursor/rules/*.mdc` is current.
|
|
298
|
+
projectGlobs: [".cursorrules", ".cursor/rules/", ".cursor/"],
|
|
299
|
+
userDir: (home) => import_node_path2.default.join(home, ".cursor"),
|
|
300
|
+
collectUser: (_projectRoot, home, exclude) => filesIn(import_node_path2.default.join(home, ".cursor", "rules"), exclude, "rules")
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: "copilot",
|
|
304
|
+
name: "GitHub Copilot",
|
|
305
|
+
projectGlobs: [
|
|
306
|
+
".github/copilot-instructions.md",
|
|
307
|
+
".github/instructions/",
|
|
308
|
+
".github/prompts/"
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: "windsurf",
|
|
313
|
+
name: "Windsurf",
|
|
314
|
+
projectGlobs: [".windsurfrules", ".windsurf/rules/", ".windsurf/"],
|
|
315
|
+
userDir: (home) => import_node_path2.default.join(home, ".windsurf")
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: "gemini",
|
|
319
|
+
name: "Gemini CLI",
|
|
320
|
+
projectGlobs: ["GEMINI.md", "**/GEMINI.md", ".gemini/"],
|
|
321
|
+
userDir: (home) => import_node_path2.default.join(home, ".gemini"),
|
|
322
|
+
collectUser: (_projectRoot, home, exclude) => {
|
|
323
|
+
const root = import_node_path2.default.join(home, ".gemini");
|
|
324
|
+
const out2 = [];
|
|
325
|
+
out2.push(...fileIfExists(import_node_path2.default.join(root, "GEMINI.md"), "user/GEMINI.md"));
|
|
326
|
+
out2.push(...fileIfExists(import_node_path2.default.join(root, "settings.json"), "user/settings.json"));
|
|
327
|
+
out2.push(...filesIn(import_node_path2.default.join(root, "commands"), exclude, "commands"));
|
|
328
|
+
return out2;
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: "cline",
|
|
333
|
+
name: "Cline",
|
|
334
|
+
projectGlobs: [".clinerules", ".clinerules/"]
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: "aider",
|
|
338
|
+
name: "Aider",
|
|
339
|
+
projectGlobs: ["CONVENTIONS.md", ".aider.conf.yml", ".aider.conf.yaml"]
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: "continue",
|
|
343
|
+
name: "Continue",
|
|
344
|
+
projectGlobs: [".continue/", ".continuerules"],
|
|
345
|
+
userDir: (home) => import_node_path2.default.join(home, ".continue")
|
|
346
|
+
}
|
|
347
|
+
];
|
|
348
|
+
function adapterById(id) {
|
|
349
|
+
return ADAPTERS.find((a) => a.id === id);
|
|
350
|
+
}
|
|
351
|
+
function homeDir() {
|
|
352
|
+
return import_node_os2.default.homedir();
|
|
353
|
+
}
|
|
354
|
+
function detectAssistants(projectRoot, projectFiles) {
|
|
355
|
+
const home = homeDir();
|
|
356
|
+
return ADAPTERS.filter((a) => {
|
|
357
|
+
if (a.userDir) {
|
|
358
|
+
try {
|
|
359
|
+
if (import_node_fs2.default.existsSync(a.userDir(home))) return true;
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return projectFiles.some((rel) => matchesAny(rel, a.projectGlobs));
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
200
367
|
// src/config.ts
|
|
368
|
+
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
369
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
370
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
201
371
|
var STORE_DIR = ".contextsync";
|
|
202
372
|
var CONFIG_FILE = "config.json";
|
|
203
373
|
var HANDOFF_FILE = "handoff.json";
|
|
@@ -226,29 +396,30 @@ var DEFAULT_EXCLUDE = [
|
|
|
226
396
|
function defaultConfig(projectRoot) {
|
|
227
397
|
return {
|
|
228
398
|
projectId: import_node_crypto.default.randomUUID(),
|
|
229
|
-
name:
|
|
399
|
+
name: import_node_path3.default.basename(projectRoot),
|
|
230
400
|
rootHint: projectRoot,
|
|
231
401
|
tiers: ["core", "handoff"],
|
|
402
|
+
assistants: ["auto"],
|
|
232
403
|
artifactPaths: ["graphify-out"],
|
|
233
404
|
exclude: [...DEFAULT_EXCLUDE],
|
|
234
405
|
remotes: {}
|
|
235
406
|
};
|
|
236
407
|
}
|
|
237
408
|
function findProjectRoot(start = process.cwd()) {
|
|
238
|
-
let dir =
|
|
409
|
+
let dir = import_node_path3.default.resolve(start);
|
|
239
410
|
for (; ; ) {
|
|
240
|
-
if (
|
|
241
|
-
if (
|
|
242
|
-
const parent =
|
|
411
|
+
if (import_node_fs3.default.existsSync(import_node_path3.default.join(dir, STORE_DIR, CONFIG_FILE))) return dir;
|
|
412
|
+
if (import_node_fs3.default.existsSync(import_node_path3.default.join(dir, ".git"))) return dir;
|
|
413
|
+
const parent = import_node_path3.default.dirname(dir);
|
|
243
414
|
if (parent === dir) return null;
|
|
244
415
|
dir = parent;
|
|
245
416
|
}
|
|
246
417
|
}
|
|
247
418
|
function storeDir(projectRoot) {
|
|
248
|
-
return
|
|
419
|
+
return import_node_path3.default.join(projectRoot, STORE_DIR);
|
|
249
420
|
}
|
|
250
421
|
function configPath(projectRoot) {
|
|
251
|
-
return
|
|
422
|
+
return import_node_path3.default.join(storeDir(projectRoot), CONFIG_FILE);
|
|
252
423
|
}
|
|
253
424
|
function loadConfig(projectRoot) {
|
|
254
425
|
const cfg = readJson(configPath(projectRoot));
|
|
@@ -257,7 +428,9 @@ function loadConfig(projectRoot) {
|
|
|
257
428
|
...defaultConfig(projectRoot),
|
|
258
429
|
...cfg,
|
|
259
430
|
remotes: cfg.remotes ?? {},
|
|
260
|
-
tiers: cfg.tiers ?? ["core", "handoff"]
|
|
431
|
+
tiers: cfg.tiers ?? ["core", "handoff"],
|
|
432
|
+
// Stores written before multi-assistant support have no `assistants` key.
|
|
433
|
+
assistants: cfg.assistants ?? ["auto"]
|
|
261
434
|
};
|
|
262
435
|
}
|
|
263
436
|
function saveConfig(projectRoot, cfg) {
|
|
@@ -265,54 +438,12 @@ function saveConfig(projectRoot, cfg) {
|
|
|
265
438
|
}
|
|
266
439
|
|
|
267
440
|
// src/collector.ts
|
|
268
|
-
var PROJECT_CONTEXT_GLOBS = [
|
|
269
|
-
"CLAUDE.md",
|
|
270
|
-
"CLAUDE.local.md",
|
|
271
|
-
"AGENTS.md",
|
|
272
|
-
"**/CLAUDE.md",
|
|
273
|
-
"**/AGENTS.md",
|
|
274
|
-
".cursorrules",
|
|
275
|
-
".github/copilot-instructions.md",
|
|
276
|
-
".claude/settings.json",
|
|
277
|
-
// `.local.json` variants are gitignored by default, so nothing else carries
|
|
278
|
-
// them — which makes them exactly the kind of file this tool exists for.
|
|
279
|
-
".claude/settings.local.json",
|
|
280
|
-
".claude/memory/",
|
|
281
|
-
".claude/plans/",
|
|
282
|
-
".claude/commands/",
|
|
283
|
-
".claude/agents/",
|
|
284
|
-
".claude/skills/"
|
|
285
|
-
];
|
|
286
|
-
function planStem(fileName) {
|
|
287
|
-
return fileName.replace(/\.md$/, "").replace(/-agent-[0-9a-f]+$/i, "");
|
|
288
|
-
}
|
|
289
|
-
function collectPlans(plansDir, projectRoot, exclude) {
|
|
290
|
-
const all = walk(plansDir, { exclude });
|
|
291
|
-
if (all.length === 0) return [];
|
|
292
|
-
const projectName = import_node_path3.default.basename(projectRoot);
|
|
293
|
-
const related = /* @__PURE__ */ new Set();
|
|
294
|
-
const stems = /* @__PURE__ */ new Set();
|
|
295
|
-
for (const abs of all) {
|
|
296
|
-
let text = "";
|
|
297
|
-
try {
|
|
298
|
-
text = import_node_fs3.default.readFileSync(abs, "utf8");
|
|
299
|
-
} catch {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
if (text.includes(projectRoot) || text.includes(projectName)) {
|
|
303
|
-
related.add(abs);
|
|
304
|
-
stems.add(planStem(import_node_path3.default.basename(abs)));
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
for (const abs of all) {
|
|
308
|
-
if (stems.has(planStem(import_node_path3.default.basename(abs)))) related.add(abs);
|
|
309
|
-
}
|
|
310
|
-
return [...related];
|
|
311
|
-
}
|
|
312
441
|
function push(out2, sourcePath, storePath, tier, sourceRoot, ctx) {
|
|
313
442
|
let size = 0;
|
|
314
443
|
try {
|
|
315
|
-
|
|
444
|
+
const st = import_node_fs4.default.statSync(sourcePath);
|
|
445
|
+
if (!st.isFile()) return;
|
|
446
|
+
size = st.size;
|
|
316
447
|
} catch {
|
|
317
448
|
return;
|
|
318
449
|
}
|
|
@@ -325,76 +456,66 @@ function push(out2, sourcePath, storePath, tier, sourceRoot, ctx) {
|
|
|
325
456
|
restoreTemplate: makeTemplate(sourcePath, ctx)
|
|
326
457
|
});
|
|
327
458
|
}
|
|
459
|
+
function templateContextFor(projectRoot) {
|
|
460
|
+
return {
|
|
461
|
+
userClaude: userClaudeDir(),
|
|
462
|
+
project: projectRoot,
|
|
463
|
+
cwdKey: cwdKey(projectRoot),
|
|
464
|
+
home: homeDir()
|
|
465
|
+
};
|
|
466
|
+
}
|
|
328
467
|
function collect(projectRoot, cfg, tiers) {
|
|
329
|
-
const
|
|
330
|
-
const
|
|
331
|
-
const ctx = { userClaude, project: projectRoot, cwdKey: key };
|
|
468
|
+
const ctx = templateContextFor(projectRoot);
|
|
469
|
+
const home = ctx.home;
|
|
332
470
|
const files = [];
|
|
333
471
|
const skippedTracked = [];
|
|
334
472
|
const exclude = [...HARD_DENY, ...cfg.exclude];
|
|
335
473
|
const tracked = gitTrackedSet(projectRoot);
|
|
474
|
+
const projectRel = walk(projectRoot, { exclude }).map((abs) => ({
|
|
475
|
+
abs,
|
|
476
|
+
rel: toPosix(import_node_path4.default.relative(projectRoot, abs))
|
|
477
|
+
}));
|
|
478
|
+
const configured = cfg.assistants && cfg.assistants.length > 0 && !cfg.assistants.includes("auto");
|
|
479
|
+
const assistants = configured ? cfg.assistants.map(adapterById).filter(Boolean) : detectAssistants(projectRoot, projectRel.map((p) => p.rel));
|
|
336
480
|
if (tiers.includes("core")) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
481
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
482
|
+
for (const adapter of assistants) {
|
|
483
|
+
for (const { abs, rel } of projectRel) {
|
|
484
|
+
if (claimed.has(rel)) continue;
|
|
485
|
+
if (!matchesAny(rel, adapter.projectGlobs)) continue;
|
|
486
|
+
claimed.add(rel);
|
|
487
|
+
if (tracked.has(abs)) {
|
|
488
|
+
skippedTracked.push(rel);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
push(files, abs, `project/${rel}`, "core", "project", ctx);
|
|
343
492
|
}
|
|
344
|
-
push(files, abs, `project/${rel}`, "core", "project", ctx);
|
|
345
|
-
}
|
|
346
|
-
const projectsDir = import_node_path3.default.join(userClaude, "projects");
|
|
347
|
-
let projectKeys = [];
|
|
348
|
-
try {
|
|
349
|
-
projectKeys = import_node_fs3.default.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && (d.name === key || d.name.startsWith(key + "-"))).map((d) => d.name);
|
|
350
|
-
} catch {
|
|
351
|
-
projectKeys = [];
|
|
352
493
|
}
|
|
353
|
-
for (const
|
|
354
|
-
|
|
355
|
-
for (const abs of
|
|
356
|
-
|
|
357
|
-
const storePath = pk === key ? `memory/${rel}` : `memory-sub/${pk.slice(key.length + 1)}/${rel}`;
|
|
358
|
-
push(files, abs, storePath, "core", "user", ctx);
|
|
494
|
+
for (const adapter of assistants) {
|
|
495
|
+
if (!adapter.collectUser) continue;
|
|
496
|
+
for (const { abs, rel } of adapter.collectUser(projectRoot, home, exclude)) {
|
|
497
|
+
push(files, abs, `assistants/${adapter.id}/${rel}`, "core", "user", ctx);
|
|
359
498
|
}
|
|
360
499
|
}
|
|
361
|
-
for (const abs of collectPlans(import_node_path3.default.join(userClaude, "plans"), projectRoot, exclude)) {
|
|
362
|
-
const rel = toPosix(import_node_path3.default.relative(import_node_path3.default.join(userClaude, "plans"), abs));
|
|
363
|
-
push(files, abs, `plans/${rel}`, "core", "user", ctx);
|
|
364
|
-
}
|
|
365
|
-
const skillsDir = import_node_path3.default.join(userClaude, "skills");
|
|
366
|
-
for (const abs of walk(skillsDir, { exclude })) {
|
|
367
|
-
const rel = toPosix(import_node_path3.default.relative(skillsDir, abs));
|
|
368
|
-
push(files, abs, `skills/${rel}`, "core", "user", ctx);
|
|
369
|
-
}
|
|
370
|
-
const agentsDir = import_node_path3.default.join(userClaude, "agents");
|
|
371
|
-
for (const abs of walk(agentsDir, { exclude })) {
|
|
372
|
-
const rel = toPosix(import_node_path3.default.relative(agentsDir, abs));
|
|
373
|
-
push(files, abs, `agents/${rel}`, "core", "user", ctx);
|
|
374
|
-
}
|
|
375
|
-
for (const name of ["CLAUDE.md", "settings.json"]) {
|
|
376
|
-
const abs = import_node_path3.default.join(userClaude, name);
|
|
377
|
-
if (import_node_fs3.default.existsSync(abs)) push(files, abs, `user/${name}`, "core", "user", ctx);
|
|
378
|
-
}
|
|
379
500
|
}
|
|
380
501
|
if (tiers.includes("artifacts")) {
|
|
381
502
|
for (const relDir of cfg.artifactPaths) {
|
|
382
|
-
const absDir =
|
|
503
|
+
const absDir = import_node_path4.default.join(projectRoot, relDir);
|
|
383
504
|
for (const abs of walk(absDir, { exclude })) {
|
|
384
|
-
const rel = toPosix(
|
|
505
|
+
const rel = toPosix(import_node_path4.default.relative(absDir, abs));
|
|
385
506
|
push(files, abs, `artifacts/${relDir}/${rel}`, "artifacts", "project", ctx);
|
|
386
507
|
}
|
|
387
508
|
}
|
|
388
509
|
}
|
|
389
510
|
if (tiers.includes("transcripts")) {
|
|
390
|
-
const projDir =
|
|
511
|
+
const projDir = import_node_path4.default.join(userClaudeDir(), "projects", ctx.cwdKey);
|
|
391
512
|
for (const abs of walk(projDir, { exclude })) {
|
|
392
|
-
const rel = toPosix(
|
|
513
|
+
const rel = toPosix(import_node_path4.default.relative(projDir, abs));
|
|
393
514
|
if (rel.startsWith("memory/")) continue;
|
|
394
|
-
push(files, abs, `transcripts/${rel}`, "transcripts", "user", ctx);
|
|
515
|
+
push(files, abs, `transcripts/claude/${rel}`, "transcripts", "user", ctx);
|
|
395
516
|
}
|
|
396
517
|
}
|
|
397
|
-
return { files, skippedTracked, ctx };
|
|
518
|
+
return { files, skippedTracked, assistants, ctx };
|
|
398
519
|
}
|
|
399
520
|
function describeExcluded(projectRoot, tiers) {
|
|
400
521
|
const userClaude = userClaudeDir();
|
|
@@ -407,15 +528,17 @@ function describeExcluded(projectRoot, tiers) {
|
|
|
407
528
|
if (filter && !filter(abs)) continue;
|
|
408
529
|
files++;
|
|
409
530
|
try {
|
|
410
|
-
bytes +=
|
|
531
|
+
bytes += import_node_fs4.default.statSync(abs).size;
|
|
411
532
|
} catch {
|
|
412
533
|
}
|
|
413
534
|
}
|
|
414
535
|
return { files, bytes };
|
|
415
536
|
};
|
|
416
537
|
if (!tiers.includes("transcripts")) {
|
|
417
|
-
const
|
|
418
|
-
|
|
538
|
+
const m = measure(
|
|
539
|
+
import_node_path4.default.join(userClaude, "projects", key),
|
|
540
|
+
(p) => !p.includes(`${import_node_path4.default.sep}memory${import_node_path4.default.sep}`)
|
|
541
|
+
);
|
|
419
542
|
if (m.files > 0) {
|
|
420
543
|
out2.push({
|
|
421
544
|
label: "session transcripts",
|
|
@@ -429,7 +552,7 @@ function describeExcluded(projectRoot, tiers) {
|
|
|
429
552
|
["file-history", "edit-undo snapshots", "transient local undo state, not portable context"],
|
|
430
553
|
["tasks", "task outputs", "session-scoped tool output"]
|
|
431
554
|
]) {
|
|
432
|
-
const m = measure(
|
|
555
|
+
const m = measure(import_node_path4.default.join(userClaude, dir));
|
|
433
556
|
if (m.files > 0) out2.push({ label, ...m, reason });
|
|
434
557
|
}
|
|
435
558
|
return out2.filter((g) => g.bytes > 0);
|
|
@@ -530,8 +653,8 @@ function isStale(h, newestContentMs) {
|
|
|
530
653
|
}
|
|
531
654
|
|
|
532
655
|
// src/scaffold.ts
|
|
533
|
-
var
|
|
534
|
-
var
|
|
656
|
+
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
657
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
535
658
|
var SLASH_COMMAND_PATH = ".claude/commands/context.md";
|
|
536
659
|
var SLASH_COMMAND_BODY = `---
|
|
537
660
|
description: Sync this project's LLM context (memory, skills, instructions, handoff)
|
|
@@ -581,26 +704,26 @@ Run the context-sync action requested in: $ARGUMENTS
|
|
|
581
704
|
Run: \`npx @tricknowtech/context status\` and summarize the result.
|
|
582
705
|
`;
|
|
583
706
|
function installSlashCommand(projectRoot) {
|
|
584
|
-
const dest =
|
|
585
|
-
if (
|
|
586
|
-
ensureDir(
|
|
587
|
-
|
|
707
|
+
const dest = import_node_path5.default.join(projectRoot, SLASH_COMMAND_PATH);
|
|
708
|
+
if (import_node_fs5.default.existsSync(dest)) return { path: SLASH_COMMAND_PATH, written: false };
|
|
709
|
+
ensureDir(import_node_path5.default.dirname(dest));
|
|
710
|
+
import_node_fs5.default.writeFileSync(dest, SLASH_COMMAND_BODY, "utf8");
|
|
588
711
|
return { path: SLASH_COMMAND_PATH, written: true };
|
|
589
712
|
}
|
|
590
713
|
function ensureGitignoreEntries(projectRoot, artifactsEnabled) {
|
|
591
|
-
const gitignore =
|
|
714
|
+
const gitignore = import_node_path5.default.join(projectRoot, ".gitignore");
|
|
592
715
|
const wanted = [".contextsync/transcripts/"];
|
|
593
716
|
if (!artifactsEnabled) wanted.push(".contextsync/artifacts/");
|
|
594
717
|
let existing = "";
|
|
595
718
|
try {
|
|
596
|
-
existing =
|
|
719
|
+
existing = import_node_fs5.default.readFileSync(gitignore, "utf8");
|
|
597
720
|
} catch {
|
|
598
721
|
}
|
|
599
722
|
const lines = new Set(existing.split("\n").map((l) => l.trim()));
|
|
600
723
|
const missing = wanted.filter((w) => !lines.has(w));
|
|
601
724
|
if (missing.length === 0) return [];
|
|
602
725
|
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
603
|
-
|
|
726
|
+
import_node_fs5.default.appendFileSync(
|
|
604
727
|
gitignore,
|
|
605
728
|
`${prefix}
|
|
606
729
|
# tricknowtech context-sync \u2014 never commit these tiers
|
|
@@ -612,7 +735,7 @@ ${missing.join("\n")}
|
|
|
612
735
|
}
|
|
613
736
|
|
|
614
737
|
// src/secrets.ts
|
|
615
|
-
var
|
|
738
|
+
var import_node_fs6 = __toESM(require("fs"), 1);
|
|
616
739
|
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;
|
|
617
740
|
function looksLikeCredential(value) {
|
|
618
741
|
if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value)) return false;
|
|
@@ -666,7 +789,7 @@ function scanFiles(files) {
|
|
|
666
789
|
for (const file of files) {
|
|
667
790
|
let buf;
|
|
668
791
|
try {
|
|
669
|
-
buf =
|
|
792
|
+
buf = import_node_fs6.default.readFileSync(file.sourcePath);
|
|
670
793
|
} catch {
|
|
671
794
|
continue;
|
|
672
795
|
}
|
|
@@ -704,8 +827,8 @@ function formatHits(hits) {
|
|
|
704
827
|
}
|
|
705
828
|
|
|
706
829
|
// src/store.ts
|
|
707
|
-
var
|
|
708
|
-
var
|
|
830
|
+
var import_node_fs7 = __toESM(require("fs"), 1);
|
|
831
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
709
832
|
var MANIFEST_FILE = "manifest.json";
|
|
710
833
|
var LocalStore = class {
|
|
711
834
|
constructor(projectRoot) {
|
|
@@ -716,13 +839,13 @@ var LocalStore = class {
|
|
|
716
839
|
return storeDir(this.projectRoot);
|
|
717
840
|
}
|
|
718
841
|
write(files, projectRoot) {
|
|
719
|
-
for (const name of
|
|
842
|
+
for (const name of import_node_fs7.default.existsSync(this.dir) ? import_node_fs7.default.readdirSync(this.dir) : []) {
|
|
720
843
|
if (name === "config.json" || name === HANDOFF_FILE) continue;
|
|
721
|
-
|
|
844
|
+
import_node_fs7.default.rmSync(import_node_path6.default.join(this.dir, name), { recursive: true, force: true });
|
|
722
845
|
}
|
|
723
846
|
const entries = [];
|
|
724
847
|
for (const file of files) {
|
|
725
|
-
const dest =
|
|
848
|
+
const dest = import_node_path6.default.join(this.dir, file.storePath);
|
|
726
849
|
try {
|
|
727
850
|
copyFile(file.sourcePath, dest);
|
|
728
851
|
} catch {
|
|
@@ -743,11 +866,11 @@ var LocalStore = class {
|
|
|
743
866
|
writtenFrom: projectRoot,
|
|
744
867
|
entries
|
|
745
868
|
};
|
|
746
|
-
writeJson(
|
|
869
|
+
writeJson(import_node_path6.default.join(this.dir, MANIFEST_FILE), manifest);
|
|
747
870
|
return manifest;
|
|
748
871
|
}
|
|
749
872
|
readManifest() {
|
|
750
|
-
return readJson(
|
|
873
|
+
return readJson(import_node_path6.default.join(this.dir, MANIFEST_FILE));
|
|
751
874
|
}
|
|
752
875
|
restore(ctx, opts = {}) {
|
|
753
876
|
const manifest = this.readManifest();
|
|
@@ -755,12 +878,12 @@ var LocalStore = class {
|
|
|
755
878
|
const skipped = [];
|
|
756
879
|
if (!manifest) return { restored, skipped };
|
|
757
880
|
for (const entry of manifest.entries) {
|
|
758
|
-
const src =
|
|
759
|
-
if (!
|
|
881
|
+
const src = import_node_path6.default.join(this.dir, entry.storePath);
|
|
882
|
+
if (!import_node_fs7.default.existsSync(src)) continue;
|
|
760
883
|
const dest = resolveTemplate(entry.restoreTemplate, ctx);
|
|
761
|
-
if (!opts.force &&
|
|
884
|
+
if (!opts.force && import_node_fs7.default.existsSync(dest)) {
|
|
762
885
|
try {
|
|
763
|
-
if (!
|
|
886
|
+
if (!import_node_fs7.default.readFileSync(dest).equals(import_node_fs7.default.readFileSync(src))) {
|
|
764
887
|
skipped.push(entry.storePath);
|
|
765
888
|
continue;
|
|
766
889
|
}
|
|
@@ -770,8 +893,8 @@ var LocalStore = class {
|
|
|
770
893
|
}
|
|
771
894
|
}
|
|
772
895
|
try {
|
|
773
|
-
ensureDir(
|
|
774
|
-
|
|
896
|
+
ensureDir(import_node_path6.default.dirname(dest));
|
|
897
|
+
import_node_fs7.default.copyFileSync(src, dest);
|
|
775
898
|
restored.push(entry.storePath);
|
|
776
899
|
} catch {
|
|
777
900
|
skipped.push(entry.storePath);
|
|
@@ -780,10 +903,10 @@ var LocalStore = class {
|
|
|
780
903
|
return { restored, skipped };
|
|
781
904
|
}
|
|
782
905
|
readHandoff() {
|
|
783
|
-
return readJson(
|
|
906
|
+
return readJson(import_node_path6.default.join(this.dir, HANDOFF_FILE));
|
|
784
907
|
}
|
|
785
908
|
writeHandoff(handoff) {
|
|
786
|
-
writeJson(
|
|
909
|
+
writeJson(import_node_path6.default.join(this.dir, HANDOFF_FILE), handoff);
|
|
787
910
|
}
|
|
788
911
|
};
|
|
789
912
|
|
|
@@ -808,12 +931,12 @@ function requireProject() {
|
|
|
808
931
|
}
|
|
809
932
|
const cfg = loadConfig(root);
|
|
810
933
|
if (!cfg) {
|
|
811
|
-
return fail([`No context store found at ${
|
|
934
|
+
return fail([`No context store found at ${import_node_path7.default.join(root, ".contextsync")}.`, "Run `ctx init` first."]);
|
|
812
935
|
}
|
|
813
936
|
return { root, cfg };
|
|
814
937
|
}
|
|
815
938
|
function templateContext(root) {
|
|
816
|
-
return
|
|
939
|
+
return templateContextFor(root);
|
|
817
940
|
}
|
|
818
941
|
function effectiveTiers(cfg) {
|
|
819
942
|
const tiers = cfg.tiers.filter((t) => LOCAL_TIERS.includes(t));
|
|
@@ -831,7 +954,7 @@ function cmdInit(opts = {}) {
|
|
|
831
954
|
saveConfig(root, cfg);
|
|
832
955
|
const lines = [
|
|
833
956
|
`Initialised context store for "${cfg.name}"`,
|
|
834
|
-
` store ${
|
|
957
|
+
` store ${import_node_path7.default.relative(root, storeDir(root))}/`,
|
|
835
958
|
` tiers ${cfg.tiers.join(", ")}`
|
|
836
959
|
];
|
|
837
960
|
const slash = installSlashCommand(root);
|
|
@@ -849,7 +972,7 @@ function cmdPush(opts = {}) {
|
|
|
849
972
|
if ("code" in found) return found;
|
|
850
973
|
const { root, cfg } = found;
|
|
851
974
|
const { tiers, refused } = effectiveTiers(cfg);
|
|
852
|
-
const { files, skippedTracked } = collect(root, cfg, tiers);
|
|
975
|
+
const { files, skippedTracked, assistants } = collect(root, cfg, tiers);
|
|
853
976
|
const lines = [];
|
|
854
977
|
if (refused.length > 0) {
|
|
855
978
|
lines.push(
|
|
@@ -888,12 +1011,15 @@ function cmdPush(opts = {}) {
|
|
|
888
1011
|
} else {
|
|
889
1012
|
const store = new LocalStore(root);
|
|
890
1013
|
store.write(files, root);
|
|
891
|
-
lines.push(`Synced ${files.length} files (${formatBytes(totalBytes)}) to ${
|
|
1014
|
+
lines.push(`Synced ${files.length} files (${formatBytes(totalBytes)}) to ${import_node_path7.default.relative(root, storeDir(root))}/`);
|
|
892
1015
|
}
|
|
893
1016
|
for (const tier of ALL_TIERS) {
|
|
894
1017
|
const t = totals[tier];
|
|
895
1018
|
if (t.count > 0) lines.push(` ${tier.padEnd(11)} ${String(t.count).padStart(4)} files ${formatBytes(t.bytes)}`);
|
|
896
1019
|
}
|
|
1020
|
+
if (assistants.length > 0) {
|
|
1021
|
+
lines.push("", `Assistants: ${assistants.map((a) => a.name).join(", ")}`);
|
|
1022
|
+
}
|
|
897
1023
|
if (skippedTracked.length > 0) {
|
|
898
1024
|
lines.push("", `${skippedTracked.length} project files skipped \u2014 git already tracks them.`);
|
|
899
1025
|
}
|
|
@@ -930,7 +1056,7 @@ function cmdPull(opts = {}) {
|
|
|
930
1056
|
const store = new LocalStore(root);
|
|
931
1057
|
const manifest = store.readManifest();
|
|
932
1058
|
if (!manifest) {
|
|
933
|
-
return fail([`No manifest in ${
|
|
1059
|
+
return fail([`No manifest in ${import_node_path7.default.relative(root, storeDir(root))}/.`, "Run `ctx push` on the source machine first."]);
|
|
934
1060
|
}
|
|
935
1061
|
const ctx = templateContext(root);
|
|
936
1062
|
const { restored, skipped } = store.restore(ctx, { force: opts.force });
|
|
@@ -981,7 +1107,7 @@ function cmdHandoff(opts = {}) {
|
|
|
981
1107
|
const check2 = validateHandoff(parsed);
|
|
982
1108
|
if (!check2.ok) return fail(["Handoff is not valid:", ...check2.errors.map((e) => ` \xB7 ${e}`)]);
|
|
983
1109
|
store.writeHandoff(check2.handoff);
|
|
984
|
-
return ok([`Handoff saved (${
|
|
1110
|
+
return ok([`Handoff saved (${import_node_path7.default.relative(root, storeDir(root))}/handoff.json).`, "", ...formatHandoff(check2.handoff)]);
|
|
985
1111
|
}
|
|
986
1112
|
const raw = store.readHandoff();
|
|
987
1113
|
if (!raw) {
|
|
@@ -1006,15 +1132,21 @@ function cmdDoctor() {
|
|
|
1006
1132
|
lines.push(` ${okFlag ? "\u2713" : "\u2717"} ${label.padEnd(28)} ${detail}`);
|
|
1007
1133
|
};
|
|
1008
1134
|
const cfg = loadConfig(root);
|
|
1009
|
-
check(Boolean(cfg), "store initialised", cfg ? `${
|
|
1135
|
+
check(Boolean(cfg), "store initialised", cfg ? `${import_node_path7.default.relative(root, storeDir(root))}/` : "missing \u2014 run `ctx init`");
|
|
1010
1136
|
if (!cfg) return { code: 1, lines: ["Setup check", "", ...lines] };
|
|
1011
1137
|
const store = new LocalStore(root);
|
|
1012
1138
|
const manifest = store.readManifest();
|
|
1013
1139
|
check(Boolean(manifest), "pushed at least once", manifest ? `${manifest.entries.length} files` : "never \u2014 run `ctx push`");
|
|
1140
|
+
const detected = collect(root, cfg, ["core"]).assistants;
|
|
1141
|
+
check(
|
|
1142
|
+
detected.length > 0,
|
|
1143
|
+
"assistants detected",
|
|
1144
|
+
detected.length > 0 ? detected.map((a) => a.name).join(", ") : "none \u2014 no known assistant config found"
|
|
1145
|
+
);
|
|
1014
1146
|
check(
|
|
1015
|
-
|
|
1147
|
+
import_node_fs8.default.existsSync(import_node_path7.default.join(root, SLASH_COMMAND_PATH)),
|
|
1016
1148
|
"/context slash command",
|
|
1017
|
-
|
|
1149
|
+
import_node_fs8.default.existsSync(import_node_path7.default.join(root, SLASH_COMMAND_PATH)) ? SLASH_COMMAND_PATH : `missing \u2014 run \`ctx init --force\``
|
|
1018
1150
|
);
|
|
1019
1151
|
const inGit = isGitRepo(root);
|
|
1020
1152
|
check(inGit, "git repository", inGit ? "yes \u2014 store travels with the repo" : "no \u2014 the store will not sync anywhere");
|
|
@@ -1032,7 +1164,7 @@ function cmdDoctor() {
|
|
|
1032
1164
|
const memEntry = manifest?.entries.find((e) => e.storePath.startsWith("memory/"));
|
|
1033
1165
|
if (memEntry) {
|
|
1034
1166
|
const dest = resolveTemplate(memEntry.restoreTemplate, ctx);
|
|
1035
|
-
const expectedDir =
|
|
1167
|
+
const expectedDir = import_node_path7.default.join(userClaudeDir(), "projects", cwdKey(root), "memory");
|
|
1036
1168
|
check(dest.startsWith(expectedDir), "memory restore path", dest.startsWith(expectedDir) ? expectedDir : `WRONG \u2192 ${dest}`);
|
|
1037
1169
|
} else {
|
|
1038
1170
|
check(false, "memory captured", "none found \u2014 is ~/.claude/projects/<key>/memory populated?");
|
|
@@ -1057,15 +1189,16 @@ function cmdStatus() {
|
|
|
1057
1189
|
const store = new LocalStore(root);
|
|
1058
1190
|
const manifest = store.readManifest();
|
|
1059
1191
|
const { tiers } = effectiveTiers(cfg);
|
|
1060
|
-
const { files, skippedTracked } = collect(root, cfg, tiers);
|
|
1192
|
+
const { files, skippedTracked, assistants } = collect(root, cfg, tiers);
|
|
1061
1193
|
const rawHandoff = store.readHandoff();
|
|
1062
1194
|
const hv = rawHandoff ? validateHandoff(rawHandoff) : null;
|
|
1063
1195
|
const handoffLabel = !rawHandoff ? "none \u2014 run `/context push` to record where you left off" : hv?.ok ? `${handoffAge(hv.handoff)}` : "malformed (will be ignored)";
|
|
1064
1196
|
const lines = [
|
|
1065
1197
|
`Project ${cfg.name}`,
|
|
1066
|
-
`Store ${
|
|
1198
|
+
`Store ${import_node_path7.default.relative(root, storeDir(root))}/`,
|
|
1067
1199
|
`Tiers ${cfg.tiers.join(", ")}`,
|
|
1068
1200
|
`Handoff ${handoffLabel}`,
|
|
1201
|
+
`Tools ${assistants.length > 0 ? assistants.map((a) => a.name).join(", ") : "none detected"}`,
|
|
1069
1202
|
`Remotes ${Object.keys(cfg.remotes).length > 0 ? Object.keys(cfg.remotes).join(", ") : "none (local only)"}`,
|
|
1070
1203
|
""
|
|
1071
1204
|
];
|
|
@@ -1082,7 +1215,7 @@ function cmdStatus() {
|
|
|
1082
1215
|
if (!e) return false;
|
|
1083
1216
|
if (e.size !== f.size) return true;
|
|
1084
1217
|
try {
|
|
1085
|
-
return !
|
|
1218
|
+
return !import_node_fs8.default.readFileSync(import_node_path7.default.join(storeDir(root), k)).equals(import_node_fs8.default.readFileSync(f.sourcePath));
|
|
1086
1219
|
} catch {
|
|
1087
1220
|
return true;
|
|
1088
1221
|
}
|
|
@@ -1107,7 +1240,7 @@ function cmdStatus() {
|
|
|
1107
1240
|
}
|
|
1108
1241
|
|
|
1109
1242
|
// src/cli.ts
|
|
1110
|
-
var VERSION = "0.
|
|
1243
|
+
var VERSION = "0.3.0";
|
|
1111
1244
|
var USAGE = `tricknowtech context-sync \u2014 carry a project's LLM context between machines
|
|
1112
1245
|
|
|
1113
1246
|
Usage
|