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.
@@ -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
- db.prepare("INSERT INTO story_relations (relation_id, parent_key, child_key, relation_type, created_at) VALUES (?, ?, ?, ?, ?)").run(newId("rel"), run.story_key, key, "auto-split subtask", now);
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 > 5) {
189
- // Split complex tasks without subtasks
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
- db.prepare("INSERT INTO story_relations (relation_id, parent_key, child_key, relation_type, created_at) VALUES (?, ?, ?, ?, ?)").run(newId("rel"), run.story_key, key, "auto-split from plan", now);
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
  }
@@ -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
- // If the story seems like a large implementation, convert to planning story
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
- return insertStory(db, { title: finalTitle, body_md: finalBody, epic_key: epic_key ?? null, priority: priority ?? 0, state: 'queued' });
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}: ${finalTitle}`;
22
+ return `✅ Queued story ${story_key}: ${title}`;
29
23
  },
30
24
  });
31
25
  }
@@ -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
- const agentName = next.stage_key;
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",
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.1",
21
+ "astrocode-workflow": "^0.1.3",
22
22
  "jsonc-parser": "^3.2.0",
23
23
  "zod": "4.1.8"
24
24
  },
@@ -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
- "auto-split subtask",
250
+ "split from implement",
248
251
  now
249
252
  );
250
253
  }
251
- } else if (complexity > 5) {
252
- // Split complex tasks without subtasks
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
- "auto-split from plan",
272
+ "split from implement",
268
273
  now
269
274
  );
270
275
  }
@@ -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
- // If the story seems like a large implementation, convert to planning story
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
- return insertStory(db, { title: finalTitle, body_md: finalBody, epic_key: epic_key ?? null, priority: priority ?? 0, state: 'queued' });
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}: ${finalTitle}`;
31
+ return `✅ Queued story ${story_key}: ${title}`;
40
32
  },
41
33
  });
42
34
  }
@@ -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
- // Mark stage started + set subagent_type to the stage agent.
139
- const agentName = next.stage_key;
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
- // Convert to genesis planning story if needed
93
- const isGenesisCandidate = storyKey === 'S-0001' || isInitialStory(db, storyKey) ||
94
- (story.body_md && story.body_md.length > 100 &&
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
- if (isGenesisCandidate) {
98
- const planningTitle = `Plan and decompose: ${story.title}`;
99
- 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 || ''}`;
100
- db.prepare("UPDATE stories SET title=?, body_md=? WHERE story_key=?").run(planningTitle, planningBody, storyKey);
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
- // Lock story
104
- db.prepare(
105
- "UPDATE stories SET state='in_progress', in_progress=1, locked_by_run_id=?, locked_at=?, updated_at=? WHERE story_key=?"
106
- ).run(run_id, now, now, storyKey);
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);