astrocode-workflow 0.3.0 → 0.3.2

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 (139) hide show
  1. package/dist/index.js +6 -0
  2. package/dist/shared/metrics.d.ts +66 -0
  3. package/dist/shared/metrics.js +112 -0
  4. package/dist/src/agents/commands.d.ts +9 -0
  5. package/dist/src/agents/commands.js +121 -0
  6. package/dist/src/agents/prompts.d.ts +3 -0
  7. package/dist/src/agents/prompts.js +232 -0
  8. package/dist/src/agents/registry.d.ts +6 -0
  9. package/dist/src/agents/registry.js +242 -0
  10. package/dist/src/agents/types.d.ts +14 -0
  11. package/dist/src/agents/types.js +8 -0
  12. package/dist/src/config/config-handler.d.ts +4 -0
  13. package/dist/src/config/config-handler.js +46 -0
  14. package/dist/src/config/defaults.d.ts +3 -0
  15. package/dist/src/config/defaults.js +3 -0
  16. package/dist/src/config/loader.d.ts +11 -0
  17. package/dist/src/config/loader.js +82 -0
  18. package/dist/src/config/schema.d.ts +194 -0
  19. package/dist/src/config/schema.js +223 -0
  20. package/dist/src/hooks/continuation-enforcer.d.ts +34 -0
  21. package/dist/src/hooks/continuation-enforcer.js +190 -0
  22. package/dist/src/hooks/inject-provider.d.ts +22 -0
  23. package/dist/src/hooks/inject-provider.js +120 -0
  24. package/dist/src/hooks/tool-output-truncator.d.ts +25 -0
  25. package/dist/src/hooks/tool-output-truncator.js +57 -0
  26. package/dist/src/index.d.ts +3 -0
  27. package/dist/src/index.js +308 -0
  28. package/dist/src/shared/deep-merge.d.ts +8 -0
  29. package/dist/src/shared/deep-merge.js +25 -0
  30. package/dist/src/shared/hash.d.ts +1 -0
  31. package/dist/src/shared/hash.js +4 -0
  32. package/dist/src/shared/log.d.ts +7 -0
  33. package/dist/src/shared/log.js +24 -0
  34. package/dist/src/shared/metrics.d.ts +66 -0
  35. package/dist/src/shared/metrics.js +112 -0
  36. package/dist/src/shared/model-tuning.d.ts +9 -0
  37. package/dist/src/shared/model-tuning.js +28 -0
  38. package/dist/src/shared/paths.d.ts +19 -0
  39. package/dist/src/shared/paths.js +64 -0
  40. package/dist/src/shared/text.d.ts +4 -0
  41. package/dist/src/shared/text.js +19 -0
  42. package/dist/src/shared/time.d.ts +1 -0
  43. package/dist/src/shared/time.js +3 -0
  44. package/dist/src/state/adapters/index.d.ts +41 -0
  45. package/dist/src/state/adapters/index.js +115 -0
  46. package/dist/src/state/db.d.ts +16 -0
  47. package/dist/src/state/db.js +225 -0
  48. package/dist/src/state/ids.d.ts +8 -0
  49. package/dist/src/state/ids.js +25 -0
  50. package/dist/src/state/repo-lock.d.ts +3 -0
  51. package/dist/src/state/repo-lock.js +29 -0
  52. package/dist/src/state/schema.d.ts +2 -0
  53. package/dist/src/state/schema.js +251 -0
  54. package/dist/src/state/types.d.ts +71 -0
  55. package/dist/src/state/types.js +1 -0
  56. package/dist/src/tools/artifacts.d.ts +18 -0
  57. package/dist/src/tools/artifacts.js +71 -0
  58. package/dist/src/tools/health.d.ts +8 -0
  59. package/dist/src/tools/health.js +119 -0
  60. package/dist/src/tools/index.d.ts +20 -0
  61. package/dist/src/tools/index.js +94 -0
  62. package/dist/src/tools/init.d.ts +17 -0
  63. package/dist/src/tools/init.js +96 -0
  64. package/dist/src/tools/injects.d.ts +53 -0
  65. package/dist/src/tools/injects.js +325 -0
  66. package/dist/src/tools/metrics.d.ts +7 -0
  67. package/dist/src/tools/metrics.js +61 -0
  68. package/dist/src/tools/repair.d.ts +8 -0
  69. package/dist/src/tools/repair.js +25 -0
  70. package/dist/src/tools/reset.d.ts +8 -0
  71. package/dist/src/tools/reset.js +92 -0
  72. package/dist/src/tools/run.d.ts +13 -0
  73. package/dist/src/tools/run.js +54 -0
  74. package/dist/src/tools/spec.d.ts +12 -0
  75. package/dist/src/tools/spec.js +44 -0
  76. package/dist/src/tools/stage.d.ts +23 -0
  77. package/dist/src/tools/stage.js +371 -0
  78. package/dist/src/tools/status.d.ts +8 -0
  79. package/dist/src/tools/status.js +125 -0
  80. package/dist/src/tools/story.d.ts +23 -0
  81. package/dist/src/tools/story.js +85 -0
  82. package/dist/src/tools/workflow.d.ts +13 -0
  83. package/dist/src/tools/workflow.js +355 -0
  84. package/dist/src/ui/inject.d.ts +12 -0
  85. package/dist/src/ui/inject.js +107 -0
  86. package/dist/src/ui/toasts.d.ts +13 -0
  87. package/dist/src/ui/toasts.js +39 -0
  88. package/dist/src/workflow/artifacts.d.ts +24 -0
  89. package/dist/src/workflow/artifacts.js +45 -0
  90. package/dist/src/workflow/baton.d.ts +72 -0
  91. package/dist/src/workflow/baton.js +166 -0
  92. package/dist/src/workflow/context.d.ts +20 -0
  93. package/dist/src/workflow/context.js +113 -0
  94. package/dist/src/workflow/directives.d.ts +39 -0
  95. package/dist/src/workflow/directives.js +137 -0
  96. package/dist/src/workflow/repair.d.ts +8 -0
  97. package/dist/src/workflow/repair.js +99 -0
  98. package/dist/src/workflow/state-machine.d.ts +86 -0
  99. package/dist/src/workflow/state-machine.js +216 -0
  100. package/dist/src/workflow/story-helpers.d.ts +9 -0
  101. package/dist/src/workflow/story-helpers.js +13 -0
  102. package/dist/state/db.d.ts +1 -0
  103. package/dist/state/db.js +9 -0
  104. package/dist/state/repo-lock.d.ts +3 -0
  105. package/dist/state/repo-lock.js +29 -0
  106. package/dist/test/integration/db-transactions.test.d.ts +1 -0
  107. package/dist/test/integration/db-transactions.test.js +126 -0
  108. package/dist/test/integration/injection-metrics.test.d.ts +1 -0
  109. package/dist/test/integration/injection-metrics.test.js +129 -0
  110. package/dist/tools/health.d.ts +8 -0
  111. package/dist/tools/health.js +119 -0
  112. package/dist/tools/index.js +9 -0
  113. package/dist/tools/metrics.d.ts +7 -0
  114. package/dist/tools/metrics.js +61 -0
  115. package/dist/tools/reset.d.ts +8 -0
  116. package/dist/tools/reset.js +92 -0
  117. package/dist/tools/workflow.js +210 -215
  118. package/dist/ui/inject.d.ts +6 -0
  119. package/dist/ui/inject.js +86 -67
  120. package/dist/workflow/state-machine.d.ts +32 -32
  121. package/dist/workflow/state-machine.js +85 -170
  122. package/package.json +6 -3
  123. package/src/index.ts +8 -0
  124. package/src/shared/metrics.ts +148 -0
  125. package/src/state/db.ts +10 -1
  126. package/src/state/repo-lock.ts +158 -0
  127. package/src/tools/health.ts +128 -0
  128. package/src/tools/index.ts +12 -3
  129. package/src/tools/init.ts +26 -14
  130. package/src/tools/metrics.ts +71 -0
  131. package/src/tools/repair.ts +21 -8
  132. package/src/tools/reset.ts +100 -0
  133. package/src/tools/stage.ts +12 -0
  134. package/src/tools/status.ts +17 -3
  135. package/src/tools/story.ts +41 -15
  136. package/src/tools/workflow.ts +123 -121
  137. package/src/ui/inject.ts +113 -79
  138. package/src/workflow/state-machine.ts +123 -227
  139. package/src/tools/workflow.ts.backup +0 -681
