astrocode-workflow 0.1.57 → 0.1.59

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 (49) hide show
  1. package/README.md +243 -11
  2. package/dist/agents/prompts.d.ts +1 -0
  3. package/dist/agents/prompts.js +159 -0
  4. package/dist/agents/registry.js +11 -1
  5. package/dist/config/loader.js +34 -0
  6. package/dist/config/schema.d.ts +12 -1
  7. package/dist/config/schema.js +14 -0
  8. package/dist/hooks/continuation-enforcer.d.ts +9 -1
  9. package/dist/hooks/continuation-enforcer.js +2 -1
  10. package/dist/hooks/inject-provider.d.ts +9 -1
  11. package/dist/hooks/inject-provider.js +11 -3
  12. package/dist/hooks/tool-output-truncator.d.ts +9 -1
  13. package/dist/hooks/tool-output-truncator.js +2 -1
  14. package/dist/index.js +228 -45
  15. package/dist/state/adapters/index.d.ts +4 -2
  16. package/dist/state/adapters/index.js +23 -27
  17. package/dist/state/db.d.ts +6 -8
  18. package/dist/state/db.js +106 -45
  19. package/dist/tools/index.d.ts +13 -3
  20. package/dist/tools/index.js +14 -31
  21. package/dist/tools/init.d.ts +10 -1
  22. package/dist/tools/init.js +73 -18
  23. package/dist/tools/spec.d.ts +0 -1
  24. package/dist/tools/spec.js +4 -1
  25. package/dist/tools/status.d.ts +1 -1
  26. package/dist/tools/status.js +70 -52
  27. package/dist/tools/workflow.js +6 -3
  28. package/dist/workflow/directives.d.ts +2 -0
  29. package/dist/workflow/directives.js +34 -19
  30. package/dist/workflow/state-machine.d.ts +27 -0
  31. package/dist/workflow/state-machine.js +167 -86
  32. package/package.json +1 -1
  33. package/src/agents/prompts.ts +160 -0
  34. package/src/agents/registry.ts +16 -1
  35. package/src/config/loader.ts +39 -4
  36. package/src/config/schema.ts +16 -0
  37. package/src/hooks/continuation-enforcer.ts +9 -2
  38. package/src/hooks/inject-provider.ts +18 -4
  39. package/src/hooks/tool-output-truncator.ts +9 -2
  40. package/src/index.ts +260 -56
  41. package/src/state/adapters/index.ts +21 -26
  42. package/src/state/db.ts +114 -58
  43. package/src/tools/index.ts +29 -31
  44. package/src/tools/init.ts +91 -22
  45. package/src/tools/spec.ts +6 -2
  46. package/src/tools/status.ts +71 -55
  47. package/src/tools/workflow.ts +7 -4
  48. package/src/workflow/directives.ts +103 -75
  49. package/src/workflow/state-machine.ts +229 -105
package/README.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  Astrocode is a **DB-first, stage-gated agent harness** for OpenCode, rewritten in an **Oh-My-OpenCode–style plugin architecture**:
4
4
 
5
+ ## Design Goals & Non-Goals
6
+
7
+ **Goals**:
8
+ - Deterministic, stage-gated agent workflows
9
+ - Durable SQLite ledger for auditability and resumability
10
+ - Plugin architecture for extensibility
11
+ - Visible continuation with safety rails
12
+
13
+ **Non-Goals**:
14
+ - General-purpose workflow engine (focused on agent work)
15
+ - Full CI/CD replacement (no deployment pipelines)
16
+ - Distributed runner support (single-machine only)
17
+ - Real-time collaboration features
18
+
19
+ Contributions should align with these goals.
20
+
5
21
  - TypeScript-first implementation in `src/`
6
22
  - OpenCode `config` handler registers **one primary agent** + hidden stage subagents
7
23
  - Hooks for **continuation**, **toasts**, and **tool-output truncation**
@@ -25,28 +41,223 @@ This repository is meant to be a clean starting point for Astrocode v2:
25
41
  - A **Baton** is a stage output artifact written to disk + recorded in DB (`artifacts` + `stage_runs.summary_md/output_json`).
26
42
  - **Continuation** is an explicit directive injected by hooks when a run is still active (`continuations` table).
27
43
 
