@chemx/starter-kit 26.9.21-8 → 26.9.24-393

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 (138) 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/history.js +22 -6
  6. package/cli/audit/hook-shape-validator.js +312 -0
  7. package/cli/audit/prompts.js +13 -10
  8. package/cli/audit/prompts.spec.js +52 -1
  9. package/cli/audit/reporter-clones.js +33 -0
  10. package/cli/audit/reporter-hotspot-graph.js +34 -0
  11. package/cli/audit/rules-registry.js +6 -1
  12. package/cli/audit/rules.js +10 -2
  13. package/cli/audit/rules.spec.js +129 -0
  14. package/cli/audit-stages.spec.js +30 -0
  15. package/cli/audit.js +12 -3
  16. package/cli/commands/cmd-audit.js +56 -12
  17. package/cli/commands/cmd-project.js +100 -0
  18. package/cli/commands/cmd-router.js +52 -3
  19. package/cli/commands-schema.js +28 -0
  20. package/cli/create.spec.js +2 -2
  21. package/cli/exploder.js +258 -0
  22. package/cli/exploder.spec.js +99 -0
  23. package/cli/generator-compact.spec.js +104 -0
  24. package/cli/generator-framework.spec.js +72 -0
  25. package/cli/generator-templates/archetypes/collection-archetypes.js +9 -9
  26. package/cli/generator-templates/archetypes/index.js +34 -4
  27. package/cli/generator-templates/archetypes/input-archetypes.js +2 -2
  28. package/cli/generator-templates/archetypes/layout-archetypes.js +2 -2
  29. package/cli/generator-templates/archetypes/minimal-archetypes.js +131 -0
  30. package/cli/generator-templates/archetypes/status-archetypes.js +4 -4
  31. package/cli/generator-templates/archetypes/vector-matcher.js +50 -0
  32. package/cli/generator-templates/archetypes/vector-matcher.spec.js +25 -0
  33. package/cli/generator-templates/compact.js +130 -0
  34. package/cli/generator-templates/controller-converters.js +93 -0
  35. package/cli/generator-templates/controller.js +2 -64
  36. package/cli/generator-templates/react.js +1 -1
  37. package/cli/generator-templates/svelte.js +1 -1
  38. package/cli/generator-templates/types.js +2 -2
  39. package/cli/generator-templates/vue.js +1 -1
  40. package/cli/generator-templates.js +1 -0
  41. package/cli/generator-templates.spec.js +6 -1
  42. package/cli/generator.js +94 -7
  43. package/cli/help.js +1 -1
  44. package/cli/index.js +23 -8
  45. package/cli/installer-templates.js +1 -1
  46. package/cli/mcp/antigravity.js +5 -2
  47. package/cli/mcp/installer.js +7 -7
  48. package/cli/mcp/installer.spec.js +3 -3
  49. package/cli/mcp/manifests.js +4 -2
  50. package/cli/mcp/prompts.js +1 -1
  51. package/cli/mcp/server.js +2 -0
  52. package/cli/mcp/tools-audit.js +97 -0
  53. package/cli/mcp/tools-generate.js +62 -0
  54. package/cli/mcp/tools-patch.js +72 -0
  55. package/cli/mcp/tools-patterns.js +71 -0
  56. package/cli/mcp/tools-project.js +75 -0
  57. package/cli/mcp/tools-project.spec.js +51 -0
  58. package/cli/mcp/tools-q-modes.js +69 -0
  59. package/cli/mcp/tools-q.js +58 -0
  60. package/cli/mcp/tools-read.js +69 -0
  61. package/cli/mcp/tools-search-util.js +35 -0
  62. package/cli/mcp/tools-search.js +15 -294
  63. package/cli/mcp/tools-team-feed.js +57 -0
  64. package/cli/mcp/tools-team-locks.js +63 -0
  65. package/cli/mcp/tools-team-mailbox.js +32 -0
  66. package/cli/mcp/tools-team-tasks.js +85 -0
  67. package/cli/mcp/tools-team.js +37 -183
  68. package/cli/mcp/tools-verify.js +47 -0
  69. package/cli/mcp/tools-write.js +33 -0
  70. package/cli/mcp/tools.js +79 -372
  71. package/cli/mutators.js +10 -23
  72. package/cli/pillars-schema.js +3 -3
  73. package/cli/project-detector.js +47 -0
  74. package/cli/reader-shorthand.spec.js +60 -0
  75. package/cli/reader.js +67 -24
  76. package/cli/search-bulletproof.spec.js +104 -0
  77. package/cli/search-db.js +33 -5
  78. package/cli/search-queries-hotspot-graph.js +65 -0
  79. package/cli/search-queries-hotspot-graph.spec.js +66 -0
  80. package/cli/search-queries-similar.js +84 -0
  81. package/cli/search-queries-similar.spec.js +43 -0
  82. package/cli/search-schema.js +79 -11
  83. package/cli/search.js +13 -2
  84. package/cli/team/index.js +9 -0
  85. package/cli/team/team-commands.js +106 -64
  86. package/cli/team/team-concurrency-stress.spec.js +95 -0
  87. package/cli/team/team-db-lock-promotion.js +15 -11
  88. package/cli/team/team-db-mailbox.js +86 -0
  89. package/cli/team/team-db-task-helpers.js +80 -28
  90. package/cli/team/team-db-tasks.js +13 -5
  91. package/cli/team/team-flags.js +72 -0
  92. package/cli/team/team-format.js +87 -46
  93. package/cli/team/team-mailbox-empirical.spec.js +191 -0
  94. package/cli/team/team-mailbox.spec.js +93 -0
  95. package/cli/team/team-memory.js +84 -0
  96. package/cli/team/team-memory.spec.js +74 -0
  97. package/cli/team/team-projects-coordinator.js +100 -0
  98. package/cli/team/team-projects-format.js +45 -0
  99. package/cli/team/team-projects-memory.js +70 -0
  100. package/cli/team/team-projects.js +94 -0
  101. package/cli/team/team-projects.spec.js +116 -0
  102. package/cli/team/team-schema-memory.js +25 -0
  103. package/cli/team/team-schema-projects.js +50 -0
  104. package/cli/team/team-schema.js +33 -14
  105. package/cli/team/team-tasks-hierarchy.spec.js +176 -0
  106. package/cli/team/team-tokens.js +95 -0
  107. package/cli/team/team-triage.js +274 -52
  108. package/cli/team/team-verification-gate.spec.js +253 -0
  109. package/cli/team/team.spec.js +1 -1
  110. package/cli/tesseract-hud.js +59 -0
  111. package/cli/tesseract-manifesto.js +61 -0
  112. package/cli/tesseract-state.js +58 -0
  113. package/cli/tesseract.js +81 -0
  114. package/cli/tesseract.spec.js +55 -0
  115. package/cli/trend.js +84 -0
  116. package/cli/trend.spec.js +51 -0
  117. package/cli/ui-actions-tasks.js +13 -5
  118. package/cli/ui-actions.js +10 -2
  119. package/cli/ui-client-api.js +2 -2
  120. package/cli/ui-client-script.js +12 -15
  121. package/cli/ui-e2e-verification.spec.js +13 -3
  122. package/cli/ui-filetree.spec.js +5 -5
  123. package/cli/ui-handlers.js +10 -2
  124. package/cli/ui-html.js +26 -34
  125. package/cli/ui-index.html +905 -0
  126. package/cli/ui-kanban.spec.js +12 -12
  127. package/cli/ui-server.js +19 -3
  128. package/cli/ui-sse.js +57 -0
  129. package/cli/ui-sse.spec.js +74 -0
  130. package/cli/ui-styles.js +195 -81
  131. package/cli/ui-template-kanban.js +15 -1
  132. package/cli/ui-vbulletin.spec.js +12 -13
  133. package/cli/ui.spec.js +12 -5
  134. package/cli/verify.js +19 -8
  135. package/package.json +3 -1
  136. package/scripts/check-framework-generation.mjs +195 -0
  137. package/scripts/pre-commit.sh +2 -7
  138. package/scripts/publish-both.mjs +4 -0
