astrocode-workflow 0.0.1
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/LICENSE +1 -0
- package/README.md +85 -0
- package/dist/agents/commands.d.ts +9 -0
- package/dist/agents/commands.js +121 -0
- package/dist/agents/prompts.d.ts +2 -0
- package/dist/agents/prompts.js +27 -0
- package/dist/agents/registry.d.ts +6 -0
- package/dist/agents/registry.js +223 -0
- package/dist/agents/types.d.ts +14 -0
- package/dist/agents/types.js +8 -0
- package/dist/config/config-handler.d.ts +4 -0
- package/dist/config/config-handler.js +46 -0
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.js +3 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.js +48 -0
- package/dist/config/schema.d.ts +176 -0
- package/dist/config/schema.js +198 -0
- package/dist/hooks/continuation-enforcer.d.ts +26 -0
- package/dist/hooks/continuation-enforcer.js +166 -0
- package/dist/hooks/tool-output-truncator.d.ts +17 -0
- package/dist/hooks/tool-output-truncator.js +56 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +108 -0
- package/dist/shared/deep-merge.d.ts +8 -0
- package/dist/shared/deep-merge.js +25 -0
- package/dist/shared/hash.d.ts +1 -0
- package/dist/shared/hash.js +4 -0
- package/dist/shared/log.d.ts +7 -0
- package/dist/shared/log.js +24 -0
- package/dist/shared/model-tuning.d.ts +9 -0
- package/dist/shared/model-tuning.js +28 -0
- package/dist/shared/paths.d.ts +19 -0
- package/dist/shared/paths.js +51 -0
- package/dist/shared/text.d.ts +4 -0
- package/dist/shared/text.js +19 -0
- package/dist/shared/time.d.ts +1 -0
- package/dist/shared/time.js +3 -0
- package/dist/state/adapters/index.d.ts +39 -0
- package/dist/state/adapters/index.js +119 -0
- package/dist/state/db.d.ts +17 -0
- package/dist/state/db.js +83 -0
- package/dist/state/ids.d.ts +8 -0
- package/dist/state/ids.js +25 -0
- package/dist/state/schema.d.ts +2 -0
- package/dist/state/schema.js +247 -0
- package/dist/state/types.d.ts +70 -0
- package/dist/state/types.js +1 -0
- package/dist/tools/artifacts.d.ts +18 -0
- package/dist/tools/artifacts.js +71 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +100 -0
- package/dist/tools/init.d.ts +8 -0
- package/dist/tools/init.js +41 -0
- package/dist/tools/injects.d.ts +23 -0
- package/dist/tools/injects.js +99 -0
- package/dist/tools/repair.d.ts +8 -0
- package/dist/tools/repair.js +25 -0
- package/dist/tools/run.d.ts +13 -0
- package/dist/tools/run.js +54 -0
- package/dist/tools/spec.d.ts +13 -0
- package/dist/tools/spec.js +41 -0
- package/dist/tools/stage.d.ts +23 -0
- package/dist/tools/stage.js +284 -0
- package/dist/tools/status.d.ts +8 -0
- package/dist/tools/status.js +107 -0
- package/dist/tools/story.d.ts +23 -0
- package/dist/tools/story.js +85 -0
- package/dist/tools/workflow.d.ts +8 -0
- package/dist/tools/workflow.js +197 -0
- package/dist/ui/inject.d.ts +5 -0
- package/dist/ui/inject.js +9 -0
- package/dist/ui/toasts.d.ts +13 -0
- package/dist/ui/toasts.js +39 -0
- package/dist/workflow/artifacts.d.ts +24 -0
- package/dist/workflow/artifacts.js +45 -0
- package/dist/workflow/baton.d.ts +66 -0
- package/dist/workflow/baton.js +101 -0
- package/dist/workflow/context.d.ts +12 -0
- package/dist/workflow/context.js +67 -0
- package/dist/workflow/directives.d.ts +37 -0
- package/dist/workflow/directives.js +111 -0
- package/dist/workflow/repair.d.ts +8 -0
- package/dist/workflow/repair.js +99 -0
- package/dist/workflow/state-machine.d.ts +43 -0
- package/dist/workflow/state-machine.js +127 -0
- package/dist/workflow/story-helpers.d.ts +9 -0
- package/dist/workflow/story-helpers.js +13 -0
- package/package.json +32 -0
- package/src/agents/commands.ts +137 -0
- package/src/agents/prompts.ts +28 -0
- package/src/agents/registry.ts +310 -0
- package/src/agents/types.ts +31 -0
- package/src/config/config-handler.ts +48 -0
- package/src/config/defaults.ts +4 -0
- package/src/config/loader.ts +55 -0
- package/src/config/schema.ts +236 -0
- package/src/hooks/continuation-enforcer.ts +217 -0
- package/src/hooks/tool-output-truncator.ts +82 -0
- package/src/index.ts +131 -0
- package/src/shared/deep-merge.ts +28 -0
- package/src/shared/hash.ts +5 -0
- package/src/shared/log.ts +30 -0
- package/src/shared/model-tuning.ts +48 -0
- package/src/shared/paths.ts +70 -0
- package/src/shared/text.ts +20 -0
- package/src/shared/time.ts +3 -0
- package/src/shims.node.d.ts +20 -0
- package/src/state/adapters/index.ts +155 -0
- package/src/state/db.ts +105 -0
- package/src/state/ids.ts +33 -0
- package/src/state/schema.ts +249 -0
- package/src/state/types.ts +76 -0
- package/src/tools/artifacts.ts +83 -0
- package/src/tools/index.ts +111 -0
- package/src/tools/init.ts +50 -0
- package/src/tools/injects.ts +108 -0
- package/src/tools/repair.ts +31 -0
- package/src/tools/run.ts +62 -0
- package/src/tools/spec.ts +50 -0
- package/src/tools/stage.ts +361 -0
- package/src/tools/status.ts +119 -0
- package/src/tools/story.ts +106 -0
- package/src/tools/workflow.ts +241 -0
- package/src/ui/inject.ts +13 -0
- package/src/ui/toasts.ts +48 -0
- package/src/workflow/artifacts.ts +69 -0
- package/src/workflow/baton.ts +141 -0
- package/src/workflow/context.ts +86 -0
- package/src/workflow/directives.ts +170 -0
- package/src/workflow/repair.ts +138 -0
- package/src/workflow/state-machine.ts +194 -0
- package/src/workflow/story-helpers.ts +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Astrocode (v2 alpha)
|
|
2
|
+
|
|
3
|
+
Astrocode is a **DB-first, stage-gated agent harness** for OpenCode, rewritten in an **Oh-My-OpenCode–style plugin architecture**:
|
|
4
|
+
|
|
5
|
+
- TypeScript-first implementation in `src/`
|
|
6
|
+
- OpenCode `config` handler registers **one primary agent** + hidden stage subagents
|
|
7
|
+
- Hooks for **continuation**, **toasts**, and **tool-output truncation**
|
|
8
|
+
- A durable SQLite ledger in `.astro/astro.db` is the **source of truth**
|
|
9
|
+
- Stage artifacts (batons/evidence) are written to `.astro/runs/<run_id>/...`
|
|
10
|
+
|
|
11
|
+
> Design intent: *CI/CD for agent work* — auditable, resumable, deterministic progression with a “keep going” harness.
|
|
12
|
+
|
|
13
|
+
## What’s included in this repo (source-only)
|
|
14
|
+
|
|
15
|
+
This repository is meant to be a clean starting point for Astrocode v2:
|
|
16
|
+
- No `.opencode/node_modules`
|
|
17
|
+
- No hand-written `.opencode/` tools or agents
|
|
18
|
+
- Everything is registered via TypeScript at runtime (like OMO)
|
|
19
|
+
|
|
20
|
+
## Quick mental model
|
|
21
|
+
|
|
22
|
+
- **Stories** are backlog tickets (`stories` table).
|
|
23
|
+
- A **Run** is an execution attempt of a story (`runs`, `stage_runs`).
|
|
24
|
+
- A **Stage** is one step of the pipeline (`frame → plan → spec → implement → review → verify → close`).
|
|
25
|
+
- A **Baton** is a stage output artifact written to disk + recorded in DB (`artifacts` + `stage_runs.summary_md/output_json`).
|
|
26
|
+
- **Continuation** is an explicit directive injected by hooks when a run is still active (`continuations` table).
|
|
27
|
+
|
|
28
|
+
## Primary tools (high leverage)
|
|
29
|
+
|
|
30
|
+
- `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
|
|
33
|
+
- `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
|
|
38
|
+
|
|
39
|
+
Advanced (octopus) tools exist for artifacts, injects, and admin controls.
|
|
40
|
+
|
|
41
|
+
## Continuation (“infinite agency”, but visible)
|
|
42
|
+
|
|
43
|
+
When a run is active and a session goes idle, or a tool completes, Astrocode injects a **visible** directive like:
|
|
44
|
+
|
|
45
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — CONTINUE]`
|
|
46
|
+
|
|
47
|
+
This directive is deduped (hashed + recorded in DB) to avoid spam loops.
|
|
48
|
+
|
|
49
|
+
You can optionally enable bounded auto-continue (rate-limit safe) in config.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
Add to your OpenCode config (`~/.config/opencode/opencode.json`):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"plugin": [
|
|
58
|
+
"astrocode-harness"
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then start OpenCode - it will install the plugin automatically.
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
Project config is read from:
|
|
68
|
+
|
|
69
|
+
- `.astro/astrocode.config.jsonc` (preferred)
|
|
70
|
+
- `astrocode.config.jsonc` (fallback)
|
|
71
|
+
|
|
72
|
+
Config is validated (Zod via `@opencode-ai/plugin/tool.schema`) and merged with defaults.
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install
|
|
78
|
+
npm run build
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> 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
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AstrocodeConfig } from "../config/schema";
|
|
2
|
+
export type CommandDefinition = {
|
|
3
|
+
description: string;
|
|
4
|
+
template: string;
|
|
5
|
+
argumentHint?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function createAstroCommands(_opts: {
|
|
8
|
+
pluginConfig: AstrocodeConfig;
|
|
9
|
+
}): Record<string, CommandDefinition>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export function createAstroCommands(_opts) {
|
|
2
|
+
// Keep templates very short: they just steer toward tools.
|
|
3
|
+
return {
|
|
4
|
+
"astro": {
|
|
5
|
+
description: "Astrocode help.",
|
|
6
|
+
argumentHint: "",
|
|
7
|
+
template: `<command-instruction>
|
|
8
|
+
You are using Astrocode vNext.
|
|
9
|
+
Show a short help message with the key commands:
|
|
10
|
+
- /astro-status
|
|
11
|
+
- /astro-init
|
|
12
|
+
- /astro-queue \"title\" --body=\"...\"
|
|
13
|
+
- /astro-approve S-123
|
|
14
|
+
- /astro-next
|
|
15
|
+
- /astro-loop --max-steps=50
|
|
16
|
+
- /astro-repair
|
|
17
|
+
</command-instruction>
|
|
18
|
+
|
|
19
|
+
<user-task>
|
|
20
|
+
$ARGUMENTS
|
|
21
|
+
</user-task>`,
|
|
22
|
+
},
|
|
23
|
+
"astro-status": {
|
|
24
|
+
description: "Show Astrocode status: current run, stage, blockers, next action.",
|
|
25
|
+
argumentHint: "",
|
|
26
|
+
template: `<command-instruction>
|
|
27
|
+
Call tool astro_status.
|
|
28
|
+
Return a compact dashboard.
|
|
29
|
+
</command-instruction>
|
|
30
|
+
|
|
31
|
+
<user-task>
|
|
32
|
+
$ARGUMENTS
|
|
33
|
+
</user-task>`,
|
|
34
|
+
},
|
|
35
|
+
"astro-init": {
|
|
36
|
+
description: "Initialize Astrocode (.astro directory + SQLite DB).",
|
|
37
|
+
argumentHint: "",
|
|
38
|
+
template: `<command-instruction>
|
|
39
|
+
Call tool astro_init.
|
|
40
|
+
Then call astro_status.
|
|
41
|
+
</command-instruction>
|
|
42
|
+
|
|
43
|
+
<user-task>
|
|
44
|
+
$ARGUMENTS
|
|
45
|
+
</user-task>`,
|
|
46
|
+
},
|
|
47
|
+
"astro-queue": {
|
|
48
|
+
description: "Create a queued story.",
|
|
49
|
+
argumentHint: "\"title\" --body=\"...\" --priority=50",
|
|
50
|
+
template: `<command-instruction>
|
|
51
|
+
Create a story using astro_story_queue.
|
|
52
|
+
Title is required. Body optional.
|
|
53
|
+
Return story_key.
|
|
54
|
+
</command-instruction>
|
|
55
|
+
|
|
56
|
+
<user-task>
|
|
57
|
+
$ARGUMENTS
|
|
58
|
+
</user-task>`,
|
|
59
|
+
},
|
|
60
|
+
"astro-approve": {
|
|
61
|
+
description: "Approve a story so it can be run.",
|
|
62
|
+
argumentHint: "S-123",
|
|
63
|
+
template: `<command-instruction>
|
|
64
|
+
Call astro_story_approve with story_key.
|
|
65
|
+
Then call astro_status.
|
|
66
|
+
</command-instruction>
|
|
67
|
+
|
|
68
|
+
<user-task>
|
|
69
|
+
$ARGUMENTS
|
|
70
|
+
</user-task>`,
|
|
71
|
+
},
|
|
72
|
+
"astro-next": {
|
|
73
|
+
description: "Advance Astrocode by one deterministic step.",
|
|
74
|
+
argumentHint: "",
|
|
75
|
+
template: `<command-instruction>
|
|
76
|
+
Call tool astro_workflow_proceed with mode=step.
|
|
77
|
+
If it injects a CONTINUE directive, obey it.
|
|
78
|
+
</command-instruction>
|
|
79
|
+
|
|
80
|
+
<user-task>
|
|
81
|
+
$ARGUMENTS
|
|
82
|
+
</user-task>`,
|
|
83
|
+
},
|
|
84
|
+
"astro-loop": {
|
|
85
|
+
description: "Run Astrocode loop for N steps (bounded).",
|
|
86
|
+
argumentHint: "--max-steps=N",
|
|
87
|
+
template: `<command-instruction>
|
|
88
|
+
Parse --max-steps (default 50).
|
|
89
|
+
Call tool astro_workflow_proceed with mode=loop and max_steps.
|
|
90
|
+
Stop if blocked.
|
|
91
|
+
</command-instruction>
|
|
92
|
+
|
|
93
|
+
<user-task>
|
|
94
|
+
$ARGUMENTS
|
|
95
|
+
</user-task>`,
|
|
96
|
+
},
|
|
97
|
+
"astro-repair": {
|
|
98
|
+
description: "Repair invariants / recover from inconsistent state.",
|
|
99
|
+
argumentHint: "",
|
|
100
|
+
template: `<command-instruction>
|
|
101
|
+
Call astro_repair.
|
|
102
|
+
Return repair report summary + pointers.
|
|
103
|
+
</command-instruction>
|
|
104
|
+
|
|
105
|
+
<user-task>
|
|
106
|
+
$ARGUMENTS
|
|
107
|
+
</user-task>`,
|
|
108
|
+
},
|
|
109
|
+
"astro-board": {
|
|
110
|
+
description: "Show story board grouped by state.",
|
|
111
|
+
argumentHint: "",
|
|
112
|
+
template: `<command-instruction>
|
|
113
|
+
Call astro_story_board.
|
|
114
|
+
</command-instruction>
|
|
115
|
+
|
|
116
|
+
<user-task>
|
|
117
|
+
$ARGUMENTS
|
|
118
|
+
</user-task>`,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const BASE_ORCH_PROMPT = "You are Astro (Orchestrator) for Astrocode.\n\nMission:\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\nOperating rules:\n- Prefer calling astro_workflow_proceed (step/loop) and astro_status.\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- Never delegate from subagents (enforced by permissions).\n";
|
|
2
|
+
export declare const BASE_STAGE_PROMPT = "You are an Astro stage subagent.\n\nFollow the latest [SYSTEM DIRECTIVE: ASTROCODE \u2014 STAGE_*] you receive.\n\nOutput exactly:\n1) Baton markdown (short, structured)\n2) Valid ASTRO JSON between markers:\n<!-- ASTRO_JSON_BEGIN -->\n{...}\n<!-- ASTRO_JSON_END -->\n\nDo not narrate. If blocked, ask exactly ONE question and stop.\n";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const BASE_ORCH_PROMPT = `You are Astro (Orchestrator) for Astrocode.
|
|
2
|
+
|
|
3
|
+
Mission:
|
|
4
|
+
- Advance a deterministic pipeline: frame → plan → spec → implement → review → verify → close.
|
|
5
|
+
- The SQLite DB is the source of truth. Prefer tools over prose.
|
|
6
|
+
- Never narrate what prompts you received.
|
|
7
|
+
- Keep outputs short; store large outputs as artifacts and reference paths.
|
|
8
|
+
|
|
9
|
+
Operating rules:
|
|
10
|
+
- Prefer calling astro_workflow_proceed (step/loop) and astro_status.
|
|
11
|
+
- Delegate stage work only to the stage subagent matching the current stage.
|
|
12
|
+
- If a stage subagent returns status=blocked, inject the BLOCKED directive and stop.
|
|
13
|
+
- Never delegate from subagents (enforced by permissions).
|
|
14
|
+
`;
|
|
15
|
+
export const BASE_STAGE_PROMPT = `You are an Astro stage subagent.
|
|
16
|
+
|
|
17
|
+
Follow the latest [SYSTEM DIRECTIVE: ASTROCODE — STAGE_*] you receive.
|
|
18
|
+
|
|
19
|
+
Output exactly:
|
|
20
|
+
1) Baton markdown (short, structured)
|
|
21
|
+
2) Valid ASTRO JSON between markers:
|
|
22
|
+
<!-- ASTRO_JSON_BEGIN -->
|
|
23
|
+
{...}
|
|
24
|
+
<!-- ASTRO_JSON_END -->
|
|
25
|
+
|
|
26
|
+
Do not narrate. If blocked, ask exactly ONE question and stop.
|
|
27
|
+
`;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { applyModelTuning } from "../shared/model-tuning";
|
|
2
|
+
import { BASE_ORCH_PROMPT, BASE_STAGE_PROMPT } from "./prompts";
|
|
3
|
+
function denyAll() {
|
|
4
|
+
return { "*": "deny" };
|
|
5
|
+
}
|
|
6
|
+
function orchestratorPermissions() {
|
|
7
|
+
return {
|
|
8
|
+
"*": "deny",
|
|
9
|
+
// Common OpenCode tools:
|
|
10
|
+
"read": "allow",
|
|
11
|
+
"grep": "allow",
|
|
12
|
+
"bash": "allow",
|
|
13
|
+
"edit": "allow",
|
|
14
|
+
"write": "allow",
|
|
15
|
+
"question": "allow",
|
|
16
|
+
// Task delegation:
|
|
17
|
+
"task": "allow",
|
|
18
|
+
"delegate_task": "allow",
|
|
19
|
+
"slashcommand": "allow",
|
|
20
|
+
// Astro tools:
|
|
21
|
+
"astro_init": "allow",
|
|
22
|
+
"astro_status": "allow",
|
|
23
|
+
"astro_workflow_proceed": "allow",
|
|
24
|
+
"astro_repair": "allow",
|
|
25
|
+
"astro_spec_get": "allow",
|
|
26
|
+
"astro_spec_set": "allow",
|
|
27
|
+
"astro_story_queue": "allow",
|
|
28
|
+
"astro_story_approve": "allow",
|
|
29
|
+
"astro_story_board": "allow",
|
|
30
|
+
"astro_story_set_state": "allow",
|
|
31
|
+
"astro_run_get": "allow",
|
|
32
|
+
"astro_run_abort": "allow",
|
|
33
|
+
"astro_stage_start": "allow",
|
|
34
|
+
"astro_stage_complete": "allow",
|
|
35
|
+
"astro_stage_fail": "allow",
|
|
36
|
+
"astro_stage_reset": "allow",
|
|
37
|
+
"astro_artifact_put": "allow",
|
|
38
|
+
"astro_artifact_list": "allow",
|
|
39
|
+
"astro_artifact_get": "allow",
|
|
40
|
+
"astro_inject_put": "allow",
|
|
41
|
+
"astro_inject_list": "allow",
|
|
42
|
+
"astro_inject_search": "allow",
|
|
43
|
+
"astro_inject_get": "allow",
|
|
44
|
+
// Git helpers (optional)
|
|
45
|
+
"astro_git_status": "allow",
|
|
46
|
+
"astro_git_diff": "allow",
|
|
47
|
+
"astro_git_commit": "ask",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function stageReadOnlyPermissions() {
|
|
51
|
+
return {
|
|
52
|
+
"*": "deny",
|
|
53
|
+
"read": "allow",
|
|
54
|
+
"grep": "allow",
|
|
55
|
+
"question": "allow",
|
|
56
|
+
"task": "deny",
|
|
57
|
+
"delegate_task": "deny",
|
|
58
|
+
"slashcommand": "deny",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function stageImplementPermissions() {
|
|
62
|
+
return {
|
|
63
|
+
"*": "deny",
|
|
64
|
+
"read": "allow",
|
|
65
|
+
"grep": "allow",
|
|
66
|
+
"edit": "allow",
|
|
67
|
+
"write": "allow",
|
|
68
|
+
"bash": "allow",
|
|
69
|
+
"question": "allow",
|
|
70
|
+
"task": "deny",
|
|
71
|
+
"delegate_task": "deny",
|
|
72
|
+
"slashcommand": "deny",
|
|
73
|
+
"astro_artifact_put": "allow",
|
|
74
|
+
"astro_artifact_list": "allow",
|
|
75
|
+
"astro_artifact_get": "allow",
|
|
76
|
+
"astro_git_diff": "allow",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function stageVerifyPermissions() {
|
|
80
|
+
return {
|
|
81
|
+
"*": "deny",
|
|
82
|
+
"read": "allow",
|
|
83
|
+
"grep": "allow",
|
|
84
|
+
"bash": "allow",
|
|
85
|
+
"question": "allow",
|
|
86
|
+
"task": "deny",
|
|
87
|
+
"delegate_task": "deny",
|
|
88
|
+
"slashcommand": "deny",
|
|
89
|
+
"astro_artifact_put": "allow",
|
|
90
|
+
"astro_artifact_list": "allow",
|
|
91
|
+
"astro_artifact_get": "allow",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function createAstroAgents(opts) {
|
|
95
|
+
const { systemDefaultModel, pluginConfig } = opts;
|
|
96
|
+
// Ensure agents config exists with defaults
|
|
97
|
+
if (!pluginConfig.agents) {
|
|
98
|
+
pluginConfig.agents = {
|
|
99
|
+
orchestrator_name: "Astro",
|
|
100
|
+
stage_agent_names: {
|
|
101
|
+
frame: "AstroFrame",
|
|
102
|
+
plan: "AstroPlan",
|
|
103
|
+
spec: "AstroSpec",
|
|
104
|
+
implement: "AstroImplement",
|
|
105
|
+
review: "AstroReview",
|
|
106
|
+
verify: "AstroVerify",
|
|
107
|
+
close: "AstroClose"
|
|
108
|
+
},
|
|
109
|
+
librarian_name: "AstroLibrarian",
|
|
110
|
+
explore_name: "AstroExplore",
|
|
111
|
+
agent_variant_overrides: {}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const orchestratorName = pluginConfig.agents.orchestrator_name;
|
|
115
|
+
const modelFor = (agentName) => {
|
|
116
|
+
const overrides = pluginConfig.agents?.agent_variant_overrides || {};
|
|
117
|
+
const override = overrides[agentName];
|
|
118
|
+
return override?.model;
|
|
119
|
+
};
|
|
120
|
+
const variantFor = (agentName) => {
|
|
121
|
+
const overrides = pluginConfig.agents?.agent_variant_overrides || {};
|
|
122
|
+
const override = overrides[agentName];
|
|
123
|
+
return override?.variant;
|
|
124
|
+
};
|
|
125
|
+
const mk = (name, base, preset) => {
|
|
126
|
+
const tuned = applyModelTuning(base, preset);
|
|
127
|
+
// Apply per-agent model/variant overrides from config:
|
|
128
|
+
const withOverrides = {
|
|
129
|
+
...tuned,
|
|
130
|
+
model: modelFor(name),
|
|
131
|
+
variant: variantFor(name),
|
|
132
|
+
};
|
|
133
|
+
return withOverrides;
|
|
134
|
+
};
|
|
135
|
+
const agents = {};
|
|
136
|
+
agents[orchestratorName] = mk(orchestratorName, {
|
|
137
|
+
description: "Astrocode vNext orchestrator. DB-driven stage machine with continuation + artifact discipline.",
|
|
138
|
+
mode: "primary",
|
|
139
|
+
maxTokens: 32000,
|
|
140
|
+
prompt: BASE_ORCH_PROMPT,
|
|
141
|
+
permission: orchestratorPermissions(),
|
|
142
|
+
}, "orchestrator");
|
|
143
|
+
// Stage agents (hidden)
|
|
144
|
+
const stageAgents = pluginConfig.agents.stage_agent_names;
|
|
145
|
+
agents[stageAgents.frame] = mk(stageAgents.frame, {
|
|
146
|
+
description: "Stage agent: frame. Produces scope + DoD baton.",
|
|
147
|
+
mode: "subagent",
|
|
148
|
+
hidden: true,
|
|
149
|
+
temperature: 0.1,
|
|
150
|
+
prompt: BASE_STAGE_PROMPT,
|
|
151
|
+
permission: stageReadOnlyPermissions(),
|
|
152
|
+
}, "frame");
|
|
153
|
+
agents[stageAgents.plan] = mk(stageAgents.plan, {
|
|
154
|
+
description: "Stage agent: plan. Produces bounded tasks + files + tests.",
|
|
155
|
+
mode: "subagent",
|
|
156
|
+
hidden: true,
|
|
157
|
+
temperature: 0.1,
|
|
158
|
+
prompt: BASE_STAGE_PROMPT,
|
|
159
|
+
permission: stageReadOnlyPermissions(),
|
|
160
|
+
}, "plan");
|
|
161
|
+
agents[stageAgents.spec] = mk(stageAgents.spec, {
|
|
162
|
+
description: "Stage agent: spec. Produces implementation contracts (minimal).",
|
|
163
|
+
mode: "subagent",
|
|
164
|
+
hidden: true,
|
|
165
|
+
temperature: 0.1,
|
|
166
|
+
prompt: BASE_STAGE_PROMPT,
|
|
167
|
+
permission: stageReadOnlyPermissions(),
|
|
168
|
+
}, "spec");
|
|
169
|
+
agents[stageAgents.implement] = mk(stageAgents.implement, {
|
|
170
|
+
description: "Stage agent: implement. Makes code changes and references diff artifacts.",
|
|
171
|
+
mode: "subagent",
|
|
172
|
+
hidden: true,
|
|
173
|
+
temperature: 0.2,
|
|
174
|
+
prompt: BASE_STAGE_PROMPT,
|
|
175
|
+
permission: stageImplementPermissions(),
|
|
176
|
+
}, "implement");
|
|
177
|
+
agents[stageAgents.review] = mk(stageAgents.review, {
|
|
178
|
+
description: "Stage agent: review. Checks spec compliance and risks.",
|
|
179
|
+
mode: "subagent",
|
|
180
|
+
hidden: true,
|
|
181
|
+
temperature: 0.1,
|
|
182
|
+
prompt: BASE_STAGE_PROMPT,
|
|
183
|
+
permission: stageReadOnlyPermissions(),
|
|
184
|
+
}, "review");
|
|
185
|
+
agents[stageAgents.verify] = mk(stageAgents.verify, {
|
|
186
|
+
description: "Stage agent: verify. Runs checks and produces evidence artifacts.",
|
|
187
|
+
mode: "subagent",
|
|
188
|
+
hidden: true,
|
|
189
|
+
temperature: 0.1,
|
|
190
|
+
prompt: BASE_STAGE_PROMPT,
|
|
191
|
+
permission: stageReadOnlyPermissions(),
|
|
192
|
+
}, "verify");
|
|
193
|
+
agents[stageAgents.close] = mk(stageAgents.close, {
|
|
194
|
+
description: "Stage agent: close. Final run summary + acceptance confirmation.",
|
|
195
|
+
mode: "subagent",
|
|
196
|
+
hidden: true,
|
|
197
|
+
temperature: 0.1,
|
|
198
|
+
prompt: BASE_STAGE_PROMPT,
|
|
199
|
+
permission: stageReadOnlyPermissions(),
|
|
200
|
+
}, "close");
|
|
201
|
+
// Optional utility agents (hidden) — intentionally minimal prompt; prefer stage directives.
|
|
202
|
+
agents[pluginConfig.agents.librarian_name] = mk(pluginConfig.agents.librarian_name, {
|
|
203
|
+
description: "Utility agent: librarian. Fast repo exploration + notes as artifacts.",
|
|
204
|
+
mode: "subagent",
|
|
205
|
+
hidden: true,
|
|
206
|
+
temperature: 0.1,
|
|
207
|
+
prompt: BASE_STAGE_PROMPT,
|
|
208
|
+
permission: stageReadOnlyPermissions(),
|
|
209
|
+
}, "utility");
|
|
210
|
+
agents[pluginConfig.agents.explore_name] = mk(pluginConfig.agents.explore_name, {
|
|
211
|
+
description: "Utility agent: explore. Spikes / scans / code search.",
|
|
212
|
+
mode: "subagent",
|
|
213
|
+
hidden: true,
|
|
214
|
+
temperature: 0.1,
|
|
215
|
+
prompt: BASE_STAGE_PROMPT,
|
|
216
|
+
permission: stageReadOnlyPermissions(),
|
|
217
|
+
}, "utility");
|
|
218
|
+
// Allow user config to disable certain agents
|
|
219
|
+
for (const disabled of pluginConfig.disabled_agents) {
|
|
220
|
+
delete agents[disabled];
|
|
221
|
+
}
|
|
222
|
+
return agents;
|
|
223
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AgentConfig } from "@opencode-ai/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Matches OMO-style heuristics:
|
|
4
|
+
* - OpenAI provider models: "openai/..."
|
|
5
|
+
* - GitHub Copilot GPT models: "github-copilot/gpt-..."
|
|
6
|
+
*/
|
|
7
|
+
export declare function isGptModel(model: string): boolean;
|
|
8
|
+
export type AstroAgentName = "Astro (Orchestrator)" | "astro_frame" | "astro_plan" | "astro_spec" | "astro_implement" | "astro_review" | "astro_verify" | "astro_close" | "astro_librarian" | "astro_explore";
|
|
9
|
+
export type PermissionValue = "ask" | "allow" | "deny";
|
|
10
|
+
export type AgentOverrideConfig = Partial<AgentConfig> & {
|
|
11
|
+
prompt_append?: string;
|
|
12
|
+
variant?: string;
|
|
13
|
+
};
|
|
14
|
+
export type AgentOverrides = Partial<Record<AstroAgentName, AgentOverrideConfig>>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matches OMO-style heuristics:
|
|
3
|
+
* - OpenAI provider models: "openai/..."
|
|
4
|
+
* - GitHub Copilot GPT models: "github-copilot/gpt-..."
|
|
5
|
+
*/
|
|
6
|
+
export function isGptModel(model) {
|
|
7
|
+
return model.startsWith("openai/") || model.startsWith("github-copilot/gpt-");
|
|
8
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createAstroAgents } from "../agents/registry";
|
|
2
|
+
import { createAstroCommands } from "../agents/commands";
|
|
3
|
+
/** Best-effort extraction of system default model from an OpenCode config object. */
|
|
4
|
+
function detectSystemDefaultModel(config) {
|
|
5
|
+
const direct = config?.model;
|
|
6
|
+
if (typeof direct === "string" && direct.trim())
|
|
7
|
+
return direct.trim();
|
|
8
|
+
const agentDefault = config?.agent?.default?.model;
|
|
9
|
+
if (typeof agentDefault === "string" && agentDefault.trim())
|
|
10
|
+
return agentDefault.trim();
|
|
11
|
+
const firstAgent = config?.agent && typeof config.agent === "object" ? Object.values(config.agent)[0] : null;
|
|
12
|
+
const firstModel = firstAgent?.model;
|
|
13
|
+
if (typeof firstModel === "string" && firstModel.trim())
|
|
14
|
+
return firstModel.trim();
|
|
15
|
+
return "openai/gpt-4o";
|
|
16
|
+
}
|
|
17
|
+
export function createConfigHandler(opts) {
|
|
18
|
+
const { pluginConfig } = opts;
|
|
19
|
+
return async function configHandler(config) {
|
|
20
|
+
try {
|
|
21
|
+
const systemDefaultModel = detectSystemDefaultModel(config);
|
|
22
|
+
const agents = createAstroAgents({ systemDefaultModel, pluginConfig });
|
|
23
|
+
const commands = createAstroCommands({ pluginConfig });
|
|
24
|
+
// Merge agents (do not override user-defined agent configs with same key; user wins)
|
|
25
|
+
config.agent = config.agent ?? {};
|
|
26
|
+
for (const [name, agentCfg] of Object.entries(agents)) {
|
|
27
|
+
if (pluginConfig.disabled_agents?.includes(name))
|
|
28
|
+
continue;
|
|
29
|
+
if (!config.agent[name])
|
|
30
|
+
config.agent[name] = agentCfg;
|
|
31
|
+
}
|
|
32
|
+
// Merge commands (user wins)
|
|
33
|
+
config.command = config.command ?? {};
|
|
34
|
+
for (const [name, cmd] of Object.entries(commands)) {
|
|
35
|
+
if (pluginConfig.disabled_commands?.includes(name))
|
|
36
|
+
continue;
|
|
37
|
+
if (!config.command[name])
|
|
38
|
+
config.command[name] = cmd;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
console.warn("[astrocode] Config handler failed:", e);
|
|
43
|
+
// Don't crash OpenCode, just skip config modifications
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AstrocodeConfig } from "./schema";
|
|
2
|
+
export type ConfigFileDetection = {
|
|
3
|
+
format: "jsonc" | "json";
|
|
4
|
+
path: string;
|
|
5
|
+
} | {
|
|
6
|
+
format: "none";
|
|
7
|
+
path: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function detectConfigFile(basePathNoExt: string): ConfigFileDetection;
|
|
10
|
+
export declare function loadConfigFromPath(p: string): AstrocodeConfig | null;
|
|
11
|
+
export declare function loadAstrocodeConfig(repoRoot: string): AstrocodeConfig;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
4
|
+
import { AstrocodeConfigSchema } from "./schema";
|
|
5
|
+
import { deepMerge } from "../shared/deep-merge";
|
|
6
|
+
import { warn } from "../shared/log";
|
|
7
|
+
export function detectConfigFile(basePathNoExt) {
|
|
8
|
+
const jsoncPath = basePathNoExt + ".jsonc";
|
|
9
|
+
if (fs.existsSync(jsoncPath))
|
|
10
|
+
return { format: "jsonc", path: jsoncPath };
|
|
11
|
+
const jsonPath = basePathNoExt + ".json";
|
|
12
|
+
if (fs.existsSync(jsonPath))
|
|
13
|
+
return { format: "json", path: jsonPath };
|
|
14
|
+
return { format: "none", path: basePathNoExt + ".jsonc" };
|
|
15
|
+
}
|
|
16
|
+
export function loadConfigFromPath(p) {
|
|
17
|
+
if (!fs.existsSync(p))
|
|
18
|
+
return null;
|
|
19
|
+
try {
|
|
20
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
21
|
+
const parsed = parseJsonc(raw);
|
|
22
|
+
const cfg = AstrocodeConfigSchema.parse(parsed);
|
|
23
|
+
return cfg;
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
warn(`Failed to load astrocode config at ${p}; using defaults`, String(e));
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function loadAstrocodeConfig(repoRoot) {
|
|
31
|
+
// project-level: prefer .astro/astrocode.config.jsonc; fallback astrocode.config.jsonc
|
|
32
|
+
const projectBase = path.join(repoRoot, ".astro", "astrocode.config");
|
|
33
|
+
const projectDetected = detectConfigFile(projectBase);
|
|
34
|
+
const projectCfg = loadConfigFromPath(projectDetected.path);
|
|
35
|
+
const legacyBase = path.join(repoRoot, "astrocode.config");
|
|
36
|
+
const legacyDetected = detectConfigFile(legacyBase);
|
|
37
|
+
const legacyCfg = loadConfigFromPath(legacyDetected.path);
|
|
38
|
+
// Start with defaults and merge configs
|
|
39
|
+
let cfg = AstrocodeConfigSchema.parse({});
|
|
40
|
+
if (legacyCfg)
|
|
41
|
+
cfg = deepMerge(cfg, legacyCfg);
|
|
42
|
+
if (projectCfg)
|
|
43
|
+
cfg = deepMerge(cfg, projectCfg);
|
|
44
|
+
// Ensure the final config is fully validated with all required defaults
|
|
45
|
+
cfg = AstrocodeConfigSchema.parse(cfg);
|
|
46
|
+
// Config loaded successfully (silent)
|
|
47
|
+
return cfg;
|
|
48
|
+
}
|