specdrive-cli 0.1.10 → 0.1.12

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.
Files changed (74) hide show
  1. package/README.md +955 -697
  2. package/agents/00-onboarding.md +261 -0
  3. package/agents/01-constitution.md +214 -201
  4. package/agents/02-specification.md +249 -226
  5. package/agents/03-uiux.md +156 -144
  6. package/agents/04-cascade.md +151 -122
  7. package/agents/05-discover-skills.md +136 -136
  8. package/agents/06-documentation.md +158 -145
  9. package/agents/07-implementation.md +201 -169
  10. package/agents/08-performance.md +179 -165
  11. package/agents/09-review-complete.md +239 -168
  12. package/agents/10-security.md +180 -167
  13. package/agents/11-test.md +195 -0
  14. package/commands/gates.js +73 -73
  15. package/commands/manifest.json +113 -95
  16. package/commands/permissions.json +39 -0
  17. package/commands/router.js +151 -127
  18. package/commands/tools.json +19 -19
  19. package/dashboard/app.js +394 -0
  20. package/dashboard/index.html +74 -0
  21. package/dashboard/server.js +166 -0
  22. package/dashboard/style.css +157 -0
  23. package/mcp/mcp.json +31 -0
  24. package/mcp/server.js +108 -0
  25. package/package.json +35 -32
  26. package/schemas/config.schema.json +20 -0
  27. package/schemas/workflow-state.schema.json +149 -38
  28. package/scripts/anti-redundancy.js +176 -176
  29. package/scripts/audit-log.js +46 -46
  30. package/scripts/check-permission.js +87 -0
  31. package/scripts/diff-spec.js +50 -50
  32. package/scripts/diff-version.js +96 -0
  33. package/scripts/generate-adapters.js +80 -80
  34. package/scripts/generate-from-template.js +97 -97
  35. package/scripts/generate-openapi.js +75 -75
  36. package/scripts/github-team-sync.js +80 -80
  37. package/scripts/install-hooks.js +20 -20
  38. package/scripts/load-plugins.js +65 -65
  39. package/scripts/migrate-openspec.js +318 -0
  40. package/scripts/migrate-speckit.js +322 -0
  41. package/scripts/migrate.js +12 -62
  42. package/scripts/onboard.js +312 -0
  43. package/scripts/pre-commit.js +56 -20
  44. package/scripts/team.js +113 -113
  45. package/scripts/test-adapters.js +118 -118
  46. package/scripts/test-create.js +13 -13
  47. package/scripts/test-end-to-end.js +137 -137
  48. package/scripts/test-router.js +110 -110
  49. package/scripts/test-state-transitions.js +146 -146
  50. package/scripts/test-validator.js +152 -152
  51. package/scripts/validate-config.js +36 -0
  52. package/scripts/validate-governance.js +150 -130
  53. package/scripts/verify.js +525 -0
  54. package/scripts/version-new.js +202 -0
  55. package/src/index.js +1010 -807
  56. package/templates/expo/plan.json +12 -0
  57. package/templates/expo/spec.json +12 -0
  58. package/templates/expo/tasks.json +5 -0
  59. package/templates/fastapi/plan.json +12 -0
  60. package/templates/fastapi/spec.json +12 -0
  61. package/templates/fastapi/tasks.json +5 -0
  62. package/templates/generic/plan.json +12 -0
  63. package/templates/generic/spec.json +11 -0
  64. package/templates/generic/tasks.json +5 -0
  65. package/templates/nextjs/plan.json +23 -0
  66. package/templates/nextjs/spec.json +12 -0
  67. package/templates/nextjs/tasks.json +5 -0
  68. package/templates/react-node/plan.json +15 -0
  69. package/templates/react-node/spec.json +12 -0
  70. package/templates/react-node/tasks.json +5 -0
  71. package/templates/registry.json +30 -0
  72. package/templates/turborepo/plan.json +12 -0
  73. package/templates/turborepo/spec.json +12 -0
  74. package/templates/turborepo/tasks.json +5 -0
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const projectRoot = process.cwd();
7
+ const sdriveRoot = path.join(projectRoot, ".sdrive");
8
+
9
+ const speckitDir = path.join(projectRoot, ".specify");
10
+ const speckitSpecsDir = path.join(speckitDir, "specs");
11
+ const speckitMemoryDir = path.join(speckitDir, "memory");
12
+ const speckitConstitution = path.join(speckitMemoryDir, "constitution.md");
13
+
14
+ if (!fs.existsSync(speckitDir)) {
15
+ console.error("❌ .specify/ folder not found. Nothing to migrate.");
16
+ process.exit(1);
17
+ }
18
+
19
+ function ensureDir(dir) {
20
+ fs.mkdirSync(dir, { recursive: true });
21
+ }
22
+
23
+ function readFile(filePath) {
24
+ try {
25
+ return fs.readFileSync(filePath, "utf8");
26
+ } catch (error) {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ function listDirectories(dir) {
32
+ if (!fs.existsSync(dir)) return [];
33
+ return fs
34
+ .readdirSync(dir, { withFileTypes: true })
35
+ .filter((d) => d.isDirectory())
36
+ .map((d) => d.name);
37
+ }
38
+
39
+ function stripNumberPrefix(name) {
40
+ // Spec-Kit uses 001-feature-name
41
+ return name.replace(/^[0-9]+[-_]/, "");
42
+ }
43
+
44
+ function slugify(name) {
45
+ return name
46
+ .toLowerCase()
47
+ .replace(/[^a-z0-9]+/g, "-")
48
+ .replace(/^-+|-+$/g, "");
49
+ }
50
+
51
+ function findInDir(dir, names) {
52
+ for (const name of names) {
53
+ const full = path.join(dir, name);
54
+ if (fs.existsSync(full)) return full;
55
+ }
56
+ return null;
57
+ }
58
+
59
+ function writeSpecDriveFeature(featureName, sourceDir) {
60
+ const featureFolder = path.join(
61
+ sdriveRoot,
62
+ "specs",
63
+ "ongoing",
64
+ featureName,
65
+ "v1"
66
+ );
67
+ const govFolder = path.join(sdriveRoot, "governance", featureName, "v1");
68
+
69
+ ensureDir(featureFolder);
70
+ ensureDir(govFolder);
71
+
72
+ const specSource = findInDir(sourceDir, ["spec.md", "specification.md"]);
73
+ const planSource = findInDir(sourceDir, ["plan.md", "technical-plan.md"]);
74
+ const tasksSource = findInDir(sourceDir, ["tasks.md", "todo.md"]);
75
+
76
+ const now = new Date().toISOString();
77
+
78
+ const specContent = specSource
79
+ ? readFile(specSource)
80
+ : `# ${featureName}\n\n(Migrated from Spec-Kit — no source spec found)`;
81
+
82
+ const planContent = planSource
83
+ ? readFile(planSource)
84
+ : `# Technical Plan: ${featureName} (migrated)\n\nThis plan was migrated from Spec-Kit. No technical plan was extracted.\n`;
85
+
86
+ const tasksContent = tasksSource
87
+ ? readFile(tasksSource)
88
+ : `# Execution Tasks: ${featureName}\n\n(Migrated from Spec-Kit — no tasks found)\n`;
89
+
90
+ // spec.md
91
+ fs.writeFileSync(
92
+ path.join(featureFolder, "spec.md"),
93
+ `---
94
+ title: ${featureName}
95
+ status: ongoing
96
+ version: v1
97
+ created: ${now}
98
+ updated: ${now}
99
+ retroactive: true
100
+ migratedFrom: speckit
101
+ ---
102
+
103
+ ${specContent}
104
+ `
105
+ );
106
+
107
+ // plan.md
108
+ fs.writeFileSync(path.join(featureFolder, "plan.md"), planContent);
109
+
110
+ // tasks.md
111
+ fs.writeFileSync(path.join(featureFolder, "tasks.md"), tasksContent);
112
+
113
+ // governance JSON
114
+ fs.writeFileSync(
115
+ path.join(govFolder, "spec.json"),
116
+ JSON.stringify(
117
+ {
118
+ feature: featureName,
119
+ title: featureName,
120
+ version: 1,
121
+ retroactive: true,
122
+ migratedFrom: "speckit",
123
+ overview: {
124
+ problemStatement: "(migrated from Spec-Kit)",
125
+ proposedSolution: "(migrated from Spec-Kit)",
126
+ targetAudience: "(migrated from Spec-Kit)"
127
+ },
128
+ userStories: [],
129
+ functionalRequirements: []
130
+ },
131
+ null,
132
+ 2
133
+ )
134
+ );
135
+
136
+ fs.writeFileSync(
137
+ path.join(govFolder, "plan.json"),
138
+ JSON.stringify(
139
+ {
140
+ feature: featureName,
141
+ version: 1,
142
+ architecture: { approach: "(migrated)", components: [] },
143
+ dataModel: { entities: [], flowDescription: "(migrated)" }
144
+ },
145
+ null,
146
+ 2
147
+ )
148
+ );
149
+
150
+ fs.writeFileSync(
151
+ path.join(govFolder, "tasks.json"),
152
+ JSON.stringify(
153
+ { feature: featureName, version: 1, phases: [] },
154
+ null,
155
+ 2
156
+ )
157
+ );
158
+
159
+ fs.writeFileSync(
160
+ path.join(govFolder, "traceability.json"),
161
+ JSON.stringify(
162
+ { feature: featureName, version: 1, rows: [] },
163
+ null,
164
+ 2
165
+ )
166
+ );
167
+
168
+ return {
169
+ feature: featureName,
170
+ source: path.relative(projectRoot, sourceDir),
171
+ spec: specSource ? path.relative(projectRoot, specSource) : null,
172
+ plan: planSource ? path.relative(projectRoot, planSource) : null,
173
+ tasks: tasksSource ? path.relative(projectRoot, tasksSource) : null
174
+ };
175
+ }
176
+
177
+ function migrateSpecs() {
178
+ const migrated = [];
179
+
180
+ if (!fs.existsSync(speckitSpecsDir)) {
181
+ return migrated;
182
+ }
183
+
184
+ const folders = listDirectories(speckitSpecsDir);
185
+
186
+ for (const folder of folders) {
187
+ const cleaned = stripNumberPrefix(folder);
188
+ const featureName = slugify(cleaned);
189
+ const sourceDir = path.join(speckitSpecsDir, folder);
190
+
191
+ const result = writeSpecDriveFeature(featureName, sourceDir);
192
+ migrated.push(result);
193
+ console.log(`✅ Migrated spec: ${featureName}`);
194
+ }
195
+
196
+ return migrated;
197
+ }
198
+
199
+ function migrateConstitution() {
200
+ if (!fs.existsSync(speckitConstitution)) {
201
+ return false;
202
+ }
203
+
204
+ const content = readFile(speckitConstitution);
205
+ if (!content) return false;
206
+
207
+ const targetConstitution = path.join(sdriveRoot, "constitution.md");
208
+
209
+ if (fs.existsSync(targetConstitution)) {
210
+ // Append instead of overwrite
211
+ const existing = readFile(targetConstitution) || "";
212
+ fs.writeFileSync(
213
+ targetConstitution,
214
+ `${existing}\n\n---\n\n## Migrated from Spec-Kit\n\n${content}\n`
215
+ );
216
+ console.log("✅ Appended Spec-Kit constitution to existing SpecDrive constitution.");
217
+ } else {
218
+ fs.writeFileSync(
219
+ targetConstitution,
220
+ `# Project Constitution\n\n> Migrated from Spec-Kit\n\n${content}\n`
221
+ );
222
+ console.log("✅ Created SpecDrive constitution from Spec-Kit constitution.");
223
+ }
224
+
225
+ return true;
226
+ }
227
+
228
+ function updateWorkflowState(migrated) {
229
+ const statePath = path.join(sdriveRoot, "workflow-state.json");
230
+
231
+ let state = { features: [] };
232
+ if (fs.existsSync(statePath)) {
233
+ state = JSON.parse(fs.readFileSync(statePath, "utf8"));
234
+ }
235
+
236
+ if (!state.onboarding) {
237
+ state.onboarding = {
238
+ mode: "brownfield",
239
+ date: new Date().toISOString(),
240
+ missing: [],
241
+ deferred: [],
242
+ retroactiveFeatures: [],
243
+ verifiedFeatures: [],
244
+ notes: ""
245
+ };
246
+ }
247
+
248
+ if (!state.onboarding.migrated) {
249
+ state.onboarding.migrated = [];
250
+ }
251
+
252
+ for (const item of migrated) {
253
+ const existing = state.features.find((f) => f.name === item.feature);
254
+
255
+ if (!existing) {
256
+ state.features.push({
257
+ name: item.feature,
258
+ currentVersion: "v1",
259
+ versions: [
260
+ {
261
+ id: "v1",
262
+ phase: "ongoing",
263
+ currentStep: "review",
264
+ gates: {
265
+ specification: "pending",
266
+ planning: "pending",
267
+ implementation: "pending",
268
+ review: "pending"
269
+ },
270
+ history: [
271
+ {
272
+ event: "feature_migrated",
273
+ timestamp: new Date().toISOString(),
274
+ by: "onboarding"
275
+ }
276
+ ]
277
+ }
278
+ ]
279
+ });
280
+ }
281
+
282
+ state.onboarding.migrated.push({
283
+ feature: item.feature,
284
+ from: "speckit",
285
+ date: new Date().toISOString(),
286
+ source: item.source
287
+ });
288
+
289
+ if (!state.onboarding.retroactiveFeatures.includes(item.feature)) {
290
+ state.onboarding.retroactiveFeatures.push(item.feature);
291
+ }
292
+ }
293
+
294
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
295
+ }
296
+
297
+ function main() {
298
+ console.log("🔍 Migrating from Spec-Kit...\n");
299
+
300
+ const migrated = migrateSpecs();
301
+ const constitutionMigrated = migrateConstitution();
302
+
303
+ if (migrated.length === 0 && !constitutionMigrated) {
304
+ console.log("\n⚠️ No Spec-Kit features or constitution found to migrate.");
305
+ process.exit(0);
306
+ }
307
+
308
+ updateWorkflowState(migrated);
309
+
310
+ console.log("");
311
+ console.log(`✅ Migration complete: ${migrated.length} features.`);
312
+ if (constitutionMigrated) {
313
+ console.log("✅ Constitution merged.");
314
+ }
315
+ console.log("");
316
+ console.log("Original .specify/ folder was NOT deleted.");
317
+ console.log("All migrated features are marked retroactive: true.");
318
+ console.log("");
319
+ console.log("Next: run sdrive validate to confirm the new files.");
320
+ }
321
+
322
+ main();
@@ -1,62 +1,12 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require("fs");
4
- const path = require("path");
5
-
6
- const projectRoot = process.cwd();
7
-
8
- function migrateOpenSpec() {
9
- console.log("🔍 Looking for OpenSpec files...");
10
-
11
- const openspecDir = path.join(projectRoot, "openspec");
12
-
13
- if (!fs.existsSync(openspecDir)) {
14
- console.log("❌ openspec/ not found.");
15
- return false;
16
- }
17
-
18
- const specDir = path.join(openspecDir, "specs");
19
-
20
- if (!fs.existsSync(specDir)) {
21
- console.log("❌ openspec/specs not found.");
22
- return false;
23
- }
24
-
25
- console.log("✅ OpenSpec found. Migrating...");
26
-
27
- return true;
28
- }
29
-
30
- function migrateSpecKit() {
31
- console.log("🔍 Looking for Spec-Kit files...");
32
-
33
- const specKitDir = path.join(projectRoot, ".specify");
34
-
35
- if (!fs.existsSync(specKitDir)) {
36
- console.log("❌ .specify not found.");
37
- return false;
38
- }
39
-
40
- console.log("✅ Spec-Kit found. Migrating...");
41
-
42
- return true;
43
- }
44
-
45
- function main() {
46
- console.log("=== SpecDrive Migration Tool ===\n");
47
-
48
- let migrated = false;
49
-
50
- migrated = migrateOpenSpec() || migrated;
51
- migrated = migrateSpecKit() || migrated;
52
-
53
- if (migrated) {
54
- console.log("\n✅ Migration complete.");
55
- process.exit(0);
56
- } else {
57
- console.log("\n❌ No supported spec system found.");
58
- process.exit(1);
59
- }
60
- }
61
-
62
- main();
1
+ #!/usr/bin/env node
2
+
3
+ console.error("⚠️ `sdrive migrate` is deprecated.");
4
+ console.error("");
5
+ console.error("Migration now happens during Brownfield Onboarding.");
6
+ console.error("");
7
+ console.error("Run:");
8
+ console.error(" sdrive onboard");
9
+ console.error(" /sdrive:onboard");
10
+ console.error("");
11
+ console.error("The Onboarding Agent will detect OpenSpec or Spec-Kit and offer to migrate.");
12
+ process.exit(1);
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const projectRoot = process.cwd();
7
+ const sdriveRoot = path.join(projectRoot, ".sdrive");
8
+ const onboardingDir = path.join(sdriveRoot, "onboarding");
9
+
10
+ const inventoryPath = path.join(onboardingDir, "inventory.json");
11
+ const reportPath = path.join(onboardingDir, "report.md");
12
+
13
+ const DETECT_FILES = {
14
+ manifests: [
15
+ "package.json",
16
+ "requirements.txt",
17
+ "pyproject.toml",
18
+ "go.mod",
19
+ "pubspec.yaml",
20
+ "Cargo.toml",
21
+ "pom.xml",
22
+ "build.gradle"
23
+ ],
24
+ docs: [
25
+ "README.md",
26
+ "CONTRIBUTING.md"
27
+ ],
28
+ codeRoots: [
29
+ "src",
30
+ "app",
31
+ "lib",
32
+ "pages",
33
+ "server",
34
+ "internal",
35
+ "cmd",
36
+ "pkg",
37
+ "api",
38
+ "apps",
39
+ "packages"
40
+ ],
41
+ ci: [
42
+ ".github/workflows",
43
+ ".gitlab-ci.yml"
44
+ ],
45
+ tests: [
46
+ "tests",
47
+ "__tests__",
48
+ "spec",
49
+ "test"
50
+ ],
51
+ configs: [
52
+ "tsconfig.json",
53
+ "jsconfig.json",
54
+ ".eslintrc",
55
+ ".eslintrc.js",
56
+ ".eslintrc.json",
57
+ ".prettierrc",
58
+ ".prettierrc.js",
59
+ ".prettierrc.json"
60
+ ],
61
+ specdrive: [
62
+ ".sdrive/constitution.md",
63
+ ".sdrive/skills",
64
+ ".sdrive/specs",
65
+ ".sdrive/governance",
66
+ ".sdrive/workflow-state.json",
67
+ ".sdrive/schemas",
68
+ ".sdrive/scripts",
69
+ ".sdrive/commands",
70
+ ".sdrive/agents",
71
+ ".sdrive/dashboard",
72
+ ".sdrive/mcp"
73
+ ],
74
+ externalSpecSystems: [
75
+ "openspec",
76
+ "openspec/specs",
77
+ "openspec/changes",
78
+ "openspec/archive",
79
+ ".specify",
80
+ ".specify/specs",
81
+ ".specify/memory"
82
+ ]
83
+ };
84
+
85
+ function exists(relativePath) {
86
+ return fs.existsSync(path.join(projectRoot, relativePath));
87
+ }
88
+
89
+ function countFiles(relativePath) {
90
+ const full = path.join(projectRoot, relativePath);
91
+
92
+ if (!fs.existsSync(full)) return 0;
93
+
94
+ const stat = fs.statSync(full);
95
+ if (!stat.isDirectory()) return 1;
96
+
97
+ let count = 0;
98
+ const walk = (dir) => {
99
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
100
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
101
+ const fullEntry = path.join(dir, entry.name);
102
+ if (entry.isDirectory()) {
103
+ walk(fullEntry);
104
+ } else {
105
+ count++;
106
+ }
107
+ }
108
+ };
109
+ walk(full);
110
+ return count;
111
+ }
112
+
113
+ function scanSection(list) {
114
+ const result = [];
115
+ for (const relativePath of list) {
116
+ const present = exists(relativePath);
117
+ result.push({
118
+ path: relativePath,
119
+ present,
120
+ fileCount: present ? countFiles(relativePath) : 0
121
+ });
122
+ }
123
+ return result;
124
+ }
125
+
126
+ function detectExternalSpecSystems() {
127
+ const detection = {
128
+ openspec: {
129
+ detected: false,
130
+ specs: 0,
131
+ changes: 0,
132
+ archived: 0
133
+ },
134
+ speckit: {
135
+ detected: false,
136
+ specs: 0,
137
+ hasConstitution: false
138
+ }
139
+ };
140
+
141
+ // OpenSpec detection
142
+ const openspecDir = path.join(projectRoot, "openspec");
143
+ if (fs.existsSync(openspecDir)) {
144
+ detection.openspec.detected = true;
145
+
146
+ const specsDir = path.join(openspecDir, "specs");
147
+ const changesDir = path.join(openspecDir, "changes");
148
+ const archiveDir = path.join(openspecDir, "archive");
149
+
150
+ if (fs.existsSync(specsDir)) {
151
+ detection.openspec.specs = fs
152
+ .readdirSync(specsDir, { withFileTypes: true })
153
+ .filter((d) => d.isDirectory()).length;
154
+ }
155
+
156
+ if (fs.existsSync(changesDir)) {
157
+ detection.openspec.changes = fs
158
+ .readdirSync(changesDir, { withFileTypes: true })
159
+ .filter((d) => d.isDirectory()).length;
160
+ }
161
+
162
+ if (fs.existsSync(archiveDir)) {
163
+ detection.openspec.archived = fs
164
+ .readdirSync(archiveDir, { withFileTypes: true })
165
+ .filter((d) => d.isDirectory()).length;
166
+ }
167
+ }
168
+
169
+ // Spec-Kit detection
170
+ const speckitDir = path.join(projectRoot, ".specify");
171
+ if (fs.existsSync(speckitDir)) {
172
+ detection.speckit.detected = true;
173
+
174
+ const specsDir = path.join(speckitDir, "specs");
175
+ const constitutionPath = path.join(speckitDir, "memory", "constitution.md");
176
+
177
+ if (fs.existsSync(specsDir)) {
178
+ detection.speckit.specs = fs
179
+ .readdirSync(specsDir, { withFileTypes: true })
180
+ .filter((d) => d.isDirectory()).length;
181
+ }
182
+
183
+ if (fs.existsSync(constitutionPath)) {
184
+ detection.speckit.hasConstitution = true;
185
+ }
186
+ }
187
+
188
+ return detection;
189
+ }
190
+
191
+ function buildInventory() {
192
+ const inventory = {
193
+ projectRoot,
194
+ scannedAt: new Date().toISOString(),
195
+ mode: "brownfield",
196
+ sections: {
197
+ manifests: scanSection(DETECT_FILES.manifests),
198
+ docs: scanSection(DETECT_FILES.docs),
199
+ codeRoots: scanSection(DETECT_FILES.codeRoots),
200
+ ci: scanSection(DETECT_FILES.ci),
201
+ tests: scanSection(DETECT_FILES.tests),
202
+ configs: scanSection(DETECT_FILES.configs),
203
+ specdrive: scanSection(DETECT_FILES.specdrive),
204
+ externalSpecSystems: scanSection(DETECT_FILES.externalSpecSystems)
205
+ },
206
+ detectedSystems: detectExternalSpecSystems(),
207
+ missing: [],
208
+ present: [],
209
+ deferred: []
210
+ };
211
+
212
+ const flatten = (section) => section.map((item) => item);
213
+
214
+ const all = [
215
+ ...flatten(inventory.sections.manifests),
216
+ ...flatten(inventory.sections.docs),
217
+ ...flatten(inventory.sections.codeRoots),
218
+ ...flatten(inventory.sections.ci),
219
+ ...flatten(inventory.sections.tests),
220
+ ...flatten(inventory.sections.configs),
221
+ ...flatten(inventory.sections.specdrive)
222
+ ];
223
+
224
+ for (const item of all) {
225
+ if (item.present) {
226
+ inventory.present.push(item.path);
227
+ } else {
228
+ inventory.missing.push(item.path);
229
+ }
230
+ }
231
+
232
+ return inventory;
233
+ }
234
+
235
+ function buildReport(inventory) {
236
+ const lines = [];
237
+
238
+ lines.push("# Onboarding Report");
239
+ lines.push("");
240
+ lines.push(`Scanned at: ${inventory.scannedAt}`);
241
+ lines.push(`Project root: ${inventory.projectRoot}`);
242
+ lines.push("");
243
+
244
+ // External Spec Systems
245
+ const ext = inventory.detectedSystems || {};
246
+ if (ext.openspec?.detected || ext.speckit?.detected) {
247
+ lines.push("## Detected External Spec Systems");
248
+ if (ext.openspec?.detected) {
249
+ lines.push(`- ✅ OpenSpec`);
250
+ lines.push(` - Specs: ${ext.openspec.specs}`);
251
+ lines.push(` - Active changes: ${ext.openspec.changes}`);
252
+ lines.push(` - Archived: ${ext.openspec.archived}`);
253
+ }
254
+ if (ext.speckit?.detected) {
255
+ lines.push(`- ✅ Spec-Kit`);
256
+ lines.push(` - Specs: ${ext.speckit.specs}`);
257
+ lines.push(` - Constitution: ${ext.speckit.hasConstitution ? "found" : "missing"}`);
258
+ }
259
+ lines.push("");
260
+ lines.push("> These can be migrated into SpecDrive during onboarding via the Onboarding Agent.");
261
+ lines.push("");
262
+ }
263
+
264
+ lines.push("## Present");
265
+ inventory.present.forEach((p) => lines.push(`- ✅ ${p}`));
266
+ lines.push("");
267
+ lines.push("## Missing");
268
+ inventory.missing.forEach((p) => lines.push(`- ❌ ${p}`));
269
+ lines.push("");
270
+ lines.push("## Deferred");
271
+ if (inventory.deferred.length === 0) {
272
+ lines.push("(none yet)");
273
+ } else {
274
+ inventory.deferred.forEach((p) => lines.push(`- ⚠️ ${p}`));
275
+ }
276
+ lines.push("");
277
+
278
+ return lines.join("\n");
279
+ }
280
+
281
+ function main() {
282
+ fs.mkdirSync(onboardingDir, { recursive: true });
283
+
284
+ const inventory = buildInventory();
285
+ fs.writeFileSync(inventoryPath, JSON.stringify(inventory, null, 2));
286
+
287
+ const report = buildReport(inventory);
288
+ fs.writeFileSync(reportPath, report);
289
+
290
+ console.log(`✅ Inventory written: ${path.relative(projectRoot, inventoryPath)}`);
291
+ console.log(`✅ Report written: ${path.relative(projectRoot, reportPath)}`);
292
+ console.log("");
293
+ console.log(`Present: ${inventory.present.length}`);
294
+ console.log(`Missing: ${inventory.missing.length}`);
295
+ console.log(`Deferred: ${inventory.deferred.length}`);
296
+
297
+ const ext = inventory.detectedSystems || {};
298
+ if (ext.openspec?.detected || ext.speckit?.detected) {
299
+ console.log("");
300
+ console.log("🔍 External Spec Systems Detected:");
301
+ if (ext.openspec?.detected) {
302
+ console.log(` - OpenSpec (${ext.openspec.specs} specs, ${ext.openspec.changes} changes)`);
303
+ }
304
+ if (ext.speckit?.detected) {
305
+ console.log(` - Spec-Kit (${ext.speckit.specs} specs)`);
306
+ }
307
+ console.log("");
308
+ console.log(" Run /sdrive:onboard to migrate them.");
309
+ }
310
+ }
311
+
312
+ main();