swarmdo 1.27.0 → 1.29.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 (38) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +5 -1
  2. package/.claude-plugin/README.md +3 -3
  3. package/.claude-plugin/docs/INSTALLATION.md +1 -1
  4. package/.claude-plugin/docs/PLUGIN_SUMMARY.md +1 -1
  5. package/.claude-plugin/docs/QUICKSTART.md +3 -3
  6. package/.claude-plugin/hooks/hooks.json +19 -1
  7. package/.claude-plugin/marketplace.json +17 -17
  8. package/.claude-plugin/plugin.json +7 -39
  9. package/.claude-plugin/scripts/install.sh +3 -3
  10. package/.claude-plugin/scripts/swarmdo-hook.sh +1 -1
  11. package/.claude-plugin/scripts/verify.sh +2 -2
  12. package/README.md +10 -8
  13. package/package.json +1 -1
  14. package/v3/@swarmdo/cli/.claude/helpers/auto-memory-hook.mjs +5 -1
  15. package/v3/@swarmdo/cli/bin/cli.js +38 -0
  16. package/v3/@swarmdo/cli/dist/src/apply/apply.d.ts +6 -0
  17. package/v3/@swarmdo/cli/dist/src/apply/apply.js +41 -8
  18. package/v3/@swarmdo/cli/dist/src/commands/apply.js +17 -4
  19. package/v3/@swarmdo/cli/dist/src/commands/compact-snapshot.d.ts +17 -0
  20. package/v3/@swarmdo/cli/dist/src/commands/compact-snapshot.js +133 -0
  21. package/v3/@swarmdo/cli/dist/src/commands/index.js +4 -3
  22. package/v3/@swarmdo/cli/dist/src/commands/pack.d.ts +8 -0
  23. package/v3/@swarmdo/cli/dist/src/commands/pack.js +28 -2
  24. package/v3/@swarmdo/cli/dist/src/compact/compact.js +17 -7
  25. package/v3/@swarmdo/cli/dist/src/compact-snapshot/compact-snapshot.d.ts +58 -0
  26. package/v3/@swarmdo/cli/dist/src/compact-snapshot/compact-snapshot.js +104 -0
  27. package/v3/@swarmdo/cli/dist/src/config-lint/lint.js +16 -2
  28. package/v3/@swarmdo/cli/dist/src/init/helpers-generator.js +33 -3
  29. package/v3/@swarmdo/cli/dist/src/redact/redact.js +1 -1
  30. package/v3/@swarmdo/cli/dist/src/sbom/sbom.js +5 -2
  31. package/v3/@swarmdo/cli/dist/src/swarmvector/model-prices.js +12 -2
  32. package/v3/@swarmdo/cli/dist/src/testreport/testreport.js +11 -2
  33. package/v3/@swarmdo/cli/dist/src/transcript/export.d.ts +5 -0
  34. package/v3/@swarmdo/cli/dist/src/transcript/export.js +8 -1
  35. package/v3/@swarmdo/cli/dist/src/usage/claude-pricing.d.ts +6 -2
  36. package/v3/@swarmdo/cli/dist/src/usage/claude-pricing.js +33 -4
  37. package/v3/@swarmdo/cli/dist/src/usage/transcript-usage.js +1 -1
  38. package/v3/@swarmdo/cli/package.json +2 -2
@@ -11,14 +11,23 @@
11
11
  * No XML dependency — well-formed JUnit escapes <>& in attribute values, so a
12
12
  * focused tokenizer over <testcase>/<failure> is safe.
13
13
  */