@@ -0,0 +1,251 @@
1
+ // src/state/schema.ts
2
+ // NOTE: This schema is intentionally additive over the original Astrocode schema.
3
+ // vNext adds continuation/snapshot/session tables and stronger indexes.
4
+ //
5
+ // Source of truth: SQLite file at .astro/astro.db
6
+ export const SCHEMA_VERSION = 2;
7
+ export const SCHEMA_SQL = `
8
+ PRAGMA foreign_keys = ON;
9
+
10
+ CREATE TABLE IF NOT EXISTS repo_state (
11
+ id INTEGER PRIMARY KEY CHECK (id = 1),
12
+ schema_version INTEGER NOT NULL,
13
+ created_at TEXT NOT NULL,
14
+ updated_at TEXT NOT NULL,
15
+ spec_hash_before TEXT,
16
+ spec_hash_after TEXT,
17
+ last_run_id TEXT,
18
+ last_story_key TEXT,
19
+ last_event_at TEXT
20
+ );
21
+
22
+ CREATE TABLE IF NOT EXISTS settings (
23
+ key TEXT PRIMARY KEY,
24
+ value TEXT NOT NULL,
25
+ updated_at TEXT NOT NULL
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS epics (
29
+ epic_key TEXT PRIMARY KEY,
30
+ title TEXT NOT NULL,
31
+ body_md TEXT NOT NULL DEFAULT '',
32
+ state TEXT NOT NULL DEFAULT 'active',
33
+ priority INTEGER NOT NULL DEFAULT 0,
34
+ created_at TEXT NOT NULL,
35
+ updated_at TEXT NOT NULL
36
+ );
37
+
38
+ CREATE TABLE IF NOT EXISTS story_drafts (
39
+ draft_id TEXT PRIMARY KEY,
40
+ title TEXT NOT NULL,
41
+ body_md TEXT NOT NULL DEFAULT '',
42
+ meta_json TEXT NOT NULL DEFAULT '{}',
43
+ created_at TEXT NOT NULL,
44
+ updated_at TEXT NOT NULL
45
+ );
46
+
47
+ CREATE TABLE IF NOT EXISTS story_keyseq (
48
+ id INTEGER PRIMARY KEY CHECK (id = 1),
49
+ next_story_num INTEGER NOT NULL
50
+ );
51
+
52
+ CREATE TABLE IF NOT EXISTS stories (
53
+ story_key TEXT PRIMARY KEY,
54
+ epic_key TEXT,
55
+ title TEXT NOT NULL,
56
+ body_md TEXT NOT NULL DEFAULT '',
57
+ state TEXT NOT NULL DEFAULT 'queued', -- queued|approved|in_progress|done|blocked|archived
58
+ priority INTEGER NOT NULL DEFAULT 0,
59
+ approved_at TEXT,
60
+ locked_by_run_id TEXT,
61
+ locked_at TEXT,
62
+ in_progress INTEGER NOT NULL DEFAULT 0,
63
+ created_at TEXT NOT NULL,
64
+ updated_at TEXT NOT NULL,
65
+ FOREIGN KEY (epic_key) REFERENCES epics(epic_key)
66
+ );
67
+
68
+ CREATE TABLE IF NOT EXISTS runs (
69
+ run_id TEXT PRIMARY KEY,
70
+ story_key TEXT NOT NULL,
71
+ status TEXT NOT NULL DEFAULT 'created', -- created|running|completed|failed|aborted
72
+ pipeline_stages_json TEXT NOT NULL DEFAULT '[]',
73
+ current_stage_key TEXT,
74
+ created_at TEXT NOT NULL,
75
+ started_at TEXT,
76
+ completed_at TEXT,
77
+ updated_at TEXT NOT NULL,
78
+ error_text TEXT,
79
+ FOREIGN KEY (story_key) REFERENCES stories(story_key)
80
+ );
81
+
82
+ CREATE TABLE IF NOT EXISTS stage_runs (
83
+ stage_run_id TEXT PRIMARY KEY,
84
+ run_id TEXT NOT NULL,
85
+ stage_key TEXT NOT NULL,
86
+ stage_index INTEGER NOT NULL,
87
+ status TEXT NOT NULL DEFAULT 'pending', -- pending|running|completed|failed|skipped
88
+ created_at TEXT NOT NULL,
89
+ subagent_type TEXT,
90
+ subagent_session_id TEXT,
91
+ started_at TEXT,
92
+ completed_at TEXT,
93
+ updated_at TEXT NOT NULL,
94
+ baton_path TEXT,
95
+ summary_md TEXT,
96
+ output_json TEXT,
97
+ error_text TEXT,
98
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
99
+ );
100
+
101
+ CREATE TABLE IF NOT EXISTS artifacts (
102
+ artifact_id TEXT PRIMARY KEY,
103
+ run_id TEXT,
104
+ stage_key TEXT,
105
+ type TEXT NOT NULL, -- plan|baton|evidence|diff|log|summary|commit|tool_output|snapshot
106
+ path TEXT NOT NULL,
107
+ sha256 TEXT,
108
+ meta_json TEXT NOT NULL DEFAULT '{}',
109
+ created_at TEXT NOT NULL,
110
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
111
+ );
112
+
113
+ CREATE TABLE IF NOT EXISTS tool_runs (
114
+ tool_run_id TEXT PRIMARY KEY,
115
+ run_id TEXT,
116
+ stage_key TEXT,
117
+ tool_name TEXT NOT NULL,
118
+ args_json TEXT NOT NULL DEFAULT '{}',
119
+ output_summary TEXT NOT NULL DEFAULT '',
120
+ output_artifact_id TEXT,
121
+ created_at TEXT NOT NULL,
122
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
123
+ );
124
+
125
+ CREATE TABLE IF NOT EXISTS events (
126
+ event_id TEXT PRIMARY KEY,
127
+ run_id TEXT,
128
+ stage_key TEXT,
129
+ type TEXT NOT NULL,
130
+ body_json TEXT NOT NULL DEFAULT '{}',
131
+ created_at TEXT NOT NULL,
132
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
133
+ );
134
+
135
+ CREATE TABLE IF NOT EXISTS injects (
136
+ inject_id TEXT PRIMARY KEY,
137
+ type TEXT NOT NULL DEFAULT 'note',
138
+ title TEXT NOT NULL,
139
+ body_md TEXT NOT NULL,
140
+ tags_json TEXT NOT NULL DEFAULT '[]',
141
+ scope TEXT NOT NULL DEFAULT 'repo', -- repo|run:<id>|story:<key>|global
142
+ source TEXT NOT NULL DEFAULT 'user', -- user|tool|agent|import
143
+ priority INTEGER NOT NULL DEFAULT 50,
144
+ expires_at TEXT,
145
+ sha256 TEXT,
146
+ created_at TEXT NOT NULL,
147
+ updated_at TEXT NOT NULL
148
+ );
149
+
150
+ CREATE TABLE IF NOT EXISTS running_batches (
151
+ batch_id TEXT PRIMARY KEY,
152
+ run_id TEXT,
153
+ session_id TEXT,
154
+ status TEXT NOT NULL DEFAULT 'running', -- running|completed|failed|aborted
155
+ created_at TEXT NOT NULL,
156
+ updated_at TEXT NOT NULL,
157
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
158
+ );
159
+
160
+ CREATE TABLE IF NOT EXISTS workflow_metrics (
161
+ metric_id TEXT PRIMARY KEY,
162
+ run_id TEXT,
163
+ stage_key TEXT,
164
+ name TEXT NOT NULL,
165
+ value_num REAL,
166
+ value_text TEXT,
167
+ created_at TEXT NOT NULL,
168
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
169
+ );
170
+
171
+ CREATE TABLE IF NOT EXISTS template_intents (
172
+ intent_key TEXT PRIMARY KEY,
173
+ body_md TEXT NOT NULL,
174
+ updated_at TEXT NOT NULL
175
+ );
176
+
177
+ -- vNext tables
178
+
179
+ CREATE TABLE IF NOT EXISTS story_relations (
180
+ parent_story_key TEXT NOT NULL,
181
+ child_story_key TEXT NOT NULL,
182
+ relation_type TEXT NOT NULL DEFAULT 'split',
183
+ reason TEXT NOT NULL DEFAULT '',
184
+ created_at TEXT NOT NULL,
185
+ PRIMARY KEY (parent_story_key, child_story_key),
186
+ FOREIGN KEY (parent_story_key) REFERENCES stories(story_key),
187
+ FOREIGN KEY (child_story_key) REFERENCES stories(story_key)
188
+ );
189
+
190
+ CREATE TABLE IF NOT EXISTS continuations (
191
+ continuation_id INTEGER PRIMARY KEY AUTOINCREMENT,
192
+ session_id TEXT NOT NULL,
193
+ run_id TEXT,
194
+ directive_hash TEXT NOT NULL,
195
+ kind TEXT NOT NULL, -- continue|stage|blocked|repair
196
+ reason TEXT NOT NULL DEFAULT '',
197
+ created_at TEXT NOT NULL,
198
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
199
+ );
200
+
201
+ CREATE INDEX IF NOT EXISTS idx_continuations_session_created ON continuations(session_id, created_at DESC);
202
+ CREATE INDEX IF NOT EXISTS idx_continuations_run_created ON continuations(run_id, created_at DESC);
203
+
204
+ CREATE TABLE IF NOT EXISTS context_snapshots (
205
+ snapshot_id TEXT PRIMARY KEY,
206
+ run_id TEXT NOT NULL,
207
+ stage_key TEXT NOT NULL,
208
+ summary_md TEXT NOT NULL,
209
+ created_at TEXT NOT NULL,
210
+ FOREIGN KEY (run_id) REFERENCES runs(run_id)
211
+ );
212
+
213
+ CREATE INDEX IF NOT EXISTS idx_context_snapshots_run_created ON context_snapshots(run_id, created_at DESC);
214
+
215
+ CREATE TABLE IF NOT EXISTS agent_sessions (
216
+ session_id TEXT PRIMARY KEY,
217
+ parent_session_id TEXT,
218
+ agent_name TEXT NOT NULL,
219
+ run_id TEXT,
220
+ stage_key TEXT,
221
+ status TEXT NOT NULL DEFAULT 'active',
222
+ created_at TEXT NOT NULL,
223
+ updated_at TEXT NOT NULL
224
+ );
225
+
226
+ -- Indexes
227
+
228
+ CREATE INDEX IF NOT EXISTS idx_stories_state ON stories(state);
229
+ CREATE INDEX IF NOT EXISTS idx_runs_story ON runs(story_key);
230
+ CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status);
231
+ CREATE INDEX IF NOT EXISTS idx_stage_runs_run ON stage_runs(run_id, stage_index);
232
+ CREATE INDEX IF NOT EXISTS idx_artifacts_run_stage ON artifacts(run_id, stage_key, created_at DESC);
233
+ CREATE INDEX IF NOT EXISTS idx_events_run ON events(run_id, created_at DESC);
234
+ CREATE INDEX IF NOT EXISTS idx_tool_runs_run ON tool_runs(run_id, created_at DESC);
235
+ CREATE INDEX IF NOT EXISTS idx_injects_scope_priority ON injects(scope, priority DESC, created_at DESC);
236
+ CREATE INDEX IF NOT EXISTS idx_injects_scope_type_priority_updated ON injects(scope, type, priority DESC, updated_at DESC);
237
+ CREATE INDEX IF NOT EXISTS idx_injects_expires ON injects(expires_at) WHERE expires_at IS NOT NULL;
238
+ CREATE INDEX IF NOT EXISTS idx_injects_sha256 ON injects(sha256) WHERE sha256 IS NOT NULL;
239
+
240
+ -- Stronger invariants (SQLite partial indexes)
241
+ -- Only one run may be 'running' at a time (single-repo harness by default).
242
+ CREATE UNIQUE INDEX IF NOT EXISTS uniq_single_running_run
243
+ ON runs(status)
244
+ WHERE status = 'running';
245
+
246
+ -- Only one story may be in_progress=1 at a time (pairs with single running run).
247
+ CREATE UNIQUE INDEX IF NOT EXISTS uniq_single_in_progress_story
248
+ ON stories(in_progress)
249
+ WHERE in_progress = 1;
250
+
251
+ `;
@@ -0,0 +1,71 @@
1
+ export type StoryState = "queued" | "approved" | "in_progress" | "done" | "blocked" | "archived";
2
+ export type RunStatus = "created" | "running" | "completed" | "failed" | "aborted";
3
+ export type StageStatus = "pending" | "running" | "completed" | "failed" | "skipped";
4
+ export type StageKey = "frame" | "plan" | "spec" | "implement" | "review" | "verify" | "close";
5
+ export type StoryRow = {
6
+ story_key: string;
7
+ epic_key: string | null;
8
+ title: string;
9
+ body_md: string;
10
+ state: StoryState;
11
+ priority: number;
12
+ approved_at: string | null;
13
+ locked_by_run_id: string | null;
14
+ locked_at: string | null;
15
+ in_progress: 0 | 1;
16
+ created_at: string;
17
+ updated_at: string;
18
+ };
19
+ export type RunRow = {
20
+ run_id: string;
21
+ story_key: string;
22
+ status: RunStatus;
23
+ pipeline_stages_json: string;
24
+ current_stage_key: string | null;
25
+ created_at: string;
26
+ started_at: string | null;
27
+ completed_at: string | null;
28
+ updated_at: string;
29
+ error_text: string | null;
30
+ };
31
+ export type StageRunRow = {
32
+ stage_run_id: string;
33
+ run_id: string;
34
+ stage_key: StageKey;
35
+ stage_index: number;
36
+ status: StageStatus;
37
+ created_at: string;
38
+ subagent_type: string | null;
39
+ subagent_session_id: string | null;
40
+ started_at: string | null;
41
+ completed_at: string | null;
42
+ updated_at: string;
43
+ baton_path: string | null;
44
+ summary_md: string | null;
45
+ output_json: string | null;
46
+ error_text: string | null;
47
+ };
48
+ export type ArtifactRow = {
49
+ artifact_id: string;
50
+ run_id: string | null;
51
+ stage_key: string | null;
52
+ type: string;
53
+ path: string;
54
+ sha256: string | null;
55
+ meta_json: string;
56
+ created_at: string;
57
+ };
58
+ export type InjectRow = {
59
+ inject_id: string;
60
+ type: string;
61
+ title: string;
62
+ body_md: string;
63
+ tags_json: string;
64
+ scope: string;
65
+ source: string;
66
+ priority: number;
67
+ expires_at: string | null;
68
+ sha256: string | null;
69
+ created_at: string;
70
+ updated_at: string;
71
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { type ToolDefinition } from "@opencode-ai/plugin/tool";
2
+ import type { AstrocodeConfig } from "../config/schema";
3
+ import type { SqliteDb } from "../state/db";
4
+ export declare function createAstroArtifactPutTool(opts: {
5
+ ctx: any;
6
+ config: AstrocodeConfig;
7
+ db: SqliteDb;
8
+ }): ToolDefinition;
9
+ export declare function createAstroArtifactListTool(opts: {
10
+ ctx: any;
11
+ config: AstrocodeConfig;
12
+ db: SqliteDb;
13
+ }): ToolDefinition;
14
+ export declare function createAstroArtifactGetTool(opts: {
15
+ ctx: any;
16
+ config: AstrocodeConfig;
17
+ db: SqliteDb;
18
+ }): ToolDefinition;
@@ -0,0 +1,71 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { tool } from "@opencode-ai/plugin/tool";
4
+ import { putArtifact, listArtifacts, getArtifact } from "../workflow/artifacts";
5
+ export function createAstroArtifactPutTool(opts) {
6
+ const { ctx, db } = opts;
7
+ return tool({
8
+ description: "Write an artifact file under .astro and record it in the DB.",
9
+ args: {
10
+ run_id: tool.schema.string().nullable().optional(),
11
+ stage_key: tool.schema.string().nullable().optional(),
12
+ type: tool.schema.string().default("log"),
13
+ rel_path: tool.schema.string().min(1),
14
+ content: tool.schema.string().default(""),
15
+ meta_json: tool.schema.string().default("{}"),
16
+ },
17
+ execute: async ({ run_id, stage_key, type, rel_path, content, meta_json }) => {
18
+ const repoRoot = ctx.directory;
19
+ const meta = JSON.parse(meta_json || "{}");
20
+ const res = putArtifact({
21
+ repoRoot,
22
+ db,
23
+ run_id: run_id ?? null,
24
+ stage_key: stage_key ?? null,
25
+ type,
26
+ rel_path,
27
+ content,
28
+ meta,
29
+ });
30
+ return `✅ Wrote artifact ${res.artifact_id} (${type}) at ${rel_path} (sha256=${res.sha256})`;
31
+ },
32
+ });
33
+ }
34
+ export function createAstroArtifactListTool(opts) {
35
+ const { db } = opts;
36
+ return tool({
37
+ description: "List artifacts (optionally filtered by run_id, stage_key, type).",
38
+ args: {
39
+ run_id: tool.schema.string().optional(),
40
+ stage_key: tool.schema.string().optional(),
41
+ type: tool.schema.string().optional(),
42
+ },
43
+ execute: async ({ run_id, stage_key, type }) => {
44
+ const rows = listArtifacts(db, { run_id, stage_key, type });
45
+ return JSON.stringify(rows, null, 2);
46
+ },
47
+ });
48
+ }
49
+ export function createAstroArtifactGetTool(opts) {
50
+ const { ctx, config, db } = opts;
51
+ return tool({
52
+ description: "Get artifact metadata (and optionally file contents).",
53
+ args: {
54
+ artifact_id: tool.schema.string().min(1),
55
+ include_body: tool.schema.boolean().default(false),
56
+ max_body_chars: tool.schema.number().int().positive().default(50_000),
57
+ },
58
+ execute: async ({ artifact_id, include_body, max_body_chars }) => {
59
+ const row = getArtifact(db, artifact_id);
60
+ if (!row)
61
+ throw new Error(`Artifact not found: ${artifact_id}`);
62
+ if (!include_body)
63
+ return JSON.stringify(row, null, 2);
64
+ const repoRoot = ctx.directory;
65
+ const abs = path.join(repoRoot, row.path);
66
+ const body = fs.existsSync(abs) ? fs.readFileSync(abs, "utf-8") : "(missing file)";
67
+ const clipped = body.length > max_body_chars ? body.slice(0, max_body_chars) + "\n…(truncated)" : body;
68
+ return JSON.stringify({ ...row, body: clipped }, null, 2);
69
+ },
70
+ });
71
+ }
@@ -0,0 +1,8 @@
1
+ import { type ToolDefinition } from "@opencode-ai/plugin/tool";
2
+ import type { AstrocodeConfig } from "../config/schema";
3
+ import type { SqliteDb } from "../state/db";
4
+ export declare function createAstroHealthTool(opts: {
5
+ ctx: any;
6
+ config: AstrocodeConfig;
7
+ db: SqliteDb;
8
+ }): ToolDefinition;
@@ -0,0 +1,119 @@
1
+ // src/tools/health.ts
2
+ import { tool } from "@opencode-ai/plugin/tool";
3
+ import { getSchemaVersion } from "../state/db";
4
+ import { getActiveRun } from "../workflow/state-machine";
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ export function createAstroHealthTool(opts) {
8
+ const { ctx, config, db } = opts;
9
+ return tool({
10
+ description: "Check Astrocode health: DB status, locks, schema, active runs, recent events.",
11
+ args: {},
12
+ execute: async () => {
13
+ const lines = [];
14
+ const repoRoot = ctx.directory || process.cwd();
15
+ const dbPath = config.db?.path || ".astro/astro.db";
16
+ const fullDbPath = path.resolve(repoRoot, dbPath);
17
+ // System info
18
+ lines.push("# Astrocode Health Check");
19
+ lines.push(`- PID: ${process.pid || "unknown"}`);
20
+ lines.push(`- Repo: ${repoRoot}`);
21
+ lines.push(`- DB Path: ${fullDbPath}`);
22
+ // Lock status
23
+ const lockPath = `${repoRoot}/.astro/astro.lock`;
24
+ try {
25
+ if (fs.existsSync(lockPath)) {
26
+ const lockContent = fs.readFileSync(lockPath, "utf8").trim();
27
+ const parts = lockContent.split(" ");
28
+ if (parts.length >= 2) {
29
+ const pid = parseInt(parts[0]);
30
+ const startedAt = parts[1];
31
+ // Check if PID is still running
32
+ try {
33
+ process.kill(pid, 0); // Signal 0 just checks if process exists
34
+ lines.push(`- Lock: HELD by PID ${pid} (started ${startedAt})`);
35
+ }
36
+ catch {
37
+ lines.push(`- Lock: STALE (PID ${pid} not running, started ${startedAt})`);
38
+ lines.push(` → Run: rm "${lockPath}"`);
39
+ }
40
+ }
41
+ else {
42
+ lines.push(`- Lock: MALFORMED (${lockContent})`);
43
+ }
44
+ }
45
+ else {
46
+ lines.push(`- Lock: NONE (no lock file)`);
47
+ }
48
+ }
49
+ catch (e) {
50
+ lines.push(`- Lock: ERROR (${String(e)})`);
51
+ }
52
+ // DB file status
53
+ const dbExists = fs.existsSync(fullDbPath);
54
+ const walExists = fs.existsSync(`${fullDbPath}-wal`);
55
+ const shmExists = fs.existsSync(`${fullDbPath}-shm`);
56
+ lines.push(`- DB Files:`);
57
+ lines.push(` - Main: ${dbExists ? "EXISTS" : "MISSING"}`);
58
+ lines.push(` - WAL: ${walExists ? "EXISTS" : "MISSING"}`);
59
+ lines.push(` - SHM: ${shmExists ? "EXISTS" : "MISSING"}`);
60
+ if (!dbExists) {
61
+ lines.push(`- STATUS: DB MISSING - run astro_init first`);
62
+ return lines.join("\n");
63
+ }
64
+ // Schema version
65
+ try {
66
+ const schemaVersion = getSchemaVersion(db);
67
+ lines.push(`- Schema Version: ${schemaVersion}`);
68
+ }
69
+ catch (e) {
70
+ lines.push(`- Schema Version: ERROR (${String(e)})`);
71
+ lines.push(`- STATUS: DB CORRUPTED`);
72
+ return lines.join("\n");
73
+ }
74
+ // Active run
75
+ try {
76
+ const activeRun = getActiveRun(db);
77
+ if (activeRun) {
78
+ lines.push(`- Active Run: ${activeRun.run_id} (${activeRun.status})`);
79
+ lines.push(` - Story: ${activeRun.story_key}`);
80
+ lines.push(` - Stage: ${activeRun.current_stage_key || "none"}`);
81
+ lines.push(` - Started: ${activeRun.started_at}`);
82
+ }
83
+ else {
84
+ lines.push(`- Active Run: NONE`);
85
+ }
86
+ }
87
+ catch (e) {
88
+ lines.push(`- Active Run: ERROR (${String(e)})`);
89
+ }
90
+ // Recent events
91
+ try {
92
+ const events = db.prepare(`
93
+ SELECT event_id, run_id, stage_key, type, created_at
94
+ FROM events
95
+ ORDER BY created_at DESC
96
+ LIMIT 10
97
+ `).all();
98
+ lines.push(`- Recent Events (${events.length}):`);
99
+ for (const event of events) {
100
+ const stage = event.stage_key ? `/${event.stage_key}` : "";
101
+ lines.push(` - ${event.created_at}: ${event.type} (${event.run_id || "global"}${stage})`);
102
+ }
103
+ }
104
+ catch (e) {
105
+ lines.push(`- Recent Events: ERROR (${String(e)})`);
106
+ }
107
+ // Status summary
108
+ lines.push(``);
109
+ lines.push(`## Status`);
110
+ lines.push(`✅ DB accessible`);
111
+ lines.push(`✅ Schema valid`);
112
+ lines.push(`✅ Lock file checked`);
113
+ if (walExists || shmExists) {
114
+ lines.push(`⚠️ WAL/SHM files present - indicates unclean shutdown or active transaction`);
115
+ }
116
+ return lines.join("\n");
117
+ },
118
+ });
119
+ }
@@ -0,0 +1,20 @@
1
+ import type { ToolDefinition } from "@opencode-ai/plugin/tool";
2
+ import type { AstrocodeConfig } from "../config/schema";
3
+ import type { SqliteDb } from "../state/db";
4
+ import { AgentConfig } from "@opencode-ai/sdk";
5
+ type RuntimeState = {
6
+ db: SqliteDb | null;
7
+ limitedMode: boolean;
8
+ limitedModeReason: null | {
9
+ code: "db_init_failed" | "schema_too_old" | "schema_downgrade" | "schema_migration_failed";
10
+ details: any;
11
+ };
12
+ };
13
+ type CreateAstroToolsOptions = {
14
+ ctx: any;
15
+ config: AstrocodeConfig;
16
+ agents?: Record<string, AgentConfig>;
17
+ runtime: RuntimeState;
18
+ };
19
+ export declare function createAstroTools(opts: CreateAstroToolsOptions): Record<string, ToolDefinition>;
20
+ export {};
@@ -0,0 +1,94 @@
1
+ import { createAstroInitTool } from "./init";
2
+ import { createAstroStatusTool } from "./status";
3
+ import { createAstroStoryQueueTool, createAstroStoryApproveTool, createAstroStoryBoardTool, createAstroStorySetStateTool } from "./story";
4
+ import { createAstroSpecGetTool, createAstroSpecSetTool } from "./spec";
5
+ import { createAstroRunGetTool, createAstroRunAbortTool } from "./run";
6
+ import { createAstroWorkflowProceedTool } from "./workflow";
7
+ import { createAstroStageStartTool, createAstroStageCompleteTool, createAstroStageFailTool, createAstroStageResetTool } from "./stage";
8
+ import { createAstroArtifactPutTool, createAstroArtifactListTool, createAstroArtifactGetTool } from "./artifacts";
9
+ import { createAstroInjectPutTool, createAstroInjectListTool, createAstroInjectSearchTool, createAstroInjectGetTool, createAstroInjectEligibleTool, createAstroInjectDebugDueTool } from "./injects";
10
+ import { createAstroRepairTool } from "./repair";
11
+ import { createAstroHealthTool } from "./health";
12
+ import { createAstroResetTool } from "./reset";
13
+ import { createAstroMetricsTool } from "./metrics";
14
+ export function createAstroTools(opts) {
15
+ const { ctx, config, agents, runtime } = opts;
16
+ const { db } = runtime;
17
+ const hasDatabase = db !== null; // Source of truth: DB availability
18
+ const tools = {};
19
+ // Always available tools (work without database - guaranteed DB-independent)
20
+ tools.astro_status = createAstroStatusTool({ ctx, config });
21
+ tools.astro_spec_get = createAstroSpecGetTool({ ctx, config });
22
+ tools.astro_health = createAstroHealthTool({ ctx, config, db });
23
+ tools.astro_reset = createAstroResetTool({ ctx, config, db });
24
+ tools.astro_metrics = createAstroMetricsTool({ ctx, config });
25
+ // Recovery tool - available even in limited mode to allow DB initialization
26
+ tools.astro_init = createAstroInitTool({ ctx, config, runtime });
27
+ // Database-dependent tools
28
+ if (hasDatabase) {
29
+ // Ensure agents are available for workflow tools that require them
30
+ if (!agents) {
31
+ throw new Error("astro_workflow_proceed requires agents to be provided in normal mode.");
32
+ }
33
+ tools.astro_story_queue = createAstroStoryQueueTool({ ctx, config, db });
34
+ tools.astro_story_approve = createAstroStoryApproveTool({ ctx, config, db });
35
+ tools.astro_story_board = createAstroStoryBoardTool({ ctx, config, db });
36
+ tools.astro_story_set_state = createAstroStorySetStateTool({ ctx, config, db });
37
+ tools.astro_spec_set = createAstroSpecSetTool({ ctx, config, db });
38
+ tools.astro_run_get = createAstroRunGetTool({ ctx, config, db });
39
+ tools.astro_run_abort = createAstroRunAbortTool({ ctx, config, db });
40
+ tools.astro_workflow_proceed = createAstroWorkflowProceedTool({ ctx, config, db, agents });
41
+ tools.astro_stage_start = createAstroStageStartTool({ ctx, config, db });
42
+ tools.astro_stage_complete = createAstroStageCompleteTool({ ctx, config, db });
43
+ tools.astro_stage_fail = createAstroStageFailTool({ ctx, config, db });
44
+ tools.astro_stage_reset = createAstroStageResetTool({ ctx, config, db });
45
+ tools.astro_artifact_put = createAstroArtifactPutTool({ ctx, config, db });
46
+ tools.astro_artifact_list = createAstroArtifactListTool({ ctx, config, db });
47
+ tools.astro_artifact_get = createAstroArtifactGetTool({ ctx, config, db });
48
+ tools.astro_inject_put = createAstroInjectPutTool({ ctx, config, db });
49
+ tools.astro_inject_list = createAstroInjectListTool({ ctx, config, db });
50
+ tools.astro_inject_search = createAstroInjectSearchTool({ ctx, config, db });
51
+ tools.astro_inject_get = createAstroInjectGetTool({ ctx, config, db });
52
+ tools.astro_inject_eligible = createAstroInjectEligibleTool({ ctx, config, db });
53
+ tools.astro_inject_debug_due = createAstroInjectDebugDueTool({ ctx, config, db });
54
+ tools.astro_repair = createAstroRepairTool({ ctx, config, db });
55
+ }
56
+ // Create aliases for backward compatibility
57
+ const aliases = [
58
+ ["_astro_init", "astro_init"],
59
+ ["_astro_status", "astro_status"],
60
+ ["_astro_story_queue", "astro_story_queue"],
61
+ ["_astro_story_approve", "astro_story_approve"],
62
+ ["_astro_story_board", "astro_story_board"],
63
+ ["_astro_story_set_state", "astro_story_set_state"],
64
+ ["_astro_spec_get", "astro_spec_get"],
65
+ ["_astro_spec_set", "astro_spec_set"],
66
+ ["_astro_run_get", "astro_run_get"],
67
+ ["_astro_run_abort", "astro_run_abort"],
68
+ ["_astro_workflow_proceed", "astro_workflow_proceed"],
69
+ ["_astro_stage_start", "astro_stage_start"],
70
+ ["_astro_stage_complete", "astro_stage_complete"],
71
+ ["_astro_stage_fail", "astro_stage_fail"],
72
+ ["_astro_stage_reset", "astro_stage_reset"],
73
+ ["_astro_artifact_put", "astro_artifact_put"],
74
+ ["_astro_artifact_list", "astro_artifact_list"],
75
+ ["_astro_artifact_get", "astro_artifact_get"],
76
+ ["_astro_inject_put", "astro_inject_put"],
77
+ ["_astro_inject_list", "astro_inject_list"],
78
+ ["_astro_inject_search", "astro_inject_search"],
79
+ ["_astro_inject_get", "astro_inject_get"],
80
+ ["_astro_inject_eligible", "astro_inject_eligible"],
81
+ ["_astro_inject_debug_due", "astro_inject_debug_due"],
82
+ ["_astro_repair", "astro_repair"],
83
+ ["_astro_health", "astro_health"],
84
+ ["_astro_reset", "astro_reset"],
85
+ ["_astro_metrics", "astro_metrics"],
86
+ ];
87
+ // Only add aliases for tools that exist
88
+ for (const [alias, target] of aliases) {
89
+ if (tools[target]) {
90
+ tools[alias] = tools[target];
91
+ }
92
+ }
93
+ return tools;
94
+ }
@@ -0,0 +1,17 @@
1
+ import { type ToolDefinition } from "@opencode-ai/plugin/tool";
2
+ import type { AstrocodeConfig } from "../config/schema";
3
+ import type { SqliteDb } from "../state/db";
4
+ type RuntimeState = {
5
+ db: SqliteDb | null;
6
+ limitedMode: boolean;
7
+ limitedModeReason: null | {
8
+ code: "db_init_failed" | "schema_too_old" | "schema_downgrade" | "schema_migration_failed";
9
+ details: any;
10
+ };
11
+ };
12
+ export declare function createAstroInitTool(opts: {
13
+ ctx: any;
14
+ config: AstrocodeConfig;
15
+ runtime: RuntimeState;
16
+ }): ToolDefinition;
17
+ export {};