@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/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 import_node_fs7 = __toESM(require("fs"), 1);
41
- var import_node_path6 = __toESM(require("path"), 1);
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 import_node_fs3 = __toESM(require("fs"), 1);
45
- var import_node_path3 = __toESM(require("path"), 1);
44
+ var import_node_fs4 = __toESM(require("fs"), 1);
45
+ var import_node_path4 = __toESM(require("path"), 1);
46
46
 
47
- // src/config.ts
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
- ].sort((a, b) => b[0].length - a[0].length);
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: import_node_path2.default.basename(projectRoot),
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 = import_node_path2.default.resolve(start);
409
+ let dir = import_node_path3.default.resolve(start);
239
410
  for (; ; ) {
240
- if (import_node_fs2.default.existsSync(import_node_path2.default.join(dir, STORE_DIR, CONFIG_FILE))) return dir;
241
- if (import_node_fs2.default.existsSync(import_node_path2.default.join(dir, ".git"))) return dir;
242
- const parent = import_node_path2.default.dirname(dir);
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 import_node_path2.default.join(projectRoot, STORE_DIR);
419
+ return import_node_path3.default.join(projectRoot, STORE_DIR);
249
420
  }
250
421
  function configPath(projectRoot) {
251
- return import_node_path2.default.join(storeDir(projectRoot), CONFIG_FILE);
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
- size = import_node_fs3.default.statSync(sourcePath).size;
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 userClaude = userClaudeDir();
330
- const key = cwdKey(projectRoot);
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
- for (const abs of walk(projectRoot, { exclude })) {
338
- const rel = toPosix(import_node_path3.default.relative(projectRoot, abs));
339
- if (!matchesAny(rel, PROJECT_CONTEXT_GLOBS)) continue;
340
- if (tracked.has(abs)) {
341
- skippedTracked.push(rel);
342
- continue;
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 pk of projectKeys) {
354
- const memoryDir = import_node_path3.default.join(projectsDir, pk, "memory");
355
- for (const abs of walk(memoryDir, { exclude })) {
356
- const rel = toPosix(import_node_path3.default.relative(memoryDir, abs));
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 = import_node_path3.default.join(projectRoot, relDir);
503
+ const absDir = import_node_path4.default.join(projectRoot, relDir);
383
504
  for (const abs of walk(absDir, { exclude })) {
384
- const rel = toPosix(import_node_path3.default.relative(absDir, abs));
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 = import_node_path3.default.join(userClaude, "projects", key);
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(import_node_path3.default.relative(projDir, abs));
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 += import_node_fs3.default.statSync(abs).size;
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 projDir = import_node_path3.default.join(userClaude, "projects", key);
418
- const m = measure(projDir, (p) => !p.includes(`${import_node_path3.default.sep}memory${import_node_path3.default.sep}`));
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(import_node_path3.default.join(userClaude, dir));
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 import_node_fs4 = __toESM(require("fs"), 1);
534
- var import_node_path4 = __toESM(require("path"), 1);
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 = import_node_path4.default.join(projectRoot, SLASH_COMMAND_PATH);
585
- if (import_node_fs4.default.existsSync(dest)) return { path: SLASH_COMMAND_PATH, written: false };
586
- ensureDir(import_node_path4.default.dirname(dest));
587
- import_node_fs4.default.writeFileSync(dest, SLASH_COMMAND_BODY, "utf8");
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 = import_node_path4.default.join(projectRoot, ".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 = import_node_fs4.default.readFileSync(gitignore, "utf8");
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
- import_node_fs4.default.appendFileSync(
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 import_node_fs5 = __toESM(require("fs"), 1);
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 = import_node_fs5.default.readFileSync(file.sourcePath);
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 import_node_fs6 = __toESM(require("fs"), 1);
708
- var import_node_path5 = __toESM(require("path"), 1);
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 import_node_fs6.default.existsSync(this.dir) ? import_node_fs6.default.readdirSync(this.dir) : []) {
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
- import_node_fs6.default.rmSync(import_node_path5.default.join(this.dir, name), { recursive: true, force: true });
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 = import_node_path5.default.join(this.dir, file.storePath);
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(import_node_path5.default.join(this.dir, MANIFEST_FILE), manifest);
869
+ writeJson(import_node_path6.default.join(this.dir, MANIFEST_FILE), manifest);
747
870
  return manifest;
748
871
  }
749
872
  readManifest() {
750
- return readJson(import_node_path5.default.join(this.dir, MANIFEST_FILE));
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 = import_node_path5.default.join(this.dir, entry.storePath);
759
- if (!import_node_fs6.default.existsSync(src)) continue;
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 && import_node_fs6.default.existsSync(dest)) {
884
+ if (!opts.force && import_node_fs7.default.existsSync(dest)) {
762
885
  try {
763
- if (!import_node_fs6.default.readFileSync(dest).equals(import_node_fs6.default.readFileSync(src))) {
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(import_node_path5.default.dirname(dest));
774
- import_node_fs6.default.copyFileSync(src, dest);
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(import_node_path5.default.join(this.dir, HANDOFF_FILE));
906
+ return readJson(import_node_path6.default.join(this.dir, HANDOFF_FILE));
784
907
  }
785
908
  writeHandoff(handoff) {
786
- writeJson(import_node_path5.default.join(this.dir, HANDOFF_FILE), handoff);
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 ${import_node_path6.default.join(root, ".contextsync")}.`, "Run `ctx init` first."]);
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 { userClaude: userClaudeDir(), project: root, cwdKey: cwdKey(root) };
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 ${import_node_path6.default.relative(root, storeDir(root))}/`,
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 ${import_node_path6.default.relative(root, storeDir(root))}/`);
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 ${import_node_path6.default.relative(root, storeDir(root))}/.`, "Run `ctx push` on the source machine first."]);
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 (${import_node_path6.default.relative(root, storeDir(root))}/handoff.json).`, "", ...formatHandoff(check2.handoff)]);
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 ? `${import_node_path6.default.relative(root, storeDir(root))}/` : "missing \u2014 run `ctx init`");
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
- import_node_fs7.default.existsSync(import_node_path6.default.join(root, SLASH_COMMAND_PATH)),
1147
+ import_node_fs8.default.existsSync(import_node_path7.default.join(root, SLASH_COMMAND_PATH)),
1016
1148
  "/context slash command",
1017
- import_node_fs7.default.existsSync(import_node_path6.default.join(root, SLASH_COMMAND_PATH)) ? SLASH_COMMAND_PATH : `missing \u2014 run \`ctx init --force\``
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 = import_node_path6.default.join(userClaudeDir(), "projects", cwdKey(root), "memory");
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 ${import_node_path6.default.relative(root, storeDir(root))}/`,
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 !import_node_fs7.default.readFileSync(import_node_path6.default.join(storeDir(root), k)).equals(import_node_fs7.default.readFileSync(f.sourcePath));
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.2.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