knowzcode 0.4.0 → 0.6.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.
Files changed (79) hide show
  1. package/.claude-plugin/marketplace.json +61 -61
  2. package/.claude-plugin/plugin.json +8 -8
  3. package/LICENSE +121 -121
  4. package/README.md +379 -354
  5. package/agents/analyst.md +114 -114
  6. package/agents/architect.md +200 -200
  7. package/agents/builder.md +104 -104
  8. package/agents/closer.md +177 -177
  9. package/agents/context-scout.md +54 -54
  10. package/agents/knowledge-migrator.md +349 -349
  11. package/agents/knowz-scout.md +83 -83
  12. package/agents/knowz-scribe.md +180 -180
  13. package/agents/microfix-specialist.md +135 -135
  14. package/agents/project-advisor.md +111 -111
  15. package/agents/reviewer.md +172 -172
  16. package/agents/security-officer.md +194 -194
  17. package/agents/test-advisor.md +162 -162
  18. package/agents/update-coordinator.md +394 -394
  19. package/bin/knowzcode.mjs +1801 -1199
  20. package/commands/audit.md +328 -328
  21. package/commands/connect-mcp.md +574 -549
  22. package/commands/fix.md +107 -107
  23. package/commands/init.md +616 -500
  24. package/commands/learn.md +332 -332
  25. package/commands/plan.md +272 -272
  26. package/commands/register.md +757 -733
  27. package/commands/status.md +338 -309
  28. package/commands/telemetry-setup.md +368 -368
  29. package/commands/telemetry.md +188 -188
  30. package/commands/work.md +1204 -1204
  31. package/knowzcode/automation_manifest.md +59 -59
  32. package/knowzcode/claude_code_execution.md +431 -431
  33. package/knowzcode/copilot_execution.md +231 -231
  34. package/knowzcode/enterprise/compliance_manifest.md +137 -137
  35. package/knowzcode/enterprise/compliance_status.md +30 -30
  36. package/knowzcode/enterprise/guidelines/code-quality.md +67 -67
  37. package/knowzcode/enterprise/guidelines/security.md +355 -355
  38. package/knowzcode/enterprise/templates/guideline-template.md +55 -55
  39. package/knowzcode/gitignore.template +13 -13
  40. package/knowzcode/knowzcode_architecture.md +51 -51
  41. package/knowzcode/knowzcode_log.md +142 -142
  42. package/knowzcode/knowzcode_loop.md +601 -596
  43. package/knowzcode/knowzcode_orchestration.md +66 -66
  44. package/knowzcode/knowzcode_project.md +48 -48
  45. package/knowzcode/knowzcode_tracker.md +40 -40
  46. package/knowzcode/knowzcode_vaults.md +257 -257
  47. package/knowzcode/mcp_config.md +196 -191
  48. package/knowzcode/planning/Readme.md +6 -6
  49. package/knowzcode/platform_adapters.md +2577 -1260
  50. package/knowzcode/prompts/Execute_Micro_Fix.md +57 -57
  51. package/knowzcode/prompts/Investigate_Codebase.md +227 -227
  52. package/knowzcode/prompts/Migrate_Knowledge.md +301 -301
  53. package/knowzcode/prompts/Refactor_Node.md +72 -72
  54. package/knowzcode/prompts/Spec_Verification_Checkpoint.md +59 -59
  55. package/knowzcode/prompts/[LOOP_1A]__Propose_Change_Set.md +52 -52
  56. package/knowzcode/prompts/[LOOP_1B]__Draft_Specs.md +75 -75
  57. package/knowzcode/prompts/[LOOP_2A]__Implement_Change_Set.md +55 -55
  58. package/knowzcode/prompts/[LOOP_2B]__Verify_Implementation.md +72 -72
  59. package/knowzcode/prompts/[LOOP_3]__Finalize_And_Commit.md +67 -67
  60. package/knowzcode/specs/Readme.md +10 -10
  61. package/knowzcode/telemetry_config.md +89 -89
  62. package/knowzcode/user_preferences.md +120 -120
  63. package/package.json +53 -53
  64. package/skills/alias-resolver.json +15 -15
  65. package/skills/architecture-diff.json +12 -12
  66. package/skills/check-installation-status.json +14 -14
  67. package/skills/continue.md +126 -126
  68. package/skills/environment-guard.json +12 -12
  69. package/skills/generate-workgroup-id.json +25 -25
  70. package/skills/install-knowzcode.json +21 -21
  71. package/skills/load-core-context.json +18 -18
  72. package/skills/log-entry-builder.json +15 -15
  73. package/skills/spec-quality-check.json +14 -14
  74. package/skills/spec-template.json +15 -15
  75. package/skills/spec-validator.json +25 -25
  76. package/skills/start-work.md +224 -224
  77. package/skills/tracker-scan.json +12 -12
  78. package/skills/tracker-update.json +28 -28
  79. package/skills/validate-installation.json +14 -14