28
- ## Primary tools (high leverage)
44
+ ## Glossary & State Machine
45
+
46
+ ### Key Terms
47
+
48
+ - **Story**: Backlog ticket representing work to be done
49
+ - **Run**: Execution instance of a story (contains stages)
50
+ - **Stage**: Individual step in the pipeline (frame, plan, spec, implement, review, verify, close)
51
+ - **Baton**: Output artifact from a stage (evidence of completion)
52
+ - **Continuation**: System directive to resume active runs
53
+ - **Inject**: Persistent note or policy stored in DB
54
+
55
+ ### Run State Machine
56
+
57
+ ```
58
+ Story: queued → approved → in_progress (has active run) → done/blocked/archived
59
+
60
+ Run: created → running → completed/failed/aborted
61
+
62
+ Stage: pending → running → completed/failed → (next stage)
63
+ ```
64
+
65
+ ## Stage Gate Semantics
66
+
67
+ Stages progress deterministically through a fixed pipeline: `frame → plan → spec → implement → review → verify → close`.
68
+
69
+ - **Approval**: Required only once per story (via `astro_story_approve`). Stages auto-advance without additional approval.
70
+ - **Deterministic Progression**: Each stage's inputs are pinned (story spec, prior stage outputs, config snapshot). Tool outputs are hashed and stored as artifacts for auditability.
71
+ - **Gate Conditions**:
72
+ - `frame`: Always starts first
73
+ - `plan`: Requires `frame` completion
74
+ - `spec`: Requires `plan` completion
75
+ - `implement`: Requires `spec` completion + valid stage output
76
+ - `review`: Requires `implement` completion
77
+ - `verify`: Requires `review` completion
78
+ - `close`: Requires `verify` completion + all tests pass
79
+ - **Failure Handling**: If a stage fails (error in output or timeout), the run halts. Use `astro_stage_reset` to retry from that stage.
80
+
81
+ ## Artifact Layout
82
+
83
+ Astrocode writes durable artifacts to `.astro/` with this structure:
84
+
85
+ ```
86
+ .astro/
87
+ ├── astro.db # SQLite ledger (source of truth for state)
88
+ ├── spec.md # Current project spec
89
+ ├── runs/
90
+ │ └── <run_id>/ # One directory per run
91
+ │ ├── stages/
92
+ │ │ ├── frame/
93
+ │ │ │ ├── summary.md # Stage output summary
94
+ │ │ │ └── artifacts/ # Stage-specific files
95
+ │ │ ├── plan/
96
+ │ │ │ ├── summary.md
97
+ │ │ │ └── artifacts/
98
+ │ │ └── ... # spec, implement, review, verify, close
99
+ │ └── run_meta.json # Run metadata
100
+ └── injects/ # Persistent notes/policies
101
+ ```
102
+
103
+ **Source of Truth**: DB is canonical for state (runs, stages, approvals). Disk artifacts are evidence payloads, content-addressed by hash for integrity.
29
104
 
105
+ ## Tools by User Journey
106
+
107
+ ### Setup
30
108
  - `astro_init` — initialize `.astro/` + DB schema (idempotent)
31
- - `astro_status` — compact dashboard of current run/stage + next action
32
- - `astro_story_queue` — create a story
109
+ - `astro_spec_set` — set/update project spec
110
+ - `astro_spec_get` — get current project spec
111
+
112
+ ### Backlog Management
113
+ - `astro_story_queue` — create a story (ticket)
33
114
  - `astro_story_approve` — approve story for execution
34
- - `astro_workflow_proceed` — deterministic step/loop orchestrator (the main “harness” tool)
35
- - `astro_stage_start` — start a stage (primitive)
36
- - `astro_stage_complete` — complete a stage from a stage-agent output (parses `ASTRO_JSON_*` markers)
37
- - `astro_repair` — repair invariants / recover corrupt state
115
+ - `astro_story_board` — view stories grouped by state
116
+ - `astro_story_set_state` — admin: manually set story state
117
+
118
+ ### Run Lifecycle
119
+ - `astro_status` — compact dashboard of active run/stage + next action
120
+ - `astro_workflow_proceed` — advance pipeline by one step or loop until blocked
121
+ - `astro_run_get` — get run details and stage statuses
122
+ - `astro_run_abort` — abort active run and unlock story
123
+
124
+ ### Stage Lifecycle
125
+ - `astro_stage_start` — start a stage manually
126
+ - `astro_stage_complete` — complete stage from agent output (parses `ASTRO_JSON_*`)
127
+ - `astro_stage_reset` — reset stage back to pending
128
+ - `astro_stage_fail` — manually fail a stage
129
+
130
+ ### Artifacts & Evidence
131
+ - `astro_artifact_put` — write artifact to disk + record in DB
132
+ - `astro_artifact_list` — list artifacts for run/stage
133
+ - `astro_artifact_get` — get artifact content/metadata
134
+
135
+ ### Injects (Notes/Policies)
136
+ - `astro_inject_put` — create/update persistent inject
137
+ - `astro_inject_list` — list injects
138
+ - `astro_inject_get` — get inject content
139
+ - `astro_inject_search` — search injects by query
38
140
 
39
- Advanced (octopus) tools exist for artifacts, injects, and admin controls.
141
+ ### Diagnostics & Repair
142
+ - `astro_repair` — repair DB invariants and recover state
143
+ - `astro_inject_eligible` — debug: show eligible injects
144
+ - `astro_inject_debug_due` — debug: comprehensive injection diagnostics
40
145
 
