astrocode-workflow 0.4.4 → 0.4.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.
Files changed (46) hide show
  1. package/dist/src/hooks/continuation-enforcer.d.ts +1 -9
  2. package/dist/src/hooks/continuation-enforcer.js +12 -2
  3. package/dist/src/hooks/inject-provider.d.ts +1 -9
  4. package/dist/src/hooks/inject-provider.js +14 -5
  5. package/dist/src/state/types.d.ts +9 -0
  6. package/dist/src/tools/artifacts.d.ts +4 -4
  7. package/dist/src/tools/artifacts.js +12 -3
  8. package/dist/src/tools/health.d.ts +2 -2
  9. package/dist/src/tools/health.js +18 -11
  10. package/dist/src/tools/index.js +27 -34
  11. package/dist/src/tools/injects.d.ts +8 -8
  12. package/dist/src/tools/injects.js +24 -6
  13. package/dist/src/tools/metrics.d.ts +6 -5
  14. package/dist/src/tools/repair.d.ts +2 -2
  15. package/dist/src/tools/repair.js +5 -1
  16. package/dist/src/tools/reset.d.ts +2 -2
  17. package/dist/src/tools/reset.js +9 -1
  18. package/dist/src/tools/run.d.ts +3 -3
  19. package/dist/src/tools/run.js +8 -2
  20. package/dist/src/tools/spec.d.ts +2 -2
  21. package/dist/src/tools/spec.js +3 -2
  22. package/dist/src/tools/stage.d.ts +5 -5
  23. package/dist/src/tools/stage.js +16 -4
  24. package/dist/src/tools/status.d.ts +2 -2
  25. package/dist/src/tools/status.js +25 -2
  26. package/dist/src/tools/story.d.ts +5 -5
  27. package/dist/src/tools/story.js +16 -4
  28. package/dist/src/tools/workflow.d.ts +2 -2
  29. package/dist/src/tools/workflow.js +5 -1
  30. package/package.json +1 -1
  31. package/src/hooks/continuation-enforcer.ts +11 -9
  32. package/src/hooks/inject-provider.ts +16 -12
  33. package/src/state/types.ts +11 -0
  34. package/src/tools/artifacts.ts +16 -7
  35. package/src/tools/health.ts +22 -13
  36. package/src/tools/index.ts +32 -40
  37. package/src/tools/injects.ts +32 -14
  38. package/src/tools/metrics.ts +3 -6
  39. package/src/tools/repair.ts +8 -4
  40. package/src/tools/reset.ts +11 -3
  41. package/src/tools/run.ts +11 -5
  42. package/src/tools/spec.ts +5 -4
  43. package/src/tools/stage.ts +22 -10
  44. package/src/tools/status.ts +28 -5
  45. package/src/tools/story.ts +21 -9
  46. package/src/tools/workflow.ts +8 -3
@@ -2,12 +2,12 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
4
4
  import type { AstrocodeConfig } from "../config/schema";
5
- import type { SqliteDb } from "../state/db";
5
+ import type { RuntimeState } from "../state/types";
6
6
  import { getAstroPaths } from "../shared/paths";
7
7
  import { putArtifact, listArtifacts, getArtifact } from "../workflow/artifacts";
8
8
 
9
- export function createAstroArtifactPutTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
10
- const { ctx, db } = opts;
9
+ export function createAstroArtifactPutTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
10
+ const { ctx, runtime } = opts;
11
11
 
