@smartmemory/compose 0.1.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 (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1014 -0
  3. package/bin/compose.js +1515 -0
  4. package/dist/assets/_baseUniq-CQwX6VLz.js +1 -0
  5. package/dist/assets/arc-SxJ2J1sh.js +1 -0
  6. package/dist/assets/architectureDiagram-Q4EWVU46-BykunY1F.js +36 -0
  7. package/dist/assets/blockDiagram-DXYQGD6D-ohAKBOUw.js +132 -0
  8. package/dist/assets/c4Diagram-AHTNJAMY-DBDC3ENB.js +10 -0
  9. package/dist/assets/channel-DGElom1e.js +1 -0
  10. package/dist/assets/chunk-4BX2VUAB-Cv93Z7uM.js +1 -0
  11. package/dist/assets/chunk-4TB4RGXK-DE0WBDkj.js +206 -0
  12. package/dist/assets/chunk-55IACEB6-CE1EXenG.js +1 -0
  13. package/dist/assets/chunk-EDXVE4YY-DA7Ana6H.js +1 -0
  14. package/dist/assets/chunk-FMBD7UC4-CTDIPA3p.js +15 -0
  15. package/dist/assets/chunk-OYMX7WX6-uGBaPaTX.js +231 -0
  16. package/dist/assets/chunk-QZHKN3VN-CYlnXuUO.js +1 -0
  17. package/dist/assets/chunk-YZCP3GAM-ojGkzcZK.js +1 -0
  18. package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +1 -0
  19. package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +1 -0
  20. package/dist/assets/clone-DUJKJXd7.js +1 -0
  21. package/dist/assets/cose-bilkent-S5V4N54A-Bktn9hL-.js +1 -0
  22. package/dist/assets/dagre-KV5264BT-DFaSzuRF.js +4 -0
  23. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  24. package/dist/assets/diagram-5BDNPKRD-DnfmDzEm.js +10 -0
  25. package/dist/assets/diagram-G4DWMVQ6-Bm8W9YnG.js +24 -0
  26. package/dist/assets/diagram-MMDJMWI5-B5-TSKvp.js +43 -0
  27. package/dist/assets/diagram-TYMM5635-ls4rqlky.js +24 -0
  28. package/dist/assets/erDiagram-SMLLAGMA-giG6WO-r.js +85 -0
  29. package/dist/assets/flowDiagram-DWJPFMVM-XvlUuz-7.js +162 -0
  30. package/dist/assets/ganttDiagram-T4ZO3ILL-hLBV57oV.js +292 -0
  31. package/dist/assets/gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js +106 -0
  32. package/dist/assets/graph-D0Cfv00Y.js +1 -0
  33. package/dist/assets/index-CUd6pFGF.css +1 -0
  34. package/dist/assets/index-DReRlzZI.js +1144 -0
  35. package/dist/assets/infoDiagram-42DDH7IO-DbqRsOo3.js +2 -0
  36. package/dist/assets/init-Gi6I4Gst.js +1 -0
  37. package/dist/assets/ishikawaDiagram-UXIWVN3A-DnCdx7zb.js +70 -0
  38. package/dist/assets/journeyDiagram-VCZTEJTY-CfD7eNcP.js +139 -0
  39. package/dist/assets/kanban-definition-6JOO6SKY-BYaO9-mK.js +89 -0
  40. package/dist/assets/katex-DkKDou_j.js +257 -0
  41. package/dist/assets/layout-Bj72wOEB.js +1 -0
  42. package/dist/assets/linear-BRFo114D.js +1 -0
  43. package/dist/assets/min-GCHnKlJS.js +1 -0
  44. package/dist/assets/mindmap-definition-QFDTVHPH-n0PMebY4.js +96 -0
  45. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  46. package/dist/assets/pieDiagram-DEJITSTG-pN4CljHF.js +30 -0
  47. package/dist/assets/quadrantDiagram-34T5L4WZ-DNoAy8-D.js +7 -0
  48. package/dist/assets/requirementDiagram-MS252O5E-BhtY05PT.js +84 -0
  49. package/dist/assets/sankeyDiagram-XADWPNL6-B6AD-16A.js +10 -0
  50. package/dist/assets/sequenceDiagram-FGHM5R23-DShHM-uk.js +157 -0
  51. package/dist/assets/stateDiagram-FHFEXIEX-DMxn7HTo.js +1 -0
  52. package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +1 -0
  53. package/dist/assets/timeline-definition-GMOUNBTQ-Cdu6uq52.js +120 -0
  54. package/dist/assets/vennDiagram-DHZGUBPP-CpK29iRe.js +34 -0
  55. package/dist/assets/wardley-RL74JXVD-BQgSkdcO.js +162 -0
  56. package/dist/assets/wardleyDiagram-NUSXRM2D-DJHYev6O.js +20 -0
  57. package/dist/assets/xychartDiagram-5P7HB3ND-1d75pbaO.js +7 -0
  58. package/dist/index.html +30 -0
  59. package/lib/agent-chains.js +65 -0
  60. package/lib/agent-string.js +86 -0
  61. package/lib/budget-ledger.js +86 -0
  62. package/lib/build-all.js +162 -0
  63. package/lib/build-dag.js +120 -0
  64. package/lib/build-stream-writer.js +190 -0
  65. package/lib/build.js +2997 -0
  66. package/lib/capability-checker.js +53 -0
  67. package/lib/cert-inject.js +38 -0
  68. package/lib/cli-progress.js +483 -0
  69. package/lib/constants.js +69 -0
  70. package/lib/cross-layer-audit.js +84 -0
  71. package/lib/debug-discipline.js +173 -0
  72. package/lib/feature-json.js +106 -0
  73. package/lib/gate-prompt.js +291 -0
  74. package/lib/gate-tiers.js +194 -0
  75. package/lib/health-history.js +119 -0
  76. package/lib/health-score.js +227 -0
  77. package/lib/ideabox.js +570 -0
  78. package/lib/import.js +244 -0
  79. package/lib/migrate-roadmap.js +94 -0
  80. package/lib/model-pricing.js +67 -0
  81. package/lib/new.js +413 -0
  82. package/lib/pipeline-cli.js +489 -0
  83. package/lib/plan-parser.js +103 -0
  84. package/lib/qa-scoping.js +474 -0
  85. package/lib/questionnaire.js +200 -0
  86. package/lib/resolve-port.js +7 -0
  87. package/lib/result-normalizer.js +349 -0
  88. package/lib/review-lenses.js +166 -0
  89. package/lib/roadmap-gen.js +210 -0
  90. package/lib/roadmap-parser.js +176 -0
  91. package/lib/server-probe.js +23 -0
  92. package/lib/staleness.js +87 -0
  93. package/lib/step-prompt.js +260 -0
  94. package/lib/step-validator.js +49 -0
  95. package/lib/stratum-mcp-client.js +365 -0
  96. package/lib/team-flag.js +46 -0
  97. package/lib/test-bootstrap.js +401 -0
  98. package/lib/triage.js +274 -0
  99. package/lib/vision-writer.js +391 -0
  100. package/package.json +111 -0
  101. package/pipelines/bug-fix.stratum.yaml +230 -0
  102. package/pipelines/build.stratum.yaml +498 -0
  103. package/pipelines/content.stratum.yaml +112 -0
  104. package/pipelines/coverage-sweep.stratum.yaml +52 -0
  105. package/pipelines/refactor.stratum.yaml +169 -0
  106. package/pipelines/research.stratum.yaml +88 -0
  107. package/pipelines/review-fix.stratum.yaml +109 -0
  108. package/presets/team-feature.stratum.yaml +105 -0
  109. package/presets/team-research.stratum.yaml +108 -0
  110. package/presets/team-review.stratum.yaml +106 -0
  111. package/scripts/agent-activity-hook.sh +31 -0
  112. package/scripts/agent-error-hook.sh +28 -0
  113. package/scripts/analyze-orphans.mjs +50 -0
  114. package/scripts/find-orphans.mjs +26 -0
  115. package/scripts/fix-phases.mjs +49 -0
  116. package/scripts/generate-stratum-spec.mjs +137 -0
  117. package/scripts/import-roadmap.mjs +116 -0
  118. package/scripts/phase-audit.mjs +33 -0
  119. package/scripts/run-pipeline.mjs +314 -0
  120. package/scripts/session-end-hook.sh +18 -0
  121. package/scripts/session-start-hook.sh +38 -0
  122. package/scripts/vision-hook.sh +104 -0
  123. package/scripts/vision-track.mjs +554 -0
  124. package/scripts/wire-all-orphans.mjs +108 -0
  125. package/scripts/wire-orphans.mjs +164 -0
  126. package/server/activity-routes.js +123 -0
  127. package/server/agent-health.js +197 -0
  128. package/server/agent-hooks.js +102 -0
  129. package/server/agent-mcp.js +10 -0
  130. package/server/agent-registry.js +95 -0
  131. package/server/agent-server.js +290 -0
  132. package/server/agent-spawn.js +251 -0
  133. package/server/agent-templates.js +77 -0
  134. package/server/artifact-manager.js +247 -0
  135. package/server/artifact-templates/architecture.md +28 -0
  136. package/server/artifact-templates/blueprint.md +21 -0
  137. package/server/artifact-templates/design.md +36 -0
  138. package/server/artifact-templates/plan.md +25 -0
  139. package/server/artifact-templates/prd.md +43 -0
  140. package/server/artifact-templates/report.md +40 -0
  141. package/server/block-tracker.js +90 -0
  142. package/server/build-stream-bridge.js +502 -0
  143. package/server/coalescing-buffer.js +46 -0
  144. package/server/compose-mcp-tools.js +479 -0
  145. package/server/compose-mcp.js +324 -0
  146. package/server/connectors/agent-connector.js +78 -0
  147. package/server/connectors/claude-sdk-connector.js +198 -0
  148. package/server/connectors/codex-connector.js +240 -0
  149. package/server/connectors/connector-discovery.js +18 -0
  150. package/server/connectors/connector-runtime.js +13 -0
  151. package/server/connectors/opencode-connector.js +200 -0
  152. package/server/design-routes.js +540 -0
  153. package/server/design-session.js +161 -0
  154. package/server/feature-scan.js +593 -0
  155. package/server/file-watcher.js +284 -0
  156. package/server/find-root.js +29 -0
  157. package/server/graph-export.js +343 -0
  158. package/server/ideabox-cache.js +77 -0
  159. package/server/ideabox-routes.js +294 -0
  160. package/server/index.js +156 -0
  161. package/server/model-tiers.js +49 -0
  162. package/server/pipeline-routes.js +288 -0
  163. package/server/policy-evaluator.js +36 -0
  164. package/server/project-root.js +122 -0
  165. package/server/security.js +23 -0
  166. package/server/session-manager.js +403 -0
  167. package/server/session-routes.js +190 -0
  168. package/server/session-store.js +107 -0
  169. package/server/settings-routes.js +35 -0
  170. package/server/settings-store.js +234 -0
  171. package/server/stratum-api.js +102 -0
  172. package/server/stratum-client.js +192 -0
  173. package/server/stratum-sync.js +193 -0
  174. package/server/summarizer.js +139 -0
  175. package/server/supervisor.js +196 -0
  176. package/server/vision-routes.js +668 -0
  177. package/server/vision-server.js +393 -0
  178. package/server/vision-store.js +360 -0
  179. package/server/vision-utils.js +179 -0
  180. package/server/worktree-gc.js +137 -0
  181. package/templates/ROADMAP.md +46 -0
package/lib/import.js ADDED
@@ -0,0 +1,244 @@
1
+ /**
2
+ * import.js — Scan an existing project and generate a structured analysis.
3
+ *
4
+ * Produces docs/discovery/project-analysis.md which is automatically
5
+ * picked up by compose new/build as context for agents.
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
9
+ import { join, relative, extname, basename } from 'node:path';
10
+
11
+ import { runAndNormalize } from './result-normalizer.js';
12
+ import { ClaudeSDKConnector } from '../server/connectors/claude-sdk-connector.js';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // File tree scanner
16
+ // ---------------------------------------------------------------------------
17
+
18
+ const IGNORE_DIRS = new Set([
19
+ 'node_modules', '.git', '.compose', 'dist', 'build', 'coverage',
20
+ '.next', '.nuxt', '__pycache__', '.venv', 'venv', 'target',
21
+ '.cache', '.parcel-cache', '.turbo',
22
+ ]);
23
+
24
+ const IGNORE_FILES = new Set([
25
+ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
26
+ '.DS_Store', 'Thumbs.db',
27
+ ]);
28
+
29
+ function walkTree(dir, root, maxDepth = 4, depth = 0) {
30
+ const entries = [];
31
+ if (depth > maxDepth) return entries;
32
+
33
+ let items;
34
+ try { items = readdirSync(dir); } catch { return entries; }
35
+
36
+ for (const item of items) {
37
+ if (IGNORE_DIRS.has(item) || IGNORE_FILES.has(item)) continue;
38
+ if (item.startsWith('.') && depth === 0 && item !== '.env.example') continue;
39
+
40
+ const fullPath = join(dir, item);
41
+ const relPath = relative(root, fullPath);
42
+ let stat;
43
+ try { stat = statSync(fullPath); } catch { continue; }
44
+
45
+ if (stat.isDirectory()) {
46
+ entries.push({ path: relPath, type: 'dir' });
47
+ entries.push(...walkTree(fullPath, root, maxDepth, depth + 1));
48
+ } else {
49
+ entries.push({ path: relPath, type: 'file', size: stat.size });
50
+ }
51
+ }
52
+ return entries;
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Key file reader
57
+ // ---------------------------------------------------------------------------
58
+
59
+ const KEY_FILES = [
60
+ 'README.md', 'README', 'readme.md',
61
+ 'package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod', 'Gemfile',
62
+ 'Makefile', 'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml',
63
+ '.env.example',
64
+ 'tsconfig.json', 'vite.config.js', 'vite.config.ts',
65
+ 'CLAUDE.md', 'AGENTS.md',
66
+ 'ROADMAP.md', 'CHANGELOG.md',
67
+ ];
68
+
69
+ const KEY_DIRS = ['docs', 'src', 'lib', 'bin', 'test', 'tests', 'spec', 'api', 'scripts'];
70
+
71
+ function readKeyFiles(cwd, maxContentSize = 8000) {
72
+ const files = {};
73
+
74
+ // Read known key files
75
+ for (const name of KEY_FILES) {
76
+ const p = join(cwd, name);
77
+ if (existsSync(p)) {
78
+ const content = readFileSync(p, 'utf-8');
79
+ files[name] = content.length > maxContentSize
80
+ ? content.slice(0, maxContentSize) + '\n...(truncated)'
81
+ : content;
82
+ }
83
+ }
84
+
85
+ // Read top-level files in key directories (just filenames + first lines)
86
+ for (const dir of KEY_DIRS) {
87
+ const dirPath = join(cwd, dir);
88
+ if (!existsSync(dirPath)) continue;
89
+ try {
90
+ const items = readdirSync(dirPath);
91
+ for (const item of items.slice(0, 30)) {
92
+ const p = join(dirPath, item);
93
+ let stat;
94
+ try { stat = statSync(p); } catch { continue; }
95
+ if (!stat.isFile()) continue;
96
+
97
+ const relPath = join(dir, item);
98
+ const content = readFileSync(p, 'utf-8');
99
+ // Include first 40 lines to give structure context
100
+ const preview = content.split('\n').slice(0, 40).join('\n');
101
+ files[relPath] = preview.length < content.length
102
+ ? preview + '\n...(truncated)'
103
+ : preview;
104
+ }
105
+ } catch { /* skip */ }
106
+ }
107
+
108
+ return files;
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Main
113
+ // ---------------------------------------------------------------------------
114
+
115
+ /**
116
+ * Scan a project and generate a structured analysis.
117
+ *
118
+ * @param {object} opts
119
+ * @param {string} [opts.cwd] - Working directory
120
+ * @param {Function} [opts.connectorFactory] - Override connector (for testing)
121
+ */
122
+ export async function runImport(opts = {}) {
123
+ const cwd = opts.cwd ?? process.cwd();
124
+ const getConnector = opts.connectorFactory ?? (() => new ClaudeSDKConnector({ cwd }));
125
+
126
+ console.log('Scanning project...\n');
127
+
128
+ // 1. Build file tree
129
+ const tree = walkTree(cwd, cwd);
130
+ const fileCount = tree.filter(e => e.type === 'file').length;
131
+ const dirCount = tree.filter(e => e.type === 'dir').length;
132
+ console.log(` ${fileCount} files, ${dirCount} directories`);
133
+
134
+ // 2. Read key files
135
+ const keyFiles = readKeyFiles(cwd);
136
+ console.log(` ${Object.keys(keyFiles).length} key files read`);
137
+
138
+ // 3. Build tree string
139
+ const treeStr = tree.map(e => {
140
+ if (e.type === 'dir') return `${e.path}/`;
141
+ const kb = (e.size / 1024).toFixed(1);
142
+ return `${e.path} (${kb}kb)`;
143
+ }).join('\n');
144
+
145
+ // 4. Build key file contents
146
+ const keyFileStr = Object.entries(keyFiles)
147
+ .map(([name, content]) => `--- ${name} ---\n${content}`)
148
+ .join('\n\n');
149
+
150
+ // 5. Dispatch to claude for analysis
151
+ console.log('\nAnalyzing...\n');
152
+
153
+ const prompt = `You are analyzing an existing software project to produce a structured analysis document.
154
+
155
+ ## Project file tree
156
+ \`\`\`
157
+ ${treeStr}
158
+ \`\`\`
159
+
160
+ ## Key file contents
161
+ ${keyFileStr}
162
+
163
+ ## Task
164
+
165
+ Write a comprehensive project analysis to \`docs/discovery/project-analysis.md\` with these sections:
166
+
167
+ ### 1. Project Overview
168
+ - What this project does (inferred from code, README, configs)
169
+ - Primary language/framework/runtime
170
+ - Current maturity (prototype, MVP, production)
171
+
172
+ ### 2. Architecture
173
+ - High-level component map (what talks to what)
174
+ - Key directories and what they contain
175
+ - Entry points (CLI, server, library exports)
176
+ - External dependencies and what they're used for
177
+
178
+ ### 3. Feature Inventory
179
+ For each distinct feature/capability already built:
180
+ - Feature name and suggested code (e.g., AUTH-1, API-2)
181
+ - Status: working, partial, stub
182
+ - Key files involved
183
+ - Test coverage: tested, untested, partially tested
184
+
185
+ ### 4. Patterns & Conventions
186
+ - Code style (ESM/CJS, naming, file organization)
187
+ - Testing approach (framework, fixtures, mocks)
188
+ - Build/deploy setup
189
+ - Configuration approach
190
+
191
+ ### 5. Gaps & Opportunities
192
+ - Missing tests
193
+ - Missing docs
194
+ - Incomplete features
195
+ - Technical debt
196
+
197
+ ### 6. Suggested Roadmap
198
+ Based on the analysis, suggest a phased roadmap with feature codes.
199
+ Use the format:
200
+ | # | Feature | Item | Status |
201
+ |---|---------|------|--------|
202
+ | 1 | CODE-1 | Description | COMPLETE/PARTIAL/PLANNED |
203
+
204
+ Write the full analysis to \`docs/discovery/project-analysis.md\`.
205
+
206
+ Return JSON: { "summary": string, "features": [{"code": string, "name": string, "status": string}], "artifact": "docs/discovery/project-analysis.md" }`;
207
+
208
+ const connector = getConnector();
209
+ const { result } = await runAndNormalize(connector, prompt, {
210
+ output_contract: 'AnalysisResult',
211
+ output_fields: [
212
+ { name: 'summary', type: 'string' },
213
+ { name: 'features', type: 'array' },
214
+ { name: 'artifact', type: 'string' },
215
+ ],
216
+ });
217
+
218
+ // 6. Verify the analysis was written
219
+ const analysisPath = join(cwd, 'docs', 'discovery', 'project-analysis.md');
220
+ if (!existsSync(analysisPath)) {
221
+ // Agent may not have written it — write a fallback from the result
222
+ mkdirSync(join(cwd, 'docs', 'discovery'), { recursive: true });
223
+ if (result?.summary) {
224
+ writeFileSync(analysisPath, `# Project Analysis\n\n${result.summary}\n`);
225
+ console.log(`Wrote fallback analysis to ${relative(cwd, analysisPath)}`);
226
+ }
227
+ } else {
228
+ console.log(`Analysis written to ${relative(cwd, analysisPath)}`);
229
+ }
230
+
231
+ // 7. Report features found
232
+ if (result?.features?.length) {
233
+ console.log(`\nFeatures identified:`);
234
+ for (const f of result.features) {
235
+ const status = f.status ?? 'unknown';
236
+ console.log(` ${f.code.padEnd(10)} ${f.name} (${status})`);
237
+ }
238
+ }
239
+
240
+ console.log('\nNext steps:');
241
+ console.log(' compose new <name> "intent" # kickoff with this context');
242
+ console.log(' compose feature <CODE> "desc" # add a specific feature');
243
+ console.log(' compose build <CODE> # build a feature');
244
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * migrate-roadmap.js — One-time migration from ROADMAP.md to feature.json files.
3
+ *
4
+ * Reads the existing ROADMAP.md, extracts feature entries using roadmap-parser,
5
+ * and creates feature.json files for each real (non-anonymous) feature.
6
+ */
7
+
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { parseRoadmap } from './roadmap-parser.js';
11
+ import { readFeature, writeFeature } from './feature-json.js';
12
+
13
+ /**
14
+ * Migrate ROADMAP.md entries to feature.json files.
15
+ *
16
+ * @param {string} cwd - Project root
17
+ * @param {object} [opts]
18
+ * @param {string} [opts.featuresDir] - Relative features path
19
+ * @param {boolean} [opts.dryRun] - Print what would be created without writing
20
+ * @param {boolean} [opts.overwrite] - Overwrite existing feature.json files
21
+ * @returns {{ created: string[], skipped: string[], updated: string[] }}
22
+ */
23
+ export function migrateRoadmap(cwd, opts = {}) {
24
+ const featuresDir = opts.featuresDir ?? 'docs/features';
25
+ const roadmapPath = join(cwd, 'ROADMAP.md');
26
+
27
+ if (!existsSync(roadmapPath)) {
28
+ throw new Error(`No ROADMAP.md found at ${roadmapPath}`);
29
+ }
30
+
31
+ const text = readFileSync(roadmapPath, 'utf-8');
32
+ const entries = parseRoadmap(text);
33
+
34
+ const created = [];
35
+ const skipped = [];
36
+ const updated = [];
37
+
38
+ for (const entry of entries) {
39
+ // Skip anonymous entries
40
+ if (entry.code.startsWith('_anon_')) continue;
41
+
42
+ const existing = readFeature(cwd, entry.code, featuresDir);
43
+
44
+ if (existing && !opts.overwrite) {
45
+ skipped.push(entry.code);
46
+ continue;
47
+ }
48
+
49
+ // Extract phase name (strip milestone nesting for clean grouping)
50
+ const phase = extractPhase(entry.phaseId);
51
+
52
+ const feature = {
53
+ code: entry.code,
54
+ description: entry.description,
55
+ status: entry.status,
56
+ phase,
57
+ position: entry.position,
58
+ ...(existing ?? {}),
59
+ // Always update status from ROADMAP (source of truth during migration)
60
+ status: entry.status,
61
+ };
62
+
63
+ // Set created date if new
64
+ if (!feature.created) {
65
+ feature.created = new Date().toISOString().slice(0, 10);
66
+ }
67
+
68
+ if (opts.dryRun) {
69
+ console.log(`${existing ? 'update' : 'create'}: ${featuresDir}/${entry.code}/feature.json`);
70
+ } else {
71
+ writeFeature(cwd, feature, featuresDir);
72
+ }
73
+
74
+ if (existing) {
75
+ updated.push(entry.code);
76
+ } else {
77
+ created.push(entry.code);
78
+ }
79
+ }
80
+
81
+ return { created, skipped, updated };
82
+ }
83
+
84
+ /**
85
+ * Clean up phase ID from the parser (strip milestone nesting).
86
+ * "STRAT-1: Stratum Process Engine + Compose MVP > Milestone 2: Headless Compose Runner"
87
+ * → "STRAT-1: Stratum Process Engine + Compose MVP"
88
+ */
89
+ function extractPhase(phaseId) {
90
+ if (!phaseId) return null;
91
+ // Use the top-level phase, not the milestone
92
+ const parts = phaseId.split(' > ');
93
+ return parts[0].trim();
94
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * model-pricing.js — Token cost lookup and USD calculation.
3
+ *
4
+ * Prices are per-million tokens (MTok) as of 2025.
5
+ * Input price includes standard prompt tokens.
6
+ * Cache write tokens (cache_creation_input_tokens) are billed at 1.25x input rate.
7
+ * Cache read tokens (cache_read_input_tokens) are billed at 0.1x input rate.
8
+ */
9
+
10
+ /**
11
+ * Pricing map: modelID → { inputPerMTok, outputPerMTok } in USD.
12
+ * Keys are matched by prefix so 'claude-sonnet-4-6' matches 'claude-sonnet-4-6-20250514' etc.
13
+ */
14
+ export const MODEL_PRICING = {
15
+ 'claude-opus-4-7': { inputPerMTok: 5, outputPerMTok: 25 },
16
+ 'claude-opus-4-6': { inputPerMTok: 5, outputPerMTok: 25 },
17
+ 'claude-sonnet-4-6': { inputPerMTok: 3, outputPerMTok: 15 },
18
+ 'claude-haiku-4-5': { inputPerMTok: 1, outputPerMTok: 5 },
19
+ };
20
+
21
+ /**
22
+ * Look up pricing for a model ID.
23
+ * Tries exact match first, then prefix match (handles dated variants).
24
+ *
25
+ * @param {string} modelID
26
+ * @returns {{ inputPerMTok: number, outputPerMTok: number } | null}
27
+ */
28
+ function lookupPricing(modelID) {
29
+ if (!modelID) return null;
30
+
31
+ // Exact match
32
+ if (MODEL_PRICING[modelID]) return MODEL_PRICING[modelID];
33
+
34
+ // Prefix match — handles e.g. 'claude-sonnet-4-6-20250514'
35
+ for (const [key, pricing] of Object.entries(MODEL_PRICING)) {
36
+ if (modelID.startsWith(key)) return pricing;
37
+ }
38
+
39
+ return null;
40
+ }
41
+
42
+ /**
43
+ * Calculate the USD cost for a single agent call.
44
+ *
45
+ * @param {string} modelID
46
+ * @param {number} inputTokens Standard prompt tokens
47
+ * @param {number} outputTokens Completion tokens
48
+ * @param {number} [cacheWriteTokens] cache_creation_input_tokens (billed at 1.25x input)
49
+ * @param {number} [cacheReadTokens] cache_read_input_tokens (billed at 0.1x input)
50
+ * @returns {number} USD cost (0 for unknown models or zero-token calls)
51
+ */
52
+ export function calculateCost(modelID, inputTokens, outputTokens, cacheWriteTokens = 0, cacheReadTokens = 0) {
53
+ const pricing = lookupPricing(modelID);
54
+ if (!pricing) return 0;
55
+
56
+ const totalInput = (inputTokens ?? 0) + (cacheWriteTokens ?? 0) + (cacheReadTokens ?? 0);
57
+ const totalOutput = outputTokens ?? 0;
58
+
59
+ if (totalInput === 0 && totalOutput === 0) return 0;
60
+
61
+ const inputCost = ((inputTokens ?? 0) / 1_000_000) * pricing.inputPerMTok;
62
+ const writeCost = ((cacheWriteTokens ?? 0) / 1_000_000) * pricing.inputPerMTok * 1.25;
63
+ const readCost = ((cacheReadTokens ?? 0) / 1_000_000) * pricing.inputPerMTok * 0.1;
64
+ const outputCost = ((outputTokens ?? 0) / 1_000_000) * pricing.outputPerMTok;
65
+
66
+ return inputCost + writeCost + readCost + outputCost;
67
+ }