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.
Files changed (32) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/v3/@swarmdo/cli/dist/src/apply/apply.d.ts +2 -0
  4. package/v3/@swarmdo/cli/dist/src/apply/apply.js +29 -2
  5. package/v3/@swarmdo/cli/dist/src/changelog/changelog.js +26 -3
  6. package/v3/@swarmdo/cli/dist/src/codegraph/codegraph.d.ts +26 -7
  7. package/v3/@swarmdo/cli/dist/src/codegraph/codegraph.js +97 -13
  8. package/v3/@swarmdo/cli/dist/src/codegraph/store.d.ts +8 -1
  9. package/v3/@swarmdo/cli/dist/src/codegraph/store.js +78 -1
  10. package/v3/@swarmdo/cli/dist/src/commands/cycles.js +4 -1
  11. package/v3/@swarmdo/cli/dist/src/commands/hotspots.js +5 -2
  12. package/v3/@swarmdo/cli/dist/src/commands/release.js +5 -1
  13. package/v3/@swarmdo/cli/dist/src/commands/testreport.js +2 -1
  14. package/v3/@swarmdo/cli/dist/src/cycles/cycles.d.ts +12 -2
  15. package/v3/@swarmdo/cli/dist/src/cycles/cycles.js +5 -3
  16. package/v3/@swarmdo/cli/dist/src/env/env.d.ts +5 -2
  17. package/v3/@swarmdo/cli/dist/src/env/env.js +24 -3
  18. package/v3/@swarmdo/cli/dist/src/hotspots/hotspots.d.ts +15 -0
  19. package/v3/@swarmdo/cli/dist/src/hotspots/hotspots.js +33 -1
  20. package/v3/@swarmdo/cli/dist/src/license/license.d.ts +31 -2
  21. package/v3/@swarmdo/cli/dist/src/license/license.js +133 -11
  22. package/v3/@swarmdo/cli/dist/src/pack/pack.d.ts +2 -3
  23. package/v3/@swarmdo/cli/dist/src/pack/pack.js +68 -12
  24. package/v3/@swarmdo/cli/dist/src/redact/redact.js +14 -2
  25. package/v3/@swarmdo/cli/dist/src/sbom/sbom.d.ts +16 -0
  26. package/v3/@swarmdo/cli/dist/src/sbom/sbom.js +28 -1
  27. package/v3/@swarmdo/cli/dist/src/testreport/testreport.d.ts +14 -0
  28. package/v3/@swarmdo/cli/dist/src/testreport/testreport.js +71 -7
  29. package/v3/@swarmdo/cli/dist/src/usage/claude-pricing.d.ts +6 -0
  30. package/v3/@swarmdo/cli/dist/src/usage/claude-pricing.js +12 -9
  31. package/v3/@swarmdo/cli/dist/src/usage/transcript-usage.js +4 -1
  32. 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(/&amp;/g, '&'); // last, so &amp;lt; → &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 = decodeXml((fail[3] ?? '').trim());
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
- return list.reduce((acc, s) => ({
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 head = `${s.passed} passed · ${s.failed} failed · ${s.skipped} skipped (${s.total} total, ${s.durationMs}ms)`;
168
- if (s.failures.length === 0)
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
- t.cacheWriteTokens * price.cacheWrite +
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.19.0",
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",