@trailstep/create-flows 0.3.1 → 0.3.3
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/README.md +83 -24
- package/dist/chunk-RF3PMHPX.js +2594 -0
- package/dist/chunk-RF3PMHPX.js.map +1 -0
- package/dist/index.d.ts +26 -3
- package/dist/index.js +509 -628
- package/dist/index.js.map +1 -1
- package/dist/step-ZAE3Y7FI.js +7 -0
- package/dist/step-ZAE3Y7FI.js.map +1 -0
- package/package.json +199 -7
package/dist/index.js
CHANGED
|
@@ -1,527 +1,365 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
MAX_IMPLEMENTATION_DOC_REVIEW_ATTEMPTS,
|
|
3
|
+
STORY_BOUNDARY,
|
|
4
|
+
STORY_CONTEXT_END,
|
|
5
|
+
STORY_CONTEXT_START,
|
|
6
|
+
STORY_STATE_KEYS,
|
|
7
|
+
normalizeTakeItAwayWorkflowOptions,
|
|
8
|
+
resetStoryLocalState,
|
|
9
|
+
reviewOutput,
|
|
10
|
+
reviewPasses,
|
|
11
|
+
storyRouterStep,
|
|
12
|
+
takeItAwayInput,
|
|
13
|
+
takeItAwayOutput
|
|
14
|
+
} from "./chunk-RF3PMHPX.js";
|
|
3
15
|
|
|
4
|
-
// src/
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
completedStories: { type: "array", items: { type: "string" } },
|
|
14
|
-
summary: { type: "string" }
|
|
16
|
+
// src/grill-it-away/workflow.ts
|
|
17
|
+
import { defineWorkflow, shape } from "@trailstep/authoring";
|
|
18
|
+
|
|
19
|
+
// src/feature-implementation/shared/agent-roles.ts
|
|
20
|
+
var featureImplementationAgents = {
|
|
21
|
+
featureWriter: {
|
|
22
|
+
size: "medium",
|
|
23
|
+
thinking: "medium",
|
|
24
|
+
description: "Turns a conversation/feature request into a standalone feature doc."
|
|
15
25
|
},
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
planner: {
|
|
27
|
+
size: "large",
|
|
28
|
+
thinking: "high",
|
|
29
|
+
description: "Creates or improves the implementation strategy: architecture-aware, TDD/vertical-slice/tracer-bullet story design."
|
|
30
|
+
},
|
|
31
|
+
reviewer: {
|
|
32
|
+
size: "large",
|
|
33
|
+
thinking: "high",
|
|
34
|
+
description: "Reviews implementation strategies before story slicing."
|
|
35
|
+
},
|
|
36
|
+
slicer: {
|
|
37
|
+
size: "large",
|
|
38
|
+
thinking: "high",
|
|
39
|
+
description: "Slices an approved implementation strategy into coherent implementation stories."
|
|
40
|
+
},
|
|
41
|
+
storyExplorer: {
|
|
42
|
+
size: "small",
|
|
43
|
+
thinking: "low",
|
|
44
|
+
description: "Finds story-specific files, seams, and validation hints without editing."
|
|
45
|
+
},
|
|
46
|
+
testWriter: {
|
|
47
|
+
size: "medium",
|
|
48
|
+
thinking: "medium",
|
|
49
|
+
description: "Writes focused behavioral red tests for the active story."
|
|
50
|
+
},
|
|
51
|
+
storyImplementer: {
|
|
52
|
+
size: "medium",
|
|
53
|
+
thinking: "medium",
|
|
54
|
+
description: "Implements the smallest green slice for the active story."
|
|
55
|
+
},
|
|
56
|
+
storyDoctor: {
|
|
57
|
+
size: "large",
|
|
58
|
+
thinking: "high",
|
|
59
|
+
description: "Diagnoses and repairs repeated active-story validation failures before the workflow exhausts retries."
|
|
60
|
+
},
|
|
61
|
+
validator: {
|
|
62
|
+
size: "small",
|
|
63
|
+
thinking: "low",
|
|
64
|
+
description: "Runs focused validation commands and reports concise results."
|
|
65
|
+
},
|
|
66
|
+
storyReviewer: {
|
|
67
|
+
size: "large",
|
|
68
|
+
thinking: "high",
|
|
69
|
+
description: "Reviews isolated story diffs and phase evidence before commit."
|
|
70
|
+
}
|
|
71
|
+
};
|
|
30
72
|
|
|
31
73
|
// src/grill-it-away/grill/step.ts
|
|
32
74
|
import { step as step8 } from "@trailstep/authoring";
|
|
33
75
|
|
|
76
|
+
// src/feature-implementation/initialize-take-it-away/step.ts
|
|
77
|
+
import { state as state6, step as step7 } from "@trailstep/authoring";
|
|
78
|
+
|
|
34
79
|
// src/feature-implementation/create-feature-doc/step.ts
|
|
35
|
-
import { Document as
|
|
36
|
-
|
|
37
|
-
// src/feature-implementation/create-or-improve-implementation-
|
|
38
|
-
import { Document, state as state4, step as
|
|
39
|
-
|
|
40
|
-
// src/feature-implementation/review-implementation-
|
|
41
|
-
import { fail as
|
|
42
|
-
|
|
43
|
-
// src/feature-implementation/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
80
|
+
import { Document as Document3, state as state5, step as step6 } from "@trailstep/authoring";
|
|
81
|
+
|
|
82
|
+
// src/feature-implementation/create-or-improve-implementation-strategy/step.ts
|
|
83
|
+
import { Document as Document2, state as state4, step as step5 } from "@trailstep/authoring";
|
|
84
|
+
|
|
85
|
+
// src/feature-implementation/review-implementation-strategy/step.ts
|
|
86
|
+
import { fail as fail3, step as step4 } from "@trailstep/authoring";
|
|
87
|
+
|
|
88
|
+
// src/feature-implementation/slice-implementation-stories/step.ts
|
|
89
|
+
import { Document, state as state3, step as step3 } from "@trailstep/authoring";
|
|
90
|
+
|
|
91
|
+
// src/feature-implementation/split-implementation-stories/step.ts
|
|
92
|
+
import { document, fail as fail2, state as state2, step as step2 } from "@trailstep/authoring";
|
|
93
|
+
|
|
94
|
+
// src/feature-implementation/planning-checkpoint/step.ts
|
|
95
|
+
import {
|
|
96
|
+
fail,
|
|
97
|
+
jsonSchema,
|
|
98
|
+
state,
|
|
99
|
+
step
|
|
100
|
+
} from "@trailstep/authoring";
|
|
101
|
+
var planningCheckpointAnswer = jsonSchema({
|
|
55
102
|
type: "object",
|
|
56
103
|
properties: {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
methodologyRatings: {
|
|
60
|
-
type: "object",
|
|
61
|
-
properties: {
|
|
62
|
-
tdd: RATING_SCHEMA,
|
|
63
|
-
verticalSlicing: RATING_SCHEMA,
|
|
64
|
-
tracerBullet: RATING_SCHEMA,
|
|
65
|
-
dependencies: RATING_SCHEMA,
|
|
66
|
-
architecture: RATING_SCHEMA
|
|
67
|
-
},
|
|
68
|
-
required: ["tdd", "verticalSlicing", "tracerBullet", "dependencies", "architecture"],
|
|
69
|
-
additionalProperties: false
|
|
70
|
-
},
|
|
71
|
-
requiredImprovements: { type: "array", items: { type: "string" } }
|
|
104
|
+
action: { type: "string", enum: ["approve", "revise", "abort"] },
|
|
105
|
+
notes: { type: "string" }
|
|
72
106
|
},
|
|
73
|
-
required: ["
|
|
107
|
+
required: ["action"],
|
|
74
108
|
additionalProperties: false
|
|
75
109
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
import { state } from "@trailstep/authoring";
|
|
96
|
-
var execFileAsync = promisify(execFile);
|
|
97
|
-
var STORY_STATE_KEYS = {
|
|
98
|
-
activeStory: "activeStory",
|
|
99
|
-
activeStoryStartCommit: "activeStoryStartCommit",
|
|
100
|
-
completedStories: "completedStories",
|
|
101
|
-
storyQueue: "storyQueue"
|
|
102
|
-
};
|
|
103
|
-
async function runGit(args, cwd) {
|
|
104
|
-
try {
|
|
105
|
-
const { stdout } = await execFileAsync("git", [...args], {
|
|
106
|
-
cwd,
|
|
107
|
-
maxBuffer: 1024 * 1024 * 10
|
|
110
|
+
var planningCheckpointStep = step({ id: "planning-checkpoint" }).wait(async (context) => {
|
|
111
|
+
const input = context.input;
|
|
112
|
+
const waitId = await nextPlanningCheckpointWaitId();
|
|
113
|
+
return {
|
|
114
|
+
id: waitId,
|
|
115
|
+
kind: "input",
|
|
116
|
+
message: [
|
|
117
|
+
"Review the accepted implementation plan and story slices before execution.",
|
|
118
|
+
`Implementation doc: ${input.implementationDoc.path}`,
|
|
119
|
+
'Answer with {"action":"approve"} to continue, {"action":"revise","notes":"..."} to return to planning, or {"action":"abort","notes":"..."} to stop.'
|
|
120
|
+
].join("\n"),
|
|
121
|
+
output: planningCheckpointAnswer
|
|
122
|
+
};
|
|
123
|
+
}).do(async (context, input) => {
|
|
124
|
+
const answer = readPlanningCheckpointAnswer(context.waits);
|
|
125
|
+
if (!answer) {
|
|
126
|
+
return fail({
|
|
127
|
+
code: "planning_checkpoint_missing_answer",
|
|
128
|
+
message: "Planning checkpoint resumed without a recorded human answer."
|
|
108
129
|
});
|
|
109
|
-
return { ok: true, stdout: stdout.trimEnd() };
|
|
110
|
-
} catch (error) {
|
|
111
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
-
return { ok: false, error: message };
|
|
113
130
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return existing;
|
|
121
|
-
}
|
|
122
|
-
const cwd = state.cwd;
|
|
123
|
-
if (!cwd) {
|
|
124
|
-
const missingCwd = {
|
|
125
|
-
warning: "Workflow cwd is unavailable; the story reviewer cannot verify committed story changes from a durable git baseline."
|
|
126
|
-
};
|
|
127
|
-
await state.set(STORY_STATE_KEYS.activeStoryStartCommit, missingCwd);
|
|
128
|
-
return missingCwd;
|
|
129
|
-
}
|
|
130
|
-
const result = await runGit(["rev-parse", "HEAD"], cwd);
|
|
131
|
-
if (!result.ok || result.stdout.length === 0) {
|
|
132
|
-
const missingGitBaseline = {
|
|
133
|
-
warning: `Unable to record story start commit with \`git rev-parse HEAD\`: ${result.ok ? "no commit was returned" : result.error}`
|
|
134
|
-
};
|
|
135
|
-
await state.set(STORY_STATE_KEYS.activeStoryStartCommit, missingGitBaseline);
|
|
136
|
-
return missingGitBaseline;
|
|
131
|
+
if (answer.action === "abort") {
|
|
132
|
+
return fail({
|
|
133
|
+
code: "planning_checkpoint_aborted",
|
|
134
|
+
message: answer.notes?.trim() || "Human aborted at the planning checkpoint.",
|
|
135
|
+
details: { implementationDocPath: input.implementationDoc.path }
|
|
136
|
+
});
|
|
137
137
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
async function loadStoryReviewGitContext() {
|
|
146
|
-
const baseline = await state.get(
|
|
147
|
-
STORY_STATE_KEYS.activeStoryStartCommit
|
|
148
|
-
);
|
|
149
|
-
const warnings = [];
|
|
150
|
-
if (baseline?.warning) {
|
|
151
|
-
warnings.push(baseline.warning);
|
|
138
|
+
if (answer.action === "revise") {
|
|
139
|
+
await state.set(STORY_STATE_KEYS.skipNextPlanningCheckpoint, true);
|
|
140
|
+
return createOrImproveImplementationStrategyStep({
|
|
141
|
+
featureDoc: input.featureDoc,
|
|
142
|
+
previousReview: humanRevisionReview(answer.notes),
|
|
143
|
+
attempt: 1
|
|
144
|
+
});
|
|
152
145
|
}
|
|
153
|
-
const
|
|
154
|
-
if (!
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
146
|
+
const activeStory = await state.get(STORY_STATE_KEYS.activeStory);
|
|
147
|
+
if (!activeStory) {
|
|
148
|
+
return fail({
|
|
149
|
+
code: "planning_checkpoint_missing_active_story",
|
|
150
|
+
message: "Planning checkpoint approved, but no active story was prepared for execution.",
|
|
151
|
+
details: { implementationDocPath: input.implementationDoc.path }
|
|
152
|
+
});
|
|
159
153
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
154
|
+
return storyRouterStep({ reason: "start-story", currentStory: activeStory });
|
|
155
|
+
});
|
|
156
|
+
async function shouldPauseForPlanningCheckpoint() {
|
|
157
|
+
const options = await state.get(STORY_STATE_KEYS.workflowOptions);
|
|
158
|
+
if (!(options?.planningCheckpoint.enabled ?? false)) {
|
|
159
|
+
return false;
|
|
163
160
|
}
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
);
|
|
161
|
+
const skipNextCheckpoint = await state.get(
|
|
162
|
+
STORY_STATE_KEYS.skipNextPlanningCheckpoint
|
|
163
|
+
);
|
|
164
|
+
if (skipNextCheckpoint) {
|
|
165
|
+
await state.set(STORY_STATE_KEYS.skipNextPlanningCheckpoint, false);
|
|
166
|
+
return false;
|
|
169
167
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
} else {
|
|
183
|
-
const committed = await runGit(["diff", `${storyStartCommit}..HEAD`], cwd);
|
|
184
|
-
if (!committed.ok) {
|
|
185
|
-
warnings.push(
|
|
186
|
-
`Unable to inspect committed story changes with \`git diff ${storyStartCommit}..HEAD\`: ${committed.error}`
|
|
187
|
-
);
|
|
188
|
-
} else {
|
|
189
|
-
committedDiff = committed.stdout;
|
|
190
|
-
}
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
async function nextPlanningCheckpointWaitId() {
|
|
171
|
+
const previousCount = await state.get(STORY_STATE_KEYS.planningCheckpointCount) ?? 0;
|
|
172
|
+
const nextCount = previousCount + 1;
|
|
173
|
+
await state.set(STORY_STATE_KEYS.planningCheckpointCount, nextCount);
|
|
174
|
+
return nextCount === 1 ? "planning-checkpoint" : `planning-checkpoint-${nextCount}`;
|
|
175
|
+
}
|
|
176
|
+
function readPlanningCheckpointAnswer(waits) {
|
|
177
|
+
for (const [waitId, value] of Object.entries(waits)) {
|
|
178
|
+
if (!waitId.startsWith("planning-checkpoint") || !isPlanningCheckpointAnswer(value)) {
|
|
179
|
+
continue;
|
|
191
180
|
}
|
|
181
|
+
return value;
|
|
192
182
|
}
|
|
183
|
+
return void 0;
|
|
184
|
+
}
|
|
185
|
+
function isPlanningCheckpointAnswer(value) {
|
|
186
|
+
if (typeof value !== "object" || value === null) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
const action = value.action;
|
|
190
|
+
return action === "approve" || action === "revise" || action === "abort";
|
|
191
|
+
}
|
|
192
|
+
function humanRevisionReview(notes) {
|
|
193
|
+
const trimmedNotes = notes?.trim();
|
|
193
194
|
return {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
score: 1,
|
|
196
|
+
summary: trimmedNotes ? `Human requested planning revision: ${trimmedNotes}` : "Human requested planning revision at the checkpoint.",
|
|
197
|
+
methodologyRatings: {
|
|
198
|
+
tdd: 1,
|
|
199
|
+
verticalSlicing: 1,
|
|
200
|
+
tracerBullet: 1,
|
|
201
|
+
dependencies: 1,
|
|
202
|
+
architecture: 1
|
|
203
|
+
},
|
|
204
|
+
requiredImprovements: [
|
|
205
|
+
trimmedNotes || "Revise the implementation plan per human checkpoint feedback."
|
|
206
|
+
]
|
|
199
207
|
};
|
|
200
208
|
}
|
|
201
209
|
|
|
202
|
-
// src/feature-implementation/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
210
|
+
// src/feature-implementation/split-implementation-stories/story-context.ts
|
|
211
|
+
function parseStoryContextBlocks(rawBlocks) {
|
|
212
|
+
return rawBlocks.flatMap((rawBlock) => {
|
|
213
|
+
const parsed = parseStoryContextBlock(rawBlock);
|
|
214
|
+
return parsed ? [parsed] : [];
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function selectStoryContext({
|
|
218
|
+
blocks,
|
|
219
|
+
storyContent,
|
|
220
|
+
audience,
|
|
221
|
+
phase
|
|
222
|
+
}) {
|
|
223
|
+
return blocks.filter(
|
|
224
|
+
(block) => blockMatchesAudience(block, audience) && blockMatchesStory(block, storyContent) && blockMatchesPhase(block, phase)
|
|
225
|
+
).map((block) => block.body).join("\n\n---\n\n");
|
|
226
|
+
}
|
|
227
|
+
function parseStoryContextBlock(rawBlock) {
|
|
228
|
+
const lines = rawBlock.replace(/\r\n?/g, "\n").split("\n");
|
|
229
|
+
const firstBlankLine = lines.findIndex((line) => line.trim().length === 0);
|
|
230
|
+
const metadataLines = firstBlankLine === -1 ? lines : lines.slice(0, firstBlankLine);
|
|
231
|
+
const bodyLines = firstBlankLine === -1 ? [] : lines.slice(firstBlankLine + 1);
|
|
232
|
+
const metadata = parseMetadataLines(metadataLines);
|
|
233
|
+
const body = bodyLines.join("\n").trim();
|
|
234
|
+
if (body.length === 0 || !hasRecognizedScopeMetadata(metadata)) {
|
|
235
|
+
return void 0;
|
|
216
236
|
}
|
|
237
|
+
return { ...metadata, body };
|
|
217
238
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
message: commitResult.message,
|
|
232
|
-
details: commitResult.details
|
|
233
|
-
});
|
|
239
|
+
function parseMetadataLines(lines) {
|
|
240
|
+
const metadata = {};
|
|
241
|
+
for (const line of lines) {
|
|
242
|
+
const match = /^([A-Za-z-]+):\s*(.*?)\s*$/.exec(line.trim());
|
|
243
|
+
if (!match) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const key = match[1]?.toLowerCase();
|
|
247
|
+
const value = match[2] ?? "";
|
|
248
|
+
if (key === "audience") {
|
|
249
|
+
const audience = parseAudience(value);
|
|
250
|
+
if (audience) {
|
|
251
|
+
metadata.audience = audience;
|
|
234
252
|
}
|
|
253
|
+
continue;
|
|
235
254
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
const mode = process.env.TRAILSTEP_STORY_COMMIT_MODE;
|
|
241
|
-
return ["1", "true", "enabled", "worktree"].includes(mode?.toLowerCase() ?? "");
|
|
242
|
-
}
|
|
243
|
-
async function commitReviewedStoryChanges(activeStory, implementationSummary) {
|
|
244
|
-
const cwd = state2.cwd;
|
|
245
|
-
if (!cwd) {
|
|
246
|
-
return {
|
|
247
|
-
ok: false,
|
|
248
|
-
code: "story_commit_missing_cwd",
|
|
249
|
-
message: "Cannot commit reviewed story because the TrailStep workflow cwd is unavailable.",
|
|
250
|
-
details: { storyPath: activeStory.path }
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
const insideWorkTree = await runGit2(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
254
|
-
if (!insideWorkTree.ok || insideWorkTree.stdout !== "true") {
|
|
255
|
-
return {
|
|
256
|
-
ok: false,
|
|
257
|
-
code: "story_commit_not_git_worktree",
|
|
258
|
-
message: "Cannot commit reviewed story because the workflow is not running inside a git worktree.",
|
|
259
|
-
details: {
|
|
260
|
-
storyPath: activeStory.path,
|
|
261
|
-
gitError: insideWorkTree.ok ? void 0 : insideWorkTree.error
|
|
255
|
+
if (key === "stories") {
|
|
256
|
+
const stories = parseStoryList(value);
|
|
257
|
+
if (stories) {
|
|
258
|
+
metadata.stories = stories;
|
|
262
259
|
}
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
const add = await runGit2(["add", "-A"], cwd);
|
|
266
|
-
if (!add.ok) {
|
|
267
|
-
return {
|
|
268
|
-
ok: false,
|
|
269
|
-
code: "story_commit_stage_failed",
|
|
270
|
-
message: "Cannot commit reviewed story because `git add -A` failed.",
|
|
271
|
-
details: { storyPath: activeStory.path, gitError: add.error }
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
const staged = await runGit2(["diff", "--cached", "--name-only"], cwd);
|
|
275
|
-
if (!staged.ok) {
|
|
276
|
-
return {
|
|
277
|
-
ok: false,
|
|
278
|
-
code: "story_commit_status_failed",
|
|
279
|
-
message: "Cannot commit reviewed story because staged changes could not be inspected.",
|
|
280
|
-
details: { storyPath: activeStory.path, gitError: staged.error }
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
if (staged.stdout.trim().length === 0) {
|
|
284
|
-
const baseline = await state2.get(
|
|
285
|
-
STORY_STATE_KEYS.activeStoryStartCommit
|
|
286
|
-
);
|
|
287
|
-
const head = await runGit2(["rev-parse", "HEAD"], cwd);
|
|
288
|
-
if (baseline?.commit && head.ok && head.stdout !== baseline.commit) {
|
|
289
|
-
return { ok: true };
|
|
260
|
+
continue;
|
|
290
261
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
details: {
|
|
296
|
-
storyPath: activeStory.path,
|
|
297
|
-
storyStartCommit: baseline?.commit,
|
|
298
|
-
head: head.ok ? head.stdout : void 0,
|
|
299
|
-
gitError: head.ok ? void 0 : head.error
|
|
262
|
+
if (key === "phases") {
|
|
263
|
+
const phases = parsePhaseList(value);
|
|
264
|
+
if (phases) {
|
|
265
|
+
metadata.phases = phases;
|
|
300
266
|
}
|
|
301
|
-
}
|
|
267
|
+
}
|
|
302
268
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
"-m",
|
|
310
|
-
commitBody(activeStory, implementationSummary, staged.stdout)
|
|
311
|
-
],
|
|
312
|
-
cwd
|
|
313
|
-
);
|
|
314
|
-
if (!commit.ok) {
|
|
315
|
-
return {
|
|
316
|
-
ok: false,
|
|
317
|
-
code: "story_commit_failed",
|
|
318
|
-
message: "The story passed review, but TrailStep could not create the story commit.",
|
|
319
|
-
details: { storyPath: activeStory.path, gitError: commit.error }
|
|
320
|
-
};
|
|
269
|
+
return metadata;
|
|
270
|
+
}
|
|
271
|
+
function parseAudience(value) {
|
|
272
|
+
const normalized = value.trim().toLowerCase();
|
|
273
|
+
if (normalized === "implementer" || normalized === "reviewer" || normalized === "all") {
|
|
274
|
+
return normalized;
|
|
321
275
|
}
|
|
322
|
-
return
|
|
276
|
+
return void 0;
|
|
323
277
|
}
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
extractStoryTitle(activeStory.content, completed.length + 1)
|
|
329
|
-
];
|
|
330
|
-
await state2.set(STORY_STATE_KEYS.completedStories, updatedCompleted);
|
|
331
|
-
const storyQueue = await state2.get(STORY_STATE_KEYS.storyQueue) ?? [];
|
|
332
|
-
const [nextStory, ...remaining] = storyQueue;
|
|
333
|
-
if (!nextStory) {
|
|
334
|
-
await state2.set(STORY_STATE_KEYS.activeStory, null);
|
|
335
|
-
await resetActiveStoryStartCommit();
|
|
336
|
-
const featureDoc = await state2.get("featureDoc");
|
|
337
|
-
const implementationDoc = await state2.get("implementationDoc");
|
|
338
|
-
const output = {
|
|
339
|
-
status: "implemented",
|
|
340
|
-
featureDocPath: featureDoc?.path ?? "",
|
|
341
|
-
implementationDocPath: implementationDoc?.path ?? "",
|
|
342
|
-
storyCount: updatedCompleted.length,
|
|
343
|
-
completedStories: updatedCompleted,
|
|
344
|
-
summary: `Implemented and reviewed ${updatedCompleted.length} ${updatedCompleted.length === 1 ? "story" : "stories"}.`
|
|
345
|
-
};
|
|
346
|
-
return done(output);
|
|
278
|
+
function parseStoryList(value) {
|
|
279
|
+
const items = splitMetadataList(value);
|
|
280
|
+
if (items.length === 0) {
|
|
281
|
+
return void 0;
|
|
347
282
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
283
|
+
if (items.length === 1 && items[0]?.toLowerCase() === "all") {
|
|
284
|
+
return "all";
|
|
285
|
+
}
|
|
286
|
+
return items.filter((item) => item.toLowerCase() !== "all");
|
|
287
|
+
}
|
|
288
|
+
function parsePhaseList(value) {
|
|
289
|
+
const items = splitMetadataList(value);
|
|
290
|
+
if (items.length === 0) {
|
|
291
|
+
return void 0;
|
|
292
|
+
}
|
|
293
|
+
if (items.length === 1 && items[0]?.toLowerCase() === "all") {
|
|
294
|
+
return "all";
|
|
295
|
+
}
|
|
296
|
+
const phases = items.filter(isStoryContextPhase);
|
|
297
|
+
return phases.length > 0 ? phases : void 0;
|
|
353
298
|
}
|
|
354
|
-
function
|
|
355
|
-
return
|
|
299
|
+
function splitMetadataList(value) {
|
|
300
|
+
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
356
301
|
}
|
|
357
|
-
function
|
|
302
|
+
function isStoryContextPhase(value) {
|
|
358
303
|
return [
|
|
359
|
-
"
|
|
360
|
-
"",
|
|
361
|
-
|
|
362
|
-
"",
|
|
363
|
-
"
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
"Staged files:",
|
|
367
|
-
stagedFiles.split("\n").filter((file) => file.length > 0).map((file) => `- ${file}`).join("\n")
|
|
368
|
-
].join("\n");
|
|
304
|
+
"deterministic-context-preflight",
|
|
305
|
+
"explore-story",
|
|
306
|
+
"write-red-tests",
|
|
307
|
+
"implement-green",
|
|
308
|
+
"validate-story",
|
|
309
|
+
"review-story-implementation"
|
|
310
|
+
].includes(value);
|
|
369
311
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
import { promptSections, section } from "@trailstep/authoring";
|
|
373
|
-
|
|
374
|
-
// src/feature-implementation/shared/feature-methodology.md?raw
|
|
375
|
-
var feature_methodology_default = "# Feature workflow methodology\n\n## Planning principles\n\n- Start from observable behavior and user value.\n- Preserve uncertainty explicitly; do not invent missing product decisions.\n- Prefer the smallest vertical slice that proves a complete path through the system.\n- Use tracer bullets to de-risk integration seams early with thin end-to-end behavior.\n- Every implementation story must begin with a behavioral red test that fails for the right reason.\n- The green phase should add only enough production code to pass the focused red test.\n- Refactor only after focused tests pass, and keep refactors scoped to the feature.\n- Dependencies are hard dependencies only: story B depends on story A when B cannot pass its own tests until A is complete.\n\n## Review scoring\n\nScore on a 1-5 integer scale:\n\n- 5: excellent; strongly aligned with TDD, vertical slicing, tracer-bullet risk reduction, dependency clarity, and local architecture.\n- 4: good; acceptable with only minor non-blocking improvements.\n- 3: incomplete; useful but needs material changes before continuing.\n- 2: poor; misses core methodology or important requirements.\n- 1: unusable; unsafe, incoherent, or not traceable to the feature.\n\nScores of 4 or 5 pass. Scores below 4 must list specific `requiredImprovements` so the next attempt can address them directly.\n";
|
|
376
|
-
|
|
377
|
-
// src/feature-implementation/shared/story-implementation-contract.md?raw
|
|
378
|
-
var story_implementation_contract_default = "# Story implementation contract\n\nThe story loop works one story at a time. Each story is self-contained. You will not see non-context overview text or other stories; you will see only the active story plus any shared `<context>` content prepended by the split-stories step.\n\n## Protect pre-existing uncommitted work\n\nRun `git status --short` before making any change.\n\n- The preferred execution mode is a TrailStep-created isolated worktree/branch. In that mode, a successful review is followed by an automatic story commit, so the next story starts from a clean diff boundary.\n- Treat every pre-existing uncommitted file as protected \u2014 it may be unfinished human work, another run's in-progress work, or a sign that isolation failed.\n- Do not run destructive cleanup: no `git reset`, `git checkout --`, broad `git clean`, or equivalent revert/format-away operations.\n- Do not delete or rewrite unrelated chunks in a file that already has changes.\n- If this story requires editing a file that is already dirty, preserve the existing changes and make only the minimal additional change the story requires.\n- If dirty work makes the story ambiguous or blocked, stop and report a blocked state instead of removing it.\n- Do not create git commits yourself unless the story explicitly requires it. TrailStep creates the reviewed story commit after the reviewer passes the story when auto-commit mode is enabled.\n\n## Implementer responsibilities\n\n- Implement only the current story and required dependency-compatible adjustments. Do not start later stories.\n- Use strict behavioral-red TDD: write or update a focused failing behavioral test first, confirm it fails for the intended reason when feasible, implement the smallest change to pass it, then refactor only after it's green.\n- Run the story's validation commands before reporting completion when feasible.\n- Do not report success until implementation and focused validation are done, or until a true blocked state is reached \u2014 report the blocker clearly instead of pretending the story is complete.\n\n## Reviewer responsibilities\n\n- Review only the active story diff from the recorded story baseline through HEAD plus the current uncommitted diff. Do not try to review every dirty change in the checkout as global context.\n- Inspect with read-only commands only (`git status --short`, the exact `git diff <storyStartCommit>..HEAD` command supplied in your prompt, and `git diff`). Do not run tests or other commands \u2014 the implementer is responsible for passing focused validation; this is an AI-only review.\n- Never edit code, delete files, revert files, run cleanup, stage files, commit files, or otherwise modify the working tree to isolate your review.\n- If unrelated dirty files, missing baseline information, or overlapping changes make the active story diff unreviewable, return a failing structured review that explains the isolation problem instead of changing the repository.\n- Review against: the story's acceptance criteria, TDD evidence and test quality, vertical-slice completeness, tracer-bullet alignment where applicable, minimal scope with no gold-plating, and integration with existing architecture/project conventions.\n\n## Completion\n\nA story is complete when code is changed as needed, behavioral tests exist, focused validation passes according to the implementer, and the implementation is well integrated with the existing architecture.\n";
|
|
379
|
-
|
|
380
|
-
// src/feature-implementation/review-story-implementation/prompt.ts
|
|
381
|
-
var fragments = {
|
|
382
|
-
methodology: feature_methodology_default.trimEnd(),
|
|
383
|
-
storyContract: story_implementation_contract_default.trimEnd()
|
|
384
|
-
};
|
|
385
|
-
function reviewStoryImplementationPrompt({
|
|
386
|
-
input
|
|
387
|
-
}) {
|
|
388
|
-
const committedDiffCommand = input.gitContext.storyStartCommit ? `git diff ${input.gitContext.storyStartCommit}..HEAD` : "git diff <missing-story-start-commit>..HEAD";
|
|
389
|
-
return promptSections(
|
|
390
|
-
fragments.methodology,
|
|
391
|
-
fragments.storyContract,
|
|
392
|
-
section("Story", input.currentStory.content),
|
|
393
|
-
section("Implementer summary", input.implementationSummary ?? "Not provided."),
|
|
394
|
-
section(
|
|
395
|
-
"Story review git baseline",
|
|
396
|
-
[
|
|
397
|
-
`Recorded story start commit: ${input.gitContext.storyStartCommit ?? "MISSING"}`,
|
|
398
|
-
`Inspect committed story changes with: \`${committedDiffCommand}\``,
|
|
399
|
-
"Inspect uncommitted working tree changes with: `git diff`",
|
|
400
|
-
"Inspect status with: `git status --short`",
|
|
401
|
-
input.gitContext.warnings.length > 0 ? [
|
|
402
|
-
"",
|
|
403
|
-
"Warnings / blocked context:",
|
|
404
|
-
...input.gitContext.warnings.map((warning) => `- ${warning}`)
|
|
405
|
-
].join("\n") : ""
|
|
406
|
-
].filter((line) => line.length > 0).join("\n")
|
|
407
|
-
),
|
|
408
|
-
section(
|
|
409
|
-
"Committed story changes (`git diff <storyStartCommit>..HEAD`)",
|
|
410
|
-
input.gitContext.committedDiff ?? "Not available."
|
|
411
|
-
),
|
|
412
|
-
section(
|
|
413
|
-
"Uncommitted working tree changes (`git diff`)",
|
|
414
|
-
input.gitContext.uncommittedDiff ?? "Not available."
|
|
415
|
-
),
|
|
416
|
-
section(
|
|
417
|
-
"Working tree status (`git status --short`)",
|
|
418
|
-
input.gitContext.statusShort ?? "Not available."
|
|
419
|
-
),
|
|
420
|
-
section(
|
|
421
|
-
"Task",
|
|
422
|
-
"Review only the active story slice represented by the recorded story start commit through HEAD, plus the current uncommitted working tree changes, against the story above and the implementer summary. Prefer the supplied diff/status sections; if you need to inspect locally, use read-only commands only (`git status --short`, the listed `git diff <storyStartCommit>..HEAD`, and `git diff`) \u2014 do not run tests, edit code, revert files, clean files, or try to isolate the diff by changing the repository. If unrelated dirty files, a missing/invalid baseline, or ambiguous context prevents a trustworthy review, say so in the structured review and require the implementer/workflow to fix the isolation; never remove changes yourself. Respond only with the structured review."
|
|
423
|
-
)
|
|
424
|
-
);
|
|
312
|
+
function hasRecognizedScopeMetadata(metadata) {
|
|
313
|
+
return Boolean(metadata.audience || metadata.stories || metadata.phases);
|
|
425
314
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (!reviewPasses(review)) {
|
|
433
|
-
if (input.attempt >= MAX_STORY_REVIEW_ATTEMPTS) {
|
|
434
|
-
return fail2({
|
|
435
|
-
code: "story_review_exhausted",
|
|
436
|
-
message: `Story failed review ${MAX_STORY_REVIEW_ATTEMPTS} times in a row (last score ${review.score}/5).`,
|
|
437
|
-
details: { review, storyPath: input.currentStory.path }
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
return implementStoryStep({
|
|
441
|
-
currentStory: input.currentStory,
|
|
442
|
-
previousStoryReview: review,
|
|
443
|
-
attempt: input.attempt + 1
|
|
444
|
-
});
|
|
315
|
+
function blockMatchesAudience(block, audience) {
|
|
316
|
+
return block.audience === audience || block.audience === "all";
|
|
317
|
+
}
|
|
318
|
+
function blockMatchesStory(block, storyContent) {
|
|
319
|
+
if (block.stories === "all") {
|
|
320
|
+
return true;
|
|
445
321
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
// src/feature-implementation/implement-story/prompt.ts
|
|
453
|
-
import { jsonSchema as jsonSchema3, list, promptSections as promptSections2, section as section2 } from "@trailstep/authoring";
|
|
454
|
-
|
|
455
|
-
// src/feature-implementation/shared/project-architecture-guidance.md?raw
|
|
456
|
-
var project_architecture_guidance_default = "# Architecture awareness\n\nBefore naming files, choosing package/module boundaries, picking integration seams, or ordering stories/dependencies, ground the decision in the target project's own conventions rather than inventing structure:\n\n- Look for and read the project's own architecture/convention docs at its root and nearby \u2014 `CLAUDE.md`, `AGENTS.md`, `.pi/rules/`, ADRs, architecture docs, or an equivalent README section.\n- Prefer existing module/package boundaries, naming conventions, and file layout patterns already established in the project over introducing new ones.\n- When the project's own guidance is silent or ambiguous on a non-trivial integration choice, say so explicitly as an assumption rather than silently picking one convention over another.\n";
|
|
457
|
-
|
|
458
|
-
// src/feature-implementation/implement-story/prompt.ts
|
|
459
|
-
var fragments2 = {
|
|
460
|
-
methodology: feature_methodology_default.trimEnd(),
|
|
461
|
-
architectureGuidance: project_architecture_guidance_default.trimEnd(),
|
|
462
|
-
storyContract: story_implementation_contract_default.trimEnd()
|
|
463
|
-
};
|
|
464
|
-
var implementStoryOutput = jsonSchema3({
|
|
465
|
-
type: "object",
|
|
466
|
-
properties: {
|
|
467
|
-
blocked: { type: "boolean" },
|
|
468
|
-
blockedReason: { type: "string" },
|
|
469
|
-
summary: {
|
|
470
|
-
type: "string",
|
|
471
|
-
description: "Files changed, commands run, and their results."
|
|
472
|
-
}
|
|
473
|
-
},
|
|
474
|
-
required: ["blocked", "summary"],
|
|
475
|
-
additionalProperties: false
|
|
476
|
-
});
|
|
477
|
-
function implementStoryPrompt({ input }) {
|
|
478
|
-
const taskBody = input.previousStoryReview === void 0 ? "Implement this story." : promptSections2(
|
|
479
|
-
`This is retry attempt ${input.attempt} for this story. A previous review scored the implementation ${input.previousStoryReview.score}/5: ${input.previousStoryReview.summary}`,
|
|
480
|
-
`Required improvements to address, without discarding what already works:
|
|
481
|
-
|
|
482
|
-
${list(input.previousStoryReview.requiredImprovements)}`
|
|
483
|
-
);
|
|
484
|
-
return promptSections2(
|
|
485
|
-
fragments2.methodology,
|
|
486
|
-
fragments2.architectureGuidance,
|
|
487
|
-
fragments2.storyContract,
|
|
488
|
-
section2("Story", input.currentStory.content),
|
|
489
|
-
section2(
|
|
490
|
-
"Task",
|
|
491
|
-
`${taskBody}
|
|
492
|
-
|
|
493
|
-
Set \`blocked: true\` with a clear \`blockedReason\` instead of guessing or pretending the story is complete. Otherwise set \`blocked: false\` and use \`summary\` to report files changed, commands run, and their results.`
|
|
494
|
-
)
|
|
495
|
-
);
|
|
322
|
+
if (!block.stories) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
const storyLabels = extractStoryLabels(storyContent).map(normalizeStoryLabel);
|
|
326
|
+
return block.stories.some((story) => storyLabels.includes(normalizeStoryLabel(story)));
|
|
496
327
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
agent: "implementer",
|
|
501
|
-
output: implementStoryOutput
|
|
502
|
-
}).do(async (promptOutput, input) => {
|
|
503
|
-
if (promptOutput.blocked) {
|
|
504
|
-
return fail3({
|
|
505
|
-
code: "story_blocked",
|
|
506
|
-
message: promptOutput.blockedReason ?? "Story implementation reported a blocked state.",
|
|
507
|
-
details: { storyPath: input.currentStory.path }
|
|
508
|
-
});
|
|
328
|
+
function blockMatchesPhase(block, phase) {
|
|
329
|
+
if (!block.phases || block.phases === "all" || block.phases.includes(phase)) {
|
|
330
|
+
return true;
|
|
509
331
|
}
|
|
510
|
-
return
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
332
|
+
return phase === "deterministic-context-preflight" && block.phases.includes("explore-story");
|
|
333
|
+
}
|
|
334
|
+
function extractStoryLabels(storyContent) {
|
|
335
|
+
const match = /^#{1,6}\s+(Story\s+\d+)(?::\s*(.+))?\s*$/im.exec(storyContent);
|
|
336
|
+
const ordinal = match?.[1]?.trim();
|
|
337
|
+
if (!ordinal) {
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
const title = match?.[2]?.trim();
|
|
341
|
+
return title ? [ordinal, `${ordinal}: ${title}`] : [ordinal];
|
|
342
|
+
}
|
|
343
|
+
function normalizeStoryLabel(value) {
|
|
344
|
+
return value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
345
|
+
}
|
|
517
346
|
|
|
518
347
|
// src/feature-implementation/split-implementation-stories/step.ts
|
|
519
|
-
var splitImplementationStoriesStep =
|
|
520
|
-
async ({
|
|
521
|
-
|
|
522
|
-
|
|
348
|
+
var splitImplementationStoriesStep = step2({ id: "split-implementation-stories" }).do(
|
|
349
|
+
async ({
|
|
350
|
+
featureDoc,
|
|
351
|
+
implementationDoc
|
|
352
|
+
}) => {
|
|
353
|
+
const contextStartCount = countStandaloneMarkerLines(
|
|
354
|
+
implementationDoc.content,
|
|
355
|
+
STORY_CONTEXT_START
|
|
356
|
+
);
|
|
357
|
+
const contextEndCount = countStandaloneMarkerLines(
|
|
358
|
+
implementationDoc.content,
|
|
359
|
+
STORY_CONTEXT_END
|
|
360
|
+
);
|
|
523
361
|
if (contextStartCount !== contextEndCount) {
|
|
524
|
-
return
|
|
362
|
+
return fail2({
|
|
525
363
|
code: "unbalanced_story_context",
|
|
526
364
|
message: `Found ${contextStartCount} \`${STORY_CONTEXT_START}\` marker(s) and ${contextEndCount} \`${STORY_CONTEXT_END}\` marker(s) in implementation-doc.md. Context blocks must be balanced.`,
|
|
527
365
|
details: { implementationDocPath: implementationDoc.path }
|
|
@@ -529,21 +367,25 @@ var splitImplementationStoriesStep = step4({ id: "split-implementation-stories"
|
|
|
529
367
|
}
|
|
530
368
|
const contextMatches = Array.from(implementationDoc.content.matchAll(storyContextPattern()));
|
|
531
369
|
if (contextMatches.length !== contextStartCount) {
|
|
532
|
-
return
|
|
370
|
+
return fail2({
|
|
533
371
|
code: "invalid_story_context_order",
|
|
534
372
|
message: `Every \`${STORY_CONTEXT_START}\` marker in implementation-doc.md must be followed by a matching \`${STORY_CONTEXT_END}\` marker.`,
|
|
535
373
|
details: { implementationDocPath: implementationDoc.path }
|
|
536
374
|
});
|
|
537
375
|
}
|
|
538
|
-
const
|
|
539
|
-
|
|
376
|
+
const parsedContextBlocks = parseStoryContextBlocks(
|
|
377
|
+
contextMatches.map(([, context]) => context ?? "")
|
|
378
|
+
);
|
|
540
379
|
const contentWithoutContextBlocks = implementationDoc.content.replace(
|
|
541
380
|
storyContextPattern(),
|
|
542
381
|
""
|
|
543
382
|
);
|
|
544
|
-
const chunks = contentWithoutContextBlocks.split(STORY_BOUNDARY).slice(1).map((chunk) => chunk.trim()).filter((chunk) => chunk.length > 0)
|
|
383
|
+
const chunks = contentWithoutContextBlocks.split(STORY_BOUNDARY).slice(1).map((chunk) => chunk.trim()).filter((chunk) => chunk.length > 0);
|
|
384
|
+
const storyContexts = chunks.map(
|
|
385
|
+
(chunk) => buildStoryPhaseContexts(parsedContextBlocks, chunk)
|
|
386
|
+
);
|
|
545
387
|
if (chunks.length === 0) {
|
|
546
|
-
return
|
|
388
|
+
return fail2({
|
|
547
389
|
code: "no_stories_found",
|
|
548
390
|
message: `No \`${STORY_BOUNDARY}\`-delimited stories found in implementation-doc.md.`,
|
|
549
391
|
details: { implementationDocPath: implementationDoc.path }
|
|
@@ -554,201 +396,280 @@ var splitImplementationStoriesStep = step4({ id: "split-implementation-stories"
|
|
|
554
396
|
storyDocs.push(await document(chunk));
|
|
555
397
|
}
|
|
556
398
|
const [firstStory, ...remaining] = storyDocs;
|
|
557
|
-
|
|
558
|
-
await
|
|
559
|
-
await
|
|
560
|
-
await
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
);
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const index = value.indexOf(needle, offset);
|
|
572
|
-
if (index === -1) {
|
|
573
|
-
return count;
|
|
399
|
+
const [firstStoryContext = {}, ...remainingStoryContexts] = storyContexts;
|
|
400
|
+
await state2.set(STORY_STATE_KEYS.storyQueue, remaining);
|
|
401
|
+
await state2.set(STORY_STATE_KEYS.storyContextQueue, remainingStoryContexts);
|
|
402
|
+
await state2.set(STORY_STATE_KEYS.completedStories, []);
|
|
403
|
+
await state2.set(STORY_STATE_KEYS.activeStory, firstStory);
|
|
404
|
+
await state2.set(STORY_STATE_KEYS.activeStoryContext, firstStoryContext);
|
|
405
|
+
await state2.set(STORY_STATE_KEYS.activePhase, "story-router");
|
|
406
|
+
await resetStoryLocalState({
|
|
407
|
+
preserveActiveStoryStartCommit: true,
|
|
408
|
+
preserveLatestStoryRouterState: true,
|
|
409
|
+
preserveStoryBaseline: true
|
|
410
|
+
});
|
|
411
|
+
if (featureDoc && await shouldPauseForPlanningCheckpoint()) {
|
|
412
|
+
return planningCheckpointStep({ featureDoc, implementationDoc });
|
|
574
413
|
}
|
|
575
|
-
|
|
576
|
-
offset = index + needle.length;
|
|
414
|
+
return storyRouterStep({ reason: "start-story", currentStory: firstStory });
|
|
577
415
|
}
|
|
416
|
+
);
|
|
417
|
+
function buildStoryPhaseContexts(blocks, storyContent) {
|
|
418
|
+
const phases = [
|
|
419
|
+
"deterministic-context-preflight",
|
|
420
|
+
"explore-story",
|
|
421
|
+
"write-red-tests",
|
|
422
|
+
"implement-green",
|
|
423
|
+
"validate-story",
|
|
424
|
+
"review-story-implementation"
|
|
425
|
+
];
|
|
426
|
+
return Object.fromEntries(
|
|
427
|
+
phases.map((phase) => [
|
|
428
|
+
phase,
|
|
429
|
+
selectStoryContext({
|
|
430
|
+
blocks,
|
|
431
|
+
storyContent,
|
|
432
|
+
audience: phase === "review-story-implementation" ? "reviewer" : "implementer",
|
|
433
|
+
phase
|
|
434
|
+
})
|
|
435
|
+
])
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
function countStandaloneMarkerLines(value, marker) {
|
|
439
|
+
return Array.from(value.matchAll(standaloneMarkerLinePattern(marker))).length;
|
|
440
|
+
}
|
|
441
|
+
function standaloneMarkerLinePattern(marker) {
|
|
442
|
+
return new RegExp(`^[ \\t]*${escapeRegExp(marker)}[ \\t]*$`, "gm");
|
|
578
443
|
}
|
|
579
444
|
function storyContextPattern() {
|
|
580
445
|
return new RegExp(
|
|
581
|
-
|
|
582
|
-
"
|
|
446
|
+
`^[ \\t]*${escapeRegExp(STORY_CONTEXT_START)}[ \\t]*\\r?\\n([\\s\\S]*?)^[ \\t]*${escapeRegExp(STORY_CONTEXT_END)}[ \\t]*$`,
|
|
447
|
+
"gm"
|
|
583
448
|
);
|
|
584
449
|
}
|
|
585
|
-
function prependSharedContext(story, sharedContext) {
|
|
586
|
-
if (sharedContext.length === 0) {
|
|
587
|
-
return story;
|
|
588
|
-
}
|
|
589
|
-
return ["## Shared implementation context", sharedContext, "## Active story", story].join("\n\n");
|
|
590
|
-
}
|
|
591
450
|
function escapeRegExp(value) {
|
|
592
451
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
593
452
|
}
|
|
594
453
|
|
|
595
|
-
// src/feature-implementation/
|
|
596
|
-
import {
|
|
454
|
+
// src/feature-implementation/slice-implementation-stories/prompt.ts
|
|
455
|
+
import { promptSections, section } from "@trailstep/authoring";
|
|
456
|
+
|
|
457
|
+
// src/feature-implementation/shared/feature-methodology.md?raw
|
|
458
|
+
var feature_methodology_default = "# Feature workflow methodology\n\n## Planning principles\n\n- Start from observable behavior and user value.\n- Preserve uncertainty explicitly; do not invent missing product decisions.\n- Prefer the smallest vertical slice that proves a complete path through the system.\n- Slice by integration-visible value and risk, not by numeric story budgets.\n- Split a story when it contains separable user-visible/integration-visible outcomes, independent risk seams, or hard dependency boundaries that can each be proven by distinct behavioral tests.\n- Bundle work into the same story when splitting would create micro-stories, docs/package-metadata-only tasks, duplicate validation-only paths, or horizontal setup/refactor/test-only tasks that are not independently observable.\n- Use tracer bullets to de-risk integration seams early with thin end-to-end behavior.\n- Every implementation story must begin with a behavioral red test that fails for the right reason.\n- The green phase should add only enough production code to pass the focused red test.\n- Refactor only after focused tests pass, and keep refactors scoped to the feature.\n- Dependencies are hard dependencies only: story B depends on story A when B cannot pass its own tests until A is complete.\n\n## Review scoring\n\nScore on a 1-5 integer scale:\n\n- 5: excellent; strongly aligned with TDD, cost-aware vertical slicing, tracer-bullet risk reduction, dependency clarity, and local architecture.\n- 4: good; acceptable with only minor non-blocking improvements.\n- 3: incomplete; useful but needs material changes before continuing.\n- 2: poor; misses core methodology or important requirements.\n- 1: unusable; unsafe, incoherent, or not traceable to the feature.\n\nScores of 4 or 5 pass. Scores below 4 must list specific `requiredImprovements` so the next attempt can address them directly.\n";
|
|
597
459
|
|
|
598
460
|
// src/feature-implementation/shared/implementation-doc-format.md?raw
|
|
599
|
-
var implementation_doc_format_default = "# Implementation doc format\n\n`implementation-doc.md` converts `feature-doc.md` into implementation-ready stories.\n\nUse this structure:\n\n```markdown\n# Implementation Plan: <Feature Name>\n\n## Overview for planner/reviewer\n\nOptional high-level planning notes that do not need to be seen by story implementers.\n\n<context>\n\n## Shared
|
|
461
|
+
var implementation_doc_format_default = "# Implementation doc format\n\n`implementation-doc.md` converts `feature-doc.md` into implementation-ready stories.\n\nUse this structure:\n\n```markdown\n# Implementation Plan: <Feature Name>\n\n## Overview for planner/reviewer\n\nOptional high-level planning notes that do not need to be seen by story implementers.\n\n<context>\naudience: implementer\nstories: all\nphases: deterministic-context-preflight\n\n## Shared Implementer Exploration Context\n\nPut only cross-story details that an implementer needs during story exploration: outcome constraints, architecture/integration notes, repository conventions, shared validation command hints, assumptions, and hard dependency graph notes. Do not put reviewer-only instructions, full implementation-doc process guidance, or unrelated story details here.\n\nScoped context blocks are stored separately from story bodies. They are not prepended to every split story. The router passes matching implementer context only to the narrow phase that needs it.\n\n</context>\n\n<!-- trailstep-story-boundary -->\n\n### Story 001: <title>\n\nDependencies: none\n\n#### Goal\n\n#### User-Visible / Integration-Visible Slice\n\n#### Acceptance Criteria\n\n#### Red Phase\n\n- Test file:\n- Test case:\n- Expected failing assertion:\n\n#### Green Phase\n\n#### Refactor Phase\n\n#### Validation Commands\n\n#### Notes for Implementer\n\n<!-- trailstep-story-boundary -->\n\n### Story 002: <title>\n\nDependencies: Story 001\n\n...\n```\n\nRules:\n\n- Text above the first `<!-- trailstep-story-boundary -->` is never part of an active story body. Only scoped `<context>` ... `</context>` blocks with recognized metadata can be selected as separate phase context.\n- Context blocks must begin with metadata lines before the first blank line. Recognized keys are `audience: implementer|reviewer|all`, `stories: all|Story 001|Story 001: Title`, and `phases: all|deterministic-context-preflight|explore-story|write-red-tests|implement-green|validate-story|review-story-implementation`.\n- Unscoped context blocks are ignored instead of being blindly prepended to every story. If a detail applies to only one story, prefer putting it directly in that story. No story-critical detail may live only in non-context overview text.\n- Every `<context>` marker must have a matching `</context>` marker. Context markers are recognized only when the marker is the sole non-whitespace content on its line; inline mentions of `<context>` in prose are ignored by the splitter.\n- Every story must start right after a `<!-- trailstep-story-boundary -->` line, on its own line, with nothing else on that line. Splitting is mechanical and depends on this exact marker \u2014 do not use markdown headings alone to separate stories, and do not add or omit a boundary marker except between/before stories.\n- Stories must be topologically ordered.\n- Every story must be implementation-ready and self-contained, written as instructions to an implementer who will not see this file, only their own story's content.\n- Avoid horizontal stories unless they are embedded in an observable vertical slice.\n- Split versus bundle by delivery value, risk, and validation distinctness, not by numeric story budgets.\n- Split a candidate story when it contains separable integration-visible outcomes, independent high-risk seams, or true hard dependency boundaries that can each be proven by a distinct behavioral red test.\n- Bundle small changes when splitting would produce micro-stories, duplicate validation paths, docs/package-metadata-only stories, package metadata wiring without behavior, or horizontal setup/refactor/test-only work that is not independently observable.\n- Keep slicing rationale in the overview or reviewer context only; do not put split/bundle justification inside story bodies shown to implementers.\n- Name concrete files/tests/commands when they can be inferred from the repository.\n- Preserve uncertainty as assumptions or blocked states instead of guessing.\n";
|
|
600
462
|
|
|
601
|
-
// src/feature-implementation/
|
|
602
|
-
var
|
|
463
|
+
// src/feature-implementation/slice-implementation-stories/prompt.ts
|
|
464
|
+
var fragments = {
|
|
603
465
|
methodology: feature_methodology_default.trimEnd(),
|
|
604
466
|
implementationDocFormat: implementation_doc_format_default.trimEnd()
|
|
605
467
|
};
|
|
606
|
-
function
|
|
468
|
+
function sliceImplementationStoriesPrompt({
|
|
469
|
+
input
|
|
470
|
+
}) {
|
|
471
|
+
return promptSections(
|
|
472
|
+
fragments.methodology,
|
|
473
|
+
fragments.implementationDocFormat,
|
|
474
|
+
section("Feature doc", input.featureDoc.content),
|
|
475
|
+
section("Reviewed implementation strategy", input.implementationStrategy.content),
|
|
476
|
+
section(
|
|
477
|
+
"Task",
|
|
478
|
+
"Generate only the implementation-ready story document to be mechanically split for execution. Consume the feature doc and reviewed strategy, but do not copy broad architecture/risk planning into overview prose that implementers will not see. Use `<!-- trailstep-story-boundary -->` only to delimit story bodies. Keep each story self-contained for its implementer; put genuinely shared implementer-only phase context in balanced `<context>` blocks with metadata. Do not add a separate strategy section, reviewer notes, or non-story planning artifact content."
|
|
479
|
+
)
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/feature-implementation/slice-implementation-stories/step.ts
|
|
484
|
+
var sliceImplementationStoriesStep = step3({ id: "slice-implementation-stories" }).prompt(sliceImplementationStoriesPrompt, {
|
|
485
|
+
agent: "slicer",
|
|
486
|
+
output: Document
|
|
487
|
+
}).do(async (implementationDoc, input) => {
|
|
488
|
+
await state3.set("implementationDoc", implementationDoc);
|
|
489
|
+
return splitImplementationStoriesStep({ featureDoc: input.featureDoc, implementationDoc });
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// src/feature-implementation/review-implementation-strategy/prompt.ts
|
|
493
|
+
import { list, promptSections as promptSections2, section as section2 } from "@trailstep/authoring";
|
|
494
|
+
|
|
495
|
+
// src/feature-implementation/shared/implementation-strategy-format.md?raw
|
|
496
|
+
var implementation_strategy_format_default = "# Implementation strategy format\n\n`implementation-strategy.md` captures architecture and risk planning before executable story slicing.\n\nUse this structure:\n\n```markdown\n# Implementation Strategy: <Feature Name>\n\n## Objective and Scope\n\n## Architecture Approach\n\n## Key Files and Integration Points\n\n## Risk Plan\n\n## Testing Strategy\n\n## Slicing Guidance\n```\n\nRules:\n\n- Do not include `<!-- trailstep-story-boundary -->` or any other TrailStep story boundary markup.\n- Do not write executable story bodies in this artifact.\n- Focus on architecture, risk, sequencing rationale, tracer-bullet path, hard dependencies, and validation strategy.\n- Preserve uncertainty as assumptions or explicit risks instead of guessing.\n";
|
|
497
|
+
|
|
498
|
+
// src/feature-implementation/review-implementation-strategy/prompt.ts
|
|
499
|
+
var fragments2 = {
|
|
500
|
+
methodology: feature_methodology_default.trimEnd(),
|
|
501
|
+
implementationStrategyFormat: implementation_strategy_format_default.trimEnd()
|
|
502
|
+
};
|
|
503
|
+
function reviewImplementationStrategyPrompt({
|
|
607
504
|
input
|
|
608
505
|
}) {
|
|
609
|
-
const reviewCriteria =
|
|
506
|
+
const reviewCriteria = list([
|
|
610
507
|
"traceability to the feature doc",
|
|
611
|
-
"
|
|
612
|
-
"
|
|
508
|
+
"sound architecture and integration planning",
|
|
509
|
+
"explicit risk and uncertainty handling",
|
|
613
510
|
"a clear tracer-bullet strategy",
|
|
614
|
-
"hard dependency
|
|
615
|
-
"
|
|
616
|
-
"
|
|
617
|
-
"
|
|
618
|
-
"correct use of the `<!-- trailstep-story-boundary -->` marker between stories, and that no story-critical detail lives only in non-context overview text above the first marker"
|
|
511
|
+
"hard dependency reasoning without over-constraining",
|
|
512
|
+
"testing and validation strategy",
|
|
513
|
+
"useful slicing guidance for a later story slicer",
|
|
514
|
+
"absence of `<!-- trailstep-story-boundary -->` markers or executable story bodies"
|
|
619
515
|
]);
|
|
620
|
-
return
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
516
|
+
return promptSections2(
|
|
517
|
+
fragments2.methodology,
|
|
518
|
+
fragments2.implementationStrategyFormat,
|
|
519
|
+
section2("Feature doc", input.featureDoc.content),
|
|
520
|
+
section2("Implementation strategy under review", input.implementationStrategy.content),
|
|
521
|
+
section2(
|
|
626
522
|
"Task",
|
|
627
|
-
`Critically review the implementation
|
|
523
|
+
`Critically review the implementation strategy above against the feature doc and the methodology. Review for:
|
|
628
524
|
|
|
629
525
|
${reviewCriteria}
|
|
630
526
|
|
|
631
|
-
Do not edit the
|
|
527
|
+
Do not edit the strategy. Respond only with the structured review.`
|
|
632
528
|
)
|
|
633
529
|
);
|
|
634
530
|
}
|
|
635
531
|
|
|
636
|
-
// src/feature-implementation/review-implementation-
|
|
637
|
-
var
|
|
532
|
+
// src/feature-implementation/review-implementation-strategy/step.ts
|
|
533
|
+
var reviewImplementationStrategyStep = step4({ id: "review-implementation-strategy" }).prompt(reviewImplementationStrategyPrompt, {
|
|
638
534
|
agent: "reviewer",
|
|
639
535
|
output: reviewOutput
|
|
640
536
|
}).do((review, input) => {
|
|
641
537
|
if (reviewPasses(review)) {
|
|
642
|
-
return
|
|
538
|
+
return sliceImplementationStoriesStep({
|
|
539
|
+
featureDoc: input.featureDoc,
|
|
540
|
+
implementationStrategy: input.implementationStrategy
|
|
541
|
+
});
|
|
643
542
|
}
|
|
644
543
|
if (input.attempt >= MAX_IMPLEMENTATION_DOC_REVIEW_ATTEMPTS) {
|
|
645
|
-
return
|
|
646
|
-
code: "
|
|
647
|
-
message: `implementation-
|
|
648
|
-
details: { review,
|
|
544
|
+
return fail3({
|
|
545
|
+
code: "implementation_strategy_review_exhausted",
|
|
546
|
+
message: `implementation-strategy.md failed review ${MAX_IMPLEMENTATION_DOC_REVIEW_ATTEMPTS} times in a row (last score ${review.score}/5).`,
|
|
547
|
+
details: { review, implementationStrategyPath: input.implementationStrategy.path }
|
|
649
548
|
});
|
|
650
549
|
}
|
|
651
|
-
return
|
|
550
|
+
return createOrImproveImplementationStrategyStep({
|
|
652
551
|
featureDoc: input.featureDoc,
|
|
653
552
|
previousReview: review,
|
|
654
553
|
attempt: input.attempt + 1
|
|
655
554
|
});
|
|
656
555
|
});
|
|
657
556
|
|
|
658
|
-
// src/feature-implementation/create-or-improve-implementation-
|
|
659
|
-
import { list as
|
|
660
|
-
|
|
557
|
+
// src/feature-implementation/create-or-improve-implementation-strategy/prompt.ts
|
|
558
|
+
import { list as list2, promptSections as promptSections3, section as section3 } from "@trailstep/authoring";
|
|
559
|
+
|
|
560
|
+
// src/feature-implementation/shared/project-architecture-guidance.md?raw
|
|
561
|
+
var project_architecture_guidance_default = "# Architecture awareness\n\nBefore naming files, choosing package/module boundaries, picking integration seams, or ordering stories/dependencies, ground the decision in the target project's own conventions rather than inventing structure:\n\n- Look for and read the project's own architecture/convention docs at its root and nearby \u2014 `CLAUDE.md`, `AGENTS.md`, `.pi/rules/`, ADRs, architecture docs, or an equivalent README section.\n- Prefer existing module/package boundaries, naming conventions, and file layout patterns already established in the project over introducing new ones.\n- When the project's own guidance is silent or ambiguous on a non-trivial integration choice, say so explicitly as an assumption rather than silently picking one convention over another.\n";
|
|
562
|
+
|
|
563
|
+
// src/feature-implementation/create-or-improve-implementation-strategy/prompt.ts
|
|
564
|
+
var fragments3 = {
|
|
661
565
|
methodology: feature_methodology_default.trimEnd(),
|
|
662
566
|
architectureGuidance: project_architecture_guidance_default.trimEnd(),
|
|
663
|
-
|
|
567
|
+
implementationStrategyFormat: implementation_strategy_format_default.trimEnd()
|
|
664
568
|
};
|
|
665
|
-
function
|
|
569
|
+
function createOrImproveImplementationStrategyPrompt({
|
|
666
570
|
input
|
|
667
571
|
}) {
|
|
668
|
-
const taskBody = input.previousReview === void 0 ? "Create `implementation-
|
|
669
|
-
`This is improvement attempt ${input.attempt}. A previous review scored this
|
|
572
|
+
const taskBody = input.previousReview === void 0 ? "Create `implementation-strategy.md` from the feature doc above. This is architecture/risk planning only, not executable story generation." : promptSections3(
|
|
573
|
+
`This is improvement attempt ${input.attempt}. A previous review scored this strategy ${input.previousReview.score}/5: ${input.previousReview.summary}`,
|
|
670
574
|
`Required improvements to address, without discarding what already works:
|
|
671
575
|
|
|
672
|
-
${
|
|
576
|
+
${list2(input.previousReview.requiredImprovements)}`
|
|
673
577
|
);
|
|
674
|
-
return
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
578
|
+
return promptSections3(
|
|
579
|
+
fragments3.methodology,
|
|
580
|
+
fragments3.architectureGuidance,
|
|
581
|
+
fragments3.implementationStrategyFormat,
|
|
582
|
+
section3("Feature doc", input.featureDoc.content),
|
|
583
|
+
section3(
|
|
680
584
|
"Task",
|
|
681
585
|
`${taskBody}
|
|
682
586
|
|
|
683
|
-
|
|
587
|
+
Plan the implementation architecture, risks, tracer-bullet path, hard dependencies, test strategy, and slicing guidance. Do not include story boundary markers and do not write full story bodies; a separate slicer will turn this strategy into implementation-ready stories.`
|
|
684
588
|
)
|
|
685
589
|
);
|
|
686
590
|
}
|
|
687
591
|
|
|
688
|
-
// src/feature-implementation/create-or-improve-implementation-
|
|
689
|
-
var
|
|
690
|
-
id: "create-or-improve-implementation-
|
|
691
|
-
}).prompt(
|
|
592
|
+
// src/feature-implementation/create-or-improve-implementation-strategy/step.ts
|
|
593
|
+
var createOrImproveImplementationStrategyStep = step5({
|
|
594
|
+
id: "create-or-improve-implementation-strategy"
|
|
595
|
+
}).prompt(createOrImproveImplementationStrategyPrompt, {
|
|
692
596
|
agent: "planner",
|
|
693
|
-
output:
|
|
694
|
-
}).do(async (
|
|
695
|
-
await state4.set("
|
|
696
|
-
return
|
|
597
|
+
output: Document2
|
|
598
|
+
}).do(async (implementationStrategy, input) => {
|
|
599
|
+
await state4.set("implementationStrategy", implementationStrategy);
|
|
600
|
+
return reviewImplementationStrategyStep({
|
|
697
601
|
featureDoc: input.featureDoc,
|
|
698
|
-
|
|
602
|
+
implementationStrategy,
|
|
699
603
|
attempt: input.attempt
|
|
700
604
|
});
|
|
701
605
|
});
|
|
702
606
|
|
|
703
607
|
// src/feature-implementation/create-feature-doc/prompt.ts
|
|
704
|
-
import { promptSections as
|
|
608
|
+
import { promptSections as promptSections4, section as section4 } from "@trailstep/authoring";
|
|
705
609
|
|
|
706
610
|
// src/feature-implementation/shared/feature-doc-format.md?raw
|
|
707
|
-
var feature_doc_format_default = "# Feature doc format\n\n`feature-doc.md` is an intermediate
|
|
611
|
+
var feature_doc_format_default = "# Feature doc format\n\n`feature-doc.md` is an intermediate, scope-preserving feature description used as input to implementation planning. It should distill the real request without turning it into an idealized product specification.\n\nUse this structure:\n\n```markdown\n# <Feature Name>\n\n## Must-Have Outcome\n\nSummarize the smallest confirmed outcome the user actually requested. Prefer the user's concrete wording and avoid expanding into adjacent product vision.\n\n## Users / Jobs / Pain\n\n## In-Scope Proposed Behavior\n\nDescribe only behavior needed for the must-have outcome. If something seems useful but was not requested or is uncertain, keep it out of this section.\n\n## Explicit Non-Goals\n\nList non-goals, exclusions, and scope limits explicitly stated by the conversation. Include strongly implied exclusions only when tied to clear evidence. If none were stated, say so instead of inventing exclusions.\n\n## Examples and Acceptance Criteria\n\n## Edge Cases and Failure Modes\n\n## Affected Areas and Integration Points\n\n## Testing Expectations\n\n## Documentation / Migration / Rollout Notes\n\n## Assumptions and Open Questions\n\nCapture missing product decisions, uncertain details, and assumptions required for planning. Do not resolve uncertainty by inventing answers.\n\n## Conversation Context Worth Preserving\n\nRecord constraints, rationale, terminology, user preferences, tradeoffs, and notable uncertainty that a planner or implementer should know without rereading the full conversation.\n\n## Optional / Future Ideas\n\nList explicitly mentioned nice-to-haves, possible follow-ups, or adjacent ideas separately from the current implementation scope. These ideas are not acceptance criteria unless the conversation explicitly makes them must-haves.\n\n## Structured Scope Facts\n\nProvide a compact, planning-friendly fact list without introducing new scope. Use `- Key: value` bullets for confirmed actors, in-scope surfaces, data/entities, integrations, constraints, explicit exclusions, and open decisions that later planning should preserve mechanically.\n```\n\nThe document should be detailed enough that another agent can plan implementation without reading the original conversation, while still preserving important uncertainty and scope boundaries.\n";
|
|
708
612
|
|
|
709
613
|
// src/feature-implementation/create-feature-doc/prompt.ts
|
|
710
|
-
var
|
|
614
|
+
var fragments4 = {
|
|
711
615
|
methodology: feature_methodology_default.trimEnd(),
|
|
712
616
|
featureDocFormat: feature_doc_format_default.trimEnd()
|
|
713
617
|
};
|
|
714
618
|
function createFeatureDocPrompt({ input }) {
|
|
715
|
-
return
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
619
|
+
return promptSections4(
|
|
620
|
+
fragments4.methodology,
|
|
621
|
+
fragments4.featureDocFormat,
|
|
622
|
+
section4("Conversation / feature request", input.conversation),
|
|
623
|
+
section4(
|
|
624
|
+
"Distillation guardrails",
|
|
625
|
+
[
|
|
626
|
+
"Treat the conversation/request as evidence, not as permission to finish a product vision.",
|
|
627
|
+
"Preserve the user's actual must-have outcome, explicit constraints, uncertainty, and explicit non-goals; include implied scope limits only when tied to clear evidence.",
|
|
628
|
+
"Do not invent scope, acceptance criteria, integrations, personas, product decisions, or polish that the user did not ask for.",
|
|
629
|
+
"When a broad request includes brainstorming, ambitions, or adjacent possibilities, keep the confirmed current scope in the must-have/in-scope sections and move nonessential ideas to Optional / Future Ideas.",
|
|
630
|
+
"Use Structured Scope Facts for compact planning metadata copied or derived directly from the conversation; do not let that fact list add new requirements.",
|
|
631
|
+
"If a detail is unknown, record it as an assumption or open question instead of choosing an answer.",
|
|
632
|
+
"A later planning agent may implement the must-have and in-scope sections; optional/future ideas are context only and must not become story scope unless explicitly promoted by the user."
|
|
633
|
+
].join("\n")
|
|
634
|
+
),
|
|
635
|
+
section4(
|
|
720
636
|
"Task",
|
|
721
|
-
|
|
637
|
+
"Write `feature-doc.md` following the format above, based on the conversation/request. Make it detailed enough that another agent can plan implementation without reading the original conversation, while preserving scope boundaries and uncertainty. Do not inflate the request into an idealized product spec."
|
|
722
638
|
)
|
|
723
639
|
);
|
|
724
640
|
}
|
|
725
641
|
|
|
726
642
|
// src/feature-implementation/create-feature-doc/step.ts
|
|
727
|
-
var createFeatureDocStep =
|
|
643
|
+
var createFeatureDocStep = step6({ id: "create-feature-doc" }).prompt(createFeatureDocPrompt, {
|
|
728
644
|
agent: "featureWriter",
|
|
729
|
-
output:
|
|
645
|
+
output: Document3
|
|
730
646
|
}).do(async (featureDoc) => {
|
|
731
647
|
await state5.set("featureDoc", featureDoc);
|
|
732
|
-
return
|
|
648
|
+
return createOrImproveImplementationStrategyStep({ featureDoc, attempt: 1 });
|
|
733
649
|
});
|
|
734
650
|
|
|
735
|
-
// src/feature-implementation/
|
|
736
|
-
|
|
737
|
-
|
|
651
|
+
// src/feature-implementation/initialize-take-it-away/step.ts
|
|
652
|
+
var initializeTakeItAwayStep = step7({ id: "initialize-take-it-away" }).do(
|
|
653
|
+
async (input) => {
|
|
654
|
+
await state6.set(STORY_STATE_KEYS.workflowOptions, normalizeTakeItAwayWorkflowOptions(input));
|
|
655
|
+
await state6.set(STORY_STATE_KEYS.workflowWarnings, []);
|
|
656
|
+
return createFeatureDocStep(input);
|
|
657
|
+
}
|
|
658
|
+
);
|
|
738
659
|
|
|
739
660
|
// src/grill-it-away/grill/prompt.ts
|
|
740
|
-
import { promptSections as
|
|
661
|
+
import { promptSections as promptSections5, section as section5 } from "@trailstep/authoring";
|
|
741
662
|
function grillPrompt() {
|
|
742
|
-
return
|
|
743
|
-
|
|
663
|
+
return promptSections5(
|
|
664
|
+
section5(
|
|
744
665
|
"Role",
|
|
745
666
|
"You are the interactive front door for starting a new feature in the current project. Open by asking the user what they want to build."
|
|
746
667
|
),
|
|
747
|
-
|
|
668
|
+
section5(
|
|
748
669
|
"Task",
|
|
749
670
|
"Keep asking follow-up questions until you are confident you understand the feature request well enough to hand off to a planning agent. There is no fixed checklist of required topics \u2014 use your judgment, and do not end the conversation prematurely just to finish quickly."
|
|
750
671
|
),
|
|
751
|
-
|
|
672
|
+
section5(
|
|
752
673
|
"Output",
|
|
753
674
|
"Your structured output's `conversation` field must contain the full raw transcript of the conversation so far, not a summary. Preserve as much of the actual back-and-forth as possible."
|
|
754
675
|
)
|
|
@@ -760,13 +681,13 @@ var grillStep = step8({ id: "grill" }).prompt(grillPrompt, {
|
|
|
760
681
|
agent: "grillingAgent",
|
|
761
682
|
mode: "interactive",
|
|
762
683
|
output: takeItAwayInput
|
|
763
|
-
}).do((output) =>
|
|
684
|
+
}).do((output) => initializeTakeItAwayStep(output));
|
|
764
685
|
|
|
765
686
|
// src/grill-it-away/workflow.ts
|
|
766
687
|
var grillItAway = defineWorkflow({
|
|
767
688
|
id: "grill-it-away",
|
|
768
689
|
description: "Interactively grills the user until it understands the requested feature.",
|
|
769
|
-
inputShape:
|
|
690
|
+
inputShape: shape({}),
|
|
770
691
|
outputShape: takeItAwayOutput,
|
|
771
692
|
agents: {
|
|
772
693
|
grillingAgent: {
|
|
@@ -774,26 +695,7 @@ var grillItAway = defineWorkflow({
|
|
|
774
695
|
thinking: "medium",
|
|
775
696
|
description: "Grills the user until it understands the requested feature."
|
|
776
697
|
},
|
|
777
|
-
|
|
778
|
-
size: "medium",
|
|
779
|
-
thinking: "medium",
|
|
780
|
-
description: "Turns a conversation/feature request into a standalone feature doc."
|
|
781
|
-
},
|
|
782
|
-
planner: {
|
|
783
|
-
size: "large",
|
|
784
|
-
thinking: "high",
|
|
785
|
-
description: "Creates or improves the implementation doc: architecture-aware, TDD/vertical-slice/tracer-bullet story design."
|
|
786
|
-
},
|
|
787
|
-
reviewer: {
|
|
788
|
-
size: "large",
|
|
789
|
-
thinking: "high",
|
|
790
|
-
description: "Reviews isolated implementation docs and story diffs against methodology."
|
|
791
|
-
},
|
|
792
|
-
implementer: {
|
|
793
|
-
size: "medium",
|
|
794
|
-
thinking: "medium",
|
|
795
|
-
description: "Implements one story at a time using strict behavioral-red TDD."
|
|
796
|
-
}
|
|
698
|
+
...featureImplementationAgents
|
|
797
699
|
},
|
|
798
700
|
start() {
|
|
799
701
|
return grillStep();
|
|
@@ -807,30 +709,9 @@ var takeItAway = defineWorkflow2({
|
|
|
807
709
|
description: "Turns an already-organic conversation/feature request into a reviewed implementation plan, then implements it story by story.",
|
|
808
710
|
inputShape: takeItAwayInput,
|
|
809
711
|
outputShape: takeItAwayOutput,
|
|
810
|
-
agents:
|
|
811
|
-
featureWriter: {
|
|
812
|
-
size: "medium",
|
|
813
|
-
thinking: "medium",
|
|
814
|
-
description: "Turns a conversation/feature request into a standalone feature doc."
|
|
815
|
-
},
|
|
816
|
-
planner: {
|
|
817
|
-
size: "large",
|
|
818
|
-
thinking: "high",
|
|
819
|
-
description: "Creates or improves the implementation doc: architecture-aware, TDD/vertical-slice/tracer-bullet story design."
|
|
820
|
-
},
|
|
821
|
-
reviewer: {
|
|
822
|
-
size: "large",
|
|
823
|
-
thinking: "high",
|
|
824
|
-
description: "Reviews isolated implementation docs and story diffs against methodology."
|
|
825
|
-
},
|
|
826
|
-
implementer: {
|
|
827
|
-
size: "medium",
|
|
828
|
-
thinking: "medium",
|
|
829
|
-
description: "Implements one story at a time using strict behavioral-red TDD."
|
|
830
|
-
}
|
|
831
|
-
},
|
|
712
|
+
agents: featureImplementationAgents,
|
|
832
713
|
start(input) {
|
|
833
|
-
return
|
|
714
|
+
return initializeTakeItAwayStep(input);
|
|
834
715
|
}
|
|
835
716
|
});
|
|
836
717
|
export {
|