astrocode-workflow 0.1.3 → 0.1.6
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/dist/tools/stage.js +9 -4
- package/dist/tools/story.js +5 -11
- package/dist/tools/workflow.js +22 -1
- package/dist/workflow/state-machine.js +5 -0
- package/package.json +2 -2
- package/src/tools/stage.ts +9 -4
- package/src/tools/story.ts +5 -13
- package/src/tools/workflow.ts +28 -2
- package/src/workflow/state-machine.ts +17 -12
package/dist/tools/stage.js
CHANGED
|
@@ -168,11 +168,13 @@ export function createAstroStageCompleteTool(opts) {
|
|
|
168
168
|
}
|
|
169
169
|
// Automatic story splitting for complex tasks
|
|
170
170
|
if (allow_new_stories && parsed.astro_json.tasks?.length) {
|
|
171
|
+
console.log(`[Astrocode] Splitting ${parsed.astro_json.tasks.length} tasks into stories`);
|
|
171
172
|
for (const task of parsed.astro_json.tasks) {
|
|
172
173
|
const complexity = task.complexity ?? 5;
|
|
173
174
|
const subtasks = task.subtasks ?? [];
|
|
174
175
|
if (subtasks.length > 0) {
|
|
175
176
|
// Split into subtasks
|
|
177
|
+
console.log(`[Astrocode] Splitting task "${task.title}" into ${subtasks.length} subtasks`);
|
|
176
178
|
for (const subtask of subtasks) {
|
|
177
179
|
const key = insertStory(db, {
|
|
178
180
|
title: `${task.title}: ${subtask}`,
|
|
@@ -182,11 +184,13 @@ export function createAstroStageCompleteTool(opts) {
|
|
|
182
184
|
epic_key: run.story_key
|
|
183
185
|
});
|
|
184
186
|
newStoryKeys.push(key);
|
|
185
|
-
|
|
187
|
+
console.log(`[Astrocode] Created story ${key} for subtask`);
|
|
188
|
+
db.prepare("INSERT INTO story_relations (relation_id, parent_key, child_key, relation_type, created_at) VALUES (?, ?, ?, ?, ?)").run(newId("rel"), run.story_key, key, "split from implement", now);
|
|
186
189
|
}
|
|
187
190
|
}
|
|
188
|
-
else if (complexity >
|
|
189
|
-
// Split complex tasks
|
|
191
|
+
else if (complexity > 6) {
|
|
192
|
+
// Split complex tasks
|
|
193
|
+
console.log(`[Astrocode] Splitting complex task "${task.title}" (complexity ${complexity})`);
|
|
190
194
|
const key = insertStory(db, {
|
|
191
195
|
title: task.title,
|
|
192
196
|
body_md: task.description ?? "",
|
|
@@ -195,7 +199,8 @@ export function createAstroStageCompleteTool(opts) {
|
|
|
195
199
|
epic_key: run.story_key
|
|
196
200
|
});
|
|
197
201
|
newStoryKeys.push(key);
|
|
198
|
-
|
|
202
|
+
console.log(`[Astrocode] Created story ${key} for complex task`);
|
|
203
|
+
db.prepare("INSERT INTO story_relations (relation_id, parent_key, child_key, relation_type, created_at) VALUES (?, ?, ?, ?, ?)").run(newId("rel"), run.story_key, key, "split from implement", now);
|
|
199
204
|
}
|
|
200
205
|
}
|
|
201
206
|
}
|
package/dist/tools/story.js
CHANGED
|
@@ -13,19 +13,13 @@ export function createAstroStoryQueueTool(opts) {
|
|
|
13
13
|
priority: tool.schema.number().int().default(0),
|
|
14
14
|
},
|
|
15
15
|
execute: async ({ title, body_md, epic_key, priority }) => {
|
|
16
|
-
|
|
17
|
-
const isLargeImplementation = (title.toLowerCase().includes('implement') || body_md?.toLowerCase().includes('implement')) &&
|
|
18
|
-
(body_md?.length || 0) > 100;
|
|
19
|
-
let finalTitle = title;
|
|
20
|
-
let finalBody = body_md;
|
|
21
|
-
if (isLargeImplementation) {
|
|
22
|
-
finalTitle = `Plan and decompose: ${title}`;
|
|
23
|
-
finalBody = `Analyze the requirements in the provided spec and break down "${title}" into 50-200 detailed, granular implementation stories. Each story should be focused on a specific, implementable task with clear acceptance criteria.\n\nOriginal description: ${body_md}`;
|
|
24
|
-
}
|
|
16
|
+
console.log(`[Astrocode] Creating story: ${title}`);
|
|
25
17
|
const story_key = withTx(db, () => {
|
|
26
|
-
|
|
18
|
+
const key = insertStory(db, { title, body_md, epic_key: epic_key ?? null, priority: priority ?? 0, state: 'queued' });
|
|
19
|
+
console.log(`[Astrocode] Inserted story ${key} into DB`);
|
|
20
|
+
return key;
|
|
27
21
|
});
|
|
28
|
-
return `✅ Queued story ${story_key}: ${
|
|
22
|
+
return `✅ Queued story ${story_key}: ${title}`;
|
|
29
23
|
},
|
|
30
24
|
});
|
|
31
25
|
}
|
package/dist/tools/workflow.js
CHANGED
|
@@ -7,6 +7,19 @@ import { injectChatPrompt } from "../ui/inject";
|
|
|
7
7
|
import { nowISO } from "../shared/time";
|
|
8
8
|
import { newEventId } from "../state/ids";
|
|
9
9
|
import { createToastManager } from "../ui/toasts";
|
|
10
|
+
// Agent name mapping for case-sensitive resolution
|
|
11
|
+
const STAGE_TO_AGENT_MAP = {
|
|
12
|
+
frame: "Frame",
|
|
13
|
+
plan: "Plan",
|
|
14
|
+
spec: "Spec",
|
|
15
|
+
implement: "Implement",
|
|
16
|
+
review: "Review",
|
|
17
|
+
verify: "Verify",
|
|
18
|
+
close: "Close"
|
|
19
|
+
};
|
|
20
|
+
function resolveAgentName(stageKey) {
|
|
21
|
+
return STAGE_TO_AGENT_MAP[stageKey] || "General";
|
|
22
|
+
}
|
|
10
23
|
function stageGoal(stage, cfg) {
|
|
11
24
|
switch (stage) {
|
|
12
25
|
case "frame":
|
|
@@ -111,7 +124,15 @@ export function createAstroWorkflowProceedTool(opts) {
|
|
|
111
124
|
const run = db.prepare("SELECT * FROM runs WHERE run_id=?").get(active.run_id);
|
|
112
125
|
const story = db.prepare("SELECT * FROM stories WHERE story_key=?").get(run.story_key);
|
|
113
126
|
// Mark stage started + set subagent_type to the stage agent.
|
|
114
|
-
|
|
127
|
+
let agentName = resolveAgentName(next.stage_key);
|
|
128
|
+
// Validate agent availability (check system config for registered agents)
|
|
129
|
+
const systemConfig = ctx.config; // OpenCode's system config
|
|
130
|
+
if (!systemConfig.agent || !systemConfig.agent[agentName]) {
|
|
131
|
+
console.warn(`Agent ${agentName} not found in system config. Available agents:`, Object.keys(systemConfig.agent || {}));
|
|
132
|
+
// Fallback to General
|
|
133
|
+
agentName = "General";
|
|
134
|
+
}
|
|
135
|
+
console.log(`Delegating stage ${next.stage_key} to agent: ${agentName}`);
|
|
115
136
|
withTx(db, () => {
|
|
116
137
|
startStage(db, active.run_id, next.stage_key, { subagent_type: agentName });
|
|
117
138
|
});
|
|
@@ -52,6 +52,7 @@ function isInitialStory(db, storyKey) {
|
|
|
52
52
|
return relations.count === 0;
|
|
53
53
|
}
|
|
54
54
|
export function createRunForStory(db, config, storyKey) {
|
|
55
|
+
console.log(`[Astrocode] Creating run for story: ${storyKey}`);
|
|
55
56
|
const story = getStory(db, storyKey);
|
|
56
57
|
if (!story)
|
|
57
58
|
throw new Error(`Story not found: ${storyKey}`);
|
|
@@ -60,17 +61,21 @@ export function createRunForStory(db, config, storyKey) {
|
|
|
60
61
|
const run_id = newRunId();
|
|
61
62
|
const now = nowISO();
|
|
62
63
|
const pipeline = config.workflow.pipeline;
|
|
64
|
+
console.log(`[Astrocode] Generated run ID: ${run_id}`);
|
|
63
65
|
// Convert to genesis planning story if needed
|
|
64
66
|
const isGenesisCandidate = storyKey === 'S-0001' || isInitialStory(db, storyKey) ||
|
|
65
67
|
(story.body_md && story.body_md.length > 100 &&
|
|
66
68
|
(story.title.toLowerCase().includes('implement') || story.body_md.toLowerCase().includes('implement')));
|
|
67
69
|
if (isGenesisCandidate) {
|
|
70
|
+
console.log(`[Astrocode] Converting story ${storyKey} to genesis planning story`);
|
|
68
71
|
const planningTitle = `Plan and decompose: ${story.title}`;
|
|
69
72
|
const planningBody = `Analyze the requirements and break down "${story.title}" into 50-200 detailed, granular implementation stories. Each story should be focused on a specific, implementable task with clear acceptance criteria.\n\nOriginal request: ${story.body_md || ''}`;
|
|
70
73
|
db.prepare("UPDATE stories SET title=?, body_md=? WHERE story_key=?").run(planningTitle, planningBody, storyKey);
|
|
71
74
|
}
|
|
72
75
|
// Lock story
|
|
76
|
+
console.log(`[Astrocode] Locking story ${storyKey} for run ${run_id}`);
|
|
73
77
|
db.prepare("UPDATE stories SET state='in_progress', in_progress=1, locked_by_run_id=?, locked_at=?, updated_at=? WHERE story_key=?").run(run_id, now, now, storyKey);
|
|
78
|
+
console.log(`[Astrocode] Inserting run ${run_id} into DB`);
|
|
74
79
|
db.prepare("INSERT INTO runs (run_id, story_key, status, pipeline_stages_json, current_stage_key, created_at, started_at, updated_at) VALUES (?, ?, 'running', ?, ?, ?, ?, ?)").run(run_id, storyKey, JSON.stringify(pipeline), pipeline[0] ?? null, now, now, now);
|
|
75
80
|
// Stage runs
|
|
76
81
|
const insertStage = db.prepare("INSERT INTO stage_runs (stage_run_id, run_id, stage_key, stage_index, status, updated_at) VALUES (?, ?, ?, ?, 'pending', ?)");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astrocode-workflow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@opencode-ai/plugin": "^1.1.19",
|
|
20
20
|
"@opencode-ai/sdk": "^1.1.19",
|
|
21
|
-
"astrocode-workflow": "^0.1.
|
|
21
|
+
"astrocode-workflow": "^0.1.3",
|
|
22
22
|
"jsonc-parser": "^3.2.0",
|
|
23
23
|
"zod": "4.1.8"
|
|
24
24
|
},
|
package/src/tools/stage.ts
CHANGED
|
@@ -224,11 +224,13 @@ export function createAstroStageCompleteTool(opts: { ctx: any; config: Astrocode
|
|
|
224
224
|
|
|
225
225
|
// Automatic story splitting for complex tasks
|
|
226
226
|
if (allow_new_stories && parsed.astro_json.tasks?.length) {
|
|
227
|
+
console.log(`[Astrocode] Splitting ${parsed.astro_json.tasks.length} tasks into stories`);
|
|
227
228
|
for (const task of parsed.astro_json.tasks) {
|
|
228
229
|
const complexity = task.complexity ?? 5;
|
|
229
230
|
const subtasks = task.subtasks ?? [];
|
|
230
231
|
if (subtasks.length > 0) {
|
|
231
232
|
// Split into subtasks
|
|
233
|
+
console.log(`[Astrocode] Splitting task "${task.title}" into ${subtasks.length} subtasks`);
|
|
232
234
|
for (const subtask of subtasks) {
|
|
233
235
|
const key = insertStory(db, {
|
|
234
236
|
title: `${task.title}: ${subtask}`,
|
|
@@ -238,18 +240,20 @@ export function createAstroStageCompleteTool(opts: { ctx: any; config: Astrocode
|
|
|
238
240
|
epic_key: run.story_key
|
|
239
241
|
});
|
|
240
242
|
newStoryKeys.push(key);
|
|
243
|
+
console.log(`[Astrocode] Created story ${key} for subtask`);
|
|
241
244
|
db.prepare(
|
|
242
245
|
"INSERT INTO story_relations (relation_id, parent_key, child_key, relation_type, created_at) VALUES (?, ?, ?, ?, ?)"
|
|
243
246
|
).run(
|
|
244
247
|
newId("rel"),
|
|
245
248
|
run.story_key,
|
|
246
249
|
key,
|
|
247
|
-
"
|
|
250
|
+
"split from implement",
|
|
248
251
|
now
|
|
249
252
|
);
|
|
250
253
|
}
|
|
251
|
-
} else if (complexity >
|
|
252
|
-
// Split complex tasks
|
|
254
|
+
} else if (complexity > 6) {
|
|
255
|
+
// Split complex tasks
|
|
256
|
+
console.log(`[Astrocode] Splitting complex task "${task.title}" (complexity ${complexity})`);
|
|
253
257
|
const key = insertStory(db, {
|
|
254
258
|
title: task.title,
|
|
255
259
|
body_md: task.description ?? "",
|
|
@@ -258,13 +262,14 @@ export function createAstroStageCompleteTool(opts: { ctx: any; config: Astrocode
|
|
|
258
262
|
epic_key: run.story_key
|
|
259
263
|
});
|
|
260
264
|
newStoryKeys.push(key);
|
|
265
|
+
console.log(`[Astrocode] Created story ${key} for complex task`);
|
|
261
266
|
db.prepare(
|
|
262
267
|
"INSERT INTO story_relations (relation_id, parent_key, child_key, relation_type, created_at) VALUES (?, ?, ?, ?, ?)"
|
|
263
268
|
).run(
|
|
264
269
|
newId("rel"),
|
|
265
270
|
run.story_key,
|
|
266
271
|
key,
|
|
267
|
-
"
|
|
272
|
+
"split from implement",
|
|
268
273
|
now
|
|
269
274
|
);
|
|
270
275
|
}
|
package/src/tools/story.ts
CHANGED
|
@@ -20,23 +20,15 @@ export function createAstroStoryQueueTool(opts: { ctx: any; config: AstrocodeCon
|
|
|
20
20
|
priority: tool.schema.number().int().default(0),
|
|
21
21
|
},
|
|
22
22
|
execute: async ({ title, body_md, epic_key, priority }) => {
|
|
23
|
-
|
|
24
|
-
const isLargeImplementation = (title.toLowerCase().includes('implement') || body_md?.toLowerCase().includes('implement')) &&
|
|
25
|
-
(body_md?.length || 0) > 100;
|
|
26
|
-
|
|
27
|
-
let finalTitle = title;
|
|
28
|
-
let finalBody = body_md;
|
|
29
|
-
|
|
30
|
-
if (isLargeImplementation) {
|
|
31
|
-
finalTitle = `Plan and decompose: ${title}`;
|
|
32
|
-
finalBody = `Analyze the requirements in the provided spec and break down "${title}" into 50-200 detailed, granular implementation stories. Each story should be focused on a specific, implementable task with clear acceptance criteria.\n\nOriginal description: ${body_md}`;
|
|
33
|
-
}
|
|
23
|
+
console.log(`[Astrocode] Creating story: ${title}`);
|
|
34
24
|
|
|
35
25
|
const story_key = withTx(db, () => {
|
|
36
|
-
|
|
26
|
+
const key = insertStory(db, { title, body_md, epic_key: epic_key ?? null, priority: priority ?? 0, state: 'queued' });
|
|
27
|
+
console.log(`[Astrocode] Inserted story ${key} into DB`);
|
|
28
|
+
return key;
|
|
37
29
|
});
|
|
38
30
|
|
|
39
|
-
return `✅ Queued story ${story_key}: ${
|
|
31
|
+
return `✅ Queued story ${story_key}: ${title}`;
|
|
40
32
|
},
|
|
41
33
|
});
|
|
42
34
|
}
|
package/src/tools/workflow.ts
CHANGED
|
@@ -11,6 +11,21 @@ import { nowISO } from "../shared/time";
|
|
|
11
11
|
import { newEventId } from "../state/ids";
|
|
12
12
|
import { createToastManager } from "../ui/toasts";
|
|
13
13
|
|
|
14
|
+
// Agent name mapping for case-sensitive resolution
|
|
15
|
+
const STAGE_TO_AGENT_MAP: Record<string, string> = {
|
|
16
|
+
frame: "Frame",
|
|
17
|
+
plan: "Plan",
|
|
18
|
+
spec: "Spec",
|
|
19
|
+
implement: "Implement",
|
|
20
|
+
review: "Review",
|
|
21
|
+
verify: "Verify",
|
|
22
|
+
close: "Close"
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function resolveAgentName(stageKey: string): string {
|
|
26
|
+
return STAGE_TO_AGENT_MAP[stageKey] || "General";
|
|
27
|
+
}
|
|
28
|
+
|
|
14
29
|
function stageGoal(stage: StageKey, cfg: AstrocodeConfig): string {
|
|
15
30
|
switch (stage) {
|
|
16
31
|
case "frame":
|
|
@@ -135,8 +150,19 @@ export function createAstroWorkflowProceedTool(opts: { ctx: any; config: Astroco
|
|
|
135
150
|
const run = db.prepare("SELECT * FROM runs WHERE run_id=?").get(active.run_id) as any;
|
|
136
151
|
const story = db.prepare("SELECT * FROM stories WHERE story_key=?").get(run.story_key) as any;
|
|
137
152
|
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
// Mark stage started + set subagent_type to the stage agent.
|
|
154
|
+
let agentName = resolveAgentName(next.stage_key);
|
|
155
|
+
|
|
156
|
+
// Validate agent availability (check system config for registered agents)
|
|
157
|
+
const systemConfig = ctx.config; // OpenCode's system config
|
|
158
|
+
if (!systemConfig.agent || !systemConfig.agent[agentName]) {
|
|
159
|
+
console.warn(`Agent ${agentName} not found in system config. Available agents:`, Object.keys(systemConfig.agent || {}));
|
|
160
|
+
// Fallback to General
|
|
161
|
+
agentName = "General";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log(`Delegating stage ${next.stage_key} to agent: ${agentName}`);
|
|
165
|
+
|
|
140
166
|
withTx(db, () => {
|
|
141
167
|
startStage(db, active.run_id, next.stage_key, { subagent_type: agentName });
|
|
142
168
|
});
|
|
@@ -81,6 +81,7 @@ function isInitialStory(db: SqliteDb, storyKey: string): boolean {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function createRunForStory(db: SqliteDb, config: AstrocodeConfig, storyKey: string): { run_id: string } {
|
|
84
|
+
console.log(`[Astrocode] Creating run for story: ${storyKey}`);
|
|
84
85
|
const story = getStory(db, storyKey);
|
|
85
86
|
if (!story) throw new Error(`Story not found: ${storyKey}`);
|
|
86
87
|
if (story.state !== "approved") throw new Error(`Story must be approved to run: ${storyKey} (state=${story.state})`);
|
|
@@ -88,23 +89,27 @@ export function createRunForStory(db: SqliteDb, config: AstrocodeConfig, storyKe
|
|
|
88
89
|
const run_id = newRunId();
|
|
89
90
|
const now = nowISO();
|
|
90
91
|
const pipeline = config.workflow.pipeline;
|
|
92
|
+
console.log(`[Astrocode] Generated run ID: ${run_id}`);
|
|
91
93
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
// Convert to genesis planning story if needed
|
|
95
|
+
const isGenesisCandidate = storyKey === 'S-0001' || isInitialStory(db, storyKey) ||
|
|
96
|
+
(story.body_md && story.body_md.length > 100 &&
|
|
95
97
|
(story.title.toLowerCase().includes('implement') || story.body_md.toLowerCase().includes('implement')));
|
|
96
98
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
if (isGenesisCandidate) {
|
|
100
|
+
console.log(`[Astrocode] Converting story ${storyKey} to genesis planning story`);
|
|
101
|
+
const planningTitle = `Plan and decompose: ${story.title}`;
|
|
102
|
+
const planningBody = `Analyze the requirements and break down "${story.title}" into 50-200 detailed, granular implementation stories. Each story should be focused on a specific, implementable task with clear acceptance criteria.\n\nOriginal request: ${story.body_md || ''}`;
|
|
103
|
+
db.prepare("UPDATE stories SET title=?, body_md=? WHERE story_key=?").run(planningTitle, planningBody, storyKey);
|
|
104
|
+
}
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
// Lock story
|
|
107
|
+
console.log(`[Astrocode] Locking story ${storyKey} for run ${run_id}`);
|
|
108
|
+
db.prepare(
|
|
109
|
+
"UPDATE stories SET state='in_progress', in_progress=1, locked_by_run_id=?, locked_at=?, updated_at=? WHERE story_key=?"
|
|
110
|
+
).run(run_id, now, now, storyKey);
|
|
107
111
|
|
|
112
|
+
console.log(`[Astrocode] Inserting run ${run_id} into DB`);
|
|
108
113
|
db.prepare(
|
|
109
114
|
"INSERT INTO runs (run_id, story_key, status, pipeline_stages_json, current_stage_key, created_at, started_at, updated_at) VALUES (?, ?, 'running', ?, ?, ?, ?, ?)"
|
|
110
115
|
).run(run_id, storyKey, JSON.stringify(pipeline), pipeline[0] ?? null, now, now, now);
|