@chemx/starter-kit 26.9.21-8 → 26.9.23-1269

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 (130) hide show
  1. package/cli/audit/ast-visitors.js +10 -17
  2. package/cli/audit/autofix.js +17 -2
  3. package/cli/audit/clone-detector.js +64 -0
  4. package/cli/audit/clone-detector.spec.js +46 -0
  5. package/cli/audit/hook-shape-validator.js +312 -0
  6. package/cli/audit/prompts.js +1 -1
  7. package/cli/audit/reporter-clones.js +33 -0
  8. package/cli/audit/reporter-hotspot-graph.js +34 -0
  9. package/cli/audit/rules-registry.js +6 -1
  10. package/cli/audit/rules.js +10 -2
  11. package/cli/audit/rules.spec.js +129 -0
  12. package/cli/audit-stages.spec.js +30 -0
  13. package/cli/audit.js +12 -3
  14. package/cli/commands/cmd-audit.js +43 -3
  15. package/cli/commands/cmd-project.js +100 -0
  16. package/cli/commands/cmd-router.js +34 -2
  17. package/cli/commands-schema.js +14 -0
  18. package/cli/create.spec.js +2 -2
  19. package/cli/exploder.js +258 -0
  20. package/cli/exploder.spec.js +99 -0
  21. package/cli/generator-compact.spec.js +104 -0
  22. package/cli/generator-framework.spec.js +72 -0
  23. package/cli/generator-templates/archetypes/collection-archetypes.js +9 -9
  24. package/cli/generator-templates/archetypes/index.js +34 -4
  25. package/cli/generator-templates/archetypes/input-archetypes.js +2 -2
  26. package/cli/generator-templates/archetypes/layout-archetypes.js +2 -2
  27. package/cli/generator-templates/archetypes/minimal-archetypes.js +131 -0
  28. package/cli/generator-templates/archetypes/status-archetypes.js +4 -4
  29. package/cli/generator-templates/archetypes/vector-matcher.js +50 -0
  30. package/cli/generator-templates/archetypes/vector-matcher.spec.js +25 -0
  31. package/cli/generator-templates/compact.js +130 -0
  32. package/cli/generator-templates/controller-converters.js +93 -0
  33. package/cli/generator-templates/controller.js +2 -64
  34. package/cli/generator-templates/react.js +1 -1
  35. package/cli/generator-templates/svelte.js +1 -1
  36. package/cli/generator-templates/types.js +2 -2
  37. package/cli/generator-templates/vue.js +1 -1
  38. package/cli/generator-templates.js +1 -0
  39. package/cli/generator-templates.spec.js +6 -1
  40. package/cli/generator.js +94 -7
  41. package/cli/help.js +1 -1
  42. package/cli/index.js +21 -8
  43. package/cli/installer-templates.js +1 -1
  44. package/cli/mcp/antigravity.js +3 -2
  45. package/cli/mcp/installer.js +7 -7
  46. package/cli/mcp/installer.spec.js +3 -3
  47. package/cli/mcp/manifests.js +3 -2
  48. package/cli/mcp/prompts.js +1 -1
  49. package/cli/mcp/server.js +2 -0
  50. package/cli/mcp/tools-audit.js +95 -0
  51. package/cli/mcp/tools-generate.js +62 -0
  52. package/cli/mcp/tools-patch.js +72 -0
  53. package/cli/mcp/tools-patterns.js +71 -0
  54. package/cli/mcp/tools-project.js +75 -0
  55. package/cli/mcp/tools-project.spec.js +51 -0
  56. package/cli/mcp/tools-q-modes.js +69 -0
  57. package/cli/mcp/tools-q.js +58 -0
  58. package/cli/mcp/tools-read.js +69 -0
  59. package/cli/mcp/tools-search-util.js +35 -0
  60. package/cli/mcp/tools-search.js +15 -294
  61. package/cli/mcp/tools-team-feed.js +57 -0
  62. package/cli/mcp/tools-team-locks.js +63 -0
  63. package/cli/mcp/tools-team-mailbox.js +32 -0
  64. package/cli/mcp/tools-team-tasks.js +84 -0
  65. package/cli/mcp/tools-team.js +37 -183
  66. package/cli/mcp/tools-verify.js +47 -0
  67. package/cli/mcp/tools-write.js +33 -0
  68. package/cli/mcp/tools.js +72 -376
  69. package/cli/mutators.js +10 -23
  70. package/cli/pillars-schema.js +3 -3
  71. package/cli/project-detector.js +47 -0
  72. package/cli/reader-shorthand.spec.js +60 -0
  73. package/cli/reader.js +67 -24
  74. package/cli/search-bulletproof.spec.js +104 -0
  75. package/cli/search-db.js +33 -5
  76. package/cli/search-queries-hotspot-graph.js +65 -0
  77. package/cli/search-queries-hotspot-graph.spec.js +66 -0
  78. package/cli/search-queries-similar.js +84 -0
  79. package/cli/search-queries-similar.spec.js +43 -0
  80. package/cli/search-schema.js +30 -10
  81. package/cli/search.js +9 -2
  82. package/cli/team/index.js +9 -0
  83. package/cli/team/team-commands.js +104 -64
  84. package/cli/team/team-concurrency-stress.spec.js +95 -0
  85. package/cli/team/team-db-mailbox.js +86 -0
  86. package/cli/team/team-db-task-helpers.js +30 -28
  87. package/cli/team/team-db-tasks.js +13 -5
  88. package/cli/team/team-flags.js +69 -0
  89. package/cli/team/team-format.js +87 -46
  90. package/cli/team/team-mailbox-empirical.spec.js +191 -0
  91. package/cli/team/team-mailbox.spec.js +93 -0
  92. package/cli/team/team-memory.js +84 -0
  93. package/cli/team/team-memory.spec.js +74 -0
  94. package/cli/team/team-projects-coordinator.js +100 -0
  95. package/cli/team/team-projects-format.js +45 -0
  96. package/cli/team/team-projects-memory.js +70 -0
  97. package/cli/team/team-projects.js +94 -0
  98. package/cli/team/team-projects.spec.js +116 -0
  99. package/cli/team/team-schema-memory.js +25 -0
  100. package/cli/team/team-schema-projects.js +50 -0
  101. package/cli/team/team-schema.js +33 -14
  102. package/cli/team/team-tasks-hierarchy.spec.js +80 -0
  103. package/cli/team/team-tokens.js +95 -0
  104. package/cli/team/team-triage.js +159 -12
  105. package/cli/team/team-verification-gate.spec.js +253 -0
  106. package/cli/team/team.spec.js +1 -1
  107. package/cli/trend.js +84 -0
  108. package/cli/trend.spec.js +51 -0
  109. package/cli/ui-actions-tasks.js +13 -5
  110. package/cli/ui-actions.js +10 -2
  111. package/cli/ui-client-api.js +2 -2
  112. package/cli/ui-client-script.js +12 -15
  113. package/cli/ui-e2e-verification.spec.js +13 -3
  114. package/cli/ui-filetree.spec.js +5 -5
  115. package/cli/ui-handlers.js +10 -2
  116. package/cli/ui-html.js +26 -34
  117. package/cli/ui-index.html +905 -0
  118. package/cli/ui-kanban.spec.js +12 -12
  119. package/cli/ui-server.js +7 -0
  120. package/cli/ui-sse.js +57 -0
  121. package/cli/ui-sse.spec.js +74 -0
  122. package/cli/ui-styles.js +195 -81
  123. package/cli/ui-template-kanban.js +15 -1
  124. package/cli/ui-vbulletin.spec.js +12 -13
  125. package/cli/ui.spec.js +12 -5
  126. package/cli/verify.js +19 -8
  127. package/package.json +3 -1
  128. package/scripts/check-framework-generation.mjs +195 -0
  129. package/scripts/pre-commit.sh +2 -7
  130. package/scripts/publish-both.mjs +4 -0
