@mod-computer/cli 0.2.4 → 0.2.5
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/package.json +3 -3
- package/dist/app.js +0 -227
- package/dist/cli.bundle.js.map +0 -7
- package/dist/cli.js +0 -132
- package/dist/commands/add.js +0 -245
- package/dist/commands/agents-run.js +0 -71
- package/dist/commands/auth.js +0 -259
- package/dist/commands/branch.js +0 -1411
- package/dist/commands/claude-sync.js +0 -772
- package/dist/commands/comment.js +0 -568
- package/dist/commands/diff.js +0 -182
- package/dist/commands/index.js +0 -73
- package/dist/commands/init.js +0 -597
- package/dist/commands/ls.js +0 -135
- package/dist/commands/members.js +0 -687
- package/dist/commands/mv.js +0 -282
- package/dist/commands/recover.js +0 -207
- package/dist/commands/rm.js +0 -257
- package/dist/commands/spec.js +0 -386
- package/dist/commands/status.js +0 -296
- package/dist/commands/sync.js +0 -119
- package/dist/commands/trace.js +0 -1752
- package/dist/commands/workspace.js +0 -447
- package/dist/components/conflict-resolution-ui.js +0 -120
- package/dist/components/messages.js +0 -5
- package/dist/components/thread.js +0 -8
- package/dist/config/features.js +0 -83
- package/dist/containers/branches-container.js +0 -140
- package/dist/containers/directory-container.js +0 -92
- package/dist/containers/thread-container.js +0 -214
- package/dist/containers/threads-container.js +0 -27
- package/dist/containers/workspaces-container.js +0 -27
- package/dist/daemon/conflict-resolution.js +0 -172
- package/dist/daemon/content-hash.js +0 -31
- package/dist/daemon/file-sync.js +0 -985
- package/dist/daemon/index.js +0 -203
- package/dist/daemon/mime-types.js +0 -166
- package/dist/daemon/offline-queue.js +0 -211
- package/dist/daemon/path-utils.js +0 -64
- package/dist/daemon/share-policy.js +0 -83
- package/dist/daemon/wasm-errors.js +0 -189
- package/dist/daemon/worker.js +0 -557
- package/dist/daemon-worker.js +0 -258
- package/dist/errors/workspace-errors.js +0 -48
- package/dist/lib/auth-server.js +0 -216
- package/dist/lib/browser.js +0 -35
- package/dist/lib/diff.js +0 -284
- package/dist/lib/formatters.js +0 -204
- package/dist/lib/git.js +0 -137
- package/dist/lib/local-fs.js +0 -201
- package/dist/lib/prompts.js +0 -56
- package/dist/lib/storage.js +0 -213
- package/dist/lib/trace-formatters.js +0 -314
- package/dist/services/add-service.js +0 -554
- package/dist/services/add-validation.js +0 -124
- package/dist/services/automatic-file-tracker.js +0 -303
- package/dist/services/cli-orchestrator.js +0 -227
- package/dist/services/feature-flags.js +0 -187
- package/dist/services/file-import-service.js +0 -283
- package/dist/services/file-transformation-service.js +0 -218
- package/dist/services/logger.js +0 -44
- package/dist/services/mod-config.js +0 -67
- package/dist/services/modignore-service.js +0 -328
- package/dist/services/sync-daemon.js +0 -244
- package/dist/services/thread-notification-service.js +0 -50
- package/dist/services/thread-service.js +0 -147
- package/dist/stores/use-directory-store.js +0 -96
- package/dist/stores/use-threads-store.js +0 -46
- package/dist/stores/use-workspaces-store.js +0 -54
- package/dist/types/add-types.js +0 -99
- package/dist/types/config.js +0 -16
- package/dist/types/index.js +0 -2
- package/dist/types/workspace-connection.js +0 -53
- package/dist/types.js +0 -1
package/dist/commands/init.js
DELETED
|
@@ -1,597 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// glassware[type="implementation", id="cli-init-command--da781f68", requirements="requirement-cli-init-ux-1--1e5666e7,requirement-cli-init-ux-2--b69b045f,requirement-cli-init-ux-3--3dde4846,requirement-cli-init-ux-4--140d9249,requirement-cli-init-ux-5a--09a5bdab,requirement-cli-init-ux-5b--4cb7bb13,requirement-cli-init-ux-6--1627332e,requirement-cli-init-ux-7--97fe5eff,requirement-cli-init-app-1--1c2b11b4,requirement-cli-init-app-5--74a2ea93"]
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import { createModWorkspace, createModUser, setTextContent, detectMimeType, mimeTypeToCanvasType } from '@mod/mod-core';
|
|
6
|
-
import { readConfig, readWorkspaceConnection, writeWorkspaceConnection, ensureModDir, } from '../lib/storage.js';
|
|
7
|
-
import { select, input, validateWorkspaceName } from '../lib/prompts.js';
|
|
8
|
-
import { addWorkspaceToSharePolicy, addFilesToSharePolicy } from '../daemon/share-policy.js';
|
|
9
|
-
import { FileImportService } from '../services/file-import-service.js';
|
|
10
|
-
export async function initCommand(args, repo) {
|
|
11
|
-
const isForce = args.includes('--force');
|
|
12
|
-
try {
|
|
13
|
-
ensureModDir();
|
|
14
|
-
// Check for existing workspace connection
|
|
15
|
-
const currentDir = process.cwd();
|
|
16
|
-
const existingConnection = readWorkspaceConnection(currentDir);
|
|
17
|
-
if (existingConnection && !isForce) {
|
|
18
|
-
console.log('Already initialized');
|
|
19
|
-
console.log(`Workspace: ${existingConnection.workspaceName}`);
|
|
20
|
-
console.log('');
|
|
21
|
-
// Offer to resume import if there are files not yet synced
|
|
22
|
-
const resumeChoice = await select('What would you like to do?', [
|
|
23
|
-
{ label: 'Resume file import (if interrupted)', value: 'resume' },
|
|
24
|
-
{ label: 'Start syncing', value: 'sync' },
|
|
25
|
-
{ label: 'Reinitialize with different workspace', value: 'force' },
|
|
26
|
-
]);
|
|
27
|
-
if (resumeChoice === 'resume') {
|
|
28
|
-
console.log('Checking for files to import...');
|
|
29
|
-
try {
|
|
30
|
-
await importDirectoryFiles(repo, { id: existingConnection.workspaceId });
|
|
31
|
-
console.log('Resume complete');
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
console.error('Resume failed:', error instanceof Error ? error.message : error);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
else if (resumeChoice === 'sync') {
|
|
39
|
-
console.log('Run `mod sync start` to begin syncing');
|
|
40
|
-
}
|
|
41
|
-
else if (resumeChoice === 'force') {
|
|
42
|
-
console.log('Reinitializing...');
|
|
43
|
-
// Continue with force init below
|
|
44
|
-
}
|
|
45
|
-
if (resumeChoice !== 'force') {
|
|
46
|
-
process.exit(0);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// Check auth state
|
|
50
|
-
const config = readConfig();
|
|
51
|
-
const isAuthenticated = !!config.auth;
|
|
52
|
-
let workspaceConnection;
|
|
53
|
-
if (isAuthenticated) {
|
|
54
|
-
workspaceConnection = await handleAuthenticatedInit(repo, config.auth.email);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
workspaceConnection = await handleUnauthenticatedInit(repo);
|
|
58
|
-
}
|
|
59
|
-
// Workspace connection already saved during creation
|
|
60
|
-
// Update last synced time
|
|
61
|
-
workspaceConnection.lastSyncedAt = new Date().toISOString();
|
|
62
|
-
writeWorkspaceConnection(currentDir, workspaceConnection);
|
|
63
|
-
// Install the /mod agent skill for spec-driven development
|
|
64
|
-
installModSkill();
|
|
65
|
-
console.log('Installed /mod skill to .claude/skills/mod/');
|
|
66
|
-
// Display success message
|
|
67
|
-
displayInitializationSuccess(workspaceConnection, isAuthenticated);
|
|
68
|
-
process.exit(0);
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
console.error('Initialization failed:', error.message);
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
// glassware[type="implementation", id="impl-init-authenticated--1fe6c419", requirements="requirement-cli-init-app-2--51f0306f,requirement-cli-init-app-3--76844b98,requirement-cli-init-qual-1--ca672702"]
|
|
76
|
-
async function handleAuthenticatedInit(repo, email) {
|
|
77
|
-
console.log(`Signed in as ${email}`);
|
|
78
|
-
// Get workspace list from user document (app-2)
|
|
79
|
-
const config = readConfig();
|
|
80
|
-
let workspaces = [];
|
|
81
|
-
try {
|
|
82
|
-
const userDocId = config.auth?.userDocId;
|
|
83
|
-
if (!userDocId) {
|
|
84
|
-
console.warn('User document not found. Creating local workspace.');
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
// Fetch workspaces from user document (app-3)
|
|
88
|
-
const userHandle = await repo.find(userDocId);
|
|
89
|
-
await userHandle.whenReady();
|
|
90
|
-
const userDoc = userHandle.doc();
|
|
91
|
-
if (userDoc) {
|
|
92
|
-
const workspaceIds = userDoc.workspaceIds || [];
|
|
93
|
-
// Load workspace metadata for each workspace
|
|
94
|
-
for (const wsId of workspaceIds) {
|
|
95
|
-
try {
|
|
96
|
-
const wsHandle = await repo.find(wsId);
|
|
97
|
-
await wsHandle.whenReady();
|
|
98
|
-
const ws = wsHandle.doc();
|
|
99
|
-
workspaces.push({
|
|
100
|
-
id: wsId,
|
|
101
|
-
name: ws?.title || ws?.name || 'Untitled',
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
// Workspace might not be available, add with just ID
|
|
106
|
-
workspaces.push({
|
|
107
|
-
id: wsId,
|
|
108
|
-
name: `Workspace ${String(wsId).slice(0, 8)}`,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
// Handle network errors gracefully (qual-1)
|
|
117
|
-
console.warn('Could not load cloud workspaces. Creating local workspace.');
|
|
118
|
-
}
|
|
119
|
-
// Build options
|
|
120
|
-
const options = [
|
|
121
|
-
...workspaces.map((w) => ({
|
|
122
|
-
label: w.name,
|
|
123
|
-
value: { type: 'existing', id: w.id, name: w.name },
|
|
124
|
-
})),
|
|
125
|
-
{
|
|
126
|
-
label: '+ Create new workspace',
|
|
127
|
-
value: { type: 'create', id: '', name: '' },
|
|
128
|
-
},
|
|
129
|
-
];
|
|
130
|
-
const choice = await select('Select workspace:', options);
|
|
131
|
-
if (choice.type === 'create') {
|
|
132
|
-
return await createNewWorkspace(repo);
|
|
133
|
-
}
|
|
134
|
-
// Add existing workspace to share policy so it can sync
|
|
135
|
-
addWorkspaceToSharePolicy(choice.id);
|
|
136
|
-
// Save workspace connection immediately
|
|
137
|
-
const currentDir = process.cwd();
|
|
138
|
-
const workspaceConnection = {
|
|
139
|
-
path: currentDir,
|
|
140
|
-
workspaceId: choice.id,
|
|
141
|
-
workspaceName: choice.name,
|
|
142
|
-
connectedAt: new Date().toISOString(),
|
|
143
|
-
lastSyncedAt: new Date().toISOString(),
|
|
144
|
-
};
|
|
145
|
-
writeWorkspaceConnection(currentDir, workspaceConnection);
|
|
146
|
-
console.log('Workspace connection saved');
|
|
147
|
-
// Register existing workspace on UserDoc (in case it wasn't registered before)
|
|
148
|
-
if (config.auth?.userDocId) {
|
|
149
|
-
try {
|
|
150
|
-
const modUser = createModUser(repo);
|
|
151
|
-
await modUser.addWorkspace(config.auth.userDocId, choice.id);
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
// Silently ignore - workspace is already connected
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// Import files from directory if this is a new connection
|
|
158
|
-
await importDirectoryFiles(repo, { id: choice.id });
|
|
159
|
-
return workspaceConnection;
|
|
160
|
-
}
|
|
161
|
-
async function handleUnauthenticatedInit(repo) {
|
|
162
|
-
const choice = await select('Select option:', [
|
|
163
|
-
{
|
|
164
|
-
label: 'Create local workspace',
|
|
165
|
-
value: 'create',
|
|
166
|
-
description: 'Work offline, sync later',
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
label: 'Sign in to sync with team',
|
|
170
|
-
value: 'signin',
|
|
171
|
-
description: 'Access cloud workspaces',
|
|
172
|
-
},
|
|
173
|
-
]);
|
|
174
|
-
if (choice === 'signin') {
|
|
175
|
-
// Trigger auth flow
|
|
176
|
-
console.log('');
|
|
177
|
-
console.log('Please run `mod auth login` to sign in, then run `mod init` again.');
|
|
178
|
-
process.exit(0);
|
|
179
|
-
}
|
|
180
|
-
return await createNewWorkspace(repo);
|
|
181
|
-
}
|
|
182
|
-
// glassware[type="implementation", id="impl-init-create-workspace--4ff92c7c", requirements="requirement-cli-init-app-4--761a37a6,requirement-cli-init-int-1--e3f9f2b6"]
|
|
183
|
-
async function createNewWorkspace(repo) {
|
|
184
|
-
const currentDirName = path.basename(process.cwd());
|
|
185
|
-
const currentDir = process.cwd();
|
|
186
|
-
const defaultName = currentDirName.charAt(0).toUpperCase() + currentDirName.slice(1);
|
|
187
|
-
const name = await input('Workspace name', {
|
|
188
|
-
default: defaultName,
|
|
189
|
-
validate: validateWorkspaceName,
|
|
190
|
-
});
|
|
191
|
-
console.log('Creating workspace...');
|
|
192
|
-
// Create new workspace - works with or without auth (app-4)
|
|
193
|
-
const modWorkspace = createModWorkspace(repo);
|
|
194
|
-
// No branching by default (enableBranching: true to opt-in)
|
|
195
|
-
// The CLI stores workspace IDs in its local storage instead
|
|
196
|
-
const workspace = await modWorkspace.createWorkspace({ name });
|
|
197
|
-
// Add workspace to share policy so it can sync
|
|
198
|
-
addWorkspaceToSharePolicy(workspace.id);
|
|
199
|
-
// CRITICAL: Save workspace connection immediately before import
|
|
200
|
-
// This allows resuming if import is interrupted
|
|
201
|
-
const workspaceConnection = {
|
|
202
|
-
path: currentDir,
|
|
203
|
-
workspaceId: workspace.id,
|
|
204
|
-
workspaceName: workspace.name,
|
|
205
|
-
connectedAt: new Date().toISOString(),
|
|
206
|
-
lastSyncedAt: new Date().toISOString(),
|
|
207
|
-
};
|
|
208
|
-
writeWorkspaceConnection(currentDir, workspaceConnection);
|
|
209
|
-
console.log('Workspace connection saved');
|
|
210
|
-
// Wait for workspace document to sync to server
|
|
211
|
-
console.log('Syncing workspace...');
|
|
212
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
213
|
-
// Register workspace on UserDoc for cross-device discovery (do this before import)
|
|
214
|
-
const config = readConfig();
|
|
215
|
-
if (config.auth?.userDocId) {
|
|
216
|
-
try {
|
|
217
|
-
const modUser = createModUser(repo);
|
|
218
|
-
await modUser.addWorkspace(config.auth.userDocId, workspace.id);
|
|
219
|
-
// Wait for user doc to sync
|
|
220
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
221
|
-
console.log('Workspace registered for sync');
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
// Don't fail the init if registration fails - workspace still created
|
|
225
|
-
console.warn('Note: Could not register workspace for cross-device sync');
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
// Import existing files from the directory
|
|
229
|
-
await importDirectoryFiles(repo, workspace);
|
|
230
|
-
console.log('Workspace synced');
|
|
231
|
-
return workspaceConnection;
|
|
232
|
-
}
|
|
233
|
-
// glassware[type="implementation", id="impl-cli-init-import--74b5c00f", requirements="requirement-cli-add-init-integration--e953a345"]
|
|
234
|
-
// TODO: Migrate to use AddService instead of FileImportService for consistency with `mod add`
|
|
235
|
-
async function importDirectoryFiles(repo, workspace) {
|
|
236
|
-
const currentDir = process.cwd();
|
|
237
|
-
// Check if directory has any files to import
|
|
238
|
-
const entries = fs.readdirSync(currentDir);
|
|
239
|
-
const hasFiles = entries.some(entry => {
|
|
240
|
-
const fullPath = path.join(currentDir, entry);
|
|
241
|
-
const stats = fs.statSync(fullPath);
|
|
242
|
-
return stats.isFile() && entry !== '.modconfig';
|
|
243
|
-
});
|
|
244
|
-
if (!hasFiles) {
|
|
245
|
-
console.log('No files found to import');
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
console.log('Importing files from directory...');
|
|
249
|
-
const importService = new FileImportService(repo);
|
|
250
|
-
try {
|
|
251
|
-
// Scan for files to import
|
|
252
|
-
const preview = await importService.previewImport({
|
|
253
|
-
workingDirectory: currentDir,
|
|
254
|
-
verbose: false
|
|
255
|
-
});
|
|
256
|
-
if (preview.filteredFiles.length === 0) {
|
|
257
|
-
console.log('No trackable files found to import');
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
console.log(`Found ${preview.filteredFiles.length} files to scan`);
|
|
261
|
-
// Import the files with batching to avoid memory issues
|
|
262
|
-
const modWorkspace = createModWorkspace(repo);
|
|
263
|
-
const workspaceHandle = await modWorkspace.openWorkspace(workspace.id);
|
|
264
|
-
// Query existing files to enable resume capability
|
|
265
|
-
console.log('Checking for existing files...');
|
|
266
|
-
const existingFiles = await workspaceHandle.file.list();
|
|
267
|
-
const existingPaths = new Set(existingFiles
|
|
268
|
-
.map(f => f.metadata?.typeData?.path || f.metadata?.typeData?.relativePath)
|
|
269
|
-
.filter(Boolean));
|
|
270
|
-
// Filter out files that already exist
|
|
271
|
-
const filesToImport = preview.filteredFiles.filter(filePath => {
|
|
272
|
-
const relativePath = path.relative(currentDir, filePath);
|
|
273
|
-
return !existingPaths.has(relativePath);
|
|
274
|
-
});
|
|
275
|
-
if (filesToImport.length === 0) {
|
|
276
|
-
console.log('All files already imported, nothing to do');
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
const skippedCount = preview.filteredFiles.length - filesToImport.length;
|
|
280
|
-
if (skippedCount > 0) {
|
|
281
|
-
console.log(`Skipping ${skippedCount} files that already exist`);
|
|
282
|
-
}
|
|
283
|
-
console.log(`Importing ${filesToImport.length} new files`);
|
|
284
|
-
const importedFileIds = [];
|
|
285
|
-
let importedCount = 0;
|
|
286
|
-
const BATCH_SIZE = 20; // Reduced batch size to avoid Automerge concurrency issues
|
|
287
|
-
// Split files into batches
|
|
288
|
-
const batches = [];
|
|
289
|
-
for (let i = 0; i < filesToImport.length; i += BATCH_SIZE) {
|
|
290
|
-
batches.push(filesToImport.slice(i, i + BATCH_SIZE));
|
|
291
|
-
}
|
|
292
|
-
// Process each batch
|
|
293
|
-
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
|
|
294
|
-
const batch = batches[batchIndex];
|
|
295
|
-
// Process files sequentially to avoid Automerge concurrency issues
|
|
296
|
-
// Concurrent modifications cause "recursive use of an object" errors
|
|
297
|
-
for (const filePath of batch) {
|
|
298
|
-
try {
|
|
299
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
300
|
-
const relativePath = path.relative(currentDir, filePath);
|
|
301
|
-
const pathParts = relativePath.split(path.sep);
|
|
302
|
-
const fileName = pathParts.pop();
|
|
303
|
-
// Detect proper mime type instead of hardcoding markdown
|
|
304
|
-
const mimeType = detectMimeType(fileName);
|
|
305
|
-
const canvasType = mimeTypeToCanvasType(mimeType);
|
|
306
|
-
const isCodeFile = canvasType === 'code';
|
|
307
|
-
const isTextFile = mimeType.startsWith('text/') ||
|
|
308
|
-
mimeType === 'application/json' ||
|
|
309
|
-
mimeType === 'application/javascript';
|
|
310
|
-
// Create file in workspace
|
|
311
|
-
// Code files: Store content directly (no prosemirror formatting)
|
|
312
|
-
// Text/markdown files: Create empty first, then apply setTextContent for proper formatting
|
|
313
|
-
const fileDoc = await workspaceHandle.file.create({
|
|
314
|
-
text: isCodeFile ? content : '',
|
|
315
|
-
metadata: {
|
|
316
|
-
type: 'text',
|
|
317
|
-
path: relativePath,
|
|
318
|
-
name: fileName,
|
|
319
|
-
mimeType,
|
|
320
|
-
createdAt: new Date().toISOString(),
|
|
321
|
-
updatedAt: new Date().toISOString(),
|
|
322
|
-
createdBy: 'cli'
|
|
323
|
-
}
|
|
324
|
-
}, {
|
|
325
|
-
name: fileName,
|
|
326
|
-
mimeType
|
|
327
|
-
});
|
|
328
|
-
// Apply proper richtext formatting using setTextContent for text/markdown files only
|
|
329
|
-
// Code files should NOT use setTextContent - they need plain text for CodeMirror
|
|
330
|
-
if (isTextFile && !isCodeFile && content.length > 0) {
|
|
331
|
-
await setTextContent(fileDoc, ['text'], content);
|
|
332
|
-
}
|
|
333
|
-
importedFileIds.push(fileDoc.documentId);
|
|
334
|
-
importedCount++;
|
|
335
|
-
// Longer delay to ensure Automerge completes save cycle
|
|
336
|
-
// The "recursive use" error happens when save is triggered while another save is in progress
|
|
337
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
338
|
-
}
|
|
339
|
-
catch (error) {
|
|
340
|
-
console.warn(` Failed to import ${filePath}:`, error instanceof Error ? error.message : error);
|
|
341
|
-
// On Automerge error, wait longer before continuing
|
|
342
|
-
if (error instanceof Error && error.message.includes('recursive')) {
|
|
343
|
-
console.warn(' Automerge concurrency detected, waiting 2s...');
|
|
344
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
console.log(` Imported ${importedCount}/${filesToImport.length} files...`);
|
|
349
|
-
// Trigger garbage collection hint between batches to free memory
|
|
350
|
-
if (global.gc && batchIndex < batches.length - 1) {
|
|
351
|
-
global.gc();
|
|
352
|
-
}
|
|
353
|
-
// Small delay between batches to allow memory cleanup
|
|
354
|
-
if (batchIndex < batches.length - 1) {
|
|
355
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
// Add imported files to share policy in batches
|
|
359
|
-
if (importedFileIds.length > 0) {
|
|
360
|
-
const SHARE_POLICY_BATCH_SIZE = 100;
|
|
361
|
-
for (let i = 0; i < importedFileIds.length; i += SHARE_POLICY_BATCH_SIZE) {
|
|
362
|
-
const batch = importedFileIds.slice(i, i + SHARE_POLICY_BATCH_SIZE);
|
|
363
|
-
addFilesToSharePolicy(batch);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
console.log(`✓ Imported ${importedCount} files (${skippedCount} already existed)`);
|
|
367
|
-
}
|
|
368
|
-
catch (error) {
|
|
369
|
-
console.warn('Failed to import files:', error instanceof Error ? error.message : error);
|
|
370
|
-
throw error; // Re-throw so caller knows import failed
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* Install the /mod agent skill to .claude/skills/mod/
|
|
375
|
-
* This enables coding agents to use spec-driven development workflows
|
|
376
|
-
*/
|
|
377
|
-
function installModSkill() {
|
|
378
|
-
const currentDir = process.cwd();
|
|
379
|
-
const skillDir = path.join(currentDir, '.claude', 'skills', 'mod');
|
|
380
|
-
const referencesDir = path.join(skillDir, 'references');
|
|
381
|
-
// Create directories if they don't exist
|
|
382
|
-
if (!fs.existsSync(skillDir)) {
|
|
383
|
-
fs.mkdirSync(skillDir, { recursive: true });
|
|
384
|
-
}
|
|
385
|
-
if (!fs.existsSync(referencesDir)) {
|
|
386
|
-
fs.mkdirSync(referencesDir, { recursive: true });
|
|
387
|
-
}
|
|
388
|
-
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
389
|
-
const commandsMdPath = path.join(referencesDir, 'commands.md');
|
|
390
|
-
// Only write if files don't exist (don't overwrite user modifications)
|
|
391
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
392
|
-
fs.writeFileSync(skillMdPath, SKILL_MD_CONTENT);
|
|
393
|
-
}
|
|
394
|
-
if (!fs.existsSync(commandsMdPath)) {
|
|
395
|
-
fs.writeFileSync(commandsMdPath, COMMANDS_MD_CONTENT);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
const SKILL_MD_CONTENT = `---
|
|
399
|
-
name: mod
|
|
400
|
-
description: Spec-driven development with traceability. Implements specs, adds traces, verifies coverage. Use when implementing from specifications, adding requirement traceability, or checking trace coverage before merging.
|
|
401
|
-
---
|
|
402
|
-
|
|
403
|
-
# Mod: Spec-Driven Development
|
|
404
|
-
|
|
405
|
-
Use this skill when the user asks to implement from specs, add traceability, or verify coverage.
|
|
406
|
-
|
|
407
|
-
## Triggers
|
|
408
|
-
|
|
409
|
-
- "implement this spec"
|
|
410
|
-
- "implement specs/*.md"
|
|
411
|
-
- "/mod implement <file>"
|
|
412
|
-
- "add tests for this spec"
|
|
413
|
-
- "check trace coverage"
|
|
414
|
-
|
|
415
|
-
## Workflow
|
|
416
|
-
|
|
417
|
-
### 1. Setup (if needed)
|
|
418
|
-
|
|
419
|
-
Check if workspace exists:
|
|
420
|
-
\`\`\`bash
|
|
421
|
-
mod status
|
|
422
|
-
\`\`\`
|
|
423
|
-
|
|
424
|
-
If not initialized:
|
|
425
|
-
\`\`\`bash
|
|
426
|
-
mod init
|
|
427
|
-
mod auth login
|
|
428
|
-
\`\`\`
|
|
429
|
-
|
|
430
|
-
### 2. Read the Spec
|
|
431
|
-
|
|
432
|
-
Parse the spec file for requirements. Requirements may be marked with glassware annotations or be plain markdown sections.
|
|
433
|
-
|
|
434
|
-
### 3. Implement with Traces
|
|
435
|
-
|
|
436
|
-
For each requirement:
|
|
437
|
-
|
|
438
|
-
1. Write the implementation
|
|
439
|
-
2. Add a trace connecting implementation to requirement:
|
|
440
|
-
\`\`\`bash
|
|
441
|
-
mod trace add <file>:<line> --type=implementation --link=<requirement-id>
|
|
442
|
-
\`\`\`
|
|
443
|
-
|
|
444
|
-
3. Verify the trace was added:
|
|
445
|
-
\`\`\`bash
|
|
446
|
-
mod trace report <spec-file>
|
|
447
|
-
\`\`\`
|
|
448
|
-
|
|
449
|
-
### 4. Add Tests with Traces
|
|
450
|
-
|
|
451
|
-
For each implementation:
|
|
452
|
-
|
|
453
|
-
1. Write tests
|
|
454
|
-
2. Add traces connecting tests to implementations:
|
|
455
|
-
\`\`\`bash
|
|
456
|
-
mod trace add <test-file>:<line> --type=test --link=<implementation-id>
|
|
457
|
-
\`\`\`
|
|
458
|
-
|
|
459
|
-
### 5. Verify Coverage
|
|
460
|
-
|
|
461
|
-
Before completing, always run:
|
|
462
|
-
\`\`\`bash
|
|
463
|
-
mod trace report <spec-file>
|
|
464
|
-
mod trace unmet
|
|
465
|
-
mod trace diff
|
|
466
|
-
\`\`\`
|
|
467
|
-
|
|
468
|
-
Address any gaps:
|
|
469
|
-
- Unmet requirements -> implement them
|
|
470
|
-
- Untraced files -> add traces or mark as utility
|
|
471
|
-
|
|
472
|
-
### 6. Final Validation
|
|
473
|
-
|
|
474
|
-
\`\`\`bash
|
|
475
|
-
mod trace diff && mod trace unmet
|
|
476
|
-
\`\`\`
|
|
477
|
-
|
|
478
|
-
Only report completion when both commands exit 0 (all files traced, all requirements implemented).
|
|
479
|
-
|
|
480
|
-
## Commands Reference
|
|
481
|
-
|
|
482
|
-
| Command | Purpose |
|
|
483
|
-
|---------|---------|
|
|
484
|
-
| \`mod init\` | Initialize workspace |
|
|
485
|
-
| \`mod auth login\` | Authenticate |
|
|
486
|
-
| \`mod trace add <file>:<line> --type=<type> [--link=<id>]\` | Add trace |
|
|
487
|
-
| \`mod trace link <source> <target>\` | Link two traces |
|
|
488
|
-
| \`mod trace report <file>\` | Show trace coverage for spec |
|
|
489
|
-
| \`mod trace coverage\` | Workspace-wide stats |
|
|
490
|
-
| \`mod trace unmet\` | Requirements without implementations |
|
|
491
|
-
| \`mod trace diff\` | Untraced files on branch (auto-detects base) |
|
|
492
|
-
|
|
493
|
-
## Types
|
|
494
|
-
|
|
495
|
-
- \`requirement\` - Spec requirement
|
|
496
|
-
- \`specification\` - Technical spec detail
|
|
497
|
-
- \`implementation\` - Code that builds
|
|
498
|
-
- \`test\` - Code that verifies
|
|
499
|
-
- \`utility\` - Intentionally untraced helpers
|
|
500
|
-
|
|
501
|
-
## Important
|
|
502
|
-
|
|
503
|
-
- Always verify coverage before completing
|
|
504
|
-
- If \`mod trace diff\` or \`mod trace unmet\` fails, address gaps before finishing
|
|
505
|
-
- Use \`mod trace unmet\` to find requirements that still need implementation
|
|
506
|
-
- Mark helper files as \`utility\` type to avoid false positives in diff
|
|
507
|
-
`;
|
|
508
|
-
const COMMANDS_MD_CONTENT = `# Mod CLI Commands Reference
|
|
509
|
-
|
|
510
|
-
## Authentication
|
|
511
|
-
|
|
512
|
-
\`\`\`bash
|
|
513
|
-
mod auth login # OAuth login (opens browser)
|
|
514
|
-
mod auth login --dev # Dev mode (no OAuth)
|
|
515
|
-
mod auth logout
|
|
516
|
-
mod auth status
|
|
517
|
-
\`\`\`
|
|
518
|
-
|
|
519
|
-
## Workspace
|
|
520
|
-
|
|
521
|
-
\`\`\`bash
|
|
522
|
-
mod init # Initialize workspace in current directory
|
|
523
|
-
mod status # Show workspace status
|
|
524
|
-
\`\`\`
|
|
525
|
-
|
|
526
|
-
## Traces
|
|
527
|
-
|
|
528
|
-
\`\`\`bash
|
|
529
|
-
# Add traces
|
|
530
|
-
mod trace add <file>:<line> --type=<type> ["description"]
|
|
531
|
-
mod trace add <file>:<line> --type=<type> --link=<trace-id>
|
|
532
|
-
|
|
533
|
-
# Link traces
|
|
534
|
-
mod trace link <source-id> <target-id>
|
|
535
|
-
|
|
536
|
-
# View traces
|
|
537
|
-
mod trace list # All traces
|
|
538
|
-
mod trace list --type=requirement # Filter by type
|
|
539
|
-
mod trace list --file=<path> # Filter by file
|
|
540
|
-
mod trace get <trace-id> # Get trace details
|
|
541
|
-
|
|
542
|
-
# Reports
|
|
543
|
-
mod trace report <file> # Per-document coverage
|
|
544
|
-
mod trace coverage # Workspace-wide stats
|
|
545
|
-
|
|
546
|
-
# Find gaps
|
|
547
|
-
mod trace diff # Untraced files on branch (auto-detects base)
|
|
548
|
-
mod trace diff main..HEAD # Explicit range
|
|
549
|
-
mod trace unmet # Requirements without implementations
|
|
550
|
-
\`\`\`
|
|
551
|
-
|
|
552
|
-
## Trace Types
|
|
553
|
-
|
|
554
|
-
| Type | Use For |
|
|
555
|
-
|------|---------|
|
|
556
|
-
| \`requirement\` | Specs, user stories, acceptance criteria |
|
|
557
|
-
| \`specification\` | Detailed technical specs |
|
|
558
|
-
| \`implementation\` | Code that builds something |
|
|
559
|
-
| \`test\` | Code that verifies something |
|
|
560
|
-
| \`design\` | Design docs, architecture notes |
|
|
561
|
-
| \`decision\` | ADRs, decision records |
|
|
562
|
-
| \`utility\` | Helpers that don't need tracing |
|
|
563
|
-
|
|
564
|
-
## Comments
|
|
565
|
-
|
|
566
|
-
\`\`\`bash
|
|
567
|
-
mod comment add <file>:<line> "text"
|
|
568
|
-
mod comment list [file]
|
|
569
|
-
\`\`\`
|
|
570
|
-
|
|
571
|
-
## Exit Codes for CI
|
|
572
|
-
|
|
573
|
-
Commands exit non-zero when issues exist:
|
|
574
|
-
|
|
575
|
-
| Command | Exit 0 | Exit 1 |
|
|
576
|
-
|---------|--------|--------|
|
|
577
|
-
| \`mod trace diff\` | All changed files traced | Untraced files exist |
|
|
578
|
-
| \`mod trace unmet\` | All requirements implemented | Unmet requirements |
|
|
579
|
-
| \`mod trace report\` | Always (informational) | - |
|
|
580
|
-
|
|
581
|
-
Pre-merge validation:
|
|
582
|
-
\`\`\`bash
|
|
583
|
-
mod trace diff && mod trace unmet && git push
|
|
584
|
-
\`\`\`
|
|
585
|
-
`;
|
|
586
|
-
function displayInitializationSuccess(connection, isAuthenticated) {
|
|
587
|
-
console.log('');
|
|
588
|
-
console.log('Initialized Mod in ' + connection.path);
|
|
589
|
-
console.log(`Workspace: ${connection.workspaceName}`);
|
|
590
|
-
console.log('');
|
|
591
|
-
if (isAuthenticated) {
|
|
592
|
-
console.log('Run `mod sync start` to begin syncing');
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
console.log('Run `mod auth login` to enable sync with collaborators');
|
|
596
|
-
}
|
|
597
|
-
}
|