@chemx/starter-kit 26.9.25-789 → 26.9.26-253
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/README.md +2 -2
- package/blueprints/molecule-capsule/m-sample-card.spec.ts +10 -4
- package/cli/audit/prompts.js +1 -1
- package/cli/commands/cmd-router.js +20 -2
- package/cli/commands-schema.js +65 -6
- package/cli/generator-help.js +26 -6
- package/cli/generator-jig-cli.js +67 -0
- package/cli/generator-jig.js +479 -0
- package/cli/generator-jig.spec.js +148 -0
- package/cli/generator.js +15 -2
- package/cli/index.js +4 -1
- package/cli/installer-templates.js +8 -3
- package/cli/mcp/manifests.js +22 -4
- package/cli/mcp/tools-generate.js +22 -2
- package/cli/mcp/tools-read.js +50 -8
- package/cli/mcp/tools-verify.js +2 -0
- package/cli/mcp/tools.js +52 -3
- package/cli/project-detector.js +105 -2
- package/cli/reader-cli.js +146 -0
- package/cli/reader-logic.js +392 -0
- package/cli/reader-logic.spec.js +160 -0
- package/cli/reader.js +78 -171
- package/cli/search-commands-graph.js +125 -1
- package/cli/search-commands.js +7 -1
- package/cli/search-db.js +2 -0
- package/cli/search-queries-graph.js +294 -0
- package/cli/search-queries.js +6 -1
- package/cli/search.js +28 -30
- package/cli/team/team-adversarial-locks.spec.js +71 -0
- package/cli/team/team-commands-lock.js +64 -0
- package/cli/team/team-commands.js +16 -23
- package/cli/team/team-db-locks.js +1 -1
- package/cli/team/team-flags.js +4 -1
- package/cli/team/team-format.js +88 -0
- package/cli/team/team-memory.js +99 -13
- package/cli/team/team-memory.spec.js +49 -0
- package/cli/team/team-multiprocess-concurrency.spec.js +1 -1
- package/cli/tesseract.spec.js +2 -2
- package/cli/theme.js +1 -0
- package/cli/trace.spec.js +92 -0
- package/cli/verify-helpers.js +80 -18
- package/cli/verify-lint.js +224 -0
- package/cli/verify-lint.spec.js +44 -0
- package/cli/verify.js +8 -1
- package/cli/verify.spec.js +52 -0
- package/package.json +4 -4
- package/scripts/pre-commit.sh +8 -3
package/cli/team/team-format.js
CHANGED
|
@@ -179,3 +179,91 @@ export const formatTaskDetailCard = (task, events = []) => {
|
|
|
179
179
|
lines.push(`\x1b[90m${'─'.repeat(58)}\x1b[0m\n`);
|
|
180
180
|
return lines.join('\n');
|
|
181
181
|
};
|
|
182
|
+
|
|
183
|
+
export const formatTeamHelpCard = () => {
|
|
184
|
+
const lines = [
|
|
185
|
+
'',
|
|
186
|
+
'\x1b[1m\x1b[36m⚡ [Chemical X] Multi-Agent Swarm Orchestration (team)\x1b[0m',
|
|
187
|
+
`\x1b[90m${'─'.repeat(58)}\x1b[0m`,
|
|
188
|
+
' \x1b[1mUsage:\x1b[0m chemx team <command> [options]',
|
|
189
|
+
'',
|
|
190
|
+
' \x1b[1mCommands:\x1b[0m',
|
|
191
|
+
' \x1b[32mstatus\x1b[0m Display swarm agent, task, and lock status card',
|
|
192
|
+
' \x1b[32mtask\x1b[0m [action] Manage agent tasks (list, show, add, claim, done, update)',
|
|
193
|
+
' \x1b[32mlock\x1b[0m [acquire|release] Acquire or release a mutual exclusion file lock',
|
|
194
|
+
' \x1b[32munlock\x1b[0m <file> Release a file lock held by an agent',
|
|
195
|
+
' \x1b[32mfeed\x1b[0m View swarm feed event timeline',
|
|
196
|
+
' \x1b[32mpost\x1b[0m <message> Post broadcast message to swarm feed',
|
|
197
|
+
' \x1b[32minbox\x1b[0m [@agent] View agent mailbox and notifications',
|
|
198
|
+
' \x1b[32mdm\x1b[0m <@agent> <msg> Send direct message to another agent',
|
|
199
|
+
' \x1b[32mtokens\x1b[0m Inspect token consumption and telemetry breakdown',
|
|
200
|
+
' \x1b[32mtriage\x1b[0m Generate tasks automatically from AST audit hazards',
|
|
201
|
+
' \x1b[32mbenchmark\x1b[0m Run memory and token reduction ablation benchmark',
|
|
202
|
+
' \x1b[32mtrain\x1b[0m Train/evaluate vector and pattern indices',
|
|
203
|
+
'',
|
|
204
|
+
' \x1b[1mGlobal Options:\x1b[0m',
|
|
205
|
+
' \x1b[33m--as\x1b[0m <@agent> Execute action as specified agent handle',
|
|
206
|
+
' \x1b[33m--json\x1b[0m Output machine-readable JSON format',
|
|
207
|
+
' \x1b[33m--help, -h\x1b[0m Show help for team or specific subcommands',
|
|
208
|
+
`\x1b[90m${'─'.repeat(58)}\x1b[0m\n`
|
|
209
|
+
];
|
|
210
|
+
return lines.join('\n');
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const formatTaskHelpCard = () => {
|
|
214
|
+
const lines = [
|
|
215
|
+
'',
|
|
216
|
+
'\x1b[1m\x1b[36m⚡ [Chemical X] Task Coordination Commands (team task)\x1b[0m',
|
|
217
|
+
`\x1b[90m${'─'.repeat(58)}\x1b[0m`,
|
|
218
|
+
' \x1b[1mUsage:\x1b[0m chemx team task <action> [arguments] [options]',
|
|
219
|
+
'',
|
|
220
|
+
' \x1b[1mActions:\x1b[0m',
|
|
221
|
+
' \x1b[32mlist\x1b[0m List swarm tasks (default action)',
|
|
222
|
+
' \x1b[32mshow\x1b[0m <taskId> Display full details and activity stream for a task',
|
|
223
|
+
' \x1b[32madd\x1b[0m "<title>" Create a new task in the queue',
|
|
224
|
+
' \x1b[32mclaim\x1b[0m <taskId> Claim an unassigned task for an agent',
|
|
225
|
+
' \x1b[32mdone\x1b[0m <taskId> Complete a task with automatic verification audit',
|
|
226
|
+
' \x1b[32mupdate\x1b[0m <taskId> [status] Update task status (in_progress, blocked, done)',
|
|
227
|
+
' \x1b[32mcomment\x1b[0m <taskId> <msg> Post a comment or status update to a task',
|
|
228
|
+
' \x1b[32mtriage\x1b[0m Generate tasks from AST architectural hazards',
|
|
229
|
+
' \x1b[32mreconcile\x1b[0m Auto-resolve tasks whose hazards have been fixed',
|
|
230
|
+
' \x1b[32mset-target\x1b[0m <id> <path> Assign target file path to a task',
|
|
231
|
+
' \x1b[32mvds-slot\x1b[0m <id> <moscow> <p> Set MoSCoW slot and priority on a task',
|
|
232
|
+
' \x1b[32mtrace\x1b[0m <id> <url> Attach provenance trace URL to a task',
|
|
233
|
+
'',
|
|
234
|
+
' \x1b[1mOptions:\x1b[0m',
|
|
235
|
+
' \x1b[33m--as\x1b[0m <@agent> Agent handle (e.g. @agent-1)',
|
|
236
|
+
' \x1b[33m--status\x1b[0m <status> Filter by status (todo, in_progress, blocked, done)',
|
|
237
|
+
' \x1b[33m--agent\x1b[0m <@agent> Filter tasks assigned to agent',
|
|
238
|
+
' \x1b[33m--prio\x1b[0m <1-5> Task priority filter or setting',
|
|
239
|
+
' \x1b[33m--force, -f\x1b[0m Force complete task even with remaining hazards',
|
|
240
|
+
' \x1b[33m--json\x1b[0m Output machine-readable JSON format',
|
|
241
|
+
' \x1b[33m--help, -h\x1b[0m Show this help message',
|
|
242
|
+
`\x1b[90m${'─'.repeat(58)}\x1b[0m\n`
|
|
243
|
+
];
|
|
244
|
+
return lines.join('\n');
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export const formatLockHelpCard = () => {
|
|
248
|
+
const lines = [
|
|
249
|
+
'',
|
|
250
|
+
'\x1b[1m\x1b[36m⚡ [Chemical X] Mutual Exclusion File Locking (team lock)\x1b[0m',
|
|
251
|
+
`\x1b[90m${'─'.repeat(58)}\x1b[0m`,
|
|
252
|
+
' \x1b[1mUsage:\x1b[0m chemx team lock [acquire|release] <filePath> [options]',
|
|
253
|
+
' chemx team unlock <filePath> [options]',
|
|
254
|
+
'',
|
|
255
|
+
' \x1b[1mActions:\x1b[0m',
|
|
256
|
+
' \x1b[32macquire\x1b[0m <path> Acquire mutual exclusion lock (or enqueue in FIFO)',
|
|
257
|
+
' \x1b[32mrelease\x1b[0m <path> Release lock and promote next FIFO waiter',
|
|
258
|
+
'',
|
|
259
|
+
' \x1b[1mOptions:\x1b[0m',
|
|
260
|
+
' \x1b[33m--as\x1b[0m <@agent> Agent handle acquiring/releasing lock (default: @agent)',
|
|
261
|
+
' \x1b[33m--purpose\x1b[0m "<reason>" Reason or intent for acquiring lock',
|
|
262
|
+
' \x1b[33m--priority\x1b[0m <1-5> Priority in FIFO wait queue (1 = highest, default: 2)',
|
|
263
|
+
' \x1b[33m--pid\x1b[0m <pid> Optional process PID for crash-recovery lease reclamation',
|
|
264
|
+
' \x1b[33m--json\x1b[0m Output machine-readable JSON format',
|
|
265
|
+
' \x1b[33m--help, -h\x1b[0m Show this help message',
|
|
266
|
+
`\x1b[90m${'─'.repeat(58)}\x1b[0m\n`
|
|
267
|
+
];
|
|
268
|
+
return lines.join('\n');
|
|
269
|
+
};
|
package/cli/team/team-memory.js
CHANGED
|
@@ -17,7 +17,10 @@ export const recordMemoryInjection = (db, entry = {}) => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
export const recordMemoryUtilization = (db, { taskId, utilizedProvenance = [], causedRetry = false } = {}) => {
|
|
20
|
-
|
|
20
|
+
const hasDb = Boolean(db);
|
|
21
|
+
const hasTaskId = Boolean(taskId);
|
|
22
|
+
const canRecord = hasDb && hasTaskId;
|
|
23
|
+
if (!canRecord) return;
|
|
21
24
|
if (causedRetry) db.prepare('UPDATE agent_memory_log SET caused_retry = 1 WHERE task_id = ?').run(taskId);
|
|
22
25
|
for (const prov of utilizedProvenance) {
|
|
23
26
|
db.prepare('UPDATE agent_memory_log SET was_utilized = 1 WHERE task_id = ? AND (provenance_path = ? OR provenance_path LIKE ?)').run(taskId, prov, `%${prov}%`);
|
|
@@ -51,14 +54,79 @@ export const calculateMemoryMetrics = (db) => {
|
|
|
51
54
|
|
|
52
55
|
export const runAblationComparison = (db) => {
|
|
53
56
|
const m = calculateMemoryMetrics(db);
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
const row = db ? db.prepare(`
|
|
58
|
+
SELECT
|
|
59
|
+
COUNT(*) as totalTasks,
|
|
60
|
+
COALESCE(SUM(prompt_tokens), 0) as totalPromptTokens,
|
|
61
|
+
COALESCE(SUM(completion_tokens), 0) as totalCompletionTokens,
|
|
62
|
+
COALESCE(SUM(cached_tokens), 0) as totalCachedTokens,
|
|
63
|
+
COALESCE(SUM(cost_usd), 0) as totalCost
|
|
64
|
+
FROM agent_tasks
|
|
65
|
+
`).get() : null;
|
|
66
|
+
|
|
67
|
+
const taskStats = {
|
|
68
|
+
totalTasks: Number(row?.totalTasks || 0),
|
|
69
|
+
totalPromptTokens: Number(row?.totalPromptTokens || 0),
|
|
70
|
+
totalCompletionTokens: Number(row?.totalCompletionTokens || 0),
|
|
71
|
+
totalCachedTokens: Number(row?.totalCachedTokens || 0),
|
|
72
|
+
totalCost: Number(row?.totalCost || 0)
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const hasTasks = taskStats.totalTasks > 0;
|
|
76
|
+
const hasMemories = m.totalCount > 0;
|
|
77
|
+
const hasRealData = Boolean(hasTasks || hasMemories);
|
|
78
|
+
|
|
79
|
+
if (!hasRealData) {
|
|
80
|
+
const turns = 10;
|
|
81
|
+
const baseTokens = turns * 2500;
|
|
82
|
+
const memTokens = 700;
|
|
83
|
+
const tokensSaved = baseTokens - memTokens;
|
|
84
|
+
const precisionGain = (100 / 12.0).toFixed(1) + 'x';
|
|
85
|
+
return {
|
|
86
|
+
hasRealData: false,
|
|
87
|
+
hasMeasuredTokens: false,
|
|
88
|
+
isSimulated: true,
|
|
89
|
+
workloadTurns: turns,
|
|
90
|
+
memoryEnabled: { mode: 'SQLite AST & Event Memory', tokensUsed: memTokens, staleRetryRatePct: 0.0, retrievalPrecisionPct: 100, astQualityScore: 100 },
|
|
91
|
+
memoryDisabled: { mode: 'Stateless Monolithic File Re-read', tokensUsed: baseTokens, staleRetryRatePct: 18.5, retrievalPrecisionPct: 12.0, astQualityScore: 84 },
|
|
92
|
+
delta: {
|
|
93
|
+
tokenReductionPct: Math.round((tokensSaved / baseTokens) * 100),
|
|
94
|
+
tokenReductionBasis: 'estimated_typical_usage',
|
|
95
|
+
isEstimated: true,
|
|
96
|
+
costAvoidedUsd: Number(((tokensSaved / 1000000) * 10.0).toFixed(4)),
|
|
97
|
+
retrievalPrecisionGainRatio: precisionGain,
|
|
98
|
+
staleRetriesEliminated: Math.round(turns * 0.185)
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const turns = taskStats.totalTasks + m.totalCount;
|
|
104
|
+
const liveTokens = taskStats.totalPromptTokens + taskStats.totalCompletionTokens;
|
|
105
|
+
const hasMeasuredTokens = liveTokens > 0;
|
|
106
|
+
const memTokens = hasMeasuredTokens ? liveTokens : Math.max(turns * 70, m.totalInjectedTokens || 70);
|
|
107
|
+
const baseTokens = turns * 2500 + Math.round(memTokens * 0.5);
|
|
108
|
+
const tokensSaved = Math.max(0, baseTokens - memTokens);
|
|
109
|
+
const tokenReductionPct = baseTokens > 0 ? Math.round((tokensSaved / baseTokens) * 100) : 0;
|
|
110
|
+
const costAvoidedUsd = Number(((tokensSaved / 1000000) * 10.0).toFixed(4));
|
|
111
|
+
const precisionGainRatio = (m.precisionPct / 12.0).toFixed(1) + 'x';
|
|
112
|
+
const retryRatePct = m.totalCount > 0 ? Number(((m.staleRetries / m.totalCount) * 100).toFixed(1)) : 0.0;
|
|
113
|
+
const staleRetriesEliminated = Math.max(0, Math.round(turns * 0.185) - m.staleRetries);
|
|
114
|
+
|
|
57
115
|
return {
|
|
116
|
+
hasRealData: true,
|
|
117
|
+
hasMeasuredTokens,
|
|
118
|
+
isSimulated: false,
|
|
58
119
|
workloadTurns: turns,
|
|
59
|
-
memoryEnabled: { mode: 'SQLite AST & Event Memory', tokensUsed: memTokens, staleRetryRatePct:
|
|
120
|
+
memoryEnabled: { mode: 'SQLite AST & Event Memory', tokensUsed: memTokens, staleRetryRatePct: retryRatePct, retrievalPrecisionPct: m.precisionPct, astQualityScore: 100 },
|
|
60
121
|
memoryDisabled: { mode: 'Stateless Monolithic File Re-read', tokensUsed: baseTokens, staleRetryRatePct: 18.5, retrievalPrecisionPct: 12.0, astQualityScore: 84 },
|
|
61
|
-
delta: {
|
|
122
|
+
delta: {
|
|
123
|
+
tokenReductionPct,
|
|
124
|
+
tokenReductionBasis: hasMeasuredTokens ? 'measured' : 'estimated_typical_usage',
|
|
125
|
+
isEstimated: !hasMeasuredTokens,
|
|
126
|
+
costAvoidedUsd,
|
|
127
|
+
retrievalPrecisionGainRatio: precisionGainRatio,
|
|
128
|
+
staleRetriesEliminated
|
|
129
|
+
}
|
|
62
130
|
};
|
|
63
131
|
};
|
|
64
132
|
|
|
@@ -66,19 +134,37 @@ export const formatAblationCard = (a) => {
|
|
|
66
134
|
const m = a.memoryEnabled;
|
|
67
135
|
const d = a.memoryDisabled;
|
|
68
136
|
const delta = a.delta;
|
|
137
|
+
const headerSuffix = a.hasRealData ? '[Live Project Telemetry]' : '[Reference Projection - Zero Local Tasks]';
|
|
69
138
|
const lines = [
|
|
70
139
|
'',
|
|
71
|
-
|
|
72
|
-
`\x1b[90m${'─'.repeat(58)}\x1b[0m
|
|
140
|
+
`\x1b[1m\x1b[36m⚡ [Chemical X] Persistent Memory Ablation Benchmark ${headerSuffix}\x1b[0m`,
|
|
141
|
+
`\x1b[90m${'─'.repeat(58)}\x1b[0m`
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
if (!a.hasRealData) {
|
|
145
|
+
lines.push(
|
|
146
|
+
' \x1b[33mℹ Notice: Zero local task history recorded. Displaying reference projection.\x1b[0m',
|
|
147
|
+
' \x1b[33m Run team tasks and memory operations to generate live project benchmarks.\x1b[0m',
|
|
148
|
+
`\x1b[90m${'─'.repeat(58)}\x1b[0m`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const tokenReductionLabel = a.hasMeasuredTokens
|
|
153
|
+
? `${delta.tokenReductionPct}% fewer tokens consumed (measured vs modeled baseline)`
|
|
154
|
+
: `${delta.tokenReductionPct}% fewer tokens consumed (estimated based on typical usage)`;
|
|
155
|
+
const costAvoidedSuffix = a.hasMeasuredTokens ? '' : ' (projected)';
|
|
156
|
+
const memoryTokensSuffix = a.hasMeasuredTokens ? '' : ' (est.)';
|
|
157
|
+
|
|
158
|
+
lines.push(
|
|
73
159
|
` \x1b[1mWorkload:\x1b[0m ${a.workloadTurns} agent turns / tasks`,
|
|
74
|
-
` \x1b[1mMemory Mode (SQLite):\x1b[0m ${m.tokensUsed.toLocaleString()} tokens │ Precision: ${m.retrievalPrecisionPct}% │ Retry: ${m.staleRetryRatePct}%`,
|
|
75
|
-
` \x1b[1mBaseline (Stateless):\x1b[0m ${d.tokensUsed.toLocaleString()} tokens │ Precision: ${d.retrievalPrecisionPct}% │ Retry: ${d.staleRetryRatePct}%`,
|
|
160
|
+
` \x1b[1mMemory Mode (SQLite):\x1b[0m ${m.tokensUsed.toLocaleString()} tokens${memoryTokensSuffix} │ Precision: ${m.retrievalPrecisionPct}% │ Retry: ${m.staleRetryRatePct}%`,
|
|
161
|
+
` \x1b[1mBaseline (Stateless):\x1b[0m ${d.tokensUsed.toLocaleString()} tokens (modeled) │ Precision: ${d.retrievalPrecisionPct}% │ Retry: ${d.staleRetryRatePct}%`,
|
|
76
162
|
`\x1b[90m${'─'.repeat(58)}\x1b[0m`,
|
|
77
|
-
` \x1b[32m✔ Token Reduction:\x1b[0m ${
|
|
78
|
-
` \x1b[32m✔ Cost Avoided:\x1b[0m
|
|
163
|
+
` \x1b[32m✔ Token Reduction:\x1b[0m ${tokenReductionLabel}`,
|
|
164
|
+
` \x1b[32m✔ Cost Avoided:\x1b[0m ${delta.costAvoidedUsd.toFixed(4)} USD${costAvoidedSuffix}`,
|
|
79
165
|
` \x1b[32m✔ Precision Gain:\x1b[0m ${delta.retrievalPrecisionGainRatio} higher retrieval precision per dollar`,
|
|
80
166
|
` \x1b[32m✔ Stale Retries:\x1b[0m ${delta.staleRetriesEliminated} retries eliminated`,
|
|
81
167
|
`\x1b[90m${'─'.repeat(58)}\x1b[0m\n`
|
|
82
|
-
|
|
168
|
+
);
|
|
83
169
|
return lines.join('\n');
|
|
84
170
|
};
|
|
@@ -68,7 +68,56 @@ test("runAblationComparison and formatAblationCard generate metrics card without
|
|
|
68
68
|
assert.strictEqual(ablation.memoryEnabled.astQualityScore, 100);
|
|
69
69
|
assert.strictEqual(ablation.memoryDisabled.astQualityScore, 84);
|
|
70
70
|
assert.ok(ablation.delta.tokenReductionPct >= 90);
|
|
71
|
+
assert.strictEqual(ablation.hasRealData, false);
|
|
72
|
+
assert.strictEqual(ablation.isSimulated, true);
|
|
71
73
|
const card = formatAblationCard(ablation);
|
|
72
74
|
assert.ok(card.includes("Persistent Memory Ablation Benchmark"));
|
|
75
|
+
assert.ok(card.includes("Reference Projection"));
|
|
73
76
|
assert.strictEqual(card.includes("—"), false);
|
|
74
77
|
}));
|
|
78
|
+
|
|
79
|
+
test("runAblationComparison computes dynamic metrics from live task and memory records", () => withTmpCwd((db) => {
|
|
80
|
+
db.prepare(`
|
|
81
|
+
INSERT INTO agent_tasks (title, status, prompt_tokens, completion_tokens, cost_usd, created_at, updated_at)
|
|
82
|
+
VALUES ('Refactor parser', 'done', 1500, 300, 0.05, 1000, 1000)
|
|
83
|
+
`).run();
|
|
84
|
+
|
|
85
|
+
recordMemoryInjection(db, { taskId: 1, provenance: "src/parser.ts", tokens: 200 });
|
|
86
|
+
recordMemoryUtilization(db, { taskId: 1, utilizedProvenance: ["src/parser.ts"], causedRetry: false });
|
|
87
|
+
|
|
88
|
+
const ablation = runAblationComparison(db);
|
|
89
|
+
assert.strictEqual(ablation.hasRealData, true);
|
|
90
|
+
assert.strictEqual(ablation.isSimulated, false);
|
|
91
|
+
assert.strictEqual(ablation.workloadTurns, 2);
|
|
92
|
+
assert.strictEqual(ablation.memoryEnabled.tokensUsed, 1800);
|
|
93
|
+
assert.ok(ablation.delta.tokenReductionPct > 0);
|
|
94
|
+
assert.ok(ablation.delta.retrievalPrecisionGainRatio.endsWith('x'));
|
|
95
|
+
|
|
96
|
+
const card = formatAblationCard(ablation);
|
|
97
|
+
assert.ok(card.includes("Live Project Telemetry"));
|
|
98
|
+
assert.ok(card.includes("measured vs modeled baseline"));
|
|
99
|
+
assert.strictEqual(ablation.hasMeasuredTokens, true);
|
|
100
|
+
assert.strictEqual(ablation.delta.isEstimated, false);
|
|
101
|
+
assert.strictEqual(ablation.delta.tokenReductionBasis, "measured");
|
|
102
|
+
assert.strictEqual(card.includes("—"), false);
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
test("runAblationComparison marks token reduction as estimated when live tasks lack token counts", () => withTmpCwd((db) => {
|
|
106
|
+
db.prepare(`
|
|
107
|
+
INSERT INTO agent_tasks (title, status, created_at, updated_at)
|
|
108
|
+
VALUES ('Task without token telemetry', 'done', 1000, 1000)
|
|
109
|
+
`).run();
|
|
110
|
+
|
|
111
|
+
const ablation = runAblationComparison(db);
|
|
112
|
+
assert.strictEqual(ablation.hasRealData, true);
|
|
113
|
+
assert.strictEqual(ablation.hasMeasuredTokens, false);
|
|
114
|
+
assert.strictEqual(ablation.delta.isEstimated, true);
|
|
115
|
+
assert.strictEqual(ablation.delta.tokenReductionBasis, "estimated_typical_usage");
|
|
116
|
+
assert.strictEqual(ablation.delta.tokenReductionPct, 97);
|
|
117
|
+
|
|
118
|
+
const card = formatAblationCard(ablation);
|
|
119
|
+
assert.ok(card.includes("estimated based on typical usage"));
|
|
120
|
+
assert.ok(card.includes("(projected)"));
|
|
121
|
+
assert.strictEqual(card.includes("—"), false);
|
|
122
|
+
}));
|
|
123
|
+
|
|
@@ -129,7 +129,7 @@ test('swarm multi-process: lock automatically releases when holding worker proce
|
|
|
129
129
|
import { openIndexDb } from '${path.resolve('cli/search-schema.js')}';
|
|
130
130
|
import { requestFileLock } from '${path.resolve('cli/team/team-db-locks.js')}';
|
|
131
131
|
const db = openIndexDb('${tmpDir}');
|
|
132
|
-
requestFileLock(db, '${testFile}', '@crashing-worker', { ttlMs: 3600000 });
|
|
132
|
+
requestFileLock(db, '${testFile}', '@crashing-worker', { ttlMs: 3600000, pid: process.pid });
|
|
133
133
|
process.exit(1);
|
|
134
134
|
`;
|
|
135
135
|
|
package/cli/tesseract.spec.js
CHANGED
|
@@ -27,7 +27,7 @@ test('tesseract: cli command execution succeeds with exit code 0', () => {
|
|
|
27
27
|
const cliPath = path.resolve('cli/index.js');
|
|
28
28
|
const res = spawnSync(process.execPath, [cliPath, 'tesseract'], {
|
|
29
29
|
encoding: 'utf8',
|
|
30
|
-
timeout:
|
|
30
|
+
timeout: 15000
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
assert.strictEqual(res.status, 0, 'CLI tesseract should exit with 0');
|
|
@@ -40,7 +40,7 @@ test('tesseract: cli aliases cube and matrix work identically', () => {
|
|
|
40
40
|
for (const alias of ['cube', 'matrix']) {
|
|
41
41
|
const res = spawnSync(process.execPath, [cliPath, alias], {
|
|
42
42
|
encoding: 'utf8',
|
|
43
|
-
timeout:
|
|
43
|
+
timeout: 15000
|
|
44
44
|
});
|
|
45
45
|
assert.strictEqual(res.status, 0, `CLI ${alias} should exit with 0`);
|
|
46
46
|
assert.ok(res.stdout.includes('TESSERACT'), `CLI ${alias} should render Tesseract`);
|
package/cli/theme.js
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {
|
|
4
|
+
extractCalleesFromCode,
|
|
5
|
+
calculateCallTrace,
|
|
6
|
+
calculateBacktrace
|
|
7
|
+
} from './search-queries-graph.js';
|
|
8
|
+
import {
|
|
9
|
+
handleCallTraceCommand,
|
|
10
|
+
handleBacktraceCommand
|
|
11
|
+
} from './search-commands-graph.js';
|
|
12
|
+
import { openIndexDb, upsertFileIndex } from './search-db.js';
|
|
13
|
+
|
|
14
|
+
describe('Call Trace & Backtrace Engine', () => {
|
|
15
|
+
it('extracts call expressions from code snippet', () => {
|
|
16
|
+
const code = `
|
|
17
|
+
const handleAction = async () => {
|
|
18
|
+
if (!canProceed.value) return;
|
|
19
|
+
state.value = { status: 'loading' };
|
|
20
|
+
await api.postOrder(items.value);
|
|
21
|
+
emit('success');
|
|
22
|
+
};
|
|
23
|
+
`;
|
|
24
|
+
const callees = extractCalleesFromCode(code);
|
|
25
|
+
assert.ok(callees.includes('api.postOrder'), 'Should extract api.postOrder');
|
|
26
|
+
assert.ok(callees.includes('emit'), 'Should extract emit');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('calculates forward call trace gracefully on unknown or empty target', () => {
|
|
30
|
+
const db = openIndexDb();
|
|
31
|
+
const trace = calculateCallTrace(db, 'nonExistentSymbol', { maxDepth: 2 });
|
|
32
|
+
assert.equal(trace.target, 'nonExistentSymbol');
|
|
33
|
+
assert.equal(trace.totalCallees, 0);
|
|
34
|
+
assert.deepEqual(trace.callees, []);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('calculates reverse backtrace using recursive imports CTE', () => {
|
|
38
|
+
const db = openIndexDb();
|
|
39
|
+
|
|
40
|
+
upsertFileIndex(db, {
|
|
41
|
+
path: 'src/controllers/trace-cart.ts',
|
|
42
|
+
mtime: Date.now(),
|
|
43
|
+
size: 400,
|
|
44
|
+
tier: 'hook',
|
|
45
|
+
lines: 40,
|
|
46
|
+
chars: 400,
|
|
47
|
+
symbols: [{ name: 'useTraceCartController', kind: 'const', isExport: true, startLine: 1, endLine: 40 }],
|
|
48
|
+
imports: []
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
upsertFileIndex(db, {
|
|
52
|
+
path: 'src/organisms/trace-cart.vue',
|
|
53
|
+
mtime: Date.now(),
|
|
54
|
+
size: 800,
|
|
55
|
+
tier: 'organism',
|
|
56
|
+
lines: 80,
|
|
57
|
+
chars: 800,
|
|
58
|
+
symbols: [{ name: 'TraceCartOrganism', kind: 'const', isExport: true, startLine: 1, endLine: 80 }],
|
|
59
|
+
imports: [
|
|
60
|
+
{ importedSymbol: 'useTraceCartController', sourceModule: '@/controllers/trace-cart.ts', resolvedPath: 'src/controllers/trace-cart.ts', line: 3 }
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
upsertFileIndex(db, {
|
|
65
|
+
path: 'src/views/trace-checkout.vue',
|
|
66
|
+
mtime: Date.now(),
|
|
67
|
+
size: 500,
|
|
68
|
+
tier: 'view',
|
|
69
|
+
lines: 50,
|
|
70
|
+
chars: 500,
|
|
71
|
+
symbols: [{ name: 'TraceCheckoutView', kind: 'const', isExport: true, startLine: 1, endLine: 50 }],
|
|
72
|
+
imports: [
|
|
73
|
+
{ importedSymbol: 'TraceCartOrganism', sourceModule: '@/organisms/trace-cart.vue', resolvedPath: 'src/organisms/trace-cart.vue', line: 5 }
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const backtrace = calculateBacktrace(db, 'useTraceCartController', { maxDepth: 5 });
|
|
78
|
+
assert.equal(backtrace.target, 'useTraceCartController');
|
|
79
|
+
assert.ok(backtrace.totalCallers >= 2, `Expected at least 2 callers, got ${backtrace.totalCallers}`);
|
|
80
|
+
assert.ok(backtrace.chains.some((c) => c.includes('trace-checkout.vue')), 'Causal chain should reach root view');
|
|
81
|
+
assert.ok(backtrace.rootCallers.some((r) => r.tier === 'view'), 'Root callers should include view tier');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('formats call trace and backtrace as JSON payloads', () => {
|
|
85
|
+
const db = openIndexDb();
|
|
86
|
+
const traceRes = handleCallTraceCommand(db, 'foo', { isJson: true, isCli: false });
|
|
87
|
+
assert.equal(traceRes.target, 'foo');
|
|
88
|
+
|
|
89
|
+
const backtraceRes = handleBacktraceCommand(db, 'foo', { isJson: true, isCli: false });
|
|
90
|
+
assert.equal(backtraceRes.target, 'foo');
|
|
91
|
+
});
|
|
92
|
+
});
|
package/cli/verify-helpers.js
CHANGED
|
@@ -30,16 +30,59 @@ export const detectTypecheckCommand = (customCmd, cwd = process.cwd()) => {
|
|
|
30
30
|
return `${pm} run typecheck`;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const detectTestCommand = (customCmd, cwd = process.cwd()) => {
|
|
34
|
-
|
|
33
|
+
export const detectTestCommand = (customCmd, cwd = process.cwd(), options = {}) => {
|
|
34
|
+
const target = options.target ? String(options.target).trim() : null;
|
|
35
|
+
const filter = options.filter ? String(options.filter).trim() : null;
|
|
36
|
+
|
|
37
|
+
if (customCmd && customCmd.trim().length > 0) {
|
|
38
|
+
let cmd = customCmd.trim();
|
|
39
|
+
if (target && !cmd.includes(target)) cmd += ` ${target}`;
|
|
40
|
+
if (filter && !cmd.includes(filter)) cmd += ` -t "${filter}"`;
|
|
41
|
+
return cmd;
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
const pkg = loadLocalPackageJson(cwd);
|
|
36
45
|
const scripts = (pkg && pkg.scripts) || {};
|
|
46
|
+
const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
|
|
37
47
|
const pm = resolvePackageManager(cwd);
|
|
38
48
|
|
|
49
|
+
const hasVitest = Boolean(
|
|
50
|
+
deps.vitest ||
|
|
51
|
+
(scripts.test && scripts.test.includes('vitest')) ||
|
|
52
|
+
fs.existsSync(path.join(cwd, 'vitest.config.ts')) ||
|
|
53
|
+
fs.existsSync(path.join(cwd, 'vitest.config.js'))
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const hasNodeTest = Boolean(
|
|
57
|
+
scripts.test && (scripts.test.includes('node --test') || scripts.test.includes('node:test'))
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (target || filter) {
|
|
61
|
+
if (hasVitest) {
|
|
62
|
+
let vitestCmd = 'npx vitest run';
|
|
63
|
+
if (target) vitestCmd += ` ${target}`;
|
|
64
|
+
if (filter) vitestCmd += ` -t "${filter}"`;
|
|
65
|
+
return vitestCmd;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (hasNodeTest || target?.endsWith('.spec.js') || target?.endsWith('.test.js')) {
|
|
69
|
+
let nodeCmd = 'node --test';
|
|
70
|
+
if (target) nodeCmd += ` ${target}`;
|
|
71
|
+
if (filter) nodeCmd += ` --test-name-pattern="${filter}"`;
|
|
72
|
+
return nodeCmd;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const baseTest = pm === 'yarn' ? 'yarn test' : `${pm} test`;
|
|
76
|
+
let args = '';
|
|
77
|
+
if (target) args += ` ${target}`;
|
|
78
|
+
if (filter) args += ` -t "${filter}"`;
|
|
79
|
+
return `${baseTest} --${args}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
39
82
|
const isLegitTest = scripts.test && !scripts.test.includes('no test specified');
|
|
40
83
|
if (isLegitTest) return pm === 'yarn' ? 'yarn test' : `${pm} run test`;
|
|
41
84
|
|
|
42
|
-
if (
|
|
85
|
+
if (hasVitest) {
|
|
43
86
|
return 'npx vitest run';
|
|
44
87
|
}
|
|
45
88
|
|
|
@@ -91,37 +134,56 @@ export const parseTestOutput = (stdout = '', stderr = '', exitCode = 0) => {
|
|
|
91
134
|
let skipped = 0;
|
|
92
135
|
let checkmarkPasses = 0;
|
|
93
136
|
let crossmarkFails = 0;
|
|
137
|
+
let hasRunnerSummary = false;
|
|
94
138
|
|
|
95
139
|
for (const line of lines) {
|
|
96
140
|
const clean = stripAnsi(line).trim();
|
|
97
141
|
if (!clean) continue;
|
|
98
142
|
|
|
99
|
-
if (clean.startsWith('✔')) checkmarkPasses++;
|
|
143
|
+
if (clean.startsWith('✔') || clean.startsWith('✓')) checkmarkPasses++;
|
|
100
144
|
if (clean.startsWith('✖') || clean.startsWith('FAIL ')) crossmarkFails++;
|
|
101
145
|
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
146
|
+
const isRunnerSummary = /^Tests:?\s+/i.test(clean);
|
|
147
|
+
if (isRunnerSummary) {
|
|
148
|
+
const passM = clean.match(/(\d+)\s+passed/i);
|
|
149
|
+
const failM = clean.match(/(\d+)\s+failed/i);
|
|
150
|
+
const skipM = clean.match(/(\d+)\s+(?:skipped|todo|pending)/i);
|
|
151
|
+
const totalParenM = clean.match(/\((\d+)\)/);
|
|
152
|
+
const totalWordM = clean.match(/(\d+)\s+total/i);
|
|
153
|
+
|
|
154
|
+
if (passM) passed = parseInt(passM[1], 10);
|
|
155
|
+
if (failM) failed = parseInt(failM[1], 10);
|
|
156
|
+
if (skipM) skipped = parseInt(skipM[1], 10);
|
|
157
|
+
|
|
158
|
+
if (totalParenM) totalTests = parseInt(totalParenM[1], 10);
|
|
159
|
+
else if (totalWordM) totalTests = parseInt(totalWordM[1], 10);
|
|
160
|
+
else totalTests = passed + failed + skipped;
|
|
161
|
+
|
|
162
|
+
hasRunnerSummary = true;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
104
165
|
|
|
105
|
-
|
|
106
|
-
|
|
166
|
+
if (!hasRunnerSummary) {
|
|
167
|
+
const nodeTests = clean.match(/^(?:[ℹ#]\s+)?tests\s+(\d+)$/i);
|
|
168
|
+
if (nodeTests) totalTests = parseInt(nodeTests[1], 10);
|
|
107
169
|
|
|
108
|
-
|
|
109
|
-
|
|
170
|
+
const nodePass = clean.match(/^(?:[ℹ#]\s+)?pass\s+(\d+)$/i);
|
|
171
|
+
if (nodePass) passed = parseInt(nodePass[1], 10);
|
|
110
172
|
|
|
111
|
-
|
|
112
|
-
|
|
173
|
+
const nodeFail = clean.match(/^(?:[ℹ#]\s+)?fail\s+(\d+)$/i);
|
|
174
|
+
if (nodeFail) failed = parseInt(nodeFail[1], 10);
|
|
113
175
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (vitestMatch[1]) failed = parseInt(vitestMatch[1], 10);
|
|
117
|
-
if (vitestMatch[2]) passed = parseInt(vitestMatch[2], 10);
|
|
118
|
-
if (vitestMatch[3]) totalTests = parseInt(vitestMatch[3], 10);
|
|
176
|
+
const nodeSkip = clean.match(/^(?:[ℹ#]\s+)?(?:skipped|todo)\s+(\d+)$/i);
|
|
177
|
+
if (nodeSkip) skipped += parseInt(nodeSkip[1], 10);
|
|
119
178
|
}
|
|
120
179
|
}
|
|
121
180
|
|
|
122
181
|
if (passed === 0 && checkmarkPasses > 0) passed = checkmarkPasses;
|
|
123
182
|
if (failed === 0 && crossmarkFails > 0) failed = crossmarkFails;
|
|
124
|
-
if (totalTests === 0) totalTests = passed + failed;
|
|
183
|
+
if (totalTests === 0) totalTests = passed + failed + skipped;
|
|
184
|
+
const isCleanExit = exitCode === 0 && failed === 0;
|
|
185
|
+
const isTestCountUnderflow = isCleanExit && totalTests < passed;
|
|
186
|
+
if (isTestCountUnderflow) totalTests = passed;
|
|
125
187
|
|
|
126
188
|
const failures = [];
|
|
127
189
|
if (exitCode !== 0 || failed > 0) {
|