@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.
- package/LICENSE +21 -0
- package/README.md +1014 -0
- package/bin/compose.js +1515 -0
- package/dist/assets/_baseUniq-CQwX6VLz.js +1 -0
- package/dist/assets/arc-SxJ2J1sh.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-BykunY1F.js +36 -0
- package/dist/assets/blockDiagram-DXYQGD6D-ohAKBOUw.js +132 -0
- package/dist/assets/c4Diagram-AHTNJAMY-DBDC3ENB.js +10 -0
- package/dist/assets/channel-DGElom1e.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-Cv93Z7uM.js +1 -0
- package/dist/assets/chunk-4TB4RGXK-DE0WBDkj.js +206 -0
- package/dist/assets/chunk-55IACEB6-CE1EXenG.js +1 -0
- package/dist/assets/chunk-EDXVE4YY-DA7Ana6H.js +1 -0
- package/dist/assets/chunk-FMBD7UC4-CTDIPA3p.js +15 -0
- package/dist/assets/chunk-OYMX7WX6-uGBaPaTX.js +231 -0
- package/dist/assets/chunk-QZHKN3VN-CYlnXuUO.js +1 -0
- package/dist/assets/chunk-YZCP3GAM-ojGkzcZK.js +1 -0
- package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +1 -0
- package/dist/assets/clone-DUJKJXd7.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-Bktn9hL-.js +1 -0
- package/dist/assets/dagre-KV5264BT-DFaSzuRF.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-5BDNPKRD-DnfmDzEm.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-Bm8W9YnG.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-B5-TSKvp.js +43 -0
- package/dist/assets/diagram-TYMM5635-ls4rqlky.js +24 -0
- package/dist/assets/erDiagram-SMLLAGMA-giG6WO-r.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-XvlUuz-7.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-hLBV57oV.js +292 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js +106 -0
- package/dist/assets/graph-D0Cfv00Y.js +1 -0
- package/dist/assets/index-CUd6pFGF.css +1 -0
- package/dist/assets/index-DReRlzZI.js +1144 -0
- package/dist/assets/infoDiagram-42DDH7IO-DbqRsOo3.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-UXIWVN3A-DnCdx7zb.js +70 -0
- package/dist/assets/journeyDiagram-VCZTEJTY-CfD7eNcP.js +139 -0
- package/dist/assets/kanban-definition-6JOO6SKY-BYaO9-mK.js +89 -0
- package/dist/assets/katex-DkKDou_j.js +257 -0
- package/dist/assets/layout-Bj72wOEB.js +1 -0
- package/dist/assets/linear-BRFo114D.js +1 -0
- package/dist/assets/min-GCHnKlJS.js +1 -0
- package/dist/assets/mindmap-definition-QFDTVHPH-n0PMebY4.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pieDiagram-DEJITSTG-pN4CljHF.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-DNoAy8-D.js +7 -0
- package/dist/assets/requirementDiagram-MS252O5E-BhtY05PT.js +84 -0
- package/dist/assets/sankeyDiagram-XADWPNL6-B6AD-16A.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-DShHM-uk.js +157 -0
- package/dist/assets/stateDiagram-FHFEXIEX-DMxn7HTo.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +1 -0
- package/dist/assets/timeline-definition-GMOUNBTQ-Cdu6uq52.js +120 -0
- package/dist/assets/vennDiagram-DHZGUBPP-CpK29iRe.js +34 -0
- package/dist/assets/wardley-RL74JXVD-BQgSkdcO.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-DJHYev6O.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-1d75pbaO.js +7 -0
- package/dist/index.html +30 -0
- package/lib/agent-chains.js +65 -0
- package/lib/agent-string.js +86 -0
- package/lib/budget-ledger.js +86 -0
- package/lib/build-all.js +162 -0
- package/lib/build-dag.js +120 -0
- package/lib/build-stream-writer.js +190 -0
- package/lib/build.js +2997 -0
- package/lib/capability-checker.js +53 -0
- package/lib/cert-inject.js +38 -0
- package/lib/cli-progress.js +483 -0
- package/lib/constants.js +69 -0
- package/lib/cross-layer-audit.js +84 -0
- package/lib/debug-discipline.js +173 -0
- package/lib/feature-json.js +106 -0
- package/lib/gate-prompt.js +291 -0
- package/lib/gate-tiers.js +194 -0
- package/lib/health-history.js +119 -0
- package/lib/health-score.js +227 -0
- package/lib/ideabox.js +570 -0
- package/lib/import.js +244 -0
- package/lib/migrate-roadmap.js +94 -0
- package/lib/model-pricing.js +67 -0
- package/lib/new.js +413 -0
- package/lib/pipeline-cli.js +489 -0
- package/lib/plan-parser.js +103 -0
- package/lib/qa-scoping.js +474 -0
- package/lib/questionnaire.js +200 -0
- package/lib/resolve-port.js +7 -0
- package/lib/result-normalizer.js +349 -0
- package/lib/review-lenses.js +166 -0
- package/lib/roadmap-gen.js +210 -0
- package/lib/roadmap-parser.js +176 -0
- package/lib/server-probe.js +23 -0
- package/lib/staleness.js +87 -0
- package/lib/step-prompt.js +260 -0
- package/lib/step-validator.js +49 -0
- package/lib/stratum-mcp-client.js +365 -0
- package/lib/team-flag.js +46 -0
- package/lib/test-bootstrap.js +401 -0
- package/lib/triage.js +274 -0
- package/lib/vision-writer.js +391 -0
- package/package.json +111 -0
- package/pipelines/bug-fix.stratum.yaml +230 -0
- package/pipelines/build.stratum.yaml +498 -0
- package/pipelines/content.stratum.yaml +112 -0
- package/pipelines/coverage-sweep.stratum.yaml +52 -0
- package/pipelines/refactor.stratum.yaml +169 -0
- package/pipelines/research.stratum.yaml +88 -0
- package/pipelines/review-fix.stratum.yaml +109 -0
- package/presets/team-feature.stratum.yaml +105 -0
- package/presets/team-research.stratum.yaml +108 -0
- package/presets/team-review.stratum.yaml +106 -0
- package/scripts/agent-activity-hook.sh +31 -0
- package/scripts/agent-error-hook.sh +28 -0
- package/scripts/analyze-orphans.mjs +50 -0
- package/scripts/find-orphans.mjs +26 -0
- package/scripts/fix-phases.mjs +49 -0
- package/scripts/generate-stratum-spec.mjs +137 -0
- package/scripts/import-roadmap.mjs +116 -0
- package/scripts/phase-audit.mjs +33 -0
- package/scripts/run-pipeline.mjs +314 -0
- package/scripts/session-end-hook.sh +18 -0
- package/scripts/session-start-hook.sh +38 -0
- package/scripts/vision-hook.sh +104 -0
- package/scripts/vision-track.mjs +554 -0
- package/scripts/wire-all-orphans.mjs +108 -0
- package/scripts/wire-orphans.mjs +164 -0
- package/server/activity-routes.js +123 -0
- package/server/agent-health.js +197 -0
- package/server/agent-hooks.js +102 -0
- package/server/agent-mcp.js +10 -0
- package/server/agent-registry.js +95 -0
- package/server/agent-server.js +290 -0
- package/server/agent-spawn.js +251 -0
- package/server/agent-templates.js +77 -0
- package/server/artifact-manager.js +247 -0
- package/server/artifact-templates/architecture.md +28 -0
- package/server/artifact-templates/blueprint.md +21 -0
- package/server/artifact-templates/design.md +36 -0
- package/server/artifact-templates/plan.md +25 -0
- package/server/artifact-templates/prd.md +43 -0
- package/server/artifact-templates/report.md +40 -0
- package/server/block-tracker.js +90 -0
- package/server/build-stream-bridge.js +502 -0
- package/server/coalescing-buffer.js +46 -0
- package/server/compose-mcp-tools.js +479 -0
- package/server/compose-mcp.js +324 -0
- package/server/connectors/agent-connector.js +78 -0
- package/server/connectors/claude-sdk-connector.js +198 -0
- package/server/connectors/codex-connector.js +240 -0
- package/server/connectors/connector-discovery.js +18 -0
- package/server/connectors/connector-runtime.js +13 -0
- package/server/connectors/opencode-connector.js +200 -0
- package/server/design-routes.js +540 -0
- package/server/design-session.js +161 -0
- package/server/feature-scan.js +593 -0
- package/server/file-watcher.js +284 -0
- package/server/find-root.js +29 -0
- package/server/graph-export.js +343 -0
- package/server/ideabox-cache.js +77 -0
- package/server/ideabox-routes.js +294 -0
- package/server/index.js +156 -0
- package/server/model-tiers.js +49 -0
- package/server/pipeline-routes.js +288 -0
- package/server/policy-evaluator.js +36 -0
- package/server/project-root.js +122 -0
- package/server/security.js +23 -0
- package/server/session-manager.js +403 -0
- package/server/session-routes.js +190 -0
- package/server/session-store.js +107 -0
- package/server/settings-routes.js +35 -0
- package/server/settings-store.js +234 -0
- package/server/stratum-api.js +102 -0
- package/server/stratum-client.js +192 -0
- package/server/stratum-sync.js +193 -0
- package/server/summarizer.js +139 -0
- package/server/supervisor.js +196 -0
- package/server/vision-routes.js +668 -0
- package/server/vision-server.js +393 -0
- package/server/vision-store.js +360 -0
- package/server/vision-utils.js +179 -0
- package/server/worktree-gc.js +137 -0
- 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
|
+
}
|