@@ -0,0 +1,33 @@
1
+ import { ANSI } from '../theme.js';
2
+
3
+ export const formatCloneReport = (cloneResult) => {
4
+ const { count, threshold, exactClones = [], nearClones = [], pairs = [] } = cloneResult;
5
+ const hasPairs = Array.isArray(pairs) && pairs.length > 0;
6
+ if (!hasPairs) {
7
+ return ` ${ANSI.LIME}✔ Zero duplicate clone hazards detected (threshold: ${threshold})${ANSI.RESET}\n`;
8
+ }
9
+
10
+ const lines = [
11
+ ` ${ANSI.BOLD}${ANSI.PINK}Semantic Clone Detection (${count} candidate pairs >= ${threshold})${ANSI.RESET}`
12
+ ];
13
+
14
+ const hasExact = exactClones.length > 0;
15
+ if (hasExact) {
16
+ lines.push(` ${ANSI.RED}${ANSI.BOLD}Hazard: ${exactClones.length} high-similarity duplicate(s) (>= 0.95)${ANSI.RESET}`);
17
+ for (const p of exactClones) {
18
+ const simPct = Math.round(p.similarity * 100);
19
+ lines.push(` ${ANSI.RED}[${simPct}%]${ANSI.RESET} ${p.fileA} <-> ${p.fileB}`);
20
+ }
21
+ }
22
+
23
+ const hasNear = nearClones.length > 0;
24
+ if (hasNear) {
25
+ lines.push(` ${ANSI.GOLD}Harmonization Candidates: ${nearClones.length} structural clone(s)${ANSI.RESET}`);
26
+ for (const p of nearClones) {
27
+ const simPct = Math.round(p.similarity * 100);
28
+ lines.push(` ${ANSI.GOLD}[${simPct}%]${ANSI.RESET} ${p.fileA} <-> ${p.fileB}`);
29
+ }
30
+ }
31
+
32
+ return lines.join('\n') + '\n';
33
+ };
@@ -0,0 +1,34 @@
1
+ import { ANSI } from '../theme.js';
2
+
3
+ export const formatHotspotGraphReport = (graphResult) => {
4
+ const { count, hotspots = [] } = graphResult;
5
+ const hasHotspots = Array.isArray(hotspots) && hotspots.length > 0;
6
+ if (!hasHotspots) {
7
+ return ` ${ANSI.LIME}✔ Zero cascading violation hotspots detected.${ANSI.RESET}\n`;
8
+ }
9
+
10
+ const lines = [
11
+ ` ${ANSI.BOLD}${ANSI.PINK}Cascading Violation Hotspot Graph (${count} files with propagating debt)${ANSI.RESET}`
12
+ ];
13
+
14
+ for (let idx = 0; idx < hotspots.length; idx++) {
15
+ const h = hotspots[idx];
16
+ const rank = idx + 1;
17
+ const blast = h.blastRadius;
18
+ const critStr = h.criticalCount > 0 ? `${ANSI.RED}${h.criticalCount} crit${ANSI.RESET}, ` : '';
19
+ const highStr = h.highCount > 0 ? `${ANSI.GOLD}${h.highCount} high${ANSI.RESET}, ` : '';
20
+ const medStr = `${h.medCount} med`;
21
+
22
+ lines.push(
23
+ ` ${ANSI.BOLD}#${rank} ${ANSI.CYAN}${h.filePath}${ANSI.RESET} [${h.tier}, ${h.lines} LOC]`
24
+ );
25
+ lines.push(
26
+ ` ${ANSI.RED}Cascading Risk: ${h.cascadingRisk} pts${ANSI.RESET} (Violations: ${critStr}${highStr}${medStr})`
27
+ );
28
+ lines.push(
29
+ ` ${ANSI.DIM}Blast Radius: ${blast.totalImpact} consumers (${blast.directConsumers} direct, ${blast.impactedTests} tests across ${blast.depth} hops)${ANSI.RESET}`
30
+ );
31
+ }
32
+
33
+ return lines.join('\n') + '\n';
34
+ };
@@ -117,9 +117,14 @@ export const RULE_REGISTRY = {
117
117
  directive: 'Extract related state and effects into dedicated domain hooks'
118
118
  },
