@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
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# agent-activity-hook.sh — PostToolUse hook that forwards tool activity to Compose server.
|
|
3
|
+
#
|
|
4
|
+
# Receives JSON on stdin from Claude Code with tool_name, tool_input, tool_response.
|
|
5
|
+
# POSTs a compact summary to the Compose server which broadcasts it via WebSocket
|
|
6
|
+
# to the Vision Tracker's agent activity feed.
|
|
7
|
+
#
|
|
8
|
+
# Runs quickly — curl with 1s timeout, backgrounded, non-blocking.
|
|
9
|
+
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
|
|
12
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
13
|
+
[ -z "$TOOL" ] && exit 0
|
|
14
|
+
|
|
15
|
+
# Extract tool_input as compact JSON
|
|
16
|
+
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
|
|
17
|
+
|
|
18
|
+
# Extract first 500 chars of tool_response (for error detection server-side)
|
|
19
|
+
RESPONSE=$(echo "$INPUT" | jq -c '.tool_response // null' | head -c 500)
|
|
20
|
+
|
|
21
|
+
# Build POST body safely with jq to avoid JSON injection
|
|
22
|
+
BODY=$(jq -n --arg tool "$TOOL" --argjson input "$TOOL_INPUT" --argjson response "$RESPONSE" \
|
|
23
|
+
'{tool: $tool, input: $input, response: $response}')
|
|
24
|
+
|
|
25
|
+
# Fire and forget — don't block the agent
|
|
26
|
+
curl -s -m 1 -X POST http://localhost:3001/api/agent/activity \
|
|
27
|
+
-H 'Content-Type: application/json' \
|
|
28
|
+
-d "$BODY" \
|
|
29
|
+
> /dev/null 2>&1 &
|
|
30
|
+
|
|
31
|
+
exit 0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# agent-error-hook.sh — PostToolUseFailure hook that forwards tool errors to Compose server.
|
|
3
|
+
#
|
|
4
|
+
# Receives JSON on stdin from Claude Code with tool_name, tool_input, error.
|
|
5
|
+
# POSTs to the Compose error endpoint for classification and UI display.
|
|
6
|
+
#
|
|
7
|
+
# Fire-and-forget, non-blocking.
|
|
8
|
+
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
|
|
11
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
12
|
+
[ -z "$TOOL" ] && exit 0
|
|
13
|
+
|
|
14
|
+
# Extract fields
|
|
15
|
+
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
|
|
16
|
+
ERROR=$(echo "$INPUT" | jq -r '.error // empty' | head -c 500)
|
|
17
|
+
|
|
18
|
+
# Build POST body safely with jq
|
|
19
|
+
BODY=$(jq -n --arg tool "$TOOL" --argjson input "$TOOL_INPUT" --arg error "$ERROR" \
|
|
20
|
+
'{tool: $tool, input: $input, error: $error}')
|
|
21
|
+
|
|
22
|
+
# Fire and forget
|
|
23
|
+
curl -s -m 1 -X POST http://localhost:3001/api/agent/error \
|
|
24
|
+
-H 'Content-Type: application/json' \
|
|
25
|
+
-d "$BODY" \
|
|
26
|
+
> /dev/null 2>&1 &
|
|
27
|
+
|
|
28
|
+
exit 0
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
const d = JSON.parse(fs.readFileSync('data/vision-state.json', 'utf8'));
|
|
3
|
+
|
|
4
|
+
const features = d.items.filter(i => i.type === 'feature');
|
|
5
|
+
const reachable = new Set(features.map(f => f.id));
|
|
6
|
+
|
|
7
|
+
let changed = true;
|
|
8
|
+
while (changed) {
|
|
9
|
+
changed = false;
|
|
10
|
+
for (const conn of d.connections) {
|
|
11
|
+
if (conn.type === 'implements' && reachable.has(conn.toId)) {
|
|
12
|
+
if (!reachable.has(conn.fromId)) {
|
|
13
|
+
reachable.add(conn.fromId);
|
|
14
|
+
changed = true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (const item of d.items) {
|
|
19
|
+
if (item.parentId && reachable.has(item.parentId)) {
|
|
20
|
+
if (!reachable.has(item.id)) {
|
|
21
|
+
reachable.add(item.id);
|
|
22
|
+
changed = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const orphans = d.items.filter(i => !reachable.has(i.id));
|
|
29
|
+
console.log(`Reachable: ${reachable.size} | Orphaned: ${orphans.length}`);
|
|
30
|
+
console.log('');
|
|
31
|
+
|
|
32
|
+
const tracks = d.items.filter(i => i.type === 'track');
|
|
33
|
+
console.log('=== TRACKS ===');
|
|
34
|
+
tracks.forEach(t => console.log(` ${t.semanticId} ${t.title}`));
|
|
35
|
+
console.log('');
|
|
36
|
+
|
|
37
|
+
console.log('=== ORPHANS ===');
|
|
38
|
+
for (const o of orphans) {
|
|
39
|
+
const conns = d.connections.filter(c => c.fromId === o.id || c.toId === o.id);
|
|
40
|
+
const targets = conns.map(c => {
|
|
41
|
+
const otherId = c.fromId === o.id ? c.toId : c.fromId;
|
|
42
|
+
const other = d.items.find(i => i.id === otherId);
|
|
43
|
+
const dir = c.fromId === o.id ? '>' : '<';
|
|
44
|
+
return `${dir}${c.type}:${other?.semanticId || other?.title?.slice(0, 25) || otherId.slice(0, 8)}`;
|
|
45
|
+
}).join(' | ');
|
|
46
|
+
const sid = (o.semanticId || o.id.slice(0, 8)).padEnd(15);
|
|
47
|
+
const typ = o.type.padEnd(10);
|
|
48
|
+
const tit = o.title.slice(0, 45).padEnd(47);
|
|
49
|
+
console.log(` ${sid} ${typ} ${tit} ${targets}`);
|
|
50
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
const d = JSON.parse(readFileSync('./data/vision-state.json', 'utf8'));
|
|
3
|
+
const items = d.items, conns = d.connections;
|
|
4
|
+
|
|
5
|
+
const childIds = new Set();
|
|
6
|
+
for (const c of conns) {
|
|
7
|
+
if (c.type === 'implements' || c.type === 'supports') childIds.add(c.fromId);
|
|
8
|
+
}
|
|
9
|
+
for (const i of items) {
|
|
10
|
+
if (i.parentId) childIds.add(i.id);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const containers = items.filter(i => i.type === 'feature' || i.type === 'track');
|
|
14
|
+
const cids = new Set(containers.map(c => c.id));
|
|
15
|
+
const orphans = items.filter(i => !cids.has(i.id) && !childIds.has(i.id));
|
|
16
|
+
|
|
17
|
+
console.log('=== CONTAINERS ===');
|
|
18
|
+
for (const c of containers) {
|
|
19
|
+
const kids = conns.filter(cn => cn.toId === c.id && (cn.type === 'implements' || cn.type === 'supports')).length;
|
|
20
|
+
console.log(`${c.id.slice(0,8)} ${c.type.padEnd(8)} ${c.title} (${kids} children)`);
|
|
21
|
+
}
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(`=== ORPHANS (${orphans.length}) ===`);
|
|
24
|
+
for (const o of orphans) {
|
|
25
|
+
console.log(`${o.id.slice(0,8)} ${o.type.padEnd(10)} ${o.phase.padEnd(15)} ${o.title}`);
|
|
26
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from 'child_process';
|
|
3
|
+
|
|
4
|
+
// Reclassify all ideas, questions, and threads to vision phase
|
|
5
|
+
const reclassify = [
|
|
6
|
+
// Ideas
|
|
7
|
+
'2662567c-deac-4eec-9084-19d9756c093d', // Dynamic decision chips
|
|
8
|
+
'bdbdd363-894d-48ad-be45-1680d1095c37', // Specialized agents per phase
|
|
9
|
+
'd45f80f0-43d5-48a2-bc80-83231f205c0c', // Background sub-agent for proactive AI
|
|
10
|
+
'83bdd6a9-c0aa-4c42-bc74-f65d2e320d0d', // General backbone + domain constraints
|
|
11
|
+
'26779dbe-6a5b-4dbb-ac30-aacc97f044bf', // Base44 insight: iteration is the value
|
|
12
|
+
'1e555432-ce1c-4279-820c-0ba4fad53b2b', // Knobs as control surface
|
|
13
|
+
'orphan-persistence-model', // Event-sourced persistence model
|
|
14
|
+
'orphan-voice-input', // Voice input for rapid capture
|
|
15
|
+
'orphan-mobile-view', // Mobile companion view
|
|
16
|
+
'idea-testing-strategy', // Verification phase: AI tests its own output
|
|
17
|
+
'e3091928-0b0d-4656-984b-8a2764eda72a', // Drill-down views for Vision Tracker
|
|
18
|
+
|
|
19
|
+
// Questions
|
|
20
|
+
'37a333aa-1f0a-4f0d-8cab-c349f06a08cd', // How do jigsaw pieces interface?
|
|
21
|
+
'5a836beb-afd4-4d21-b36d-1fe7c8a21eb8', // How to prove the thesis?
|
|
22
|
+
'417e5c2c-992a-40e1-8ba1-3bc7e25564b0', // What triggers decision chips?
|
|
23
|
+
'c38aa3ae-febb-4370-81ca-b22163825d10', // How does the item map scale?
|
|
24
|
+
'question-release-strategy', // What's the release packaging?
|
|
25
|
+
|
|
26
|
+
// Threads
|
|
27
|
+
'c3e13f70-2740-4558-997e-7f1dcb7a39c8', // Design the first permanent piece
|
|
28
|
+
'86878969-e6cf-4002-8f50-b90122acf9ab', // Fill remaining matrix rows
|
|
29
|
+
'19f05faa-d50d-4697-87d2-c77b5d710a26', // Pipeline requirements
|
|
30
|
+
'd28e17d8-b9e7-4484-84ec-27f83568371d', // Knobs design
|
|
31
|
+
'thread-impl-bootstrap', // Implementation bootstrap sequence
|
|
32
|
+
|
|
33
|
+
// Vision decision
|
|
34
|
+
'5632a099-62f6-4f38-9d20-992a3072f369', // Vision: Build me X, Forge takes it from there
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
let success = 0, fail = 0;
|
|
38
|
+
for (const id of reclassify) {
|
|
39
|
+
try {
|
|
40
|
+
execFileSync('node', ['scripts/vision-track.mjs', 'update', id, '--phase', 'vision'], { stdio: 'pipe' });
|
|
41
|
+
success++;
|
|
42
|
+
process.stdout.write('.');
|
|
43
|
+
} catch (e) {
|
|
44
|
+
fail++;
|
|
45
|
+
process.stdout.write('x');
|
|
46
|
+
console.error(`\n FAIL: ${id}: ${e.stderr?.toString().trim()}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.log(`\n\nDone: ${success} reclassified to vision, ${fail} failed`);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* generate-stratum-spec.mjs — Generates pipelines/compose_feature.stratum.yaml
|
|
4
|
+
* from contracts/lifecycle.json.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node scripts/generate-stratum-spec.mjs
|
|
7
|
+
*
|
|
8
|
+
* Design constraint: the generated spec has exactly one function and one step
|
|
9
|
+
* per contract phase, with step id and function id equal to the phase id.
|
|
10
|
+
* Revision edges (back-edges in the transition graph) are documented as
|
|
11
|
+
* comments — they are handled at runtime, not modeled in the DAG.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { resolve, dirname } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const ROOT = resolve(__dirname, '..');
|
|
20
|
+
const contract = JSON.parse(readFileSync(resolve(ROOT, 'contracts', 'lifecycle.json'), 'utf8'));
|
|
21
|
+
const OUTPUT = resolve(ROOT, 'pipelines', 'compose_feature.stratum.yaml');
|
|
22
|
+
|
|
23
|
+
const lines = [];
|
|
24
|
+
const w = (s = '') => lines.push(s);
|
|
25
|
+
|
|
26
|
+
w('version: "0.1"');
|
|
27
|
+
w();
|
|
28
|
+
w('# compose_feature pipeline');
|
|
29
|
+
w(`# Generated from contracts/lifecycle.json v${contract.version}`);
|
|
30
|
+
w('# Do not edit manually — regenerate with: node scripts/generate-stratum-spec.mjs');
|
|
31
|
+
w();
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Detect revision edges for documentation purposes.
|
|
35
|
+
// A revision edge is A → B where B comes before A in phase order.
|
|
36
|
+
// These are handled at runtime, not in the Stratum DAG.
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
const phaseIndex = Object.fromEntries(contract.phases.map((p, i) => [p.id, i]));
|
|
40
|
+
|
|
41
|
+
const revisionEdges = []; // [{ from, to }]
|
|
42
|
+
for (const [from, targets] of Object.entries(contract.transitions)) {
|
|
43
|
+
for (const target of targets) {
|
|
44
|
+
if (phaseIndex[from] > phaseIndex[target]) {
|
|
45
|
+
revisionEdges.push({ from, to: target });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Contracts
|
|
51
|
+
w('contracts:');
|
|
52
|
+
w(' PhaseResult:');
|
|
53
|
+
w(' phase: {type: string}');
|
|
54
|
+
w(' artifact: {type: string}');
|
|
55
|
+
w(' outcome: {type: string}');
|
|
56
|
+
w();
|
|
57
|
+
|
|
58
|
+
// Functions — exactly one per phase
|
|
59
|
+
w('functions:');
|
|
60
|
+
for (const phase of contract.phases) {
|
|
61
|
+
w(` ${phase.id}:`);
|
|
62
|
+
w(' mode: compute');
|
|
63
|
+
w(` intent: "${phase.description}"`);
|
|
64
|
+
w(' input:');
|
|
65
|
+
w(' featureCode: {type: string}');
|
|
66
|
+
w(' description: {type: string}');
|
|
67
|
+
w(' output: PhaseResult');
|
|
68
|
+
if (phase.artifact) {
|
|
69
|
+
w(' ensure:');
|
|
70
|
+
w(` - "file_exists('docs/features/' + input.featureCode + '/${phase.artifact}')"`);
|
|
71
|
+
}
|
|
72
|
+
// Phases that are revision targets get extra retries for the loop
|
|
73
|
+
const isRevisionTarget = revisionEdges.some(e => e.to === phase.id);
|
|
74
|
+
w(` retries: ${isRevisionTarget ? 3 : 2}`);
|
|
75
|
+
w();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Flow — exactly one step per phase, depends_on from forward transitions
|
|
79
|
+
w('flows:');
|
|
80
|
+
w(' compose_feature:');
|
|
81
|
+
w(' input:');
|
|
82
|
+
w(' featureCode: {type: string}');
|
|
83
|
+
w(' description: {type: string}');
|
|
84
|
+
w(' output: PhaseResult');
|
|
85
|
+
|
|
86
|
+
if (revisionEdges.length > 0) {
|
|
87
|
+
w(' # Revision edges (handled at runtime, not in DAG):');
|
|
88
|
+
for (const edge of revisionEdges) {
|
|
89
|
+
w(` # ${edge.from} → ${edge.to}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
w(' steps:');
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < contract.phases.length; i++) {
|
|
96
|
+
const phase = contract.phases[i];
|
|
97
|
+
|
|
98
|
+
// Find forward predecessors: phases that transition TO this phase
|
|
99
|
+
// and appear earlier in phase order (forward edges only)
|
|
100
|
+
const forwardPreds = [];
|
|
101
|
+
for (const [from, targets] of Object.entries(contract.transitions)) {
|
|
102
|
+
if (targets.includes(phase.id) && phaseIndex[from] < i) {
|
|
103
|
+
forwardPreds.push(from);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Sort by phase index — use latest predecessor for correct scheduling order.
|
|
107
|
+
// Skip paths are handled at runtime by the lifecycle manager, not in the DAG.
|
|
108
|
+
forwardPreds.sort((a, b) => phaseIndex[a] - phaseIndex[b]);
|
|
109
|
+
|
|
110
|
+
w(` - id: ${phase.id}`);
|
|
111
|
+
w(` function: ${phase.id}`);
|
|
112
|
+
w(' inputs:');
|
|
113
|
+
w(' featureCode: "$.input.featureCode"');
|
|
114
|
+
w(' description: "$.input.description"');
|
|
115
|
+
|
|
116
|
+
if (forwardPreds.length > 0) {
|
|
117
|
+
w(` depends_on: [${forwardPreds[forwardPreds.length - 1]}]`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Annotate revision edges involving this step
|
|
121
|
+
const revFrom = revisionEdges.filter(e => e.from === phase.id);
|
|
122
|
+
const revTo = revisionEdges.filter(e => e.to === phase.id);
|
|
123
|
+
if (revFrom.length > 0) {
|
|
124
|
+
for (const edge of revFrom) {
|
|
125
|
+
w(` # Revision: may loop back to ${edge.to}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (revTo.length > 0) {
|
|
129
|
+
for (const edge of revTo) {
|
|
130
|
+
w(` # Revision target: ${edge.from} may loop back here`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
w();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
writeFileSync(OUTPUT, lines.join('\n'));
|
|
137
|
+
console.log(`Generated ${OUTPUT}`);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* import-roadmap.mjs — Parse ROADMAP.md and import entries into the vision store.
|
|
4
|
+
*
|
|
5
|
+
* Usage: node scripts/import-roadmap.mjs [path-to-roadmap]
|
|
6
|
+
* Defaults to ../ROADMAP.md (project root).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { parseRoadmap } from '../lib/roadmap-parser.js';
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const API_BASE = process.env.API_BASE || 'http://localhost:3001';
|
|
16
|
+
|
|
17
|
+
const roadmapPath = process.argv[2] || path.resolve(__dirname, '../../ROADMAP.md');
|
|
18
|
+
const text = fs.readFileSync(roadmapPath, 'utf-8');
|
|
19
|
+
const entries = parseRoadmap(text);
|
|
20
|
+
|
|
21
|
+
console.log(`Parsed ${entries.length} entries from ${roadmapPath}`);
|
|
22
|
+
|
|
23
|
+
// Map roadmap statuses to vision store statuses
|
|
24
|
+
function mapStatus(s) {
|
|
25
|
+
const upper = s.toUpperCase();
|
|
26
|
+
if (upper === 'COMPLETE') return 'complete';
|
|
27
|
+
if (upper === 'IN_PROGRESS') return 'in_progress';
|
|
28
|
+
if (upper === 'PARTIAL') return 'in_progress';
|
|
29
|
+
if (upper === 'SUPERSEDED') return 'killed';
|
|
30
|
+
if (upper === 'PARKED') return 'blocked';
|
|
31
|
+
if (upper === 'MANUAL GATE') return 'blocked';
|
|
32
|
+
return 'planned';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Valid phases: vision, specification, planning, implementation, verification, release
|
|
36
|
+
function mapPhase(phaseId) {
|
|
37
|
+
const lower = phaseId.toLowerCase();
|
|
38
|
+
if (lower.includes('bootstrap')) return 'vision';
|
|
39
|
+
if (lower.includes('vision surface')) return 'vision';
|
|
40
|
+
if (lower.includes('agent awareness')) return 'specification';
|
|
41
|
+
if (lower.includes('session tracking')) return 'specification';
|
|
42
|
+
if (lower.includes('agent connector')) return 'implementation';
|
|
43
|
+
if (lower.includes('stratum sync')) return 'implementation';
|
|
44
|
+
if (lower.includes('standalone')) return 'planning';
|
|
45
|
+
if (lower.includes('lifecycle')) return 'implementation';
|
|
46
|
+
if (lower.includes('project bootstrap') || lower.includes('init-1')) return 'implementation';
|
|
47
|
+
if (lower.includes('strat-1')) return 'implementation';
|
|
48
|
+
if (lower.includes('comp-ui')) return 'implementation';
|
|
49
|
+
if (lower.includes('comp-rt')) return 'planning';
|
|
50
|
+
if (lower.includes('strat-par')) return 'implementation';
|
|
51
|
+
if (lower.includes('comp-bench')) return 'planning';
|
|
52
|
+
if (lower.includes('dogfood')) return 'verification';
|
|
53
|
+
return 'planning';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Determine item type
|
|
57
|
+
function mapType(entry) {
|
|
58
|
+
if (entry.code.startsWith('_anon_')) return 'task';
|
|
59
|
+
if (entry.code.startsWith('STRAT-')) return 'feature';
|
|
60
|
+
if (entry.code.startsWith('COMP-')) return 'feature';
|
|
61
|
+
if (entry.code.startsWith('INIT-')) return 'feature';
|
|
62
|
+
return 'task';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// First, get existing items to avoid duplicates
|
|
66
|
+
const existing = await fetch(`${API_BASE}/api/vision/items`).then(r => r.json());
|
|
67
|
+
const existingTitles = new Set((existing.items || []).map(i => i.title));
|
|
68
|
+
|
|
69
|
+
let created = 0;
|
|
70
|
+
let skipped = 0;
|
|
71
|
+
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
// Build a clean title
|
|
74
|
+
const title = entry.code.startsWith('_anon_')
|
|
75
|
+
? entry.description.substring(0, 80)
|
|
76
|
+
: `${entry.code}: ${entry.description.substring(0, 80)}`;
|
|
77
|
+
|
|
78
|
+
if (existingTitles.has(title)) {
|
|
79
|
+
skipped++;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const item = {
|
|
84
|
+
type: mapType(entry),
|
|
85
|
+
title,
|
|
86
|
+
description: `${entry.description}\n\nPhase: ${entry.phaseId}\nRoadmap status: ${entry.status}`,
|
|
87
|
+
status: mapStatus(entry.status),
|
|
88
|
+
phase: mapPhase(entry.phaseId),
|
|
89
|
+
confidence: entry.status === 'COMPLETE' ? 3 : entry.status === 'PARTIAL' || entry.status === 'IN_PROGRESS' ? 2 : 0,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Add featureCode for named features
|
|
93
|
+
if (!entry.code.startsWith('_anon_')) {
|
|
94
|
+
item.featureCode = entry.code;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const res = await fetch(`${API_BASE}/api/vision/items`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: { 'Content-Type': 'application/json' },
|
|
101
|
+
body: JSON.stringify(item),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (res.ok) {
|
|
105
|
+
created++;
|
|
106
|
+
process.stdout.write('.');
|
|
107
|
+
} else {
|
|
108
|
+
const err = await res.text();
|
|
109
|
+
console.error(`\nFailed to create "${title}": ${res.status} ${err}`);
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error(`\nFetch error for "${title}": ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(`\n\nDone: ${created} created, ${skipped} skipped (already exist)`);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
const d = JSON.parse(readFileSync('data/vision-state.json', 'utf8'));
|
|
4
|
+
|
|
5
|
+
// Show type x phase distribution
|
|
6
|
+
const dist = {};
|
|
7
|
+
for (const i of d.items) {
|
|
8
|
+
if (!dist[i.type]) dist[i.type] = {};
|
|
9
|
+
dist[i.type][i.phase] = (dist[i.type][i.phase] || 0) + 1;
|
|
10
|
+
}
|
|
11
|
+
console.log('=== TYPE x PHASE ===');
|
|
12
|
+
for (const [type, phases] of Object.entries(dist).sort()) {
|
|
13
|
+
console.log(type + ':');
|
|
14
|
+
for (const [p, c] of Object.entries(phases).sort()) {
|
|
15
|
+
console.log(' ' + p.padEnd(18) + c);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Items that are likely vision-phase
|
|
20
|
+
console.log('\n=== LIKELY VISION PHASE ===');
|
|
21
|
+
const visionTypes = new Set(['idea', 'thread', 'question']);
|
|
22
|
+
for (const i of d.items) {
|
|
23
|
+
if (i.phase === 'vision') continue;
|
|
24
|
+
const t = i.title.toLowerCase();
|
|
25
|
+
let reason = '';
|
|
26
|
+
if (visionTypes.has(i.type)) reason = `type=${i.type}`;
|
|
27
|
+
else if (t.includes('vision') && i.type !== 'track' && i.type !== 'task') reason = 'title:vision';
|
|
28
|
+
else if (t.includes('discovery') && i.type !== 'track') reason = 'title:discovery';
|
|
29
|
+
else if (t.includes('brainstorm')) reason = 'title:brainstorm';
|
|
30
|
+
if (reason) {
|
|
31
|
+
console.log(`${i.id.slice(0,8)} ${i.phase.padEnd(16)} ${i.type.padEnd(12)} ${reason.padEnd(16)} ${i.title}`);
|
|
32
|
+
}
|
|
33
|
+
}
|