swarmdo 1.19.0 → 1.27.0
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 +1 -1
- package/package.json +1 -1
- package/v3/@swarmdo/cli/dist/src/apply/apply.d.ts +2 -0
- package/v3/@swarmdo/cli/dist/src/apply/apply.js +29 -2
- package/v3/@swarmdo/cli/dist/src/changelog/changelog.js +26 -3
- package/v3/@swarmdo/cli/dist/src/codegraph/codegraph.d.ts +26 -7
- package/v3/@swarmdo/cli/dist/src/codegraph/codegraph.js +97 -13
- package/v3/@swarmdo/cli/dist/src/codegraph/store.d.ts +8 -1
- package/v3/@swarmdo/cli/dist/src/codegraph/store.js +78 -1
- package/v3/@swarmdo/cli/dist/src/commands/cycles.js +4 -1
- package/v3/@swarmdo/cli/dist/src/commands/hotspots.js +5 -2
- package/v3/@swarmdo/cli/dist/src/commands/release.js +5 -1
- package/v3/@swarmdo/cli/dist/src/commands/testreport.js +2 -1
- package/v3/@swarmdo/cli/dist/src/cycles/cycles.d.ts +12 -2
- package/v3/@swarmdo/cli/dist/src/cycles/cycles.js +5 -3
- package/v3/@swarmdo/cli/dist/src/env/env.d.ts +5 -2
- package/v3/@swarmdo/cli/dist/src/env/env.js +24 -3
- package/v3/@swarmdo/cli/dist/src/hotspots/hotspots.d.ts +15 -0
- package/v3/@swarmdo/cli/dist/src/hotspots/hotspots.js +33 -1
- package/v3/@swarmdo/cli/dist/src/license/license.d.ts +31 -2
- package/v3/@swarmdo/cli/dist/src/license/license.js +133 -11
- package/v3/@swarmdo/cli/dist/src/pack/pack.d.ts +2 -3
- package/v3/@swarmdo/cli/dist/src/pack/pack.js +68 -12
- package/v3/@swarmdo/cli/dist/src/redact/redact.js +14 -2
- package/v3/@swarmdo/cli/dist/src/sbom/sbom.d.ts +16 -0
- package/v3/@swarmdo/cli/dist/src/sbom/sbom.js +28 -1
- package/v3/@swarmdo/cli/dist/src/testreport/testreport.d.ts +14 -0
- package/v3/@swarmdo/cli/dist/src/testreport/testreport.js +71 -7
- package/v3/@swarmdo/cli/dist/src/usage/claude-pricing.d.ts +6 -0
- package/v3/@swarmdo/cli/dist/src/usage/claude-pricing.js +12 -9
- package/v3/@swarmdo/cli/dist/src/usage/transcript-usage.js +4 -1
- package/v3/@swarmdo/cli/package.json +1 -1
|
@@ -21,6 +21,37 @@ function decodeXml(s) {
|
|
|
21
21
|
.replace(/&#(\d+);/g, (_, d) => String.fromCharCode(parseInt(d, 10)))
|
|
22
22
|
.replace(/&/g, '&'); // last, so &lt; → < not <
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Decode an element's text content, honoring CDATA sections. Text OUTSIDE
|
|
26
|
+
* `<![CDATA[ … ]]>` is entity-decoded; text INSIDE is taken literally (per the
|
|
27
|
+
* XML spec, CDATA content is never entity-expanded). Maven Surefire/Failsafe
|
|
28
|
+
* wrap `<failure>`/`<error>` stack traces in CDATA, so without this the raw
|
|
29
|
+
* `<![CDATA[`/`]]>` markers leak into the failure message. Pure.
|
|
30
|
+
*/
|
|
31
|
+
function decodeXmlContent(s) {
|
|
32
|
+
const OPEN = '<![CDATA[';
|
|
33
|
+
const CLOSE = ']]>';
|
|
34
|
+
if (!s.includes(OPEN))
|
|
35
|
+
return decodeXml(s);
|
|
36
|
+
let out = '';
|
|
37
|
+
let i = 0;
|
|
38
|
+
while (i < s.length) {
|
|
39
|
+
const start = s.indexOf(OPEN, i);
|
|
40
|
+
if (start < 0) {
|
|
41
|
+
out += decodeXml(s.slice(i));
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
out += decodeXml(s.slice(i, start));
|
|
45
|
+
const end = s.indexOf(CLOSE, start + OPEN.length);
|
|
46
|
+
if (end < 0) {
|
|
47
|
+
out += s.slice(start + OPEN.length);
|
|
48
|
+
break;
|
|
49
|
+
} // unterminated → rest is literal
|
|
50
|
+
out += s.slice(start + OPEN.length, end);
|
|
51
|
+
i = end + CLOSE.length;
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
24
55
|
/** Extract key="value" / key='value' attributes from a tag's attribute text. Pure. */
|
|
25
56
|
function parseAttrs(attrText) {
|
|
26
57
|
const out = {};
|
|
@@ -62,7 +93,7 @@ export function parseJUnit(xml) {
|
|
|
62
93
|
if (fail) {
|
|
63
94
|
failed++;
|
|
64
95
|
const fAttrs = parseAttrs(fail[2]);
|
|
65
|
-
const inner =
|
|
96
|
+
const inner = decodeXmlContent(fail[3] ?? '').trim();
|
|
66
97
|
const message = fAttrs.message || inner.split('\n')[0] || undefined;
|
|
67
98
|
// Prefer explicit file/line attrs (pytest, some emitters), else sniff the trace.
|
|
68
99
|
const loc = attrs.file
|
|
@@ -82,10 +113,20 @@ export function parseJUnit(xml) {
|
|
|
82
113
|
/** Parse TAP (Test Anything Protocol) output. Pure. */
|
|
83
114
|
export function parseTAP(text) {
|
|
84
115
|
const failures = [];
|
|
85
|
-
let passed = 0, failed = 0, skipped = 0;
|
|
116
|
+
let passed = 0, failed = 0, skipped = 0, todo = 0;
|
|
117
|
+
let bailedOut = false;
|
|
118
|
+
let bailReason;
|
|
86
119
|
const lines = text.split('\n');
|
|
87
120
|
for (let i = 0; i < lines.length; i++) {
|
|
88
121
|
const line = lines[i];
|
|
122
|
+
// `Bail out!` (column 1, per the TAP spec) aborts the run — stop parsing;
|
|
123
|
+
// anything after it is not a valid result.
|
|
124
|
+
const bail = line.match(/^Bail out!(?:\s+(.*))?$/);
|
|
125
|
+
if (bail) {
|
|
126
|
+
bailedOut = true;
|
|
127
|
+
bailReason = bail[1]?.trim() || undefined;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
89
130
|
const m = line.match(/^(ok|not ok)\b\s*(\d+)?\s*-?\s*(.*)$/);
|
|
90
131
|
if (!m)
|
|
91
132
|
continue;
|
|
@@ -98,6 +139,12 @@ export function parseTAP(text) {
|
|
|
98
139
|
skipped++;
|
|
99
140
|
continue;
|
|
100
141
|
}
|
|
142
|
+
// TODO tests are "not expected to succeed" — a `not ok … # TODO` is a known
|
|
143
|
+
// stub, not a real failure (TAP spec). Count it as todo, never a failure.
|
|
144
|
+
if (directive && /todo/i.test(directive[1])) {
|
|
145
|
+
todo++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
101
148
|
if (ok) {
|
|
102
149
|
passed++;
|
|
103
150
|
}
|
|
@@ -131,7 +178,7 @@ export function parseTAP(text) {
|
|
|
131
178
|
failures.push({ suite: '', name: desc || '(unnamed)', message, file, line: lineNo });
|
|
132
179
|
}
|
|
133
180
|
}
|
|
134
|
-
return { passed, failed, skipped, total: passed + failed + skipped, durationMs: 0, failures };
|
|
181
|
+
return { passed, failed, skipped, total: passed + failed + skipped + todo, durationMs: 0, failures, ...(todo && { todo }), ...(bailedOut && { bailedOut, bailReason }) };
|
|
135
182
|
}
|
|
136
183
|
/** Sniff the format from a path extension, then content. Pure. */
|
|
137
184
|
export function detectFormat(content, path) {
|
|
@@ -153,7 +200,7 @@ export function parseTestReport(content, format) {
|
|
|
153
200
|
}
|
|
154
201
|
/** Merge several summaries (multi-file globs). Pure. */
|
|
155
202
|
export function mergeSummaries(list) {
|
|
156
|
-
|
|
203
|
+
const merged = list.reduce((acc, s) => ({
|
|
157
204
|
passed: acc.passed + s.passed,
|
|
158
205
|
failed: acc.failed + s.failed,
|
|
159
206
|
skipped: acc.skipped + s.skipped,
|
|
@@ -161,14 +208,31 @@ export function mergeSummaries(list) {
|
|
|
161
208
|
durationMs: acc.durationMs + s.durationMs,
|
|
162
209
|
failures: acc.failures.concat(s.failures),
|
|
163
210
|
}), { passed: 0, failed: 0, skipped: 0, total: 0, durationMs: 0, failures: [] });
|
|
211
|
+
const todoSum = list.reduce((n, s) => n + (s.todo ?? 0), 0);
|
|
212
|
+
if (todoSum > 0)
|
|
213
|
+
merged.todo = todoSum;
|
|
214
|
+
// Any bailed file taints the whole run; keep the first reason seen.
|
|
215
|
+
const bailed = list.find((s) => s.bailedOut);
|
|
216
|
+
if (bailed) {
|
|
217
|
+
merged.bailedOut = true;
|
|
218
|
+
merged.bailReason = bailed.bailReason;
|
|
219
|
+
}
|
|
220
|
+
return merged;
|
|
164
221
|
}
|
|
165
222
|
/** Human-readable digest. Pure. */
|
|
166
223
|
export function formatSummary(s, opts = {}) {
|
|
167
|
-
const
|
|
168
|
-
|
|
224
|
+
const todoSeg = s.todo ? ` · ${s.todo} todo` : '';
|
|
225
|
+
const head = `${s.passed} passed · ${s.failed} failed · ${s.skipped} skipped${todoSeg} (${s.total} total, ${s.durationMs}ms)`;
|
|
226
|
+
const bailLine = s.bailedOut
|
|
227
|
+
? `⚠ suite ABORTED (Bail out!${s.bailReason ? `: ${s.bailReason}` : ''}) — results incomplete`
|
|
228
|
+
: '';
|
|
229
|
+
if (s.failures.length === 0) {
|
|
230
|
+
if (s.bailedOut)
|
|
231
|
+
return `${bailLine}\n${head}`;
|
|
169
232
|
return head + (s.failed === 0 ? ' ✓' : '');
|
|
233
|
+
}
|
|
170
234
|
const shown = opts.top && opts.top > 0 ? s.failures.slice(0, opts.top) : s.failures;
|
|
171
|
-
const lines = [head, ''];
|
|
235
|
+
const lines = s.bailedOut ? [bailLine, head, ''] : [head, ''];
|
|
172
236
|
for (const f of shown) {
|
|
173
237
|
const where = f.file ? `${f.file}${f.line ? `:${f.line}` : ''}` : '';
|
|
174
238
|
lines.push(`✗ ${f.suite ? f.suite + ' › ' : ''}${f.name}${where ? ` (${where})` : ''}`);
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
export interface TranscriptModelPrice {
|
|
17
17
|
in: number;
|
|
18
18
|
out: number;
|
|
19
|
+
/** 5-minute-TTL cache write (1.25× base input) */
|
|
19
20
|
cacheWrite: number;
|
|
21
|
+
/** 1-hour-TTL cache write (2× base input) */
|
|
22
|
+
cacheWrite1h: number;
|
|
20
23
|
cacheRead: number;
|
|
21
24
|
}
|
|
22
25
|
/**
|
|
@@ -32,7 +35,10 @@ export declare function resolveTranscriptPrice(rawModelId: string): TranscriptMo
|
|
|
32
35
|
export interface TokenBundle {
|
|
33
36
|
inputTokens: number;
|
|
34
37
|
outputTokens: number;
|
|
38
|
+
/** TOTAL cache-write tokens (5-min + 1-hour). */
|
|
35
39
|
cacheWriteTokens: number;
|
|
40
|
+
/** The 1-hour-TTL SUBSET of cacheWriteTokens (≤ cacheWriteTokens). Default 0 → all writes priced at the 5-min rate. */
|
|
41
|
+
cacheWrite1hTokens?: number;
|
|
36
42
|
cacheReadTokens: number;
|
|
37
43
|
}
|
|
38
44
|
/** USD for one response at the given rates. */
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
* publishes rates — absent means "unpriced", never "guessed".
|
|
21
21
|
*/
|
|
22
22
|
const PRICE_FAMILIES = {
|
|
23
|
-
'claude-opus-4': { in: 15, out: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
24
|
-
'claude-sonnet-4': { in: 3, out: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
25
|
-
'claude-haiku-4': { in: 1, out: 5, cacheWrite: 1.25, cacheRead: 0.1 },
|
|
26
|
-
'claude-3-7-sonnet': { in: 3, out: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
27
|
-
'claude-3-5-sonnet': { in: 3, out: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
28
|
-
'claude-3-5-haiku': { in: 0.8, out: 4, cacheWrite: 1, cacheRead: 0.08 },
|
|
29
|
-
'claude-3-opus': { in: 15, out: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
30
|
-
'claude-3-haiku': { in: 0.25, out: 1.25, cacheWrite: 0.3, cacheRead: 0.03 },
|
|
23
|
+
'claude-opus-4': { in: 15, out: 75, cacheWrite: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
|
|
24
|
+
'claude-sonnet-4': { in: 3, out: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
25
|
+
'claude-haiku-4': { in: 1, out: 5, cacheWrite: 1.25, cacheWrite1h: 2, cacheRead: 0.1 },
|
|
26
|
+
'claude-3-7-sonnet': { in: 3, out: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
27
|
+
'claude-3-5-sonnet': { in: 3, out: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
28
|
+
'claude-3-5-haiku': { in: 0.8, out: 4, cacheWrite: 1, cacheWrite1h: 1.6, cacheRead: 0.08 },
|
|
29
|
+
'claude-3-opus': { in: 15, out: 75, cacheWrite: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
|
|
30
|
+
'claude-3-haiku': { in: 0.25, out: 1.25, cacheWrite: 0.3, cacheWrite1h: 0.5, cacheRead: 0.03 },
|
|
31
31
|
};
|
|
32
32
|
/**
|
|
33
33
|
* Reduce a transcript/gateway model id to the bare Anthropic id:
|
|
@@ -62,9 +62,12 @@ export function resolveTranscriptPrice(rawModelId) {
|
|
|
62
62
|
}
|
|
63
63
|
/** USD for one response at the given rates. */
|
|
64
64
|
export function transcriptCostUsd(price, t) {
|
|
65
|
+
const cacheWrite1h = t.cacheWrite1hTokens ?? 0;
|
|
66
|
+
const cacheWrite5m = Math.max(0, t.cacheWriteTokens - cacheWrite1h);
|
|
65
67
|
return ((t.inputTokens * price.in +
|
|
66
68
|
t.outputTokens * price.out +
|
|
67
|
-
|
|
69
|
+
cacheWrite5m * price.cacheWrite +
|
|
70
|
+
cacheWrite1h * price.cacheWrite1h +
|
|
68
71
|
t.cacheReadTokens * price.cacheRead) / 1_000_000);
|
|
69
72
|
}
|
|
70
73
|
//# sourceMappingURL=claude-pricing.js.map
|
|
@@ -177,6 +177,9 @@ function toUsageEvent(line, project, seen, unpriced) {
|
|
|
177
177
|
cacheWriteTokens: usage.cache_creation_input_tokens ?? 0,
|
|
178
178
|
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
|
|
179
179
|
};
|
|
180
|
+
// 1-hour-TTL cache writes cost 2× base input, not the 1.25× 5-min rate. Recent
|
|
181
|
+
// transcripts split them in `cache_creation`; older ones don't (→ all 5-min).
|
|
182
|
+
const cacheWrite1hTokens = usage.cache_creation?.ephemeral_1h_input_tokens ?? 0;
|
|
180
183
|
let costUsd;
|
|
181
184
|
let costSource;
|
|
182
185
|
const price = resolveTranscriptPrice(model);
|
|
@@ -185,7 +188,7 @@ function toUsageEvent(line, project, seen, unpriced) {
|
|
|
185
188
|
costSource = 'transcript';
|
|
186
189
|
}
|
|
187
190
|
else if (price) {
|
|
188
|
-
costUsd = transcriptCostUsd(price, tokens);
|
|
191
|
+
costUsd = transcriptCostUsd(price, { ...tokens, cacheWrite1hTokens });
|
|
189
192
|
costSource = 'computed';
|
|
190
193
|
}
|
|
191
194
|
else {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmdo/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Swarmdo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|