@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.
@@ -0,0 +1,269 @@
1
+ /** Root of the user's global Claude directory (~/.claude), overridable for tests. */
2
+ declare function userClaudeDir(): string;
3
+ /**
4
+ * Claude Code keys its per-project directories by the working directory with
5
+ * every non-alphanumeric character folded to a dash — `/root/my-project`
6
+ * becomes `-root-my-project`. Restoring on a machine where the repo lives at
7
+ * a different path means recomputing this key, which is why nothing may hard-
8
+ * code it.
9
+ */
10
+ declare function cwdKey(absPath: string): string;
11
+ /**
12
+ * Placeholders that make a restore path portable across machines.
13
+ *
14
+ * `cwdKey` is the important one: Claude Code's per-project directories are
15
+ * named after the absolute project path, so a path captured on one machine
16
+ * embeds that machine's layout. Templating the key out and recomputing it at
17
+ * restore time is what lets the same context land correctly in a repo cloned
18
+ * to a different directory.
19
+ */
20
+ interface TemplateContext {
21
+ userClaude: string;
22
+ project: string;
23
+ cwdKey: string;
24
+ }
25
+ declare function makeTemplate(absPath: string, ctx: TemplateContext): string;
26
+ declare function resolveTemplate(template: string, ctx: TemplateContext): string;
27
+ /**
28
+ * Absolute paths git already tracks in this repo. Anything in here is skipped
29
+ * by the collector: it travels with the code already, and copying it into the
30
+ * store would duplicate it and create two sources of truth that drift.
31
+ *
32
+ * Returns an empty set when not in a git repo (or git is unavailable), which
33
+ * correctly degrades to "copy everything".
34
+ */
35
+ declare function gitTrackedSet(root: string): Set<string>;
36
+ declare function isGitRepo(root: string): boolean;
37
+ declare function matchesAny(relPath: string, patterns: string[]): boolean;
38
+ interface WalkOptions {
39
+ /** Patterns (relative to `root`) to skip entirely. */
40
+ exclude?: string[];
41
+ /** Hard cap so a mis-pointed root can't hang the CLI. */
42
+ maxFiles?: number;
43
+ }
44
+ /** Recursively list files under `root`, returning absolute paths. */
45
+ declare function walk(root: string, opts?: WalkOptions): string[];
46
+ declare function formatBytes(n: number): string;
47
+
48
+ /**
49
+ * Shared types for the context-sync engine.
50
+ *
51
+ * The collector and the stores are deliberately decoupled: the collector
52
+ * decides *what* is context (tier rules, git-tracked detection, secret
53
+ * scanning), a Store decides *how* it is persisted. LocalStore writes plain
54
+ * mirrored files into the repo; a future CloudStore writes content-addressed
55
+ * blobs. Both consume the same CollectedFile[].
56
+ */
57
+ /** Which slice of context a file belongs to. */
58
+ type Tier = 'core' | 'handoff' | 'artifacts' | 'transcripts';
59
+ declare const ALL_TIERS: Tier[];
60
+ /**
61
+ * Tiers a local (in-repo) store will accept. Transcripts are excluded on
62
+ * purpose — they run to hundreds of MB and are append-only, so committing
63
+ * them would permanently bloat every clone of the repo, and they carry the
64
+ * largest secret-leak surface (full tool output, including anything read
65
+ * out of a .env).
66
+ */
67
+ declare const LOCAL_TIERS: Tier[];
68
+ /** Where a file was collected from — decides how it is restored. */
69
+ type SourceRoot =
70
+ /** Inside the project/repo itself. */
71
+ 'project'
72
+ /** Under the user's global Claude directory (~/.claude). */
73
+ | 'user';
74
+ interface CollectedFile {
75
+ /**
76
+ * Path *within the store*, always POSIX-style and relative.
77
+ * e.g. `memory/nginx_upstream_ip_cache.md`, `skills/graphify/SKILL.md`.
78
+ */
79
+ storePath: string;
80
+ /** Absolute path this was read from on this machine. */
81
+ sourcePath: string;
82
+ sourceRoot: SourceRoot;
83
+ tier: Tier;
84
+ size: number;
85
+ /**
86
+ * Absolute path to restore to, expressed as a template with `{userClaude}`
87
+ * and `{project}` placeholders so it can be re-resolved on a machine where
88
+ * the repo (or home dir) lives somewhere else. This is what makes restore
89
+ * portable — see resolveTemplate() in fsutil.ts.
90
+ */
91
+ restoreTemplate: string;
92
+ }
93
+ interface ProjectConfig {
94
+ /** Stable id for this project, used to correlate with a cloud remote. */
95
+ projectId: string;
96
+ name: string;
97
+ /**
98
+ * Absolute project root recorded at init time on the machine that ran it.
99
+ * Restoring elsewhere diffs this against the current root to rewrite the
100
+ * cwd-derived directory keys Claude Code uses (e.g. `-root-my-project`).
101
+ */
102
+ rootHint: string;
103
+ tiers: Tier[];
104
+ /** Extra project-relative dirs to capture as `artifacts`. */
105
+ artifactPaths: string[];
106
+ /** Glob-ish patterns excluded from collection, on top of the hard-coded denylist. */
107
+ exclude: string[];
108
+ remotes: Record<string, RemoteConfig>;
109
+ }
110
+ interface RemoteConfig {
111
+ type: 'tricknowtech';
112
+ url: string;
113
+ e2ee: boolean;
114
+ }
115
+ interface Handoff {
116
+ /** ISO timestamp written by the slash command. */
117
+ updatedAt: string;
118
+ /** What the session was trying to achieve. */
119
+ goal: string;
120
+ /** Decisions already made, so they aren't re-litigated on the next device. */
121
+ decisions: string[];
122
+ /** Work explicitly left unfinished. */
123
+ openThreads: string[];
124
+ /** Files touched, for fast re-orientation. */
125
+ filesTouched: string[];
126
+ /** The single next action to take. */
127
+ nextStep: string;
128
+ /** Free-form notes the model wants to carry over. */
129
+ notes?: string;
130
+ }
131
+ interface SecretHit {
132
+ storePath: string;
133
+ line: number;
134
+ rule: string;
135
+ /** The matched text, already masked — never the raw secret. */
136
+ preview: string;
137
+ }
138
+
139
+ interface CollectResult {
140
+ files: CollectedFile[];
141
+ /** Project files skipped because git already carries them. */
142
+ skippedTracked: string[];
143
+ ctx: TemplateContext;
144
+ }
145
+ /**
146
+ * Decide what constitutes this project's LLM context.
147
+ *
148
+ * Two roots are scanned, and they are treated very differently:
149
+ *
150
+ * - **The project itself** — anything git already tracks is *skipped*. It
151
+ * travels with the code, so copying it into the store would duplicate it
152
+ * and create a second copy that silently drifts out of date.
153
+ * - **The user's global Claude directory** — this is the real payload. The
154
+ * per-project memory, skills, and global instructions live outside the
155
+ * repo and are exactly what never makes it to a second machine today.
156
+ */
157
+ declare function collect(projectRoot: string, cfg: ProjectConfig, tiers: Tier[]): CollectResult;
158
+ declare function summarize(files: CollectedFile[]): Record<Tier, {
159
+ count: number;
160
+ bytes: number;
161
+ }>;
162
+
163
+ declare const STORE_DIR = ".contextsync";
164
+ declare const CONFIG_FILE = "config.json";
165
+ declare const HANDOFF_FILE = "handoff.json";
166
+ /**
167
+ * Paths never collected, regardless of tier or user config.
168
+ *
169
+ * These are credentials, not context. `.credentials.json` holds the live
170
+ * Claude OAuth token and `.claude.json` holds account state; a `.env` is the
171
+ * canonical place a project keeps its secrets. None of them belong in a store
172
+ * that gets committed to a repo.
173
+ */
174
+ declare const HARD_DENY: string[];
175
+ declare const DEFAULT_EXCLUDE: string[];
176
+ declare function defaultConfig(projectRoot: string): ProjectConfig;
177
+ /**
178
+ * Walk up from `start` looking for an existing store, then for a repo root.
179
+ * Mirrors how git resolves its own root so the CLI works from subdirectories.
180
+ */
181
+ declare function findProjectRoot(start?: string): string | null;
182
+ declare function storeDir(projectRoot: string): string;
183
+ declare function configPath(projectRoot: string): string;
184
+ declare function loadConfig(projectRoot: string): ProjectConfig | null;
185
+ declare function saveConfig(projectRoot: string, cfg: ProjectConfig): void;
186
+
187
+ declare const SLASH_COMMAND_PATH = ".claude/commands/context.md";
188
+ /**
189
+ * The `/context` slash command.
190
+ *
191
+ * This exists because of a hard division of labour: the CLI can read the
192
+ * filesystem but has no idea what the conversation was about, and the model
193
+ * knows exactly what the conversation was about but cannot walk the disk. A
194
+ * slash command is a prompt, so it is the one place both are available —
195
+ * the model writes the handoff, then shells out to the CLI to persist it.
196
+ */
197
+ declare const SLASH_COMMAND_BODY = "---\ndescription: Sync this project's LLM context (memory, skills, instructions, handoff)\nargument-hint: \"push | pull | status\"\nallowed-tools: Bash(npx @tricknowtech/context:*), Read, Write\n---\n\nRun the context-sync action requested in: $ARGUMENTS\n(If no argument was given, treat it as `status`.)\n\n## push\n\n1. Write a handoff summary of THIS conversation to `.contextsync/handoff.json`.\n Use exactly this shape \u2014 it is read back verbatim on the other device:\n\n ```json\n {\n \"updatedAt\": \"<ISO 8601 timestamp>\",\n \"goal\": \"<what this session set out to achieve, one or two sentences>\",\n \"decisions\": [\"<decisions already made, so they aren't re-litigated>\"],\n \"openThreads\": [\"<work explicitly left unfinished>\"],\n \"filesTouched\": [\"<repo-relative paths changed this session>\"],\n \"nextStep\": \"<the single next action someone should take>\",\n \"notes\": \"<anything else worth carrying over; optional>\"\n }\n ```\n\n Be specific and factual. Write what was actually decided and actually left\n open \u2014 a vague handoff is worse than none, because it reads as progress.\n Never put credentials, tokens, or .env values in any field.\n\n2. Then run: `npx @tricknowtech/context push`\n\n3. Report what was synced. If the push was refused for possible secrets, show\n the findings and STOP \u2014 do not re-run with `--allow-secrets` unless the\n user explicitly tells you the hits are false positives.\n\n## pull\n\n1. Run: `npx @tricknowtech/context pull`\n2. Read `.contextsync/handoff.json` and summarize for the user, out loud:\n the goal, what was decided, what is still open, and the next step.\n3. If any files were skipped as conflicting, list them and ask before forcing.\n\n## status\n\nRun: `npx @tricknowtech/context status` and summarize the result.\n";
198
+ declare function installSlashCommand(projectRoot: string): {
199
+ path: string;
200
+ written: boolean;
201
+ };
202
+ /**
203
+ * Keep heavyweight, opt-in tiers out of git unless the user asked for them.
204
+ * Artifacts are regenerable derived indexes; committing them by accident is a
205
+ * common way to add tens of MB to a repo that nobody notices until clone time.
206
+ */
207
+ declare function ensureGitignoreEntries(projectRoot: string, artifactsEnabled: boolean): string[];
208
+
209
+ declare function scanFiles(files: CollectedFile[]): SecretHit[];
210
+ declare function formatHits(hits: SecretHit[]): string;
211
+
212
+ declare const MANIFEST_FILE = "manifest.json";
213
+ interface ManifestEntry {
214
+ storePath: string;
215
+ restoreTemplate: string;
216
+ tier: Tier;
217
+ sourceRoot: 'project' | 'user';
218
+ size: number;
219
+ }
220
+ interface Manifest {
221
+ version: 1;
222
+ updatedAt: string;
223
+ /** Project root on the machine that wrote this, for diagnostics on restore. */
224
+ writtenFrom: string;
225
+ entries: ManifestEntry[];
226
+ }
227
+ interface RestoreResult {
228
+ restored: string[];
229
+ skipped: string[];
230
+ }
231
+ /**
232
+ * Persistence backend for collected context.
233
+ *
234
+ * LocalStore is the only implementation today. A CloudStore will implement the
235
+ * same surface over content-addressed blobs, which is why write/read/restore
236
+ * are expressed in terms of CollectedFile/Manifest rather than file paths.
237
+ */
238
+ interface Store {
239
+ write(files: CollectedFile[], projectRoot: string): Manifest;
240
+ readManifest(): Manifest | null;
241
+ restore(ctx: TemplateContext, opts?: {
242
+ force?: boolean;
243
+ }): RestoreResult;
244
+ readHandoff(): Handoff | null;
245
+ writeHandoff(handoff: Handoff): void;
246
+ }
247
+ /**
248
+ * Stores context as plain mirrored files inside the repo.
249
+ *
250
+ * Deliberately *not* content-addressed. Git already does content-addressing
251
+ * and dedup internally, so a blob store nested inside a repo would be
252
+ * redundant, unreviewable in a pull request, and unmergeable on conflict.
253
+ * Plain files diff cleanly and merge with git's normal machinery — which is
254
+ * the entire reason local mode needs no conflict-resolution layer of its own.
255
+ */
256
+ declare class LocalStore implements Store {
257
+ private readonly projectRoot;
258
+ constructor(projectRoot: string);
259
+ private get dir();
260
+ write(files: CollectedFile[], projectRoot: string): Manifest;
261
+ readManifest(): Manifest | null;
262
+ restore(ctx: TemplateContext, opts?: {
263
+ force?: boolean;
264
+ }): RestoreResult;
265
+ readHandoff(): Handoff | null;
266
+ writeHandoff(handoff: Handoff): void;
267
+ }
268
+
269
+ export { ALL_TIERS, CONFIG_FILE, type CollectResult, type CollectedFile, DEFAULT_EXCLUDE, HANDOFF_FILE, HARD_DENY, type Handoff, LOCAL_TIERS, LocalStore, MANIFEST_FILE, type Manifest, type ManifestEntry, type ProjectConfig, type RemoteConfig, type RestoreResult, SLASH_COMMAND_BODY, SLASH_COMMAND_PATH, STORE_DIR, type SecretHit, type SourceRoot, type Store, type TemplateContext, type Tier, collect, configPath, cwdKey, defaultConfig, ensureGitignoreEntries, findProjectRoot, formatBytes, formatHits, gitTrackedSet, installSlashCommand, isGitRepo, loadConfig, makeTemplate, matchesAny, resolveTemplate, saveConfig, scanFiles, storeDir, summarize, userClaudeDir, walk };
@@ -0,0 +1,269 @@
1
+ /** Root of the user's global Claude directory (~/.claude), overridable for tests. */
2
+ declare function userClaudeDir(): string;
3
+ /**
4
+ * Claude Code keys its per-project directories by the working directory with
5
+ * every non-alphanumeric character folded to a dash — `/root/my-project`
6
+ * becomes `-root-my-project`. Restoring on a machine where the repo lives at
7
+ * a different path means recomputing this key, which is why nothing may hard-
8
+ * code it.
9
+ */
10
+ declare function cwdKey(absPath: string): string;
11
+ /**
12
+ * Placeholders that make a restore path portable across machines.
13
+ *
14
+ * `cwdKey` is the important one: Claude Code's per-project directories are
15
+ * named after the absolute project path, so a path captured on one machine
16
+ * embeds that machine's layout. Templating the key out and recomputing it at
17
+ * restore time is what lets the same context land correctly in a repo cloned
18
+ * to a different directory.
19
+ */
20
+ interface TemplateContext {
21
+ userClaude: string;
22
+ project: string;
23
+ cwdKey: string;
24
+ }
25
+ declare function makeTemplate(absPath: string, ctx: TemplateContext): string;
26
+ declare function resolveTemplate(template: string, ctx: TemplateContext): string;
27
+ /**
28
+ * Absolute paths git already tracks in this repo. Anything in here is skipped
29
+ * by the collector: it travels with the code already, and copying it into the
30
+ * store would duplicate it and create two sources of truth that drift.
31
+ *
32
+ * Returns an empty set when not in a git repo (or git is unavailable), which
33
+ * correctly degrades to "copy everything".
34
+ */
35
+ declare function gitTrackedSet(root: string): Set<string>;
36
+ declare function isGitRepo(root: string): boolean;
37
+ declare function matchesAny(relPath: string, patterns: string[]): boolean;
38
+ interface WalkOptions {
39
+ /** Patterns (relative to `root`) to skip entirely. */
40
+ exclude?: string[];
41
+ /** Hard cap so a mis-pointed root can't hang the CLI. */
42
+ maxFiles?: number;
43
+ }
44
+ /** Recursively list files under `root`, returning absolute paths. */
45
+ declare function walk(root: string, opts?: WalkOptions): string[];
46
+ declare function formatBytes(n: number): string;
47
+
48
+ /**
49
+ * Shared types for the context-sync engine.
50
+ *
51
+ * The collector and the stores are deliberately decoupled: the collector
52
+ * decides *what* is context (tier rules, git-tracked detection, secret
53
+ * scanning), a Store decides *how* it is persisted. LocalStore writes plain
54
+ * mirrored files into the repo; a future CloudStore writes content-addressed
55
+ * blobs. Both consume the same CollectedFile[].
56
+ */
57
+ /** Which slice of context a file belongs to. */
58
+ type Tier = 'core' | 'handoff' | 'artifacts' | 'transcripts';
59
+ declare const ALL_TIERS: Tier[];
60
+ /**
61
+ * Tiers a local (in-repo) store will accept. Transcripts are excluded on
62
+ * purpose — they run to hundreds of MB and are append-only, so committing
63
+ * them would permanently bloat every clone of the repo, and they carry the
64
+ * largest secret-leak surface (full tool output, including anything read
65
+ * out of a .env).
66
+ */
67
+ declare const LOCAL_TIERS: Tier[];
68
+ /** Where a file was collected from — decides how it is restored. */
69
+ type SourceRoot =
70
+ /** Inside the project/repo itself. */
71
+ 'project'
72
+ /** Under the user's global Claude directory (~/.claude). */
73
+ | 'user';
74
+ interface CollectedFile {
75
+ /**
76
+ * Path *within the store*, always POSIX-style and relative.
77
+ * e.g. `memory/nginx_upstream_ip_cache.md`, `skills/graphify/SKILL.md`.
78
+ */
79
+ storePath: string;
80
+ /** Absolute path this was read from on this machine. */
81
+ sourcePath: string;
82
+ sourceRoot: SourceRoot;
83
+ tier: Tier;
84
+ size: number;
85
+ /**
86
+ * Absolute path to restore to, expressed as a template with `{userClaude}`
87
+ * and `{project}` placeholders so it can be re-resolved on a machine where
88
+ * the repo (or home dir) lives somewhere else. This is what makes restore
89
+ * portable — see resolveTemplate() in fsutil.ts.
90
+ */
91
+ restoreTemplate: string;
92
+ }
93
+ interface ProjectConfig {
94
+ /** Stable id for this project, used to correlate with a cloud remote. */
95
+ projectId: string;
96
+ name: string;
97
+ /**
98
+ * Absolute project root recorded at init time on the machine that ran it.
99
+ * Restoring elsewhere diffs this against the current root to rewrite the
100
+ * cwd-derived directory keys Claude Code uses (e.g. `-root-my-project`).
101
+ */
102
+ rootHint: string;
103
+ tiers: Tier[];
104
+ /** Extra project-relative dirs to capture as `artifacts`. */
105
+ artifactPaths: string[];
106
+ /** Glob-ish patterns excluded from collection, on top of the hard-coded denylist. */
107
+ exclude: string[];
108
+ remotes: Record<string, RemoteConfig>;
109
+ }
110
+ interface RemoteConfig {
111
+ type: 'tricknowtech';
112
+ url: string;
113
+ e2ee: boolean;
114
+ }
115
+ interface Handoff {
116
+ /** ISO timestamp written by the slash command. */
117
+ updatedAt: string;
118
+ /** What the session was trying to achieve. */
119
+ goal: string;
120
+ /** Decisions already made, so they aren't re-litigated on the next device. */
121
+ decisions: string[];
122
+ /** Work explicitly left unfinished. */
123
+ openThreads: string[];
124
+ /** Files touched, for fast re-orientation. */
125
+ filesTouched: string[];
126
+ /** The single next action to take. */
127
+ nextStep: string;
128
+ /** Free-form notes the model wants to carry over. */
129
+ notes?: string;
130
+ }
131
+ interface SecretHit {
132
+ storePath: string;
133
+ line: number;
134
+ rule: string;
135
+ /** The matched text, already masked — never the raw secret. */
136
+ preview: string;
137
+ }
138
+
139
+ interface CollectResult {
140
+ files: CollectedFile[];
141
+ /** Project files skipped because git already carries them. */
142
+ skippedTracked: string[];
143
+ ctx: TemplateContext;
144
+ }
145
+ /**
146
+ * Decide what constitutes this project's LLM context.
147
+ *
148
+ * Two roots are scanned, and they are treated very differently:
149
+ *
150
+ * - **The project itself** — anything git already tracks is *skipped*. It
151
+ * travels with the code, so copying it into the store would duplicate it
152
+ * and create a second copy that silently drifts out of date.
153
+ * - **The user's global Claude directory** — this is the real payload. The
154
+ * per-project memory, skills, and global instructions live outside the
155
+ * repo and are exactly what never makes it to a second machine today.
156
+ */
157
+ declare function collect(projectRoot: string, cfg: ProjectConfig, tiers: Tier[]): CollectResult;
158
+ declare function summarize(files: CollectedFile[]): Record<Tier, {
159
+ count: number;
160
+ bytes: number;
161
+ }>;
162
+
163
+ declare const STORE_DIR = ".contextsync";
164
+ declare const CONFIG_FILE = "config.json";
165
+ declare const HANDOFF_FILE = "handoff.json";
166
+ /**
167
+ * Paths never collected, regardless of tier or user config.
168
+ *
169
+ * These are credentials, not context. `.credentials.json` holds the live
170
+ * Claude OAuth token and `.claude.json` holds account state; a `.env` is the
171
+ * canonical place a project keeps its secrets. None of them belong in a store
172
+ * that gets committed to a repo.
173
+ */
174
+ declare const HARD_DENY: string[];
175
+ declare const DEFAULT_EXCLUDE: string[];
176
+ declare function defaultConfig(projectRoot: string): ProjectConfig;
177
+ /**
178
+ * Walk up from `start` looking for an existing store, then for a repo root.
179
+ * Mirrors how git resolves its own root so the CLI works from subdirectories.
180
+ */
181
+ declare function findProjectRoot(start?: string): string | null;
182
+ declare function storeDir(projectRoot: string): string;
183
+ declare function configPath(projectRoot: string): string;
184
+ declare function loadConfig(projectRoot: string): ProjectConfig | null;
185
+ declare function saveConfig(projectRoot: string, cfg: ProjectConfig): void;
186
+
187
+ declare const SLASH_COMMAND_PATH = ".claude/commands/context.md";
188
+ /**
189
+ * The `/context` slash command.
190
+ *
191
+ * This exists because of a hard division of labour: the CLI can read the
192
+ * filesystem but has no idea what the conversation was about, and the model
193
+ * knows exactly what the conversation was about but cannot walk the disk. A
194
+ * slash command is a prompt, so it is the one place both are available —
195
+ * the model writes the handoff, then shells out to the CLI to persist it.
196
+ */
197
+ declare const SLASH_COMMAND_BODY = "---\ndescription: Sync this project's LLM context (memory, skills, instructions, handoff)\nargument-hint: \"push | pull | status\"\nallowed-tools: Bash(npx @tricknowtech/context:*), Read, Write\n---\n\nRun the context-sync action requested in: $ARGUMENTS\n(If no argument was given, treat it as `status`.)\n\n## push\n\n1. Write a handoff summary of THIS conversation to `.contextsync/handoff.json`.\n Use exactly this shape \u2014 it is read back verbatim on the other device:\n\n ```json\n {\n \"updatedAt\": \"<ISO 8601 timestamp>\",\n \"goal\": \"<what this session set out to achieve, one or two sentences>\",\n \"decisions\": [\"<decisions already made, so they aren't re-litigated>\"],\n \"openThreads\": [\"<work explicitly left unfinished>\"],\n \"filesTouched\": [\"<repo-relative paths changed this session>\"],\n \"nextStep\": \"<the single next action someone should take>\",\n \"notes\": \"<anything else worth carrying over; optional>\"\n }\n ```\n\n Be specific and factual. Write what was actually decided and actually left\n open \u2014 a vague handoff is worse than none, because it reads as progress.\n Never put credentials, tokens, or .env values in any field.\n\n2. Then run: `npx @tricknowtech/context push`\n\n3. Report what was synced. If the push was refused for possible secrets, show\n the findings and STOP \u2014 do not re-run with `--allow-secrets` unless the\n user explicitly tells you the hits are false positives.\n\n## pull\n\n1. Run: `npx @tricknowtech/context pull`\n2. Read `.contextsync/handoff.json` and summarize for the user, out loud:\n the goal, what was decided, what is still open, and the next step.\n3. If any files were skipped as conflicting, list them and ask before forcing.\n\n## status\n\nRun: `npx @tricknowtech/context status` and summarize the result.\n";
198
+ declare function installSlashCommand(projectRoot: string): {
199
+ path: string;
200
+ written: boolean;
201
+ };
202
+ /**
203
+ * Keep heavyweight, opt-in tiers out of git unless the user asked for them.
204
+ * Artifacts are regenerable derived indexes; committing them by accident is a
205
+ * common way to add tens of MB to a repo that nobody notices until clone time.
206
+ */
207
+ declare function ensureGitignoreEntries(projectRoot: string, artifactsEnabled: boolean): string[];
208
+
209
+ declare function scanFiles(files: CollectedFile[]): SecretHit[];
210
+ declare function formatHits(hits: SecretHit[]): string;
211
+
212
+ declare const MANIFEST_FILE = "manifest.json";
213
+ interface ManifestEntry {
214
+ storePath: string;
215
+ restoreTemplate: string;
216
+ tier: Tier;
217
+ sourceRoot: 'project' | 'user';
218
+ size: number;
219
+ }
220
+ interface Manifest {
221
+ version: 1;
222
+ updatedAt: string;
223
+ /** Project root on the machine that wrote this, for diagnostics on restore. */
224
+ writtenFrom: string;
225
+ entries: ManifestEntry[];
226
+ }
227
+ interface RestoreResult {
228
+ restored: string[];
229
+ skipped: string[];
230
+ }
231
+ /**
232
+ * Persistence backend for collected context.
233
+ *
234
+ * LocalStore is the only implementation today. A CloudStore will implement the
235
+ * same surface over content-addressed blobs, which is why write/read/restore
236
+ * are expressed in terms of CollectedFile/Manifest rather than file paths.
237
+ */
238
+ interface Store {
239
+ write(files: CollectedFile[], projectRoot: string): Manifest;
240
+ readManifest(): Manifest | null;
241
+ restore(ctx: TemplateContext, opts?: {
242
+ force?: boolean;
243
+ }): RestoreResult;
244
+ readHandoff(): Handoff | null;
245
+ writeHandoff(handoff: Handoff): void;
246
+ }
247
+ /**
248
+ * Stores context as plain mirrored files inside the repo.
249
+ *
250
+ * Deliberately *not* content-addressed. Git already does content-addressing
251
+ * and dedup internally, so a blob store nested inside a repo would be
252
+ * redundant, unreviewable in a pull request, and unmergeable on conflict.
253
+ * Plain files diff cleanly and merge with git's normal machinery — which is
254
+ * the entire reason local mode needs no conflict-resolution layer of its own.
255
+ */
256
+ declare class LocalStore implements Store {
257
+ private readonly projectRoot;
258
+ constructor(projectRoot: string);
259
+ private get dir();
260
+ write(files: CollectedFile[], projectRoot: string): Manifest;
261
+ readManifest(): Manifest | null;
262
+ restore(ctx: TemplateContext, opts?: {
263
+ force?: boolean;
264
+ }): RestoreResult;
265
+ readHandoff(): Handoff | null;
266
+ writeHandoff(handoff: Handoff): void;
267
+ }
268
+
269
+ export { ALL_TIERS, CONFIG_FILE, type CollectResult, type CollectedFile, DEFAULT_EXCLUDE, HANDOFF_FILE, HARD_DENY, type Handoff, LOCAL_TIERS, LocalStore, MANIFEST_FILE, type Manifest, type ManifestEntry, type ProjectConfig, type RemoteConfig, type RestoreResult, SLASH_COMMAND_BODY, SLASH_COMMAND_PATH, STORE_DIR, type SecretHit, type SourceRoot, type Store, type TemplateContext, type Tier, collect, configPath, cwdKey, defaultConfig, ensureGitignoreEntries, findProjectRoot, formatBytes, formatHits, gitTrackedSet, installSlashCommand, isGitRepo, loadConfig, makeTemplate, matchesAny, resolveTemplate, saveConfig, scanFiles, storeDir, summarize, userClaudeDir, walk };
package/dist/index.js ADDED
@@ -0,0 +1,68 @@
1
+ import {
2
+ ALL_TIERS,
3
+ CONFIG_FILE,
4
+ DEFAULT_EXCLUDE,
5
+ HANDOFF_FILE,
6
+ HARD_DENY,
7
+ LOCAL_TIERS,
8
+ LocalStore,
9
+ MANIFEST_FILE,
10
+ SLASH_COMMAND_BODY,
11
+ SLASH_COMMAND_PATH,
12
+ STORE_DIR,
13
+ collect,
14
+ configPath,
15
+ cwdKey,
16
+ defaultConfig,
17
+ ensureGitignoreEntries,
18
+ findProjectRoot,
19
+ formatBytes,
20
+ formatHits,
21
+ gitTrackedSet,
22
+ installSlashCommand,
23
+ isGitRepo,
24
+ loadConfig,
25
+ makeTemplate,
26
+ matchesAny,
27
+ resolveTemplate,
28
+ saveConfig,
29
+ scanFiles,
30
+ storeDir,
31
+ summarize,
32
+ userClaudeDir,
33
+ walk
34
+ } from "./chunk-DEH5MUFT.js";
35
+ export {
36
+ ALL_TIERS,
37
+ CONFIG_FILE,
38
+ DEFAULT_EXCLUDE,
39
+ HANDOFF_FILE,
40
+ HARD_DENY,
41
+ LOCAL_TIERS,
42
+ LocalStore,
43
+ MANIFEST_FILE,
44
+ SLASH_COMMAND_BODY,
45
+ SLASH_COMMAND_PATH,
46
+ STORE_DIR,
47
+ collect,
48
+ configPath,
49
+ cwdKey,
50
+ defaultConfig,
51
+ ensureGitignoreEntries,
52
+ findProjectRoot,
53
+ formatBytes,
54
+ formatHits,
55
+ gitTrackedSet,
56
+ installSlashCommand,
57
+ isGitRepo,
58
+ loadConfig,
59
+ makeTemplate,
60
+ matchesAny,
61
+ resolveTemplate,
62
+ saveConfig,
63
+ scanFiles,
64
+ storeDir,
65
+ summarize,
66
+ userClaudeDir,
67
+ walk
68
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@tricknowtech/context",
3
+ "version": "0.1.0",
4
+ "description": "Carry a project's LLM context — memory, skills, instructions and session handoff — between machines. Local-first, commit it to your repo.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "bin": {
17
+ "ctx": "./dist/cli.js",
18
+ "tricknowtech-context": "./dist/cli.js"
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts --clean",
26
+ "test": "node --import tsx --test src/*.test.ts",
27
+ "typecheck": "tsc --noEmit",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "tricknowtech",
32
+ "llm",
33
+ "context",
34
+ "claude",
35
+ "ai",
36
+ "sync",
37
+ "memory"
38
+ ],
39
+ "author": "Tricknowtech",
40
+ "license": "ISC",
41
+ "homepage": "https://tricknow.tech",
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.10.0",
47
+ "tsup": "^8.3.0",
48
+ "tsx": "^4.19.0",
49
+ "typescript": "^5.7.0"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ }
54
+ }