@@ -203,3 +203,132 @@ export const debugCount = (itemCount: number) => {
203
203
  assert.strictEqual(violation, undefined);
204
204
  });
205
205
 
206
+ test("Audit Hook Shape Contract: passes flat hook with 4 actions, 2 state fields, 1 status field (7 total)", () => {
207
+ const code = `
208
+ export const useTodoList = () => {
209
+ const items = ref([]);
210
+ const filter = ref("all");
211
+ const isLoading = ref(false);
212
+
213
+ const addItem = (item) => { items.value.push(item); };
214
+ const toggleItem = (id) => { /* toggle */ };
215
+ const removeItem = (id) => { /* remove */ };
216
+ const setFilter = (next) => { filter.value = next; };
217
+
218
+ return {
219
+ items,
220
+ filter,
221
+ isLoading,
222
+ addItem,
223
+ toggleItem,
224
+ removeItem,
225
+ setFilter
226
+ };
227
+ };
228
+ `;
229
+ const violations = auditCode(code, "src/useTodoList.ts", "src/useTodoList.ts");
230
+ const shapeViolation = violations.find((v) => v.rule === "HOOK_SHAPE_CONTRACT");
231
+ assert.strictEqual(shapeViolation, undefined, "Expected HOOK_SHAPE_CONTRACT to pass for flat 7-property hook");
232
+
233
+ const overloadViolation = violations.find((v) => v.rule === "HOOK_RETURN_OVERLOAD");
234
+ assert.ok(overloadViolation, "Expected deprecated HOOK_RETURN_OVERLOAD warning");
235
+ assert.strictEqual(overloadViolation.severity, "MEDIUM", "Expected downgraded warning severity for HOOK_RETURN_OVERLOAD");
236
+ });
237
+
238
+ test("Audit Hook Shape Contract: fails hook with actions nested under actions wrapper", () => {
239
+ const code = `
240
+ export const useTodoList = () => {
241
+ const items = ref([]);
242
+ const filter = ref("all");
243
+ const isLoading = ref(false);
244
+
245
+ const addItem = (item) => { items.value.push(item); };
246
+ const toggleItem = (id) => { /* toggle */ };
247
+ const removeItem = (id) => { /* remove */ };
248
+ const setFilter = (next) => { filter.value = next; };
249
+
250
+ return {
251
+ items,
252
+ filter,
253
+ isLoading,
254
+ actions: {
255
+ addItem,
256
+ toggleItem,
257
+ removeItem,
258
+ setFilter
259
+ }
260
+ };
261
+ };
262
+ `;
263
+ const violations = auditCode(code, "src/useTodoList.ts", "src/useTodoList.ts");
264
+ const shapeViolation = violations.find((v) => v.rule === "HOOK_SHAPE_CONTRACT");
265
+ assert.ok(shapeViolation, "Expected HOOK_SHAPE_CONTRACT failure for nested action wrapper");
266
+ assert.strictEqual(shapeViolation.severity, "HIGH");
267
+ assert.ok(shapeViolation.hazard.includes("actions"));
268
+ });
269
+
270
+ test("Audit Hook Shape Contract: flags raw DOM ref in hook return", () => {
271
+ const code = `
272
+ export const useDropdown = () => {
273
+ const isOpen = ref(false);
274
+ const containerRef = ref(null);
275
+ const toggleDropdown = () => { isOpen.value = !isOpen.value; };
276
+
277
+ return {
278
+ isOpen,
279
+ containerRef,
280
+ toggleDropdown
281
+ };
282
+ };
283
+ `;
284
+ const violations = auditCode(code, "src/useDropdown.ts", "src/useDropdown.ts");
285
+ const shapeViolation = violations.find((v) => v.rule === "HOOK_SHAPE_CONTRACT");
286
+ assert.ok(shapeViolation, "Expected HOOK_SHAPE_CONTRACT failure for raw DOM ref");
287
+ assert.strictEqual(shapeViolation.severity, "HIGH");
288
+ assert.ok(shapeViolation.hazard.includes("containerRef"));
289
+ });
290
+
291
+ test("Audit Hook Shape Contract: flags non-verb action handler in hook return", () => {
292
+ const code = `
293
+ export const useCounter = () => {
294
+ const count = ref(0);
295
+ const onToggle = () => { count.value += 1; };
296
+
297
+ return {
298
+ count,
299
+ onToggle
300
+ };
301
+ };
302
+ `;
303
+ const violations = auditCode(code, "src/useCounter.ts", "src/useCounter.ts");
304
+ const shapeViolation = violations.find((v) => v.rule === "HOOK_SHAPE_CONTRACT");
305
+ assert.ok(shapeViolation, "Expected HOOK_SHAPE_CONTRACT failure for onToggle");
306
+ assert.strictEqual(shapeViolation.severity, "HIGH");
307
+ assert.ok(shapeViolation.hazard.includes("onToggle"));
308
+ });
309
+
310
+ test("Audit Hook Shape Contract: flags cross-hook naming inconsistency for loading state", () => {
311
+ const code = `
312
+ export const useFirst = () => {
313
+ const items = ref([]);
314
+ const isLoading = ref(false);
315
+ const fetchItems = () => {};
316
+ return { items, isLoading, fetchItems };
317
+ };
318
+
319
+ export const useSecond = () => {
320
+ const user = ref(null);
321
+ const loading = ref(false);
322
+ const fetchUser = () => {};
323
+ return { user, loading, fetchUser };
324
+ };
325
+ `;
326
+ const violations = auditCode(code, "src/useMulti.ts", "src/useMulti.ts");
327
+ const inconsistentViolation = violations.find(
328
+ (v) => v.rule === "HOOK_SHAPE_CONTRACT" && v.hazard.includes("loading")
329
+ );
330
+ assert.ok(inconsistentViolation, "Expected cross-hook naming inconsistency failure for loading vs isLoading");
331
+ assert.strictEqual(inconsistentViolation.severity, "HIGH");
332
+ assert.ok(inconsistentViolation.hazard.includes("isLoading"));
333
+ });
334
+
@@ -0,0 +1,30 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { runAudit } from './audit.js';
6
+
7
+ test('audit: options.stage tags the report as draft or strict', () => {
8
+ const tmpDir = path.resolve(process.cwd(), 'scratch/test-audit-stage');
9
+ if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true, force: true });
10
+ fs.mkdirSync(tmpDir, { recursive: true });
11
+
12
+ // Sample file with an inline multi-clause boolean (MEDIUM severity)
13
+ const code = `export const check = (a: number, b: number, c: number) => {
14
+ if (a > 0 && b > 0 && c > 0) return true;
15
+ return false;
16
+ };`;
17
+ fs.writeFileSync(path.join(tmpDir, 'sample.ts'), code, 'utf-8');
18
+
19
+ const draftReport = runAudit(tmpDir, { stage: 'draft' });
20
+ assert.strictEqual(draftReport.stage, 'draft');
21
+ assert.ok(draftReport.totalViolations > 0);
22
+
23
+ const hasCritical = draftReport.violations.some((v) => v.severity === 'CRITICAL');
24
+ assert.strictEqual(hasCritical, false, 'Should have 0 critical violations');
25
+
26
+ const strictReport = runAudit(tmpDir, { stage: 'strict' });
27
+ assert.strictEqual(strictReport.stage, 'strict');
28
+
29
+ fs.rmSync(tmpDir, { recursive: true, force: true });
30
+ });
package/cli/audit.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { auditCode, PILLARS, RULE_REGISTRY } from './audit/rules.js';
3
+ import { auditCode, PILLARS, RULE_REGISTRY, createHookShapeRegistry } from './audit/rules.js';
4
4
  import { createPatternRegistry } from './audit/pattern-detector.js';
