astrocode-workflow 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/dist/index.js +6 -0
  2. package/dist/shared/metrics.d.ts +66 -0
  3. package/dist/shared/metrics.js +112 -0
  4. package/dist/src/agents/commands.d.ts +9 -0
  5. package/dist/src/agents/commands.js +121 -0
  6. package/dist/src/agents/prompts.d.ts +3 -0
  7. package/dist/src/agents/prompts.js +232 -0
  8. package/dist/src/agents/registry.d.ts +6 -0
  9. package/dist/src/agents/registry.js +242 -0
  10. package/dist/src/agents/types.d.ts +14 -0
  11. package/dist/src/agents/types.js +8 -0
  12. package/dist/src/config/config-handler.d.ts +4 -0
  13. package/dist/src/config/config-handler.js +46 -0
  14. package/dist/src/config/defaults.d.ts +3 -0
  15. package/dist/src/config/defaults.js +3 -0
  16. package/dist/src/config/loader.d.ts +11 -0
  17. package/dist/src/config/loader.js +82 -0
  18. package/dist/src/config/schema.d.ts +194 -0
  19. package/dist/src/config/schema.js +223 -0
  20. package/dist/src/hooks/continuation-enforcer.d.ts +34 -0
  21. package/dist/src/hooks/continuation-enforcer.js +190 -0
  22. package/dist/src/hooks/inject-provider.d.ts +22 -0
  23. package/dist/src/hooks/inject-provider.js +120 -0
  24. package/dist/src/hooks/tool-output-truncator.d.ts +25 -0
  25. package/dist/src/hooks/tool-output-truncator.js +57 -0
  26. package/dist/src/index.d.ts +3 -0
  27. package/dist/src/index.js +308 -0
  28. package/dist/src/shared/deep-merge.d.ts +8 -0
  29. package/dist/src/shared/deep-merge.js +25 -0
  30. package/dist/src/shared/hash.d.ts +1 -0
  31. package/dist/src/shared/hash.js +4 -0
  32. package/dist/src/shared/log.d.ts +7 -0
  33. package/dist/src/shared/log.js +24 -0
  34. package/dist/src/shared/metrics.d.ts +66 -0
  35. package/dist/src/shared/metrics.js +112 -0
  36. package/dist/src/shared/model-tuning.d.ts +9 -0
  37. package/dist/src/shared/model-tuning.js +28 -0
  38. package/dist/src/shared/paths.d.ts +19 -0
  39. package/dist/src/shared/paths.js +64 -0
  40. package/dist/src/shared/text.d.ts +4 -0
  41. package/dist/src/shared/text.js +19 -0
  42. package/dist/src/shared/time.d.ts +1 -0
  43. package/dist/src/shared/time.js +3 -0
  44. package/dist/src/state/adapters/index.d.ts +41 -0
  45. package/dist/src/state/adapters/index.js +115 -0
  46. package/dist/src/state/db.d.ts +16 -0
  47. package/dist/src/state/db.js +225 -0
  48. package/dist/src/state/ids.d.ts +8 -0
  49. package/dist/src/state/ids.js +25 -0
  50. package/dist/src/state/repo-lock.d.ts +3 -0
  51. package/dist/src/state/repo-lock.js +29 -0
  52. package/dist/src/state/schema.d.ts +2 -0
  53. package/dist/src/state/schema.js +251 -0
  54. package/dist/src/state/types.d.ts +71 -0
  55. package/dist/src/state/types.js +1 -0
  56. package/dist/src/tools/artifacts.d.ts +18 -0
  57. package/dist/src/tools/artifacts.js +71 -0
  58. package/dist/src/tools/health.d.ts +8 -0
  59. package/dist/src/tools/health.js +119 -0
  60. package/dist/src/tools/index.d.ts +20 -0
  61. package/dist/src/tools/index.js +94 -0
  62. package/dist/src/tools/init.d.ts +17 -0
  63. package/dist/src/tools/init.js +96 -0
  64. package/dist/src/tools/injects.d.ts +53 -0
  65. package/dist/src/tools/injects.js +325 -0
  66. package/dist/src/tools/metrics.d.ts +7 -0
  67. package/dist/src/tools/metrics.js +61 -0
  68. package/dist/src/tools/repair.d.ts +8 -0
  69. package/dist/src/tools/repair.js +25 -0
  70. package/dist/src/tools/reset.d.ts +8 -0
  71. package/dist/src/tools/reset.js +92 -0
  72. package/dist/src/tools/run.d.ts +13 -0
  73. package/dist/src/tools/run.js +54 -0
  74. package/dist/src/tools/spec.d.ts +12 -0
  75. package/dist/src/tools/spec.js +44 -0
  76. package/dist/src/tools/stage.d.ts +23 -0
  77. package/dist/src/tools/stage.js +371 -0
  78. package/dist/src/tools/status.d.ts +8 -0
  79. package/dist/src/tools/status.js +125 -0
  80. package/dist/src/tools/story.d.ts +23 -0
  81. package/dist/src/tools/story.js +85 -0
  82. package/dist/src/tools/workflow.d.ts +13 -0
  83. package/dist/src/tools/workflow.js +355 -0
  84. package/dist/src/ui/inject.d.ts +12 -0
  85. package/dist/src/ui/inject.js +107 -0
  86. package/dist/src/ui/toasts.d.ts +13 -0
  87. package/dist/src/ui/toasts.js +39 -0
  88. package/dist/src/workflow/artifacts.d.ts +24 -0
  89. package/dist/src/workflow/artifacts.js +45 -0
  90. package/dist/src/workflow/baton.d.ts +72 -0
  91. package/dist/src/workflow/baton.js +166 -0
  92. package/dist/src/workflow/context.d.ts +20 -0
  93. package/dist/src/workflow/context.js +113 -0
  94. package/dist/src/workflow/directives.d.ts +39 -0
  95. package/dist/src/workflow/directives.js +137 -0
  96. package/dist/src/workflow/repair.d.ts +8 -0
  97. package/dist/src/workflow/repair.js +99 -0
  98. package/dist/src/workflow/state-machine.d.ts +86 -0
  99. package/dist/src/workflow/state-machine.js +216 -0
  100. package/dist/src/workflow/story-helpers.d.ts +9 -0
  101. package/dist/src/workflow/story-helpers.js +13 -0
  102. package/dist/state/db.d.ts +1 -0
  103. package/dist/state/db.js +9 -0
  104. package/dist/state/repo-lock.d.ts +3 -0
  105. package/dist/state/repo-lock.js +29 -0
  106. package/dist/test/integration/db-transactions.test.d.ts +1 -0
  107. package/dist/test/integration/db-transactions.test.js +126 -0
  108. package/dist/test/integration/injection-metrics.test.d.ts +1 -0
  109. package/dist/test/integration/injection-metrics.test.js +129 -0
  110. package/dist/tools/health.d.ts +8 -0
  111. package/dist/tools/health.js +119 -0
  112. package/dist/tools/index.js +9 -0
  113. package/dist/tools/metrics.d.ts +7 -0
  114. package/dist/tools/metrics.js +61 -0
  115. package/dist/tools/reset.d.ts +8 -0
  116. package/dist/tools/reset.js +92 -0
  117. package/dist/tools/workflow.js +210 -215
  118. package/dist/ui/inject.d.ts +6 -0
  119. package/dist/ui/inject.js +86 -67
  120. package/dist/workflow/state-machine.d.ts +32 -32
  121. package/dist/workflow/state-machine.js +85 -170
  122. package/package.json +6 -3
  123. package/src/index.ts +8 -0
  124. package/src/shared/metrics.ts +148 -0
  125. package/src/state/db.ts +10 -1
  126. package/src/state/repo-lock.ts +158 -0
  127. package/src/tools/health.ts +128 -0
  128. package/src/tools/index.ts +12 -3
  129. package/src/tools/init.ts +26 -14
  130. package/src/tools/metrics.ts +71 -0
  131. package/src/tools/repair.ts +21 -8
  132. package/src/tools/reset.ts +100 -0
  133. package/src/tools/stage.ts +12 -0
  134. package/src/tools/status.ts +17 -3
  135. package/src/tools/story.ts +41 -15
  136. package/src/tools/workflow.ts +123 -121
  137. package/src/ui/inject.ts +113 -79
  138. package/src/workflow/state-machine.ts +123 -227
  139. package/src/tools/workflow.ts.backup +0 -681
