@chemx/starter-kit 26.9.23-1269 → 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.
- package/cli/audit/history.js +22 -6
- package/cli/audit/hook-shape-validator.js +1 -1
- package/cli/audit/prompts.js +12 -9
- package/cli/audit/prompts.spec.js +52 -1
- package/cli/commands/cmd-audit.js +35 -31
- package/cli/commands/cmd-router.js +18 -1
- package/cli/commands-schema.js +14 -0
- package/cli/index.js +2 -0
- package/cli/mcp/antigravity.js +2 -0
- package/cli/mcp/manifests.js +2 -1
- package/cli/mcp/tools-audit.js +2 -0
- package/cli/mcp/tools-team-tasks.js +2 -1
- package/cli/mcp/tools.js +13 -2
- package/cli/search-schema.js +54 -6
- package/cli/search.js +4 -0
- package/cli/team/team-commands.js +4 -2
- package/cli/team/team-db-lock-promotion.js +15 -11
- package/cli/team/team-db-task-helpers.js +50 -0
- package/cli/team/team-flags.js +4 -1
- package/cli/team/team-tasks-hierarchy.spec.js +97 -1
- package/cli/team/team-triage.js +118 -43
- package/cli/tesseract-hud.js +59 -0
- package/cli/tesseract-manifesto.js +61 -0
- package/cli/tesseract-state.js +58 -0
- package/cli/tesseract.js +81 -0
- package/cli/tesseract.spec.js +55 -0
- package/cli/ui-server.js +12 -3
- package/package.json +1 -1
package/cli/audit/history.js
CHANGED
|
@@ -13,8 +13,12 @@ const RED = '\x1b[31m';
|
|
|
13
13
|
|
|
14
14
|
export const ensureChemxDir = (cwd = process.cwd()) => {
|
|
15
15
|
const dir = path.resolve(cwd, '.chemx');
|
|
16
|
-
|
|
17
|
-
fs.
|
|
16
|
+
try {
|
|
17
|
+
if (!fs.existsSync(dir)) {
|
|
18
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
return dir;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
// Ensure .chemx is gitignored if git repo exists
|
|
@@ -165,7 +169,11 @@ export const getAuditBaseline = (cwd = process.cwd()) => {
|
|
|
165
169
|
export const setAuditBaseline = (snapshot, cwd = process.cwd()) => {
|
|
166
170
|
ensureChemxDir(cwd);
|
|
167
171
|
const baselinePath = path.resolve(cwd, '.chemx', 'baseline.json');
|
|
168
|
-
|
|
172
|
+
try {
|
|
173
|
+
fs.writeFileSync(baselinePath, JSON.stringify(snapshot, null, 2), 'utf-8');
|
|
174
|
+
} catch {
|
|
175
|
+
// Read-only filesystem in sandbox
|
|
176
|
+
}
|
|
169
177
|
return snapshot;
|
|
170
178
|
};
|
|
171
179
|
|
|
@@ -196,7 +204,11 @@ export const saveAuditSnapshot = (report, cwd = process.cwd()) => {
|
|
|
196
204
|
// Keep last 50 snapshots
|
|
197
205
|
const trimmed = history.slice(-50);
|
|
198
206
|
const historyPath = path.resolve(cwd, '.chemx', 'history.json');
|
|
199
|
-
|
|
207
|
+
try {
|
|
208
|
+
fs.writeFileSync(historyPath, JSON.stringify(trimmed, null, 2), 'utf-8');
|
|
209
|
+
} catch {
|
|
210
|
+
// Read-only filesystem in sandbox
|
|
211
|
+
}
|
|
200
212
|
|
|
201
213
|
// Auto-establish first run or lower score as baseline floor
|
|
202
214
|
const baselinePath = path.resolve(cwd, '.chemx', 'baseline.json');
|
|
@@ -206,8 +218,12 @@ export const saveAuditSnapshot = (report, cwd = process.cwd()) => {
|
|
|
206
218
|
const baselineScore = currentBaseline?.health?.score ?? 101;
|
|
207
219
|
|
|
208
220
|
if (!currentBaseline || currentScore < baselineScore) {
|
|
209
|
-
|
|
210
|
-
|
|
221
|
+
try {
|
|
222
|
+
fs.writeFileSync(baselinePath, JSON.stringify(snapshot, null, 2), 'utf-8');
|
|
223
|
+
isNewBaseline = true;
|
|
224
|
+
} catch {
|
|
225
|
+
// Read-only filesystem in sandbox
|
|
226
|
+
}
|
|
211
227
|
}
|
|
212
228
|
|
|
213
229
|
const baseline = getAuditBaseline(cwd);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as t from '@babel/types';
|
|
2
2
|
import { RULE_REGISTRY } from './rules-registry.js';
|
|
3
3
|
|
|
4
|
-
const VERB_PREFIX_REGEX = /^(add|remove|toggle|set|get|fetch|load|save|delete|clear|reset|refresh|update|create|edit|send|sync|init|dispose|trigger|post|put|patch|apply|dismiss|select|deselect|filter|sort|search|open|close|show|hide|start|stop|cancel|execute|run|retry|submit|validate|mutate|next|prev|step|increment|decrement|insert|upsert|drop|enable|disable|register|unregister|login|logout|checkout|publish|archive|discard|confirm|undo|redo|claim|complete|acquire|release|download|upload|export|import|handle)([A-Z0-9_].*)?$/;
|
|
4
|
+
const VERB_PREFIX_REGEX = /^(add|remove|toggle|set|get|fetch|load|save|delete|clear|reset|refresh|update|create|edit|send|sync|init|setup|dispose|trigger|post|put|patch|apply|dismiss|select|deselect|filter|sort|search|open|close|show|hide|start|stop|cancel|execute|run|retry|submit|validate|mutate|next|prev|step|increment|decrement|insert|upsert|drop|enable|disable|register|unregister|login|logout|checkout|publish|archive|discard|confirm|undo|redo|claim|complete|acquire|release|download|upload|export|import|handle|play|pause|resume|seek|unlock|restore|advance|focus|notify|broadcast|copy|format)([A-Z0-9_].*)?$/;
|
|
5
5
|
|
|
6
6
|
const STATUS_HEURISTIC_REGEX = /^(is[A-Z]|has[A-Z]|can[A-Z]|should[A-Z])|.*(Error|Status)$/;
|
|
7
7
|
const STATUS_EXACT_NAMES = new Set(['status', 'error', 'loading', 'pending', 'ready']);
|
package/cli/audit/prompts.js
CHANGED
|
@@ -36,7 +36,7 @@ const renderTreeLines = (node, depth = 0) => {
|
|
|
36
36
|
return lines;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
export const formatGroupedPromptViolations = (violations = []) => {
|
|
39
|
+
export const formatGroupedPromptViolations = (violations = [], options = {}) => {
|
|
40
40
|
const hasNoViolations = violations.length === 0;
|
|
41
41
|
if (hasNoViolations) return [];
|
|
42
42
|
|
|
@@ -49,11 +49,14 @@ export const formatGroupedPromptViolations = (violations = []) => {
|
|
|
49
49
|
lines.push(`${idx + 1}. ${sevColor}[${rg.rule}]${RESET} ${BOLD}(${countLabel})${RESET}`);
|
|
50
50
|
lines.push(` Hazard: ${rg.hazard}`);
|
|
51
51
|
lines.push(` Directive: ${CYAN}${rg.directive}${RESET}`);
|
|
52
|
-
lines.push(
|
|
52
|
+
lines.push(` Action: chemx team task list --rule=${rg.rule}`);
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
if (options.detailedLocations) {
|
|
55
|
+
lines.push(' Locations:');
|
|
56
|
+
const tree = buildPathTree(rg.violations);
|
|
57
|
+
const treeLines = renderTreeLines(tree, 0);
|
|
58
|
+
lines.push(...treeLines);
|
|
59
|
+
}
|
|
57
60
|
lines.push('');
|
|
58
61
|
});
|
|
59
62
|
|
|
@@ -62,10 +65,10 @@ export const formatGroupedPromptViolations = (violations = []) => {
|
|
|
62
65
|
|
|
63
66
|
export const buildAgentCommandsSection = () => [
|
|
64
67
|
'### AI AGENT DISCOVERY & REFACTORING COMMANDS:',
|
|
65
|
-
'-
|
|
66
|
-
'-
|
|
67
|
-
'-
|
|
68
|
-
'-
|
|
68
|
+
'- Claim Work: Run `chemx team task list --status=queued` and claim via `chemx team task claim <id> --as=@coder`.',
|
|
69
|
+
'- Inspect Target: Run `chemx read <target_path> --outline` to inspect AST signatures without token bloat.',
|
|
70
|
+
'- Verify & Auto-Resolve: Run `chemx verify`. Clean files auto-reconcile tasks to done in SQLite.',
|
|
71
|
+
'- MCP Invocations: If invoking via MCP, pass command string directly via `chemx({ command: "..." })`.'
|
|
69
72
|
].join('\n');
|
|
70
73
|
|
|
71
74
|
export const buildGradeFPrompt = (report, options = {}) => {
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
buildAiSlopPrompt,
|
|
11
11
|
buildHotspotsPrompt,
|
|
12
12
|
buildPillarPrompt,
|
|
13
|
-
buildMasterPrompt
|
|
13
|
+
buildMasterPrompt,
|
|
14
|
+
formatGroupedPromptViolations
|
|
14
15
|
} from './prompts.js';
|
|
15
16
|
|
|
16
17
|
test('deduplicateRolePreambles: handles invalid and empty inputs', () => {
|
|
@@ -314,3 +315,53 @@ test('deduplicateRefactoringCommands: collapses multiple occurrences to single b
|
|
|
314
315
|
assert.ok(result.includes('Section 2 content'));
|
|
315
316
|
assert.ok(result.includes('- Command B'));
|
|
316
317
|
});
|
|
318
|
+
|
|
319
|
+
test('formatGroupedPromptViolations: outputs token-compact task action by default', () => {
|
|
320
|
+
const violations = [
|
|
321
|
+
{
|
|
322
|
+
rule: 'SECURITY_RAW_HTML_INJECTION',
|
|
323
|
+
severity: 'CRITICAL',
|
|
324
|
+
hazard: 'Unsanitized HTML injection',
|
|
325
|
+
directive: 'Sanitize dynamic HTML via DOMPurify',
|
|
326
|
+
filePath: 'src/view-a.vue',
|
|
327
|
+
line: 10
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
rule: 'SECURITY_RAW_HTML_INJECTION',
|
|
331
|
+
severity: 'CRITICAL',
|
|
332
|
+
hazard: 'Unsanitized HTML injection',
|
|
333
|
+
directive: 'Sanitize dynamic HTML via DOMPurify',
|
|
334
|
+
filePath: 'src/view-b.vue',
|
|
335
|
+
line: 25
|
|
336
|
+
}
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
const lines = formatGroupedPromptViolations(violations);
|
|
340
|
+
const text = lines.join('\n');
|
|
341
|
+
|
|
342
|
+
assert.ok(text.includes('[SECURITY_RAW_HTML_INJECTION]'));
|
|
343
|
+
assert.ok(text.includes('(2 items)'));
|
|
344
|
+
assert.ok(text.includes('Action: chemx team task list --rule=SECURITY_RAW_HTML_INJECTION'));
|
|
345
|
+
// Does not dump file locations tree by default
|
|
346
|
+
assert.ok(!text.includes('Locations:'));
|
|
347
|
+
assert.ok(!text.includes('view-a.vue:10'));
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('formatGroupedPromptViolations: renders file tree when detailedLocations is enabled', () => {
|
|
351
|
+
const violations = [
|
|
352
|
+
{
|
|
353
|
+
rule: 'SECURITY_RAW_HTML_INJECTION',
|
|
354
|
+
severity: 'CRITICAL',
|
|
355
|
+
hazard: 'Unsanitized HTML injection',
|
|
356
|
+
directive: 'Sanitize dynamic HTML via DOMPurify',
|
|
357
|
+
filePath: 'src/view-a.vue',
|
|
358
|
+
line: 10
|
|
359
|
+
}
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
const lines = formatGroupedPromptViolations(violations, { detailedLocations: true });
|
|
363
|
+
const text = lines.join('\n');
|
|
364
|
+
|
|
365
|
+
assert.ok(text.includes('Locations:'));
|
|
366
|
+
assert.ok(text.includes('view-a.vue:10'));
|
|
367
|
+
});
|
|
@@ -104,41 +104,45 @@ export const runAudit = async (customDir, isCli, rawArgs, loadProjectConfig) =>
|
|
|
104
104
|
const auditOptions = { outputFile, model, costPerMillion, fast: isFast, fileList, stage };
|
|
105
105
|
const report = executeAstAudit(targetDir, auditOptions);
|
|
106
106
|
saveAuditSnapshot(report);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
}
|
|
116
118
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
}
|
|
128
130
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
}
|
|
140
142
|
}
|
|
141
143
|
}
|
|
144
|
+
} catch {
|
|
145
|
+
// Gracefully bypass indexing when database is readonly in sandboxed environment
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
// Stage 1: Atomic failure predicates
|
|
@@ -200,7 +200,24 @@ export const dispatchCommand = async (firstArg, rawArgs, runAudit, getPackageVer
|
|
|
200
200
|
const { startUiServer } = await import('../ui-server.js');
|
|
201
201
|
const portArg = rawArgs.find((a) => a.startsWith('--port='));
|
|
202
202
|
const port = portArg ? parseInt(portArg.split('=')[1], 10) : 4173;
|
|
203
|
-
|
|
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);
|
|
204
221
|
break;
|
|
205
222
|
}
|
|
206
223
|
case 'help':
|
package/cli/commands-schema.js
CHANGED
|
@@ -208,5 +208,19 @@ export const COMMANDS_SCHEMA = [
|
|
|
208
208
|
'npx chemx ui',
|
|
209
209
|
'npx chemx dashboard --port=3000'
|
|
210
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
|
+
]
|
|
211
225
|
}
|
|
212
226
|
];
|
package/cli/index.js
CHANGED
|
@@ -69,6 +69,7 @@ export const writeFile = async (...a) => (await import('./patcher.js')).wr
|
|
|
69
69
|
export const runTeamCli = async (...a) => (await import('./team/index.js')).runTeamCli(...a);
|
|
70
70
|
export const runTrend = async (...a) => (await import('./trend.js')).runTrend(...a);
|
|
71
71
|
export const runPillarsWizard = async (...a) => (await import('./pillars-wizard.js')).runPillarsWizard(...a);
|
|
72
|
+
export const runTesseract = async (...a) => (await import('./tesseract.js')).runTesseract(...a);
|
|
72
73
|
export { handleError, withErrorCatcher, publishIssue };
|
|
73
74
|
|
|
74
75
|
// ---------------------------------------------------------------------------
|
|
@@ -100,6 +101,7 @@ export const ALLOWED_COMMANDS = new Set([
|
|
|
100
101
|
'init',
|
|
101
102
|
'hook', 'hooks', 'install-hooks', 'setup-ci',
|
|
102
103
|
'add:prop', 'add:state', 'add:action', 'fix',
|
|
104
|
+
'tesseract', 'cube', 'matrix',
|
|
103
105
|
'help', '--help', '-h',
|
|
104
106
|
'version', '--version', '-v'
|
|
105
107
|
]);
|
package/cli/mcp/antigravity.js
CHANGED
|
@@ -11,6 +11,8 @@ export const buildMcpInstructions = () => {
|
|
|
11
11
|
'Provides all Chemical X operations through a single tool. This avoids requiring',
|
|
12
12
|
'separate approval for each sub-operation in MCP clients that gate tool access per-tool.',
|
|
13
13
|
'Pass the desired operation via the action parameter; see action: \'help\' for the full list.',
|
|
14
|
+
'0. AUTONOMOUS COGNITIVE BRIDGE (TESSERACT):',
|
|
15
|
+
' - chemx({ action: "tesseract" }) or chemx({ command: "tesseract" }) -> Boot Jarvis cognitive lattice and mission protocol',
|
|
14
16
|
'',
|
|
15
17
|
'1. TESTING, BUILDS & VERIFICATION:',
|
|
16
18
|
' - chemx({ action: "test" }) or chemx({ command: "test" })',
|
package/cli/mcp/manifests.js
CHANGED
package/cli/mcp/tools-audit.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
buildHotspotsPrompt
|
|
12
12
|
} from '../audit/prompts.js';
|
|
13
13
|
import { syncSearchIndex, syncViolationsIndex, recordAuditSnapshot } from '../search.js';
|
|
14
|
+
import { autoGenerateTasksFromAudit } from '../team/team-triage.js';
|
|
14
15
|
import { resolveTargetCwd } from './tools-search.js';
|
|
15
16
|
|
|
16
17
|
export const handleAudit = (args = {}, cwd = process.cwd()) => {
|
|
@@ -43,6 +44,7 @@ export const handleAudit = (args = {}, cwd = process.cwd()) => {
|
|
|
43
44
|
if (syncRes?.db) {
|
|
44
45
|
syncViolationsIndex(syncRes.db, report.violations);
|
|
45
46
|
recordAuditSnapshot(syncRes.db, report);
|
|
47
|
+
autoGenerateTasksFromAudit(syncRes.db, { cwd: baseCwd, targetDir: resolvedTarget });
|
|
46
48
|
}
|
|
47
49
|
} catch (syncError) {
|
|
48
50
|
process.stderr.write(`[chemx] Search index sync bypassed: ${syncError?.message || String(syncError)}\n`);
|
|
@@ -18,7 +18,8 @@ export const handleChemxTeamTask = async (args = {}, cwd = process.cwd()) => {
|
|
|
18
18
|
|
|
19
19
|
if (action === 'list') {
|
|
20
20
|
const parentId = args.parentId !== undefined ? args.parentId : args.parent;
|
|
21
|
-
const
|
|
21
|
+
const rule = args.rule || args.ruleId;
|
|
22
|
+
const tasks = listTasks(db, { status: args.status, assigned_agent_id: args.agentId, parentId, rule, priority: args.priority });
|
|
22
23
|
const col = toColumnar(tasks, ['id', 'title', 'tier', 'status', 'priority', 'assigned_agent_id', 'target_path', 'parent_id']);
|
|
23
24
|
return { ...col, total: tasks.length, card: formatTaskListCard(tasks) };
|
|
24
25
|
}
|
package/cli/mcp/tools.js
CHANGED
|
@@ -9,7 +9,7 @@ import { handleChemxProject } from './tools-project.js';
|
|
|
9
9
|
|
|
10
10
|
export {
|
|
11
11
|
MCP_TOOLS, ALL_MCP_TOOLS, handleChemxQ, handleChemxRead, handleChemxPatch, handleChemxCheck, handleChemxWrite,
|
|
12
|
-
handleChemxTeamStatus, handleChemxTeamFeed, handleChemxTeamPost, handleChemxTeamTask, handleChemxTeamLock, handleChemxReportIssue, handleChemxProject
|
|
12
|
+
handleChemxTeamStatus, handleChemxTeamFeed, handleChemxTeamPost, handleChemxTeamTask, handleChemxTeamLock, handleChemxReportIssue, handleChemxProject, handleChemxTesseract
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const parseCommand = (command, params) => {
|
|
@@ -40,9 +40,19 @@ const parseCommand = (command, params) => {
|
|
|
40
40
|
}
|
|
41
41
|
if (subCmd === 'autofix') return { action: 'autofix', params: { path: parts[1] || 'src', ...params } };
|
|
42
42
|
if (subCmd === 'trend' || subCmd === 'trends') return { action: 'trend', params };
|
|
43
|
+
if (['tesseract', 'cube', 'matrix'].includes(subCmd)) return { action: 'tesseract', params };
|
|
43
44
|
return { action: subCmd, params };
|
|
44
45
|
};
|
|
45
46
|
|
|
47
|
+
const handleChemxTesseract = async (params = {}, cwd = process.cwd()) => {
|
|
48
|
+
const { runTesseract } = await import('../tesseract.js');
|
|
49
|
+
const result = await runTesseract(params.args || [], false, cwd);
|
|
50
|
+
const textOutput = result.text || JSON.stringify(result.payload, null, 2);
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text', text: textOutput }]
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
46
56
|
const DISPATCHER = {
|
|
47
57
|
audit: handleAudit, trend: handleChemxTrend, build: handleAuditBuild, verify: handleChemxVerify,
|
|
48
58
|
typecheck: handleChemxTypecheck, test: handleChemxTest, check: handleChemxCheck,
|
|
@@ -51,7 +61,8 @@ const DISPATCHER = {
|
|
|
51
61
|
team_status: handleChemxTeamStatus, team_feed: handleChemxTeamFeed, team_post: handleChemxTeamPost,
|
|
52
62
|
team_task: handleChemxTeamTask, team_lock: handleChemxTeamLock, q: handleChemxQ, search: handleChemxQ,
|
|
53
63
|
autofix: handleAutofix, generate: handleGenerateCapsule, patterns: handleQueryPatterns,
|
|
54
|
-
issue: handleChemxReportIssue, project: handleChemxProject, coordinator: handleChemxProject
|
|
64
|
+
issue: handleChemxReportIssue, project: handleChemxProject, coordinator: handleChemxProject,
|
|
65
|
+
tesseract: handleChemxTesseract, cube: handleChemxTesseract, matrix: handleChemxTesseract
|
|
55
66
|
};
|
|
56
67
|
|
|
57
68
|
export const handleChemx = async (args = {}, cwd = process.cwd()) => {
|
package/cli/search-schema.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import './silence-warnings.js';
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { ensureChemxDir } from './audit/history.js';
|
|
4
5
|
import { initTeamSchema } from './team/team-schema.js';
|
|
@@ -36,14 +37,61 @@ export const openIndexDb = (cwd = process.cwd(), options = {}) => {
|
|
|
36
37
|
return DB_CACHE.get(dbPath);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
let isReadOnly = false;
|
|
41
41
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
fs.accessSync(path.dirname(dbPath), fs.constants.W_OK);
|
|
43
|
+
if (fs.existsSync(dbPath)) {
|
|
44
|
+
fs.accessSync(dbPath, fs.constants.W_OK);
|
|
45
|
+
}
|
|
45
46
|
} catch {
|
|
46
|
-
|
|
47
|
+
isReadOnly = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let db = null;
|
|
51
|
+
try {
|
|
52
|
+
db = new DatabaseSync(dbPath, isReadOnly ? { readOnly: true } : {});
|
|
53
|
+
if (!isReadOnly) {
|
|
54
|
+
try {
|
|
55
|
+
db.exec('PRAGMA busy_timeout = 5000;');
|
|
56
|
+
db.exec('PRAGMA journal_mode = WAL;');
|
|
57
|
+
db.exec('PRAGMA foreign_keys = ON;');
|
|
58
|
+
} catch {
|
|
59
|
+
// Safe retry/fallback if concurrent worker holds active lock
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (!isReadOnly) {
|
|
64
|
+
try {
|
|
65
|
+
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
66
|
+
isReadOnly = true;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isReadOnly) {
|
|
76
|
+
if (typeof db.function === 'function') {
|
|
77
|
+
try {
|
|
78
|
+
db.function('vec_cosine', (b1, b2) => {
|
|
79
|
+
if (!b1 || !b2) return 0;
|
|
80
|
+
const a = new Float32Array(b1.buffer, b1.byteOffset, b1.byteLength / 4);
|
|
81
|
+
const b = new Float32Array(b2.buffer, b2.byteOffset, b2.byteLength / 4);
|
|
82
|
+
let dot = 0;
|
|
83
|
+
const len = Math.min(a.length, b.length);
|
|
84
|
+
for (let i = 0; i < len; i++) {
|
|
85
|
+
dot += a[i] * b[i];
|
|
86
|
+
}
|
|
87
|
+
return Math.max(0, Math.min(1, dot));
|
|
88
|
+
});
|
|
89
|
+
} catch {
|
|
90
|
+
// Ignored if already registered
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
DB_CACHE.set(dbPath, db);
|
|
94
|
+
return db;
|
|
47
95
|
}
|
|
48
96
|
|
|
49
97
|
db.exec(`
|
package/cli/search.js
CHANGED
|
@@ -105,6 +105,10 @@ export const syncSearchIndex = (targetDir = 'src', cwd = process.cwd(), options
|
|
|
105
105
|
const db = openIndexDb(cwd);
|
|
106
106
|
if (!db) return null;
|
|
107
107
|
|
|
108
|
+
const isWritable = () => { try { fs.accessSync(path.join(cwd, '.chemx'), fs.constants.W_OK); return true; } catch { return false; } };
|
|
109
|
+
const shouldSkipSync = !isWritable() && !options.reindex;
|
|
110
|
+
if (shouldSkipSync) return { db, updatedCount: 0 };
|
|
111
|
+
|
|
108
112
|
const targetDirs = Array.isArray(targetDir) ? targetDir : [targetDir];
|
|
109
113
|
if (targetDir === 'src' && fs.existsSync(path.resolve(cwd, 'cli'))) {
|
|
110
114
|
targetDirs.push('cli');
|
|
@@ -25,7 +25,7 @@ import { getSwarmTokenBreakdown, formatTokenBreakdownCard } from './team-tokens.
|
|
|
25
25
|
import { runAblationComparison, formatAblationCard } from './team-memory.js';
|
|
26
26
|
import { parseFlags } from './team-flags.js';
|
|
27
27
|
|
|
28
|
-
const ARG_VAL_FLAGS = ['--target', '--as', '--to', '--agent', '--since', '--limit', '--thread', '--task', '--parent'];
|
|
28
|
+
const ARG_VAL_FLAGS = ['--target', '--as', '--to', '--agent', '--since', '--limit', '--thread', '--task', '--parent', '--rule', '--priority', '--prio'];
|
|
29
29
|
|
|
30
30
|
export const runTeamCli = (rawArgs = [], isCli = false, cwd = process.cwd()) => {
|
|
31
31
|
const db = openIndexDb(cwd);
|
|
@@ -119,7 +119,9 @@ export const runTeamCli = (rawArgs = [], isCli = false, cwd = process.cwd()) =>
|
|
|
119
119
|
const tasks = listTasks(db, {
|
|
120
120
|
status: flags.status,
|
|
121
121
|
assigned_agent_id: flags.agent,
|
|
122
|
-
parentId: flags.parent
|
|
122
|
+
parentId: flags.parent,
|
|
123
|
+
rule: flags.rule,
|
|
124
|
+
priority: flags.priority
|
|
123
125
|
});
|
|
124
126
|
if (flags.isJson) {
|
|
125
127
|
const col = toColumnar(tasks, ['id', 'title', 'tier', 'status', 'priority', 'assigned_agent_id', 'target_path', 'parent_id']);
|
|
@@ -76,16 +76,20 @@ export const cleanExpiredLeases = (db) => {
|
|
|
76
76
|
if (!hasDb) return [];
|
|
77
77
|
|
|
78
78
|
const now = Date.now();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
79
|
+
try {
|
|
80
|
+
const expired = db.prepare('SELECT * FROM file_leases WHERE expires_at <= ?').all(now);
|
|
81
|
+
for (const lease of expired) {
|
|
82
|
+
db.prepare('DELETE FROM file_leases WHERE file_path = ?').run(lease.file_path);
|
|
83
|
+
postFeedEvent(db, {
|
|
84
|
+
author_id: '@system',
|
|
85
|
+
event_type: 'lock_expired',
|
|
86
|
+
file_path: lease.file_path,
|
|
87
|
+
message: `Lease expired for ${lease.file_path} held by ${lease.locked_by}`
|
|
88
|
+
});
|
|
89
|
+
promoteNextWaiter(db, lease.file_path);
|
|
90
|
+
}
|
|
91
|
+
return expired;
|
|
92
|
+
} catch {
|
|
93
|
+
return [];
|
|
89
94
|
}
|
|
90
|
-
return expired;
|
|
91
95
|
};
|
|
@@ -90,7 +90,57 @@ export const buildTaskListQuery = (filter = {}) => {
|
|
|
90
90
|
conditions.push('parent_id = ?');
|
|
91
91
|
params.push(Number(parentId));
|
|
92
92
|
}
|
|
93
|
+
const ruleFilter = filter.rule || filter.rule_id;
|
|
94
|
+
if (ruleFilter) {
|
|
95
|
+
conditions.push('(rule_id = ? OR rule_id LIKE ?)');
|
|
96
|
+
params.push(ruleFilter, `%${ruleFilter}%`);
|
|
97
|
+
}
|
|
98
|
+
const hasPriority = filter.priority !== undefined && filter.priority !== null && filter.priority !== '';
|
|
99
|
+
if (hasPriority) {
|
|
100
|
+
conditions.push('priority = ?');
|
|
101
|
+
params.push(Number(filter.priority));
|
|
102
|
+
}
|
|
93
103
|
if (conditions.length > 0) query += ` WHERE ${conditions.join(' AND ')}`;
|
|
94
104
|
query += ' ORDER BY priority ASC, id ASC';
|
|
95
105
|
return { query, params };
|
|
96
106
|
};
|
|
107
|
+
|
|
108
|
+
export const queryUnassignedHazards = (db) => {
|
|
109
|
+
if (!db) return [];
|
|
110
|
+
const query = `
|
|
111
|
+
SELECT
|
|
112
|
+
COALESCE(v.file_path, f.path) as path,
|
|
113
|
+
COALESCE(f.tier, 'molecule') as tier,
|
|
114
|
+
COALESCE(f.lines, 0) as lines,
|
|
115
|
+
COALESCE(f.health_score, CASE WHEN COUNT(v.id) > 0 THEN MAX(20, 100 - COUNT(v.id) * 15) ELSE 100 END) as health_score,
|
|
116
|
+
COALESCE(MAX(f.hazard_count, COUNT(v.id)), COUNT(v.id)) as hazard_count,
|
|
117
|
+
COUNT(v.id) as violation_count,
|
|
118
|
+
GROUP_CONCAT(DISTINCT v.rule) as rules_summary
|
|
119
|
+
FROM violations v
|
|
120
|
+
LEFT JOIN files f ON f.path = v.file_path
|
|
121
|
+
LEFT JOIN agent_tasks t ON t.target_path = v.file_path AND t.status IN ('queued', 'in_progress', 'review')
|
|
122
|
+
WHERE t.id IS NULL
|
|
123
|
+
GROUP BY v.file_path
|
|
124
|
+
UNION
|
|
125
|
+
SELECT
|
|
126
|
+
f.path,
|
|
127
|
+
f.tier,
|
|
128
|
+
f.lines,
|
|
129
|
+
f.health_score,
|
|
130
|
+
f.hazard_count,
|
|
131
|
+
f.hazard_count as violation_count,
|
|
132
|
+
'ARCHITECTURAL_HAZARD' as rules_summary
|
|
133
|
+
FROM files f
|
|
134
|
+
LEFT JOIN agent_tasks t ON t.target_path = f.path AND t.status IN ('queued', 'in_progress', 'review')
|
|
135
|
+
WHERE (f.health_score < 90 OR f.hazard_count > 0)
|
|
136
|
+
AND t.id IS NULL
|
|
137
|
+
AND f.path NOT IN (SELECT file_path FROM violations)
|
|
138
|
+
GROUP BY f.path
|
|
139
|
+
ORDER BY health_score ASC, hazard_count DESC
|
|
140
|
+
`;
|
|
141
|
+
try {
|
|
142
|
+
return db.prepare(query).all();
|
|
143
|
+
} catch {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
};
|
package/cli/team/team-flags.js
CHANGED
|
@@ -48,7 +48,10 @@ export const parseFlags = (args = []) => {
|
|
|
48
48
|
if (arg.startsWith('--target=')) flags.target = arg.split('=')[1];
|
|
49
49
|
if (arg === '--target' && hasNext) flags.target = nextArg;
|
|
50
50
|
if (arg.startsWith('--tier=')) flags.tier = arg.split('=')[1];
|
|
51
|
-
if (arg.startsWith('--
|
|
51
|
+
if (arg.startsWith('--rule=')) flags.rule = arg.split('=')[1];
|
|
52
|
+
if (arg === '--rule' && hasNext) flags.rule = nextArg;
|
|
53
|
+
if (arg.startsWith('--prio=') || arg.startsWith('--priority=')) flags.priority = parseInt(arg.split('=')[1], 10);
|
|
54
|
+
if ((arg === '--prio' || arg === '--priority') && hasNext) flags.priority = parseInt(nextArg, 10);
|
|
52
55
|
if (arg.startsWith('--purpose=')) flags.purpose = arg.split('=')[1];
|
|
53
56
|
if (arg.startsWith('--tokens=')) flags.tokens = parseInt(arg.split('=')[1], 10);
|
|
54
57
|
if (arg.startsWith('--prompt-tokens=')) flags.promptTokens = parseInt(arg.split('=')[1], 10);
|