mcp-context-sync 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +253 -49
- package/dist/cli.js.map +1 -1
- package/dist/db/connection.d.ts +10 -1
- package/dist/db/connection.js +59 -23
- package/dist/db/connection.js.map +1 -1
- package/dist/db/json-adapter.d.ts +47 -0
- package/dist/db/json-adapter.js +235 -0
- package/dist/db/json-adapter.js.map +1 -0
- package/dist/db/sqlite-adapter.d.ts +37 -0
- package/dist/db/sqlite-adapter.js +78 -0
- package/dist/db/sqlite-adapter.js.map +1 -0
- package/dist/db/storage.d.ts +41 -0
- package/dist/db/storage.js +9 -0
- package/dist/db/storage.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/resources/project-decisions.d.ts +2 -2
- package/dist/resources/project-decisions.js +3 -4
- package/dist/resources/project-decisions.js.map +1 -1
- package/dist/resources/project-snapshot.d.ts +2 -2
- package/dist/resources/project-snapshot.js +3 -4
- package/dist/resources/project-snapshot.js.map +1 -1
- package/dist/resources/status.d.ts +2 -2
- package/dist/resources/status.js +1 -2
- package/dist/resources/status.js.map +1 -1
- package/dist/tools/amend-snapshot.d.ts +2 -2
- package/dist/tools/amend-snapshot.js +9 -10
- package/dist/tools/amend-snapshot.js.map +1 -1
- package/dist/tools/get-history.d.ts +2 -2
- package/dist/tools/get-history.js +3 -4
- package/dist/tools/get-history.js.map +1 -1
- package/dist/tools/list-projects.d.ts +2 -2
- package/dist/tools/list-projects.js +1 -2
- package/dist/tools/list-projects.js.map +1 -1
- package/dist/tools/log-decision.d.ts +2 -2
- package/dist/tools/log-decision.js +2 -3
- package/dist/tools/log-decision.js.map +1 -1
- package/dist/tools/resume.d.ts +2 -2
- package/dist/tools/resume.js +7 -8
- package/dist/tools/resume.js.map +1 -1
- package/dist/tools/search-snapshots.d.ts +2 -2
- package/dist/tools/search-snapshots.js +2 -3
- package/dist/tools/search-snapshots.js.map +1 -1
- package/dist/tools/sync.d.ts +2 -2
- package/dist/tools/sync.js +5 -6
- package/dist/tools/sync.js.map +1 -1
- package/package.json +4 -2
- package/scripts/postinstall.cjs +14 -34
- package/scripts/preinstall.cjs +13 -86
package/dist/cli.js
CHANGED
|
@@ -15,8 +15,7 @@ import { execSync } from 'node:child_process';
|
|
|
15
15
|
import { dirname, join } from 'node:path';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
17
|
import { homedir, platform } from 'node:os';
|
|
18
|
-
import {
|
|
19
|
-
import { upsertProject, getProject, getNextSequenceNumber, insertSnapshot, insertDecision, insertHandoff, updateProjectSyncStats, getLatestSnapshot, getProjectAgents, listProjects, getHandoffsByProject, } from './db/queries.js';
|
|
18
|
+
import { getStorage, closeStorage } from './db/connection.js';
|
|
20
19
|
import { projectIdFromPath, normalizePath, displayNameFromPath, } from './lib/project-id.js';
|
|
21
20
|
import { generateId } from './lib/uuid.js';
|
|
22
21
|
import { renderSnapshotMarkdown, renderSnapshotBrief } from './lib/snapshot-renderer.js';
|
|
@@ -60,7 +59,7 @@ function readJsonInput() {
|
|
|
60
59
|
* Returns the matched normalized path, or null if none found.
|
|
61
60
|
*/
|
|
62
61
|
function resolveProjectDir(db, startDir) {
|
|
63
|
-
const projects = listProjects(
|
|
62
|
+
const projects = db.listProjects();
|
|
64
63
|
if (projects.length === 0)
|
|
65
64
|
return null;
|
|
66
65
|
const knownPaths = new Set(projects.map((p) => p.normalized_path));
|
|
@@ -121,14 +120,14 @@ function cmdResume() {
|
|
|
121
120
|
const wantBrief = argv.includes('--brief');
|
|
122
121
|
// --json and --brief imply --peek (no handoff registration)
|
|
123
122
|
const peek = argv.includes('--peek') || wantJson || wantBrief;
|
|
124
|
-
const db =
|
|
123
|
+
const db = getStorage();
|
|
125
124
|
const projectDir = getEffectiveProjectDir(db);
|
|
126
125
|
const projectId = projectIdFromPath(projectDir);
|
|
127
126
|
const displayName = displayNameFromPath(projectDir);
|
|
128
|
-
const project = getProject(
|
|
127
|
+
const project = db.getProject(projectId);
|
|
129
128
|
if (!project) {
|
|
130
129
|
if (!peek) {
|
|
131
|
-
const all = listProjects(
|
|
130
|
+
const all = db.listProjects();
|
|
132
131
|
console.error(`No data found for "${displayName}" (${normalizePath(projectDir)})`);
|
|
133
132
|
if (all.length > 0) {
|
|
134
133
|
console.error('\nAvailable projects:');
|
|
@@ -136,20 +135,20 @@ function cmdResume() {
|
|
|
136
135
|
console.error(` ${p.normalized_path} (${p.snapshot_count} snapshots)`);
|
|
137
136
|
}
|
|
138
137
|
}
|
|
139
|
-
|
|
138
|
+
closeStorage();
|
|
140
139
|
process.exit(peek ? 0 : 1);
|
|
141
140
|
}
|
|
142
|
-
const snapshot = getLatestSnapshot(
|
|
141
|
+
const snapshot = db.getLatestSnapshot(projectId);
|
|
143
142
|
if (!snapshot) {
|
|
144
143
|
if (!peek)
|
|
145
144
|
console.error(`Project "${displayName}" exists but has no snapshots.`);
|
|
146
|
-
|
|
145
|
+
closeStorage();
|
|
147
146
|
process.exit(peek ? 0 : 1);
|
|
148
147
|
}
|
|
149
148
|
if (!peek) {
|
|
150
149
|
const now = new Date().toISOString();
|
|
151
150
|
db.transaction(() => {
|
|
152
|
-
insertHandoff(
|
|
151
|
+
db.insertHandoff({
|
|
153
152
|
id: generateId(),
|
|
154
153
|
project_id: projectId,
|
|
155
154
|
snapshot_id: snapshot.id,
|
|
@@ -159,7 +158,7 @@ function cmdResume() {
|
|
|
159
158
|
to_timestamp: now,
|
|
160
159
|
created_at: now,
|
|
161
160
|
});
|
|
162
|
-
updateProjectSyncStats(
|
|
161
|
+
db.updateProjectSyncStats(projectId);
|
|
163
162
|
})();
|
|
164
163
|
}
|
|
165
164
|
// Output priority: --json > --brief > default (full markdown)
|
|
@@ -170,14 +169,14 @@ function cmdResume() {
|
|
|
170
169
|
console.log(renderSnapshotBrief(snapshot, displayName));
|
|
171
170
|
}
|
|
172
171
|
else {
|
|
173
|
-
const agents = getProjectAgents(
|
|
172
|
+
const agents = db.getProjectAgents(projectId);
|
|
174
173
|
console.log(renderSnapshotMarkdown(snapshot, displayName, {
|
|
175
174
|
totalSnapshots: project.snapshot_count,
|
|
176
175
|
totalHandoffs: project.handoff_count + (peek ? 0 : 1),
|
|
177
176
|
agents,
|
|
178
177
|
}));
|
|
179
178
|
}
|
|
180
|
-
|
|
179
|
+
closeStorage();
|
|
181
180
|
}
|
|
182
181
|
// ---------------------------------------------------------------------------
|
|
183
182
|
// sync
|
|
@@ -186,16 +185,16 @@ function cmdResume() {
|
|
|
186
185
|
* Save the snapshot to the database (shared by cmdSync and cmdSyncInteractive).
|
|
187
186
|
*/
|
|
188
187
|
function saveSnapshot(projectDir, agent, summary, tasksCompleted, tasksRemaining, decisions, filesModified, blockers, nextSteps, tags) {
|
|
189
|
-
const db =
|
|
188
|
+
const db = getStorage();
|
|
190
189
|
const projectId = projectIdFromPath(projectDir);
|
|
191
190
|
const normalized = normalizePath(projectDir);
|
|
192
191
|
const displayName = displayNameFromPath(projectDir);
|
|
193
192
|
const now = new Date().toISOString();
|
|
194
193
|
const { snapshotId, seqNum } = db.transaction(() => {
|
|
195
|
-
upsertProject(
|
|
196
|
-
const seqNum = getNextSequenceNumber(
|
|
194
|
+
db.upsertProject(projectId, normalized, displayName);
|
|
195
|
+
const seqNum = db.getNextSequenceNumber(projectId);
|
|
197
196
|
const snapshotId = generateId();
|
|
198
|
-
insertSnapshot(
|
|
197
|
+
db.insertSnapshot({
|
|
199
198
|
id: snapshotId,
|
|
200
199
|
project_id: projectId,
|
|
201
200
|
sequence_number: seqNum,
|
|
@@ -211,7 +210,7 @@ function saveSnapshot(projectDir, agent, summary, tasksCompleted, tasksRemaining
|
|
|
211
210
|
created_at: now,
|
|
212
211
|
});
|
|
213
212
|
for (const d of decisions) {
|
|
214
|
-
insertDecision(
|
|
213
|
+
db.insertDecision({
|
|
215
214
|
id: generateId(),
|
|
216
215
|
project_id: projectId,
|
|
217
216
|
snapshot_id: snapshotId,
|
|
@@ -224,10 +223,10 @@ function saveSnapshot(projectDir, agent, summary, tasksCompleted, tasksRemaining
|
|
|
224
223
|
created_at: now,
|
|
225
224
|
});
|
|
226
225
|
}
|
|
227
|
-
updateProjectSyncStats(
|
|
226
|
+
db.updateProjectSyncStats(projectId);
|
|
228
227
|
return { snapshotId, seqNum };
|
|
229
228
|
})();
|
|
230
|
-
|
|
229
|
+
closeStorage();
|
|
231
230
|
return { snapshotId, seqNum, displayName, normalized };
|
|
232
231
|
}
|
|
233
232
|
function cmdSync() {
|
|
@@ -296,9 +295,9 @@ function cmdSyncInteractive() {
|
|
|
296
295
|
return input.split(',').map((s) => s.trim()).filter(Boolean);
|
|
297
296
|
}
|
|
298
297
|
async function run() {
|
|
299
|
-
const db =
|
|
298
|
+
const db = getStorage();
|
|
300
299
|
const projectDir = getEffectiveProjectDir(db);
|
|
301
|
-
|
|
300
|
+
closeStorage();
|
|
302
301
|
const agent = getFlag('agent') ?? 'claude-code';
|
|
303
302
|
const displayName = displayNameFromPath(projectDir);
|
|
304
303
|
console.log('');
|
|
@@ -349,8 +348,8 @@ function cmdSyncInteractive() {
|
|
|
349
348
|
// list
|
|
350
349
|
// ---------------------------------------------------------------------------
|
|
351
350
|
function cmdList() {
|
|
352
|
-
const db =
|
|
353
|
-
const projects = listProjects(
|
|
351
|
+
const db = getStorage();
|
|
352
|
+
const projects = db.listProjects();
|
|
354
353
|
if (projects.length === 0) {
|
|
355
354
|
console.log('No projects synced yet.');
|
|
356
355
|
}
|
|
@@ -363,17 +362,17 @@ function cmdList() {
|
|
|
363
362
|
console.log();
|
|
364
363
|
}
|
|
365
364
|
}
|
|
366
|
-
|
|
365
|
+
closeStorage();
|
|
367
366
|
}
|
|
368
367
|
// ---------------------------------------------------------------------------
|
|
369
368
|
// history
|
|
370
369
|
// ---------------------------------------------------------------------------
|
|
371
370
|
function cmdHistory() {
|
|
372
371
|
const limit = parseInt(getFlag('limit') ?? '10', 10);
|
|
373
|
-
const db =
|
|
372
|
+
const db = getStorage();
|
|
374
373
|
const projectDir = getEffectiveProjectDir(db);
|
|
375
374
|
const projectId = projectIdFromPath(projectDir);
|
|
376
|
-
const handoffs = getHandoffsByProject(
|
|
375
|
+
const handoffs = db.getHandoffsByProject(projectId, limit);
|
|
377
376
|
if (handoffs.length === 0) {
|
|
378
377
|
console.log('No handoff history for this project.');
|
|
379
378
|
}
|
|
@@ -382,7 +381,7 @@ function cmdHistory() {
|
|
|
382
381
|
console.log(`${h.from_agent} -> ${h.to_agent} (${h.created_at})`);
|
|
383
382
|
}
|
|
384
383
|
}
|
|
385
|
-
|
|
384
|
+
closeStorage();
|
|
386
385
|
}
|
|
387
386
|
// ---------------------------------------------------------------------------
|
|
388
387
|
// auto-sync
|
|
@@ -391,7 +390,7 @@ function cmdAutoSync() {
|
|
|
391
390
|
const agent = getFlag('agent') ?? 'claude-code';
|
|
392
391
|
let db;
|
|
393
392
|
try {
|
|
394
|
-
db =
|
|
393
|
+
db = getStorage();
|
|
395
394
|
}
|
|
396
395
|
catch {
|
|
397
396
|
// DB not accessible — exit silently
|
|
@@ -399,13 +398,13 @@ function cmdAutoSync() {
|
|
|
399
398
|
}
|
|
400
399
|
const projectDir = getEffectiveProjectDir(db);
|
|
401
400
|
const projectId = projectIdFromPath(projectDir);
|
|
402
|
-
const project = getProject(
|
|
401
|
+
const project = db.getProject(projectId);
|
|
403
402
|
if (!project) {
|
|
404
403
|
// No existing project — exit silently, don't create one
|
|
405
|
-
|
|
404
|
+
closeStorage();
|
|
406
405
|
process.exit(0);
|
|
407
406
|
}
|
|
408
|
-
const latestSnapshot = getLatestSnapshot(
|
|
407
|
+
const latestSnapshot = db.getLatestSnapshot(projectId);
|
|
409
408
|
// Carry over remaining tasks from the latest snapshot
|
|
410
409
|
let tasksRemaining = '[]';
|
|
411
410
|
if (latestSnapshot) {
|
|
@@ -414,9 +413,9 @@ function cmdAutoSync() {
|
|
|
414
413
|
const now = new Date().toISOString();
|
|
415
414
|
try {
|
|
416
415
|
db.transaction(() => {
|
|
417
|
-
const seqNum = getNextSequenceNumber(
|
|
416
|
+
const seqNum = db.getNextSequenceNumber(projectId);
|
|
418
417
|
const snapshotId = generateId();
|
|
419
|
-
insertSnapshot(
|
|
418
|
+
db.insertSnapshot({
|
|
420
419
|
id: snapshotId,
|
|
421
420
|
project_id: projectId,
|
|
422
421
|
sequence_number: seqNum,
|
|
@@ -431,13 +430,13 @@ function cmdAutoSync() {
|
|
|
431
430
|
tags: '["auto-sync"]',
|
|
432
431
|
created_at: now,
|
|
433
432
|
});
|
|
434
|
-
updateProjectSyncStats(
|
|
433
|
+
db.updateProjectSyncStats(projectId);
|
|
435
434
|
})();
|
|
436
435
|
}
|
|
437
436
|
catch {
|
|
438
437
|
// Swallow errors — must not fail on session exit
|
|
439
438
|
}
|
|
440
|
-
|
|
439
|
+
closeStorage();
|
|
441
440
|
}
|
|
442
441
|
// ---------------------------------------------------------------------------
|
|
443
442
|
// doctor
|
|
@@ -472,19 +471,28 @@ function cmdDoctor() {
|
|
|
472
471
|
catch {
|
|
473
472
|
console.log('Node.js: unknown \u26A0');
|
|
474
473
|
}
|
|
475
|
-
// 2.
|
|
476
|
-
let
|
|
474
|
+
// 2. Storage backend
|
|
475
|
+
let storageOk = false;
|
|
476
|
+
let backendName = 'unknown';
|
|
477
477
|
try {
|
|
478
|
-
const db =
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
478
|
+
const db = getStorage();
|
|
479
|
+
backendName = db.backend;
|
|
480
|
+
storageOk = true;
|
|
481
|
+
if (backendName === 'sqlite') {
|
|
482
|
+
console.log('Storage backend: SQLite (better-sqlite3) \u2713');
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
console.log('Storage backend: JSON files (fallback) \u26A0');
|
|
486
|
+
issues.push('\u26A0 Using JSON fallback storage. better-sqlite3 is not available.\n' +
|
|
487
|
+
' SQLite is faster and supports FTS search. To enable it:\n' +
|
|
488
|
+
' Fix: Reinstall with Node 20-22 LTS, or install Visual Studio Build Tools on Windows.');
|
|
489
|
+
}
|
|
490
|
+
closeStorage();
|
|
483
491
|
}
|
|
484
492
|
catch (e) {
|
|
485
493
|
const msg = e instanceof Error ? e.message : String(e);
|
|
486
|
-
console.log('
|
|
487
|
-
issues.push(`\u2717
|
|
494
|
+
console.log('Storage backend: FAILED \u2717');
|
|
495
|
+
issues.push(`\u2717 Storage failed to initialize: ${msg}`);
|
|
488
496
|
}
|
|
489
497
|
// 3. SQLite DB
|
|
490
498
|
const dbPath = join(homedir(), '.agents', 'context-sync', 'data', 'context-sync.sqlite');
|
|
@@ -650,14 +658,14 @@ function cmdDoctor() {
|
|
|
650
658
|
}
|
|
651
659
|
// 7. Projects synced
|
|
652
660
|
console.log('\nData:');
|
|
653
|
-
if (
|
|
661
|
+
if (storageOk) {
|
|
654
662
|
try {
|
|
655
|
-
const db =
|
|
656
|
-
const projects = listProjects(
|
|
663
|
+
const db = getStorage();
|
|
664
|
+
const projects = db.listProjects();
|
|
657
665
|
const totalSnapshots = projects.reduce((sum, p) => sum + p.snapshot_count, 0);
|
|
658
666
|
const totalHandoffs = projects.reduce((sum, p) => sum + p.handoff_count, 0);
|
|
659
667
|
console.log(` Projects: ${projects.length} | Snapshots: ${totalSnapshots} | Handoffs: ${totalHandoffs}`);
|
|
660
|
-
|
|
668
|
+
closeStorage();
|
|
661
669
|
}
|
|
662
670
|
catch {
|
|
663
671
|
console.log(' (unable to read database)');
|
|
@@ -690,17 +698,201 @@ function hasMcpServer(obj, serverName) {
|
|
|
690
698
|
const servers = obj.mcpServers;
|
|
691
699
|
return !!servers && serverName in servers;
|
|
692
700
|
}
|
|
701
|
+
// ---------------------------------------------------------------------------
|
|
702
|
+
// Skill templates for /resume-project and /sync-project
|
|
703
|
+
// ---------------------------------------------------------------------------
|
|
704
|
+
function getResumeSkill(agent, mcpStyle) {
|
|
705
|
+
if (mcpStyle === 'direct') {
|
|
706
|
+
// Claude Code — can call MCP tools directly
|
|
707
|
+
return `---
|
|
708
|
+
name: resume-project
|
|
709
|
+
description: Load previous work context from context-sync. Use when starting a session, switching from another agent, or wanting to see what was previously worked on.
|
|
710
|
+
disable-model-invocation: true
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
Load previous work context so you can continue where another agent left off.
|
|
714
|
+
|
|
715
|
+
## Steps
|
|
716
|
+
|
|
717
|
+
1. **Detect environment:**
|
|
718
|
+
- \`projectDir\`: Primary working directory (absolute path to project root)
|
|
719
|
+
- \`agent\`: \`"${agent}"\`
|
|
720
|
+
|
|
721
|
+
2. **Call \`mcp__context-sync__resume\`** with \`{ "projectDir": "<path>", "agent": "${agent}" }\`
|
|
722
|
+
|
|
723
|
+
3. **Present results conversationally:**
|
|
724
|
+
- Short header: project name, previous agent, date, snapshot number
|
|
725
|
+
- Top 5 completed tasks (mention total if more)
|
|
726
|
+
- Remaining tasks (top 5)
|
|
727
|
+
- 1-2 recent decisions
|
|
728
|
+
- \`next_steps\` as the suggested starting point
|
|
729
|
+
|
|
730
|
+
4. **If error** (project not found): relay the error directly — it already lists available projects and suggestions.
|
|
731
|
+
|
|
732
|
+
5. **If MCP tool fails** (connection error): fall back to CLI: \`context-sync resume --agent ${agent}\`
|
|
733
|
+
`;
|
|
734
|
+
}
|
|
735
|
+
// Codex/Gemini — MCP + CLI fallback
|
|
736
|
+
return `---
|
|
737
|
+
name: resume-project
|
|
738
|
+
description: Load previous work context from context-sync. Use when starting a session, switching from another agent, or wanting to see what was previously worked on.
|
|
739
|
+
metadata:
|
|
740
|
+
short-description: Resume context from previous session
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
# Resume Project
|
|
744
|
+
|
|
745
|
+
Load previous work context so you can continue where another agent left off.
|
|
746
|
+
|
|
747
|
+
## Method 1: MCP Tool (preferred)
|
|
748
|
+
|
|
749
|
+
Call the MCP tool named **\`resume\`** from the **\`context-sync\`** server:
|
|
750
|
+
\`\`\`json
|
|
751
|
+
{ "projectDir": "<absolute path to project root>", "agent": "${agent}" }
|
|
752
|
+
\`\`\`
|
|
753
|
+
|
|
754
|
+
## Method 2: CLI (fallback if MCP tool call fails)
|
|
755
|
+
|
|
756
|
+
\`\`\`bash
|
|
757
|
+
context-sync resume --project "<absolute path to project root>" --agent ${agent}
|
|
758
|
+
\`\`\`
|
|
759
|
+
|
|
760
|
+
If \`--project\` is omitted, the current working directory is used automatically.
|
|
761
|
+
|
|
762
|
+
## After loading
|
|
763
|
+
|
|
764
|
+
Present results conversationally:
|
|
765
|
+
- Project name, previous agent, date, snapshot number
|
|
766
|
+
- Completed and remaining tasks (top 5 each)
|
|
767
|
+
- Key decisions
|
|
768
|
+
- \`next_steps\` as suggested starting point
|
|
769
|
+
|
|
770
|
+
If project not found, the output will list available projects.
|
|
771
|
+
`;
|
|
772
|
+
}
|
|
773
|
+
function getSyncSkill(agent, mcpStyle) {
|
|
774
|
+
if (mcpStyle === 'direct') {
|
|
775
|
+
// Claude Code — can call MCP tools directly
|
|
776
|
+
return `---
|
|
777
|
+
name: sync-project
|
|
778
|
+
description: Save a structured context snapshot for handoff to another agent. Use when finishing a session, switching agents, or reaching a checkpoint.
|
|
779
|
+
disable-model-invocation: true
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
Analyze the current session and save a structured snapshot for another agent to resume from.
|
|
783
|
+
|
|
784
|
+
## Steps
|
|
785
|
+
|
|
786
|
+
1. **Detect environment:**
|
|
787
|
+
- \`projectDir\`: Primary working directory (absolute path to project root, not a subdirectory)
|
|
788
|
+
- \`agent\`: \`"${agent}"\`
|
|
789
|
+
|
|
790
|
+
2. **Analyze conversation and extract:**
|
|
791
|
+
- \`summary\`: 1-2 sentences on what was accomplished
|
|
792
|
+
- \`tasksCompleted\`: Specific tasks finished (be precise)
|
|
793
|
+
- \`tasksRemaining\`: Pending work, TODOs, known issues
|
|
794
|
+
- \`decisions\`: Technical choices with \`decision\`, \`reasoning\`, \`alternatives\`
|
|
795
|
+
- \`filesModified\`: Files touched with \`path\`, \`action\` (created/modified/deleted), \`description\`
|
|
796
|
+
- \`blockers\`: Unresolved issues (empty array if none)
|
|
797
|
+
- \`nextSteps\`: Specific actionable instructions for the next agent
|
|
798
|
+
- \`tags\`: Lowercase categories (e.g., \`feature\`, \`backend\`, \`typescript\`)
|
|
799
|
+
|
|
800
|
+
3. **Show compact draft and ask for confirmation:**
|
|
801
|
+
\`\`\`
|
|
802
|
+
Ready to save snapshot for: [project]
|
|
803
|
+
Summary: [summary]
|
|
804
|
+
Completed: [count] | Remaining: [count] | Decisions: [count] | Files: [count]
|
|
805
|
+
Next steps: [brief]
|
|
806
|
+
Save? (yes / no / edit)
|
|
807
|
+
\`\`\`
|
|
808
|
+
|
|
809
|
+
4. **Once confirmed, call \`mcp__context-sync__sync\`** with all extracted data.
|
|
810
|
+
|
|
811
|
+
5. **Confirm** snapshot number, timestamp, and that another agent can use \`/resume-project\` to continue.
|
|
812
|
+
`;
|
|
813
|
+
}
|
|
814
|
+
// Codex/Gemini — MCP + CLI fallback
|
|
815
|
+
return `---
|
|
816
|
+
name: sync-project
|
|
817
|
+
description: Save a structured context snapshot for handoff to another agent. Use when finishing a session, switching agents, or reaching a checkpoint.
|
|
818
|
+
metadata:
|
|
819
|
+
short-description: Save context for agent handoff
|
|
820
|
+
---
|
|
821
|
+
|
|
822
|
+
# Sync Project
|
|
823
|
+
|
|
824
|
+
Analyze the current session and save a structured snapshot for another agent to resume from.
|
|
825
|
+
|
|
826
|
+
## Step 1: Gather data
|
|
827
|
+
|
|
828
|
+
- \`projectDir\`: Current working directory (absolute path to project **root**, not a subdirectory)
|
|
829
|
+
- \`agent\`: \`"${agent}"\`
|
|
830
|
+
- \`summary\`: 1-2 sentences on what was accomplished
|
|
831
|
+
- \`tasksCompleted\`: Specific finished tasks
|
|
832
|
+
- \`tasksRemaining\`: Pending work, TODOs, known issues
|
|
833
|
+
- \`decisions\`: Technical choices with \`decision\`, \`reasoning\`, \`alternatives\`
|
|
834
|
+
- \`filesModified\`: Files touched with \`path\`, \`action\` (created/modified/deleted), \`description\`
|
|
835
|
+
- \`blockers\`: Unresolved issues (empty array if none)
|
|
836
|
+
- \`nextSteps\`: Specific actionable instructions for the next agent
|
|
837
|
+
- \`tags\`: Lowercase categories (e.g., \`feature\`, \`backend\`, \`typescript\`)
|
|
838
|
+
|
|
839
|
+
Show a compact draft and ask for confirmation before saving.
|
|
840
|
+
|
|
841
|
+
## Step 2: Save (pick one method)
|
|
842
|
+
|
|
843
|
+
### Method 1: MCP Tool (preferred)
|
|
844
|
+
|
|
845
|
+
Call the MCP tool named **\`sync\`** from the **\`context-sync\`** server with the JSON above.
|
|
846
|
+
|
|
847
|
+
### Method 2: CLI (fallback if MCP tool call fails)
|
|
848
|
+
|
|
849
|
+
Write the JSON to a temp file first:
|
|
850
|
+
\`\`\`bash
|
|
851
|
+
cat > /tmp/sync.json << 'EOF'
|
|
852
|
+
{"projectDir":"<path>","agent":"${agent}","summary":"...","tasksCompleted":[...],"tasksRemaining":[...],"nextSteps":"..."}
|
|
853
|
+
EOF
|
|
854
|
+
|
|
855
|
+
context-sync sync --file /tmp/sync.json
|
|
856
|
+
\`\`\`
|
|
857
|
+
|
|
858
|
+
## Step 3: Confirm
|
|
859
|
+
|
|
860
|
+
Report the snapshot number and confirm another agent can resume.
|
|
861
|
+
`;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Install skill files for an agent. Returns paths created.
|
|
865
|
+
*/
|
|
866
|
+
function installSkills(skillsDir, agent, mcpStyle) {
|
|
867
|
+
const created = [];
|
|
868
|
+
const skills = [
|
|
869
|
+
{ name: 'resume-project', content: getResumeSkill(agent, mcpStyle) },
|
|
870
|
+
{ name: 'sync-project', content: getSyncSkill(agent, mcpStyle) },
|
|
871
|
+
];
|
|
872
|
+
for (const skill of skills) {
|
|
873
|
+
const dir = join(skillsDir, skill.name);
|
|
874
|
+
const file = join(dir, 'SKILL.md');
|
|
875
|
+
if (existsSync(file))
|
|
876
|
+
continue; // don't overwrite
|
|
877
|
+
ensureDir(dir);
|
|
878
|
+
writeFileSync(file, skill.content, 'utf-8');
|
|
879
|
+
created.push(file);
|
|
880
|
+
}
|
|
881
|
+
return created;
|
|
882
|
+
}
|
|
693
883
|
function installClaude() {
|
|
694
884
|
const label = 'Claude Code';
|
|
695
885
|
// Claude Code can store MCP config in ~/.claude.json OR ~/.claude/settings.json
|
|
696
886
|
const claudeJson = join(homedir(), '.claude.json');
|
|
697
887
|
const settingsJson = join(homedir(), '.claude', 'settings.json');
|
|
888
|
+
const skillsDir = join(homedir(), '.claude', 'skills');
|
|
698
889
|
// Check if already configured in either location
|
|
699
890
|
for (const p of [claudeJson, settingsJson]) {
|
|
700
891
|
try {
|
|
701
892
|
if (existsSync(p)) {
|
|
702
893
|
const cfg = readJsonFile(p);
|
|
703
894
|
if (hasMcpServer(cfg, 'context-sync')) {
|
|
895
|
+
installSkills(skillsDir, 'claude-code', 'direct'); // ensure skills even if MCP skipped
|
|
704
896
|
return { label, path: p, status: 'skipped' };
|
|
705
897
|
}
|
|
706
898
|
}
|
|
@@ -720,6 +912,9 @@ function installClaude() {
|
|
|
720
912
|
env: {},
|
|
721
913
|
};
|
|
722
914
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
915
|
+
// Install skills
|
|
916
|
+
const skillsDir = join(homedir(), '.claude', 'skills');
|
|
917
|
+
installSkills(skillsDir, 'claude-code', 'direct');
|
|
723
918
|
return { label, path: configPath, status: 'configured' };
|
|
724
919
|
}
|
|
725
920
|
catch (err) {
|
|
@@ -730,12 +925,15 @@ function installCodex() {
|
|
|
730
925
|
const codexDir = join(homedir(), '.codex');
|
|
731
926
|
const mcpJsonPath = join(codexDir, 'mcp.json');
|
|
732
927
|
const configTomlPath = join(codexDir, 'config.toml');
|
|
928
|
+
const skillsDir = join(codexDir, 'skills');
|
|
733
929
|
const label = 'Codex';
|
|
734
930
|
try {
|
|
931
|
+
const doSkills = () => installSkills(skillsDir, 'codex', 'cli');
|
|
735
932
|
// Check mcp.json first
|
|
736
933
|
if (existsSync(mcpJsonPath)) {
|
|
737
934
|
const config = readJsonFile(mcpJsonPath);
|
|
738
935
|
if (hasMcpServer(config, 'context-sync')) {
|
|
936
|
+
doSkills(); // ensure skills even if MCP already configured
|
|
739
937
|
return { label, path: mcpJsonPath, status: 'skipped' };
|
|
740
938
|
}
|
|
741
939
|
if (!config.mcpServers)
|
|
@@ -744,16 +942,19 @@ function installCodex() {
|
|
|
744
942
|
command: 'mcp-context-sync',
|
|
745
943
|
};
|
|
746
944
|
writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
945
|
+
doSkills();
|
|
747
946
|
return { label, path: mcpJsonPath, status: 'configured' };
|
|
748
947
|
}
|
|
749
948
|
// Check config.toml
|
|
750
949
|
if (existsSync(configTomlPath)) {
|
|
751
950
|
const tomlContent = readFileSync(configTomlPath, 'utf-8');
|
|
752
951
|
if (tomlContent.includes('[mcp_servers.context-sync]')) {
|
|
952
|
+
doSkills();
|
|
753
953
|
return { label, path: configTomlPath, status: 'skipped' };
|
|
754
954
|
}
|
|
755
955
|
const appendBlock = '\n[mcp_servers.context-sync]\ncommand = "mcp-context-sync"\n';
|
|
756
956
|
writeFileSync(configTomlPath, tomlContent + appendBlock, 'utf-8');
|
|
957
|
+
doSkills();
|
|
757
958
|
return { label, path: configTomlPath, status: 'configured' };
|
|
758
959
|
}
|
|
759
960
|
// Neither exists — create mcp.json
|
|
@@ -764,6 +965,7 @@ function installCodex() {
|
|
|
764
965
|
},
|
|
765
966
|
};
|
|
766
967
|
writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
968
|
+
doSkills();
|
|
767
969
|
return { label, path: mcpJsonPath, status: 'configured' };
|
|
768
970
|
}
|
|
769
971
|
catch (err) {
|
|
@@ -772,9 +974,11 @@ function installCodex() {
|
|
|
772
974
|
}
|
|
773
975
|
function installGemini() {
|
|
774
976
|
const configPath = join(homedir(), '.gemini', 'antigravity', 'mcp_config.json');
|
|
977
|
+
const skillsDir = join(homedir(), '.gemini', 'skills');
|
|
775
978
|
const label = 'Gemini';
|
|
776
979
|
try {
|
|
777
980
|
ensureDir(dirname(configPath));
|
|
981
|
+
installSkills(skillsDir, 'gemini-cli', 'cli');
|
|
778
982
|
const config = existsSync(configPath) ? readJsonFile(configPath) : {};
|
|
779
983
|
if (hasMcpServer(config, 'context-sync')) {
|
|
780
984
|
return { label, path: configPath, status: 'skipped' };
|