41
- ## Continuation (“infinite agency”, but visible)
146
+ ## First Run Walkthrough (90 seconds)
147
+
148
+ Get started with a minimal workflow:
149
+
150
+ ```bash
151
+ # Initialize Astrocode in your project (creates .astro/ directory and DB)
152
+ astro_init
153
+
154
+ # Create and queue a story (backlog ticket)
155
+ astro_story_queue "Add dark mode toggle to settings page"
156
+
157
+ # Approve the story for execution (returns story_key)
158
+ astro_story_approve <story_key_from_above>
159
+
160
+ # Start the deterministic workflow harness (advances stages automatically)
161
+ astro_workflow_proceed
162
+
163
+ # Check status and next action
164
+ astro_status
165
+ ```
166
+
167
+ Example `astro_status` output when a run is active:
168
+ ```
169
+ Active Run: run_abc123 (Story: Add dark mode toggle)
170
+ Current Stage: implement (in_progress)
171
+ Pipeline: frame✓ plan✓ spec✓ implement... review verify close
172
+ Next Action: Wait for stage agent completion, or run astro_workflow_proceed to advance
173
+ ```
174
+
175
+ Success looks like all stages complete with green checkmarks, and your story marked as done.
176
+
177
+ ## Continuation ("infinite agency", but visible)
42
178
 
43
179
  When a run is active and a session goes idle, or a tool completes, Astrocode injects a **visible** directive like:
44
180
 
45
181
  `[SYSTEM DIRECTIVE: ASTROCODE — CONTINUE]`
46
182
 
47
- This directive is deduped (hashed + recorded in DB) to avoid spam loops.
183
+ **Injection Triggers**:
184
+ - Session idle timeout (configurable, default 30s)
185
+ - Tool execution completion (when run is still active)
186
+ - Explicit workflow proceed calls
187
+
188
+ **Dedupe Mechanism**:
189
+ - Directive is hashed (SHA-256 of run_id + stage_key + timestamp + config snapshot)
190
+ - Recorded in `continuations` table to prevent re-injection
191
+ - Ensures deterministic, non-spammy progression
192
+
193
+ **Safety Rails for Auto-Continue** (when enabled):
194
+ - Rate limit: Max 1 continuation per 10 seconds
195
+ - Max depth: 50 continuations per run
196
+ - Cooldown: 5-minute pause after 10 consecutive continuations
197
+ - Bounds prevent runaway loops while allowing "infinite agency"
198
+
199
+ ## Stage-Agent Output Contract
200
+
201
+ Stage agents must output text containing `ASTRO_JSON_*` markers for `astro_stage_complete` to parse and advance the pipeline.
202
+
203
+ **Required Markers**:
204
+ - `ASTRO_JSON_SUMMARY`: Stage completion summary (markdown text)
205
+ - `ASTRO_JSON_OUTPUT`: Structured output data (JSON object)
48
206
 
49
- You can optionally enable bounded auto-continue (rate-limit safe) in config.
207
+ **Schema Shapes**:
208
+ ```json
209
+ // ASTRO_JSON_SUMMARY
210
+ {
211
+ "summary": "## Stage Complete\n\nImplemented the feature as planned..."
212
+ }
213
+
214
+ // ASTRO_JSON_OUTPUT
215
+ {
216
+ "status": "success" | "failed",
217
+ "artifacts": ["path/to/file1", "path/to/file2"],
218
+ "next_stage_hints": ["consider edge cases"],
219
+ "errors": ["error message"] // optional
220
+ }
221
+ ```
222
+
223
+ **Example Stage Output**:
224
+ ```
225
+ I've completed the implementation stage. All tests pass.
226
+
227
+ ASTRO_JSON_SUMMARY
228
+ {"summary": "## Implementation Complete\n\nAdded dark mode toggle with React state management."}
229
+
230
+ ASTRO_JSON_OUTPUT
231
+ {"status": "success", "artifacts": ["src/components/DarkModeToggle.tsx", "src/hooks/useDarkMode.ts"]}
232
+ ```
233
+
234
+ **Failure Modes**:
235
+ - Missing marker: Stage fails, run halts
236
+ - Invalid JSON: Parse error, stage marked failed
237
+ - Status "failed": Stage fails but run can be reset
238
+
239
+ ## Troubleshooting & Recovery
240
+
241
+ ### Common Issues
242
+
243
+ - **better-sqlite3 install failures**: Ensure build tools are installed (see Runtime Requirements). On macOS: `xcode-select --install`. On Linux: `apt install build-essential python3`.
244
+ - **Locked DB**: Close all OpenCode instances. If persistent, `astro_repair` can unlock and recover.
245
+ - **Corrupted/Incomplete stage run**: Use `astro_stage_reset <stage_key>` to retry. If DB state is invalid, run `astro_repair`.
246
+ - **Runaway continuation loop**: Disable auto-continue in config. Manually abort with `astro_run_abort` and restart.
247
+
248
+ ### astro_repair
249
+
250
+ Run `astro_repair` to fix DB inconsistencies and recover corrupt state. It:
251
+ - Validates and repairs run/stage invariants
252
+ - Re-syncs disk artifacts with DB records
253
+ - Writes a repair report artifact
254
+
255
+ It will **not**:
256
+ - Recover lost artifacts from disk
257
+ - Undo committed changes
258
+ - Fix application-level bugs
259
+
260
+ If repair fails, backup `.astro/` and re-initialize with `astro_init`.
50
261
 
51
262
  ## Installation
52
263
 
@@ -71,6 +282,16 @@ Project config is read from:
71
282
 
72
283
  Config is validated (Zod via `@opencode-ai/plugin/tool.schema`) and merged with defaults.