package/bin/knowzcode.mjs CHANGED
@@ -1,1199 +1,1801 @@
1
- #!/usr/bin/env node
2
-
3
- // KnowzCode CLI — Zero-dependency Node.js installer
4
- // Usage: npx knowzcode [install|uninstall|upgrade|detect] [options]
5
-
6
- import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, readdirSync, rmSync, statSync } from 'fs';
7
- import { join, resolve, dirname, basename } from 'path';
8
- import { fileURLToPath } from 'url';
9
- import { createInterface } from 'readline/promises';
10
-
11
- const __filename = fileURLToPath(import.meta.url);
12
- const __dirname = dirname(__filename);
13
- const PKG_ROOT = resolve(__dirname, '..');
14
- const VERSION = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8')).version;
15
-
16
- // ─── Colors ──────────────────────────────────────────────────────────────────
17
-
18
- const c = {
19
- reset: '\x1b[0m',
20
- bold: '\x1b[1m',
21
- dim: '\x1b[2m',
22
- red: '\x1b[31m',
23
- green: '\x1b[32m',
24
- yellow: '\x1b[33m',
25
- blue: '\x1b[34m',
26
- cyan: '\x1b[36m',
27
- };
28
-
29
- const log = {
30
- info: (msg) => console.log(`${c.blue}[INFO]${c.reset} ${msg}`),
31
- ok: (msg) => console.log(`${c.green}[OK]${c.reset} ${msg}`),
32
- warn: (msg) => console.log(`${c.yellow}[WARN]${c.reset} ${msg}`),
33
- err: (msg) => console.error(`${c.red}[ERROR]${c.reset} ${msg}`),
34
- };
35
-
36
- // ─── Platform Definitions ────────────────────────────────────────────────────
37
-
38
- const PLATFORMS = {
39
- claude: {
40
- name: 'Claude Code',
41
- detect: (dir) => existsSync(join(dir, '.claude')) || existsSync(join(dir, '.claude-plugin')),
42
- adapterPath: null, // Claude uses .claude/ dir structure, not a single adapter file
43
- },
44
- codex: {
45
- name: 'OpenAI Codex',
46
- detect: (dir) => existsSync(join(dir, 'AGENTS.md')) || existsSync(join(dir, 'AGENTS.override.md')) || existsSync(join(dir, '.codex')),
47
- adapterPath: (dir) => join(dir, 'AGENTS.md'),
48
- templateHeader: '## OpenAI Codex (AGENTS.md)',
49
- },
50
- gemini: {
51
- name: 'Gemini CLI',
52
- detect: (dir) => existsSync(join(dir, 'GEMINI.md')) || existsSync(join(dir, '.gemini')),
53
- adapterPath: (dir) => join(dir, 'GEMINI.md'),
54
- templateHeader: '## Google Gemini CLI (GEMINI.md)',
55
- },
56
- cursor: {
57
- name: 'Cursor',
58
- detect: (dir) => existsSync(join(dir, '.cursor', 'rules')) || existsSync(join(dir, '.cursorrules')),
59
- adapterPath: (dir) => join(dir, '.cursor', 'rules', 'knowzcode.mdc'),
60
- templateHeader: '## Cursor (`.cursor/rules/knowzcode.mdc`)',
61
- },
62
- copilot: {
63
- name: 'GitHub Copilot',
64
- detect: (dir) => existsSync(join(dir, '.github', 'copilot-instructions.md')) || existsSync(join(dir, '.github')),
65
- adapterPath: (dir) => join(dir, '.github', 'copilot-instructions.md'),
66
- templateHeader: '## GitHub Copilot',
67
- },
68
- windsurf: {
69
- name: 'Windsurf',
70
- detect: (dir) => existsSync(join(dir, '.windsurf', 'rules')) || existsSync(join(dir, '.windsurfrules')),
71
- adapterPath: (dir) => join(dir, '.windsurf', 'rules', 'knowzcode.md'),
72
- templateHeader: '## Windsurf (`.windsurf/rules/knowzcode.md`)',
73
- },
74
- };
75
-
76
- // ─── CLI Argument Parser ─────────────────────────────────────────────────────
77
-
78
- function parseArgs(argv) {
79
- const args = argv.slice(2);
80
- const opts = {
81
- command: null,
82
- target: process.cwd(),
83
- platforms: [],
84
- force: false,
85
- global: false,
86
- verbose: false,
87
- agentTeams: false,
88
- };
89
-
90
- let i = 0;
91
- while (i < args.length) {
92
- const arg = args[i];
93
- if (arg === '--target' && i + 1 < args.length) {
94
- opts.target = resolve(args[++i]);
95
- } else if (arg === '--platforms' && i + 1 < args.length) {
96
- opts.platforms = args[++i].split(',').map((p) => p.trim().toLowerCase());
97
- } else if (arg === '--force') {
98
- opts.force = true;
99
- } else if (arg === '--global') {
100
- opts.global = true;
101
- } else if (arg === '--agent-teams') {
102
- opts.agentTeams = true;
103
- } else if (arg === '--verbose') {
104
- opts.verbose = true;
105
- } else if (arg === '--help' || arg === '-h') {
106
- opts.command = 'help';
107
- } else if (arg === '--version' || arg === '-v') {
108
- opts.command = 'version';
109
- } else if (!arg.startsWith('-') && !opts.command) {
110
- opts.command = arg.toLowerCase();
111
- }
112
- i++;
113
- }
114
-
115
- return opts;
116
- }
117
-
118
- // ─── Platform Detection ──────────────────────────────────────────────────────
119
-
120
- function detectPlatforms(dir) {
121
- const detected = [];
122
- for (const [id, platform] of Object.entries(PLATFORMS)) {
123
- if (platform.detect(dir)) {
124
- detected.push(id);
125
- }
126
- }
127
- return detected;
128
- }
129
-
130
- // ─── Adapter Template Parser ─────────────────────────────────────────────────
131
- // Returns Map<platformId, { primary: string, files: Map<relativePath, { content, lang }> }>
132
-
133
- function injectVersion(content) {
134
- return content.replace(/vX\.Y\.Z/g, `v${VERSION}`);
135
- }
136
-
137
- function extractSection(content, headerIdx) {
138
- const afterHeader = content.slice(headerIdx);
139
- const nextSection = afterHeader.search(/\r?\n---\r?\n\r?\n## /);
140
- return nextSection !== -1 ? afterHeader.slice(0, nextSection) : afterHeader;
141
- }
142
-
143
- function extractFence(text, lang, startFrom = 0) {
144
- const marker = '```' + lang;
145
- const fenceStart = text.indexOf(marker, startFrom);
146
- if (fenceStart === -1) return null;
147
- const contentStart = text.indexOf('\n', fenceStart) + 1;
148
- const fenceEnd = text.indexOf('\n```', contentStart);
149
- if (fenceEnd === -1) return null;
150
- return { content: text.slice(contentStart, fenceEnd), endIdx: fenceEnd + 4 };
151
- }
152
-
153
- function parseCopilotSection(section) {
154
- const files = new Map();
155
-
156
- // Section A: copilot-instructions.md (first ```markdown before ### B.)
157
- const sectionBIdx = section.indexOf('### B.');
158
- const sectionA = sectionBIdx !== -1 ? section.slice(0, sectionBIdx) : section;
159
- const primaryFence = extractFence(sectionA, 'markdown');
160
- if (!primaryFence) return null;
161
-
162
- // Section B: prompt files (#### kc-*.prompt.md headers)
163
- const headerRegex = /#### (kc-[\w-]+\.prompt\.md)/g;
164
- const headers = [];
165
- let match;
166
- while ((match = headerRegex.exec(section)) !== null) {
167
- headers.push({ filename: match[1], index: match.index });
168
- }
169
-
170
- const sectionCIdx = section.indexOf('### C.');
171
- for (let i = 0; i < headers.length; i++) {
172
- const start = headers[i].index;
173
- const end = i + 1 < headers.length
174
- ? headers[i + 1].index
175
- : (sectionCIdx !== -1 && sectionCIdx > start ? sectionCIdx : section.length);
176
- const subSection = section.slice(start, end);
177
-
178
- const fenceOpen = subSection.indexOf('```markdown');
179
- if (fenceOpen === -1) continue;
180
- const contentStart = subSection.indexOf('\n', fenceOpen) + 1;
181
- // Use lastIndexOf to handle prompt files that contain inner code fences
182
- const lastFenceClose = subSection.lastIndexOf('\n```');
183
- if (lastFenceClose <= contentStart) continue;
184
-
185
- files.set(`.github/prompts/${headers[i].filename}`, {
186
- content: subSection.slice(contentStart, lastFenceClose),
187
- lang: 'markdown',
188
- });
189
- }
190
-
191
- // Section C: .vscode/mcp.json
192
- if (sectionCIdx !== -1) {
193
- const sectionDIdx = section.indexOf('### D.', sectionCIdx);
194
- const sectionC = section.slice(sectionCIdx, sectionDIdx !== -1 ? sectionDIdx : section.length);
195
- const jsonFence = extractFence(sectionC, 'json');
196
- if (jsonFence) {
197
- files.set('.vscode/mcp.json', { content: jsonFence.content, lang: 'json' });
198
- }
199
- }
200
-
201
- return { primary: primaryFence.content, files };
202
- }
203
-
204
- function parseGeminiSection(section) {
205
- const files = new Map();
206
-
207
- // Extract TOML blocks: ```toml fences with # .gemini/commands/kc/{name}.toml comment
208
- let searchFrom = 0;
209
- while (true) {
210
- const fenceStart = section.indexOf('```toml', searchFrom);
211
- if (fenceStart === -1) break;
212
- const contentStart = section.indexOf('\n', fenceStart) + 1;
213
- const fenceEnd = section.indexOf('\n```', contentStart);
214
- if (fenceEnd === -1) break;
215
- const tomlContent = section.slice(contentStart, fenceEnd);
216
- const pathMatch = tomlContent.match(/^# (\.gemini\/commands\/kc\/[\w-]+\.toml)/);
217
- if (pathMatch) {
218
- files.set(pathMatch[1], { content: tomlContent, lang: 'toml' });
219
- }
220
- searchFrom = fenceEnd + 4;
221
- }
222
-
223
- // Primary: ```markdown fence (GEMINI.md)
224
- const primaryFence = extractFence(section, 'markdown');
225
- if (!primaryFence) return null;
226
-
227
- return { primary: primaryFence.content, files };
228
- }
229
-
230
- function parseCodexSection(section) {
231
- const files = new Map();
232
-
233
- // Primary: first ```markdown fence (AGENTS.md)
234
- const primaryFence = extractFence(section, 'markdown');
235
- if (!primaryFence) return null;
236
-
237
- // Skill files: #### .codex/skills/kc/{name}.md headers
238
- const headerRegex = /#### (\.codex\/skills\/kc\/[\w-]+\.md)/g;
239
- const headers = [];
240
- let match;
241
- while ((match = headerRegex.exec(section)) !== null) {
242
- headers.push({ filepath: match[1], index: match.index });
243
- }
244
-
245
- for (let i = 0; i < headers.length; i++) {
246
- const start = headers[i].index;
247
- const end = i + 1 < headers.length ? headers[i + 1].index : section.length;
248
- const subSection = section.slice(start, end);
249
- const fence = extractFence(subSection, 'markdown');
250
- if (fence) {
251
- files.set(headers[i].filepath, { content: fence.content, lang: 'markdown' });
252
- }
253
- }
254
-
255
- return { primary: primaryFence.content, files };
256
- }
257
-
258
- function parseSimpleSection(section) {
259
- const primaryFence = extractFence(section, 'markdown');
260
- if (!primaryFence) return null;
261
- return { primary: primaryFence.content, files: new Map() };
262
- }
263
-
264
- function parseAdapterTemplates() {
265
- const adaptersPath = join(PKG_ROOT, 'knowzcode', 'platform_adapters.md');
266
- if (!existsSync(adaptersPath)) {
267
- log.warn('platform_adapters.md not found adapter generation will be skipped');
268
- return new Map();
269
- }
270
-
271
- const content = readFileSync(adaptersPath, 'utf8');
272
- const templates = new Map();
273
-
274
- for (const [id, platform] of Object.entries(PLATFORMS)) {
275
- if (!platform.templateHeader) continue;
276
-
277
- const headerIdx = content.indexOf(platform.templateHeader);
278
- if (headerIdx === -1) continue;
279
-
280
- const section = extractSection(content, headerIdx);
281
- let result;
282
- switch (id) {
283
- case 'copilot': result = parseCopilotSection(section); break;
284
- case 'gemini': result = parseGeminiSection(section); break;
285
- case 'codex': result = parseCodexSection(section); break;
286
- default: result = parseSimpleSection(section); break;
287
- }
288
- if (result) templates.set(id, result);
289
- }
290
-
291
- return templates;
292
- }
293
-
294
- // ─── File Copy Helpers ───────────────────────────────────────────────────────
295
-
296
- function ensureDir(dir) {
297
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
298
- }
299
-
300
- function copyDirContents(src, dst) {
301
- ensureDir(dst);
302
- if (!existsSync(src)) return;
303
-
304
- for (const entry of readdirSync(src, { withFileTypes: true })) {
305
- const srcPath = join(src, entry.name);
306
- const dstPath = join(dst, entry.name);
307
- if (entry.isDirectory()) {
308
- copyDirContents(srcPath, dstPath);
309
- } else {
310
- writeFileSync(dstPath, readFileSync(srcPath));
311
- }
312
- }
313
- }
314
-
315
- function listFilesRecursive(dir, base = dir) {
316
- const files = [];
317
- if (!existsSync(dir)) return files;
318
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
319
- const full = join(dir, entry.name);
320
- if (entry.isDirectory()) {
321
- files.push(...listFilesRecursive(full, base));
322
- } else {
323
- files.push(full);
324
- }
325
- }
326
- return files;
327
- }
328
-
329
- // ─── Marketplace Config ──────────────────────────────────────────────────────
330
-
331
- function setMarketplaceConfig(claudeDir) {
332
- ensureDir(claudeDir);
333
- const settingsFile = join(claudeDir, 'settings.json');
334
- let settings = {};
335
-
336
- if (existsSync(settingsFile)) {
337
- try {
338
- settings = JSON.parse(readFileSync(settingsFile, 'utf8'));
339
- } catch {
340
- settings = {};
341
- }
342
- }
343
-
344
- if (!settings.extraKnownMarketplaces) settings.extraKnownMarketplaces = {};
345
- settings.extraKnownMarketplaces.knowzcode = {
346
- source: { source: 'github', repo: 'knowz-io/knowzcode' },
347
- };
348
-
349
- writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
350
- }
351
-
352
- function removeMarketplaceConfig(claudeDir) {
353
- const settingsFile = join(claudeDir, 'settings.json');
354
- if (!existsSync(settingsFile)) return;
355
-
356
- try {
357
- const settings = JSON.parse(readFileSync(settingsFile, 'utf8'));
358
- if (settings.extraKnownMarketplaces && settings.extraKnownMarketplaces.knowzcode) {
359
- delete settings.extraKnownMarketplaces.knowzcode;
360
- writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
361
- }
362
- } catch {
363
- // Ignore parse errors
364
- }
365
- }
366
-
367
- // ─── Stale File Cleanup ─────────────────────────────────────────────────────
368
-
369
- function removeStaleFiles(sourceDir, targetDir) {
370
- if (!existsSync(targetDir) || !existsSync(sourceDir)) return;
371
-
372
- const sourceFiles = new Set(
373
- readdirSync(sourceDir)
374
- .filter((f) => f.endsWith('.md'))
375
- );
376
-
377
- for (const entry of readdirSync(targetDir)) {
378
- if (entry.endsWith('.md') && !sourceFiles.has(entry)) {
379
- const stale = join(targetDir, entry);
380
- if (existsSync(stale) && statSync(stale).isFile()) {
381
- log.info(`Removing stale file: ${stale}`);
382
- rmSync(stale, { force: true });
383
- }
384
- }
385
- }
386
- }
387
-
388
- // ─── Tracker & Log Initializers ──────────────────────────────────────────────
389
-
390
- function initTracker(filePath) {
391
- writeFileSync(filePath, `# KnowzCode - Status Map
392
-
393
- **Purpose:** This document tracks the development status of all implementable components (NodeIDs) defined in \`knowzcode_architecture.md\`.
394
-
395
- ---
396
- **Progress: 0%**
397
- ---
398
-
399
- | Status | WorkGroupID | Node ID | Label | Dependencies | Logical Grouping | Spec Link | Classification | Notes / Issues |
400
- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
401
- | | | | | | | | | |
402
-
403
- ---
404
- ### Status Legend:
405
-
406
- * ⚪️ **\`[TODO]\`**: Task is defined and ready to be picked up if dependencies are met.
407
- * 📝 **\`[NEEDS_SPEC]\`**: Node has been identified but requires a detailed specification.
408
- * ◆ **\`[WIP]\`**: Work In Progress. The KnowzCode AI Agent is currently working on this node.
409
- * 🟢 **\`[VERIFIED]\`**: Node has been implemented and verified.
410
- * ❗ **\`[ISSUE]\`**: A significant issue or blocker has been identified.
411
-
412
- ---
413
- *(This table will be populated as you define your architecture and NodeIDs.)*
414
- `);
415
- }
416
-
417
- function initLog(filePath) {
418
- const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
419
- writeFileSync(filePath, `# KnowzCode - Operational Record
420
-
421
- **Purpose:** Chronological record of significant events, decisions, and verification outcomes.
422
-
423
- ---
424
-
425
- ## Section 1: Operational Log
426
-
427
- ---
428
- **[NEWEST ENTRIES APPEAR HERE - DO NOT REMOVE THIS MARKER]**
429
- ---
430
- **Type:** SystemInitialization
431
- **Timestamp:** ${ts}
432
- **NodeID(s):** Project-Wide
433
- **Logged By:** knowzcode-cli
434
- **Details:**
435
- KnowzCode framework installed via \`npx knowzcode\`.
436
- - Framework files initialized
437
- - Ready for first feature
438
- ---
439
-
440
- ## Section 2: Reference Quality Criteria (ARC-Based Verification)
441
-
442
- ### Core Quality Criteria
443
- 1. **Maintainability:** Ease of modification, clarity of code and design.
444
- 2. **Reliability:** Robustness of error handling, fault tolerance.
445
- 3. **Testability:** Adequacy of unit test coverage, ease of testing.
446
- 4. **Performance:** Responsiveness, efficiency in resource utilization.
447
- 5. **Security:** Resistance to common vulnerabilities.
448
-
449
- ### Structural Criteria
450
- 6. **Readability:** Code clarity, adherence to naming conventions.
451
- 7. **Complexity Management:** Avoidance of overly complex logic.
452
- 8. **Modularity:** Adherence to Single Responsibility Principle.
453
- 9. **Code Duplication (DRY):** Minimization of redundant code.
454
- 10. **Standards Compliance:** Adherence to language best practices.
455
-
456
- *(Refer to these criteria during ARC-Based Verification.)*
457
- `);
458
- }
459
-
460
- // ─── Interactive Prompt ──────────────────────────────────────────────────────
461
-
462
- async function promptPlatforms(detected) {
463
- const rl = createInterface({ input: process.stdin, output: process.stdout });
464
- const ids = Object.keys(PLATFORMS);
465
-
466
- console.log('');
467
- console.log(`${c.bold}Select platforms to generate adapters for:${c.reset}`);
468
- console.log('');
469
- ids.forEach((id, i) => {
470
- const p = PLATFORMS[id];
471
- const tag = detected.includes(id) ? ` ${c.green}(detected)${c.reset}` : '';
472
- console.log(` [${i + 1}] ${p.name}${tag}`);
473
- });
474
- console.log(` [A] All platforms`);
475
- console.log(` [S] Skip adapters (core framework only)`);
476
- console.log('');
477
-
478
- const answer = await rl.question('Select platforms (comma-separated, e.g. 1,2): ');
479
- rl.close();
480
-
481
- const trimmed = answer.trim().toUpperCase();
482
- if (trimmed === 'S' || trimmed === '') return [];
483
- if (trimmed === 'A') return ids;
484
-
485
- const selected = [];
486
- for (const part of trimmed.split(',')) {
487
- const num = parseInt(part.trim(), 10);
488
- if (num >= 1 && num <= ids.length) {
489
- selected.push(ids[num - 1]);
490
- }
491
- }
492
- return [...new Set(selected)];
493
- }
494
-
495
- async function promptConfirm(message) {
496
- const rl = createInterface({ input: process.stdin, output: process.stdout });
497
- const answer = await rl.question(`${message} [y/N]: `);
498
- rl.close();
499
- return answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes';
500
- }
501
-
502
- // ─── Agent Teams Enablement ──────────────────────────────────────────────────
503
-
504
- function enableAgentTeams(claudeDir, isGlobal) {
505
- ensureDir(claudeDir);
506
- const settingsFile = join(claudeDir, isGlobal ? 'settings.json' : 'settings.local.json');
507
-
508
- let settings = {};
509
- if (existsSync(settingsFile)) {
510
- try {
511
- settings = JSON.parse(readFileSync(settingsFile, 'utf8'));
512
- } catch {
513
- settings = {};
514
- }
515
- }
516
-
517
- if (!settings.env) settings.env = {};
518
- settings.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = '1';
519
-
520
- writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
521
- log.ok(`Agent Teams enabled in ${settingsFile}`);
522
- }
523
-
524
- // ─── Commands ────────────────────────────────────────────────────────────────
525
-
526
- // DETECT
527
- function cmdDetect(opts) {
528
- const dir = opts.target;
529
- console.log('');
530
- console.log(`${c.bold}KnowzCode Platform Detection${c.reset}`);
531
- console.log(`${c.dim}Scanning: ${dir}${c.reset}`);
532
- console.log('');
533
-
534
- const detected = detectPlatforms(dir);
535
- const hasKnowzcode = existsSync(join(dir, 'knowzcode'));
536
-
537
- console.log(` KnowzCode framework: ${hasKnowzcode ? `${c.green}installed${c.reset}` : `${c.dim}not found${c.reset}`}`);
538
-
539
- if (hasKnowzcode) {
540
- const versionFile = join(dir, 'knowzcode', '.knowzcode-version');
541
- if (existsSync(versionFile)) {
542
- const ver = readFileSync(versionFile, 'utf8').trim();
543
- console.log(` Installed version: ${c.cyan}${ver}${c.reset}`);
544
- }
545
- }
546
-
547
- console.log('');
548
- console.log(` ${c.bold}Platforms:${c.reset}`);
549
-
550
- for (const [id, platform] of Object.entries(PLATFORMS)) {
551
- const found = detected.includes(id);
552
- const indicator = found ? `${c.green}detected${c.reset}` : `${c.dim}not detected${c.reset}`;
553
- console.log(` ${platform.name.padEnd(18)} ${indicator}`);
554
- }
555
-
556
- console.log('');
557
- if (detected.length === 0) {
558
- console.log(` No platforms detected. Run ${c.cyan}npx knowzcode install${c.reset} to set up.`);
559
- } else {
560
- console.log(` ${detected.length} platform(s) detected.`);
561
- }
562
- console.log('');
563
- }
564
-
565
- // INSTALL
566
- async function cmdInstall(opts) {
567
- const dir = opts.target;
568
- const kcDir = join(dir, 'knowzcode');
569
-
570
- console.log('');
571
- console.log(`${c.bold}KnowzCode Install${c.reset}`);
572
- console.log(`${c.dim}Target: ${dir}${c.reset}`);
573
- console.log('');
574
-
575
- // Check for existing installation
576
- if (existsSync(kcDir) && !opts.force) {
577
- log.warn('KnowzCode already installed at ' + kcDir);
578
- log.warn('Use --force to overwrite.');
579
- process.exit(1);
580
- }
581
-
582
- if (!existsSync(dir)) {
583
- log.err('Target directory does not exist: ' + dir);
584
- process.exit(1);
585
- }
586
-
587
- // 1. Copy knowzcode/ template directory
588
- log.info('Installing core framework...');
589
- const srcKc = join(PKG_ROOT, 'knowzcode');
590
- ensureDir(kcDir);
591
- ensureDir(join(kcDir, 'specs'));
592
- ensureDir(join(kcDir, 'workgroups'));
593
- ensureDir(join(kcDir, 'prompts'));
594
-
595
- // Create workgroups/README.md (workgroups/ is gitignored and excluded from npm)
596
- writeFileSync(join(kcDir, 'workgroups', 'README.md'), '# WorkGroups\n\nSession-specific WorkGroup files are stored here.\nThis directory is gitignored — contents are local to each checkout.\n');
597
-
598
- // Copy .md files (skip tracker and log — generate fresh)
599
- for (const entry of readdirSync(srcKc)) {
600
- const srcPath = join(srcKc, entry);
601
- const stat = statSync(srcPath);
602
- if (stat.isFile() && entry.endsWith('.md') && entry !== 'knowzcode_tracker.md' && entry !== 'knowzcode_log.md') {
603
- writeFileSync(join(kcDir, entry), readFileSync(srcPath));
604
- } else if (stat.isFile() && !entry.endsWith('.md')) {
605
- // Copy non-md files, handling gitignore.template → .gitignore rename
606
- if (entry === 'gitignore.template') {
607
- writeFileSync(join(kcDir, '.gitignore'), readFileSync(srcPath));
608
- } else {
609
- writeFileSync(join(kcDir, entry), readFileSync(srcPath));
610
- }
611
- }
612
- }
613
-
614
- // Copy prompts/
615
- if (existsSync(join(srcKc, 'prompts'))) {
616
- copyDirContents(join(srcKc, 'prompts'), join(kcDir, 'prompts'));
617
- }
618
-
619
- // Copy specs readme
620
- if (existsSync(join(srcKc, 'specs', 'Readme.md'))) {
621
- writeFileSync(join(kcDir, 'specs', 'Readme.md'), readFileSync(join(srcKc, 'specs', 'Readme.md')));
622
- }
623
-
624
- // Copy enterprise/ if exists
625
- if (existsSync(join(srcKc, 'enterprise'))) {
626
- copyDirContents(join(srcKc, 'enterprise'), join(kcDir, 'enterprise'));
627
- }
628
-
629
- // Initialize fresh tracker and log
630
- initTracker(join(kcDir, 'knowzcode_tracker.md'));
631
- initLog(join(kcDir, 'knowzcode_log.md'));
632
-
633
- // Write version marker
634
- writeFileSync(join(kcDir, '.knowzcode-version'), VERSION + '\n');
635
-
636
- log.ok('Core framework installed');
637
-
638
- // 2. Platform detection + selection
639
- const detected = detectPlatforms(dir);
640
- let selectedPlatforms;
641
-
642
- if (opts.platforms.length > 0) {
643
- if (opts.platforms.includes('all')) {
644
- selectedPlatforms = Object.keys(PLATFORMS);
645
- } else {
646
- selectedPlatforms = opts.platforms.filter((p) => p in PLATFORMS);
647
- }
648
- } else if (opts.force) {
649
- // Non-interactive mode with --force: install for detected platforms only
650
- selectedPlatforms = detected;
651
- } else {
652
- selectedPlatforms = await promptPlatforms(detected);
653
- }
654
-
655
- // 3. Generate adapters
656
- const templates = parseAdapterTemplates();
657
- const adapterFiles = [];
658
-
659
- for (const platformId of selectedPlatforms) {
660
- const platform = PLATFORMS[platformId];
661
-
662
- if (platformId === 'claude') {
663
- // Claude Code: copy agents, commands, skills
664
- const claudeDir = opts.global ? join(process.env.HOME || process.env.USERPROFILE || '~', '.claude') : join(dir, '.claude');
665
-
666
- log.info(`Installing Claude Code components to ${claudeDir}/`);
667
-
668
- // Remove stale files before copying on --force
669
- if (opts.force) {
670
- removeStaleFiles(join(PKG_ROOT, 'commands'), join(claudeDir, 'commands'));
671
- removeStaleFiles(join(PKG_ROOT, 'agents'), join(claudeDir, 'agents'));
672
- removeStaleFiles(join(PKG_ROOT, 'skills'), join(claudeDir, 'skills'));
673
- }
674
-
675
- copyDirContents(join(PKG_ROOT, 'commands'), join(claudeDir, 'commands'));
676
- copyDirContents(join(PKG_ROOT, 'agents'), join(claudeDir, 'agents'));
677
- copyDirContents(join(PKG_ROOT, 'skills'), join(claudeDir, 'skills'));
678
-
679
- // Pre-register marketplace in settings.json
680
- setMarketplaceConfig(claudeDir);
681
-
682
- adapterFiles.push(claudeDir + '/commands/', claudeDir + '/agents/', claudeDir + '/skills/');
683
- } else {
684
- // Other platforms: extract template and write adapter + additional files
685
- const templateSet = templates.get(platformId);
686
- if (!templateSet) {
687
- log.warn(`No adapter template found for ${platform.name} — skipping`);
688
- continue;
689
- }
690
-
691
- // Write primary adapter file
692
- const adapterFile = platform.adapterPath(dir);
693
- ensureDir(dirname(adapterFile));
694
- writeFileSync(adapterFile, injectVersion(templateSet.primary));
695
- adapterFiles.push(adapterFile);
696
- log.ok(`${platform.name} adapter: ${adapterFile}`);
697
-
698
- // Write additional files (prompts, TOMLs, skills)
699
- for (const [relativePath, { content }] of templateSet.files) {
700
- let filePath;
701
- if (platformId === 'codex' && opts.global && relativePath.startsWith('.codex/skills/')) {
702
- const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
703
- filePath = join(homeDir, relativePath);
704
- } else {
705
- filePath = join(dir, relativePath);
706
- }
707
- ensureDir(dirname(filePath));
708
- writeFileSync(filePath, injectVersion(content));
709
- adapterFiles.push(filePath);
710
- }
711
- if (templateSet.files.size > 0) {
712
- log.ok(` + ${templateSet.files.size} additional file(s)`);
713
- }
714
- }
715
- }
716
-
717
- // 4. Agent Teams enablement
718
- const agentTeamsClaudeDir = opts.global
719
- ? join(process.env.HOME || process.env.USERPROFILE || '~', '.claude')
720
- : join(dir, '.claude');
721
- let agentTeamsEnabled = false;
722
- if (opts.agentTeams) {
723
- enableAgentTeams(agentTeamsClaudeDir, opts.global);
724
- agentTeamsEnabled = true;
725
- } else if (selectedPlatforms.includes('claude') && !opts.force) {
726
- // Interactive prompt for Claude Code users
727
- console.log('');
728
- console.log(`${c.bold}Agent Teams${c.reset} enables multi-agent coordination where specialized`);
729
- console.log(`teammates handle each workflow phase. ${c.dim}(experimental)${c.reset}`);
730
- const wantTeams = await promptConfirm('Enable Agent Teams? (recommended for Claude Code)');
731
- if (wantTeams) {
732
- enableAgentTeams(agentTeamsClaudeDir, opts.global);
733
- agentTeamsEnabled = true;
734
- }
735
- }
736
-
737
- // 5. Summary
738
- console.log('');
739
- console.log(`${c.green}${c.bold}Installation complete!${c.reset}`);
740
- console.log('');
741
- console.log(' Framework: ' + kcDir + '/');
742
- if (adapterFiles.length > 0) {
743
- console.log(' Adapters:');
744
- for (const f of adapterFiles) {
745
- console.log(' ' + f);
746
- }
747
- }
748
- if (agentTeamsEnabled) {
749
- console.log(' Agent Teams: enabled');
750
- }
751
- console.log('');
752
- console.log(`${c.bold}Next steps:${c.reset}`);
753
- console.log(' 1. Edit knowzcode/knowzcode_project.md — set project name, stack, standards');
754
- console.log(' 2. Edit knowzcode/environment_context.md — configure build/test commands');
755
- if (selectedPlatforms.includes('claude')) {
756
- console.log(' 3. Install the KnowzCode plugin (recommended):');
757
- console.log(' /plugin install kc@knowzcode');
758
- console.log(' 4. Start building:');
759
- console.log(' /kc:work "Your first feature"');
760
- console.log('');
761
- console.log(' Note: Commands also work without plugin as /work, /plan, /fix, etc.');
762
- } else {
763
- console.log(' 3. Start building: use knowzcode/prompts/[LOOP_1A]__Propose_Change_Set.md');
764
- }
765
- console.log('');
766
- }
767
-
768
- // UNINSTALL
769
- async function cmdUninstall(opts) {
770
- const dir = opts.target;
771
- const kcDir = join(dir, 'knowzcode');
772
-
773
- console.log('');
774
- console.log(`${c.bold}KnowzCode Uninstall${c.reset}`);
775
- console.log(`${c.dim}Target: ${dir}${c.reset}`);
776
- console.log('');
777
-
778
- // Scan for installed components
779
- const components = [];
780
-
781
- if (existsSync(kcDir)) {
782
- components.push({ label: 'Core framework', path: kcDir });
783
- }
784
-
785
- // Claude Code components
786
- const claudeDir = join(dir, '.claude');
787
- for (const sub of ['commands', 'agents', 'skills']) {
788
- const p = join(claudeDir, sub);
789
- if (existsSync(p)) {
790
- components.push({ label: `Claude Code ${sub}`, path: p });
791
- }
792
- }
793
-
794
- // Platform adapter files
795
- const adapterChecks = {
796
- codex: join(dir, 'AGENTS.md'),
797
- gemini: join(dir, 'GEMINI.md'),
798
- cursor: join(dir, '.cursor', 'rules', 'knowzcode.mdc'),
799
- copilot: join(dir, '.github', 'copilot-instructions.md'),
800
- windsurf: join(dir, '.windsurf', 'rules', 'knowzcode.md'),
801
- };
802
-
803
- for (const [id, path] of Object.entries(adapterChecks)) {
804
- if (existsSync(path)) {
805
- components.push({ label: `${PLATFORMS[id].name} adapter`, path });
806
- }
807
- }
808
-
809
- // Additional platform-specific files/directories
810
- const copilotPromptsDir = join(dir, '.github', 'prompts');
811
- if (existsSync(copilotPromptsDir)) {
812
- for (const f of readdirSync(copilotPromptsDir)) {
813
- if (f.startsWith('kc-') && f.endsWith('.prompt.md')) {
814
- components.push({ label: `Copilot prompt: ${f}`, path: join(copilotPromptsDir, f) });
815
- }
816
- }
817
- }
818
- const vscodeMcp = join(dir, '.vscode', 'mcp.json');
819
- if (existsSync(vscodeMcp)) {
820
- components.push({ label: 'VS Code MCP config', path: vscodeMcp });
821
- }
822
- const geminiCmdDir = join(dir, '.gemini', 'commands', 'kc');
823
- if (existsSync(geminiCmdDir)) {
824
- components.push({ label: 'Gemini commands (kc/)', path: geminiCmdDir });
825
- }
826
- const codexSkillDir = join(dir, '.codex', 'skills', 'kc');
827
- if (existsSync(codexSkillDir)) {
828
- components.push({ label: 'Codex skills (kc/)', path: codexSkillDir });
829
- }
830
- const globalCodexSkillDir = join(process.env.HOME || process.env.USERPROFILE || '~', '.codex', 'skills', 'kc');
831
- if (existsSync(globalCodexSkillDir)) {
832
- components.push({ label: 'Codex skills global (~/.codex/skills/kc/)', path: globalCodexSkillDir });
833
- }
834
-
835
- if (components.length === 0) {
836
- log.info('No KnowzCode installation found.');
837
- return;
838
- }
839
-
840
- console.log(' Components found:');
841
- for (const comp of components) {
842
- console.log(` ${comp.label}: ${comp.path}`);
843
- }
844
- console.log('');
845
-
846
- // Ask about preserving user data
847
- let preserveUserData = false;
848
- if (existsSync(kcDir) && !opts.force) {
849
- preserveUserData = await promptConfirm('Preserve user data (specs/, architecture, tracker, log)?');
850
- }
851
-
852
- if (!opts.force) {
853
- const confirmed = await promptConfirm('Remove all listed components?');
854
- if (!confirmed) {
855
- log.info('Uninstall cancelled.');
856
- return;
857
- }
858
- }
859
-
860
- const removed = [];
861
-
862
- // Remove components
863
- for (const comp of components) {
864
- if (comp.path === kcDir && preserveUserData) {
865
- // Selective removal keep user data
866
- const preserve = ['specs', 'knowzcode_architecture.md', 'knowzcode_tracker.md', 'knowzcode_log.md', 'knowzcode_project.md'];
867
-
868
- for (const entry of readdirSync(kcDir)) {
869
- if (preserve.includes(entry)) continue;
870
- const entryPath = join(kcDir, entry);
871
- rmSync(entryPath, { recursive: true, force: true });
872
- }
873
- removed.push(comp.label + ' (user data preserved)');
874
- } else {
875
- rmSync(comp.path, { recursive: true, force: true });
876
- removed.push(comp.label);
877
- }
878
- }
879
-
880
- // Clean up marketplace config from settings.json
881
- removeMarketplaceConfig(claudeDir);
882
-
883
- console.log('');
884
- log.ok('Uninstall complete');
885
- console.log(' Removed:');
886
- for (const r of removed) {
887
- console.log(` ${r}`);
888
- }
889
- console.log('');
890
- }
891
-
892
- // UPGRADE
893
- async function cmdUpgrade(opts) {
894
- const dir = opts.target;
895
- const kcDir = join(dir, 'knowzcode');
896
-
897
- console.log('');
898
- console.log(`${c.bold}KnowzCode Upgrade${c.reset}`);
899
- console.log(`${c.dim}Target: ${dir}${c.reset}`);
900
- console.log('');
901
-
902
- if (!existsSync(kcDir)) {
903
- log.err('No KnowzCode installation found. Run `npx knowzcode install` first.');
904
- process.exit(1);
905
- }
906
-
907
- // Read current version
908
- const versionFile = join(kcDir, '.knowzcode-version');
909
- const currentVersion = existsSync(versionFile) ? readFileSync(versionFile, 'utf8').trim() : 'unknown';
910
-
911
- if (currentVersion === VERSION && !opts.force) {
912
- log.info(`Already at version ${VERSION}. Use --force to reinstall.`);
913
- return;
914
- }
915
-
916
- log.info(`Upgrading: ${currentVersion} ${VERSION}`);
917
-
918
- // Files to preserve (never overwrite)
919
- const preserveFiles = new Set([
920
- 'knowzcode_tracker.md',
921
- 'knowzcode_log.md',
922
- 'knowzcode_architecture.md',
923
- 'knowzcode_project.md',
924
- 'environment_context.md',
925
- 'user_preferences.md',
926
- ]);
927
- const preserveDirs = new Set(['specs', 'workgroups']);
928
-
929
- // Files to replace (always update)
930
- const srcKc = join(PKG_ROOT, 'knowzcode');
931
-
932
- // Update .md files
933
- for (const entry of readdirSync(srcKc)) {
934
- const srcPath = join(srcKc, entry);
935
- const dstPath = join(kcDir, entry);
936
- const stat = statSync(srcPath);
937
-
938
- if (stat.isFile()) {
939
- if (preserveFiles.has(entry)) {
940
- if (opts.verbose) log.info(`Preserved: ${entry}`);
941
- continue;
942
- }
943
- // Handle gitignore.template → .gitignore rename
944
- if (entry === 'gitignore.template') {
945
- writeFileSync(join(kcDir, '.gitignore'), readFileSync(srcPath));
946
- if (opts.verbose) log.info('Updated: .gitignore (from gitignore.template)');
947
- } else {
948
- writeFileSync(dstPath, readFileSync(srcPath));
949
- if (opts.verbose) log.info(`Updated: ${entry}`);
950
- }
951
- }
952
- }
953
-
954
- // Update prompts/ (always replace)
955
- if (existsSync(join(srcKc, 'prompts'))) {
956
- const promptsDst = join(kcDir, 'prompts');
957
- // Remove old prompts, copy new ones
958
- if (existsSync(promptsDst)) rmSync(promptsDst, { recursive: true, force: true });
959
- copyDirContents(join(srcKc, 'prompts'), promptsDst);
960
- if (opts.verbose) log.info('Updated: prompts/');
961
- }
962
-
963
- // Update enterprise/ (always replace)
964
- if (existsSync(join(srcKc, 'enterprise'))) {
965
- const entDst = join(kcDir, 'enterprise');
966
- if (existsSync(entDst)) rmSync(entDst, { recursive: true, force: true });
967
- copyDirContents(join(srcKc, 'enterprise'), entDst);
968
- if (opts.verbose) log.info('Updated: enterprise/');
969
- }
970
-
971
- // Update Claude Code components if present
972
- const claudeDir = join(dir, '.claude');
973
- if (existsSync(join(claudeDir, 'commands')) || existsSync(join(claudeDir, 'agents'))) {
974
- log.info('Updating Claude Code components...');
975
- // Remove stale files before copying
976
- removeStaleFiles(join(PKG_ROOT, 'commands'), join(claudeDir, 'commands'));
977
- removeStaleFiles(join(PKG_ROOT, 'agents'), join(claudeDir, 'agents'));
978
- removeStaleFiles(join(PKG_ROOT, 'skills'), join(claudeDir, 'skills'));
979
- copyDirContents(join(PKG_ROOT, 'commands'), join(claudeDir, 'commands'));
980
- copyDirContents(join(PKG_ROOT, 'agents'), join(claudeDir, 'agents'));
981
- copyDirContents(join(PKG_ROOT, 'skills'), join(claudeDir, 'skills'));
982
- // Ensure marketplace config is up to date
983
- setMarketplaceConfig(claudeDir);
984
- }
985
-
986
- // Regenerate adapters for detected platforms
987
- const detected = detectPlatforms(dir);
988
- const templates = parseAdapterTemplates();
989
- const regenerated = [];
990
-
991
- for (const platformId of detected) {
992
- if (platformId === 'claude') continue; // Already handled above
993
- const platform = PLATFORMS[platformId];
994
- if (!platform.adapterPath) continue;
995
-
996
- const adapterFile = platform.adapterPath(dir);
997
- if (!existsSync(adapterFile)) continue; // Only update existing adapters
998
-
999
- const templateSet = templates.get(platformId);
1000
- if (!templateSet) continue;
1001
-
1002
- // Update primary adapter file
1003
- writeFileSync(adapterFile, injectVersion(templateSet.primary));
1004
- regenerated.push(platform.name);
1005
-
1006
- // Regenerate additional files
1007
- const currentPaths = new Set();
1008
- for (const [relativePath, { content }] of templateSet.files) {
1009
- const filePath = join(dir, relativePath);
1010
- ensureDir(dirname(filePath));
1011
- writeFileSync(filePath, injectVersion(content));
1012
- currentPaths.add(relativePath);
1013
- }
1014
-
1015
- // Stale file cleanup for platform-owned directories
1016
- if (platformId === 'copilot') {
1017
- const promptsDir = join(dir, '.github', 'prompts');
1018
- if (existsSync(promptsDir)) {
1019
- for (const f of readdirSync(promptsDir)) {
1020
- if (f.startsWith('kc-') && f.endsWith('.prompt.md') && !currentPaths.has(`.github/prompts/${f}`)) {
1021
- log.info(`Removing stale prompt: ${f}`);
1022
- rmSync(join(promptsDir, f), { force: true });
1023
- }
1024
- }
1025
- }
1026
- } else if (platformId === 'gemini') {
1027
- const tomlDir = join(dir, '.gemini', 'commands', 'kc');
1028
- if (existsSync(tomlDir)) {
1029
- for (const f of readdirSync(tomlDir)) {
1030
- if (f.endsWith('.toml') && !currentPaths.has(`.gemini/commands/kc/${f}`)) {
1031
- log.info(`Removing stale command: ${f}`);
1032
- rmSync(join(tomlDir, f), { force: true });
1033
- }
1034
- }
1035
- }
1036
- } else if (platformId === 'codex') {
1037
- const skillDir = join(dir, '.codex', 'skills', 'kc');
1038
- if (existsSync(skillDir)) {
1039
- for (const f of readdirSync(skillDir)) {
1040
- if (f.endsWith('.md') && !currentPaths.has(`.codex/skills/kc/${f}`)) {
1041
- log.info(`Removing stale skill: ${f}`);
1042
- rmSync(join(skillDir, f), { force: true });
1043
- }
1044
- }
1045
- }
1046
- }
1047
- }
1048
-
1049
- // Check for global codex skills
1050
- const globalCodexSkillDir = join(process.env.HOME || process.env.USERPROFILE || '~', '.codex', 'skills', 'kc');
1051
- if (existsSync(globalCodexSkillDir)) {
1052
- const codexTemplateSet = templates.get('codex');
1053
- if (codexTemplateSet) {
1054
- const currentPaths = new Set([...codexTemplateSet.files.keys()]);
1055
- // Stale cleanup
1056
- for (const f of readdirSync(globalCodexSkillDir)) {
1057
- if (f.endsWith('.md') && !currentPaths.has(`.codex/skills/kc/${f}`)) {
1058
- log.info(`Removing stale global skill: ${f}`);
1059
- rmSync(join(globalCodexSkillDir, f), { force: true });
1060
- }
1061
- }
1062
- // Regenerate global skills
1063
- for (const [relativePath, { content }] of codexTemplateSet.files) {
1064
- if (relativePath.startsWith('.codex/skills/')) {
1065
- const filePath = join(process.env.HOME || process.env.USERPROFILE || '~', relativePath);
1066
- ensureDir(dirname(filePath));
1067
- writeFileSync(filePath, injectVersion(content));
1068
- }
1069
- }
1070
- log.info('Updated global Codex skills');
1071
- }
1072
- }
1073
-
1074
- // Write new version
1075
- writeFileSync(versionFile, VERSION + '\n');
1076
-
1077
- console.log('');
1078
- log.ok(`Upgraded to ${VERSION}`);
1079
- console.log('');
1080
- console.log(` ${c.bold}Preserved:${c.reset} specs/, tracker, log, architecture, project config`);
1081
- console.log(` ${c.bold}Updated:${c.reset} loop, prompts, adapters, enterprise templates`);
1082
- if (regenerated.length > 0) {
1083
- console.log(` ${c.bold}Adapters:${c.reset} ${regenerated.join(', ')}`);
1084
- }
1085
- console.log('');
1086
- }
1087
-
1088
- // HELP
1089
- function cmdHelp() {
1090
- console.log(`
1091
- ${c.bold}KnowzCode CLI${c.reset} v${VERSION}
1092
- Platform-agnostic AI development methodology
1093
-
1094
- ${c.bold}Usage:${c.reset}
1095
- npx knowzcode Interactive mode
1096
- npx knowzcode install [options] Install to current/target directory
1097
- npx knowzcode uninstall [options] Remove KnowzCode
1098
- npx knowzcode upgrade [options] Upgrade preserving user data
1099
- npx knowzcode detect Show detected platforms (dry run)
1100
-
1101
- ${c.bold}Options:${c.reset}
1102
- --target <path> Target directory (default: current directory)
1103
- --platforms <list> Comma-separated: claude,codex,gemini,cursor,copilot,windsurf,all
1104
- --force Skip confirmation prompts
1105
- --global Install Claude Code to ~/.claude/, Codex skills to ~/.codex/
1106
- --agent-teams Enable Agent Teams in .claude/settings.local.json
1107
- --verbose Show detailed output
1108
- -h, --help Show this help
1109
- -v, --version Show version
1110
-
1111
- ${c.bold}Examples:${c.reset}
1112
- npx knowzcode install --platforms claude,cursor
1113
- npx knowzcode install --platforms all --force
1114
- npx knowzcode upgrade --target ./my-project
1115
- npx knowzcode uninstall --force
1116
- npx knowzcode detect
1117
- `);
1118
- }
1119
-
1120
- // INTERACTIVE
1121
- async function cmdInteractive(opts) {
1122
- console.log('');
1123
- console.log(`${c.bold} ╔═══════════════════════════════════════╗${c.reset}`);
1124
- console.log(`${c.bold} ║ KnowzCode v${VERSION.padEnd(14)} ║${c.reset}`);
1125
- console.log(`${c.bold} ║ AI Development Methodology Installer ║${c.reset}`);
1126
- console.log(`${c.bold} ╚═══════════════════════════════════════╝${c.reset}`);
1127
- console.log('');
1128
-
1129
- const dir = opts.target;
1130
- const kcDir = join(dir, 'knowzcode');
1131
- const detected = detectPlatforms(dir);
1132
-
1133
- if (detected.length > 0) {
1134
- log.info('Detected platforms: ' + detected.map((d) => PLATFORMS[d].name).join(', '));
1135
- }
1136
-
1137
- if (existsSync(kcDir)) {
1138
- const versionFile = join(kcDir, '.knowzcode-version');
1139
- const currentVersion = existsSync(versionFile) ? readFileSync(versionFile, 'utf8').trim() : 'unknown';
1140
-
1141
- if (currentVersion !== VERSION) {
1142
- log.info(`Installed version: ${currentVersion}, available: ${VERSION}`);
1143
- const doUpgrade = await promptConfirm('Upgrade to latest version?');
1144
- if (doUpgrade) {
1145
- return cmdUpgrade(opts);
1146
- }
1147
- } else {
1148
- log.info(`KnowzCode ${currentVersion} already installed.`);
1149
- const rl = createInterface({ input: process.stdin, output: process.stdout });
1150
- console.log('');
1151
- console.log(' [1] Reinstall (overwrite)');
1152
- console.log(' [2] Uninstall');
1153
- console.log(' [3] Detect platforms');
1154
- console.log(' [4] Exit');
1155
- console.log('');
1156
- const answer = await rl.question('Select action: ');
1157
- rl.close();
1158
-
1159
- const choice = answer.trim();
1160
- if (choice === '1') return cmdInstall({ ...opts, force: true });
1161
- if (choice === '2') return cmdUninstall(opts);
1162
- if (choice === '3') return cmdDetect(opts);
1163
- return;
1164
- }
1165
- } else {
1166
- log.info('No existing installation found. Starting install...');
1167
- console.log('');
1168
- return cmdInstall(opts);
1169
- }
1170
- }
1171
-
1172
- // ─── Main ────────────────────────────────────────────────────────────────────
1173
-
1174
- async function main() {
1175
- const opts = parseArgs(process.argv);
1176
-
1177
- switch (opts.command) {
1178
- case 'install':
1179
- return cmdInstall(opts);
1180
- case 'uninstall':
1181
- return cmdUninstall(opts);
1182
- case 'upgrade':
1183
- return cmdUpgrade(opts);
1184
- case 'detect':
1185
- return cmdDetect(opts);
1186
- case 'help':
1187
- return cmdHelp();
1188
- case 'version':
1189
- console.log(VERSION);
1190
- return;
1191
- default:
1192
- return cmdInteractive(opts);
1193
- }
1194
- }
1195
-
1196
- main().catch((err) => {
1197
- log.err(err.message);
1198
- process.exit(1);
1199
- });
1
+ #!/usr/bin/env node
2
+
3
+ // KnowzCode CLI — Zero-dependency Node.js installer
4
+ // Usage: npx knowzcode [install|uninstall|upgrade|detect] [options]
5
+
6
+ import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, readdirSync, rmSync, statSync } from 'fs';
7
+ import { join, resolve, dirname, basename } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { createInterface } from 'readline/promises';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+ const PKG_ROOT = resolve(__dirname, '..');
14
+ const VERSION = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8')).version;
15
+
16
+ // ─── Colors ──────────────────────────────────────────────────────────────────
17
+
18
+ const c = {
19
+ reset: '\x1b[0m',
20
+ bold: '\x1b[1m',
21
+ dim: '\x1b[2m',
22
+ red: '\x1b[31m',
23
+ green: '\x1b[32m',
24
+ yellow: '\x1b[33m',
25
+ blue: '\x1b[34m',
26
+ cyan: '\x1b[36m',
27
+ };
28
+
29
+ const log = {
30
+ info: (msg) => console.log(`${c.blue}[INFO]${c.reset} ${msg}`),
31
+ ok: (msg) => console.log(`${c.green}[OK]${c.reset} ${msg}`),
32
+ warn: (msg) => console.log(`${c.yellow}[WARN]${c.reset} ${msg}`),
33
+ err: (msg) => console.error(`${c.red}[ERROR]${c.reset} ${msg}`),
34
+ };
35
+
36
+ // ─── Platform Definitions ────────────────────────────────────────────────────
37
+
38
+ const PLATFORMS = {
39
+ claude: {
40
+ name: 'Claude Code',
41
+ detect: (dir) => existsSync(join(dir, '.claude')) || existsSync(join(dir, '.claude-plugin')),
42
+ adapterPath: null, // Claude uses .claude/ dir structure, not a single adapter file
43
+ },
44
+ codex: {
45
+ name: 'OpenAI Codex',
46
+ detect: (dir) => existsSync(join(dir, 'AGENTS.md')) || existsSync(join(dir, 'AGENTS.override.md')) || existsSync(join(dir, '.codex')) || existsSync(join(dir, '.agents')),
47
+ adapterPath: (dir) => join(dir, 'AGENTS.md'),
48
+ templateHeader: '## OpenAI Codex (AGENTS.md)',
49
+ },
50
+ gemini: {
51
+ name: 'Gemini CLI',
52
+ detect: (dir) => existsSync(join(dir, 'GEMINI.md')) || existsSync(join(dir, '.gemini')),
53
+ adapterPath: (dir) => join(dir, 'GEMINI.md'),
54
+ templateHeader: '## Google Gemini CLI (GEMINI.md)',
55
+ },
56
+ cursor: {
57
+ name: 'Cursor',
58
+ detect: (dir) => existsSync(join(dir, '.cursor', 'rules')) || existsSync(join(dir, '.cursorrules')),
59
+ adapterPath: (dir) => join(dir, '.cursor', 'rules', 'knowzcode.mdc'),
60
+ templateHeader: '## Cursor (`.cursor/rules/knowzcode.mdc`)',
61
+ },
62
+ copilot: {
63
+ name: 'GitHub Copilot',
64
+ detect: (dir) => existsSync(join(dir, '.github', 'copilot-instructions.md')) || existsSync(join(dir, '.github')),
65
+ adapterPath: (dir) => join(dir, '.github', 'copilot-instructions.md'),
66
+ templateHeader: '## GitHub Copilot',
67
+ },
68
+ windsurf: {
69
+ name: 'Windsurf',
70
+ detect: (dir) => existsSync(join(dir, '.windsurf', 'rules')) || existsSync(join(dir, '.windsurfrules')),
71
+ adapterPath: (dir) => join(dir, '.windsurf', 'rules', 'knowzcode.md'),
72
+ templateHeader: '## Windsurf (`.windsurf/rules/knowzcode.md`)',
73
+ },
74
+ };
75
+
76
+ // ─── CLI Argument Parser ─────────────────────────────────────────────────────
77
+
78
+ function parseArgs(argv) {
79
+ const args = argv.slice(2);
80
+ const opts = {
81
+ command: null,
82
+ target: process.cwd(),
83
+ platforms: [],
84
+ force: false,
85
+ clean: false,
86
+ global: false,
87
+ verbose: false,
88
+ agentTeams: false,
89
+ };
90
+
91
+ let i = 0;
92
+ while (i < args.length) {
93
+ const arg = args[i];
94
+ if (arg === '--target' && i + 1 < args.length) {
95
+ opts.target = resolve(args[++i]);
96
+ } else if (arg === '--platforms' && i + 1 < args.length) {
97
+ opts.platforms = args[++i].split(',').map((p) => p.trim().toLowerCase());
98
+ } else if (arg === '--force') {
99
+ opts.force = true;
100
+ } else if (arg === '--clean') {
101
+ opts.clean = true;
102
+ } else if (arg === '--global') {
103
+ opts.global = true;
104
+ } else if (arg === '--agent-teams') {
105
+ opts.agentTeams = true;
106
+ } else if (arg === '--verbose') {
107
+ opts.verbose = true;
108
+ } else if (arg === '--help' || arg === '-h') {
109
+ opts.command = 'help';
110
+ } else if (arg === '--version' || arg === '-v') {
111
+ opts.command = 'version';
112
+ } else if (!arg.startsWith('-') && !opts.command) {
113
+ opts.command = arg.toLowerCase();
114
+ }
115
+ i++;
116
+ }
117
+
118
+ return opts;
119
+ }
120
+
121
+ // ─── Platform Detection ──────────────────────────────────────────────────────
122
+
123
+ function detectPlatforms(dir) {
124
+ const detected = [];
125
+ for (const [id, platform] of Object.entries(PLATFORMS)) {
126
+ if (platform.detect(dir)) {
127
+ detected.push(id);
128
+ }
129
+ }
130
+ return detected;
131
+ }
132
+
133
+ // ─── Adapter Template Parser ─────────────────────────────────────────────────
134
+ // Returns Map<platformId, { primary: string, files: Map<relativePath, { content, lang }> }>
135
+
136
+ function injectVersion(content) {
137
+ return content.replace(/vX\.Y\.Z/g, `v${VERSION}`);
138
+ }
139
+
140
+ function extractSection(content, headerIdx) {
141
+ const afterHeader = content.slice(headerIdx);
142
+ const nextSection = afterHeader.search(/\r?\n---\r?\n\r?\n## /);
143
+ return nextSection !== -1 ? afterHeader.slice(0, nextSection) : afterHeader;
144
+ }
145
+
146
+ function extractFence(text, lang, startFrom = 0) {
147
+ const marker = '```' + lang;
148
+ const fenceStart = text.indexOf(marker, startFrom);
149
+ if (fenceStart === -1) return null;
150
+ const contentStart = text.indexOf('\n', fenceStart) + 1;
151
+ // Track nested fences to find the matching closing fence
152
+ let depth = 0;
153
+ let pos = contentStart;
154
+ while (pos < text.length) {
155
+ const nextFence = text.indexOf('\n```', pos);
156
+ if (nextFence === -1) return null;
157
+ const afterBackticks = nextFence + 4;
158
+ const charAfter = afterBackticks < text.length ? text[afterBackticks] : undefined;
159
+ if (charAfter && /\w/.test(charAfter)) {
160
+ // Opening fence (```bash, ```json, etc.)
161
+ depth++;
162
+ } else {
163
+ // Closing fence (``` followed by whitespace/newline/EOF)
164
+ if (depth === 0) {
165
+ return { content: text.slice(contentStart, nextFence), endIdx: afterBackticks };
166
+ }
167
+ depth--;
168
+ }
169
+ pos = afterBackticks;
170
+ }
171
+ return null;
172
+ }
173
+
174
+ function parseCopilotSection(section) {
175
+ const files = new Map();
176
+
177
+ // Section A: copilot-instructions.md (first ```markdown before ### B.)
178
+ const sectionBIdx = section.indexOf('### B.');
179
+ const sectionA = sectionBIdx !== -1 ? section.slice(0, sectionBIdx) : section;
180
+ const primaryFence = extractFence(sectionA, 'markdown');
181
+ if (!primaryFence) return null;
182
+
183
+ // Section B: prompt files (#### kc-*.prompt.md headers)
184
+ const headerRegex = /#### (kc-[\w-]+\.prompt\.md)/g;
185
+ const headers = [];
186
+ let match;
187
+ while ((match = headerRegex.exec(section)) !== null) {
188
+ headers.push({ filename: match[1], index: match.index });
189
+ }
190
+
191
+ const sectionCIdx = section.indexOf('### C.');
192
+ for (let i = 0; i < headers.length; i++) {
193
+ const start = headers[i].index;
194
+ const end = i + 1 < headers.length
195
+ ? headers[i + 1].index
196
+ : (sectionCIdx !== -1 && sectionCIdx > start ? sectionCIdx : section.length);
197
+ const subSection = section.slice(start, end);
198
+
199
+ const fenceOpen = subSection.indexOf('```markdown');
200
+ if (fenceOpen === -1) continue;
201
+ const contentStart = subSection.indexOf('\n', fenceOpen) + 1;
202
+ // Use lastIndexOf to handle prompt files that contain inner code fences
203
+ const lastFenceClose = subSection.lastIndexOf('\n```');
204
+ if (lastFenceClose <= contentStart) continue;
205
+
206
+ files.set(`.github/prompts/${headers[i].filename}`, {
207
+ content: subSection.slice(contentStart, lastFenceClose),
208
+ lang: 'markdown',
209
+ });
210
+ }
211
+
212
+ // Section C: .vscode/mcp.json
213
+ if (sectionCIdx !== -1) {
214
+ const sectionDIdx = section.indexOf('### D.', sectionCIdx);
215
+ const sectionC = section.slice(sectionCIdx, sectionDIdx !== -1 ? sectionDIdx : section.length);
216
+ const jsonFence = extractFence(sectionC, 'json');
217
+ if (jsonFence) {
218
+ files.set('.vscode/mcp.json', { content: jsonFence.content, lang: 'json' });
219
+ }
220
+ }
221
+
222
+ return { primary: primaryFence.content, files };
223
+ }
224
+
225
+ function parseGeminiSection(section) {
226
+ const files = new Map();
227
+
228
+ // Extract TOML blocks: ```toml fences with # .gemini/commands/kc/{name}.toml comment
229
+ let searchFrom = 0;
230
+ while (true) {
231
+ const fenceStart = section.indexOf('```toml', searchFrom);
232
+ if (fenceStart === -1) break;
233
+ const contentStart = section.indexOf('\n', fenceStart) + 1;
234
+ const fenceEnd = section.indexOf('\n```', contentStart);
235
+ if (fenceEnd === -1) break;
236
+ const tomlContent = section.slice(contentStart, fenceEnd);
237
+ const pathMatch = tomlContent.match(/^# (\.gemini\/commands\/kc\/[\w-]+\.toml)/);
238
+ if (pathMatch) {
239
+ files.set(pathMatch[1], { content: tomlContent, lang: 'toml' });
240
+ }
241
+ searchFrom = fenceEnd + 4;
242
+ }
243
+
244
+ // Skill files: #### .gemini/skills/kc-{name}/SKILL.md headers
245
+ const skillRegex = /#### (\.gemini\/skills\/kc-[\w-]+\/SKILL\.md)/g;
246
+ const skillHeaders = [];
247
+ let skillMatch;
248
+ while ((skillMatch = skillRegex.exec(section)) !== null) {
249
+ skillHeaders.push({ filepath: skillMatch[1], index: skillMatch.index });
250
+ }
251
+ // Subagent files: #### .gemini/agents/kc-{name}.md headers
252
+ const agentRegex = /#### (\.gemini\/agents\/kc-[\w-]+\.md)/g;
253
+ const agentHeaders = [];
254
+ let agentMatch;
255
+ while ((agentMatch = agentRegex.exec(section)) !== null) {
256
+ agentHeaders.push({ filepath: agentMatch[1], index: agentMatch.index });
257
+ }
258
+ // Combine all subsection headers for boundary detection
259
+ const allSubHeaders = [...skillHeaders, ...agentHeaders].sort((a, b) => a.index - b.index);
260
+
261
+ for (let i = 0; i < allSubHeaders.length; i++) {
262
+ const start = allSubHeaders[i].index;
263
+ const end = i + 1 < allSubHeaders.length ? allSubHeaders[i + 1].index : section.length;
264
+ const subSection = section.slice(start, end);
265
+ const fence = extractFence(subSection, 'markdown');
266
+ if (fence) {
267
+ files.set(allSubHeaders[i].filepath, { content: fence.content, lang: 'markdown' });
268
+ }
269
+ }
270
+
271
+ // Primary: ```markdown fence (GEMINI.md) — extract from content BEFORE first skill/subagent header
272
+ const firstSubHeader = allSubHeaders.length > 0 ? allSubHeaders[0].index : section.length;
273
+ const primarySection = section.slice(0, firstSubHeader);
274
+ const primaryFence = extractFence(primarySection, 'markdown');
275
+ if (!primaryFence) return null;
276
+
277
+ return { primary: primaryFence.content, files };
278
+ }
279
+
280
+ function parseCodexSection(section) {
281
+ const files = new Map();
282
+
283
+ // Primary: first ```markdown fence (AGENTS.md)
284
+ const primaryFence = extractFence(section, 'markdown');
285
+ if (!primaryFence) return null;
286
+
287
+ // Skill files: #### .agents/skills/kc-{name}/SKILL.md headers
288
+ const headerRegex = /#### (\.agents\/skills\/kc-[\w-]+\/SKILL\.md)/g;
289
+ const headers = [];
290
+ let match;
291
+ while ((match = headerRegex.exec(section)) !== null) {
292
+ headers.push({ filepath: match[1], index: match.index });
293
+ }
294
+
295
+ for (let i = 0; i < headers.length; i++) {
296
+ const start = headers[i].index;
297
+ const end = i + 1 < headers.length ? headers[i + 1].index : section.length;
298
+ const subSection = section.slice(start, end);
299
+ const fence = extractFence(subSection, 'markdown');
300
+ if (fence) {
301
+ files.set(headers[i].filepath, { content: fence.content, lang: 'markdown' });
302
+ }
303
+ }
304
+
305
+ return { primary: primaryFence.content, files };
306
+ }
307
+
308
+ function parseSimpleSection(section) {
309
+ const primaryFence = extractFence(section, 'markdown');
310
+ if (!primaryFence) return null;
311
+ return { primary: primaryFence.content, files: new Map() };
312
+ }
313
+
314
+ function parseAdapterTemplates() {
315
+ const adaptersPath = join(PKG_ROOT, 'knowzcode', 'platform_adapters.md');
316
+ if (!existsSync(adaptersPath)) {
317
+ log.warn('platform_adapters.md not found — adapter generation will be skipped');
318
+ return new Map();
319
+ }
320
+
321
+ const content = readFileSync(adaptersPath, 'utf8');
322
+ const templates = new Map();
323
+
324
+ for (const [id, platform] of Object.entries(PLATFORMS)) {
325
+ if (!platform.templateHeader) continue;
326
+
327
+ const headerIdx = content.indexOf(platform.templateHeader);
328
+ if (headerIdx === -1) continue;
329
+
330
+ const section = extractSection(content, headerIdx);
331
+ let result;
332
+ switch (id) {
333
+ case 'copilot': result = parseCopilotSection(section); break;
334
+ case 'gemini': result = parseGeminiSection(section); break;
335
+ case 'codex': result = parseCodexSection(section); break;
336
+ default: result = parseSimpleSection(section); break;
337
+ }
338
+ if (result) templates.set(id, result);
339
+ }
340
+
341
+ return templates;
342
+ }
343
+
344
+ // ─── File Copy Helpers ───────────────────────────────────────────────────────
345
+
346
+ function ensureDir(dir) {
347
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
348
+ }
349
+
350
+ function copyDirContents(src, dst) {
351
+ ensureDir(dst);
352
+ if (!existsSync(src)) return;
353
+
354
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
355
+ const srcPath = join(src, entry.name);
356
+ const dstPath = join(dst, entry.name);
357
+ if (entry.isDirectory()) {
358
+ copyDirContents(srcPath, dstPath);
359
+ } else {
360
+ writeFileSync(dstPath, readFileSync(srcPath));
361
+ }
362
+ }
363
+ }
364
+
365
+ function listFilesRecursive(dir, base = dir) {
366
+ const files = [];
367
+ if (!existsSync(dir)) return files;
368
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
369
+ const full = join(dir, entry.name);
370
+ if (entry.isDirectory()) {
371
+ files.push(...listFilesRecursive(full, base));
372
+ } else {
373
+ files.push(full);
374
+ }
375
+ }
376
+ return files;
377
+ }
378
+
379
+ // ─── Marketplace Config ──────────────────────────────────────────────────────
380
+
381
+ function setMarketplaceConfig(claudeDir) {
382
+ ensureDir(claudeDir);
383
+ const settingsFile = join(claudeDir, 'settings.json');
384
+ let settings = {};
385
+
386
+ if (existsSync(settingsFile)) {
387
+ try {
388
+ settings = JSON.parse(readFileSync(settingsFile, 'utf8'));
389
+ } catch {
390
+ settings = {};
391
+ }
392
+ }
393
+
394
+ if (!settings.extraKnownMarketplaces) settings.extraKnownMarketplaces = {};
395
+ settings.extraKnownMarketplaces.knowzcode = {
396
+ source: { source: 'github', repo: 'knowz-io/knowzcode' },
397
+ };
398
+
399
+ writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
400
+ }
401
+
402
+ function removeMarketplaceConfig(claudeDir) {
403
+ const settingsFile = join(claudeDir, 'settings.json');
404
+ if (!existsSync(settingsFile)) return;
405
+
406
+ try {
407
+ const settings = JSON.parse(readFileSync(settingsFile, 'utf8'));
408
+ if (settings.extraKnownMarketplaces && settings.extraKnownMarketplaces.knowzcode) {
409
+ delete settings.extraKnownMarketplaces.knowzcode;
410
+ writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
411
+ }
412
+ } catch {
413
+ // Ignore parse errors
414
+ }
415
+ }
416
+
417
+ // ─── Gemini MCP Config Helpers ────────────────────────────────────────────────
418
+
419
+ function writeGeminiMcpConfig(settingsPath, apiKey, projectPath, endpoint = 'https://mcp.knowz.io/mcp') {
420
+ ensureDir(dirname(settingsPath));
421
+ let settings = {};
422
+ if (existsSync(settingsPath)) {
423
+ try {
424
+ settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
425
+ } catch {
426
+ settings = {};
427
+ }
428
+ }
429
+ if (!settings.mcpServers) settings.mcpServers = {};
430
+ settings.mcpServers.knowz = {
431
+ url: endpoint,
432
+ headers: {
433
+ 'Authorization': `Bearer ${apiKey}`,
434
+ 'X-Project-Path': projectPath,
435
+ },
436
+ };
437
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
438
+ }
439
+
440
+ function removeGeminiMcpConfig(settingsPath) {
441
+ if (!existsSync(settingsPath)) return false;
442
+ try {
443
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
444
+ if (settings.mcpServers && settings.mcpServers.knowz) {
445
+ delete settings.mcpServers.knowz;
446
+ if (Object.keys(settings.mcpServers).length === 0) {
447
+ delete settings.mcpServers;
448
+ }
449
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
450
+ return true;
451
+ }
452
+ } catch {
453
+ // Ignore parse errors
454
+ }
455
+ return false;
456
+ }
457
+
458
+ function hasGeminiMcpConfig(settingsPath) {
459
+ if (!existsSync(settingsPath)) return false;
460
+ try {
461
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
462
+ return !!(settings.mcpServers && settings.mcpServers.knowz);
463
+ } catch {
464
+ return false;
465
+ }
466
+ }
467
+
468
+ // ─── Stale File Cleanup ─────────────────────────────────────────────────────
469
+
470
+ function removeStaleFiles(sourceDir, targetDir) {
471
+ if (!existsSync(targetDir) || !existsSync(sourceDir)) return;
472
+
473
+ const sourceFiles = new Set(
474
+ readdirSync(sourceDir)
475
+ .filter((f) => f.endsWith('.md'))
476
+ );
477
+
478
+ for (const entry of readdirSync(targetDir)) {
479
+ if (entry.endsWith('.md') && !sourceFiles.has(entry)) {
480
+ const stale = join(targetDir, entry);
481
+ if (existsSync(stale) && statSync(stale).isFile()) {
482
+ log.info(`Removing stale file: ${stale}`);
483
+ rmSync(stale, { force: true });
484
+ }
485
+ }
486
+ }
487
+ }
488
+
489
+ // ─── Tracker & Log Initializers ──────────────────────────────────────────────
490
+
491
+ function initTracker(filePath) {
492
+ writeFileSync(filePath, `# KnowzCode - Status Map
493
+
494
+ **Purpose:** This document tracks the development status of all implementable components (NodeIDs) defined in \`knowzcode_architecture.md\`.
495
+
496
+ ---
497
+ **Progress: 0%**
498
+ ---
499
+
500
+ | Status | WorkGroupID | Node ID | Label | Dependencies | Logical Grouping | Spec Link | Classification | Notes / Issues |
501
+ | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
502
+ | | | | | | | | | |
503
+
504
+ ---
505
+ ### Status Legend:
506
+
507
+ * ⚪️ **\`[TODO]\`**: Task is defined and ready to be picked up if dependencies are met.
508
+ * 📝 **\`[NEEDS_SPEC]\`**: Node has been identified but requires a detailed specification.
509
+ * ◆ **\`[WIP]\`**: Work In Progress. The KnowzCode AI Agent is currently working on this node.
510
+ * 🟢 **\`[VERIFIED]\`**: Node has been implemented and verified.
511
+ * ❗ **\`[ISSUE]\`**: A significant issue or blocker has been identified.
512
+
513
+ ---
514
+ *(This table will be populated as you define your architecture and NodeIDs.)*
515
+ `);
516
+ }
517
+
518
+ function initLog(filePath) {
519
+ const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
520
+ writeFileSync(filePath, `# KnowzCode - Operational Record
521
+
522
+ **Purpose:** Chronological record of significant events, decisions, and verification outcomes.
523
+
524
+ ---
525
+
526
+ ## Section 1: Operational Log
527
+
528
+ ---
529
+ **[NEWEST ENTRIES APPEAR HERE - DO NOT REMOVE THIS MARKER]**
530
+ ---
531
+ **Type:** SystemInitialization
532
+ **Timestamp:** ${ts}
533
+ **NodeID(s):** Project-Wide
534
+ **Logged By:** knowzcode-cli
535
+ **Details:**
536
+ KnowzCode framework installed via \`npx knowzcode\`.
537
+ - Framework files initialized
538
+ - Ready for first feature
539
+ ---
540
+
541
+ ## Section 2: Reference Quality Criteria (ARC-Based Verification)
542
+
543
+ ### Core Quality Criteria
544
+ 1. **Maintainability:** Ease of modification, clarity of code and design.
545
+ 2. **Reliability:** Robustness of error handling, fault tolerance.
546
+ 3. **Testability:** Adequacy of unit test coverage, ease of testing.
547
+ 4. **Performance:** Responsiveness, efficiency in resource utilization.
548
+ 5. **Security:** Resistance to common vulnerabilities.
549
+
550
+ ### Structural Criteria
551
+ 6. **Readability:** Code clarity, adherence to naming conventions.
552
+ 7. **Complexity Management:** Avoidance of overly complex logic.
553
+ 8. **Modularity:** Adherence to Single Responsibility Principle.
554
+ 9. **Code Duplication (DRY):** Minimization of redundant code.
555
+ 10. **Standards Compliance:** Adherence to language best practices.
556
+
557
+ *(Refer to these criteria during ARC-Based Verification.)*
558
+ `);
559
+ }
560
+
561
+ // ─── Interactive Prompt ──────────────────────────────────────────────────────
562
+
563
+ async function promptPlatforms(detected) {
564
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
565
+ const ids = Object.keys(PLATFORMS);
566
+
567
+ console.log('');
568
+ console.log(`${c.bold}Select platforms to generate adapters for:${c.reset}`);
569
+ console.log('');
570
+ ids.forEach((id, i) => {
571
+ const p = PLATFORMS[id];
572
+ const tag = detected.includes(id) ? ` ${c.green}(detected)${c.reset}` : '';
573
+ console.log(` [${i + 1}] ${p.name}${tag}`);
574
+ });
575
+ console.log(` [A] All platforms`);
576
+ console.log(` [S] Skip adapters (core framework only)`);
577
+ console.log('');
578
+
579
+ const answer = await rl.question('Select platforms (comma-separated, e.g. 1,2): ');
580
+ rl.close();
581
+
582
+ const trimmed = answer.trim().toUpperCase();
583
+ if (trimmed === 'S' || trimmed === '') return [];
584
+ if (trimmed === 'A') return ids;
585
+
586
+ const selected = [];
587
+ for (const part of trimmed.split(',')) {
588
+ const num = parseInt(part.trim(), 10);
589
+ if (num >= 1 && num <= ids.length) {
590
+ selected.push(ids[num - 1]);
591
+ }
592
+ }
593
+ return [...new Set(selected)];
594
+ }
595
+
596
+ async function promptConfirm(message) {
597
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
598
+ const answer = await rl.question(`${message} [y/N]: `);
599
+ rl.close();
600
+ return answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes';
601
+ }
602
+
603
+ // ─── Agent Teams Enablement ──────────────────────────────────────────────────
604
+
605
+ function enableAgentTeams(claudeDir, isGlobal) {
606
+ ensureDir(claudeDir);
607
+ const settingsFile = join(claudeDir, isGlobal ? 'settings.json' : 'settings.local.json');
608
+
609
+ let settings = {};
610
+ if (existsSync(settingsFile)) {
611
+ try {
612
+ settings = JSON.parse(readFileSync(settingsFile, 'utf8'));
613
+ } catch {
614
+ settings = {};
615
+ }
616
+ }
617
+
618
+ if (!settings.env) settings.env = {};
619
+ settings.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = '1';
620
+
621
+ writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
622
+ log.ok(`Agent Teams enabled in ${settingsFile}`);
623
+ }
624
+
625
+ // ─── Installation Scanner ────────────────────────────────────────────────────
626
+
627
+ function scanExistingInstallation(kcDir, dir) {
628
+ const result = {
629
+ version: null,
630
+ specs: [],
631
+ trackerEntries: 0,
632
+ logEntries: 0,
633
+ hasArchitecture: false,
634
+ hasProject: false,
635
+ hasPreferences: false,
636
+ hasOrchestration: false,
637
+ workgroups: [],
638
+ installedPlatforms: [],
639
+ customizedFiles: [],
640
+ };
641
+
642
+ // Version
643
+ const versionFile = join(kcDir, '.knowzcode-version');
644
+ if (existsSync(versionFile)) {
645
+ result.version = readFileSync(versionFile, 'utf8').trim();
646
+ }
647
+
648
+ // Specs
649
+ const specsDir = join(kcDir, 'specs');
650
+ if (existsSync(specsDir)) {
651
+ result.specs = readdirSync(specsDir).filter(f => f.endsWith('.md') && f !== 'Readme.md');
652
+ }
653
+
654
+ // Tracker entries (look for non-empty table rows — rows starting with |)
655
+ const trackerFile = join(kcDir, 'knowzcode_tracker.md');
656
+ if (existsSync(trackerFile)) {
657
+ const content = readFileSync(trackerFile, 'utf8');
658
+ const rows = content.split('\n').filter(line => /^\|[^:|\-]/.test(line) && !/^\| Status/.test(line) && line.trim() !== '| | | | | | | | | |');
659
+ result.trackerEntries = rows.length;
660
+ }
661
+
662
+ // Log entries (count --- delimited entries beyond SystemInitialization)
663
+ const logFile = join(kcDir, 'knowzcode_log.md');
664
+ if (existsSync(logFile)) {
665
+ const content = readFileSync(logFile, 'utf8');
666
+ const typeMatches = content.match(/\*\*Type:\*\*/g);
667
+ result.logEntries = typeMatches ? Math.max(0, typeMatches.length - 1) : 0; // -1 for SystemInitialization
668
+ }
669
+
670
+ // Architecture — check if edited (compare size to source template)
671
+ const archFile = join(kcDir, 'knowzcode_architecture.md');
672
+ const srcArch = join(PKG_ROOT, 'knowzcode', 'knowzcode_architecture.md');
673
+ if (existsSync(archFile)) {
674
+ const installedSize = statSync(archFile).size;
675
+ const templateSize = existsSync(srcArch) ? statSync(srcArch).size : 0;
676
+ result.hasArchitecture = installedSize !== templateSize;
677
+ }
678
+
679
+ // Project config
680
+ const projectFile = join(kcDir, 'knowzcode_project.md');
681
+ const srcProject = join(PKG_ROOT, 'knowzcode', 'knowzcode_project.md');
682
+ if (existsSync(projectFile)) {
683
+ const installedSize = statSync(projectFile).size;
684
+ const templateSize = existsSync(srcProject) ? statSync(srcProject).size : 0;
685
+ result.hasProject = installedSize !== templateSize;
686
+ }
687
+
688
+ // Preferences
689
+ result.hasPreferences = existsSync(join(kcDir, 'user_preferences.md'));
690
+
691
+ // Orchestration
692
+ const orchFile = join(kcDir, 'knowzcode_orchestration.md');
693
+ const srcOrch = join(PKG_ROOT, 'knowzcode', 'knowzcode_orchestration.md');
694
+ if (existsSync(orchFile)) {
695
+ const installedSize = statSync(orchFile).size;
696
+ const templateSize = existsSync(srcOrch) ? statSync(srcOrch).size : 0;
697
+ result.hasOrchestration = installedSize !== templateSize;
698
+ }
699
+
700
+ // Workgroups
701
+ const wgDir = join(kcDir, 'workgroups');
702
+ if (existsSync(wgDir)) {
703
+ result.workgroups = readdirSync(wgDir).filter(f => f !== 'README.md');
704
+ }
705
+
706
+ // Installed platforms
707
+ const adapterChecks = {
708
+ claude: () => existsSync(join(dir, '.claude', 'commands')) || existsSync(join(dir, '.claude', 'agents')),
709
+ codex: () => existsSync(join(dir, 'AGENTS.md')),
710
+ gemini: () => existsSync(join(dir, 'GEMINI.md')),
711
+ cursor: () => existsSync(join(dir, '.cursor', 'rules', 'knowzcode.mdc')),
712
+ copilot: () => existsSync(join(dir, '.github', 'copilot-instructions.md')),
713
+ windsurf: () => existsSync(join(dir, '.windsurf', 'rules', 'knowzcode.md')),
714
+ };
715
+ for (const [id, check] of Object.entries(adapterChecks)) {
716
+ if (check()) result.installedPlatforms.push(id);
717
+ }
718
+
719
+ // Customized files — compare framework .md files against source templates
720
+ const srcKc = join(PKG_ROOT, 'knowzcode');
721
+ const userEditable = ['knowzcode_architecture.md', 'knowzcode_project.md', 'environment_context.md', 'user_preferences.md', 'knowzcode_orchestration.md'];
722
+ for (const entry of userEditable) {
723
+ const installed = join(kcDir, entry);
724
+ const source = join(srcKc, entry);
725
+ if (existsSync(installed) && existsSync(source)) {
726
+ if (statSync(installed).size !== statSync(source).size) {
727
+ result.customizedFiles.push(entry);
728
+ }
729
+ }
730
+ }
731
+
732
+ return result;
733
+ }
734
+
735
+ function displayInstallationSummary(scan, dir) {
736
+ console.log(` ${c.bold}KnowzCode v${scan.version || 'unknown'} detected${c.reset}`);
737
+ console.log('');
738
+
739
+ // User data
740
+ const hasData = scan.specs.length > 0 || scan.trackerEntries > 0 || scan.logEntries > 0 ||
741
+ scan.hasArchitecture || scan.hasProject || scan.hasPreferences || scan.workgroups.length > 0;
742
+
743
+ if (hasData) {
744
+ console.log(' Your data:');
745
+ if (scan.specs.length > 0) {
746
+ const specNames = scan.specs.slice(0, 5).map(s => s.replace('.md', '')).join(', ');
747
+ const more = scan.specs.length > 5 ? `, +${scan.specs.length - 5} more` : '';
748
+ console.log(` ${String(scan.specs.length).padStart(2)} spec(s) (${specNames}${more})`);
749
+ }
750
+ if (scan.trackerEntries > 0) console.log(` ${String(scan.trackerEntries).padStart(2)} tracker entries`);
751
+ if (scan.logEntries > 0) console.log(` ${String(scan.logEntries).padStart(2)} log entries`);
752
+ if (scan.hasArchitecture) console.log(' Architecture customized');
753
+ if (scan.hasProject) console.log(' Project config customized');
754
+ if (scan.hasPreferences) console.log(' Preferences configured');
755
+ if (scan.hasOrchestration) console.log(' Orchestration customized');
756
+ if (scan.workgroups.length > 0) console.log(` ${String(scan.workgroups.length).padStart(2)} active workgroup(s)`);
757
+ } else {
758
+ console.log(' Your data: (no customizations detected)');
759
+ }
760
+
761
+ // Platforms
762
+ const detected = detectPlatforms(dir);
763
+ console.log('');
764
+ console.log(' Platforms:');
765
+ for (const [id, platform] of Object.entries(PLATFORMS)) {
766
+ const installed = scan.installedPlatforms.includes(id);
767
+ const det = detected.includes(id);
768
+ let status;
769
+ if (installed) status = `${c.green}installed${c.reset}`;
770
+ else if (det) status = `${c.yellow}detected (not installed)${c.reset}`;
771
+ else status = `${c.dim}not installed${c.reset}`;
772
+ console.log(` ${platform.name.padEnd(18)} ${status}`);
773
+ }
774
+ }
775
+
776
+ function isAdapterInstalled(platformId, dir) {
777
+ const checks = {
778
+ claude: () => existsSync(join(dir, '.claude', 'commands')) || existsSync(join(dir, '.claude', 'agents')),
779
+ codex: () => existsSync(join(dir, 'AGENTS.md')),
780
+ gemini: () => existsSync(join(dir, 'GEMINI.md')),
781
+ cursor: () => existsSync(join(dir, '.cursor', 'rules', 'knowzcode.mdc')),
782
+ copilot: () => existsSync(join(dir, '.github', 'copilot-instructions.md')),
783
+ windsurf: () => existsSync(join(dir, '.windsurf', 'rules', 'knowzcode.md')),
784
+ };
785
+ return checks[platformId] ? checks[platformId]() : false;
786
+ }
787
+
788
+ // ─── Adapter Generation (shared helper) ──────────────────────────────────────
789
+
790
+ async function generateAdapters(dir, selectedPlatforms, opts) {
791
+ const templates = parseAdapterTemplates();
792
+ const adapterFiles = [];
793
+ let agentTeamsEnabled = false;
794
+
795
+ for (const platformId of selectedPlatforms) {
796
+ const platform = PLATFORMS[platformId];
797
+
798
+ if (platformId === 'claude') {
799
+ const claudeDir = opts.global ? join(process.env.HOME || process.env.USERPROFILE || '~', '.claude') : join(dir, '.claude');
800
+
801
+ log.info(`Installing Claude Code components to ${claudeDir}/`);
802
+
803
+ if (opts.force) {
804
+ removeStaleFiles(join(PKG_ROOT, 'commands'), join(claudeDir, 'commands'));
805
+ removeStaleFiles(join(PKG_ROOT, 'agents'), join(claudeDir, 'agents'));
806
+ removeStaleFiles(join(PKG_ROOT, 'skills'), join(claudeDir, 'skills'));
807
+ }
808
+
809
+ copyDirContents(join(PKG_ROOT, 'commands'), join(claudeDir, 'commands'));
810
+ copyDirContents(join(PKG_ROOT, 'agents'), join(claudeDir, 'agents'));
811
+ copyDirContents(join(PKG_ROOT, 'skills'), join(claudeDir, 'skills'));
812
+ setMarketplaceConfig(claudeDir);
813
+ adapterFiles.push(claudeDir + '/commands/', claudeDir + '/agents/', claudeDir + '/skills/');
814
+ } else {
815
+ const templateSet = templates.get(platformId);
816
+ if (!templateSet) {
817
+ log.warn(`No adapter template found for ${platform.name} — skipping`);
818
+ continue;
819
+ }
820
+
821
+ const adapterFile = platform.adapterPath(dir);
822
+ ensureDir(dirname(adapterFile));
823
+ writeFileSync(adapterFile, injectVersion(templateSet.primary));
824
+ adapterFiles.push(adapterFile);
825
+ log.ok(`${platform.name} adapter: ${adapterFile}`);
826
+
827
+ for (const [relativePath, { content }] of templateSet.files) {
828
+ let filePath;
829
+ if (platformId === 'codex' && opts.global && relativePath.startsWith('.agents/skills/')) {
830
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
831
+ filePath = join(homeDir, relativePath);
832
+ } else if (platformId === 'gemini' && opts.global && relativePath.startsWith('.gemini/skills/')) {
833
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
834
+ filePath = join(homeDir, relativePath);
835
+ } else {
836
+ filePath = join(dir, relativePath);
837
+ }
838
+ ensureDir(dirname(filePath));
839
+ writeFileSync(filePath, injectVersion(content));
840
+ adapterFiles.push(filePath);
841
+ }
842
+ if (templateSet.files.size > 0) {
843
+ log.ok(` + ${templateSet.files.size} additional file(s)`);
844
+ }
845
+
846
+ if (platformId === 'codex') {
847
+ const legacySkillDir = join(dir, '.codex', 'skills', 'kc');
848
+ if (existsSync(legacySkillDir)) {
849
+ log.info('Removing legacy .codex/skills/kc/ (migrated to .agents/skills/)');
850
+ rmSync(legacySkillDir, { recursive: true, force: true });
851
+ }
852
+ if (opts.global) {
853
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
854
+ const legacyGlobal = join(homeDir, '.codex', 'skills', 'kc');
855
+ if (existsSync(legacyGlobal)) {
856
+ log.info('Removing legacy global ~/.codex/skills/kc/');
857
+ rmSync(legacyGlobal, { recursive: true, force: true });
858
+ }
859
+ }
860
+ }
861
+ }
862
+ }
863
+
864
+ // Gemini MCP config offer
865
+ if (selectedPlatforms.includes('gemini') && !opts.global && !opts.force) {
866
+ console.log('');
867
+ console.log(`${c.bold}Gemini MCP Configuration${c.reset}`);
868
+ console.log(`MCP enables vector search, vault access, and AI-powered Q&A.`);
869
+ const wantMcp = await promptConfirm('Configure MCP for Gemini CLI? (requires API key)');
870
+ if (wantMcp) {
871
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
872
+ const apiKey = await rl.question('Enter your KnowzCode API key: ');
873
+ rl.close();
874
+ if (apiKey.trim()) {
875
+ const settingsPath = join(dir, '.gemini', 'settings.json');
876
+ writeGeminiMcpConfig(settingsPath, apiKey.trim(), dir);
877
+ log.ok('Gemini MCP configured in .gemini/settings.json');
878
+ } else {
879
+ log.warn('No API key provided — skipping MCP config. Use /kc:connect-mcp later.');
880
+ }
881
+ }
882
+ }
883
+
884
+ // Agent Teams
885
+ const agentTeamsClaudeDir = opts.global
886
+ ? join(process.env.HOME || process.env.USERPROFILE || '~', '.claude')
887
+ : join(dir, '.claude');
888
+ if (opts.agentTeams) {
889
+ enableAgentTeams(agentTeamsClaudeDir, opts.global);
890
+ agentTeamsEnabled = true;
891
+ } else if (selectedPlatforms.includes('claude') && !opts.force) {
892
+ console.log('');
893
+ console.log(`${c.bold}Agent Teams${c.reset} enables multi-agent coordination where specialized`);
894
+ console.log(`teammates handle each workflow phase. ${c.dim}(experimental)${c.reset}`);
895
+ const wantTeams = await promptConfirm('Enable Agent Teams? (recommended for Claude Code)');
896
+ if (wantTeams) {
897
+ enableAgentTeams(agentTeamsClaudeDir, opts.global);
898
+ agentTeamsEnabled = true;
899
+ }
900
+ }
901
+
902
+ return { adapterFiles, agentTeamsEnabled };
903
+ }
904
+
905
+ // ─── Commands ────────────────────────────────────────────────────────────────
906
+
907
+ // DETECT
908
+ function cmdDetect(opts) {
909
+ const dir = opts.target;
910
+ console.log('');
911
+ console.log(`${c.bold}KnowzCode Platform Detection${c.reset}`);
912
+ console.log(`${c.dim}Scanning: ${dir}${c.reset}`);
913
+ console.log('');
914
+
915
+ const detected = detectPlatforms(dir);
916
+ const hasKnowzcode = existsSync(join(dir, 'knowzcode'));
917
+
918
+ console.log(` KnowzCode framework: ${hasKnowzcode ? `${c.green}installed${c.reset}` : `${c.dim}not found${c.reset}`}`);
919
+
920
+ if (hasKnowzcode) {
921
+ const versionFile = join(dir, 'knowzcode', '.knowzcode-version');
922
+ if (existsSync(versionFile)) {
923
+ const ver = readFileSync(versionFile, 'utf8').trim();
924
+ console.log(` Installed version: ${c.cyan}${ver}${c.reset}`);
925
+ }
926
+ }
927
+
928
+ console.log('');
929
+ console.log(` ${c.bold}Platforms:${c.reset}`);
930
+
931
+ for (const [id, platform] of Object.entries(PLATFORMS)) {
932
+ const found = detected.includes(id);
933
+ const indicator = found ? `${c.green}detected${c.reset}` : `${c.dim}not detected${c.reset}`;
934
+ console.log(` ${platform.name.padEnd(18)} ${indicator}`);
935
+ }
936
+
937
+ console.log('');
938
+ if (detected.length === 0) {
939
+ console.log(` No platforms detected. Run ${c.cyan}npx knowzcode install${c.reset} to set up.`);
940
+ } else {
941
+ console.log(` ${detected.length} platform(s) detected.`);
942
+ }
943
+ console.log('');
944
+ }
945
+
946
+ // INSTALL
947
+ async function cmdInstall(opts) {
948
+ const dir = opts.target;
949
+ const kcDir = join(dir, 'knowzcode');
950
+
951
+ console.log('');
952
+ console.log(`${c.bold}KnowzCode Install${c.reset}`);
953
+ console.log(`${c.dim}Target: ${dir}${c.reset}`);
954
+ console.log('');
955
+
956
+ // Check for existing installation — guided flow instead of hard exit
957
+ if (existsSync(kcDir) && !opts.force) {
958
+ const scan = scanExistingInstallation(kcDir, dir);
959
+ displayInstallationSummary(scan, dir);
960
+ console.log('');
961
+ console.log(' Options:');
962
+ console.log(' [1] Add/change platform adapters only');
963
+ console.log(' [2] Reinstall framework (preserves your data)');
964
+ console.log(' [3] Cancel');
965
+ console.log('');
966
+
967
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
968
+ const answer = await rl.question('Select: ');
969
+ rl.close();
970
+
971
+ const choice = answer.trim();
972
+ if (choice === '1') return cmdAddPlatforms(opts);
973
+ if (choice === '2') { opts.force = true; /* fall through to install */ }
974
+ else return;
975
+ }
976
+
977
+ if (!existsSync(dir)) {
978
+ log.err('Target directory does not exist: ' + dir);
979
+ process.exit(1);
980
+ }
981
+
982
+ // Detect reinstall preserve user data unless --clean
983
+ const isReinstall = existsSync(join(kcDir, '.knowzcode-version'));
984
+ const preserveFiles = isReinstall && !opts.clean ? new Set([
985
+ 'knowzcode_tracker.md', 'knowzcode_log.md',
986
+ 'knowzcode_architecture.md', 'knowzcode_project.md',
987
+ 'environment_context.md', 'user_preferences.md',
988
+ 'knowzcode_orchestration.md',
989
+ ]) : new Set();
990
+
991
+ // 1. Copy knowzcode/ template directory
992
+ log.info(isReinstall ? 'Reinstalling core framework...' : 'Installing core framework...');
993
+ const srcKc = join(PKG_ROOT, 'knowzcode');
994
+ ensureDir(kcDir);
995
+ ensureDir(join(kcDir, 'specs'));
996
+ ensureDir(join(kcDir, 'workgroups'));
997
+ ensureDir(join(kcDir, 'prompts'));
998
+
999
+ // Create workgroups/README.md (workgroups/ is gitignored and excluded from npm)
1000
+ writeFileSync(join(kcDir, 'workgroups', 'README.md'), '# WorkGroups\n\nSession-specific WorkGroup files are stored here.\nThis directory is gitignored — contents are local to each checkout.\n');
1001
+
1002
+ // Copy .md files (skip tracker and log — generate fresh; skip preserved files on reinstall)
1003
+ for (const entry of readdirSync(srcKc)) {
1004
+ const srcPath = join(srcKc, entry);
1005
+ const stat = statSync(srcPath);
1006
+ if (stat.isFile() && entry.endsWith('.md') && entry !== 'knowzcode_tracker.md' && entry !== 'knowzcode_log.md') {
1007
+ if (preserveFiles.has(entry) && existsSync(join(kcDir, entry))) {
1008
+ if (opts.verbose) log.info(`Preserved: ${entry}`);
1009
+ continue;
1010
+ }
1011
+ writeFileSync(join(kcDir, entry), readFileSync(srcPath));
1012
+ } else if (stat.isFile() && !entry.endsWith('.md')) {
1013
+ // Copy non-md files, handling gitignore.template → .gitignore rename
1014
+ if (entry === 'gitignore.template') {
1015
+ writeFileSync(join(kcDir, '.gitignore'), readFileSync(srcPath));
1016
+ } else {
1017
+ writeFileSync(join(kcDir, entry), readFileSync(srcPath));
1018
+ }
1019
+ }
1020
+ }
1021
+
1022
+ // Copy prompts/
1023
+ if (existsSync(join(srcKc, 'prompts'))) {
1024
+ copyDirContents(join(srcKc, 'prompts'), join(kcDir, 'prompts'));
1025
+ }
1026
+
1027
+ // Copy specs readme don't overwrite if specs/ already has user content
1028
+ const specsDir = join(kcDir, 'specs');
1029
+ const userSpecs = existsSync(specsDir) ? readdirSync(specsDir).filter(f => f.endsWith('.md') && f !== 'Readme.md') : [];
1030
+ if (userSpecs.length === 0 && existsSync(join(srcKc, 'specs', 'Readme.md'))) {
1031
+ writeFileSync(join(kcDir, 'specs', 'Readme.md'), readFileSync(join(srcKc, 'specs', 'Readme.md')));
1032
+ }
1033
+
1034
+ // Copy enterprise/ if exists
1035
+ if (existsSync(join(srcKc, 'enterprise'))) {
1036
+ copyDirContents(join(srcKc, 'enterprise'), join(kcDir, 'enterprise'));
1037
+ }
1038
+
1039
+ // Initialize tracker and log — only create fresh if not preserving
1040
+ if (!preserveFiles.has('knowzcode_tracker.md') || !existsSync(join(kcDir, 'knowzcode_tracker.md'))) {
1041
+ initTracker(join(kcDir, 'knowzcode_tracker.md'));
1042
+ }
1043
+ if (!preserveFiles.has('knowzcode_log.md') || !existsSync(join(kcDir, 'knowzcode_log.md'))) {
1044
+ initLog(join(kcDir, 'knowzcode_log.md'));
1045
+ }
1046
+
1047
+ // Write version marker
1048
+ writeFileSync(join(kcDir, '.knowzcode-version'), VERSION + '\n');
1049
+
1050
+ if (isReinstall && preserveFiles.size > 0) {
1051
+ const preserved = [];
1052
+ if (preserveFiles.has('knowzcode_tracker.md') && existsSync(join(kcDir, 'knowzcode_tracker.md'))) preserved.push('tracker');
1053
+ if (preserveFiles.has('knowzcode_log.md') && existsSync(join(kcDir, 'knowzcode_log.md'))) preserved.push('log');
1054
+ if (preserveFiles.has('knowzcode_architecture.md') && existsSync(join(kcDir, 'knowzcode_architecture.md'))) preserved.push('architecture');
1055
+ if (preserveFiles.has('knowzcode_project.md') && existsSync(join(kcDir, 'knowzcode_project.md'))) preserved.push('project config');
1056
+ if (preserveFiles.has('environment_context.md') && existsSync(join(kcDir, 'environment_context.md'))) preserved.push('environment');
1057
+ if (preserveFiles.has('user_preferences.md') && existsSync(join(kcDir, 'user_preferences.md'))) preserved.push('preferences');
1058
+ if (preserveFiles.has('knowzcode_orchestration.md') && existsSync(join(kcDir, 'knowzcode_orchestration.md'))) preserved.push('orchestration');
1059
+ log.ok(`Core framework reinstalled (preserved: ${preserved.join(', ')})`);
1060
+ } else {
1061
+ log.ok('Core framework installed');
1062
+ }
1063
+
1064
+ // 2. Platform detection + selection
1065
+ const detected = detectPlatforms(dir);
1066
+ let selectedPlatforms;
1067
+
1068
+ if (opts.platforms.length > 0) {
1069
+ if (opts.platforms.includes('all')) {
1070
+ selectedPlatforms = Object.keys(PLATFORMS);
1071
+ } else {
1072
+ selectedPlatforms = opts.platforms.filter((p) => p in PLATFORMS);
1073
+ }
1074
+ } else if (opts.force && opts.platforms.length === 0 && !isReinstall) {
1075
+ // Non-interactive mode with --force on fresh install: install for detected platforms only
1076
+ selectedPlatforms = detected;
1077
+ } else if (opts.force && opts.platforms.length === 0 && isReinstall) {
1078
+ // Reinstall: re-generate for already-installed platforms
1079
+ const scan = scanExistingInstallation(kcDir, dir);
1080
+ selectedPlatforms = scan.installedPlatforms.length > 0 ? scan.installedPlatforms : detected;
1081
+ } else {
1082
+ selectedPlatforms = await promptPlatforms(detected);
1083
+ }
1084
+
1085
+ // 3. Generate adapters (using shared helper)
1086
+ const { adapterFiles, agentTeamsEnabled } = await generateAdapters(dir, selectedPlatforms, opts);
1087
+
1088
+ // 4. Summary
1089
+ console.log('');
1090
+ console.log(`${c.green}${c.bold}Installation complete!${c.reset}`);
1091
+ console.log('');
1092
+ console.log(' Framework: ' + kcDir + '/');
1093
+ if (adapterFiles.length > 0) {
1094
+ console.log(' Adapters:');
1095
+ for (const f of adapterFiles) {
1096
+ console.log(' ' + f);
1097
+ }
1098
+ }
1099
+ if (agentTeamsEnabled) {
1100
+ console.log(' Agent Teams: enabled');
1101
+ }
1102
+ console.log('');
1103
+ console.log(`${c.bold}Next steps:${c.reset}`);
1104
+ if (!isReinstall) {
1105
+ console.log(' 1. Edit knowzcode/knowzcode_project.md set project name, stack, standards');
1106
+ console.log(' 2. Edit knowzcode/environment_context.md configure build/test commands');
1107
+ }
1108
+ if (selectedPlatforms.includes('claude')) {
1109
+ const step = isReinstall ? 1 : 3;
1110
+ console.log(` ${step}. Install the KnowzCode plugin (recommended):`);
1111
+ console.log(' /plugin install kc@knowzcode');
1112
+ console.log(` ${step + 1}. Start building:`);
1113
+ console.log(' /kc:work "Your first feature"');
1114
+ console.log('');
1115
+ console.log(' Note: Commands also work without plugin as /work, /plan, /fix, etc.');
1116
+ } else if (!isReinstall) {
1117
+ console.log(' 3. Start building: use knowzcode/prompts/[LOOP_1A]__Propose_Change_Set.md');
1118
+ }
1119
+ console.log('');
1120
+ }
1121
+
1122
+ // ADD-PLATFORMS
1123
+ async function cmdAddPlatforms(opts) {
1124
+ const dir = opts.target;
1125
+ const kcDir = join(dir, 'knowzcode');
1126
+
1127
+ console.log('');
1128
+ console.log(`${c.bold}KnowzCode — Add/Change Platforms${c.reset}`);
1129
+ console.log(`${c.dim}Target: ${dir}${c.reset}`);
1130
+ console.log('');
1131
+
1132
+ if (!existsSync(kcDir)) {
1133
+ log.err('No KnowzCode installation found. Run `npx knowzcode install` first.');
1134
+ process.exit(1);
1135
+ }
1136
+
1137
+ const scan = scanExistingInstallation(kcDir, dir);
1138
+ const detected = detectPlatforms(dir);
1139
+ const ids = Object.keys(PLATFORMS);
1140
+
1141
+ // Show platform status
1142
+ console.log(`${c.bold}Platform status:${c.reset}`);
1143
+ console.log('');
1144
+ ids.forEach((id, i) => {
1145
+ const p = PLATFORMS[id];
1146
+ const installed = scan.installedPlatforms.includes(id);
1147
+ const det = detected.includes(id);
1148
+ let tag = '';
1149
+ if (installed) tag = ` ${c.green}(installed)${c.reset}`;
1150
+ else if (det) tag = ` ${c.yellow}(detected)${c.reset}`;
1151
+ console.log(` [${i + 1}] ${p.name}${tag}`);
1152
+ });
1153
+ console.log(` [A] All platforms`);
1154
+ console.log(` [S] Cancel`);
1155
+ console.log('');
1156
+
1157
+ let selectedPlatforms;
1158
+ if (opts.platforms.length > 0) {
1159
+ if (opts.platforms.includes('all')) {
1160
+ selectedPlatforms = ids;
1161
+ } else {
1162
+ selectedPlatforms = opts.platforms.filter((p) => p in PLATFORMS);
1163
+ }
1164
+ } else {
1165
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1166
+ const answer = await rl.question('Select platforms (comma-separated, e.g. 1,2): ');
1167
+ rl.close();
1168
+
1169
+ const trimmed = answer.trim().toUpperCase();
1170
+ if (trimmed === 'S' || trimmed === '') return;
1171
+ if (trimmed === 'A') {
1172
+ selectedPlatforms = ids;
1173
+ } else {
1174
+ selectedPlatforms = [];
1175
+ for (const part of trimmed.split(',')) {
1176
+ const num = parseInt(part.trim(), 10);
1177
+ if (num >= 1 && num <= ids.length) selectedPlatforms.push(ids[num - 1]);
1178
+ }
1179
+ selectedPlatforms = [...new Set(selectedPlatforms)];
1180
+ }
1181
+ }
1182
+
1183
+ if (selectedPlatforms.length === 0) return;
1184
+
1185
+ // Confirm regeneration for already-installed platforms
1186
+ const toRegenerate = selectedPlatforms.filter(id => scan.installedPlatforms.includes(id));
1187
+ const toAdd = selectedPlatforms.filter(id => !scan.installedPlatforms.includes(id));
1188
+
1189
+ if (toRegenerate.length > 0 && !opts.force) {
1190
+ const names = toRegenerate.map(id => PLATFORMS[id].name).join(', ');
1191
+ const confirmed = await promptConfirm(`${names} already installed. Regenerate adapter(s)?`);
1192
+ if (!confirmed) {
1193
+ // Only generate the new ones
1194
+ selectedPlatforms = toAdd;
1195
+ if (selectedPlatforms.length === 0) return;
1196
+ }
1197
+ }
1198
+
1199
+ const { adapterFiles, agentTeamsEnabled } = await generateAdapters(dir, selectedPlatforms, opts);
1200
+
1201
+ console.log('');
1202
+ log.ok('Platform adapters updated');
1203
+ if (adapterFiles.length > 0) {
1204
+ for (const f of adapterFiles) {
1205
+ console.log(' ' + f);
1206
+ }
1207
+ }
1208
+ if (agentTeamsEnabled) {
1209
+ console.log(' Agent Teams: enabled');
1210
+ }
1211
+ console.log('');
1212
+ }
1213
+
1214
+ // UNINSTALL
1215
+ async function cmdUninstall(opts) {
1216
+ const dir = opts.target;
1217
+ const kcDir = join(dir, 'knowzcode');
1218
+
1219
+ console.log('');
1220
+ console.log(`${c.bold}KnowzCode Uninstall${c.reset}`);
1221
+ console.log(`${c.dim}Target: ${dir}${c.reset}`);
1222
+ console.log('');
1223
+
1224
+ // Scan for installed components
1225
+ const components = [];
1226
+
1227
+ if (existsSync(kcDir)) {
1228
+ components.push({ label: 'Core framework', path: kcDir });
1229
+ }
1230
+
1231
+ // Claude Code components
1232
+ const claudeDir = join(dir, '.claude');
1233
+ for (const sub of ['commands', 'agents', 'skills']) {
1234
+ const p = join(claudeDir, sub);
1235
+ if (existsSync(p)) {
1236
+ components.push({ label: `Claude Code ${sub}`, path: p });
1237
+ }
1238
+ }
1239
+
1240
+ // Platform adapter files
1241
+ const adapterChecks = {
1242
+ codex: join(dir, 'AGENTS.md'),
1243
+ gemini: join(dir, 'GEMINI.md'),
1244
+ cursor: join(dir, '.cursor', 'rules', 'knowzcode.mdc'),
1245
+ copilot: join(dir, '.github', 'copilot-instructions.md'),
1246
+ windsurf: join(dir, '.windsurf', 'rules', 'knowzcode.md'),
1247
+ };
1248
+
1249
+ for (const [id, path] of Object.entries(adapterChecks)) {
1250
+ if (existsSync(path)) {
1251
+ components.push({ label: `${PLATFORMS[id].name} adapter`, path });
1252
+ }
1253
+ }
1254
+
1255
+ // Additional platform-specific files/directories
1256
+ const copilotPromptsDir = join(dir, '.github', 'prompts');
1257
+ if (existsSync(copilotPromptsDir)) {
1258
+ for (const f of readdirSync(copilotPromptsDir)) {
1259
+ if (f.startsWith('kc-') && f.endsWith('.prompt.md')) {
1260
+ components.push({ label: `Copilot prompt: ${f}`, path: join(copilotPromptsDir, f) });
1261
+ }
1262
+ }
1263
+ }
1264
+ const vscodeMcp = join(dir, '.vscode', 'mcp.json');
1265
+ if (existsSync(vscodeMcp)) {
1266
+ components.push({ label: 'VS Code MCP config', path: vscodeMcp });
1267
+ }
1268
+ const geminiCmdDir = join(dir, '.gemini', 'commands', 'kc');
1269
+ if (existsSync(geminiCmdDir)) {
1270
+ components.push({ label: 'Gemini commands (kc/)', path: geminiCmdDir });
1271
+ }
1272
+ // Gemini skills: .gemini/skills/kc-*
1273
+ const geminiSkillDir = join(dir, '.gemini', 'skills');
1274
+ if (existsSync(geminiSkillDir)) {
1275
+ for (const entry of readdirSync(geminiSkillDir)) {
1276
+ if (entry.startsWith('kc-')) {
1277
+ components.push({ label: `Gemini skill (${entry}/)`, path: join(geminiSkillDir, entry) });
1278
+ }
1279
+ }
1280
+ }
1281
+ const globalGeminiSkillDir = join(process.env.HOME || process.env.USERPROFILE || '~', '.gemini', 'skills');
1282
+ if (existsSync(globalGeminiSkillDir)) {
1283
+ for (const entry of readdirSync(globalGeminiSkillDir)) {
1284
+ if (entry.startsWith('kc-')) {
1285
+ components.push({ label: `Gemini skill — global (~/.gemini/skills/${entry}/)`, path: join(globalGeminiSkillDir, entry) });
1286
+ }
1287
+ }
1288
+ }
1289
+ // Gemini subagents: .gemini/agents/kc-*.md
1290
+ const geminiAgentDir = join(dir, '.gemini', 'agents');
1291
+ if (existsSync(geminiAgentDir)) {
1292
+ for (const entry of readdirSync(geminiAgentDir)) {
1293
+ if (entry.startsWith('kc-') && entry.endsWith('.md')) {
1294
+ components.push({ label: `Gemini subagent (${entry})`, path: join(geminiAgentDir, entry) });
1295
+ }
1296
+ }
1297
+ }
1298
+ // New path: .agents/skills/kc-*
1299
+ const agentsSkillDir = join(dir, '.agents', 'skills');
1300
+ if (existsSync(agentsSkillDir)) {
1301
+ for (const entry of readdirSync(agentsSkillDir)) {
1302
+ if (entry.startsWith('kc-')) {
1303
+ components.push({ label: `Codex skill (${entry}/)`, path: join(agentsSkillDir, entry) });
1304
+ }
1305
+ }
1306
+ }
1307
+ const globalAgentsSkillDir = join(process.env.HOME || process.env.USERPROFILE || '~', '.agents', 'skills');
1308
+ if (existsSync(globalAgentsSkillDir)) {
1309
+ for (const entry of readdirSync(globalAgentsSkillDir)) {
1310
+ if (entry.startsWith('kc-')) {
1311
+ components.push({ label: `Codex skill — global (~/.agents/skills/${entry}/)`, path: join(globalAgentsSkillDir, entry) });
1312
+ }
1313
+ }
1314
+ }
1315
+ // Legacy path: .codex/skills/kc (remove on uninstall)
1316
+ const legacyCodexSkillDir = join(dir, '.codex', 'skills', 'kc');
1317
+ if (existsSync(legacyCodexSkillDir)) {
1318
+ components.push({ label: 'Codex skills — legacy (.codex/skills/kc/)', path: legacyCodexSkillDir });
1319
+ }
1320
+ const legacyGlobalCodexSkillDir = join(process.env.HOME || process.env.USERPROFILE || '~', '.codex', 'skills', 'kc');
1321
+ if (existsSync(legacyGlobalCodexSkillDir)) {
1322
+ components.push({ label: 'Codex skills — legacy global (~/.codex/skills/kc/)', path: legacyGlobalCodexSkillDir });
1323
+ }
1324
+
1325
+ if (components.length === 0) {
1326
+ log.info('No KnowzCode installation found.');
1327
+ return;
1328
+ }
1329
+
1330
+ console.log(' Components found:');
1331
+ for (const comp of components) {
1332
+ console.log(` ${comp.label}: ${comp.path}`);
1333
+ }
1334
+ console.log('');
1335
+
1336
+ // Ask about preserving user data
1337
+ let preserveUserData = false;
1338
+ if (existsSync(kcDir) && !opts.force) {
1339
+ preserveUserData = await promptConfirm('Preserve user data (specs/, architecture, tracker, log)?');
1340
+ }
1341
+
1342
+ if (!opts.force) {
1343
+ const confirmed = await promptConfirm('Remove all listed components?');
1344
+ if (!confirmed) {
1345
+ log.info('Uninstall cancelled.');
1346
+ return;
1347
+ }
1348
+ }
1349
+
1350
+ const removed = [];
1351
+
1352
+ // Remove components
1353
+ for (const comp of components) {
1354
+ if (comp.path === kcDir && preserveUserData) {
1355
+ // Selective removal — keep user data
1356
+ const preserve = ['specs', 'knowzcode_architecture.md', 'knowzcode_tracker.md', 'knowzcode_log.md', 'knowzcode_project.md'];
1357
+
1358
+ for (const entry of readdirSync(kcDir)) {
1359
+ if (preserve.includes(entry)) continue;
1360
+ const entryPath = join(kcDir, entry);
1361
+ rmSync(entryPath, { recursive: true, force: true });
1362
+ }
1363
+ removed.push(comp.label + ' (user data preserved)');
1364
+ } else {
1365
+ rmSync(comp.path, { recursive: true, force: true });
1366
+ removed.push(comp.label);
1367
+ }
1368
+ }
1369
+
1370
+ // Clean up marketplace config from settings.json
1371
+ removeMarketplaceConfig(claudeDir);
1372
+
1373
+ // Clean up Gemini MCP config (remove only knowz entry, preserve other settings)
1374
+ const geminiSettingsProject = join(dir, '.gemini', 'settings.json');
1375
+ if (removeGeminiMcpConfig(geminiSettingsProject)) {
1376
+ removed.push('Gemini MCP config (.gemini/settings.json)');
1377
+ }
1378
+ const homeDir2 = process.env.HOME || process.env.USERPROFILE || '~';
1379
+ const geminiSettingsUser = join(homeDir2, '.gemini', 'settings.json');
1380
+ if (removeGeminiMcpConfig(geminiSettingsUser)) {
1381
+ removed.push('Gemini MCP config (~/.gemini/settings.json)');
1382
+ }
1383
+
1384
+ console.log('');
1385
+ log.ok('Uninstall complete');
1386
+ console.log(' Removed:');
1387
+ for (const r of removed) {
1388
+ console.log(` ${r}`);
1389
+ }
1390
+ console.log('');
1391
+ }
1392
+
1393
+ // UPGRADE
1394
+ async function cmdUpgrade(opts) {
1395
+ const dir = opts.target;
1396
+ const kcDir = join(dir, 'knowzcode');
1397
+
1398
+ console.log('');
1399
+ console.log(`${c.bold}KnowzCode Upgrade${c.reset}`);
1400
+ console.log(`${c.dim}Target: ${dir}${c.reset}`);
1401
+ console.log('');
1402
+
1403
+ if (!existsSync(kcDir)) {
1404
+ log.err('No KnowzCode installation found. Run `npx knowzcode install` first.');
1405
+ process.exit(1);
1406
+ }
1407
+
1408
+ // Read current version
1409
+ const versionFile = join(kcDir, '.knowzcode-version');
1410
+ const currentVersion = existsSync(versionFile) ? readFileSync(versionFile, 'utf8').trim() : 'unknown';
1411
+
1412
+ if (currentVersion === VERSION && !opts.force) {
1413
+ log.info(`Already at version ${VERSION}. Use --force to reinstall.`);
1414
+ return;
1415
+ }
1416
+
1417
+ log.info(`Upgrading: ${currentVersion} → ${VERSION}`);
1418
+
1419
+ // Files to preserve (never overwrite)
1420
+ const preserveFiles = new Set([
1421
+ 'knowzcode_tracker.md',
1422
+ 'knowzcode_log.md',
1423
+ 'knowzcode_architecture.md',
1424
+ 'knowzcode_project.md',
1425
+ 'environment_context.md',
1426
+ 'user_preferences.md',
1427
+ ]);
1428
+ const preserveDirs = new Set(['specs', 'workgroups']);
1429
+
1430
+ // Files to replace (always update)
1431
+ const srcKc = join(PKG_ROOT, 'knowzcode');
1432
+
1433
+ // Update .md files
1434
+ for (const entry of readdirSync(srcKc)) {
1435
+ const srcPath = join(srcKc, entry);
1436
+ const dstPath = join(kcDir, entry);
1437
+ const stat = statSync(srcPath);
1438
+
1439
+ if (stat.isFile()) {
1440
+ if (preserveFiles.has(entry)) {
1441
+ if (opts.verbose) log.info(`Preserved: ${entry}`);
1442
+ continue;
1443
+ }
1444
+ // Handle gitignore.template → .gitignore rename
1445
+ if (entry === 'gitignore.template') {
1446
+ writeFileSync(join(kcDir, '.gitignore'), readFileSync(srcPath));
1447
+ if (opts.verbose) log.info('Updated: .gitignore (from gitignore.template)');
1448
+ } else {
1449
+ writeFileSync(dstPath, readFileSync(srcPath));
1450
+ if (opts.verbose) log.info(`Updated: ${entry}`);
1451
+ }
1452
+ }
1453
+ }
1454
+
1455
+ // Update prompts/ (always replace)
1456
+ if (existsSync(join(srcKc, 'prompts'))) {
1457
+ const promptsDst = join(kcDir, 'prompts');
1458
+ // Remove old prompts, copy new ones
1459
+ if (existsSync(promptsDst)) rmSync(promptsDst, { recursive: true, force: true });
1460
+ copyDirContents(join(srcKc, 'prompts'), promptsDst);
1461
+ if (opts.verbose) log.info('Updated: prompts/');
1462
+ }
1463
+
1464
+ // Update enterprise/ (always replace)
1465
+ if (existsSync(join(srcKc, 'enterprise'))) {
1466
+ const entDst = join(kcDir, 'enterprise');
1467
+ if (existsSync(entDst)) rmSync(entDst, { recursive: true, force: true });
1468
+ copyDirContents(join(srcKc, 'enterprise'), entDst);
1469
+ if (opts.verbose) log.info('Updated: enterprise/');
1470
+ }
1471
+
1472
+ // Update Claude Code components if present
1473
+ const claudeDir = join(dir, '.claude');
1474
+ if (existsSync(join(claudeDir, 'commands')) || existsSync(join(claudeDir, 'agents'))) {
1475
+ log.info('Updating Claude Code components...');
1476
+ // Remove stale files before copying
1477
+ removeStaleFiles(join(PKG_ROOT, 'commands'), join(claudeDir, 'commands'));
1478
+ removeStaleFiles(join(PKG_ROOT, 'agents'), join(claudeDir, 'agents'));
1479
+ removeStaleFiles(join(PKG_ROOT, 'skills'), join(claudeDir, 'skills'));
1480
+ copyDirContents(join(PKG_ROOT, 'commands'), join(claudeDir, 'commands'));
1481
+ copyDirContents(join(PKG_ROOT, 'agents'), join(claudeDir, 'agents'));
1482
+ copyDirContents(join(PKG_ROOT, 'skills'), join(claudeDir, 'skills'));
1483
+ // Ensure marketplace config is up to date
1484
+ setMarketplaceConfig(claudeDir);
1485
+ }
1486
+
1487
+ // Regenerate adapters for detected platforms
1488
+ const detected = detectPlatforms(dir);
1489
+ const templates = parseAdapterTemplates();
1490
+ const regenerated = [];
1491
+
1492
+ for (const platformId of detected) {
1493
+ if (platformId === 'claude') continue; // Already handled above
1494
+ const platform = PLATFORMS[platformId];
1495
+ if (!platform.adapterPath) continue;
1496
+
1497
+ const adapterFile = platform.adapterPath(dir);
1498
+ if (!existsSync(adapterFile)) continue; // Only update existing adapters
1499
+
1500
+ const templateSet = templates.get(platformId);
1501
+ if (!templateSet) continue;
1502
+
1503
+ // Update primary adapter file
1504
+ writeFileSync(adapterFile, injectVersion(templateSet.primary));
1505
+ regenerated.push(platform.name);
1506
+
1507
+ // Regenerate additional files
1508
+ const currentPaths = new Set();
1509
+ for (const [relativePath, { content }] of templateSet.files) {
1510
+ const filePath = join(dir, relativePath);
1511
+ ensureDir(dirname(filePath));
1512
+ writeFileSync(filePath, injectVersion(content));
1513
+ currentPaths.add(relativePath);
1514
+ }
1515
+
1516
+ // Stale file cleanup for platform-owned directories
1517
+ if (platformId === 'copilot') {
1518
+ const promptsDir = join(dir, '.github', 'prompts');
1519
+ if (existsSync(promptsDir)) {
1520
+ for (const f of readdirSync(promptsDir)) {
1521
+ if (f.startsWith('kc-') && f.endsWith('.prompt.md') && !currentPaths.has(`.github/prompts/${f}`)) {
1522
+ log.info(`Removing stale prompt: ${f}`);
1523
+ rmSync(join(promptsDir, f), { force: true });
1524
+ }
1525
+ }
1526
+ }
1527
+ } else if (platformId === 'gemini') {
1528
+ const tomlDir = join(dir, '.gemini', 'commands', 'kc');
1529
+ if (existsSync(tomlDir)) {
1530
+ for (const f of readdirSync(tomlDir)) {
1531
+ if (f.endsWith('.toml') && !currentPaths.has(`.gemini/commands/kc/${f}`)) {
1532
+ log.info(`Removing stale command: ${f}`);
1533
+ rmSync(join(tomlDir, f), { force: true });
1534
+ }
1535
+ }
1536
+ }
1537
+ // Stale skill cleanup: .gemini/skills/kc-*
1538
+ const geminiSkillDir = join(dir, '.gemini', 'skills');
1539
+ if (existsSync(geminiSkillDir)) {
1540
+ for (const entry of readdirSync(geminiSkillDir)) {
1541
+ if (entry.startsWith('kc-') && !currentPaths.has(`.gemini/skills/${entry}/SKILL.md`)) {
1542
+ log.info(`Removing stale Gemini skill: ${entry}/`);
1543
+ rmSync(join(geminiSkillDir, entry), { recursive: true, force: true });
1544
+ }
1545
+ }
1546
+ }
1547
+ // Stale subagent cleanup: .gemini/agents/kc-*.md
1548
+ const geminiAgentDir = join(dir, '.gemini', 'agents');
1549
+ if (existsSync(geminiAgentDir)) {
1550
+ for (const entry of readdirSync(geminiAgentDir)) {
1551
+ if (entry.startsWith('kc-') && entry.endsWith('.md') && !currentPaths.has(`.gemini/agents/${entry}`)) {
1552
+ log.info(`Removing stale Gemini subagent: ${entry}`);
1553
+ rmSync(join(geminiAgentDir, entry), { force: true });
1554
+ }
1555
+ }
1556
+ }
1557
+ } else if (platformId === 'codex') {
1558
+ const skillDir = join(dir, '.agents', 'skills');
1559
+ if (existsSync(skillDir)) {
1560
+ for (const entry of readdirSync(skillDir)) {
1561
+ if (entry.startsWith('kc-') && !currentPaths.has(`.agents/skills/${entry}/SKILL.md`)) {
1562
+ log.info(`Removing stale skill: ${entry}/`);
1563
+ rmSync(join(skillDir, entry), { recursive: true, force: true });
1564
+ }
1565
+ }
1566
+ }
1567
+ // Migration: remove legacy .codex/skills/kc/ if present
1568
+ const legacySkillDir = join(dir, '.codex', 'skills', 'kc');
1569
+ if (existsSync(legacySkillDir)) {
1570
+ log.info('Removing legacy .codex/skills/kc/ (migrated to .agents/skills/)');
1571
+ rmSync(legacySkillDir, { recursive: true, force: true });
1572
+ }
1573
+ }
1574
+ }
1575
+
1576
+ // Check for global codex skills
1577
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
1578
+ const globalAgentsSkillDir = join(homeDir, '.agents', 'skills');
1579
+ if (existsSync(globalAgentsSkillDir)) {
1580
+ const codexTemplateSet = templates.get('codex');
1581
+ if (codexTemplateSet) {
1582
+ const currentPaths = new Set([...codexTemplateSet.files.keys()]);
1583
+ // Stale cleanup
1584
+ for (const entry of readdirSync(globalAgentsSkillDir)) {
1585
+ if (entry.startsWith('kc-') && !currentPaths.has(`.agents/skills/${entry}/SKILL.md`)) {
1586
+ log.info(`Removing stale global skill: ${entry}/`);
1587
+ rmSync(join(globalAgentsSkillDir, entry), { recursive: true, force: true });
1588
+ }
1589
+ }
1590
+ // Regenerate global skills
1591
+ for (const [relativePath, { content }] of codexTemplateSet.files) {
1592
+ if (relativePath.startsWith('.agents/skills/')) {
1593
+ const filePath = join(homeDir, relativePath);
1594
+ ensureDir(dirname(filePath));
1595
+ writeFileSync(filePath, injectVersion(content));
1596
+ }
1597
+ }
1598
+ log.info('Updated global Codex skills');
1599
+ }
1600
+ }
1601
+ // Migration: remove legacy global .codex/skills/kc/ if present
1602
+ const legacyGlobalSkillDir = join(homeDir, '.codex', 'skills', 'kc');
1603
+ if (existsSync(legacyGlobalSkillDir)) {
1604
+ log.info('Removing legacy global ~/.codex/skills/kc/ (migrated to ~/.agents/skills/)');
1605
+ rmSync(legacyGlobalSkillDir, { recursive: true, force: true });
1606
+ }
1607
+
1608
+ // Check for global Gemini skills
1609
+ const globalGeminiSkillDir = join(homeDir, '.gemini', 'skills');
1610
+ if (existsSync(globalGeminiSkillDir)) {
1611
+ const geminiTemplateSet = templates.get('gemini');
1612
+ if (geminiTemplateSet) {
1613
+ const currentPaths = new Set([...geminiTemplateSet.files.keys()]);
1614
+ // Stale cleanup
1615
+ for (const entry of readdirSync(globalGeminiSkillDir)) {
1616
+ if (entry.startsWith('kc-') && !currentPaths.has(`.gemini/skills/${entry}/SKILL.md`)) {
1617
+ log.info(`Removing stale global Gemini skill: ${entry}/`);
1618
+ rmSync(join(globalGeminiSkillDir, entry), { recursive: true, force: true });
1619
+ }
1620
+ }
1621
+ // Regenerate global skills
1622
+ for (const [relativePath, { content }] of geminiTemplateSet.files) {
1623
+ if (relativePath.startsWith('.gemini/skills/')) {
1624
+ const filePath = join(homeDir, relativePath);
1625
+ ensureDir(dirname(filePath));
1626
+ writeFileSync(filePath, injectVersion(content));
1627
+ }
1628
+ }
1629
+ log.info('Updated global Gemini skills');
1630
+ }
1631
+ }
1632
+
1633
+ // Preserve Gemini MCP config during upgrade (don't overwrite user's API key)
1634
+ const geminiSettingsPath = join(dir, '.gemini', 'settings.json');
1635
+ const geminiMcpPreserved = hasGeminiMcpConfig(geminiSettingsPath);
1636
+ if (geminiMcpPreserved && opts.verbose) {
1637
+ log.info('Preserved: Gemini MCP config (.gemini/settings.json)');
1638
+ }
1639
+
1640
+ // Offer to add detected-but-uninstalled platforms
1641
+ const uninstalled = detected.filter(id => !isAdapterInstalled(id, dir));
1642
+ if (uninstalled.length > 0 && !opts.force) {
1643
+ const names = uninstalled.map(id => PLATFORMS[id].name).join(', ');
1644
+ log.info(`New platforms detected: ${names}`);
1645
+ const addNew = await promptConfirm('Generate adapters for these platforms?');
1646
+ if (addNew) {
1647
+ await generateAdapters(dir, uninstalled, opts);
1648
+ regenerated.push(...uninstalled.map(id => PLATFORMS[id].name + ' (new)'));
1649
+ }
1650
+ }
1651
+
1652
+ // Write new version
1653
+ writeFileSync(versionFile, VERSION + '\n');
1654
+
1655
+ console.log('');
1656
+ log.ok(`Upgraded to ${VERSION}`);
1657
+ console.log('');
1658
+ console.log(` ${c.bold}Preserved:${c.reset} specs/, tracker, log, architecture, project config${geminiMcpPreserved ? ', Gemini MCP config' : ''}`);
1659
+ console.log(` ${c.bold}Updated:${c.reset} loop, prompts, adapters, enterprise templates`);
1660
+ if (regenerated.length > 0) {
1661
+ console.log(` ${c.bold}Adapters:${c.reset} ${regenerated.join(', ')}`);
1662
+ }
1663
+ console.log('');
1664
+ }
1665
+
1666
+ // HELP
1667
+ function cmdHelp() {
1668
+ console.log(`
1669
+ ${c.bold}KnowzCode CLI${c.reset} v${VERSION}
1670
+ Platform-agnostic AI development methodology
1671
+
1672
+ ${c.bold}Usage:${c.reset}
1673
+ npx knowzcode Interactive mode
1674
+ npx knowzcode install [options] Install (preserves data on reinstall)
1675
+ npx knowzcode add-platforms [options] Add/change platform adapters only
1676
+ npx knowzcode uninstall [options] Remove KnowzCode
1677
+ npx knowzcode upgrade [options] Upgrade preserving user data
1678
+ npx knowzcode detect Show detected platforms (dry run)
1679
+
1680
+ ${c.bold}Options:${c.reset}
1681
+ --target <path> Target directory (default: current directory)
1682
+ --platforms <list> Comma-separated: claude,codex,gemini,cursor,copilot,windsurf,all
1683
+ --force Skip confirmation prompts
1684
+ --clean Full reset on reinstall (disables data preservation)
1685
+ --global Install Claude Code to ~/.claude/, Codex skills to ~/.agents/skills/, Gemini skills to ~/.gemini/skills/
1686
+ --agent-teams Enable Agent Teams in .claude/settings.local.json
1687
+ --verbose Show detailed output
1688
+ -h, --help Show this help
1689
+ -v, --version Show version
1690
+
1691
+ ${c.bold}Examples:${c.reset}
1692
+ npx knowzcode install --platforms claude,cursor
1693
+ npx knowzcode install --platforms all --force
1694
+ npx knowzcode add-platforms --platforms cursor
1695
+ npx knowzcode upgrade --target ./my-project
1696
+ npx knowzcode uninstall --force
1697
+ npx knowzcode detect
1698
+ `);
1699
+ }
1700
+
1701
+ // INTERACTIVE
1702
+ async function cmdInteractive(opts) {
1703
+ console.log('');
1704
+ console.log(`${c.bold} ╔═══════════════════════════════════════╗${c.reset}`);
1705
+ console.log(`${c.bold} ║ KnowzCode v${VERSION.padEnd(14)} ║${c.reset}`);
1706
+ console.log(`${c.bold} ║ AI Development Methodology Installer ║${c.reset}`);
1707
+ console.log(`${c.bold} ╚═══════════════════════════════════════╝${c.reset}`);
1708
+ console.log('');
1709
+
1710
+ const dir = opts.target;
1711
+ const kcDir = join(dir, 'knowzcode');
1712
+ const detected = detectPlatforms(dir);
1713
+
1714
+ if (detected.length > 0) {
1715
+ log.info('Detected platforms: ' + detected.map((d) => PLATFORMS[d].name).join(', '));
1716
+ }
1717
+
1718
+ if (existsSync(kcDir)) {
1719
+ // Always scan and display existing installation
1720
+ const scan = scanExistingInstallation(kcDir, dir);
1721
+ displayInstallationSummary(scan, dir);
1722
+ console.log('');
1723
+
1724
+ const versionFile = join(kcDir, '.knowzcode-version');
1725
+ const currentVersion = existsSync(versionFile) ? readFileSync(versionFile, 'utf8').trim() : 'unknown';
1726
+
1727
+ if (currentVersion !== VERSION) {
1728
+ // Version mismatch — upgrade is the primary action
1729
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1730
+ console.log(` ${c.yellow}Update available: ${currentVersion} → ${VERSION}${c.reset}`);
1731
+ console.log('');
1732
+ console.log(' [1] Upgrade to v' + VERSION + ' (preserves all your data)');
1733
+ console.log(' [2] Add/change platform adapters');
1734
+ console.log(' [3] Reinstall framework (preserves your data)');
1735
+ console.log(' [4] Uninstall');
1736
+ console.log(' [5] Exit');
1737
+ console.log('');
1738
+ const answer = await rl.question('Select action: ');
1739
+ rl.close();
1740
+
1741
+ const choice = answer.trim();
1742
+ if (choice === '1') return cmdUpgrade(opts);
1743
+ if (choice === '2') return cmdAddPlatforms(opts);
1744
+ if (choice === '3') return cmdInstall({ ...opts, force: true });
1745
+ if (choice === '4') return cmdUninstall(opts);
1746
+ return;
1747
+ } else {
1748
+ // Same version — add platforms is the primary action
1749
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1750
+ console.log('');
1751
+ console.log(' [1] Add/change platform adapters');
1752
+ console.log(' [2] Reinstall framework (preserves your data)');
1753
+ console.log(' [3] Uninstall');
1754
+ console.log(' [4] Exit');
1755
+ console.log('');
1756
+ const answer = await rl.question('Select action: ');
1757
+ rl.close();
1758
+
1759
+ const choice = answer.trim();
1760
+ if (choice === '1') return cmdAddPlatforms(opts);
1761
+ if (choice === '2') return cmdInstall({ ...opts, force: true });
1762
+ if (choice === '3') return cmdUninstall(opts);
1763
+ return;
1764
+ }
1765
+ } else {
1766
+ log.info('No existing installation found. Starting install...');
1767
+ console.log('');
1768
+ return cmdInstall(opts);
1769
+ }
1770
+ }
1771
+
1772
+ // ─── Main ────────────────────────────────────────────────────────────────────
1773
+
1774
+ async function main() {
1775
+ const opts = parseArgs(process.argv);
1776
+
1777
+ switch (opts.command) {
1778
+ case 'install':
1779
+ return cmdInstall(opts);
1780
+ case 'add-platforms':
1781
+ return cmdAddPlatforms(opts);
1782
+ case 'uninstall':
1783
+ return cmdUninstall(opts);
1784
+ case 'upgrade':
1785
+ return cmdUpgrade(opts);
1786
+ case 'detect':
1787
+ return cmdDetect(opts);
1788
+ case 'help':
1789
+ return cmdHelp();
1790
+ case 'version':
1791
+ console.log(VERSION);
1792
+ return;
1793
+ default:
1794
+ return cmdInteractive(opts);
1795
+ }
1796
+ }
1797
+
1798
+ main().catch((err) => {
1799
+ log.err(err.message);
1800
+ process.exit(1);
1801
+ });