@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/index.cjs CHANGED
@@ -71,12 +71,12 @@ __export(index_exports, {
71
71
  module.exports = __toCommonJS(index_exports);
72
72
 
73
73
  // src/collector.ts
74
- var import_node_fs3 = __toESM(require("fs"), 1);
75
- var import_node_path3 = __toESM(require("path"), 1);
74
+ var import_node_fs4 = __toESM(require("fs"), 1);
75
+ var import_node_path4 = __toESM(require("path"), 1);
76
76
 
77
- // src/config.ts
78
- var import_node_crypto = __toESM(require("crypto"), 1);
77
+ // src/assistants.ts
79
78
  var import_node_fs2 = __toESM(require("fs"), 1);
79
+ var import_node_os2 = __toESM(require("os"), 1);
80
80
  var import_node_path2 = __toESM(require("path"), 1);
81
81
 
82
82
  // src/fsutil.ts
@@ -93,8 +93,9 @@ function cwdKey(absPath) {
93
93
  function makeTemplate(absPath, ctx) {
94
94
  const candidates = [
95
95
  [ctx.userClaude, "{userClaude}"],
96
- [ctx.project, "{project}"]
97
- ].sort((a, b) => b[0].length - a[0].length);
96
+ [ctx.project, "{project}"],
97
+ [ctx.home, "{home}"]
98
+ ].filter(([root]) => Boolean(root)).sort((a, b) => b[0].length - a[0].length);
98
99
  let out = absPath;
99
100
  for (const [root, token] of candidates) {
100
101
  if (absPath === root || absPath.startsWith(root + import_node_path.default.sep)) {
@@ -105,7 +106,7 @@ function makeTemplate(absPath, ctx) {
105
106
  return ctx.cwdKey ? out.split(ctx.cwdKey).join("{cwdKey}") : out;
106
107
  }
107
108
  function resolveTemplate(template, ctx) {
108
- const expanded = template.split("{userClaude}").join(ctx.userClaude).split("{project}").join(ctx.project).split("{cwdKey}").join(ctx.cwdKey);
109
+ const expanded = template.split("{userClaude}").join(ctx.userClaude).split("{project}").join(ctx.project).split("{home}").join(ctx.home).split("{cwdKey}").join(ctx.cwdKey);
109
110
  return import_node_path.default.normalize(expanded);
110
111
  }
111
112
  function toPosix(p) {
@@ -227,7 +228,176 @@ function formatBytes(n) {
227
228
  return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
228
229
  }
229
230
 
231
+ // src/assistants.ts
232
+ function filesIn(dir, exclude, prefix) {
233
+ return walk(dir, { exclude }).map((abs) => ({
234
+ abs,
235
+ rel: `${prefix}/${toPosix(import_node_path2.default.relative(dir, abs))}`
236
+ }));
237
+ }
238
+ function fileIfExists(abs, rel) {
239
+ return import_node_fs2.default.existsSync(abs) ? [{ abs, rel }] : [];
240
+ }
241
+ function planStem(fileName) {
242
+ return fileName.replace(/\.md$/, "").replace(/-agent-[0-9a-f]+$/i, "");
243
+ }
244
+ function relatedByProject(dir, projectRoot, exclude) {
245
+ const all = walk(dir, { exclude });
246
+ if (all.length === 0) return [];
247
+ const projectName = import_node_path2.default.basename(projectRoot);
248
+ const related = /* @__PURE__ */ new Set();
249
+ const stems = /* @__PURE__ */ new Set();
250
+ for (const abs of all) {
251
+ let text = "";
252
+ try {
253
+ text = import_node_fs2.default.readFileSync(abs, "utf8");
254
+ } catch {
255
+ continue;
256
+ }
257
+ if (text.includes(projectRoot) || text.includes(projectName)) {
258
+ related.add(abs);
259
+ stems.add(planStem(import_node_path2.default.basename(abs)));
260
+ }
261
+ }
262
+ for (const abs of all) {
263
+ if (stems.has(planStem(import_node_path2.default.basename(abs)))) related.add(abs);
264
+ }
265
+ return [...related];
266
+ }
267
+ var ADAPTERS = [
268
+ {
269
+ id: "claude",
270
+ name: "Claude Code",
271
+ projectGlobs: [
272
+ "CLAUDE.md",
273
+ "CLAUDE.local.md",
274
+ "**/CLAUDE.md",
275
+ ".claude/settings.json",
276
+ ".claude/settings.local.json",
277
+ ".claude/memory/",
278
+ ".claude/plans/",
279
+ ".claude/commands/",
280
+ ".claude/agents/",
281
+ ".claude/skills/"
282
+ ],
283
+ userDir: () => userClaudeDir(),
284
+ collectUser: (projectRoot, _home, exclude) => {
285
+ const root = userClaudeDir();
286
+ const out = [];
287
+ const key = cwdKey(projectRoot);
288
+ const projectsDir = import_node_path2.default.join(root, "projects");
289
+ let keys = [];
290
+ try {
291
+ keys = import_node_fs2.default.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && (d.name === key || d.name.startsWith(key + "-"))).map((d) => d.name);
292
+ } catch {
293
+ keys = [];
294
+ }
295
+ for (const k of keys) {
296
+ const memDir = import_node_path2.default.join(projectsDir, k, "memory");
297
+ const prefix = k === key ? "memory" : `memory-sub/${k.slice(key.length + 1)}`;
298
+ out.push(...filesIn(memDir, exclude, prefix));
299
+ }
300
+ out.push(...filesIn(import_node_path2.default.join(root, "skills"), exclude, "skills"));
301
+ out.push(...filesIn(import_node_path2.default.join(root, "agents"), exclude, "agents"));
302
+ for (const abs of relatedByProject(import_node_path2.default.join(root, "plans"), projectRoot, exclude)) {
303
+ out.push({ abs, rel: `plans/${import_node_path2.default.basename(abs)}` });
304
+ }
305
+ out.push(...fileIfExists(import_node_path2.default.join(root, "CLAUDE.md"), "user/CLAUDE.md"));
306
+ out.push(...fileIfExists(import_node_path2.default.join(root, "settings.json"), "user/settings.json"));
307
+ return out;
308
+ }
309
+ },
310
+ {
311
+ id: "codex",
312
+ name: "OpenAI Codex",
313
+ projectGlobs: ["AGENTS.md", "**/AGENTS.md", ".codex/"],
314
+ userDir: (home) => import_node_path2.default.join(home, ".codex"),
315
+ collectUser: (projectRoot, home, exclude) => {
316
+ const root = import_node_path2.default.join(home, ".codex");
317
+ const out = [];
318
+ out.push(...fileIfExists(import_node_path2.default.join(root, "AGENTS.md"), "user/AGENTS.md"));
319
+ out.push(...fileIfExists(import_node_path2.default.join(root, "config.toml"), "user/config.toml"));
320
+ out.push(...filesIn(import_node_path2.default.join(root, "prompts"), exclude, "prompts"));
321
+ return out;
322
+ }
323
+ },
324
+ {
325
+ id: "cursor",
326
+ name: "Cursor",
327
+ // `.cursorrules` is the legacy single-file form; `.cursor/rules/*.mdc` is current.
328
+ projectGlobs: [".cursorrules", ".cursor/rules/", ".cursor/"],
329
+ userDir: (home) => import_node_path2.default.join(home, ".cursor"),
330
+ collectUser: (_projectRoot, home, exclude) => filesIn(import_node_path2.default.join(home, ".cursor", "rules"), exclude, "rules")
331
+ },
332
+ {
333
+ id: "copilot",
334
+ name: "GitHub Copilot",
335
+ projectGlobs: [
336
+ ".github/copilot-instructions.md",
337
+ ".github/instructions/",
338
+ ".github/prompts/"
339
+ ]
340
+ },
341
+ {
342
+ id: "windsurf",
343
+ name: "Windsurf",
344
+ projectGlobs: [".windsurfrules", ".windsurf/rules/", ".windsurf/"],
345
+ userDir: (home) => import_node_path2.default.join(home, ".windsurf")
346
+ },
347
+ {
348
+ id: "gemini",
349
+ name: "Gemini CLI",
350
+ projectGlobs: ["GEMINI.md", "**/GEMINI.md", ".gemini/"],
351
+ userDir: (home) => import_node_path2.default.join(home, ".gemini"),
352
+ collectUser: (_projectRoot, home, exclude) => {
353
+ const root = import_node_path2.default.join(home, ".gemini");
354
+ const out = [];
355
+ out.push(...fileIfExists(import_node_path2.default.join(root, "GEMINI.md"), "user/GEMINI.md"));
356
+ out.push(...fileIfExists(import_node_path2.default.join(root, "settings.json"), "user/settings.json"));
357
+ out.push(...filesIn(import_node_path2.default.join(root, "commands"), exclude, "commands"));
358
+ return out;
359
+ }
360
+ },
361
+ {
362
+ id: "cline",
363
+ name: "Cline",
364
+ projectGlobs: [".clinerules", ".clinerules/"]
365
+ },
366
+ {
367
+ id: "aider",
368
+ name: "Aider",
369
+ projectGlobs: ["CONVENTIONS.md", ".aider.conf.yml", ".aider.conf.yaml"]
370
+ },
371
+ {
372
+ id: "continue",
373
+ name: "Continue",
374
+ projectGlobs: [".continue/", ".continuerules"],
375
+ userDir: (home) => import_node_path2.default.join(home, ".continue")
376
+ }
377
+ ];
378
+ function adapterById(id) {
379
+ return ADAPTERS.find((a) => a.id === id);
380
+ }
381
+ function homeDir() {
382
+ return import_node_os2.default.homedir();
383
+ }
384
+ function detectAssistants(projectRoot, projectFiles) {
385
+ const home = homeDir();
386
+ return ADAPTERS.filter((a) => {
387
+ if (a.userDir) {
388
+ try {
389
+ if (import_node_fs2.default.existsSync(a.userDir(home))) return true;
390
+ } catch {
391
+ }
392
+ }
393
+ return projectFiles.some((rel) => matchesAny(rel, a.projectGlobs));
394
+ });
395
+ }
396
+
230
397
  // src/config.ts
398
+ var import_node_crypto = __toESM(require("crypto"), 1);
399
+ var import_node_fs3 = __toESM(require("fs"), 1);
400
+ var import_node_path3 = __toESM(require("path"), 1);
231
401
  var STORE_DIR = ".contextsync";
232
402
  var CONFIG_FILE = "config.json";
233
403
  var HANDOFF_FILE = "handoff.json";
@@ -256,29 +426,30 @@ var DEFAULT_EXCLUDE = [
256
426
  function defaultConfig(projectRoot) {
257
427
  return {
258
428
  projectId: import_node_crypto.default.randomUUID(),
259
- name: import_node_path2.default.basename(projectRoot),
429
+ name: import_node_path3.default.basename(projectRoot),
260
430
  rootHint: projectRoot,
261
431
  tiers: ["core", "handoff"],
432
+ assistants: ["auto"],
262
433
  artifactPaths: ["graphify-out"],
263
434
  exclude: [...DEFAULT_EXCLUDE],
264
435
  remotes: {}
265
436
  };
266
437
  }
267
438
  function findProjectRoot(start = process.cwd()) {
268
- let dir = import_node_path2.default.resolve(start);
439
+ let dir = import_node_path3.default.resolve(start);
269
440
  for (; ; ) {
270
- if (import_node_fs2.default.existsSync(import_node_path2.default.join(dir, STORE_DIR, CONFIG_FILE))) return dir;
271
- if (import_node_fs2.default.existsSync(import_node_path2.default.join(dir, ".git"))) return dir;
272
- const parent = import_node_path2.default.dirname(dir);
441
+ if (import_node_fs3.default.existsSync(import_node_path3.default.join(dir, STORE_DIR, CONFIG_FILE))) return dir;
442
+ if (import_node_fs3.default.existsSync(import_node_path3.default.join(dir, ".git"))) return dir;
443
+ const parent = import_node_path3.default.dirname(dir);
273
444
  if (parent === dir) return null;
274
445
  dir = parent;
275
446
  }
276
447
  }
277
448
  function storeDir(projectRoot) {
278
- return import_node_path2.default.join(projectRoot, STORE_DIR);
449
+ return import_node_path3.default.join(projectRoot, STORE_DIR);
279
450
  }
280
451
  function configPath(projectRoot) {
281
- return import_node_path2.default.join(storeDir(projectRoot), CONFIG_FILE);
452
+ return import_node_path3.default.join(storeDir(projectRoot), CONFIG_FILE);
282
453
  }
283
454
  function loadConfig(projectRoot) {
284
455
  const cfg = readJson(configPath(projectRoot));
@@ -287,7 +458,9 @@ function loadConfig(projectRoot) {
287
458
  ...defaultConfig(projectRoot),
288
459
  ...cfg,
289
460
  remotes: cfg.remotes ?? {},
290
- tiers: cfg.tiers ?? ["core", "handoff"]
461
+ tiers: cfg.tiers ?? ["core", "handoff"],
462
+ // Stores written before multi-assistant support have no `assistants` key.
463
+ assistants: cfg.assistants ?? ["auto"]
291
464
  };
292
465
  }
293
466
  function saveConfig(projectRoot, cfg) {
@@ -295,54 +468,12 @@ function saveConfig(projectRoot, cfg) {
295
468
  }
296
469
 
297
470
  // src/collector.ts
298
- var PROJECT_CONTEXT_GLOBS = [
299
- "CLAUDE.md",
300
- "CLAUDE.local.md",
301
- "AGENTS.md",
302
- "**/CLAUDE.md",
303
- "**/AGENTS.md",
304
- ".cursorrules",
305
- ".github/copilot-instructions.md",
306
- ".claude/settings.json",
307
- // `.local.json` variants are gitignored by default, so nothing else carries
308
- // them — which makes them exactly the kind of file this tool exists for.
309
- ".claude/settings.local.json",
310
- ".claude/memory/",
311
- ".claude/plans/",
312
- ".claude/commands/",
313
- ".claude/agents/",
314
- ".claude/skills/"
315
- ];
316
- function planStem(fileName) {
317
- return fileName.replace(/\.md$/, "").replace(/-agent-[0-9a-f]+$/i, "");
318
- }
319
- function collectPlans(plansDir, projectRoot, exclude) {
320
- const all = walk(plansDir, { exclude });
321
- if (all.length === 0) return [];
322
- const projectName = import_node_path3.default.basename(projectRoot);
323
- const related = /* @__PURE__ */ new Set();
324
- const stems = /* @__PURE__ */ new Set();
325
- for (const abs of all) {
326
- let text = "";
327
- try {
328
- text = import_node_fs3.default.readFileSync(abs, "utf8");
329
- } catch {
330
- continue;
331
- }
332
- if (text.includes(projectRoot) || text.includes(projectName)) {
333
- related.add(abs);
334
- stems.add(planStem(import_node_path3.default.basename(abs)));
335
- }
336
- }
337
- for (const abs of all) {
338
- if (stems.has(planStem(import_node_path3.default.basename(abs)))) related.add(abs);
339
- }
340
- return [...related];
341
- }
342
471
  function push(out, sourcePath, storePath, tier, sourceRoot, ctx) {
343
472
  let size = 0;
344
473
  try {
345
- size = import_node_fs3.default.statSync(sourcePath).size;
474
+ const st = import_node_fs4.default.statSync(sourcePath);
475
+ if (!st.isFile()) return;
476
+ size = st.size;
346
477
  } catch {
347
478
  return;
348
479
  }
@@ -355,76 +486,66 @@ function push(out, sourcePath, storePath, tier, sourceRoot, ctx) {
355
486
  restoreTemplate: makeTemplate(sourcePath, ctx)
356
487
  });
357
488
  }
489
+ function templateContextFor(projectRoot) {
490
+ return {
491
+ userClaude: userClaudeDir(),
492
+ project: projectRoot,
493
+ cwdKey: cwdKey(projectRoot),
494
+ home: homeDir()
495
+ };
496
+ }
358
497
  function collect(projectRoot, cfg, tiers) {
359
- const userClaude = userClaudeDir();
360
- const key = cwdKey(projectRoot);
361
- const ctx = { userClaude, project: projectRoot, cwdKey: key };
498
+ const ctx = templateContextFor(projectRoot);
499
+ const home = ctx.home;
362
500
  const files = [];
363
501
  const skippedTracked = [];
364
502
  const exclude = [...HARD_DENY, ...cfg.exclude];
365
503
  const tracked = gitTrackedSet(projectRoot);
504
+ const projectRel = walk(projectRoot, { exclude }).map((abs) => ({
505
+ abs,
506
+ rel: toPosix(import_node_path4.default.relative(projectRoot, abs))
507
+ }));
508
+ const configured = cfg.assistants && cfg.assistants.length > 0 && !cfg.assistants.includes("auto");
509
+ const assistants = configured ? cfg.assistants.map(adapterById).filter(Boolean) : detectAssistants(projectRoot, projectRel.map((p) => p.rel));
366
510
  if (tiers.includes("core")) {
367
- for (const abs of walk(projectRoot, { exclude })) {
368
- const rel = toPosix(import_node_path3.default.relative(projectRoot, abs));
369
- if (!matchesAny(rel, PROJECT_CONTEXT_GLOBS)) continue;
370
- if (tracked.has(abs)) {
371
- skippedTracked.push(rel);
372
- continue;
511
+ const claimed = /* @__PURE__ */ new Set();
512
+ for (const adapter of assistants) {
513
+ for (const { abs, rel } of projectRel) {
514
+ if (claimed.has(rel)) continue;
515
+ if (!matchesAny(rel, adapter.projectGlobs)) continue;
516
+ claimed.add(rel);
517
+ if (tracked.has(abs)) {
518
+ skippedTracked.push(rel);
519
+ continue;
520
+ }
521
+ push(files, abs, `project/${rel}`, "core", "project", ctx);
373
522
  }
374
- push(files, abs, `project/${rel}`, "core", "project", ctx);
375
- }
376
- const projectsDir = import_node_path3.default.join(userClaude, "projects");
377
- let projectKeys = [];
378
- try {
379
- projectKeys = import_node_fs3.default.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && (d.name === key || d.name.startsWith(key + "-"))).map((d) => d.name);
380
- } catch {
381
- projectKeys = [];
382
523
  }
383
- for (const pk of projectKeys) {
384
- const memoryDir = import_node_path3.default.join(projectsDir, pk, "memory");
385
- for (const abs of walk(memoryDir, { exclude })) {
386
- const rel = toPosix(import_node_path3.default.relative(memoryDir, abs));
387
- const storePath = pk === key ? `memory/${rel}` : `memory-sub/${pk.slice(key.length + 1)}/${rel}`;
388
- push(files, abs, storePath, "core", "user", ctx);
524
+ for (const adapter of assistants) {
525
+ if (!adapter.collectUser) continue;
526
+ for (const { abs, rel } of adapter.collectUser(projectRoot, home, exclude)) {
527
+ push(files, abs, `assistants/${adapter.id}/${rel}`, "core", "user", ctx);
389
528
  }
390
529
  }
391
- for (const abs of collectPlans(import_node_path3.default.join(userClaude, "plans"), projectRoot, exclude)) {
392
- const rel = toPosix(import_node_path3.default.relative(import_node_path3.default.join(userClaude, "plans"), abs));
393
- push(files, abs, `plans/${rel}`, "core", "user", ctx);
394
- }
395
- const skillsDir = import_node_path3.default.join(userClaude, "skills");
396
- for (const abs of walk(skillsDir, { exclude })) {
397
- const rel = toPosix(import_node_path3.default.relative(skillsDir, abs));
398
- push(files, abs, `skills/${rel}`, "core", "user", ctx);
399
- }
400
- const agentsDir = import_node_path3.default.join(userClaude, "agents");
401
- for (const abs of walk(agentsDir, { exclude })) {
402
- const rel = toPosix(import_node_path3.default.relative(agentsDir, abs));
403
- push(files, abs, `agents/${rel}`, "core", "user", ctx);
404
- }
405
- for (const name of ["CLAUDE.md", "settings.json"]) {
406
- const abs = import_node_path3.default.join(userClaude, name);
407
- if (import_node_fs3.default.existsSync(abs)) push(files, abs, `user/${name}`, "core", "user", ctx);
408
- }
409
530
  }
410
531
  if (tiers.includes("artifacts")) {
411
532
  for (const relDir of cfg.artifactPaths) {
412
- const absDir = import_node_path3.default.join(projectRoot, relDir);
533
+ const absDir = import_node_path4.default.join(projectRoot, relDir);
413
534
  for (const abs of walk(absDir, { exclude })) {
414
- const rel = toPosix(import_node_path3.default.relative(absDir, abs));
535
+ const rel = toPosix(import_node_path4.default.relative(absDir, abs));
415
536
  push(files, abs, `artifacts/${relDir}/${rel}`, "artifacts", "project", ctx);
416
537
  }
417
538
  }
418
539
  }
419
540
  if (tiers.includes("transcripts")) {
420
- const projDir = import_node_path3.default.join(userClaude, "projects", key);
541
+ const projDir = import_node_path4.default.join(userClaudeDir(), "projects", ctx.cwdKey);
421
542
  for (const abs of walk(projDir, { exclude })) {
422
- const rel = toPosix(import_node_path3.default.relative(projDir, abs));
543
+ const rel = toPosix(import_node_path4.default.relative(projDir, abs));
423
544
  if (rel.startsWith("memory/")) continue;
424
- push(files, abs, `transcripts/${rel}`, "transcripts", "user", ctx);
545
+ push(files, abs, `transcripts/claude/${rel}`, "transcripts", "user", ctx);
425
546
  }
426
547
  }
427
- return { files, skippedTracked, ctx };
548
+ return { files, skippedTracked, assistants, ctx };
428
549
  }
429
550
  function describeExcluded(projectRoot, tiers) {
430
551
  const userClaude = userClaudeDir();
@@ -437,15 +558,17 @@ function describeExcluded(projectRoot, tiers) {
437
558
  if (filter && !filter(abs)) continue;
438
559
  files++;
439
560
  try {
440
- bytes += import_node_fs3.default.statSync(abs).size;
561
+ bytes += import_node_fs4.default.statSync(abs).size;
441
562
  } catch {
442
563
  }
443
564
  }
444
565
  return { files, bytes };
445
566
  };
446
567
  if (!tiers.includes("transcripts")) {
447
- const projDir = import_node_path3.default.join(userClaude, "projects", key);
448
- const m = measure(projDir, (p) => !p.includes(`${import_node_path3.default.sep}memory${import_node_path3.default.sep}`));
568
+ const m = measure(
569
+ import_node_path4.default.join(userClaude, "projects", key),
570
+ (p) => !p.includes(`${import_node_path4.default.sep}memory${import_node_path4.default.sep}`)
571
+ );
449
572
  if (m.files > 0) {
450
573
  out.push({
451
574
  label: "session transcripts",
@@ -459,7 +582,7 @@ function describeExcluded(projectRoot, tiers) {
459
582
  ["file-history", "edit-undo snapshots", "transient local undo state, not portable context"],
460
583
  ["tasks", "task outputs", "session-scoped tool output"]
461
584
  ]) {
462
- const m = measure(import_node_path3.default.join(userClaude, dir));
585
+ const m = measure(import_node_path4.default.join(userClaude, dir));
463
586
  if (m.files > 0) out.push({ label, ...m, reason });
464
587
  }
465
588
  return out.filter((g) => g.bytes > 0);
@@ -560,8 +683,8 @@ function isStale(h, newestContentMs) {
560
683
  }
561
684
 
562
685
  // src/scaffold.ts
563
- var import_node_fs4 = __toESM(require("fs"), 1);
564
- var import_node_path4 = __toESM(require("path"), 1);
686
+ var import_node_fs5 = __toESM(require("fs"), 1);
687
+ var import_node_path5 = __toESM(require("path"), 1);
565
688
  var SLASH_COMMAND_PATH = ".claude/commands/context.md";
566
689
  var SLASH_COMMAND_BODY = `---
567
690
  description: Sync this project's LLM context (memory, skills, instructions, handoff)
@@ -611,26 +734,26 @@ Run the context-sync action requested in: $ARGUMENTS
611
734
  Run: \`npx @tricknowtech/context status\` and summarize the result.
612
735
  `;
613
736
  function installSlashCommand(projectRoot) {
614
- const dest = import_node_path4.default.join(projectRoot, SLASH_COMMAND_PATH);
615
- if (import_node_fs4.default.existsSync(dest)) return { path: SLASH_COMMAND_PATH, written: false };
616
- ensureDir(import_node_path4.default.dirname(dest));
617
- import_node_fs4.default.writeFileSync(dest, SLASH_COMMAND_BODY, "utf8");
737
+ const dest = import_node_path5.default.join(projectRoot, SLASH_COMMAND_PATH);
738
+ if (import_node_fs5.default.existsSync(dest)) return { path: SLASH_COMMAND_PATH, written: false };
739
+ ensureDir(import_node_path5.default.dirname(dest));
740
+ import_node_fs5.default.writeFileSync(dest, SLASH_COMMAND_BODY, "utf8");
618
741
  return { path: SLASH_COMMAND_PATH, written: true };
619
742
  }
620
743
  function ensureGitignoreEntries(projectRoot, artifactsEnabled) {
621
- const gitignore = import_node_path4.default.join(projectRoot, ".gitignore");
744
+ const gitignore = import_node_path5.default.join(projectRoot, ".gitignore");
622
745
  const wanted = [".contextsync/transcripts/"];
623
746
  if (!artifactsEnabled) wanted.push(".contextsync/artifacts/");
624
747
  let existing = "";
625
748
  try {
626
- existing = import_node_fs4.default.readFileSync(gitignore, "utf8");
749
+ existing = import_node_fs5.default.readFileSync(gitignore, "utf8");
627
750
  } catch {
628
751
  }
629
752
  const lines = new Set(existing.split("\n").map((l) => l.trim()));
630
753
  const missing = wanted.filter((w) => !lines.has(w));
631
754
  if (missing.length === 0) return [];
632
755
  const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
633
- import_node_fs4.default.appendFileSync(
756
+ import_node_fs5.default.appendFileSync(
634
757
  gitignore,
635
758
  `${prefix}
636
759
  # tricknowtech context-sync \u2014 never commit these tiers
@@ -642,7 +765,7 @@ ${missing.join("\n")}
642
765
  }
643
766
 
644
767
  // src/secrets.ts
645
- var import_node_fs5 = __toESM(require("fs"), 1);
768
+ var import_node_fs6 = __toESM(require("fs"), 1);
646
769
  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;
647
770
  function looksLikeCredential(value) {
648
771
  if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value)) return false;
@@ -696,7 +819,7 @@ function scanFiles(files) {
696
819
  for (const file of files) {
697
820
  let buf;
698
821
  try {
699
- buf = import_node_fs5.default.readFileSync(file.sourcePath);
822
+ buf = import_node_fs6.default.readFileSync(file.sourcePath);
700
823
  } catch {
701
824
  continue;
702
825
  }
@@ -734,8 +857,8 @@ function formatHits(hits) {
734
857
  }
735
858
 
736
859
  // src/store.ts
737
- var import_node_fs6 = __toESM(require("fs"), 1);
738
- var import_node_path5 = __toESM(require("path"), 1);
860
+ var import_node_fs7 = __toESM(require("fs"), 1);
861
+ var import_node_path6 = __toESM(require("path"), 1);
739
862
  var MANIFEST_FILE = "manifest.json";
740
863
  var LocalStore = class {
741
864
  constructor(projectRoot) {
@@ -746,13 +869,13 @@ var LocalStore = class {
746
869
  return storeDir(this.projectRoot);
747
870
  }
748
871
  write(files, projectRoot) {
749
- for (const name of import_node_fs6.default.existsSync(this.dir) ? import_node_fs6.default.readdirSync(this.dir) : []) {
872
+ for (const name of import_node_fs7.default.existsSync(this.dir) ? import_node_fs7.default.readdirSync(this.dir) : []) {
750
873
  if (name === "config.json" || name === HANDOFF_FILE) continue;
751
- import_node_fs6.default.rmSync(import_node_path5.default.join(this.dir, name), { recursive: true, force: true });
874
+ import_node_fs7.default.rmSync(import_node_path6.default.join(this.dir, name), { recursive: true, force: true });
752
875
  }
753
876
  const entries = [];
754
877
  for (const file of files) {
755
- const dest = import_node_path5.default.join(this.dir, file.storePath);
878
+ const dest = import_node_path6.default.join(this.dir, file.storePath);
756
879
  try {
757
880
  copyFile(file.sourcePath, dest);
758
881
  } catch {
@@ -773,11 +896,11 @@ var LocalStore = class {
773
896
  writtenFrom: projectRoot,
774
897
  entries
775
898
  };
776
- writeJson(import_node_path5.default.join(this.dir, MANIFEST_FILE), manifest);
899
+ writeJson(import_node_path6.default.join(this.dir, MANIFEST_FILE), manifest);
777
900
  return manifest;
778
901
  }
779
902
  readManifest() {
780
- return readJson(import_node_path5.default.join(this.dir, MANIFEST_FILE));
903
+ return readJson(import_node_path6.default.join(this.dir, MANIFEST_FILE));
781
904
  }
782
905
  restore(ctx, opts = {}) {
783
906
  const manifest = this.readManifest();
@@ -785,12 +908,12 @@ var LocalStore = class {
785
908
  const skipped = [];
786
909
  if (!manifest) return { restored, skipped };
787
910
  for (const entry of manifest.entries) {
788
- const src = import_node_path5.default.join(this.dir, entry.storePath);
789
- if (!import_node_fs6.default.existsSync(src)) continue;
911
+ const src = import_node_path6.default.join(this.dir, entry.storePath);
912
+ if (!import_node_fs7.default.existsSync(src)) continue;
790
913
  const dest = resolveTemplate(entry.restoreTemplate, ctx);
791
- if (!opts.force && import_node_fs6.default.existsSync(dest)) {
914
+ if (!opts.force && import_node_fs7.default.existsSync(dest)) {
792
915
  try {
793
- if (!import_node_fs6.default.readFileSync(dest).equals(import_node_fs6.default.readFileSync(src))) {
916
+ if (!import_node_fs7.default.readFileSync(dest).equals(import_node_fs7.default.readFileSync(src))) {
794
917
  skipped.push(entry.storePath);
795
918
  continue;
796
919
  }
@@ -800,8 +923,8 @@ var LocalStore = class {
800
923
  }
801
924
  }
802
925
  try {
803
- ensureDir(import_node_path5.default.dirname(dest));
804
- import_node_fs6.default.copyFileSync(src, dest);
926
+ ensureDir(import_node_path6.default.dirname(dest));
927
+ import_node_fs7.default.copyFileSync(src, dest);
805
928
  restored.push(entry.storePath);
806
929
  } catch {
807
930
  skipped.push(entry.storePath);
@@ -810,10 +933,10 @@ var LocalStore = class {
810
933
  return { restored, skipped };
811
934
  }
812
935
  readHandoff() {
813
- return readJson(import_node_path5.default.join(this.dir, HANDOFF_FILE));
936
+ return readJson(import_node_path6.default.join(this.dir, HANDOFF_FILE));
814
937
  }
815
938
  writeHandoff(handoff) {
816
- writeJson(import_node_path5.default.join(this.dir, HANDOFF_FILE), handoff);
939
+ writeJson(import_node_path6.default.join(this.dir, HANDOFF_FILE), handoff);
817
940
  }
818
941
  };
819
942