73
284
 
285
+ ## Runtime Requirements
286
+
287
+ - **Node.js**: >=18.0.0 (for native ESM support)
288
+ - **OpenCode**: >=1.1.19 (minimum plugin API version)
289
+ - **Platforms**:
290
+ - macOS: Xcode Command Line Tools required (for better-sqlite3 native compilation)
291
+ - Linux: python3, g++ or clang++ (build essentials)
292
+ - Windows: Visual Studio Build Tools or Windows SDK
293
+ - **SQLite**: better-sqlite3 handles native compilation automatically, but may require system dependencies on some platforms.
294
+
74
295
  ## Development
75
296
 
76
297
  ```bash
@@ -80,6 +301,17 @@ npm run build
80
301
 
81
302
  > NOTE: This repo depends on `better-sqlite3` (native). It is a deliberate v2-alpha choice for performance and DB ergonomics. Backend abstraction is structured so swapping to `bun:sqlite` or `node:sqlite` is straightforward later.
82
303
 
304
+ ## Versioning & Migrations
305
+
306
+ Astrocode v2 alpha is under active development. DB schema changes may occur between versions.
307
+
308
+ - **Schema Updates**: `astro_init` handles schema migrations automatically (idempotent)
309
+ - **Breaking Changes**: May require `astro_repair` or manual DB reset
310
+ - **Backup First**: Always backup `.astro/astro.db` before upgrading
311
+ - **No Downgrades**: Schema migrations are forward-only
312
+
313
+ If you encounter schema errors, run `astro_repair` or delete `.astro/` and re-run `astro_init`.
314
+
83
315
  ## License
84
316
 
85
317
  MIT
@@ -1,2 +1,3 @@
1
1
  export declare const BASE_ORCH_PROMPT = "You are Astro (Orchestrator) for Astrocode.\n\n Mission:\n - Advance a deterministic pipeline: frame \u2192 plan \u2192 spec \u2192 implement \u2192 review \u2192 verify \u2192 close.\n - The SQLite DB is the source of truth. Prefer tools over prose.\n - Never narrate what prompts you received.\n - Keep outputs short; store large outputs as artifacts and reference paths.\n\n Operating rules:\n - Only start new runs when the user explicitly requests implementation, workflow management, or story processing.\n - Answer questions directly when possible without starting workflows.\n - Prefer calling astro_workflow_proceed (step/loop) and astro_status only when actively managing a workflow.\n - Delegate stage work only to the stage subagent matching the current stage.\n - If a stage subagent returns status=blocked, inject the BLOCKED directive and stop.\n - Follow [SYSTEM DIRECTIVE: ASTROCODE \u2014 CONTINUE] by calling astro_workflow_proceed(mode=\"step\", max_steps=1).\n - Never delegate from subagents (enforced by permissions).\n - Be discretionary: assess if the user's request requires workflow initiation or just information.\n";
2
2
  export declare const BASE_STAGE_PROMPT = "You are an Astro stage subagent.\n\n***CRITICAL: Follow the latest [SYSTEM DIRECTIVE: ASTROCODE \u2014 STAGE_*] you receive EXACTLY.***\n\nYour output MUST be in this EXACT format:\n\n1) First, a short summary paragraph (1-3 sentences).\n\n2) Then, immediately after, the ASTRO JSON between markers:\n\n<!-- ASTRO_JSON_BEGIN -->\n{\n \"stage_key\": \"CURRENT_STAGE\",\n \"status\": \"ok\",\n \"summary\": \"Brief summary of work done\",\n \"tasks\": [],\n \"files\": [],\n \"questions\": []\n}\n<!-- ASTRO_JSON_END -->\n\nMANDATORY:\n- stage_key must be \"CURRENT_STAGE\"\n- status must be \"ok\", \"blocked\", or \"failed\"\n- summary must be a non-empty string\n- tasks, files, questions must be ARRAYS (use [] for empty)\n- JSON must be valid syntax\n- Use exact markers shown above\n\nARRAY FORMAT EXAMPLES:\n- tasks: [{\"title\": \"Task name\", \"complexity\": 3}]\n- files: [{\"path\": \"file.js\", \"kind\": \"file\"}]\n- questions: [\"What framework to use?\"]\n\nEXAMPLE OUTPUT:\nI analyzed the requirements and identified key components for implementation.\n\n<!-- ASTRO_JSON_BEGIN -->\n{\n \"stage_key\": \"plan\",\n \"status\": \"ok\",\n \"summary\": \"Created implementation plan with 5 main tasks\",\n \"tasks\": [\n {\"title\": \"Setup database\", \"complexity\": 3},\n {\"title\": \"Build API endpoints\", \"complexity\": 5}\n ],\n \"files\": [\n {\"path\": \"schema.prisma\", \"kind\": \"file\"}\n ],\n \"questions\": []\n}\n<!-- ASTRO_JSON_END -->\n\nIf blocked, set status=\"blocked\" and add ONE question to questions array. Do not deviate from this format.\n";
3
+ export declare const QA_AGENT_PROMPT = "\uD83C\uDF0D Global Engineering Review Prompt (LOCKED)\n\nUse this prompt when reviewing any codebase.\n\n1\uFE0F\u20E3 How every file review starts (MANDATORY)\n\nBefore discussing details, always answer:\n\nSimple question this file answers\n\nWhat decision, contract, or responsibility does this file define?\n\nThings you have at the end of running this code\n\nWhat objects, capabilities, state, guarantees, or invariants now exist?\n\nIf you can't answer these clearly, the file is already suspect.\n\n2\uFE0F\u20E3 Canonical RULE set (GLOBAL ENGINEERING INVARIANTS)\n\nThese are engineering physics laws.\nEvery serious bug maps to one of these.\nNo ad-hoc rules are allowed.\n\nenum RULE {\n CAPABILITY_GUARANTEE =\n \"Expose a capability only when you can guarantee it will execute safely under current runtime conditions.\",\n\n RECOVERY_STRICTER =\n \"Recovery/degraded paths must be simpler and more conservative than the normal path, and must not introduce new failure modes.\",\n\n SOURCE_OF_TRUTH =\n \"For any piece of state, define exactly one authoritative source and explicit precedence rules for any mirrors, caches, or derivations.\",\n\n LIFECYCLE_DETERMINISM =\n \"Initialization and lifecycle must be deterministic: single construction, stable ordering, controlled side effects, and repeatable outcomes.\",\n\n SECURITY_BOUNDARIES =\n \"Security, authorization, and trust boundaries must be explicit, enforced, and never inferred implicitly.\"\n}\n\nIf an issue does not violate one of these rules, it is not a P0/P1 blocker.\n\n3\uFE0F\u20E3 Severity model (WHAT \"P\" MEANS)\n\nSeverity is about trust, not annoyance.\n\nP0 \u2014 Trust break\n\nUnsafe execution\n\nCorrupted or ambiguous state\n\nNon-deterministic lifecycle\n\nBroken auditability / resumability\n\nSecurity boundary violations\n\nP1 \u2014 Reliability break\n\nRuntime crashes after successful boot\n\nCapabilities exposed but unusable\n\nDegraded mode that lies or half-works\n\nRecovery paths that add fragility\n\nP2 \u2014 Quality / polish\n\nReadability, ergonomics, maintainability\n\nNo RULE violated\n\n4\uFE0F\u20E3 Mandatory P0 / P1 Blocker Format (STRICT)\n\nEvery P0 / P1 must be written exactly like this:\n\nP{0|1} \u2014 <short human title>\n\nRule: RULE.<ONE_ENUM_VALUE>\n\nDescription:\nA human-readable explanation of how this specific code violates the rule in context.\nThis is situational and concrete \u2014 not a rule.\n\nWhat:\nThe exact defect or unsafe behavior.\n\nWhere:\nPrecise file + function + construct / lines.\n\nProposed fix:\nThe smallest possible change that restores the rule.\n(Code snippets if helpful.)\n\nWhy:\nHow this fix restores the invariant and what class of failures it prevents.\n\n5\uFE0F\u20E3 Recovery / Degraded Mode Lens (AUTO-APPLIED)\n\nWhenever code introduces:\n\nlimited mode\n\nfallback\n\ncatch-based recovery\n\npartial initialization\n\nAutomatically evaluate against:\n\nRULE.RECOVERY_STRICTER\n\nRULE.CAPABILITY_GUARANTEE\n\nRULE.LIFECYCLE_DETERMINISM\n\nIf recovery adds logic, validation, or ambiguity \u2192 it is a blocker.\n\nRecovery must:\n\nreduce capability surface\n\nfail earlier, not later\n\nbe simpler than the normal path\n\nprovide a clear path back to normal\n\n6\uFE0F\u20E3 How to ask for the next file (TEACHING MODE)\n\nBefore asking for the next file, always explain:\n\nWhat this next file likely does (human-readable)\n\n\"This file takes X, registers it with Y, then enforces Z...\"\n\nWhy this file matters next\n\nWhich RULE it is likely to uphold or violate, and why reviewing it now reduces risk.\n\n7\uFE0F\u20E3 What this frame teaches over time\n\nAfter repeated use, you stop seeing \"random bugs\" and start seeing patterns:\n\nCAPABILITY_GUARANTEE violations (exposed but unsafe APIs)\n\nRECOVERY_STRICTER violations (clever fallbacks that explode)\n\nSOURCE_OF_TRUTH drift (DB vs disk vs memory)\n\nLIFECYCLE_DETERMINISM failures (double init, racey wiring)\n\nSECURITY_BOUNDARIES leaks (implicit trust)\n\nAt that point, reviews become portable skills, not project-specific knowledge.";
@@ -71,3 +71,162 @@ I analyzed the requirements and identified key components for implementation.
71
71
 
72
72
  If blocked, set status="blocked" and add ONE question to questions array. Do not deviate from this format.
73
73
  `;