@@ -0,0 +1,242 @@
1
+ import { applyModelTuning } from "../shared/model-tuning";
2
+ import { BASE_ORCH_PROMPT, BASE_STAGE_PROMPT, QA_AGENT_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: "Frame",
102
+ plan: "Plan",
103
+ spec: "Spec",
104
+ implement: "Implement",
105
+ review: "Review",
106
+ verify: "Verify",
107
+ close: "Close"
108
+ },
109
+ librarian_name: "Librarian",
110
+ explore_name: "Explore",
111
+ qa_name: "QA",
112
+ agent_variant_overrides: {}
113
+ };
114
+ }
115
+ const orchestratorName = pluginConfig.agents.orchestrator_name;
116
+ const modelFor = (agentName) => {
117
+ const overrides = pluginConfig.agents?.agent_variant_overrides || {};
118
+ const override = overrides[agentName];
119
+ return override?.model;
120
+ };
121
+ const variantFor = (agentName) => {
122
+ const overrides = pluginConfig.agents?.agent_variant_overrides || {};
123
+ const override = overrides[agentName];
124
+ return override?.variant;
125
+ };
126
+ const mk = (name, base, preset) => {
127
+ const tuned = applyModelTuning(base, preset);
128
+ // Apply per-agent model/variant overrides from config:
129
+ const withOverrides = {
130
+ ...tuned,
131
+ model: modelFor(name),
132
+ variant: variantFor(name),
133
+ };
134
+ return withOverrides;
135
+ };
136
+ const agents = {};
137
+ agents[orchestratorName] = mk(orchestratorName, {
138
+ description: "Astrocode vNext orchestrator. DB-driven stage machine with continuation + artifact discipline.",
139
+ mode: "primary",
140
+ maxTokens: 32000,
141
+ prompt: BASE_ORCH_PROMPT,
142
+ permission: orchestratorPermissions(),
143
+ }, "orchestrator");
144
+ // Stage agents (hidden)
145
+ const stageAgents = pluginConfig.agents.stage_agent_names;
146
+ agents[stageAgents.frame] = mk(stageAgents.frame, {
147
+ description: "Stage agent: frame. Produces scope + DoD baton.",
148
+ mode: "subagent",
149
+ hidden: true,
150
+ temperature: 0.1,
151
+ prompt: BASE_STAGE_PROMPT,
152
+ permission: stageReadOnlyPermissions(),
153
+ }, "frame");
154
+ agents[stageAgents.plan] = mk(stageAgents.plan, {
155
+ description: "Stage agent: plan. Produces bounded tasks + files + tests.",
156
+ mode: "subagent",
157
+ hidden: true,
158
+ temperature: 0.1,
159
+ prompt: BASE_STAGE_PROMPT,
160
+ permission: stageReadOnlyPermissions(),
161
+ }, "plan");
162
+ agents[stageAgents.spec] = mk(stageAgents.spec, {
163
+ description: "Stage agent: spec. Produces implementation contracts (minimal).",
164
+ mode: "subagent",
165
+ hidden: true,
166
+ temperature: 0.1,
167
+ prompt: BASE_STAGE_PROMPT,
168
+ permission: stageReadOnlyPermissions(),
169
+ }, "spec");
170
+ agents[stageAgents.implement] = mk(stageAgents.implement, {
171
+ description: "Stage agent: implement. Makes code changes and references diff artifacts.",
172
+ mode: "subagent",
173
+ hidden: true,
174
+ temperature: 0.2,
175
+ prompt: BASE_STAGE_PROMPT,
176
+ permission: stageImplementPermissions(),
177
+ }, "implement");
178
+ agents[stageAgents.review] = mk(stageAgents.review, {
179
+ description: "Stage agent: review. Checks spec compliance and risks.",
180
+ mode: "subagent",
181
+ hidden: true,
182
+ temperature: 0.1,
183
+ prompt: BASE_STAGE_PROMPT,
184
+ permission: stageReadOnlyPermissions(),
185
+ }, "review");
186
+ agents[stageAgents.verify] = mk(stageAgents.verify, {
187
+ description: "Stage agent: verify. Runs checks and produces evidence artifacts.",
188
+ mode: "subagent",
189
+ hidden: true,
190
+ temperature: 0.1,
191
+ prompt: BASE_STAGE_PROMPT,
192
+ permission: stageReadOnlyPermissions(),
193
+ }, "verify");
194
+ agents[stageAgents.close] = mk(stageAgents.close, {
195
+ description: "Stage agent: close. Final run summary + acceptance confirmation.",
196
+ mode: "subagent",
197
+ hidden: true,
198
+ temperature: 0.1,
199
+ prompt: BASE_STAGE_PROMPT,
200
+ permission: stageReadOnlyPermissions(),
201
+ }, "close");
202
+ // Optional utility agents (hidden) — intentionally minimal prompt; prefer stage directives.
203
+ agents[pluginConfig.agents.librarian_name] = mk(pluginConfig.agents.librarian_name, {
204
+ description: "Utility agent: librarian. Fast repo exploration + notes as artifacts.",
205
+ mode: "subagent",
206
+ hidden: true,
207
+ temperature: 0.1,
208
+ prompt: BASE_STAGE_PROMPT,
209
+ permission: stageReadOnlyPermissions(),
210
+ }, "utility");
211
+ agents[pluginConfig.agents.explore_name] = mk(pluginConfig.agents.explore_name, {
212
+ description: "Utility agent: explore. Spikes / scans / code search.",
213
+ mode: "subagent",
214
+ hidden: true,
215
+ temperature: 0.1,
216
+ prompt: BASE_STAGE_PROMPT,
217
+ permission: stageReadOnlyPermissions(),
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");
228
+ // Fallback general agent for delegation failures
229
+ agents["General"] = mk("General", {
230
+ description: "General-purpose fallback agent for delegation.",
231
+ mode: "subagent",
232
+ hidden: true,
233
+ temperature: 0.1,
234
+ prompt: BASE_STAGE_PROMPT,
235
+ permission: stageReadOnlyPermissions(),
236
+ }, "utility");
237
+ // Allow user config to disable certain agents
238
+ for (const disabled of pluginConfig.disabled_agents) {
239
+ delete agents[disabled];
240
+ }
241
+ return agents;
242
+ }
@@ -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,4 @@
1
+ import type { AstrocodeConfig } from "./schema";
2
+ export declare function createConfigHandler(opts: {
3
+ pluginConfig: AstrocodeConfig;
4
+ }): (config: Record<string, any>) => Promise<void>;
@@ -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,3 @@
1
+ import { type AstrocodeConfig } from "./schema";
2
+ /** Concrete defaults object (post-Zod defaults). */
3
+ export declare const DEFAULT_CONFIG: AstrocodeConfig;
@@ -0,0 +1,3 @@
1
+ import { AstrocodeConfigSchema } from "./schema";
2
+ /** Concrete defaults object (post-Zod defaults). */
3
+ export const DEFAULT_CONFIG = AstrocodeConfigSchema.parse({});
@@ -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,82 @@
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
+ 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
+ }
38
+ export function detectConfigFile(basePathNoExt) {
39
+ const jsoncPath = basePathNoExt + ".jsonc";
40
+ if (fs.existsSync(jsoncPath))
41
+ return { format: "jsonc", path: jsoncPath };
42
+ const jsonPath = basePathNoExt + ".json";
43
+ if (fs.existsSync(jsonPath))
44
+ return { format: "json", path: jsonPath };
45
+ return { format: "none", path: basePathNoExt + ".jsonc" };
46
+ }
47
+ export function loadConfigFromPath(p) {
48
+ if (!fs.existsSync(p))
49
+ return null;
50
+ try {
51
+ const raw = fs.readFileSync(p, "utf-8");
52
+ const parsed = parseJsonc(raw);
53
+ const cfg = AstrocodeConfigSchema.parse(parsed);
54
+ return cfg;
55
+ }
56
+ catch (e) {
57
+ warn(`Failed to load astrocode config at ${p}; using defaults`, String(e));
58
+ return null;
59
+ }
60
+ }
61
+ export function loadAstrocodeConfig(repoRoot) {
62
+ // project-level: prefer .astro/astrocode.config.jsonc; fallback astrocode.config.jsonc
63
+ const projectBase = path.join(repoRoot, ".astro", "astrocode.config");
64
+ const projectDetected = detectConfigFile(projectBase);
65
+ const projectCfg = loadConfigFromPath(projectDetected.path);
66
+ const legacyBase = path.join(repoRoot, "astrocode.config");
67
+ const legacyDetected = detectConfigFile(legacyBase);
68
+ const legacyCfg = loadConfigFromPath(legacyDetected.path);
69
+ // Start with defaults and merge configs
70
+ let cfg = AstrocodeConfigSchema.parse({});
71
+ if (legacyCfg)
72
+ cfg = deepMerge(cfg, legacyCfg);
73
+ if (projectCfg)
74
+ cfg = deepMerge(cfg, projectCfg);
75
+ // Ensure the final config is fully validated with all required defaults
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);
80
+ // Config loaded successfully (silent)
81
+ return cfg;
82
+ }
@@ -0,0 +1,194 @@
1
+ import { z } from "zod";
2
+ export declare const PermissionValueSchema: z.ZodEnum<{
3
+ ask: "ask";
4
+ allow: "allow";
5
+ deny: "deny";
6
+ }>;
7
+ export type PermissionValue = z.infer<typeof PermissionValueSchema>;
8
+ declare const StageKeySchema: z.ZodEnum<{
9
+ frame: "frame";
10
+ plan: "plan";
11
+ spec: "spec";
12
+ implement: "implement";
13
+ review: "review";
14
+ verify: "verify";
15
+ close: "close";
16
+ }>;
17
+ export type StageKey = z.infer<typeof StageKeySchema>;
18
+ export declare const AstrocodeConfigSchema: z.ZodDefault<z.ZodObject<{
19
+ disabled_hooks: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
20
+ disabled_agents: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
21
+ disabled_commands: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
22
+ determinism: z.ZodOptional<z.ZodDefault<z.ZodObject<{
23
+ mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
24
+ off: "off";
25
+ on: "on";
26
+ }>>>;
27
+ strict_stage_order: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
28
+ }, z.core.$strip>>>;
29
+ db: z.ZodOptional<z.ZodDefault<z.ZodObject<{
30
+ path: z.ZodOptional<z.ZodDefault<z.ZodString>>;
31
+ busy_timeout_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
32
+ pragmas: z.ZodOptional<z.ZodDefault<z.ZodObject<{
33
+ journal_mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
34
+ WAL: "WAL";
35
+ DELETE: "DELETE";
36
+ }>>>;
37
+ synchronous: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
38
+ NORMAL: "NORMAL";
39
+ FULL: "FULL";
40
+ OFF: "OFF";
41
+ }>>>;
42
+ foreign_keys: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
43
+ temp_store: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
44
+ DEFAULT: "DEFAULT";
45
+ MEMORY: "MEMORY";
46
+ FILE: "FILE";
47
+ }>>>;
48
+ }, z.core.$strip>>>;
49
+ schema_version_required: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
50
+ allow_auto_migrate: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
51
+ fail_on_downgrade: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
52
+ }, z.core.$strip>>>;
53
+ workflow: z.ZodOptional<z.ZodDefault<z.ZodObject<{
54
+ pipeline: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
55
+ frame: "frame";
56
+ plan: "plan";
57
+ spec: "spec";
58
+ implement: "implement";
59
+ review: "review";
60
+ verify: "verify";
61
+ close: "close";
62
+ }>>>>;
63
+ genesis_planning: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
64
+ off: "off";
65
+ first_story_only: "first_story_only";
66
+ always: "always";
67
+ }>>>;
68
+ default_mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
69
+ step: "step";
70
+ loop: "loop";
71
+ }>>>;
72
+ default_max_steps: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
73
+ loop_max_steps_hard_cap: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
74
+ plan_max_tasks: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
75
+ plan_max_lines: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
76
+ baton_summary_max_lines: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
77
+ forbid_prompt_narration: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
78
+ single_active_run_per_repo: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
79
+ lock_timeout_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
80
+ role_first_subagents: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
81
+ evidence_required: z.ZodOptional<z.ZodDefault<z.ZodObject<{
82
+ verify: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
83
+ implement: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
84
+ }, z.core.$strip>>>;
85
+ }, z.core.$strip>>>;
86
+ continuation: z.ZodOptional<z.ZodDefault<z.ZodObject<{
87
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
88
+ injection_mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
89
+ visible: "visible";
90
+ silent: "silent";
91
+ }>>>;
92
+ inject_on_session_idle: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
93
+ session_idle_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
94
+ inject_on_tool_done_if_run_active: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
95
+ inject_on_message_done_if_run_active: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
96
+ dedupe_window_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
97
+ max_same_directive_repeats: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
98
+ auto_continue: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
99
+ auto_continue_delay_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
100
+ max_auto_steps_per_session: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
101
+ }, z.core.$strip>>>;
102
+ truncation: z.ZodOptional<z.ZodDefault<z.ZodObject<{
103
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
104
+ truncate_all_tool_outputs: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
105
+ max_chars_default: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
106
+ max_chars_webfetch: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
107
+ max_chars_diff: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
108
+ persist_truncated_outputs: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
109
+ }, z.core.$strip>>>;
110
+ context_compaction: z.ZodOptional<z.ZodDefault<z.ZodObject<{
111
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
112
+ snapshot_after_stage_count: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
113
+ snapshot_max_lines: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
114
+ baton_summary_max_lines: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
115
+ inject_max_chars: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
116
+ }, z.core.$strip>>>;
117
+ artifacts: z.ZodOptional<z.ZodDefault<z.ZodObject<{
118
+ root_dir: z.ZodOptional<z.ZodDefault<z.ZodString>>;
119
+ runs_dir: z.ZodOptional<z.ZodDefault<z.ZodString>>;
120
+ spec_path: z.ZodOptional<z.ZodDefault<z.ZodString>>;
121
+ write_full_baton_md: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
122
+ write_baton_summary_md: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
123
+ write_baton_output_json: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
124
+ baton_filename: z.ZodOptional<z.ZodDefault<z.ZodString>>;
125
+ baton_summary_filename: z.ZodOptional<z.ZodDefault<z.ZodString>>;
126
+ baton_json_filename: z.ZodOptional<z.ZodDefault<z.ZodString>>;
127
+ }, z.core.$strip>>>;
128
+ agents: z.ZodOptional<z.ZodDefault<z.ZodObject<{
129
+ orchestrator_name: z.ZodOptional<z.ZodDefault<z.ZodString>>;
130
+ stage_agent_names: z.ZodOptional<z.ZodDefault<z.ZodObject<{
131
+ frame: z.ZodOptional<z.ZodDefault<z.ZodString>>;
132
+ plan: z.ZodOptional<z.ZodDefault<z.ZodString>>;
133
+ spec: z.ZodOptional<z.ZodDefault<z.ZodString>>;
134
+ implement: z.ZodOptional<z.ZodDefault<z.ZodString>>;
135
+ review: z.ZodOptional<z.ZodDefault<z.ZodString>>;
136
+ verify: z.ZodOptional<z.ZodDefault<z.ZodString>>;
137
+ close: z.ZodOptional<z.ZodDefault<z.ZodString>>;
138
+ }, z.core.$strip>>>;
139
+ librarian_name: z.ZodOptional<z.ZodDefault<z.ZodString>>;
140
+ explore_name: z.ZodOptional<z.ZodDefault<z.ZodString>>;
141
+ qa_name: z.ZodOptional<z.ZodDefault<z.ZodString>>;
142
+ agent_variant_overrides: z.ZodOptional<z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
143
+ variant: z.ZodOptional<z.ZodString>;
144
+ model: z.ZodOptional<z.ZodString>;
145
+ }, z.core.$strip>>>>;
146
+ }, z.core.$strip>>>;
147
+ permissions: z.ZodOptional<z.ZodDefault<z.ZodObject<{
148
+ enforce_task_tool_restrictions: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
149
+ deny_delegate_task_in_subagents: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
150
+ }, z.core.$strip>>>;
151
+ git: z.ZodOptional<z.ZodDefault<z.ZodObject<{
152
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
153
+ allow_dirty_start: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
154
+ auto_branch: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
155
+ branch_prefix: z.ZodOptional<z.ZodDefault<z.ZodString>>;
156
+ auto_commit: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
157
+ commit_message_template: z.ZodOptional<z.ZodDefault<z.ZodString>>;
158
+ persist_diff_artifacts: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
159
+ }, z.core.$strip>>>;
160
+ ui: z.ZodOptional<z.ZodDefault<z.ZodObject<{
161
+ toasts: z.ZodOptional<z.ZodDefault<z.ZodObject<{
162
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
163
+ throttle_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
164
+ show_run_started: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
165
+ show_stage_started: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
166
+ show_stage_completed: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
167
+ show_stage_failed: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
168
+ show_run_completed: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
169
+ show_auto_continue: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
170
+ }, z.core.$strip>>>;
171
+ continue_prompt: z.ZodOptional<z.ZodDefault<z.ZodObject<{
172
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
173
+ mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
174
+ toast_button: "toast_button";
175
+ popup: "popup";
176
+ chat_only: "chat_only";
177
+ }>>>;
178
+ idle_prompt_ms: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
179
+ }, z.core.$strip>>>;
180
+ }, z.core.$strip>>>;
181
+ inject: z.ZodOptional<z.ZodDefault<z.ZodObject<{
182
+ enabled: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
183
+ scope_allowlist: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
184
+ type_allowlist: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
185
+ max_per_turn: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
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>>>;
192
+ }, z.core.$strip>>;
193
+ export type AstrocodeConfig = z.infer<typeof AstrocodeConfigSchema>;
194
+ export {};