astrocode-workflow 0.1.58 → 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.
- package/README.md +243 -11
- package/dist/agents/prompts.d.ts +1 -0
- package/dist/agents/prompts.js +159 -0
- package/dist/agents/registry.js +11 -1
- package/dist/config/loader.js +34 -0
- package/dist/config/schema.d.ts +7 -1
- package/dist/config/schema.js +2 -0
- package/dist/hooks/continuation-enforcer.d.ts +9 -1
- package/dist/hooks/continuation-enforcer.js +2 -1
- package/dist/hooks/inject-provider.d.ts +9 -1
- package/dist/hooks/inject-provider.js +2 -1
- package/dist/hooks/tool-output-truncator.d.ts +9 -1
- package/dist/hooks/tool-output-truncator.js +2 -1
- package/dist/index.js +228 -45
- package/dist/state/adapters/index.d.ts +4 -2
- package/dist/state/adapters/index.js +23 -27
- package/dist/state/db.d.ts +6 -8
- package/dist/state/db.js +106 -45
- package/dist/tools/index.d.ts +13 -3
- package/dist/tools/index.js +14 -31
- package/dist/tools/init.d.ts +10 -1
- package/dist/tools/init.js +73 -18
- package/dist/tools/spec.d.ts +0 -1
- package/dist/tools/spec.js +4 -1
- package/dist/tools/status.d.ts +1 -1
- package/dist/tools/status.js +70 -52
- package/dist/tools/workflow.js +2 -2
- package/dist/workflow/directives.d.ts +2 -0
- package/dist/workflow/directives.js +34 -19
- package/dist/workflow/state-machine.d.ts +27 -0
- package/dist/workflow/state-machine.js +167 -86
- package/package.json +1 -1
- package/src/agents/prompts.ts +160 -0
- package/src/agents/registry.ts +16 -1
- package/src/config/loader.ts +39 -4
- package/src/config/schema.ts +3 -0
- package/src/hooks/continuation-enforcer.ts +9 -2
- package/src/hooks/inject-provider.ts +9 -2
- package/src/hooks/tool-output-truncator.ts +9 -2
- package/src/index.ts +260 -56
- package/src/state/adapters/index.ts +21 -26
- package/src/state/db.ts +114 -58
- package/src/tools/index.ts +29 -31
- package/src/tools/init.ts +91 -22
- package/src/tools/spec.ts +6 -2
- package/src/tools/status.ts +71 -55
- package/src/tools/workflow.ts +3 -3
- package/src/workflow/directives.ts +103 -75
- 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
|
-
##
|
|
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
|
-
- `
|
|
32
|
-
- `
|
|
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
|
-
- `
|
|
35
|
-
- `
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/agents/prompts.d.ts
CHANGED
|
@@ -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.";
|
package/dist/agents/prompts.js
CHANGED
|
@@ -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.`;
|
package/dist/agents/registry.js
CHANGED
|
@@ -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.",
|
package/dist/config/loader.js
CHANGED
|
@@ -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
|
}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -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>;
|
package/dist/config/schema.js
CHANGED
|
@@ -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(),
|
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
|
|
18
|
+
runtime: RuntimeState;
|
|
11
19
|
}): {
|
|
12
20
|
onChatMessage(input: ChatMessageInput): Promise<void>;
|
|
13
21
|
};
|
|
@@ -2,7 +2,8 @@ import { selectEligibleInjects } from "../tools/injects";
|
|
|
2
2
|
import { injectChatPrompt } from "../ui/inject";
|
|
3
3
|
import { nowISO } from "../shared/time";
|
|
4
4
|
export function createInjectProvider(opts) {
|
|
5
|
-
const { ctx, config,
|
|
5
|
+
const { ctx, config, runtime } = opts;
|
|
6
|
+
const { db } = runtime;
|
|
6
7
|
// Cache to avoid re-injecting the same injects repeatedly
|
|
7
8
|
const injectedCache = new Map();
|
|
8
9
|
function shouldSkipInject(injectId, nowMs) {
|
|
@@ -9,9 +9,17 @@ type ToolExecuteAfterOutput = {
|
|
|
9
9
|
output?: string;
|
|
10
10
|
metadata?: Record<string, any>;
|
|
11
11
|
};
|
|
12
|
+
type RuntimeState = {
|
|
13
|
+
db: SqliteDb | null;
|
|
14
|
+
limitedMode: boolean;
|
|
15
|
+
limitedModeReason: null | {
|
|
16
|
+
code: "db_init_failed" | "schema_too_old" | "schema_downgrade" | "schema_migration_failed";
|
|
17
|
+
details: any;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
12
20
|
export declare function createToolOutputTruncatorHook(opts: {
|
|
13
21
|
ctx: any;
|
|
14
22
|
config: AstrocodeConfig;
|
|
15
|
-
|
|
23
|
+
runtime: RuntimeState;
|
|
16
24
|
}): (input: ToolExecuteAfterInput, output: ToolExecuteAfterOutput) => Promise<void>;
|
|
17
25
|
export {};
|