74
+ export const QA_AGENT_PROMPT = `🌍 Global Engineering Review Prompt (LOCKED)
75
+
76
+ Use this prompt when reviewing any codebase.
77
+
78
+ 1️⃣ How every file review starts (MANDATORY)
79
+
80
+ Before discussing details, always answer:
81
+
82
+ Simple question this file answers
83
+
84
+ What decision, contract, or responsibility does this file define?
85
+
86
+ Things you have at the end of running this code
87
+
88
+ What objects, capabilities, state, guarantees, or invariants now exist?
89
+
90
+ If you can't answer these clearly, the file is already suspect.
91
+
92
+ 2️⃣ Canonical RULE set (GLOBAL ENGINEERING INVARIANTS)
93
+
94
+ These are engineering physics laws.
95
+ Every serious bug maps to one of these.
96
+ No ad-hoc rules are allowed.
97
+
98
+ enum RULE {
99
+ CAPABILITY_GUARANTEE =
100
+ "Expose a capability only when you can guarantee it will execute safely under current runtime conditions.",
101
+
102
+ RECOVERY_STRICTER =
103
+ "Recovery/degraded paths must be simpler and more conservative than the normal path, and must not introduce new failure modes.",
104
+
105
+ SOURCE_OF_TRUTH =
106
+ "For any piece of state, define exactly one authoritative source and explicit precedence rules for any mirrors, caches, or derivations.",
107
+
108
+ LIFECYCLE_DETERMINISM =
109
+ "Initialization and lifecycle must be deterministic: single construction, stable ordering, controlled side effects, and repeatable outcomes.",
110
+
111
+ SECURITY_BOUNDARIES =
112
+ "Security, authorization, and trust boundaries must be explicit, enforced, and never inferred implicitly."
113
+ }
114
+
115
+ If an issue does not violate one of these rules, it is not a P0/P1 blocker.
116
+
117
+ 3️⃣ Severity model (WHAT "P" MEANS)
118
+
119
+ Severity is about trust, not annoyance.
120
+
121
+ P0 — Trust break
122
+
123
+ Unsafe execution
124
+
125
+ Corrupted or ambiguous state
126
+
127
+ Non-deterministic lifecycle
128
+
129
+ Broken auditability / resumability
130
+
131
+ Security boundary violations
132
+
133
+ P1 — Reliability break
134
+
135
+ Runtime crashes after successful boot
136
+
137
+ Capabilities exposed but unusable
138
+
139
+ Degraded mode that lies or half-works
140
+
141
+ Recovery paths that add fragility
142
+
143
+ P2 — Quality / polish
144
+
145
+ Readability, ergonomics, maintainability
146
+
147
+ No RULE violated
148
+
149
+ 4️⃣ Mandatory P0 / P1 Blocker Format (STRICT)
150
+
151
+ Every P0 / P1 must be written exactly like this:
152
+
153
+ P{0|1} — <short human title>
154
+
155
+ Rule: RULE.<ONE_ENUM_VALUE>
156
+
157
+ Description:
158
+ A human-readable explanation of how this specific code violates the rule in context.
159
+ This is situational and concrete — not a rule.
160
+
161
+ What:
162
+ The exact defect or unsafe behavior.
163
+
164
+ Where:
165
+ Precise file + function + construct / lines.
166
+
167
+ Proposed fix:
168
+ The smallest possible change that restores the rule.
169
+ (Code snippets if helpful.)
170
+
171
+ Why:
172
+ How this fix restores the invariant and what class of failures it prevents.
173
+
174
+ 5️⃣ Recovery / Degraded Mode Lens (AUTO-APPLIED)
175
+
176
+ Whenever code introduces:
177
+
178
+ limited mode
179
+
180
+ fallback
181
+
182
+ catch-based recovery
183
+
184
+ partial initialization
185
+
186
+ Automatically evaluate against:
187
+
188
+ RULE.RECOVERY_STRICTER
189
+
190
+ RULE.CAPABILITY_GUARANTEE
191
+
192
+ RULE.LIFECYCLE_DETERMINISM
193
+
194
+ If recovery adds logic, validation, or ambiguity → it is a blocker.
195
+
196
+ Recovery must:
197
+
198
+ reduce capability surface
199
+
200
+ fail earlier, not later
201
+
202
+ be simpler than the normal path
203
+
204
+ provide a clear path back to normal
205
+
206
+ 6️⃣ How to ask for the next file (TEACHING MODE)
207
+
208
+ Before asking for the next file, always explain:
209
+
210
+ What this next file likely does (human-readable)
211
+
212
+ "This file takes X, registers it with Y, then enforces Z..."
213
+
214
+ Why this file matters next
215
+
216
+ Which RULE it is likely to uphold or violate, and why reviewing it now reduces risk.
217
+
218
+ 7️⃣ What this frame teaches over time
219
+
220
+ After repeated use, you stop seeing "random bugs" and start seeing patterns:
221
+
222
+ CAPABILITY_GUARANTEE violations (exposed but unsafe APIs)
223
+
224
+ RECOVERY_STRICTER violations (clever fallbacks that explode)
225
+
226
+ SOURCE_OF_TRUTH drift (DB vs disk vs memory)
227
+
228
+ LIFECYCLE_DETERMINISM failures (double init, racey wiring)
229
+
230
+ SECURITY_BOUNDARIES leaks (implicit trust)
231
+
232
+ At that point, reviews become portable skills, not project-specific knowledge.`;
@@ -1,5 +1,5 @@
1
1
  import { applyModelTuning } from "../shared/model-tuning";