5
5
  import {
6
6
  buildRemediationRoadmap,
@@ -116,6 +116,7 @@ const auditFileEntry = (fullPath, relPath, scanOptions) => {
116
116
  const hookCount = hookMatches ? hookMatches.length : 0;
117
117
  const fileViolations = auditCode(content, fullPath, relPath, {
118
118
  patternRegistry: scanOptions.patternRegistry,
119
+ hookRegistry: scanOptions.hookRegistry,
119
120
  fast: scanOptions.fast
120
121
  });
121
122
 
@@ -174,15 +175,20 @@ export const scanDirectory = (targetDir, baseDir) => {
174
175
  };
175
176
 
176
177
  export const runAudit = (targetDir = 'src', options = {}) => {
177
- const cwd = process.cwd();
178
- const absoluteTarget = path.resolve(cwd, targetDir);
178
+ const cwd = options.cwd || process.cwd();
179
+ const absoluteTarget = path.isAbsolute(targetDir) ? targetDir : path.resolve(cwd, targetDir);
179
180
  const patternRegistry = createPatternRegistry();
181
+ const hookRegistry = createHookShapeRegistry();
180
182
  const { violations, fileStats, totalHooks } = scanTree(absoluteTarget, cwd, {
181
183
  patternRegistry,
184
+ hookRegistry,
182
185
  fast: Boolean(options.fast),
183
186
  fileList: options.fileList || null
184
187
  });
185
188
 
189
+ const crossHookViolations = hookRegistry.validateCrossHookConsistency();
190
+ violations.push(...crossHookViolations);
191
+
186
192
  const scannedFiles = fileStats.length;
187
193
  const totalLoc = fileStats.reduce((acc, f) => acc + f.lineCount, 0);
188
194
  const avgLoc = scannedFiles > 0 ? Math.round(totalLoc / scannedFiles) : 0;
@@ -226,8 +232,11 @@ export const runAudit = (targetDir = 'src', options = {}) => {
226
232
  const patterns = patternRegistry.resolveHarmonizationCandidates(hotspots);
227
233
  const roadmap = buildRemediationRoadmap({ hotspots, violations, patterns });
228
234
 
235
+ const stage = options.stage || (options.relax ? 'draft' : 'strict');
236
+
229
237
  const report = {
230
238
  targetDir,
239
+ stage,
231
240
  options,
232
241
  scannedFiles,
233
242
  totalViolations: violations.length,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * cmd-audit.js — Audit command orchestrator.
2
+ * cmd-audit.js: Audit command orchestrator.
3
3
  * Single responsibility: run the full AST audit pipeline given CLI args.
4
4
  * Extracted from cli/index.js per Directive 1.A (Monolith Decomposition).
5
5
  */
@@ -47,6 +47,19 @@ export const runAudit = async (customDir, isCli, rawArgs, loadProjectConfig) =>
47
47
  const isPromptOnFail = rawArgs.includes('--prompt-on-fail');
48
48
  const isCopyPrompt = rawArgs.includes('--copy-prompt');
49
49
 
50
+ const stageFlag = rawArgs.find((arg) => arg.startsWith('--stage='));
51
+ let stage = 'strict';
52
+ if (stageFlag) {
53
+ stage = stageFlag.split('=')[1];
54
+ } else if (rawArgs.includes('--relax') || rawArgs.includes('--draft')) {
55
+ stage = 'draft';
56
+ } else if (isStrict) {
57
+ stage = 'strict';
58
+ } else if (projectConfig.stage) {
59
+ stage = projectConfig.stage;
60
+ }
61
+ const isDraft = stage === 'draft';
62
+
50
63
  const dirFlag = rawArgs.find((arg) => arg.startsWith('--dir='));
51
64
  const outputFlag = rawArgs.find((arg) => arg.startsWith('--output=') || arg.startsWith('-o='));
52
65
  const outputFile = outputFlag ? outputFlag.split('=')[1] : null;
@@ -88,7 +101,7 @@ export const runAudit = async (customDir, isCli, rawArgs, loadProjectConfig) =>
88
101
  fileList = preflight.fileList;
89
102
  }
90
103
 
91
- const auditOptions = { outputFile, model, costPerMillion, fast: isFast, fileList };
104
+ const auditOptions = { outputFile, model, costPerMillion, fast: isFast, fileList, stage };
92
105
  const report = executeAstAudit(targetDir, auditOptions);
93
106
  saveAuditSnapshot(report);
94
107
  const syncRes = syncSearchIndex(targetDir, process.cwd());
@@ -102,9 +115,35 @@ export const runAudit = async (customDir, isCli, rawArgs, loadProjectConfig) =>
102
115
  process.stdout.write(`\x1b[32m✔\x1b[0m Auto-triage generated ${createdTasks.length} team task(s) from audit violations.\n`);
103
116
  }
104
117
  }
118
+ if (rawArgs.includes('--clones')) {
119
+ const { detectSemanticClones } = await import('../audit/clone-detector.js');
120
+ const { formatCloneReport } = await import('../audit/reporter-clones.js');
121
+ const cloneThresholdArg = rawArgs.find((a) => a.startsWith('--clone-threshold='));
122
+ const threshold = cloneThresholdArg ? parseFloat(cloneThresholdArg.split('=')[1]) : 0.85;
123
+ const clones = detectSemanticClones(syncRes.db, { threshold });
124
+ if (isJson) {
125
+ report.clones = clones;
126
+ } else {
127
+ process.stdout.write(formatCloneReport(clones));
128
+ }
129
+ }
130
+ if (rawArgs.includes('--hotspot-graph')) {
131
+ const { calculateCascadingHotspotGraph } = await import('../search-queries-hotspot-graph.js');
132
+ const { formatHotspotGraphReport } = await import('../audit/reporter-hotspot-graph.js');
133
+ const limitArg = rawArgs.find((a) => a.startsWith('--limit='));
134
+ const limit = limitArg ? parseInt(limitArg.split('=')[1], 10) : 10;
135
+ const graph = calculateCascadingHotspotGraph(syncRes.db, { limit });
136
+ if (isJson) {
137
+ report.hotspotGraph = graph;
138
+ } else {
139
+ process.stdout.write(formatHotspotGraphReport(graph));
140
+ }
141
+ }
105
142
  }
106
143
 
107
144
  // Stage 1: Atomic failure predicates
145
+ const isCriticalViolation = (v) => v.severity === 'CRITICAL';
146
+ const hasCritical = report.violations.some(isCriticalViolation);
108
147
  const isSevereViolation = (v) => v.severity === 'CRITICAL' || v.severity === 'HIGH';
109
148
  const hasCriticalOrHigh = report.violations.some(isSevereViolation);
110
149
  const hasViolations = report.violations.length > 0;
@@ -116,7 +155,8 @@ export const runAudit = async (customDir, isCli, rawArgs, loadProjectConfig) =>
116
155
  const isDefaultFail = !hasThreshold && !isStrict && hasCriticalOrHigh;
117
156
 
118
157
  // Stage 2: Unified failure decision
119
- const hasFailingViolations = evaluateAuditFailure([isStrictFail, isDefaultFail, isGradeFail, isScoreFail]);
158
+ const hasStandardFailure = evaluateAuditFailure([isStrictFail, isDefaultFail, isGradeFail, isScoreFail]);
159
+ const hasFailingViolations = isDraft ? hasCritical : hasStandardFailure;
120
160
 
121
161
  if (isJson) {
122
162
  process.stdout.write(JSON.stringify(report, null, 2) + '\n');
@@ -0,0 +1,100 @@
1
+ /**
2
+ * cmd-project.js: Persistent Coordinator and Projects CLI Handler
3
+ * Dispatches init, status, step, chat, pause, resume, and learnings
4
+ */
5
+
6
+ import { openIndexDb } from '../search-db.js';
7
+ import {
8
+ initProjectSession, getActiveProjectSession, getProjectSession,
9
+ postProjectMessage, getProjectMessages, updateProjectSession
10
+ } from '../team/team-projects.js';
11
+ import { queryRelevantLearnings, formatLearningsCard } from '../team/team-projects-memory.js';
12
+ import { executeCoordinatorStep } from '../team/team-projects-coordinator.js';
13
+ import { formatProjectStatusCard } from '../team/team-projects-format.js';
14
+ import { listTasks } from '../team/team-db-tasks.js';
15
+
16
+ export const runProjectCli = async (rawArgs = [], isCli = false, cwd = process.cwd()) => {
17
+ const db = openIndexDb(cwd);
18
+ if (!db) {
19
+ if (isCli) process.stderr.write('\x1b[31m✕ SQLite database unavailable.\x1b[0m\n');
20
+ return null;
21
+ }
22
+
23
+ const isJson = rawArgs.includes('--json');
24
+ const nonFlag = rawArgs.filter((a) => !a.startsWith('-'));
25
+ const subCommand = nonFlag[0] || 'status';
26
+ const restText = nonFlag.slice(1).join(' ');
27
+
28
+ if (subCommand === 'init' || subCommand === 'start') {
29
+ const goal = restText || 'Autonomous codebase optimization';
30
+ const session = initProjectSession(db, { goal, title: goal.slice(0, 40) });
31
+ if (isCli) {
32
+ if (isJson) process.stdout.write(`${JSON.stringify(session, null, 2)}\n`);
33
+ else process.stdout.write(`\x1b[32m✔\x1b[0m Initialized project session #${session.id}: "${session.title}"\n`);
34
+ }
35
+ return session;
36
+ }
37
+
38
+ if (subCommand === 'step' || subCommand === 'turn') {
39
+ const res = executeCoordinatorStep(db, { cwd });
40
+ if (isCli) {
41
+ if (isJson) process.stdout.write(`${JSON.stringify(res, null, 2)}\n`);
42
+ else process.stdout.write(`\x1b[36m⚡ [Turn ${res.turn || 0}]\x1b[0m ${res.action || res.status} - Status: ${res.status}\n`);
43
+ }
44
+ return res;
45
+ }
46
+
47
+ if (subCommand === 'chat' || subCommand === 'msg') {
48
+ const active = getActiveProjectSession(db);
49
+ if (!active) {
50
+ if (isCli) process.stderr.write('\x1b[31m✕ No active project. Run "chemx project init <goal>" first.\x1b[0m\n');
51
+ return { error: 'no_active_session' };
52
+ }
53
+ const msg = postProjectMessage(db, {
54
+ projectId: active.id,
55
+ turnIndex: active.current_turn,
56
+ authorId: '@user',
57
+ role: 'user',
58
+ message: restText || 'Status report'
59
+ });
60
+ if (isCli) {
61
+ if (isJson) process.stdout.write(`${JSON.stringify(msg, null, 2)}\n`);
62
+ else process.stdout.write(`\x1b[32m✔\x1b[0m Sent message to coordinator: "${msg.message}"\n`);
63
+ }
64
+ return msg;
65
+ }
66
+
67
+ if (subCommand === 'learnings' || subCommand === 'memory') {
68
+ const learnings = queryRelevantLearnings(db, { limit: 10 });
69
+ if (isCli) {
70
+ if (isJson) process.stdout.write(`${JSON.stringify(learnings, null, 2)}\n`);
71
+ else process.stdout.write(formatLearningsCard(learnings));
72
+ }
73
+ return learnings;
74
+ }
75
+
76
+ if (subCommand === 'pause' || subCommand === 'resume') {
77
+ const active = getActiveProjectSession(db);
78
+ if (!active) return null;
79
+ const newStatus = subCommand === 'pause' ? 'paused' : 'active';
80
+ const updated = updateProjectSession(db, active.id, { status: newStatus });
81
+ if (isCli) process.stdout.write(`\x1b[32m✔\x1b[0m Project #${active.id} status updated to: ${newStatus}\n`);
82
+ return updated;
83
+ }
84
+
85
+ // Default: status
86
+ const session = getActiveProjectSession(db);
87
+ const messages = session ? getProjectMessages(db, session.id, 5) : [];
88
+ const activeTasks = session ? listTasks(db, { status: 'in_progress' }) : [];
89
+ const learnings = queryRelevantLearnings(db, { limit: 5 });
90
+
91
+ if (isJson) {
92
+ const payload = { session, messages, activeTasks, learnings };
93
+ if (isCli) process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
94
+ return payload;
95
+ }
96
+
97
+ const card = formatProjectStatusCard({ session, messages, activeTasks, learnings });
98
+ if (isCli) process.stdout.write(card);
99
+ return { session, messages, activeTasks, learnings };
100
+ };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * cmd-router.js — CLI command dispatch table.
2
+ * cmd-router.js: CLI command dispatch table.
3
3
  * Single responsibility: map first-arg tokens to their lazy-loaded command handlers.
4
4
  * Extracted from cli/index.js per Directive 1.A (Monolith Decomposition).
5
5
  */
@@ -23,6 +23,25 @@ export const dispatchCommand = async (firstArg, rawArgs, runAudit, getPackageVer
23
23
  runTeamCli(rawArgs.slice(1), true);
24
24
  break;
25
25
  }
26
+ case 'project':
27
+ case 'coordinator': {
28
+ const { runProjectCli } = await import('./cmd-project.js');
29
+ await runProjectCli(rawArgs.slice(1), true);
30
+ break;
31
+ }
32
+ case 'tokens':
33
+ case 'telemetry': {
34
+ const { runTeamCli } = await import('../team/index.js');
35
+ runTeamCli(['tokens', ...rawArgs.slice(1)], true);
36
+ break;
37
+ }
38
+ case 'benchmark':
39
+ case 'ablation':
40
+ case 'memory': {
41
+ const { runTeamCli } = await import('../team/index.js');
42
+ runTeamCli(['benchmark', ...rawArgs.slice(1)], true);
43
+ break;
44
+ }
26
45
  case 'mcp':
27
46
  case 'mcp-server':
28
47
  case 'server': {
@@ -31,7 +50,8 @@ export const dispatchCommand = async (firstArg, rawArgs, runAudit, getPackageVer
31
50
  break;
32
51
  }
33
52
  case 'read':
34
- case 'view': {
53
+ case 'view':
54
+ case 'r': {
35
55
  const { runReaderCli } = await import('../reader.js');
36
56
  runReaderCli(rawArgs.slice(1), true);
37
57
  break;
@@ -67,6 +87,12 @@ export const dispatchCommand = async (firstArg, rawArgs, runAudit, getPackageVer
67
87
  await runAudit(posDir, true);
68
88
  break;
69
89
  }
90
+ case 'trend':
91
+ case 'trends': {
92
+ const { runTrend } = await import('../trend.js');
93
+ runTrend(rawArgs.slice(1), process.cwd());
94
+ break;
95
+ }
70
96
  case 'init': {
71
97
  const { runInit } = await import('../scaffold.js');
72
98
  await runInit(rawArgs[1] || 'src/chemical-x', rawArgs, runAudit);
@@ -107,6 +133,12 @@ export const dispatchCommand = async (firstArg, rawArgs, runAudit, getPackageVer
107
133
  await runMutatorCli(rawArgs, true);
108
134
  break;
109
135
  }
136
+ case 'explode':
137
+ case 'unpack': {
138
+ const { runExplodeCli } = await import('../exploder.js');
139
+ await runExplodeCli(rawArgs.slice(1), true);
140
+ break;
141
+ }
110
142
  case 'add': {
111
143
  if (['prop', 'state', 'action'].includes(rawArgs[1])) {
112
144
  const { runMutatorCli } = await import('../mutators.js');
@@ -194,5 +194,19 @@ export const COMMANDS_SCHEMA = [
194
194
  'npx chemx pillars --preset=recommended',
195
195
  'npx chemx pillars --preset=minimal --dry-run'
196
196
  ]
197
+ },
198
+ {
199
+ name: 'ui',
200
+ aliases: ['dashboard', 'preview'],
201
+ usage: 'npx chemx ui [options]',
202
+ summary: 'Launch the Chemical X interactive web dashboard.',
203
+ description: 'Provides real-time Kanban task boards, agent rails, live SQLite Database Studio, and codebase AST tree exploration.',
204
+ flags: [
205
+ { flag: '--port=<N>', desc: 'Server port (default: 4173)' }
206
+ ],
207
+ examples: [
208
+ 'npx chemx ui',
209
+ 'npx chemx dashboard --port=3000'
210
+ ]
197
211
  }
198
212
  ];
@@ -63,7 +63,7 @@ describe('Scaffolding Entrypoint Invocations', () => {
63
63
  const result = spawnSync('node', [CREATE_BIN, '--help'], {
64
64
  encoding: 'utf-8',
65
65
  cwd: tmpBase,
66
- timeout: 5000
66
+ timeout: 15000
67
67
  });
68
68
 
69
69
  assert.equal(result.status, 0, 'Should exit with code 0 on --help');
@@ -78,7 +78,7 @@ describe('Scaffolding Entrypoint Invocations', () => {
78
78
  const result = spawnSync('node', [CREATE_BIN, '--version'], {
79
79
  encoding: 'utf-8',
80
80
  cwd: tmpBase,
81
- timeout: 5000
81
+ timeout: 15000
82
82
  });
83
83
 
84
84
  assert.equal(result.status, 0, 'Should exit with code 0 on --version');