12
12
  return tool({
13
13
  description: "Write an artifact file under .astro and record it in the DB.",
@@ -20,6 +20,9 @@ export function createAstroArtifactPutTool(opts: { ctx: any; config: AstrocodeCo
20
20
  meta_json: tool.schema.string().default("{}"),
21
21
  },
22
22
  execute: async ({ run_id, stage_key, type, rel_path, content, meta_json }) => {
23
+ const { db } = runtime;
24
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
25
+
23
26
  const repoRoot = ctx.directory as string;
24
27
  const meta = JSON.parse(meta_json || "{}") as Record<string, unknown>;
25
28
 
@@ -39,8 +42,8 @@ export function createAstroArtifactPutTool(opts: { ctx: any; config: AstrocodeCo
39
42
  });
40
43
  }
41
44
 
42
- export function createAstroArtifactListTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
43
- const { db } = opts;
45
+ export function createAstroArtifactListTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
46
+ const { runtime } = opts;
44
47
 
45
48
  return tool({
46
49
  description: "List artifacts (optionally filtered by run_id, stage_key, type).",
@@ -50,14 +53,17 @@ export function createAstroArtifactListTool(opts: { ctx: any; config: AstrocodeC
50
53
  type: tool.schema.string().optional(),
51
54
  },
52
55
  execute: async ({ run_id, stage_key, type }) => {
56
+ const { db } = runtime;
57
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
58
+
53
59
  const rows = listArtifacts(db, { run_id, stage_key, type });
54
60
  return JSON.stringify(rows, null, 2);
55
61
  },
56
62
  });
57
63
  }
58
64
 
59
- export function createAstroArtifactGetTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
60
- const { ctx, config, db } = opts;
65
+ export function createAstroArtifactGetTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
66
+ const { ctx, runtime } = opts;
61
67
 
62
68
  return tool({
63
69
  description: "Get artifact metadata (and optionally file contents).",
@@ -67,6 +73,9 @@ export function createAstroArtifactGetTool(opts: { ctx: any; config: AstrocodeCo
67
73
  max_body_chars: tool.schema.number().int().positive().default(50_000),
68
74
  },
69
75
  execute: async ({ artifact_id, include_body, max_body_chars }) => {
76
+ const { db } = runtime;
77
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
78
+
70
79
  const row = getArtifact(db, artifact_id);
71
80
  if (!row) throw new Error(`Artifact not found: ${artifact_id}`);
72
81
 
@@ -1,14 +1,14 @@
1
1
  // src/tools/health.ts
2
2
  import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
3
3
  import type { AstrocodeConfig } from "../config/schema";
4
- import type { SqliteDb } from "../state/db";
4
+ import type { RuntimeState } from "../state/types";
5
5
  import { getSchemaVersion } from "../state/db";
6
6
  import { getActiveRun } from "../workflow/state-machine";
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
9
 
10
- export function createAstroHealthTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
11
- const { ctx, config, db } = opts;
10
+ export function createAstroHealthTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
11
+ const { ctx, config, runtime } = opts;
12
12
 
13
13
  return tool({
14
14
  description: "Check Astrocode health: DB status, locks, schema, active runs, recent events.",
@@ -30,13 +30,24 @@ export function createAstroHealthTool(opts: { ctx: any; config: AstrocodeConfig;
30
30
  const walExists = fs.existsSync(`${fullDbPath}-wal`);
31
31
  const shmExists = fs.existsSync(`${fullDbPath}-shm`);
32
32
 
33
- lines.push(`- DB Files:`);
34
- lines.push(` - Main: ${dbExists ? "EXISTS" : "MISSING"}`);
35
- lines.push(` - WAL: ${walExists ? "EXISTS" : "MISSING"}`);
36
- lines.push(` - SHM: ${shmExists ? "EXISTS" : "MISSING"}`);
33
+ lines.push(`## Database File`);
34
+ lines.push(`- Exists: ${dbExists ? "" : ""}`);
35
+ lines.push(`- WAL Mode Active: ${walExists ? "YES" : "NO"}`);
37
36
 
38
- if (!dbExists) {
39
- lines.push(`- STATUS: DB MISSING - run astro_init first`);
37
+ if (dbExists) {
38
+ const stats = fs.statSync(fullDbPath);
39
+ lines.push(`- Size: ${Math.round(stats.size / 1024)} KB`);
40
+ lines.push(`- Last Modified: ${stats.mtime.toISOString()}`);
41
+ }
42
+
43
+ const { db } = runtime;
44
+
45
+ if (!db) {
46
+ lines.push(``, `## Status`);
47
+ lines.push(`❌ DB not connected (Limited Mode)`);
48
+ if (runtime.limitedModeReason) {
49
+ lines.push(`- Reason: ${runtime.limitedModeReason.code}`);
50
+ }
40
51
  return lines.join("\n");
41
52
  }
42
53
 
@@ -46,8 +57,6 @@ export function createAstroHealthTool(opts: { ctx: any; config: AstrocodeConfig;
46
57
  lines.push(`- Schema Version: ${schemaVersion}`);
47
58
  } catch (e) {
48
59
  lines.push(`- Schema Version: ERROR (${String(e)})`);
49
- lines.push(`- STATUS: DB CORRUPTED`);
50
- return lines.join("\n");
51
60
  }
52
61
 
53
62
  // Active run
@@ -59,7 +68,7 @@ export function createAstroHealthTool(opts: { ctx: any; config: AstrocodeConfig;
59
68
  lines.push(` - Stage: ${activeRun.current_stage_key || "none"}`);
60
69
  lines.push(` - Started: ${activeRun.started_at}`);
61
70
  } else {
62
- lines.push(`- Active Run: NONE`);
71
+ lines.push(`- Active Run: *(none)*`);
63
72
  }
64
73
  } catch (e) {
65
74
  lines.push(`- Active Run: ERROR (${String(e)})`);
@@ -90,7 +99,7 @@ export function createAstroHealthTool(opts: { ctx: any; config: AstrocodeConfig;
90
99
  lines.push(`✅ Schema valid`);
91
100
 
92
101
  if (walExists || shmExists) {
93
- lines.push(`⚠️ WAL/SHM files present - indicates unclean shutdown or active transaction`);
102
+ lines.push(`ℹ️ SQLite temporary files present (normal during active use)`);
94
103
  }
95
104
 
96
105
  return lines.join("\n");
@@ -33,53 +33,45 @@ type CreateAstroToolsOptions = {
33
33
 
34
34
  export function createAstroTools(opts: CreateAstroToolsOptions): Record<string, ToolDefinition> {
35
35
  const { ctx, config, agents, runtime } = opts;
36
- const { db } = runtime;
37
- const hasDatabase = db !== null; // Source of truth: DB availability
38
36
 
39
37
  const tools: Record<string, ToolDefinition> = {};
40
38
 
41
- // Always available tools (work without database - guaranteed DB-independent)
42
- tools.astro_status = createAstroStatusTool({ ctx, config });
43
- tools.astro_spec_get = createAstroSpecGetTool({ ctx, config });
44
- tools.astro_health = createAstroHealthTool({ ctx, config, db });
45
- tools.astro_reset = createAstroResetTool({ ctx, config, db });
46
- tools.astro_metrics = createAstroMetricsTool({ ctx, config });
39
+ // Always available tools (work without database - guaranteed DB-independent)
40
+ tools.astro_status = createAstroStatusTool({ ctx, config, runtime });
41
+ tools.astro_spec_get = createAstroSpecGetTool({ ctx, config });
42
+ tools.astro_health = createAstroHealthTool({ ctx, config, runtime });
43
+ tools.astro_reset = createAstroResetTool({ ctx, config, runtime });
44
+ tools.astro_metrics = createAstroMetricsTool({ ctx, config });
47
45
 
48
- // Recovery tool - available even in limited mode to allow DB initialization
49
- tools.astro_init = createAstroInitTool({ ctx, config, runtime });
46
+ // Recovery tool - available even in limited mode to allow DB initialization
47
+ tools.astro_init = createAstroInitTool({ ctx, config, runtime });
50
48
 
51
49
  // Database-dependent tools
52
- if (hasDatabase) {
53
- // Ensure agents are available for workflow tools that require them
54
- if (!agents) {
55
- throw new Error("astro_workflow_proceed requires agents to be provided in normal mode.");
56
- }
57
-
58
- tools.astro_story_queue = createAstroStoryQueueTool({ ctx, config, db });
59
- tools.astro_story_approve = createAstroStoryApproveTool({ ctx, config, db });
60
- tools.astro_story_board = createAstroStoryBoardTool({ ctx, config, db });
61
- tools.astro_story_set_state = createAstroStorySetStateTool({ ctx, config, db });
62
- tools.astro_spec_set = createAstroSpecSetTool({ ctx, config, db });
63
- tools.astro_run_get = createAstroRunGetTool({ ctx, config, db });
64
- tools.astro_run_abort = createAstroRunAbortTool({ ctx, config, db });
65
- tools.astro_workflow_proceed = createAstroWorkflowProceedTool({ ctx, config, db, agents });
66
- tools.astro_stage_start = createAstroStageStartTool({ ctx, config, db });
67
- tools.astro_stage_complete = createAstroStageCompleteTool({ ctx, config, db });
68
- tools.astro_stage_fail = createAstroStageFailTool({ ctx, config, db });
69
- tools.astro_stage_reset = createAstroStageResetTool({ ctx, config, db });
70
- tools.astro_artifact_put = createAstroArtifactPutTool({ ctx, config, db });
71
- tools.astro_artifact_list = createAstroArtifactListTool({ ctx, config, db });
72
- tools.astro_artifact_get = createAstroArtifactGetTool({ ctx, config, db });
73
- tools.astro_inject_put = createAstroInjectPutTool({ ctx, config, db });
74
- tools.astro_inject_list = createAstroInjectListTool({ ctx, config, db });
75
- tools.astro_inject_search = createAstroInjectSearchTool({ ctx, config, db });
76
- tools.astro_inject_get = createAstroInjectGetTool({ ctx, config, db });
77
- tools.astro_inject_eligible = createAstroInjectEligibleTool({ ctx, config, db });
78
- tools.astro_inject_debug_due = createAstroInjectDebugDueTool({ ctx, config, db });
79
- tools.astro_repair = createAstroRepairTool({ ctx, config, db });
80
- }
50
+ // We register these even if runtime.db is null, so they can become active if astro_init succeeds in-process.
51
+ // The tools themselves must handle the missing DB case gracefully (returning "not initialized").
52
+ tools.astro_story_queue = createAstroStoryQueueTool({ ctx, config, runtime });
53
+ tools.astro_story_approve = createAstroStoryApproveTool({ ctx, config, runtime });
54
+ tools.astro_story_board = createAstroStoryBoardTool({ ctx, config, runtime });
55
+ tools.astro_story_set_state = createAstroStorySetStateTool({ ctx, config, runtime });
56
+ tools.astro_spec_set = createAstroSpecSetTool({ ctx, config, runtime });
57
+ tools.astro_run_get = createAstroRunGetTool({ ctx, config, runtime });
58
+ tools.astro_run_abort = createAstroRunAbortTool({ ctx, config, runtime });
59
+ tools.astro_workflow_proceed = createAstroWorkflowProceedTool({ ctx, config, runtime, agents });
60
+ tools.astro_stage_start = createAstroStageStartTool({ ctx, config, runtime });
61
+ tools.astro_stage_complete = createAstroStageCompleteTool({ ctx, config, runtime });
62
+ tools.astro_stage_fail = createAstroStageFailTool({ ctx, config, runtime });
63
+ tools.astro_stage_reset = createAstroStageResetTool({ ctx, config, runtime });
64
+ tools.astro_artifact_put = createAstroArtifactPutTool({ ctx, config, runtime });
65
+ tools.astro_artifact_list = createAstroArtifactListTool({ ctx, config, runtime });
66
+ tools.astro_artifact_get = createAstroArtifactGetTool({ ctx, config, runtime });
67
+ tools.astro_inject_put = createAstroInjectPutTool({ ctx, config, runtime });
68
+ tools.astro_inject_list = createAstroInjectListTool({ ctx, config, runtime });
69
+ tools.astro_inject_search = createAstroInjectSearchTool({ ctx, config, runtime });
70
+ tools.astro_inject_get = createAstroInjectGetTool({ ctx, config, runtime });
71
+ tools.astro_inject_eligible = createAstroInjectEligibleTool({ ctx, config, runtime });
72
+ tools.astro_inject_debug_due = createAstroInjectDebugDueTool({ ctx, config, runtime });
73
+ tools.astro_repair = createAstroRepairTool({ ctx, config, runtime });
81
74
 
82
- // Create aliases for backward compatibility
83
75
  const aliases: Array<[string, string]> = [
84
76
  ["_astro_init", "astro_init"],
85
77
  ["_astro_status", "astro_status"],
@@ -1,6 +1,6 @@
1
1
  import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
2
2
  import type { AstrocodeConfig } from "../config/schema";
3
- import type { SqliteDb } from "../state/db";
3
+ import type { RuntimeState } from "../state/types";
4
4
  import { withTx } from "../state/db";
5
5
  import { nowISO } from "../shared/time";
6
6
  import { sha256Hex } from "../shared/hash";
@@ -54,8 +54,8 @@ function newInjectId(): string {
54
54
  return `inj_${Date.now()}_${Math.random().toString(16).slice(2)}`;
55
55
  }
56
56
 
57
- export function createAstroInjectPutTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
58
- const { db } = opts;
57
+ export function createAstroInjectPutTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
58
+ const { runtime } = opts;
59
59
 
60
60
  return tool({
61
61
  description: "Create/update an inject (note/policy) stored in the DB. Useful for persistent rules.",
@@ -71,6 +71,9 @@ export function createAstroInjectPutTool(opts: { ctx: any; config: AstrocodeConf
71
71
  expires_at: tool.schema.string().nullable().optional(),
72
72
  },
73
73
  execute: async ({ inject_id, type, title, body_md, tags_json, scope, source, priority, expires_at }) => {
74
+ const { db } = runtime;
75
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
76
+
74
77
  const id = inject_id ?? newInjectId();
75
78
  const now = nowISO();
76
79
  const sha = sha256Hex(body_md);
@@ -132,8 +135,8 @@ export function createAstroInjectPutTool(opts: { ctx: any; config: AstrocodeConf
132
135
  });
133
136
  }
134
137
 
135
- export function createAstroInjectListTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
136
- const { db } = opts;
138
+ export function createAstroInjectListTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
139
+ const { runtime } = opts;
137
140
 
138
141
  return tool({
139
142
  description: "List injects (optionally filtered by scope/type).",
@@ -143,6 +146,9 @@ export function createAstroInjectListTool(opts: { ctx: any; config: AstrocodeCon
143
146
  limit: tool.schema.number().int().positive().default(50),
144
147
  },
145
148
  execute: async ({ scope, type, limit }) => {
149
+ const { db } = runtime;
150
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
151
+
146
152
  const where: string[] = [];
147
153
  const params: any[] = [];
148
154
 
@@ -172,8 +178,8 @@ export function createAstroInjectListTool(opts: { ctx: any; config: AstrocodeCon
172
178
  });
173
179
  }
174
180
 
175
- export function createAstroInjectGetTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
176
- const { db } = opts;
181
+ export function createAstroInjectGetTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
182
+ const { runtime } = opts;
177
183
 
178
184
  return tool({
179
185
  description: "Get an inject by id (full body).",
@@ -181,6 +187,9 @@ export function createAstroInjectGetTool(opts: { ctx: any; config: AstrocodeConf
181
187
  inject_id: tool.schema.string().min(1),
182
188
  },
183
189
  execute: async ({ inject_id }) => {
190
+ const { db } = runtime;
191
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
192
+
184
193
  const row = db.prepare("SELECT * FROM injects WHERE inject_id=?").get(inject_id) as any;
185
194
  if (!row) throw new Error(`Inject not found: ${inject_id}`);
186
195
  return JSON.stringify(row, null, 2);
@@ -188,8 +197,8 @@ export function createAstroInjectGetTool(opts: { ctx: any; config: AstrocodeConf
188
197
  });
189
198
  }
190
199
 
191
- export function createAstroInjectSearchTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
192
- const { db } = opts;
200
+ export function createAstroInjectSearchTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
201
+ const { runtime } = opts;
193
202
 
194
203
  return tool({
195
204
  description: "Search injects by query substring over title/body/tags. Returns matches ordered by priority/recency.",
@@ -199,6 +208,9 @@ export function createAstroInjectSearchTool(opts: { ctx: any; config: AstrocodeC
199
208
  limit: tool.schema.number().int().positive().default(20),
200
209
  },
201
210
  execute: async ({ q, scope, limit }) => {
211
+ const { db } = runtime;
212
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
213
+
202
214
  const like = `%${q}%`;
203
215
  const where: string[] = ["(title LIKE ? OR body_md LIKE ? OR tags_json LIKE ?)"];
204
216
  const params: any[] = [like, like, like];
@@ -238,7 +250,7 @@ export type InjectRow = {
238
250
  };
239
251
 
240
252
  export function selectEligibleInjects(
241
- db: SqliteDb,
253
+ db: any,
242
254
  opts: {
243
255
  nowIso: string;
244
256
  scopeAllowlist: string[];
@@ -273,8 +285,8 @@ export function selectEligibleInjects(
273
285
  return db.prepare(sql).all(...params) as InjectRow[];
274
286
  }
275
287
 
276
- export function createAstroInjectEligibleTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
277
- const { db } = opts;
288
+ export function createAstroInjectEligibleTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
289
+ const { runtime } = opts;
278
290
 
279
291
  return tool({
280
292
  description: "Debug: show which injects are eligible right now for injection.",
@@ -284,6 +296,9 @@ export function createAstroInjectEligibleTool(opts: { ctx: any; config: Astrocod
284
296
  limit: tool.schema.number().int().positive().default(50),
285
297
  },
286
298
  execute: async ({ scopes_json, types_json, limit }) => {
299
+ const { db } = runtime;
300
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
301
+
287
302
  const now = nowISO();
288
303
  const scopes = parseJsonStringArray("scopes_json", scopes_json);
289
304
  const types = parseJsonStringArray("types_json", types_json);
@@ -303,8 +318,8 @@ export function createAstroInjectEligibleTool(opts: { ctx: any; config: Astrocod
303
318
  });
304
319
  }
305
320
 
306
- export function createAstroInjectDebugDueTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
307
- const { db } = opts;
321
+ export function createAstroInjectDebugDueTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
322
+ const { runtime } = opts;
308
323
 
309
324
  return tool({
310
325
  description: "Debug: show comprehensive injection diagnostics - why injects were selected/skipped.",
@@ -313,6 +328,9 @@ export function createAstroInjectDebugDueTool(opts: { ctx: any; config: Astrocod
313
328
  types_json: tool.schema.string().default('["note","policy"]'),
314
329
  },
315
330
  execute: async ({ scopes_json, types_json }) => {
331
+ const { db } = runtime;
332
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
333
+
316
334
  const now = nowISO();
317
335
  const nowMs = Date.parse(now);
318
336
 
@@ -1,13 +1,10 @@
1
1
  // src/tools/metrics.ts
2
2
  import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
3
+ import type { AstrocodeConfig } from "../config/schema";
4
+ import type { RuntimeState } from "../state/types";
3
5
  import { metrics } from "../shared/metrics";
4
6
 
5
- type CreateAstroMetricsToolOptions = {
6
- ctx: any;
7
- config: any;
8
- };
9
-
10
- export function createAstroMetricsTool(opts: CreateAstroMetricsToolOptions): ToolDefinition {
7
+ export function createAstroMetricsTool(opts: { ctx: any; config: AstrocodeConfig; runtime?: RuntimeState }): ToolDefinition {
11
8
  return tool({
12
9
  description: "Get performance metrics for Astrocode operations including transaction times, injection success rates, and error statistics.",
13
10
  args: {},
@@ -1,15 +1,14 @@
1
- import path from "node:path";
2
1
  import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
3
2
  import type { AstrocodeConfig } from "../config/schema";
4
- import type { SqliteDb } from "../state/db";
3
+ import type { RuntimeState } from "../state/types";
5
4
  import { withTx } from "../state/db";
6
5
  import { repairState, formatRepairReport } from "../workflow/repair";
7
6
  import { putArtifact } from "../workflow/artifacts";
8
7
  import { nowISO } from "../shared/time";
9
8
 
10
9
 
11
- export function createAstroRepairTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
12
- const { ctx, config, db } = opts;
10
+ export function createAstroRepairTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
11
+ const { ctx, config, runtime } = opts;
13
12
 
14
13
  return tool({
15
14
  description: "Repair Astrocode invariants and recover from inconsistent DB state. Writes a repair report artifact.",
@@ -17,6 +16,11 @@ export function createAstroRepairTool(opts: { ctx: any; config: AstrocodeConfig;
17
16
  write_report_artifact: tool.schema.boolean().default(true),
18
17
  },
19
18
  execute: async ({ write_report_artifact }) => {
19
+ const { db } = runtime;
20
+ if (!db) {
21
+ return "⚠️ Cannot run repair: Astrocode is not initialized. Run **astro_init** first.";
22
+ }
23
+
20
24
  const repoRoot = ctx.directory as string;
21
25
 
22
26
  // Repair database state
@@ -1,12 +1,12 @@
1
1
  // src/tools/reset.ts
2
2
  import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
3
3
  import type { AstrocodeConfig } from "../config/schema";
4
- import type { SqliteDb } from "../state/db";
4
+ import type { RuntimeState } from "../state/types";
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
7
 
8
- export function createAstroResetTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
9
- const { ctx, config, db } = opts;
8
+ export function createAstroResetTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
9
+ const { ctx, config, runtime } = opts;
10
10
 
11
11
  return tool({
12
12
  description: "Reset Astrocode database: safely delete all DB files and WAL/SHM after killing concurrent processes.",
@@ -28,6 +28,14 @@ export function createAstroResetTool(opts: { ctx: any; config: AstrocodeConfig;
28
28
  ].join("\n");
29
29
  }
30
30
 
31
+ // Close DB connection if open
32
+ if (runtime.db) {
33
+ try {
34
+ runtime.db.close();
35
+ } catch { /* ignore */ }
36
+ runtime.db = null;
37
+ }
38
+
31
39
  const repoRoot = (ctx as any).directory || process.cwd();
32
40
  const dbPath = config.db?.path || ".astro/astro.db";
33
41
  const fullDbPath = path.resolve(repoRoot, dbPath);
package/src/tools/run.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
2
2
  import type { AstrocodeConfig } from "../config/schema";
3
- import type { SqliteDb } from "../state/db";
3
+ import type { RuntimeState } from "../state/types";
4
4
  import { abortRun, getActiveRun, getStageRuns, getStory } from "../workflow/state-machine";
5
5
 
6
- export function createAstroRunGetTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
7
- const { db } = opts;
6
+ export function createAstroRunGetTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
7
+ const { runtime } = opts;
8
8
 
9
9
  return tool({
10
10
  description: "Get run details (and stage run statuses). Defaults to active run if run_id omitted.",
@@ -13,6 +13,9 @@ export function createAstroRunGetTool(opts: { ctx: any; config: AstrocodeConfig;
13
13
  include_stage_summaries: tool.schema.boolean().default(false),
14
14
  },
15
15
  execute: async ({ run_id, include_stage_summaries }) => {
16
+ const { db } = runtime;
17
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
18
+
16
19
  const active = getActiveRun(db);
17
20
  const rid = run_id ?? active?.run_id;
18
21
  if (!rid) return "No active run.";
@@ -42,8 +45,8 @@ export function createAstroRunGetTool(opts: { ctx: any; config: AstrocodeConfig;
42
45
  });
43
46
  }
44
47
 
45
- export function createAstroRunAbortTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
46
- const { db } = opts;
48
+ export function createAstroRunAbortTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
49
+ const { runtime } = opts;
47
50
 
48
51
  return tool({
49
52
  description: "Abort a run and unlock its story (returns story to approved). Defaults to active run if run_id omitted.",
@@ -52,6 +55,9 @@ export function createAstroRunAbortTool(opts: { ctx: any; config: AstrocodeConfi
52
55
  reason: tool.schema.string().default("aborted by user"),
53
56
  },
54
57
  execute: async ({ run_id, reason }) => {
58
+ const { db } = runtime;
59
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
60
+
55
61
  const active = getActiveRun(db);
56
62
  const rid = run_id ?? active?.run_id;
57
63
  if (!rid) return "No active run to abort.";
package/src/tools/spec.ts CHANGED
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
4
4
  import type { AstrocodeConfig } from "../config/schema";
5
- import type { SqliteDb } from "../state/db";
5
+ import type { RuntimeState } from "../state/types";
6
6
  import { getAstroPaths, ensureAstroDirs } from "../shared/paths";
7
7
  import { nowISO } from "../shared/time";
8
8
  import { sha256Hex } from "../shared/hash";
@@ -25,8 +25,8 @@ export function createAstroSpecGetTool(opts: { ctx: any; config: AstrocodeConfig
25
25
  });
26
26
  }
27
27
 
28
- export function createAstroSpecSetTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
29
- const { ctx, config, db } = opts;
28
+ export function createAstroSpecSetTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
29
+ const { ctx, config, runtime } = opts;
30
30
 
31
31
  return tool({
32
32
  description: "Set/replace the project spec at .astro/spec.md and record its hash in the DB.",
@@ -34,8 +34,9 @@ export function createAstroSpecSetTool(opts: { ctx: any; config: AstrocodeConfig
34
34
  spec_md: tool.schema.string().min(1),
35
35
  },
36
36
  execute: async ({ spec_md }) => {
37
+ const { db } = runtime;
37
38
  if (!db) {
38
- return "❌ Database not available. Cannot track spec hash. Astrocode is running in limited mode.";
39
+ return "❌ Database not available. Cannot track spec hash. Astrocode is running in limited mode. Run **astro_init** first.";
39
40
  }
40
41
 
41
42
  const repoRoot = ctx.directory as string;
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import fs from "node:fs";
3
3
  import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
4
4
  import type { AstrocodeConfig } from "../config/schema";
5
- import type { SqliteDb } from "../state/db";
5
+ import type { RuntimeState } from "../state/types";
6
6
  import { withTx } from "../state/db";
7
7
  import type { StageKey, StageRunRow } from "../state/types";
8
8
  import { buildBatonSummary, parseStageOutputText } from "../workflow/baton";
@@ -28,7 +28,7 @@ function ensureStageMatches(run: any, stage_key: StageKey) {
28
28
  }
29
29
 
30
30
  function splitTasksIntoStories(
31
- db: SqliteDb,
31
+ db: any,
32
32
  tasks: any[],
33
33
  run: any,
34
34
  now: string,
@@ -82,8 +82,8 @@ function splitTasksIntoStories(
82
82
  }
83
83
  }
84
84
 
85
- export function createAstroStageStartTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
86
- const { config, db } = opts;
85
+ export function createAstroStageStartTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
86
+ const { runtime } = opts;
87
87
 
88
88
  return tool({
89
89
  description: "Start a stage for a run (sets stage_run.status=running). Usually called by astro_workflow_proceed.",
@@ -94,6 +94,9 @@ export function createAstroStageStartTool(opts: { ctx: any; config: AstrocodeCon
94
94
  subagent_session_id: tool.schema.string().optional(),
95
95
  },
96
96
  execute: async ({ run_id, stage_key, subagent_type, subagent_session_id }) => {
97
+ const { db } = runtime;
98
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
99
+
97
100
  const active = getActiveRun(db);
98
101
  const rid = run_id ?? active?.run_id;
99
102
  if (!rid) throw new Error("No active run and no run_id provided.");
@@ -111,8 +114,8 @@ export function createAstroStageStartTool(opts: { ctx: any; config: AstrocodeCon
111
114
  });
112
115
  }
113
116
 
114
- export function createAstroStageCompleteTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
115
- const { ctx, config, db } = opts;
117
+ export function createAstroStageCompleteTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
118
+ const { ctx, config, runtime } = opts;
116
119
 
117
120
  return tool({
118
121
  description:
@@ -128,6 +131,9 @@ export function createAstroStageCompleteTool(opts: { ctx: any; config: Astrocode
128
131
  relation_reason: tool.schema.string().default("split from stage output"),
129
132
  },
130
133
  execute: async ({ run_id, stage_key, output_text, allow_new_stories, relation_reason }) => {
134
+ const { db } = runtime;
135
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
136
+
131
137
  const repoRoot = ctx.directory as string;
132
138
  const paths = getAstroPaths(repoRoot, config.db.path);
133
139
  ensureAstroDirs(paths);
@@ -396,8 +402,8 @@ Ensure JSON has required fields (stage_key, status) and valid syntax.`;
396
402
  });
397
403
  }
398
404
 
399
- export function createAstroStageFailTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
400
- const { db } = opts;
405
+ export function createAstroStageFailTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
406
+ const { runtime } = opts;
401
407
 
402
408
  return tool({
403
409
  description: "Manually fail a stage and mark run failed.",
@@ -407,6 +413,9 @@ export function createAstroStageFailTool(opts: { ctx: any; config: AstrocodeConf
407
413
  error_text: tool.schema.string().min(1),
408
414
  },
409
415
  execute: async ({ run_id, stage_key, error_text }) => {
416
+ const { db } = runtime;
417
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
418
+
410
419
  const active = getActiveRun(db);
411
420
  const rid = run_id ?? active?.run_id;
412
421
  if (!rid) throw new Error("No active run and no run_id provided.");
@@ -439,8 +448,8 @@ export function createAstroStageFailTool(opts: { ctx: any; config: AstrocodeConf
439
448
  });
440
449
  }
441
450
 
442
- export function createAstroStageResetTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
443
- const { db } = opts;
451
+ export function createAstroStageResetTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
452
+ const { runtime } = opts;
444
453
 
445
454
  return tool({
446
455
  description:
@@ -451,6 +460,9 @@ export function createAstroStageResetTool(opts: { ctx: any; config: AstrocodeCon
451
460
  note: tool.schema.string().default("reset by user"),
452
461
  },
453
462
  execute: async ({ run_id, stage_key, note }) => {
463
+ const { db } = runtime;
464
+ if (!db) return "⚠️ Astrocode not initialized. Run **astro_init** first.";
465
+
454
466
  const run = db.prepare("SELECT * FROM runs WHERE run_id=?").get(run_id) as any;
455
467
  if (!run) throw new Error(`Run not found: ${run_id}`);
456
468