@tricknowtech/context 0.1.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 ADDED
@@ -0,0 +1,691 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ALL_TIERS: () => ALL_TIERS,
34
+ CONFIG_FILE: () => CONFIG_FILE,
35
+ DEFAULT_EXCLUDE: () => DEFAULT_EXCLUDE,
36
+ HANDOFF_FILE: () => HANDOFF_FILE,
37
+ HARD_DENY: () => HARD_DENY,
38
+ LOCAL_TIERS: () => LOCAL_TIERS,
39
+ LocalStore: () => LocalStore,
40
+ MANIFEST_FILE: () => MANIFEST_FILE,
41
+ SLASH_COMMAND_BODY: () => SLASH_COMMAND_BODY,
42
+ SLASH_COMMAND_PATH: () => SLASH_COMMAND_PATH,
43
+ STORE_DIR: () => STORE_DIR,
44
+ collect: () => collect,
45
+ configPath: () => configPath,
46
+ cwdKey: () => cwdKey,
47
+ defaultConfig: () => defaultConfig,
48
+ ensureGitignoreEntries: () => ensureGitignoreEntries,
49
+ findProjectRoot: () => findProjectRoot,
50
+ formatBytes: () => formatBytes,
51
+ formatHits: () => formatHits,
52
+ gitTrackedSet: () => gitTrackedSet,
53
+ installSlashCommand: () => installSlashCommand,
54
+ isGitRepo: () => isGitRepo,
55
+ loadConfig: () => loadConfig,
56
+ makeTemplate: () => makeTemplate,
57
+ matchesAny: () => matchesAny,
58
+ resolveTemplate: () => resolveTemplate,
59
+ saveConfig: () => saveConfig,
60
+ scanFiles: () => scanFiles,
61
+ storeDir: () => storeDir,
62
+ summarize: () => summarize,
63
+ userClaudeDir: () => userClaudeDir,
64
+ walk: () => walk
65
+ });
66
+ module.exports = __toCommonJS(index_exports);
67
+
68
+ // src/collector.ts
69
+ var import_node_fs3 = __toESM(require("fs"), 1);
70
+ var import_node_path3 = __toESM(require("path"), 1);
71
+
72
+ // src/config.ts
73
+ var import_node_crypto = __toESM(require("crypto"), 1);
74
+ var import_node_fs2 = __toESM(require("fs"), 1);
75
+ var import_node_path2 = __toESM(require("path"), 1);
76
+
77
+ // src/fsutil.ts
78
+ var import_node_child_process = require("child_process");
79
+ var import_node_fs = __toESM(require("fs"), 1);
80
+ var import_node_os = __toESM(require("os"), 1);
81
+ var import_node_path = __toESM(require("path"), 1);
82
+ function userClaudeDir() {
83
+ return process.env.CLAUDE_CONFIG_DIR ?? import_node_path.default.join(import_node_os.default.homedir(), ".claude");
84
+ }
85
+ function cwdKey(absPath) {
86
+ return absPath.replace(/[^a-zA-Z0-9]/g, "-");
87
+ }
88
+ function makeTemplate(absPath, ctx) {
89
+ const candidates = [
90
+ [ctx.userClaude, "{userClaude}"],
91
+ [ctx.project, "{project}"]
92
+ ].sort((a, b) => b[0].length - a[0].length);
93
+ let out = absPath;
94
+ for (const [root, token] of candidates) {
95
+ if (absPath === root || absPath.startsWith(root + import_node_path.default.sep)) {
96
+ out = token + "/" + toPosix(import_node_path.default.relative(root, absPath));
97
+ break;
98
+ }
99
+ }
100
+ return ctx.cwdKey ? out.split(ctx.cwdKey).join("{cwdKey}") : out;
101
+ }
102
+ function resolveTemplate(template, ctx) {
103
+ const expanded = template.split("{userClaude}").join(ctx.userClaude).split("{project}").join(ctx.project).split("{cwdKey}").join(ctx.cwdKey);
104
+ return import_node_path.default.normalize(expanded);
105
+ }
106
+ function toPosix(p) {
107
+ return p.split(import_node_path.default.sep).join("/");
108
+ }
109
+ function gitTrackedSet(root) {
110
+ try {
111
+ const out = (0, import_node_child_process.execFileSync)("git", ["-C", root, "ls-files", "-z"], {
112
+ encoding: "utf8",
113
+ maxBuffer: 64 * 1024 * 1024,
114
+ stdio: ["ignore", "pipe", "ignore"]
115
+ });
116
+ const set = /* @__PURE__ */ new Set();
117
+ for (const rel of out.split("\0")) {
118
+ if (rel) set.add(import_node_path.default.resolve(root, rel));
119
+ }
120
+ return set;
121
+ } catch {
122
+ return /* @__PURE__ */ new Set();
123
+ }
124
+ }
125
+ function isGitRepo(root) {
126
+ try {
127
+ (0, import_node_child_process.execFileSync)("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], {
128
+ stdio: "ignore"
129
+ });
130
+ return true;
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+ function globToRegExp(pattern) {
136
+ let re = "";
137
+ for (let i = 0; i < pattern.length; i++) {
138
+ const c = pattern[i];
139
+ if (c === "*") {
140
+ if (pattern[i + 1] === "*") {
141
+ if (pattern[i + 2] === "/") {
142
+ re += "(?:.*/)?";
143
+ i += 2;
144
+ } else {
145
+ re += ".*";
146
+ i += 1;
147
+ }
148
+ } else {
149
+ re += "[^/]*";
150
+ }
151
+ } else if (c === "?") {
152
+ re += "[^/]";
153
+ } else if ("\\^$.|+()[]{}".includes(c)) {
154
+ re += "\\" + c;
155
+ } else {
156
+ re += c;
157
+ }
158
+ }
159
+ if (pattern.endsWith("/")) re += ".*";
160
+ return new RegExp("^" + re + "$");
161
+ }
162
+ function matchesAny(relPath, patterns) {
163
+ const p = toPosix(relPath);
164
+ return patterns.some((pattern) => {
165
+ const re = globToRegExp(pattern);
166
+ if (re.test(p)) return true;
167
+ if (!pattern.includes("/")) {
168
+ return p.split("/").some((seg) => globToRegExp(pattern).test(seg));
169
+ }
170
+ return false;
171
+ });
172
+ }
173
+ function walk(root, opts = {}) {
174
+ const { exclude = [], maxFiles = 5e4 } = opts;
175
+ const out = [];
176
+ if (!import_node_fs.default.existsSync(root)) return out;
177
+ const stack = [root];
178
+ while (stack.length > 0) {
179
+ const dir = stack.pop();
180
+ let entries;
181
+ try {
182
+ entries = import_node_fs.default.readdirSync(dir, { withFileTypes: true });
183
+ } catch {
184
+ continue;
185
+ }
186
+ for (const entry of entries) {
187
+ const abs = import_node_path.default.join(dir, entry.name);
188
+ const rel = toPosix(import_node_path.default.relative(root, abs));
189
+ if (matchesAny(rel, exclude)) continue;
190
+ if (entry.isDirectory()) {
191
+ stack.push(abs);
192
+ } else if (entry.isFile()) {
193
+ if (out.length >= maxFiles) return out;
194
+ out.push(abs);
195
+ }
196
+ }
197
+ }
198
+ return out;
199
+ }
200
+ function ensureDir(dir) {
201
+ import_node_fs.default.mkdirSync(dir, { recursive: true });
202
+ }
203
+ function readJson(file) {
204
+ try {
205
+ return JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
210
+ function writeJson(file, value) {
211
+ ensureDir(import_node_path.default.dirname(file));
212
+ import_node_fs.default.writeFileSync(file, JSON.stringify(value, null, 2) + "\n", "utf8");
213
+ }
214
+ function copyFile(from, to) {
215
+ ensureDir(import_node_path.default.dirname(to));
216
+ import_node_fs.default.copyFileSync(from, to);
217
+ }
218
+ function formatBytes(n) {
219
+ if (n < 1024) return `${n} B`;
220
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
221
+ if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
222
+ return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
223
+ }
224
+
225
+ // src/config.ts
226
+ var STORE_DIR = ".contextsync";
227
+ var CONFIG_FILE = "config.json";
228
+ var HANDOFF_FILE = "handoff.json";
229
+ var HARD_DENY = [
230
+ "**/.env",
231
+ "**/.env.*",
232
+ "**/.credentials.json",
233
+ "**/.claude.json",
234
+ "**/*.pem",
235
+ "**/*.key",
236
+ "**/id_rsa*",
237
+ "**/id_ed25519*",
238
+ "**/node_modules/",
239
+ "**/.git/",
240
+ "**/vendor/",
241
+ `**/${STORE_DIR}/`
242
+ ];
243
+ var DEFAULT_EXCLUDE = [
244
+ "**/*.log",
245
+ "**/.DS_Store",
246
+ "**/dist/",
247
+ "**/build/",
248
+ "**/.next/",
249
+ "**/__pycache__/"
250
+ ];
251
+ function defaultConfig(projectRoot) {
252
+ return {
253
+ projectId: import_node_crypto.default.randomUUID(),
254
+ name: import_node_path2.default.basename(projectRoot),
255
+ rootHint: projectRoot,
256
+ tiers: ["core", "handoff"],
257
+ artifactPaths: ["graphify-out"],
258
+ exclude: [...DEFAULT_EXCLUDE],
259
+ remotes: {}
260
+ };
261
+ }
262
+ function findProjectRoot(start = process.cwd()) {
263
+ let dir = import_node_path2.default.resolve(start);
264
+ for (; ; ) {
265
+ if (import_node_fs2.default.existsSync(import_node_path2.default.join(dir, STORE_DIR, CONFIG_FILE))) return dir;
266
+ if (import_node_fs2.default.existsSync(import_node_path2.default.join(dir, ".git"))) return dir;
267
+ const parent = import_node_path2.default.dirname(dir);
268
+ if (parent === dir) return null;
269
+ dir = parent;
270
+ }
271
+ }
272
+ function storeDir(projectRoot) {
273
+ return import_node_path2.default.join(projectRoot, STORE_DIR);
274
+ }
275
+ function configPath(projectRoot) {
276
+ return import_node_path2.default.join(storeDir(projectRoot), CONFIG_FILE);
277
+ }
278
+ function loadConfig(projectRoot) {
279
+ const cfg = readJson(configPath(projectRoot));
280
+ if (!cfg) return null;
281
+ return {
282
+ ...defaultConfig(projectRoot),
283
+ ...cfg,
284
+ remotes: cfg.remotes ?? {},
285
+ tiers: cfg.tiers ?? ["core", "handoff"]
286
+ };
287
+ }
288
+ function saveConfig(projectRoot, cfg) {
289
+ writeJson(configPath(projectRoot), cfg);
290
+ }
291
+
292
+ // src/collector.ts
293
+ var PROJECT_CONTEXT_GLOBS = [
294
+ "CLAUDE.md",
295
+ "CLAUDE.local.md",
296
+ "AGENTS.md",
297
+ "**/CLAUDE.md",
298
+ "**/AGENTS.md",
299
+ ".cursorrules",
300
+ ".github/copilot-instructions.md",
301
+ ".claude/settings.json",
302
+ ".claude/memory/",
303
+ ".claude/plans/",
304
+ ".claude/commands/",
305
+ ".claude/agents/",
306
+ ".claude/skills/"
307
+ ];
308
+ function push(out, sourcePath, storePath, tier, sourceRoot, ctx) {
309
+ let size = 0;
310
+ try {
311
+ size = import_node_fs3.default.statSync(sourcePath).size;
312
+ } catch {
313
+ return;
314
+ }
315
+ out.push({
316
+ storePath,
317
+ sourcePath,
318
+ sourceRoot,
319
+ tier,
320
+ size,
321
+ restoreTemplate: makeTemplate(sourcePath, ctx)
322
+ });
323
+ }
324
+ function collect(projectRoot, cfg, tiers) {
325
+ const userClaude = userClaudeDir();
326
+ const key = cwdKey(projectRoot);
327
+ const ctx = { userClaude, project: projectRoot, cwdKey: key };
328
+ const files = [];
329
+ const skippedTracked = [];
330
+ const exclude = [...HARD_DENY, ...cfg.exclude];
331
+ const tracked = gitTrackedSet(projectRoot);
332
+ if (tiers.includes("core")) {
333
+ for (const abs of walk(projectRoot, { exclude })) {
334
+ const rel = toPosix(import_node_path3.default.relative(projectRoot, abs));
335
+ if (!matchesAny(rel, PROJECT_CONTEXT_GLOBS)) continue;
336
+ if (tracked.has(abs)) {
337
+ skippedTracked.push(rel);
338
+ continue;
339
+ }
340
+ push(files, abs, `project/${rel}`, "core", "project", ctx);
341
+ }
342
+ const memoryDir = import_node_path3.default.join(userClaude, "projects", key, "memory");
343
+ for (const abs of walk(memoryDir, { exclude })) {
344
+ const rel = toPosix(import_node_path3.default.relative(memoryDir, abs));
345
+ push(files, abs, `memory/${rel}`, "core", "user", ctx);
346
+ }
347
+ const skillsDir = import_node_path3.default.join(userClaude, "skills");
348
+ for (const abs of walk(skillsDir, { exclude })) {
349
+ const rel = toPosix(import_node_path3.default.relative(skillsDir, abs));
350
+ push(files, abs, `skills/${rel}`, "core", "user", ctx);
351
+ }
352
+ const agentsDir = import_node_path3.default.join(userClaude, "agents");
353
+ for (const abs of walk(agentsDir, { exclude })) {
354
+ const rel = toPosix(import_node_path3.default.relative(agentsDir, abs));
355
+ push(files, abs, `agents/${rel}`, "core", "user", ctx);
356
+ }
357
+ for (const name of ["CLAUDE.md", "settings.json"]) {
358
+ const abs = import_node_path3.default.join(userClaude, name);
359
+ if (import_node_fs3.default.existsSync(abs)) push(files, abs, `user/${name}`, "core", "user", ctx);
360
+ }
361
+ }
362
+ if (tiers.includes("artifacts")) {
363
+ for (const relDir of cfg.artifactPaths) {
364
+ const absDir = import_node_path3.default.join(projectRoot, relDir);
365
+ for (const abs of walk(absDir, { exclude })) {
366
+ const rel = toPosix(import_node_path3.default.relative(absDir, abs));
367
+ push(files, abs, `artifacts/${relDir}/${rel}`, "artifacts", "project", ctx);
368
+ }
369
+ }
370
+ }
371
+ if (tiers.includes("transcripts")) {
372
+ const projDir = import_node_path3.default.join(userClaude, "projects", key);
373
+ for (const abs of walk(projDir, { exclude })) {
374
+ const rel = toPosix(import_node_path3.default.relative(projDir, abs));
375
+ if (rel.startsWith("memory/")) continue;
376
+ push(files, abs, `transcripts/${rel}`, "transcripts", "user", ctx);
377
+ }
378
+ }
379
+ return { files, skippedTracked, ctx };
380
+ }
381
+ function summarize(files) {
382
+ const empty = { count: 0, bytes: 0 };
383
+ const out = {
384
+ core: { ...empty },
385
+ handoff: { ...empty },
386
+ artifacts: { ...empty },
387
+ transcripts: { ...empty }
388
+ };
389
+ for (const f of files) {
390
+ out[f.tier].count++;
391
+ out[f.tier].bytes += f.size;
392
+ }
393
+ return out;
394
+ }
395
+
396
+ // src/scaffold.ts
397
+ var import_node_fs4 = __toESM(require("fs"), 1);
398
+ var import_node_path4 = __toESM(require("path"), 1);
399
+ var SLASH_COMMAND_PATH = ".claude/commands/context.md";
400
+ var SLASH_COMMAND_BODY = `---
401
+ description: Sync this project's LLM context (memory, skills, instructions, handoff)
402
+ argument-hint: "push | pull | status"
403
+ allowed-tools: Bash(npx @tricknowtech/context:*), Read, Write
404
+ ---
405
+
406
+ Run the context-sync action requested in: $ARGUMENTS
407
+ (If no argument was given, treat it as \`status\`.)
408
+
409
+ ## push
410
+
411
+ 1. Write a handoff summary of THIS conversation to \`.contextsync/handoff.json\`.
412
+ Use exactly this shape \u2014 it is read back verbatim on the other device:
413
+
414
+ \`\`\`json
415
+ {
416
+ "updatedAt": "<ISO 8601 timestamp>",
417
+ "goal": "<what this session set out to achieve, one or two sentences>",
418
+ "decisions": ["<decisions already made, so they aren't re-litigated>"],
419
+ "openThreads": ["<work explicitly left unfinished>"],
420
+ "filesTouched": ["<repo-relative paths changed this session>"],
421
+ "nextStep": "<the single next action someone should take>",
422
+ "notes": "<anything else worth carrying over; optional>"
423
+ }
424
+ \`\`\`
425
+
426
+ Be specific and factual. Write what was actually decided and actually left
427
+ open \u2014 a vague handoff is worse than none, because it reads as progress.
428
+ Never put credentials, tokens, or .env values in any field.
429
+
430
+ 2. Then run: \`npx @tricknowtech/context push\`
431
+
432
+ 3. Report what was synced. If the push was refused for possible secrets, show
433
+ the findings and STOP \u2014 do not re-run with \`--allow-secrets\` unless the
434
+ user explicitly tells you the hits are false positives.
435
+
436
+ ## pull
437
+
438
+ 1. Run: \`npx @tricknowtech/context pull\`
439
+ 2. Read \`.contextsync/handoff.json\` and summarize for the user, out loud:
440
+ the goal, what was decided, what is still open, and the next step.
441
+ 3. If any files were skipped as conflicting, list them and ask before forcing.
442
+
443
+ ## status
444
+
445
+ Run: \`npx @tricknowtech/context status\` and summarize the result.
446
+ `;
447
+ function installSlashCommand(projectRoot) {
448
+ const dest = import_node_path4.default.join(projectRoot, SLASH_COMMAND_PATH);
449
+ if (import_node_fs4.default.existsSync(dest)) return { path: SLASH_COMMAND_PATH, written: false };
450
+ ensureDir(import_node_path4.default.dirname(dest));
451
+ import_node_fs4.default.writeFileSync(dest, SLASH_COMMAND_BODY, "utf8");
452
+ return { path: SLASH_COMMAND_PATH, written: true };
453
+ }
454
+ function ensureGitignoreEntries(projectRoot, artifactsEnabled) {
455
+ const gitignore = import_node_path4.default.join(projectRoot, ".gitignore");
456
+ const wanted = [".contextsync/transcripts/"];
457
+ if (!artifactsEnabled) wanted.push(".contextsync/artifacts/");
458
+ let existing = "";
459
+ try {
460
+ existing = import_node_fs4.default.readFileSync(gitignore, "utf8");
461
+ } catch {
462
+ }
463
+ const lines = new Set(existing.split("\n").map((l) => l.trim()));
464
+ const missing = wanted.filter((w) => !lines.has(w));
465
+ if (missing.length === 0) return [];
466
+ const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
467
+ import_node_fs4.default.appendFileSync(
468
+ gitignore,
469
+ `${prefix}
470
+ # tricknowtech context-sync \u2014 never commit these tiers
471
+ ${missing.join("\n")}
472
+ `,
473
+ "utf8"
474
+ );
475
+ return missing;
476
+ }
477
+
478
+ // src/secrets.ts
479
+ var import_node_fs5 = __toESM(require("fs"), 1);
480
+ 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;
481
+ function looksLikeCredential(value) {
482
+ if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value)) return false;
483
+ if (/^[A-Za-z][A-Za-z_]*$/.test(value) && value.length < 24) return false;
484
+ const classes = [/[a-z]/, /[A-Z]/, /[0-9]/, /[_\-+/=]/].filter((re) => re.test(value)).length;
485
+ return classes >= 2;
486
+ }
487
+ var RULES = [
488
+ { name: "private-key-block", re: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/ },
489
+ { name: "aws-access-key-id", re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/ },
490
+ { name: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/ },
491
+ { name: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/ },
492
+ { name: "stripe-live-key", re: /\b[sr]k_live_[A-Za-z0-9]{16,}\b/ },
493
+ { name: "razorpay-live-key", re: /\brzp_live_[A-Za-z0-9]{10,}\b/ },
494
+ { name: "google-api-key", re: /\bAIza[0-9A-Za-z_-]{35}\b/ },
495
+ { name: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/ },
496
+ // Tricknowtech's own public-API key formats — esk_/wak_/fak_ are live
497
+ // credentials for a customer's account, and csk_ will be ours.
498
+ { name: "tricknowtech-api-key", re: /\b(?:esk|wak|fak|csk)_[a-f0-9]{40,}\b/ },
499
+ // Connection strings carrying an inline password.
500
+ { name: "db-url-with-password", re: /\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:[^\s:/@]{4,}@[^\s/]+/ },
501
+ // Generic `SOMETHING_SECRET=value` / `"apiKey": "value"` assignments.
502
+ // The value charset deliberately excludes brackets and whitespace so that
503
+ // `tokens: data.get(...)` truncates to a short identifier and falls out.
504
+ {
505
+ name: "assigned-secret",
506
+ re: /\b([A-Za-z0-9_]*(?:SECRET|PASSWORD|PASSWD|TOKEN|API[_-]?KEY|APIKEY|ACCESS[_-]?KEY|PRIVATE[_-]?KEY|CREDENTIAL|AUTH)[A-Za-z0-9_]*)["']?\s*[:=]\s*["']?([A-Za-z0-9_\-+/=.]{8,})/i,
507
+ accept: (m) => !BENIGN_KEY.test(m[1]) && looksLikeCredential(m[2])
508
+ }
509
+ ];
510
+ var PLACEHOLDER = /^(?:x{3,}|\.{3,}|-+|_+|\$\{.*\}|<.*>|\{\{.*\}\}|change[_-]?me|your[_-].*|placeholder|example|dummy|sample|redacted|null|true|false|undefined|test|password|secret|token)$/i;
511
+ function isPlaceholder(value) {
512
+ if (PLACEHOLDER.test(value)) return true;
513
+ return new Set(value).size <= 2;
514
+ }
515
+ function mask(value) {
516
+ const trimmed = value.trim();
517
+ if (trimmed.length <= 8) return "*".repeat(trimmed.length);
518
+ return `${trimmed.slice(0, 4)}${"*".repeat(Math.min(12, trimmed.length - 8))}${trimmed.slice(-4)}`;
519
+ }
520
+ function looksBinary(buf) {
521
+ const n = Math.min(buf.length, 1024);
522
+ for (let i = 0; i < n; i++) {
523
+ if (buf[i] === 0) return true;
524
+ }
525
+ return false;
526
+ }
527
+ var MAX_SCAN_BYTES = 5 * 1024 * 1024;
528
+ function scanFiles(files) {
529
+ const hits = [];
530
+ for (const file of files) {
531
+ let buf;
532
+ try {
533
+ buf = import_node_fs5.default.readFileSync(file.sourcePath);
534
+ } catch {
535
+ continue;
536
+ }
537
+ if (looksBinary(buf)) continue;
538
+ const text = buf.subarray(0, MAX_SCAN_BYTES).toString("utf8");
539
+ const lines = text.split("\n");
540
+ for (let i = 0; i < lines.length; i++) {
541
+ const line = lines[i];
542
+ if (line.length > 4e3) continue;
543
+ for (const rule of RULES) {
544
+ const m = rule.re.exec(line);
545
+ if (!m) continue;
546
+ if (rule.accept && !rule.accept(m)) continue;
547
+ const captured = m[m.length - 1] ?? m[0];
548
+ if (isPlaceholder(captured)) continue;
549
+ hits.push({
550
+ storePath: file.storePath,
551
+ line: i + 1,
552
+ rule: rule.name,
553
+ preview: mask(captured)
554
+ });
555
+ break;
556
+ }
557
+ }
558
+ }
559
+ return hits;
560
+ }
561
+ function formatHits(hits) {
562
+ const shown = hits.slice(0, 20);
563
+ const lines = shown.map((h) => ` ${h.storePath}:${h.line} [${h.rule}] ${h.preview}`);
564
+ if (hits.length > shown.length) {
565
+ lines.push(` \u2026 and ${hits.length - shown.length} more`);
566
+ }
567
+ return lines.join("\n");
568
+ }
569
+
570
+ // src/store.ts
571
+ var import_node_fs6 = __toESM(require("fs"), 1);
572
+ var import_node_path5 = __toESM(require("path"), 1);
573
+ var MANIFEST_FILE = "manifest.json";
574
+ var LocalStore = class {
575
+ constructor(projectRoot) {
576
+ this.projectRoot = projectRoot;
577
+ }
578
+ projectRoot;
579
+ get dir() {
580
+ return storeDir(this.projectRoot);
581
+ }
582
+ write(files, projectRoot) {
583
+ for (const name of import_node_fs6.default.existsSync(this.dir) ? import_node_fs6.default.readdirSync(this.dir) : []) {
584
+ if (name === "config.json" || name === HANDOFF_FILE) continue;
585
+ import_node_fs6.default.rmSync(import_node_path5.default.join(this.dir, name), { recursive: true, force: true });
586
+ }
587
+ const entries = [];
588
+ for (const file of files) {
589
+ const dest = import_node_path5.default.join(this.dir, file.storePath);
590
+ try {
591
+ copyFile(file.sourcePath, dest);
592
+ } catch {
593
+ continue;
594
+ }
595
+ entries.push({
596
+ storePath: file.storePath,
597
+ restoreTemplate: file.restoreTemplate,
598
+ tier: file.tier,
599
+ sourceRoot: file.sourceRoot,
600
+ size: file.size
601
+ });
602
+ }
603
+ entries.sort((a, b) => a.storePath.localeCompare(b.storePath));
604
+ const manifest = {
605
+ version: 1,
606
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
607
+ writtenFrom: projectRoot,
608
+ entries
609
+ };
610
+ writeJson(import_node_path5.default.join(this.dir, MANIFEST_FILE), manifest);
611
+ return manifest;
612
+ }
613
+ readManifest() {
614
+ return readJson(import_node_path5.default.join(this.dir, MANIFEST_FILE));
615
+ }
616
+ restore(ctx, opts = {}) {
617
+ const manifest = this.readManifest();
618
+ const restored = [];
619
+ const skipped = [];
620
+ if (!manifest) return { restored, skipped };
621
+ for (const entry of manifest.entries) {
622
+ const src = import_node_path5.default.join(this.dir, entry.storePath);
623
+ if (!import_node_fs6.default.existsSync(src)) continue;
624
+ const dest = resolveTemplate(entry.restoreTemplate, ctx);
625
+ if (!opts.force && import_node_fs6.default.existsSync(dest)) {
626
+ try {
627
+ if (!import_node_fs6.default.readFileSync(dest).equals(import_node_fs6.default.readFileSync(src))) {
628
+ skipped.push(entry.storePath);
629
+ continue;
630
+ }
631
+ } catch {
632
+ skipped.push(entry.storePath);
633
+ continue;
634
+ }
635
+ }
636
+ try {
637
+ ensureDir(import_node_path5.default.dirname(dest));
638
+ import_node_fs6.default.copyFileSync(src, dest);
639
+ restored.push(entry.storePath);
640
+ } catch {
641
+ skipped.push(entry.storePath);
642
+ }
643
+ }
644
+ return { restored, skipped };
645
+ }
646
+ readHandoff() {
647
+ return readJson(import_node_path5.default.join(this.dir, HANDOFF_FILE));
648
+ }
649
+ writeHandoff(handoff) {
650
+ writeJson(import_node_path5.default.join(this.dir, HANDOFF_FILE), handoff);
651
+ }
652
+ };
653
+
654
+ // src/types.ts
655
+ var ALL_TIERS = ["core", "handoff", "artifacts", "transcripts"];
656
+ var LOCAL_TIERS = ["core", "handoff", "artifacts"];
657
+ // Annotate the CommonJS export names for ESM import in node:
658
+ 0 && (module.exports = {
659
+ ALL_TIERS,
660
+ CONFIG_FILE,
661
+ DEFAULT_EXCLUDE,
662
+ HANDOFF_FILE,
663
+ HARD_DENY,
664
+ LOCAL_TIERS,
665
+ LocalStore,
666
+ MANIFEST_FILE,
667
+ SLASH_COMMAND_BODY,
668
+ SLASH_COMMAND_PATH,
669
+ STORE_DIR,
670
+ collect,
671
+ configPath,
672
+ cwdKey,
673
+ defaultConfig,
674
+ ensureGitignoreEntries,
675
+ findProjectRoot,
676
+ formatBytes,
677
+ formatHits,
678
+ gitTrackedSet,
679
+ installSlashCommand,
680
+ isGitRepo,
681
+ loadConfig,
682
+ makeTemplate,
683
+ matchesAny,
684
+ resolveTemplate,
685
+ saveConfig,
686
+ scanFiles,
687
+ storeDir,
688
+ summarize,
689
+ userClaudeDir,
690
+ walk
691
+ });