2
- import { BASE_ORCH_PROMPT, BASE_STAGE_PROMPT } from "./prompts";
2
+ import { BASE_ORCH_PROMPT, BASE_STAGE_PROMPT, QA_AGENT_PROMPT } from "./prompts";
3
3
  function denyAll() {
4
4
  return { "*": "deny" };
5
5
  }
@@ -108,6 +108,7 @@ export function createAstroAgents(opts) {
108
108
  },
109
109
  librarian_name: "Librarian",
110
110
  explore_name: "Explore",
111
+ qa_name: "QA",
111
112
  agent_variant_overrides: {}
112
113
  };
113
114
  }
@@ -215,6 +216,15 @@ export function createAstroAgents(opts) {
215
216
  prompt: BASE_STAGE_PROMPT,
216
217
  permission: stageReadOnlyPermissions(),
217
218
  }, "utility");
219
+ // QA agent for code review and verification
220
+ agents[pluginConfig.agents.qa_name] = mk(pluginConfig.agents.qa_name, {
221
+ description: "QA agent: Global engineering review with canonical rules and severity model.",
222
+ mode: "subagent",
223
+ hidden: false, // Make it visible for delegation
224
+ temperature: 0.1,
225
+ prompt: QA_AGENT_PROMPT,
226
+ permission: stageReadOnlyPermissions(), // Read-only for safety
227
+ }, "utility");
218
228
  // Fallback general agent for delegation failures
