@smartmemory/compose 0.1.3-beta → 0.1.4-beta
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/bin/compose.js +4 -4
- package/lib/pipeline-cli.js +29 -28
- package/package.json +1 -1
- package/pipelines/new.stratum.yaml +182 -0
package/bin/compose.js
CHANGED
|
@@ -576,13 +576,13 @@ if (cmd === 'new') {
|
|
|
576
576
|
if (result.options.reviewAgent === 'Codex (automated review)') {
|
|
577
577
|
const { pipelineSet } = await import('../lib/pipeline-cli.js')
|
|
578
578
|
try {
|
|
579
|
-
pipelineSet(cwd, 'review_gate', ['--mode', 'review'])
|
|
580
|
-
} catch { /*
|
|
579
|
+
pipelineSet(cwd, 'review_gate', ['--mode', 'review'], 'new.stratum.yaml')
|
|
580
|
+
} catch { /* kickoff spec missing or review_gate already absent */ }
|
|
581
581
|
} else if (result.options.reviewAgent === 'Skip review') {
|
|
582
582
|
const { pipelineDisable } = await import('../lib/pipeline-cli.js')
|
|
583
583
|
try {
|
|
584
|
-
pipelineDisable(cwd, ['review_gate'])
|
|
585
|
-
} catch { /*
|
|
584
|
+
pipelineDisable(cwd, ['review_gate'], 'new.stratum.yaml')
|
|
585
|
+
} catch { /* kickoff spec missing or review_gate already absent */ }
|
|
586
586
|
}
|
|
587
587
|
} else if (hasAnswers && !autoMode) {
|
|
588
588
|
// Load saved answers to enrich intent without prompting
|
package/lib/pipeline-cli.js
CHANGED
|
@@ -20,12 +20,13 @@ import { parse, stringify } from 'yaml'
|
|
|
20
20
|
// Helpers
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
|
|
23
|
-
function loadSpec(cwd) {
|
|
24
|
-
const specPath = join(cwd, 'pipelines',
|
|
23
|
+
function loadSpec(cwd, specName = 'build.stratum.yaml') {
|
|
24
|
+
const specPath = join(cwd, 'pipelines', specName)
|
|
25
25
|
if (!existsSync(specPath)) {
|
|
26
26
|
throw new Error(`No pipeline found at ${specPath}. Run 'compose init' first.`)
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
const flowName = specName.replace(/\.stratum\.yaml$/, '')
|
|
29
|
+
return { specPath, spec: parse(readFileSync(specPath, 'utf-8')), flowName }
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function saveSpec(specPath, spec) {
|
|
@@ -54,12 +55,12 @@ const LEVEL_COLORS = {
|
|
|
54
55
|
}
|
|
55
56
|
const RESET = '\x1b[0m'
|
|
56
57
|
|
|
57
|
-
export function pipelineShow(cwd) {
|
|
58
|
-
const { spec } = loadSpec(cwd)
|
|
59
|
-
const mainFlow = spec.flows?.
|
|
60
|
-
if (!mainFlow) throw new Error(
|
|
58
|
+
export function pipelineShow(cwd, specName = 'build.stratum.yaml') {
|
|
59
|
+
const { spec, flowName } = loadSpec(cwd, specName)
|
|
60
|
+
const mainFlow = spec.flows?.[flowName]
|
|
61
|
+
if (!mainFlow) throw new Error(`No "${flowName}" flow found in pipeline spec.`)
|
|
61
62
|
|
|
62
|
-
console.log(`\n Pipeline:
|
|
63
|
+
console.log(`\n Pipeline: ${flowName} (${mainFlow.steps.length} steps)\n`)
|
|
63
64
|
|
|
64
65
|
for (const step of mainFlow.steps) {
|
|
65
66
|
const isGate = !!step.function
|
|
@@ -133,10 +134,10 @@ function gateTimeout(spec, funcName) {
|
|
|
133
134
|
// set
|
|
134
135
|
// ---------------------------------------------------------------------------
|
|
135
136
|
|
|
136
|
-
export function pipelineSet(cwd, stepId, flags) {
|
|
137
|
-
const { specPath, spec } = loadSpec(cwd)
|
|
138
|
-
const mainFlow = spec.flows?.
|
|
139
|
-
if (!mainFlow) throw new Error(
|
|
137
|
+
export function pipelineSet(cwd, stepId, flags, specName = 'build.stratum.yaml') {
|
|
138
|
+
const { specPath, spec, flowName } = loadSpec(cwd, specName)
|
|
139
|
+
const mainFlow = spec.flows?.[flowName]
|
|
140
|
+
if (!mainFlow) throw new Error(`No "${flowName}" flow found.`)
|
|
140
141
|
|
|
141
142
|
const { step, idx } = findStep(mainFlow.steps, stepId)
|
|
142
143
|
|
|
@@ -305,10 +306,10 @@ function convertToAgent(spec, mainFlow, step, stepId) {
|
|
|
305
306
|
// add
|
|
306
307
|
// ---------------------------------------------------------------------------
|
|
307
308
|
|
|
308
|
-
export function pipelineAdd(cwd, flags) {
|
|
309
|
-
const { specPath, spec } = loadSpec(cwd)
|
|
310
|
-
const mainFlow = spec.flows?.
|
|
311
|
-
if (!mainFlow) throw new Error(
|
|
309
|
+
export function pipelineAdd(cwd, flags, specName = 'build.stratum.yaml') {
|
|
310
|
+
const { specPath, spec, flowName } = loadSpec(cwd, specName)
|
|
311
|
+
const mainFlow = spec.flows?.[flowName]
|
|
312
|
+
if (!mainFlow) throw new Error(`No "${flowName}" flow found.`)
|
|
312
313
|
|
|
313
314
|
const id = flagVal(flags, '--id')
|
|
314
315
|
const after = flagVal(flags, '--after')
|
|
@@ -353,10 +354,10 @@ export function pipelineAdd(cwd, flags) {
|
|
|
353
354
|
// remove
|
|
354
355
|
// ---------------------------------------------------------------------------
|
|
355
356
|
|
|
356
|
-
export function pipelineRemove(cwd, stepId) {
|
|
357
|
-
const { specPath, spec } = loadSpec(cwd)
|
|
358
|
-
const mainFlow = spec.flows?.
|
|
359
|
-
if (!mainFlow) throw new Error(
|
|
357
|
+
export function pipelineRemove(cwd, stepId, specName = 'build.stratum.yaml') {
|
|
358
|
+
const { specPath, spec, flowName } = loadSpec(cwd, specName)
|
|
359
|
+
const mainFlow = spec.flows?.[flowName]
|
|
360
|
+
if (!mainFlow) throw new Error(`No "${flowName}" flow found.`)
|
|
360
361
|
|
|
361
362
|
const { step, idx } = findStep(mainFlow.steps, stepId)
|
|
362
363
|
|
|
@@ -388,10 +389,10 @@ export function pipelineRemove(cwd, stepId) {
|
|
|
388
389
|
// enable / disable
|
|
389
390
|
// ---------------------------------------------------------------------------
|
|
390
391
|
|
|
391
|
-
export function pipelineEnable(cwd, stepIds) {
|
|
392
|
-
const { specPath, spec } = loadSpec(cwd)
|
|
393
|
-
const mainFlow = spec.flows?.
|
|
394
|
-
if (!mainFlow) throw new Error(
|
|
392
|
+
export function pipelineEnable(cwd, stepIds, specName = 'build.stratum.yaml') {
|
|
393
|
+
const { specPath, spec, flowName } = loadSpec(cwd, specName)
|
|
394
|
+
const mainFlow = spec.flows?.[flowName]
|
|
395
|
+
if (!mainFlow) throw new Error(`No "${flowName}" flow found.`)
|
|
395
396
|
|
|
396
397
|
for (const stepId of stepIds) {
|
|
397
398
|
const { step } = findStep(mainFlow.steps, stepId)
|
|
@@ -403,10 +404,10 @@ export function pipelineEnable(cwd, stepIds) {
|
|
|
403
404
|
saveSpec(specPath, spec)
|
|
404
405
|
}
|
|
405
406
|
|
|
406
|
-
export function pipelineDisable(cwd, stepIds) {
|
|
407
|
-
const { specPath, spec } = loadSpec(cwd)
|
|
408
|
-
const mainFlow = spec.flows?.
|
|
409
|
-
if (!mainFlow) throw new Error(
|
|
407
|
+
export function pipelineDisable(cwd, stepIds, specName = 'build.stratum.yaml') {
|
|
408
|
+
const { specPath, spec, flowName } = loadSpec(cwd, specName)
|
|
409
|
+
const mainFlow = spec.flows?.[flowName]
|
|
410
|
+
if (!mainFlow) throw new Error(`No "${flowName}" flow found.`)
|
|
410
411
|
|
|
411
412
|
for (const stepId of stepIds) {
|
|
412
413
|
const { step } = findStep(mainFlow.steps, stepId)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartmemory/compose",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4-beta",
|
|
4
4
|
"description": "Structured AI dev pipeline — goal-to-product orchestration with gates, iteration loops, and feature lifecycle management.",
|
|
5
5
|
"author": "SmartMemory",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
version: "0.2"
|
|
2
|
+
|
|
3
|
+
workflow:
|
|
4
|
+
name: new
|
|
5
|
+
description: "Product kickoff — research, brainstorm, and scaffold a new project from intent"
|
|
6
|
+
input:
|
|
7
|
+
projectName:
|
|
8
|
+
type: string
|
|
9
|
+
required: true
|
|
10
|
+
intent:
|
|
11
|
+
type: string
|
|
12
|
+
required: true
|
|
13
|
+
|
|
14
|
+
contracts:
|
|
15
|
+
ResearchResult:
|
|
16
|
+
priorArt: {type: array}
|
|
17
|
+
patterns: {type: array}
|
|
18
|
+
risks: {type: array}
|
|
19
|
+
summary: {type: string}
|
|
20
|
+
|
|
21
|
+
BrainstormResult:
|
|
22
|
+
features: {type: array}
|
|
23
|
+
userStories: {type: array}
|
|
24
|
+
archOptions: {type: array}
|
|
25
|
+
summary: {type: string}
|
|
26
|
+
|
|
27
|
+
RoadmapResult:
|
|
28
|
+
phases: {type: array}
|
|
29
|
+
features: {type: array}
|
|
30
|
+
summary: {type: string}
|
|
31
|
+
artifact: {type: string}
|
|
32
|
+
|
|
33
|
+
ScaffoldResult:
|
|
34
|
+
created: {type: array}
|
|
35
|
+
summary: {type: string}
|
|
36
|
+
|
|
37
|
+
functions:
|
|
38
|
+
review_gate:
|
|
39
|
+
mode: gate
|
|
40
|
+
timeout: 7200
|
|
41
|
+
|
|
42
|
+
roadmap_gate:
|
|
43
|
+
mode: gate
|
|
44
|
+
timeout: 3600
|
|
45
|
+
|
|
46
|
+
flows:
|
|
47
|
+
new:
|
|
48
|
+
input:
|
|
49
|
+
projectName: {type: string}
|
|
50
|
+
intent: {type: string}
|
|
51
|
+
output: ScaffoldResult
|
|
52
|
+
steps:
|
|
53
|
+
# Phase: Research prior art
|
|
54
|
+
- id: research
|
|
55
|
+
agent: claude
|
|
56
|
+
intent: >
|
|
57
|
+
Research prior art for this product idea. Search the web for:
|
|
58
|
+
1. Existing tools that solve the same or similar problems
|
|
59
|
+
2. Common architectural patterns used in this domain
|
|
60
|
+
3. Known pitfalls and risks
|
|
61
|
+
|
|
62
|
+
Write your findings to docs/discovery/research.md.
|
|
63
|
+
If docs/discovery/research.md already exists, read it and build on it
|
|
64
|
+
rather than starting from scratch.
|
|
65
|
+
inputs:
|
|
66
|
+
intent: "$.input.intent"
|
|
67
|
+
output_contract: ResearchResult
|
|
68
|
+
ensure:
|
|
69
|
+
- "file_exists('docs/discovery/research.md')"
|
|
70
|
+
validate:
|
|
71
|
+
artifact: docs/discovery/research.md
|
|
72
|
+
criteria:
|
|
73
|
+
- "Contains at least 2 existing tools or prior art entries"
|
|
74
|
+
- "Mentions architectural patterns or common approaches"
|
|
75
|
+
- "Lists risks or pitfalls"
|
|
76
|
+
retries: 3
|
|
77
|
+
|
|
78
|
+
# Phase: Brainstorm features
|
|
79
|
+
- id: brainstorm
|
|
80
|
+
agent: claude
|
|
81
|
+
intent: >
|
|
82
|
+
Given the product intent (and any available research findings),
|
|
83
|
+
brainstorm the product. Generate:
|
|
84
|
+
1. A feature list — discrete capabilities the product should have,
|
|
85
|
+
each with a short code (e.g. LOG-1, LOG-2) and description.
|
|
86
|
+
Order features by dependency: foundational first, advanced later.
|
|
87
|
+
2. User stories — "As a <user>, I want <goal>, so that <benefit>"
|
|
88
|
+
3. Architecture options — 2-3 high-level approaches with trade-offs
|
|
89
|
+
|
|
90
|
+
If docs/discovery/research.md exists, read it first for prior-art
|
|
91
|
+
context. Otherwise proceed from the product intent alone — research
|
|
92
|
+
is an optional input, not a hard requirement (the questionnaire can
|
|
93
|
+
skip it).
|
|
94
|
+
|
|
95
|
+
Write the full brainstorm to docs/discovery/brainstorm.md.
|
|
96
|
+
If docs/discovery/brainstorm.md already exists, read it and refine it
|
|
97
|
+
rather than starting from scratch.
|
|
98
|
+
inputs:
|
|
99
|
+
intent: "$.input.intent"
|
|
100
|
+
research: "$.steps.research.output.summary" # may be null when research is skipped
|
|
101
|
+
output_contract: BrainstormResult
|
|
102
|
+
ensure:
|
|
103
|
+
- "file_exists('docs/discovery/brainstorm.md')"
|
|
104
|
+
validate:
|
|
105
|
+
artifact: docs/discovery/brainstorm.md
|
|
106
|
+
criteria:
|
|
107
|
+
- "Contains at least 3 features with short codes (e.g. LOG-1, LOG-2)"
|
|
108
|
+
- "Contains user stories in 'As a... I want... so that...' format"
|
|
109
|
+
- "Contains at least 2 architecture options with trade-offs"
|
|
110
|
+
retries: 3
|
|
111
|
+
depends_on: [research]
|
|
112
|
+
|
|
113
|
+
# Gate: Human reviews brainstorm
|
|
114
|
+
- id: review_gate
|
|
115
|
+
function: review_gate
|
|
116
|
+
on_approve: roadmap
|
|
117
|
+
on_revise: brainstorm
|
|
118
|
+
on_kill: null
|
|
119
|
+
depends_on: [brainstorm]
|
|
120
|
+
|
|
121
|
+
# Phase: Structure the roadmap
|
|
122
|
+
- id: roadmap
|
|
123
|
+
agent: claude
|
|
124
|
+
intent: >
|
|
125
|
+
Take the approved brainstorm and structure it into a phased ROADMAP.
|
|
126
|
+
|
|
127
|
+
First read docs/discovery/brainstorm.md for the full brainstorm.
|
|
128
|
+
If ROADMAP.md already exists, read it and update it rather than overwriting.
|
|
129
|
+
|
|
130
|
+
Write ROADMAP.md in the project root with:
|
|
131
|
+
1. Project name and description
|
|
132
|
+
2. Roadmap conventions (status values, numbering rules)
|
|
133
|
+
3. Phases grouping features by dependency order
|
|
134
|
+
4. Each feature as a numbered row with its code, description, and PLANNED status
|
|
135
|
+
|
|
136
|
+
Follow this table format for each phase:
|
|
137
|
+
| # | Feature | Item | Status |
|
|
138
|
+
|---|---------|------|--------|
|
|
139
|
+
| 1 | CODE-1 | Description | PLANNED |
|
|
140
|
+
inputs:
|
|
141
|
+
intent: "$.input.intent"
|
|
142
|
+
brainstorm: "$.steps.brainstorm.output.summary"
|
|
143
|
+
output_contract: RoadmapResult
|
|
144
|
+
ensure:
|
|
145
|
+
- "file_exists('ROADMAP.md')"
|
|
146
|
+
validate:
|
|
147
|
+
artifact: ROADMAP.md
|
|
148
|
+
criteria:
|
|
149
|
+
- "Contains a markdown table with feature codes and status columns"
|
|
150
|
+
- "Features are organized into phases"
|
|
151
|
+
- "All features have PLANNED status"
|
|
152
|
+
retries: 3
|
|
153
|
+
depends_on: [review_gate]
|
|
154
|
+
|
|
155
|
+
# Gate: Human approves the roadmap
|
|
156
|
+
- id: roadmap_gate
|
|
157
|
+
function: roadmap_gate
|
|
158
|
+
on_approve: scaffold
|
|
159
|
+
on_revise: roadmap
|
|
160
|
+
on_kill: null
|
|
161
|
+
depends_on: [roadmap]
|
|
162
|
+
|
|
163
|
+
# Phase: Scaffold feature folders
|
|
164
|
+
- id: scaffold
|
|
165
|
+
agent: claude
|
|
166
|
+
intent: >
|
|
167
|
+
Read the approved ROADMAP.md and create a feature folder for each
|
|
168
|
+
feature listed. For each feature:
|
|
169
|
+
1. Create docs/features/<CODE>/design.md with a seed design doc containing:
|
|
170
|
+
- Title: "<CODE>: <description>"
|
|
171
|
+
- Status: PLANNED
|
|
172
|
+
- Created date
|
|
173
|
+
- Intent section with the feature description
|
|
174
|
+
- Notes section explaining this is a seed for compose build
|
|
175
|
+
inputs:
|
|
176
|
+
intent: "$.input.intent"
|
|
177
|
+
roadmap: "$.steps.roadmap.output.summary"
|
|
178
|
+
output_contract: ScaffoldResult
|
|
179
|
+
ensure:
|
|
180
|
+
- "len(result.created) > 0"
|
|
181
|
+
retries: 2
|
|
182
|
+
depends_on: [roadmap_gate]
|