119
119
  HOOK_RETURN_OVERLOAD: {
120
+ pillar: PILLARS.PILLAR_3,
121
+ severity: 'MEDIUM',
122
+ directive: 'Limit hook return values to 3 to 5 properties (Deprecated: use HOOK_SHAPE_CONTRACT)'
123
+ },
124
+ HOOK_SHAPE_CONTRACT: {
120
125
  pillar: PILLARS.PILLAR_3,
121
126
  severity: 'HIGH',
122
- directive: 'Limit hook return values to 3 to 5 properties (State + Status + Actions)'
127
+ directive: 'Structure hook returns into flat State, Status, and verb-prefixed Actions buckets'
123
128
  },
124
129
  CONTROLLER_VIEW_MISMATCH: {
125
130
  pillar: PILLARS.PILLAR_3,
@@ -19,8 +19,9 @@ import {
19
19
  createExtendedVisitors
20
20
  } from './extended-visitors.js';
21
21
  import { createPatternVisitors, recordTemplatePatterns } from './pattern-detector.js';
22
+ import { createHookShapeRegistry } from './hook-shape-validator.js';
22
23
 
23
- export { PILLARS, RULE_REGISTRY };
24
+ export { PILLARS, RULE_REGISTRY, createHookShapeRegistry };
24
25
 
25
26
  export const auditCode = (content, filePath, relativePath, options = {}) => {
26
27
  const violations = [];
@@ -145,7 +146,8 @@ export const auditCode = (content, filePath, relativePath, options = {}) => {
145
146
  }
146
147
 
147
148
  const traverseFn = traverse.default || traverse;
148
- const visitors = createAstVisitors({ relativePath, violations });
149
+ const hookRegistry = options.hookRegistry || createHookShapeRegistry();
150
+ const visitors = createAstVisitors({ relativePath, violations, hookRegistry });
149
151
  const slopVisitors = createAiSlopVisitors({ relativePath, violations });
150
152
  const extendedVisitors = createExtendedVisitors({ relativePath, violations });
151
153
  const patternVisitors = options.patternRegistry
@@ -169,5 +171,11 @@ export const auditCode = (content, filePath, relativePath, options = {}) => {
169
171
 
170
172
  traverseFn(ast, mergedVisitors);
171
173
 
174
+ const shouldRunLocalConsistency = !options.hookRegistry;
175
+ if (shouldRunLocalConsistency) {
176
+ const consistencyViolations = hookRegistry.validateCrossHookConsistency();
177
+ violations.push(...consistencyViolations);
178
+ }
179
+
172
180
  return violations;
173
181
  };
@@ -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,23 +101,53 @@ 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
- const syncRes = syncSearchIndex(targetDir, process.cwd());
95
-
96
- if (syncRes?.db) {
97
- syncViolationsIndex(syncRes.db, report.violations);
98
- recordAuditSnapshot(syncRes.db, report);
99
- if (rawArgs.includes('--triage')) {
100
- const createdTasks = autoGenerateTasksFromAudit(syncRes.db, { cwd: process.cwd(), targetDir });
101
- if (isCli && !isJson) {
102
- process.stdout.write(`\x1b[32m✔\x1b[0m Auto-triage generated ${createdTasks.length} team task(s) from audit violations.\n`);
107
+ try {
108
+ const syncRes = syncSearchIndex(targetDir, process.cwd());
109
+
110
+ if (syncRes?.db) {
111
+ syncViolationsIndex(syncRes.db, report.violations);
112
+ const shouldTriage = !rawArgs.includes('--no-triage');
113
+ if (shouldTriage) {
114
+ const createdTasks = autoGenerateTasksFromAudit(syncRes.db, { cwd: process.cwd(), targetDir });
115
+ if (isCli && !isJson && createdTasks.length > 0) {
116
+ process.stdout.write(`\x1b[32m✔\x1b[0m Auto-triage synchronized ${createdTasks.length} team task(s) in SQLite backlog.\n`);
117
+ }
118
+ }
119
+ if (rawArgs.includes('--clones')) {
120
+ const { detectSemanticClones } = await import('../audit/clone-detector.js');
121
+ const { formatCloneReport } = await import('../audit/reporter-clones.js');
122
+ const cloneThresholdArg = rawArgs.find((a) => a.startsWith('--clone-threshold='));
123
+ const threshold = cloneThresholdArg ? parseFloat(cloneThresholdArg.split('=')[1]) : 0.85;
124
+ const clones = detectSemanticClones(syncRes.db, { threshold });
125
+ if (isJson) {
126
+ report.clones = clones;
127
+ } else {
128
+ process.stdout.write(formatCloneReport(clones));
129
+ }
130
+ }
131
+ if (rawArgs.includes('--hotspot-graph')) {
132
+ const { calculateCascadingHotspotGraph } = await import('../search-queries-hotspot-graph.js');
133
+ const { formatHotspotGraphReport } = await import('../audit/reporter-hotspot-graph.js');
134
+ const limitArg = rawArgs.find((a) => a.startsWith('--limit='));
135
+ const limit = limitArg ? parseInt(limitArg.split('=')[1], 10) : 10;
136
+ const graph = calculateCascadingHotspotGraph(syncRes.db, { limit });
137
+ if (isJson) {
138
+ report.hotspotGraph = graph;
139
+ } else {
140
+ process.stdout.write(formatHotspotGraphReport(graph));
141
+ }
103
142
  }
104
143
  }
144
+ } catch {
145
+ // Gracefully bypass indexing when database is readonly in sandboxed environment
105
146
  }
106
147
 
107
148
  // Stage 1: Atomic failure predicates
149
+ const isCriticalViolation = (v) => v.severity === 'CRITICAL';
150
+ const hasCritical = report.violations.some(isCriticalViolation);
108
151
  const isSevereViolation = (v) => v.severity === 'CRITICAL' || v.severity === 'HIGH';
109
152
  const hasCriticalOrHigh = report.violations.some(isSevereViolation);
110
153
  const hasViolations = report.violations.length > 0;
@@ -116,7 +159,8 @@ export const runAudit = async (customDir, isCli, rawArgs, loadProjectConfig) =>
116
159
  const isDefaultFail = !hasThreshold && !isStrict && hasCriticalOrHigh;
117
160
 
118
161
  // Stage 2: Unified failure decision
119
- const hasFailingViolations = evaluateAuditFailure([isStrictFail, isDefaultFail, isGradeFail, isScoreFail]);
162
+ const hasStandardFailure = evaluateAuditFailure([isStrictFail, isDefaultFail, isGradeFail, isScoreFail]);
163
+ const hasFailingViolations = isDraft ? hasCritical : hasStandardFailure;
120
164
 
121
165
  if (isJson) {
122
166
  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');
@@ -168,7 +200,24 @@ export const dispatchCommand = async (firstArg, rawArgs, runAudit, getPackageVer
168
200
  const { startUiServer } = await import('../ui-server.js');
169
201
  const portArg = rawArgs.find((a) => a.startsWith('--port='));
170
202
  const port = portArg ? parseInt(portArg.split('=')[1], 10) : 4173;
171
- await startUiServer({ port, isCli: true, cwd: process.cwd() });
203
+ const hostArg = rawArgs.find((a) => a.startsWith('--host='));
204
+ const host = hostArg ? hostArg.split('=')[1] : '0.0.0.0';
205
+ const { server } = await startUiServer({ port, host, isCli: true, cwd: process.cwd() });
206
+ await new Promise((resolve) => {
207
+ const shutdown = () => {
208
+ server.close(() => resolve());
209
+ };
210
+ process.on('SIGINT', shutdown);
211
+ process.on('SIGTERM', shutdown);
212
+ server.on('close', resolve);
213
+ });
214
+ break;
215
+ }
216
+ case 'tesseract':
217
+ case 'cube':
218
+ case 'matrix': {
219
+ const { runTesseract } = await import('../tesseract.js');
220
+ await runTesseract(rawArgs.slice(1), true);
172
221
  break;
173
222
  }
174
223
  case 'help':
@@ -194,5 +194,33 @@ 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
+ ]
211
+ },
212
+ {
213
+ name: 'tesseract',
214
+ aliases: ['cube', 'matrix'],
215
+ usage: 'npx chemx tesseract [options]',
216
+ summary: 'Autonomous AI Cognitive Bridge and Human-AI Symbiosis Interface.',
217
+ description: 'Deploys the full Chemical X cognitive lattice, AST topology, swarm telemetry, and Jarvis operational directives for instant AI onboarding.',
218
+ flags: [
219
+ { flag: '--json', desc: 'Output machine-readable JSON cognitive payload' }
220
+ ],
221
+ examples: [
222
+ 'npx chemx tesseract',
223
+ 'npx chemx tesseract --json'
224
+ ]
197
225
  }
198
226
  ];