219
229
  agents["General"] = mk("General", {
220
230
  description: "General-purpose fallback agent for delegation.",
@@ -4,6 +4,37 @@ import { parse as parseJsonc } from "jsonc-parser";
4
4
  import { AstrocodeConfigSchema } from "./schema";
5
5
  import { deepMerge } from "../shared/deep-merge";
6
6
  import { warn } from "../shared/log";
7
+ function validateJsonSerializable(obj, path = "") {
8
+ if (obj === null || obj === undefined)
9
+ return;
10
+ if (typeof obj === "boolean" || typeof obj === "number" || typeof obj === "string")
11
+ return;
12
+ if (typeof obj === "bigint") {
13
+ throw new Error(`Config contains non-JSON-serializable bigint at ${path}`);
14
+ }
15
+ if (typeof obj === "symbol") {
16
+ throw new Error(`Config contains non-JSON-serializable symbol at ${path}`);
17
+ }
18
+ if (typeof obj === "function") {
19
+ throw new Error(`Config contains non-JSON-serializable function at ${path}`);
20
+ }
21
+ if (obj instanceof Date) {
22
+ throw new Error(`Config contains non-JSON-serializable Date at ${path}`);
23
+ }
24
+ if (obj instanceof Map || obj instanceof Set) {
25
+ throw new Error(`Config contains non-JSON-serializable ${obj.constructor.name} at ${path}`);
26
+ }
27
+ if (Array.isArray(obj)) {
28
+ for (let i = 0; i < obj.length; i++) {
29
+ validateJsonSerializable(obj[i], `${path}[${i}]`);
30
+ }
31
+ }
32
+ else if (typeof obj === "object") {
33
+ for (const key of Object.keys(obj)) {
34
+ validateJsonSerializable(obj[key], path ? `${path}.${key}` : key);
35
+ }
36
+ }
37
+ }
7
38
  export function detectConfigFile(basePathNoExt) {
8
39
  const jsoncPath = basePathNoExt + ".jsonc";
9
40
  if (fs.existsSync(jsoncPath))
@@ -43,6 +74,9 @@ export function loadAstrocodeConfig(repoRoot) {
43
74
  cfg = deepMerge(cfg, projectCfg);
44
75
  // Ensure the final config is fully validated with all required defaults
45
76
  cfg = AstrocodeConfigSchema.parse(cfg);
77
+ // CRITICAL CONTRACT: ensure config is JSON-serializable for recovery mode compatibility
78
+ // This prevents silent data corruption in cloneConfig's JSON fallback
79
+ validateJsonSerializable(cfg);
46
80
  // Config loaded successfully (silent)
47
81
  return cfg;
48
82
  }
@@ -21,8 +21,8 @@ export declare const AstrocodeConfigSchema: z.ZodDefault<z.ZodObject<{
21
21
  disabled_commands: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
22
22
  determinism: z.ZodOptional<z.ZodDefault<z.ZodObject<{
23
23
  mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
24
- on: "on";
25
24
  off: "off";
25
+ on: "on";
26
26
  }>>>;
27
27
  strict_stage_order: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
28
28
  }, z.core.$strip>>>;
@@ -60,6 +60,11 @@ export declare const AstrocodeConfigSchema: z.ZodDefault<z.ZodObject<{
60
60
  verify: "verify";
61
61
  close: "close";
62
62
  }>>>>;
63
+ genesis_planning: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
64
+ off: "off";
65
+ first_story_only: "first_story_only";
66
+ always: "always";
67
+ }>>>;
63
68
  default_mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
64
69
  step: "step";
65
70
  loop: "loop";
@@ -133,6 +138,7 @@ export declare const AstrocodeConfigSchema: z.ZodDefault<z.ZodObject<{
133
138
  }, z.core.$strip>>>;
134
139
  librarian_name: z.ZodOptional<z.ZodDefault<z.ZodString>>;
135
140
  explore_name: z.ZodOptional<z.ZodDefault<z.ZodString>>;
141
+ qa_name: z.ZodOptional<z.ZodDefault<z.ZodString>>;
136
142
  agent_variant_overrides: z.ZodOptional<z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
137
143
  variant: z.ZodOptional<z.ZodString>;
138
144
  model: z.ZodOptional<z.ZodString>;
@@ -178,6 +184,11 @@ export declare const AstrocodeConfigSchema: z.ZodDefault<z.ZodObject<{
178
184
  type_allowlist: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
179
185
  max_per_turn: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
180
186
  }, z.core.$strip>>>;
187
+ debug: z.ZodOptional<z.ZodDefault<z.ZodObject<{
188
+ telemetry: z.ZodOptional<z.ZodDefault<z.ZodObject<{
189
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
190
+ }, z.core.$strip>>>;
191
+ }, z.core.$strip>>>;
181
192
  }, z.core.$strip>>;
182
193
  export type AstrocodeConfig = z.infer<typeof AstrocodeConfigSchema>;
183
194
  export {};
@@ -92,6 +92,7 @@ const WorkflowSchema = z.object({
92
92
  "verify",
93
93
  "close",
94
94
  ]),
95
+ genesis_planning: z.enum(["off", "first_story_only", "always"]).default("first_story_only"),
95
96
  default_mode: z.enum(["step", "loop"]).default("step"),
96
97
  default_max_steps: z.number().int().positive().default(1),
97
98
  loop_max_steps_hard_cap: z.number().int().positive().default(200),
@@ -141,6 +142,7 @@ const AgentsSchema = z.object({
141
142
  .default({}),
142
143
  librarian_name: z.string().default("Librarian"),
143
144
  explore_name: z.string().default("Explore"),
145
+ qa_name: z.string().default("QA"),
144
146
  agent_variant_overrides: z
145
147
  .record(z.string(), z.object({
146
148
  variant: z.string().optional(),
@@ -170,6 +172,17 @@ const InjectSchema = z
170
172
  })
171
173
  .partial()
172
174
  .default({});
175
+ const DebugSchema = z
176
+ .object({
177
+ telemetry: z
178
+ .object({
179
+ enabled: z.boolean().default(false),
180
+ })
181
+ .partial()
182
+ .default({}),
183
+ })
184
+ .partial()
185
+ .default({});
173
186
  const UiSchema = z
174
187
  .object({
175
188
  toasts: ToastsSchema,
@@ -206,4 +219,5 @@ export const AstrocodeConfigSchema = z.object({
206
219
  git: GitSchema,
207
220
  ui: UiSchema,
208
221
  inject: InjectSchema,
222
+ debug: DebugSchema,
209
223
  }).partial().default({});
@@ -14,10 +14,18 @@ type EventInput = {
14
14
  properties: any;
15
15
  };
16
16
  };
17
+ type RuntimeState = {
18
+ db: SqliteDb | null;
19
+ limitedMode: boolean;
20
+ limitedModeReason: null | {
21
+ code: "db_init_failed" | "schema_too_old" | "schema_downgrade" | "schema_migration_failed";
22
+ details: any;
23
+ };
24
+ };
17
25
  export declare function createContinuationEnforcer(opts: {
18
26
  ctx: any;
19
27
  config: AstrocodeConfig;
20
- db: SqliteDb;
28
+ runtime: RuntimeState;
21
29
  }): {
22
30
  onToolAfter(input: ToolExecuteAfterInput): Promise<void>;
23
31
  onChatMessage(_input: ChatMessageInput): Promise<void>;
@@ -9,7 +9,8 @@ function msFromIso(iso) {
9
9
  return Number.isFinite(t) ? t : 0;
10
10
  }
11
11
  export function createContinuationEnforcer(opts) {
12
- const { ctx, config, db } = opts;
12
+ const { ctx, config, runtime } = opts;
13
+ const { db } = runtime;
13
14
  const toasts = createToastManager({ ctx, throttleMs: config.ui.toasts.throttle_ms });
14
15
  const sessions = new Map();
15
16
  function getState(sessionId) {
@@ -4,10 +4,18 @@ type ChatMessageInput = {
4
4
  sessionID: string;
5
5
  agent: string;
6
6
  };
7
+ type RuntimeState = {
8
+ db: SqliteDb | null;
9
+ limitedMode: boolean;
10
+ limitedModeReason: null | {
11
+ code: "db_init_failed" | "schema_too_old" | "schema_downgrade" | "schema_migration_failed";
12
+ details: any;
13
+ };
14
+ };
7
15
  export declare function createInjectProvider(opts: {
8
16
  ctx: any;
9
17
  config: AstrocodeConfig;
10
- db: SqliteDb;
18
+ runtime: RuntimeState;
11
19
  }): {
12
20
  onChatMessage(input: ChatMessageInput): Promise<void>;
13
21
  };