@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 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
  }
@@ -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: args.authorId,
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
- return toColumnar(tasks, ['id', 'title', 'tier', 'status', 'priority', 'assigned_agent_id', 'target_path']);
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
- return createTask(db, {
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
- return claimTask(db, args.taskId, args.agentId || '@agent');
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
- return completeTaskWithAudit(db, args.taskId, args.agentId || '@agent', { cwd });
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
- for (const t of tasks) {
130
- process.stdout.write(` #${t.id} [${t.status}] (${t.assigned_agent_id || 'unassigned'}): ${t.title}\n`);
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 res = claimTask(db, taskId, flags.as || '@agent');
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 res = completeTaskWithAudit(db, taskId, flags.as || '@agent', { cwd });
148
- if (isCli) process.stdout.write(`\x1b[32m✔\x1b[0m Completed task #${taskId}\n`);
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 (isCli) process.stdout.write(`\x1b[32m✔\x1b[0m Created task #${task.id}: ${task.title}\n`);
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
- return null;
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
  };
@@ -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
- f.path, f.tier, f.lines, f.health_score, f.hazard_count,
18
- COUNT(v.id) as violation_count
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) AND t.id IS NULL
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 f.health_score ASC, f.hazard_count DESC
49
+ ORDER BY health_score ASC, hazard_count DESC
25
50
  `;
26
- return db.prepare(query).all();
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 limit = options.maxTasks || 5;
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 hazards. Refactor into molecular compliance.`,
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
  });
@@ -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
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chemx/starter-kit",
3
- "version": "26.9.20-390",
3
+ "version": "26.9.20-408",
4
4
  "description": "Chemical X Protocol: Private drop-in architecture starter kit and capsule generator",
5
5
  "type": "module",
6
6
  "bin": {