@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.
- package/cli/audit/ast-visitors.js +10 -17
- package/cli/audit/autofix.js +17 -2
- package/cli/audit/clone-detector.js +64 -0
- package/cli/audit/clone-detector.spec.js +46 -0
- package/cli/audit/hook-shape-validator.js +312 -0
- package/cli/audit/prompts.js +1 -1
- package/cli/audit/reporter-clones.js +33 -0
- package/cli/audit/reporter-hotspot-graph.js +34 -0
- package/cli/audit/rules-registry.js +6 -1
- package/cli/audit/rules.js +10 -2
- package/cli/audit/rules.spec.js +129 -0
- package/cli/audit-stages.spec.js +30 -0
- package/cli/audit.js +12 -3
- package/cli/commands/cmd-audit.js +43 -3
- package/cli/commands/cmd-project.js +100 -0
- package/cli/commands/cmd-router.js +34 -2
- package/cli/commands-schema.js +14 -0
- package/cli/create.spec.js +2 -2
- package/cli/exploder.js +258 -0
- package/cli/exploder.spec.js +99 -0
- package/cli/generator-compact.spec.js +104 -0
- package/cli/generator-framework.spec.js +72 -0
- package/cli/generator-templates/archetypes/collection-archetypes.js +9 -9
- package/cli/generator-templates/archetypes/index.js +34 -4
- package/cli/generator-templates/archetypes/input-archetypes.js +2 -2
- package/cli/generator-templates/archetypes/layout-archetypes.js +2 -2
- package/cli/generator-templates/archetypes/minimal-archetypes.js +131 -0
- package/cli/generator-templates/archetypes/status-archetypes.js +4 -4
- package/cli/generator-templates/archetypes/vector-matcher.js +50 -0
- package/cli/generator-templates/archetypes/vector-matcher.spec.js +25 -0
- package/cli/generator-templates/compact.js +130 -0
- package/cli/generator-templates/controller-converters.js +93 -0
- package/cli/generator-templates/controller.js +2 -64
- package/cli/generator-templates/react.js +1 -1
- package/cli/generator-templates/svelte.js +1 -1
- package/cli/generator-templates/types.js +2 -2
- package/cli/generator-templates/vue.js +1 -1
- package/cli/generator-templates.js +1 -0
- package/cli/generator-templates.spec.js +6 -1
- package/cli/generator.js +94 -7
- package/cli/help.js +1 -1
- package/cli/index.js +21 -8
- package/cli/installer-templates.js +1 -1
- package/cli/mcp/antigravity.js +3 -2
- package/cli/mcp/installer.js +7 -7
- package/cli/mcp/installer.spec.js +3 -3
- package/cli/mcp/manifests.js +3 -2
- package/cli/mcp/prompts.js +1 -1
- package/cli/mcp/server.js +2 -0
- package/cli/mcp/tools-audit.js +95 -0
- package/cli/mcp/tools-generate.js +62 -0
- package/cli/mcp/tools-patch.js +72 -0
- package/cli/mcp/tools-patterns.js +71 -0
- package/cli/mcp/tools-project.js +75 -0
- package/cli/mcp/tools-project.spec.js +51 -0
- package/cli/mcp/tools-q-modes.js +69 -0
- package/cli/mcp/tools-q.js +58 -0
- package/cli/mcp/tools-read.js +69 -0
- package/cli/mcp/tools-search-util.js +35 -0
- package/cli/mcp/tools-search.js +15 -294
- package/cli/mcp/tools-team-feed.js +57 -0
- package/cli/mcp/tools-team-locks.js +63 -0
- package/cli/mcp/tools-team-mailbox.js +32 -0
- package/cli/mcp/tools-team-tasks.js +84 -0
- package/cli/mcp/tools-team.js +37 -183
- package/cli/mcp/tools-verify.js +47 -0
- package/cli/mcp/tools-write.js +33 -0
- package/cli/mcp/tools.js +72 -376
- package/cli/mutators.js +10 -23
- package/cli/pillars-schema.js +3 -3
- package/cli/project-detector.js +47 -0
- package/cli/reader-shorthand.spec.js +60 -0
- package/cli/reader.js +67 -24
- package/cli/search-bulletproof.spec.js +104 -0
- package/cli/search-db.js +33 -5
- package/cli/search-queries-hotspot-graph.js +65 -0
- package/cli/search-queries-hotspot-graph.spec.js +66 -0
- package/cli/search-queries-similar.js +84 -0
- package/cli/search-queries-similar.spec.js +43 -0
- package/cli/search-schema.js +30 -10
- package/cli/search.js +9 -2
- package/cli/team/index.js +9 -0
- package/cli/team/team-commands.js +104 -64
- package/cli/team/team-concurrency-stress.spec.js +95 -0
- package/cli/team/team-db-mailbox.js +86 -0
- package/cli/team/team-db-task-helpers.js +30 -28
- package/cli/team/team-db-tasks.js +13 -5
- package/cli/team/team-flags.js +69 -0
- package/cli/team/team-format.js +87 -46
- package/cli/team/team-mailbox-empirical.spec.js +191 -0
- package/cli/team/team-mailbox.spec.js +93 -0
- package/cli/team/team-memory.js +84 -0
- package/cli/team/team-memory.spec.js +74 -0
- package/cli/team/team-projects-coordinator.js +100 -0
- package/cli/team/team-projects-format.js +45 -0
- package/cli/team/team-projects-memory.js +70 -0
- package/cli/team/team-projects.js +94 -0
- package/cli/team/team-projects.spec.js +116 -0
- package/cli/team/team-schema-memory.js +25 -0
- package/cli/team/team-schema-projects.js +50 -0
- package/cli/team/team-schema.js +33 -14
- package/cli/team/team-tasks-hierarchy.spec.js +80 -0
- package/cli/team/team-tokens.js +95 -0
- package/cli/team/team-triage.js +159 -12
- package/cli/team/team-verification-gate.spec.js +253 -0
- package/cli/team/team.spec.js +1 -1
- package/cli/trend.js +84 -0
- package/cli/trend.spec.js +51 -0
- package/cli/ui-actions-tasks.js +13 -5
- package/cli/ui-actions.js +10 -2
- package/cli/ui-client-api.js +2 -2
- package/cli/ui-client-script.js +12 -15
- package/cli/ui-e2e-verification.spec.js +13 -3
- package/cli/ui-filetree.spec.js +5 -5
- package/cli/ui-handlers.js +10 -2
- package/cli/ui-html.js +26 -34
- package/cli/ui-index.html +905 -0
- package/cli/ui-kanban.spec.js +12 -12
- package/cli/ui-server.js +7 -0
- package/cli/ui-sse.js +57 -0
- package/cli/ui-sse.spec.js +74 -0
- package/cli/ui-styles.js +195 -81
- package/cli/ui-template-kanban.js +15 -1
- package/cli/ui-vbulletin.spec.js +12 -13
- package/cli/ui.spec.js +12 -5
- package/cli/verify.js +19 -8
- package/package.json +3 -1
- package/scripts/check-framework-generation.mjs +195 -0
- package/scripts/pre-commit.sh +2 -7
- package/scripts/publish-both.mjs +4 -0
package/cli/audit/rules.spec.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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');
|
package/cli/commands-schema.js
CHANGED
|
@@ -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
|
];
|
package/cli/create.spec.js
CHANGED
|
@@ -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:
|
|
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:
|
|
81
|
+
timeout: 15000
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
assert.equal(result.status, 0, 'Should exit with code 0 on --version');
|