@chemx/starter-kit 26.9.20-390 → 26.9.20-408
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/index.js +10 -1
- package/cli/mcp/tools-team.js +37 -11
- package/cli/mcp/tools.js +13 -0
- package/cli/team/team-commands.js +61 -13
- package/cli/team/team-triage.js +67 -10
- package/cli/team/team.spec.js +103 -0
- package/package.json +1 -1
package/cli/index.js
CHANGED
|
@@ -41,7 +41,7 @@ import { runReaderCli, readTokenOptimized } from './reader.js';
|
|
|
41
41
|
import { runPatcherCli, patchFile, runWriterCli, writeFile } from './patcher.js';
|
|
42
42
|
import { runMcpServer, runMcpInstaller } from './mcp/index.js';
|
|
43
43
|
import { runTypecheckAudit, runTestAudit, runProjectVerify } from './verify.js';
|
|
44
|
-
import { runTeamCli } from './team/index.js';
|
|
44
|
+
import { runTeamCli, autoGenerateTasksFromAudit } from './team/index.js';
|
|
45
45
|
import { startUiServer } from './ui-server.js';
|
|
46
46
|
import { sanitizeOutputStreams } from './terminal.js';
|
|
47
47
|
import {
|
|
@@ -151,6 +151,14 @@ export const runAudit = async (customDir = null, isCli = false) => {
|
|
|
151
151
|
if (syncRes?.db) {
|
|
152
152
|
syncViolationsIndex(syncRes.db, report.violations);
|
|
153
153
|
recordAuditSnapshot(syncRes.db, report);
|
|
154
|
+
|
|
155
|
+
const isTriage = rawArgs.includes('--triage');
|
|
156
|
+
if (isTriage) {
|
|
157
|
+
const createdTasks = autoGenerateTasksFromAudit(syncRes.db, { cwd: process.cwd(), targetDir });
|
|
158
|
+
if (isCli && !isJson) {
|
|
159
|
+
process.stdout.write(`\x1b[32m✔\x1b[0m Auto-triage generated ${createdTasks.length} team task(s) from audit violations.\n`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
154
162
|
}
|
|
155
163
|
|
|
156
164
|
const isSevereViolation = (v) => {
|
|
@@ -234,6 +242,7 @@ export const runAudit = async (customDir = null, isCli = false) => {
|
|
|
234
242
|
if (isStrictFail) {
|
|
235
243
|
process.stdout.write(` \x1b[31m•\x1b[0m Strict mode: ${report.violations.length} total violation(s) detected\n`);
|
|
236
244
|
}
|
|
245
|
+
process.stdout.write(' \x1b[36m💡 Convert hazards into team tasks: chemx team task triage\x1b[0m\n');
|
|
237
246
|
}
|
|
238
247
|
}
|
|
239
248
|
}
|
package/cli/mcp/tools-team.js
CHANGED
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
updateTaskStatus,
|
|
11
11
|
requestFileLock,
|
|
12
12
|
releaseFileLock,
|
|
13
|
-
getFileLockStatus
|
|
13
|
+
getFileLockStatus,
|
|
14
|
+
registerAgent
|
|
14
15
|
} from '../team/team-db.js';
|
|
15
|
-
import { completeTaskWithAudit } from '../team/team-triage.js';
|
|
16
|
+
import { completeTaskWithAudit, autoGenerateTasksFromAudit } from '../team/team-triage.js';
|
|
16
17
|
import { handleError } from '../errors/index.js';
|
|
17
18
|
|
|
18
19
|
export const handleChemxTeamStatus = async (args = {}, cwd = process.cwd()) => {
|
|
@@ -49,9 +50,11 @@ export const handleChemxTeamFeed = async (args = {}, cwd = process.cwd()) => {
|
|
|
49
50
|
export const handleChemxTeamPost = async (args = {}, cwd = process.cwd()) => {
|
|
50
51
|
const db = openIndexDb(cwd);
|
|
51
52
|
if (!db) return { error: 'sqlite_unavailable' };
|
|
53
|
+
const authorHandle = args.authorId || '@agent';
|
|
54
|
+
registerAgent(db, { id: authorHandle, role: 'contributor' });
|
|
52
55
|
return postFeedEvent(db, {
|
|
53
56
|
message: args.message,
|
|
54
|
-
author_id:
|
|
57
|
+
author_id: authorHandle,
|
|
55
58
|
recipient_id: args.recipientId,
|
|
56
59
|
thread_id: args.threadId,
|
|
57
60
|
task_id: args.taskId,
|
|
@@ -67,25 +70,48 @@ export const handleChemxTeamTask = async (args = {}, cwd = process.cwd()) => {
|
|
|
67
70
|
|
|
68
71
|
if (action === 'list') {
|
|
69
72
|
const tasks = listTasks(db, { status: args.status, assigned_agent_id: args.agentId });
|
|
70
|
-
|
|
73
|
+
const col = toColumnar(tasks, ['id', 'title', 'tier', 'status', 'priority', 'assigned_agent_id', 'target_path']);
|
|
74
|
+
return { ...col, total: tasks.length };
|
|
71
75
|
}
|
|
72
|
-
if (action === 'create') {
|
|
73
|
-
|
|
76
|
+
if (action === 'create' || action === 'add') {
|
|
77
|
+
const authorHandle = args.agentId || '@agent';
|
|
78
|
+
registerAgent(db, { id: authorHandle, role: 'contributor' });
|
|
79
|
+
if (args.assignedAgentId) {
|
|
80
|
+
registerAgent(db, { id: args.assignedAgentId, role: 'executor' });
|
|
81
|
+
}
|
|
82
|
+
const task = createTask(db, {
|
|
74
83
|
title: args.title,
|
|
75
84
|
target_path: args.targetPath,
|
|
76
|
-
tier: args.tier,
|
|
77
|
-
priority: args.priority || 2
|
|
85
|
+
tier: args.tier || 'molecule',
|
|
86
|
+
priority: args.priority || 2,
|
|
87
|
+
assigned_agent_id: args.assignedAgentId || null
|
|
78
88
|
});
|
|
89
|
+
if (task) {
|
|
90
|
+
postFeedEvent(db, {
|
|
91
|
+
author_id: authorHandle,
|
|
92
|
+
task_id: task.id,
|
|
93
|
+
event_type: 'task_created',
|
|
94
|
+
message: `Created task #${task.id}: ${task.title}`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return task;
|
|
79
98
|
}
|
|
80
99
|
if (action === 'claim') {
|
|
81
|
-
|
|
100
|
+
const agentHandle = args.agentId || '@agent';
|
|
101
|
+
registerAgent(db, { id: agentHandle, role: 'executor' });
|
|
102
|
+
return claimTask(db, args.taskId, agentHandle);
|
|
82
103
|
}
|
|
83
|
-
if (action === 'done') {
|
|
84
|
-
|
|
104
|
+
if (action === 'done' || action === 'complete') {
|
|
105
|
+
const agentHandle = args.agentId || '@agent';
|
|
106
|
+
registerAgent(db, { id: agentHandle, role: 'executor' });
|
|
107
|
+
return completeTaskWithAudit(db, args.taskId, agentHandle, { cwd });
|
|
85
108
|
}
|
|
86
109
|
if (action === 'block') {
|
|
87
110
|
return updateTaskStatus(db, args.taskId, 'blocked', { blockedReason: args.blockedReason || 'Blocked' });
|
|
88
111
|
}
|
|
112
|
+
if (action === 'triage') {
|
|
113
|
+
return autoGenerateTasksFromAudit(db, { cwd, maxTasks: args.maxTasks || 10 });
|
|
114
|
+
}
|
|
89
115
|
return { error: `Unknown task action: ${action}` };
|
|
90
116
|
};
|
|
91
117
|
|
package/cli/mcp/tools.js
CHANGED
|
@@ -14,6 +14,7 @@ import { createCapsuleFiles } from '../generator.js';
|
|
|
14
14
|
import { runBuildAudit } from '../build.js';
|
|
15
15
|
import { runAutofix } from '../audit/autofix.js';
|
|
16
16
|
import { runTypecheckAudit, runTestAudit, runProjectVerify } from '../verify.js';
|
|
17
|
+
import { syncSearchIndex, syncViolationsIndex, recordAuditSnapshot } from '../search.js';
|
|
17
18
|
import { MCP_TOOLS, ALL_MCP_TOOLS } from './manifests.js';
|
|
18
19
|
import {
|
|
19
20
|
resolveTargetCwd,
|
|
@@ -131,6 +132,18 @@ const handleAudit = (args = {}, cwd = process.cwd()) => {
|
|
|
131
132
|
};
|
|
132
133
|
|
|
133
134
|
const report = executeAstAudit(rawTarget, options);
|
|
135
|
+
|
|
136
|
+
// Sync AST audit results to SQLite index database
|
|
137
|
+
try {
|
|
138
|
+
const syncRes = syncSearchIndex(rawTarget, cwd);
|
|
139
|
+
if (syncRes?.db) {
|
|
140
|
+
syncViolationsIndex(syncRes.db, report.violations);
|
|
141
|
+
recordAuditSnapshot(syncRes.db, report);
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// Continue if SQLite synchronization fails
|
|
145
|
+
}
|
|
146
|
+
|
|
134
147
|
const isSevereViolation = (v) => {
|
|
135
148
|
const isCritical = v.severity === 'CRITICAL';
|
|
136
149
|
const isHigh = v.severity === 'HIGH';
|
|
@@ -126,15 +126,23 @@ export const runTeamCli = (rawArgs = [], isCli = false, cwd = process.cwd()) =>
|
|
|
126
126
|
return col;
|
|
127
127
|
}
|
|
128
128
|
if (isCli) {
|
|
129
|
-
|
|
130
|
-
process.stdout.write(
|
|
129
|
+
if (tasks.length === 0) {
|
|
130
|
+
process.stdout.write(' (No tasks found in backlog. Run "chemx team task triage" or "chemx team task add" to create tasks.)\n');
|
|
131
|
+
} else {
|
|
132
|
+
for (const t of tasks) {
|
|
133
|
+
const assignee = t.assigned_agent_id ? `(${t.assigned_agent_id})` : '(unassigned)';
|
|
134
|
+
const target = t.target_path ? ` [${t.target_path}]` : '';
|
|
135
|
+
process.stdout.write(` #${t.id} [${t.status}] ${assignee}${target}: ${t.title}\n`);
|
|
136
|
+
}
|
|
131
137
|
}
|
|
132
138
|
}
|
|
133
139
|
return tasks;
|
|
134
140
|
}
|
|
135
141
|
if (taskAction === 'claim') {
|
|
136
142
|
const taskId = nonFlagPositional[1];
|
|
137
|
-
const
|
|
143
|
+
const agentHandle = flags.as || '@agent';
|
|
144
|
+
registerAgent(db, { id: agentHandle, role: 'executor' });
|
|
145
|
+
const res = claimTask(db, taskId, agentHandle);
|
|
138
146
|
if (isCli) {
|
|
139
147
|
if (flags.isJson) process.stdout.write(`${JSON.stringify(res, null, 2)}\n`);
|
|
140
148
|
else if (res.success) process.stdout.write(`\x1b[32m✔\x1b[0m Claimed task #${taskId}\n`);
|
|
@@ -142,23 +150,59 @@ export const runTeamCli = (rawArgs = [], isCli = false, cwd = process.cwd()) =>
|
|
|
142
150
|
}
|
|
143
151
|
return res;
|
|
144
152
|
}
|
|
145
|
-
if (taskAction === 'done') {
|
|
153
|
+
if (taskAction === 'done' || taskAction === 'complete') {
|
|
146
154
|
const taskId = nonFlagPositional[1];
|
|
147
|
-
const
|
|
148
|
-
|
|
155
|
+
const agentHandle = flags.as || '@agent';
|
|
156
|
+
registerAgent(db, { id: agentHandle, role: 'executor' });
|
|
157
|
+
const res = completeTaskWithAudit(db, taskId, agentHandle, { cwd });
|
|
158
|
+
if (isCli) {
|
|
159
|
+
if (flags.isJson) process.stdout.write(`${JSON.stringify(res, null, 2)}\n`);
|
|
160
|
+
else process.stdout.write(`\x1b[32m✔\x1b[0m Completed task #${taskId}\n`);
|
|
161
|
+
}
|
|
149
162
|
return res;
|
|
150
163
|
}
|
|
151
|
-
if (taskAction === 'create') {
|
|
164
|
+
if (taskAction === 'create' || taskAction === 'add' || taskAction === 'new') {
|
|
152
165
|
const title = nonFlagPositional.slice(1).join(' ') || 'Untitled Task';
|
|
166
|
+
const authorHandle = flags.as || '@agent';
|
|
167
|
+
registerAgent(db, { id: authorHandle, role: 'contributor' });
|
|
168
|
+
if (flags.agent) {
|
|
169
|
+
registerAgent(db, { id: flags.agent, role: 'executor' });
|
|
170
|
+
}
|
|
153
171
|
const task = createTask(db, {
|
|
154
172
|
title,
|
|
155
|
-
tier: flags.tier,
|
|
173
|
+
tier: flags.tier || 'molecule',
|
|
156
174
|
target_path: flags.target,
|
|
157
|
-
priority: flags.priority || 2
|
|
175
|
+
priority: flags.priority || 2,
|
|
176
|
+
assigned_agent_id: flags.agent || null
|
|
158
177
|
});
|
|
159
|
-
if (
|
|
178
|
+
if (task) {
|
|
179
|
+
postFeedEvent(db, {
|
|
180
|
+
author_id: authorHandle,
|
|
181
|
+
task_id: task.id,
|
|
182
|
+
event_type: 'task_created',
|
|
183
|
+
message: `Created task #${task.id}: ${task.title}`
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (isCli) {
|
|
187
|
+
if (flags.isJson) process.stdout.write(`${JSON.stringify(task, null, 2)}\n`);
|
|
188
|
+
else process.stdout.write(`\x1b[32m✔\x1b[0m Created task #${task.id}: ${task.title}\n`);
|
|
189
|
+
}
|
|
160
190
|
return task;
|
|
161
191
|
}
|
|
192
|
+
if (taskAction === 'triage') {
|
|
193
|
+
const tasks = autoGenerateTasksFromAudit(db, { cwd, maxTasks: flags.priority || 10 });
|
|
194
|
+
if (isCli) {
|
|
195
|
+
if (flags.isJson) process.stdout.write(`${JSON.stringify(tasks, null, 2)}\n`);
|
|
196
|
+
else if (tasks.length > 0) process.stdout.write(`\x1b[32m✔\x1b[0m Triage generated ${tasks.length} task(s) from AST index\n`);
|
|
197
|
+
else process.stdout.write('\x1b[34mℹ\x1b[0m Triage found 0 architectural hazards to convert into tasks. (All files compliant!)\n');
|
|
198
|
+
}
|
|
199
|
+
return tasks;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (isCli) {
|
|
203
|
+
process.stderr.write(`\x1b[31m✕ Unknown task action: "${taskAction}". Available actions: list, add, claim, done, triage\x1b[0m\n`);
|
|
204
|
+
}
|
|
205
|
+
return { error: `Unknown task action: ${taskAction}` };
|
|
162
206
|
}
|
|
163
207
|
|
|
164
208
|
if (subCommand === 'lock') {
|
|
@@ -187,13 +231,17 @@ export const runTeamCli = (rawArgs = [], isCli = false, cwd = process.cwd()) =>
|
|
|
187
231
|
}
|
|
188
232
|
|
|
189
233
|
if (subCommand === 'triage') {
|
|
190
|
-
const tasks = autoGenerateTasksFromAudit(db);
|
|
234
|
+
const tasks = autoGenerateTasksFromAudit(db, { cwd, maxTasks: flags.priority || 10 });
|
|
191
235
|
if (isCli) {
|
|
192
236
|
if (flags.isJson) process.stdout.write(`${JSON.stringify(tasks, null, 2)}\n`);
|
|
193
|
-
else process.stdout.write(`\x1b[32m✔\x1b[0m Triage generated ${tasks.length} task(s) from AST index\n`);
|
|
237
|
+
else if (tasks.length > 0) process.stdout.write(`\x1b[32m✔\x1b[0m Triage generated ${tasks.length} task(s) from AST index\n`);
|
|
238
|
+
else process.stdout.write('\x1b[34mℹ\x1b[0m Triage found 0 architectural hazards to convert into tasks. (All files compliant!)\n');
|
|
194
239
|
}
|
|
195
240
|
return tasks;
|
|
196
241
|
}
|
|
197
242
|
|
|
198
|
-
|
|
243
|
+
if (isCli) {
|
|
244
|
+
process.stderr.write(`\x1b[31m✕ Unknown team command: "${subCommand}". Available commands: status, task, feed, post, lock, unlock, triage\x1b[0m\n`);
|
|
245
|
+
}
|
|
246
|
+
return { error: `Unknown team command: ${subCommand}` };
|
|
199
247
|
};
|
package/cli/team/team-triage.js
CHANGED
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
* Connects AST health metrics with autonomous swarm task generation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
6
8
|
import { createTask, updateTaskStatus, getTask } from './team-db-tasks.js';
|
|
7
9
|
import { releaseFileLock } from './team-db-locks.js';
|
|
8
10
|
import { postFeedEvent } from './team-db-feed.js';
|
|
11
|
+
import { registerAgent } from './team-db-agents.js';
|
|
9
12
|
import { ingestTaskTelemetry } from './team-telemetry.js';
|
|
13
|
+
import { runAudit as executeAstAudit } from '../audit.js';
|
|
14
|
+
import { syncSearchIndex, syncViolationsIndex, recordAuditSnapshot } from '../search.js';
|
|
10
15
|
|
|
11
16
|
export { ingestTaskTelemetry };
|
|
12
17
|
|
|
@@ -14,29 +19,81 @@ export const queryUnassignedHazards = (db) => {
|
|
|
14
19
|
if (!db) return [];
|
|
15
20
|
const query = `
|
|
16
21
|
SELECT
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
COALESCE(v.file_path, f.path) as path,
|
|
23
|
+
COALESCE(f.tier, 'molecule') as tier,
|
|
24
|
+
COALESCE(f.lines, 0) as lines,
|
|
25
|
+
COALESCE(f.health_score, CASE WHEN COUNT(v.id) > 0 THEN MAX(20, 100 - COUNT(v.id) * 15) ELSE 100 END) as health_score,
|
|
26
|
+
COALESCE(MAX(f.hazard_count, COUNT(v.id)), COUNT(v.id)) as hazard_count,
|
|
27
|
+
COUNT(v.id) as violation_count,
|
|
28
|
+
GROUP_CONCAT(DISTINCT v.rule) as rules_summary
|
|
29
|
+
FROM violations v
|
|
30
|
+
LEFT JOIN files f ON f.path = v.file_path
|
|
31
|
+
LEFT JOIN agent_tasks t ON t.target_path = v.file_path AND t.status IN ('queued', 'in_progress', 'review')
|
|
32
|
+
WHERE t.id IS NULL
|
|
33
|
+
GROUP BY v.file_path
|
|
34
|
+
UNION
|
|
35
|
+
SELECT
|
|
36
|
+
f.path,
|
|
37
|
+
f.tier,
|
|
38
|
+
f.lines,
|
|
39
|
+
f.health_score,
|
|
40
|
+
f.hazard_count,
|
|
41
|
+
f.hazard_count as violation_count,
|
|
42
|
+
'ARCHITECTURAL_HAZARD' as rules_summary
|
|
19
43
|
FROM files f
|
|
20
|
-
LEFT JOIN violations v ON v.file_path = f.path
|
|
21
44
|
LEFT JOIN agent_tasks t ON t.target_path = f.path AND t.status IN ('queued', 'in_progress', 'review')
|
|
22
|
-
WHERE (f.health_score < 90 OR f.hazard_count > 0)
|
|
45
|
+
WHERE (f.health_score < 90 OR f.hazard_count > 0)
|
|
46
|
+
AND t.id IS NULL
|
|
47
|
+
AND f.path NOT IN (SELECT file_path FROM violations)
|
|
23
48
|
GROUP BY f.path
|
|
24
|
-
ORDER BY
|
|
49
|
+
ORDER BY health_score ASC, hazard_count DESC
|
|
25
50
|
`;
|
|
26
|
-
|
|
51
|
+
try {
|
|
52
|
+
return db.prepare(query).all();
|
|
53
|
+
} catch {
|
|
54
|
+
// Fallback if schema doesn't yet have all tables
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
27
57
|
};
|
|
28
58
|
|
|
29
59
|
export const autoGenerateTasksFromAudit = (db, options = {}) => {
|
|
30
60
|
if (!db) return [];
|
|
31
|
-
const
|
|
61
|
+
const cwd = options.cwd || process.cwd();
|
|
62
|
+
|
|
63
|
+
// If violations and files are empty, auto-audit to seed index if possible
|
|
64
|
+
try {
|
|
65
|
+
const violationsCount = db.prepare('SELECT COUNT(*) as count FROM violations').get()?.count || 0;
|
|
66
|
+
const filesCount = db.prepare('SELECT COUNT(*) as count FROM files').get()?.count || 0;
|
|
67
|
+
if (violationsCount === 0 && filesCount === 0) {
|
|
68
|
+
const targetDir = options.targetDir || (fs.existsSync(path.resolve(cwd, 'src')) ? 'src' : '.');
|
|
69
|
+
const report = executeAstAudit(targetDir, { cwd });
|
|
70
|
+
if (report?.violations) {
|
|
71
|
+
syncSearchIndex(targetDir, cwd);
|
|
72
|
+
syncViolationsIndex(db, report.violations);
|
|
73
|
+
recordAuditSnapshot(db, report);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Fall through to existing db state
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const limit = options.maxTasks || 10;
|
|
32
81
|
const candidates = queryUnassignedHazards(db).slice(0, limit);
|
|
33
82
|
const createdTasks = [];
|
|
34
83
|
|
|
84
|
+
registerAgent(db, {
|
|
85
|
+
id: '@triage-bot',
|
|
86
|
+
name: 'Triage Bot',
|
|
87
|
+
role: 'triage',
|
|
88
|
+
capabilities: ['audit', 'triage', 'task_creation']
|
|
89
|
+
});
|
|
90
|
+
|
|
35
91
|
for (const item of candidates) {
|
|
92
|
+
const rulesText = item.rules_summary ? ` (${item.rules_summary})` : '';
|
|
36
93
|
const task = createTask(db, {
|
|
37
|
-
title: `Resolve architectural hazards in ${item.path}`,
|
|
38
|
-
description: `Target file has health score ${item.health_score}/100 with ${item.hazard_count} detected
|
|
39
|
-
tier: item.tier,
|
|
94
|
+
title: `Resolve architectural hazards in ${item.path}${rulesText}`,
|
|
95
|
+
description: `Target file has health score ${item.health_score}/100 with ${item.hazard_count || item.violation_count || 1} detected hazard(s). Refactor into molecular compliance.`,
|
|
96
|
+
tier: item.tier || 'molecule',
|
|
40
97
|
target_path: item.path,
|
|
41
98
|
priority: item.health_score < 70 ? 1 : 2
|
|
42
99
|
});
|
package/cli/team/team.spec.js
CHANGED
|
@@ -37,6 +37,8 @@ import {
|
|
|
37
37
|
autoGenerateTasksFromAudit,
|
|
38
38
|
completeTaskWithAudit
|
|
39
39
|
} from './team-triage.js';
|
|
40
|
+
import { runTeamCli } from './team-commands.js';
|
|
41
|
+
import { openIndexDb } from '../search-db.js';
|
|
40
42
|
import { executeMcpTool } from '../mcp/tools.js';
|
|
41
43
|
import { parseTranscriptFile, ingestTaskTelemetry } from './team-telemetry.js';
|
|
42
44
|
|
|
@@ -342,3 +344,104 @@ test('team-telemetry: ingests token metrics into SQLite during completeTaskWithA
|
|
|
342
344
|
assert.strictEqual(swarmStatus.tokens.cost_usd, 0.00325);
|
|
343
345
|
});
|
|
344
346
|
|
|
347
|
+
test('team-commands: runTeamCli handles task triage, add alias, and agent auto-registration', () => {
|
|
348
|
+
const tmpCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-team-cli-test-'));
|
|
349
|
+
try {
|
|
350
|
+
const db = openIndexDb(tmpCwd);
|
|
351
|
+
assert.ok(db, 'Database should be created');
|
|
352
|
+
|
|
353
|
+
// Test empty list output
|
|
354
|
+
const emptyList = runTeamCli(['task', 'list'], false, tmpCwd);
|
|
355
|
+
assert.deepStrictEqual(emptyList, []);
|
|
356
|
+
|
|
357
|
+
// Test task add alias
|
|
358
|
+
const created = runTeamCli(['task', 'add', 'Fix', 'architectural', 'monolith', '--as=@architect', '--prio=1'], false, tmpCwd);
|
|
359
|
+
assert.ok(created);
|
|
360
|
+
assert.strictEqual(created.id, 1);
|
|
361
|
+
assert.strictEqual(created.title, 'Fix architectural monolith');
|
|
362
|
+
assert.strictEqual(created.priority, 1);
|
|
363
|
+
|
|
364
|
+
// Verify agent was registered automatically
|
|
365
|
+
const agent = getAgent(db, '@architect');
|
|
366
|
+
assert.ok(agent, 'Author agent should be registered in agents table');
|
|
367
|
+
assert.strictEqual(agent.role, 'contributor');
|
|
368
|
+
|
|
369
|
+
// Verify task_created event was posted to agent_feed
|
|
370
|
+
const feed = queryFeed(db, { task_id: 1 });
|
|
371
|
+
assert.strictEqual(feed.length, 1);
|
|
372
|
+
assert.strictEqual(feed[0].author_id, '@architect');
|
|
373
|
+
assert.strictEqual(feed[0].event_type, 'task_created');
|
|
374
|
+
|
|
375
|
+
// Seed a violation into violations table without pre-populating files table
|
|
376
|
+
db.prepare(`
|
|
377
|
+
INSERT INTO violations (file_path, rule, severity, pillar, line, hazard, directive)
|
|
378
|
+
VALUES ('src/components/BrokenCard.tsx', 'NO_RAW_DOM', 'HIGH', 'FOUNDATIONS', 14, 'Raw button used', 'Wrap in AtomButton')
|
|
379
|
+
`).run();
|
|
380
|
+
|
|
381
|
+
// Test task triage command: should pick up unassigned hazard and generate task
|
|
382
|
+
const triaged = runTeamCli(['task', 'triage'], false, tmpCwd);
|
|
383
|
+
assert.ok(Array.isArray(triaged));
|
|
384
|
+
assert.strictEqual(triaged.length, 1);
|
|
385
|
+
assert.strictEqual(triaged[0].target_path, 'src/components/BrokenCard.tsx');
|
|
386
|
+
|
|
387
|
+
// Verify triage bot registered in agents table
|
|
388
|
+
const triageBot = getAgent(db, '@triage-bot');
|
|
389
|
+
assert.ok(triageBot, 'Triage bot should be registered in agents table');
|
|
390
|
+
|
|
391
|
+
// Verify triage feed event
|
|
392
|
+
const triageFeed = queryFeed(db, { event_type: 'triage_generated' });
|
|
393
|
+
assert.strictEqual(triageFeed.length, 1);
|
|
394
|
+
|
|
395
|
+
// Test task list now shows both tasks
|
|
396
|
+
const allTasks = runTeamCli(['task', 'list'], false, tmpCwd);
|
|
397
|
+
assert.strictEqual(allTasks.length, 2);
|
|
398
|
+
} finally {
|
|
399
|
+
fs.rmSync(tmpCwd, { recursive: true, force: true });
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test('mcp-tools: chemx_team_task supports action triage and add with agent auto-registration', async () => {
|
|
404
|
+
const tmpCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'chemx-mcp-team-test-'));
|
|
405
|
+
try {
|
|
406
|
+
const db = openIndexDb(tmpCwd);
|
|
407
|
+
assert.ok(db);
|
|
408
|
+
|
|
409
|
+
// Create task via MCP
|
|
410
|
+
const created = await executeMcpTool('chemx_team_task', {
|
|
411
|
+
action: 'add',
|
|
412
|
+
title: 'Decompose Search Monolith',
|
|
413
|
+
agentId: '@planner-bot',
|
|
414
|
+
priority: 1
|
|
415
|
+
}, tmpCwd);
|
|
416
|
+
assert.ok(created);
|
|
417
|
+
assert.strictEqual(created.title, 'Decompose Search Monolith');
|
|
418
|
+
|
|
419
|
+
// Verify agent auto-registered
|
|
420
|
+
const agent = getAgent(db, '@planner-bot');
|
|
421
|
+
assert.ok(agent);
|
|
422
|
+
|
|
423
|
+
// Seed violation
|
|
424
|
+
db.prepare(`
|
|
425
|
+
INSERT INTO violations (file_path, rule, severity, pillar, line, hazard, directive)
|
|
426
|
+
VALUES ('src/views/HugeView.tsx', 'LINE_LIMIT', 'CRITICAL', 'MOLECULAR', 1, 'Exceeds line limit', 'Decompose')
|
|
427
|
+
`).run();
|
|
428
|
+
|
|
429
|
+
// Triage via MCP
|
|
430
|
+
const triaged = await executeMcpTool('chemx_team_task', {
|
|
431
|
+
action: 'triage'
|
|
432
|
+
}, tmpCwd);
|
|
433
|
+
assert.ok(Array.isArray(triaged));
|
|
434
|
+
assert.strictEqual(triaged.length, 1);
|
|
435
|
+
assert.strictEqual(triaged[0].target_path, 'src/views/HugeView.tsx');
|
|
436
|
+
|
|
437
|
+
// List via MCP
|
|
438
|
+
const listRes = await executeMcpTool('chemx_team_task', {
|
|
439
|
+
action: 'list'
|
|
440
|
+
}, tmpCwd);
|
|
441
|
+
assert.strictEqual(listRes.total, 2);
|
|
442
|
+
assert.strictEqual(listRes.rows.length, 2);
|
|
443
|
+
} finally {
|
|
444
|
+
fs.rmSync(tmpCwd, { recursive: true, force: true });
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|