14
- /** Decode the five predefined XML entities. Pure. */
14
+ /** A numeric character reference → its char, or the original ref if the code
15
+ * point is out of the valid Unicode range (so a malformed entity never throws
16
+ * and leaks literally instead of aborting the parse). Pure. */
17
+ function fromCodePointSafe(n, original) {
18
+ return Number.isInteger(n) && n >= 0 && n <= 0x10ffff ? String.fromCodePoint(n) : original;
19
+ }
20
+ /** Decode the five predefined XML entities plus decimal AND hex numeric
21
+ * character references (`&#65;`, `&#x1b;`). XML 1.0 allows both numeric forms;
22
+ * JUnit stack traces with ANSI colouring escape control chars as hex. Pure. */
15
23
  function decodeXml(s) {
16
24
  return s
17
25
  .replace(/&lt;/g, '<')
18
26
  .replace(/&gt;/g, '>')
19
27
  .replace(/&quot;/g, '"')
20
28
  .replace(/&apos;/g, "'")
21
- .replace(/&#(\d+);/g, (_, d) => String.fromCharCode(parseInt(d, 10)))
29
+ .replace(/&#x([0-9a-fA-F]+);/g, (m, h) => fromCodePointSafe(parseInt(h, 16), m))
30
+ .replace(/&#(\d+);/g, (m, d) => fromCodePointSafe(parseInt(d, 10), m))
22
31
  .replace(/&amp;/g, '&'); // last, so &amp;lt; → &lt; not <
23
32
  }
24
33
  /**
@@ -45,6 +45,11 @@ export interface RenderOptions {
45
45
  export declare function cleanUserText(s: string): string;
46
46
  /** Flatten tool_result content (string | array of {text}) to text. */
47
47
  export declare function contentToText(content: unknown): string;
48
+ /** Count the conversational turns in a rendered body by matching only the
49
+ * exact role-heading lines. Anchored to end-of-line so `### Foo` markdown
50
+ * headings written *inside* a user/assistant message don't inflate the count.
51
+ * Pure. */
52
+ export declare function countRenderedTurns(markdown: string): number;
48
53
  /** Render an ordered list of parsed transcript lines into Markdown. */
49
54
  export declare function renderTranscriptMarkdown(lines: RawTranscriptLine[], opts?: RenderOptions): string;
50
55
  export interface SessionSummary {
@@ -92,6 +92,13 @@ function renderLine(role, content, opts) {
92
92
  sections.push(results.join('\n'));
93
93
  return sections.join('\n\n');
94
94
  }
95
+ /** Count the conversational turns in a rendered body by matching only the
96
+ * exact role-heading lines. Anchored to end-of-line so `### Foo` markdown
97
+ * headings written *inside* a user/assistant message don't inflate the count.
98
+ * Pure. */
99
+ export function countRenderedTurns(markdown) {
100
+ return (markdown.match(/^### (?:🤖 Assistant|👤 User)$/gm) || []).length;
101
+ }
95
102
  /** Render an ordered list of parsed transcript lines into Markdown. */
96
103
  export function renderTranscriptMarkdown(lines, opts = {}) {
97
104
  const resolved = {
@@ -202,7 +209,7 @@ export function exportSession(idOrLatest, opts = {}) {
202
209
  const sessionId = sessionIdFromFile(file);
203
210
  const project = path.basename(path.dirname(file));
204
211
  const body = renderTranscriptMarkdown(lines, opts);
205
- const turns = (body.match(/^### /gm) || []).length;
212
+ const turns = countRenderedTurns(body);
206
213
  const header = `# Claude Code session \`${sessionId}\`\n\n- Project: \`${project}\`\n- Turns: ${turns}\n\n---\n\n`;
207
214
  return { markdown: header + body, sessionId, project, turns, file };
208
215
  }
@@ -29,8 +29,12 @@ export interface TranscriptModelPrice {
29
29
  * 'claude-sonnet-4-6@20260115' → 'claude-sonnet-4-6' (Vertex)
30
30
  */
31
31
  export declare function normalizeTranscriptModelId(raw: string): string;
32
- /** Longest-prefix price lookup; undefined = unpriced (report it, don't guess). */
33
- export declare function resolveTranscriptPrice(rawModelId: string): TranscriptModelPrice | undefined;
32
+ /**
33
+ * Longest-prefix price lookup; undefined = unpriced (report it, don't guess).
34
+ * Pass the transcript entry's timestamp as `atMs` to get date-ranged promo
35
+ * rates (e.g. Sonnet 5 intro pricing); omitted → sticker price.
36
+ */
37
+ export declare function resolveTranscriptPrice(rawModelId: string, atMs?: number): TranscriptModelPrice | undefined;
34
38
  /** Token counts for one billed API response. */
35
39
  export interface TokenBundle {
36
40
  inputTokens: number;
@@ -16,11 +16,20 @@
16
16
  * Price families, matched by LONGEST normalized-id prefix so dated ids like
17
17
  * `claude-sonnet-4-6-20260115` resolve without per-snapshot entries.
18
18
  *
19
- * The Claude 5 tier (fable/mythos) is intentionally absent until Anthropic
20
- * publishes rates absent means "unpriced", never "guessed".
19
+ * Opus pricing split (#41): Opus 4.0/4.1 were $15/$75, but Opus 4.5 dropped
20
+ * the tier to $5/$25 and 4.6/4.7/4.8 kept it. The bare 'claude-opus-4' entry
21
+ * stays at legacy rates for 4.0/4.1; the longer per-version prefixes win for
22
+ * 4.5+ via longest-prefix matching.
21
23
  */
22
24
  const PRICE_FAMILIES = {
25
+ 'claude-fable-5': { in: 10, out: 50, cacheWrite: 12.5, cacheWrite1h: 20, cacheRead: 1 },
26
+ 'claude-mythos-5': { in: 10, out: 50, cacheWrite: 12.5, cacheWrite1h: 20, cacheRead: 1 },
23
27
  'claude-opus-4': { in: 15, out: 75, cacheWrite: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
28
+ 'claude-opus-4-5': { in: 5, out: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
29
+ 'claude-opus-4-6': { in: 5, out: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
30
+ 'claude-opus-4-7': { in: 5, out: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
31
+ 'claude-opus-4-8': { in: 5, out: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
32
+ 'claude-sonnet-5': { in: 3, out: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
24
33
  'claude-sonnet-4': { in: 3, out: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
25
34
  'claude-haiku-4': { in: 1, out: 5, cacheWrite: 1.25, cacheWrite1h: 2, cacheRead: 0.1 },
26
35
  'claude-3-7-sonnet': { in: 3, out: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
@@ -29,6 +38,15 @@ const PRICE_FAMILIES = {
29
38
  'claude-3-opus': { in: 15, out: 75, cacheWrite: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
30
39
  'claude-3-haiku': { in: 0.25, out: 1.25, cacheWrite: 0.3, cacheWrite1h: 0.5, cacheRead: 0.03 },
31
40
  };
41
+ const PROMOS = [
42
+ {
43
+ // Sonnet 5 introductory pricing: $2/$10 per Mtok through 2026-08-31.
44
+ prefix: 'claude-sonnet-5',
45
+ fromMs: Date.UTC(2026, 5, 30), // launched 2026-06-30
46
+ untilMs: Date.UTC(2026, 7, 31, 23, 59, 59, 999),
47
+ price: { in: 2, out: 10, cacheWrite: 2.5, cacheWrite1h: 4, cacheRead: 0.2 },
48
+ },
49
+ ];
32
50
  /**
33
51
  * Reduce a transcript/gateway model id to the bare Anthropic id:
34
52
  * 'anthropic/claude-opus-4-8' → 'claude-opus-4-8'
@@ -47,9 +65,20 @@ export function normalizeTranscriptModelId(raw) {
47
65
  id = id.replace(/-v\d+$/, ''); // Bedrock '-v2'
48
66
  return id;
49
67
  }
50
- /** Longest-prefix price lookup; undefined = unpriced (report it, don't guess). */
51
- export function resolveTranscriptPrice(rawModelId) {
68
+ /**
69
+ * Longest-prefix price lookup; undefined = unpriced (report it, don't guess).
70
+ * Pass the transcript entry's timestamp as `atMs` to get date-ranged promo
71
+ * rates (e.g. Sonnet 5 intro pricing); omitted → sticker price.
72
+ */
73
+ export function resolveTranscriptPrice(rawModelId, atMs) {
52
74
  const id = normalizeTranscriptModelId(rawModelId);
75
+ if (atMs !== undefined) {
76
+ for (const promo of PROMOS) {
77
+ if (id.startsWith(promo.prefix) && atMs >= promo.fromMs && atMs <= promo.untilMs) {
78
+ return promo.price;
79
+ }
80
+ }
81
+ }
53
82
  let best;
54
83
  let bestLen = 0;
55
84
  for (const [prefix, price] of Object.entries(PRICE_FAMILIES)) {
@@ -182,7 +182,7 @@ function toUsageEvent(line, project, seen, unpriced) {
182
182
  const cacheWrite1hTokens = usage.cache_creation?.ephemeral_1h_input_tokens ?? 0;
183
183
  let costUsd;
184
184
  let costSource;
185
- const price = resolveTranscriptPrice(model);
185
+ const price = resolveTranscriptPrice(model, ts.getTime());
186
186
  if (typeof line.costUSD === 'number') {
187
187
  costUsd = line.costUSD;
188
188
  costSource = 'transcript';
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmdo/cli",
3
- "version": "1.27.0",
3
+ "version": "1.29.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",
@@ -83,7 +83,7 @@
83
83
  "test:pattern-store": "npx tsx src/transfer/store/tests/standalone-test.ts",
84
84
  "postinstall": "node ./scripts/postinstall.cjs",
85
85
  "build:standalone": "node scripts/build-standalone.mjs",
86
- "prepublishOnly": "node -e \"if (process.env.npm_command === 'publish') { console.error('\\n@swarmdo/cli must be published from the self-contained staging dir (file:../ siblings do not ship from here):\\n\\n npm run build && npm run build:standalone && npm publish dist-standalone/\\n'); process.exit(1); } // pnpm runs prepublishOnly when packing local directory deps during install \u2014 that path must not fail\""
86
+ "prepublishOnly": "node -e \"if (process.env.npm_command === 'publish') { console.error('\\n@swarmdo/cli must be published from the self-contained staging dir (file:../ siblings do not ship from here):\\n\\n npm run build && npm run build:standalone && npm publish dist-standalone/\\n'); process.exit(1); } // pnpm runs prepublishOnly when packing local directory deps during install that path must not fail\""
87
87
  },
88
88
  "devDependencies": {
89
89
  "@types/better-sqlite3": "^7.6.0",