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