astrocode-workflow 0.3.1 → 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.
- package/dist/index.js +6 -0
- package/dist/shared/metrics.d.ts +66 -0
- package/dist/shared/metrics.js +112 -0
- package/dist/src/agents/commands.d.ts +9 -0
- package/dist/src/agents/commands.js +121 -0
- package/dist/src/agents/prompts.d.ts +3 -0
- package/dist/src/agents/prompts.js +232 -0
- package/dist/src/agents/registry.d.ts +6 -0
- package/dist/src/agents/registry.js +242 -0
- package/dist/src/agents/types.d.ts +14 -0
- package/dist/src/agents/types.js +8 -0
- package/dist/src/config/config-handler.d.ts +4 -0
- package/dist/src/config/config-handler.js +46 -0
- package/dist/src/config/defaults.d.ts +3 -0
- package/dist/src/config/defaults.js +3 -0
- package/dist/src/config/loader.d.ts +11 -0
- package/dist/src/config/loader.js +82 -0
- package/dist/src/config/schema.d.ts +194 -0
- package/dist/src/config/schema.js +223 -0
- package/dist/src/hooks/continuation-enforcer.d.ts +34 -0
- package/dist/src/hooks/continuation-enforcer.js +190 -0
- package/dist/src/hooks/inject-provider.d.ts +22 -0
- package/dist/src/hooks/inject-provider.js +120 -0
- package/dist/src/hooks/tool-output-truncator.d.ts +25 -0
- package/dist/src/hooks/tool-output-truncator.js +57 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +308 -0
- package/dist/src/shared/deep-merge.d.ts +8 -0
- package/dist/src/shared/deep-merge.js +25 -0
- package/dist/src/shared/hash.d.ts +1 -0
- package/dist/src/shared/hash.js +4 -0
- package/dist/src/shared/log.d.ts +7 -0
- package/dist/src/shared/log.js +24 -0
- package/dist/src/shared/metrics.d.ts +66 -0
- package/dist/src/shared/metrics.js +112 -0
- package/dist/src/shared/model-tuning.d.ts +9 -0
- package/dist/src/shared/model-tuning.js +28 -0
- package/dist/src/shared/paths.d.ts +19 -0
- package/dist/src/shared/paths.js +64 -0
- package/dist/src/shared/text.d.ts +4 -0
- package/dist/src/shared/text.js +19 -0
- package/dist/src/shared/time.d.ts +1 -0
- package/dist/src/shared/time.js +3 -0
- package/dist/src/state/adapters/index.d.ts +41 -0
- package/dist/src/state/adapters/index.js +115 -0
- package/dist/src/state/db.d.ts +16 -0
- package/dist/src/state/db.js +225 -0
- package/dist/src/state/ids.d.ts +8 -0
- package/dist/src/state/ids.js +25 -0
- package/dist/src/state/repo-lock.d.ts +3 -0
- package/dist/src/state/repo-lock.js +29 -0
- package/dist/src/state/schema.d.ts +2 -0
- package/dist/src/state/schema.js +251 -0
- package/dist/src/state/types.d.ts +71 -0
- package/dist/src/state/types.js +1 -0
- package/dist/src/tools/artifacts.d.ts +18 -0
- package/dist/src/tools/artifacts.js +71 -0
- package/dist/src/tools/health.d.ts +8 -0
- package/dist/src/tools/health.js +119 -0
- package/dist/src/tools/index.d.ts +20 -0
- package/dist/src/tools/index.js +94 -0
- package/dist/src/tools/init.d.ts +17 -0
- package/dist/src/tools/init.js +96 -0
- package/dist/src/tools/injects.d.ts +53 -0
- package/dist/src/tools/injects.js +325 -0
- package/dist/src/tools/metrics.d.ts +7 -0
- package/dist/src/tools/metrics.js +61 -0
- package/dist/src/tools/repair.d.ts +8 -0
- package/dist/src/tools/repair.js +25 -0
- package/dist/src/tools/reset.d.ts +8 -0
- package/dist/src/tools/reset.js +92 -0
- package/dist/src/tools/run.d.ts +13 -0
- package/dist/src/tools/run.js +54 -0
- package/dist/src/tools/spec.d.ts +12 -0
- package/dist/src/tools/spec.js +44 -0
- package/dist/src/tools/stage.d.ts +23 -0
- package/dist/src/tools/stage.js +371 -0
- package/dist/src/tools/status.d.ts +8 -0
- package/dist/src/tools/status.js +125 -0
- package/dist/src/tools/story.d.ts +23 -0
- package/dist/src/tools/story.js +85 -0
- package/dist/src/tools/workflow.d.ts +13 -0
- package/dist/src/tools/workflow.js +355 -0
- package/dist/src/ui/inject.d.ts +12 -0
- package/dist/src/ui/inject.js +107 -0
- package/dist/src/ui/toasts.d.ts +13 -0
- package/dist/src/ui/toasts.js +39 -0
- package/dist/src/workflow/artifacts.d.ts +24 -0
- package/dist/src/workflow/artifacts.js +45 -0
- package/dist/src/workflow/baton.d.ts +72 -0
- package/dist/src/workflow/baton.js +166 -0
- package/dist/src/workflow/context.d.ts +20 -0
- package/dist/src/workflow/context.js +113 -0
- package/dist/src/workflow/directives.d.ts +39 -0
- package/dist/src/workflow/directives.js +137 -0
- package/dist/src/workflow/repair.d.ts +8 -0
- package/dist/src/workflow/repair.js +99 -0
- package/dist/src/workflow/state-machine.d.ts +86 -0
- package/dist/src/workflow/state-machine.js +216 -0
- package/dist/src/workflow/story-helpers.d.ts +9 -0
- package/dist/src/workflow/story-helpers.js +13 -0
- package/dist/state/db.d.ts +1 -0
- package/dist/state/db.js +9 -0
- package/dist/state/repo-lock.d.ts +3 -0
- package/dist/state/repo-lock.js +29 -0
- package/dist/test/integration/db-transactions.test.d.ts +1 -0
- package/dist/test/integration/db-transactions.test.js +126 -0
- package/dist/test/integration/injection-metrics.test.d.ts +1 -0
- package/dist/test/integration/injection-metrics.test.js +129 -0
- package/dist/tools/health.d.ts +8 -0
- package/dist/tools/health.js +119 -0
- package/dist/tools/index.js +9 -0
- package/dist/tools/metrics.d.ts +7 -0
- package/dist/tools/metrics.js +61 -0
- package/dist/tools/reset.d.ts +8 -0
- package/dist/tools/reset.js +92 -0
- package/dist/tools/workflow.js +178 -168
- package/dist/ui/inject.js +21 -9
- package/package.json +6 -3
- package/src/index.ts +8 -0
- package/src/shared/metrics.ts +148 -0
- package/src/state/db.ts +10 -1
- package/src/state/repo-lock.ts +158 -0
- package/src/tools/health.ts +128 -0
- package/src/tools/index.ts +12 -3
- package/src/tools/init.ts +26 -14
- package/src/tools/metrics.ts +71 -0
- package/src/tools/repair.ts +21 -8
- package/src/tools/reset.ts +100 -0
- package/src/tools/stage.ts +12 -0
- package/src/tools/status.ts +17 -3
- package/src/tools/story.ts +41 -15
- package/src/tools/workflow.ts +15 -1
- package/src/ui/inject.ts +21 -9
package/src/tools/stage.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { getAstroPaths, ensureAstroDirs, toPosix } from "../shared/paths";
|
|
|
13
13
|
import { failRun, getActiveRun, getStageRuns, startStage, completeRun } from "../workflow/state-machine";
|
|
14
14
|
import { newEventId, newId } from "../state/ids";
|
|
15
15
|
import { insertStory } from "../workflow/story-helpers";
|
|
16
|
+
import { withRepoLock } from "../state/repo-lock";
|
|
16
17
|
|
|
17
18
|
function nextStageKey(pipeline: StageKey[], current: StageKey): StageKey | null {
|
|
18
19
|
const i = pipeline.indexOf(current);
|
|
@@ -128,6 +129,15 @@ export function createAstroStageCompleteTool(opts: { ctx: any; config: Astrocode
|
|
|
128
129
|
},
|
|
129
130
|
execute: async ({ run_id, stage_key, output_text, allow_new_stories, relation_reason }) => {
|
|
130
131
|
const repoRoot = ctx.directory as string;
|
|
132
|
+
const lockPath = path.join(repoRoot, ".astro", "astro.lock");
|
|
133
|
+
const sessionId = (ctx as any).sessionID as string | undefined;
|
|
134
|
+
|
|
135
|
+
return withRepoLock({
|
|
136
|
+
lockPath,
|
|
137
|
+
repoRoot,
|
|
138
|
+
sessionId,
|
|
139
|
+
owner: "astro_stage_complete",
|
|
140
|
+
fn: async () => {
|
|
131
141
|
const paths = getAstroPaths(repoRoot, config.db.path);
|
|
132
142
|
ensureAstroDirs(paths);
|
|
133
143
|
|
|
@@ -391,6 +401,8 @@ Ensure JSON has required fields (stage_key, status) and valid syntax.`;
|
|
|
391
401
|
lines.push(context);
|
|
392
402
|
|
|
393
403
|
return lines.join("\n").trim();
|
|
404
|
+
},
|
|
405
|
+
});
|
|
394
406
|
},
|
|
395
407
|
});
|
|
396
408
|
}
|
package/src/tools/status.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import type { AstrocodeConfig } from "../config/schema";
|
|
3
4
|
import type { SqliteDb } from "../state/db";
|
|
4
5
|
import { decideNextAction, getActiveRun, getStageRuns, getStory } from "../workflow/state-machine";
|
|
6
|
+
import { withRepoLock } from "../state/repo-lock";
|
|
5
7
|
|
|
6
8
|
function statusIcon(status: string): string {
|
|
7
9
|
switch (status) {
|
|
@@ -36,7 +38,7 @@ function stageIcon(status: string): string {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export function createAstroStatusTool(opts: { ctx: any; config: AstrocodeConfig; db?: SqliteDb | null }): ToolDefinition {
|
|
39
|
-
const { config, db } = opts;
|
|
41
|
+
const { ctx, config, db } = opts;
|
|
40
42
|
|
|
41
43
|
return tool({
|
|
42
44
|
description: "Show a compact Astrocode status dashboard: active run/stage, pipeline, story board counts, and next action.",
|
|
@@ -55,7 +57,17 @@ export function createAstroStatusTool(opts: { ctx: any; config: AstrocodeConfig;
|
|
|
55
57
|
].join("\n");
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
const repoRoot = ctx.directory as string;
|
|
61
|
+
const lockPath = path.join(repoRoot, ".astro", "astro.lock");
|
|
62
|
+
const sessionId = (ctx as any).sessionID as string | undefined;
|
|
63
|
+
|
|
64
|
+
return withRepoLock({
|
|
65
|
+
lockPath,
|
|
66
|
+
repoRoot,
|
|
67
|
+
sessionId,
|
|
68
|
+
owner: "astro_status",
|
|
69
|
+
fn: async () => {
|
|
70
|
+
try {
|
|
59
71
|
const active = getActiveRun(db);
|
|
60
72
|
|
|
61
73
|
const lines: string[] = [];
|
|
@@ -129,7 +141,9 @@ export function createAstroStatusTool(opts: { ctx: any; config: AstrocodeConfig;
|
|
|
129
141
|
`⛔ Database error.`,
|
|
130
142
|
`Error: ${msg}`,
|
|
131
143
|
].join("\n");
|
|
132
|
-
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
});
|
|
133
147
|
},
|
|
134
148
|
});
|
|
135
149
|
}
|
package/src/tools/story.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import type { AstrocodeConfig } from "../config/schema";
|
|
3
4
|
import type { SqliteDb } from "../state/db";
|
|
4
5
|
import { withTx } from "../state/db";
|
|
@@ -6,10 +7,11 @@ import { nowISO } from "../shared/time";
|
|
|
6
7
|
import type { StoryState } from "../state/types";
|
|
7
8
|
|
|
8
9
|
import { insertStory } from "../workflow/story-helpers";
|
|
10
|
+
import { withRepoLock } from "../state/repo-lock";
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
export function createAstroStoryQueueTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
|
|
12
|
-
const { db } = opts;
|
|
14
|
+
const { ctx, db } = opts;
|
|
13
15
|
|
|
14
16
|
return tool({
|
|
15
17
|
description: "Create a queued story (ticket) in Astrocode. Returns story_key.",
|
|
@@ -20,18 +22,30 @@ export function createAstroStoryQueueTool(opts: { ctx: any; config: AstrocodeCon
|
|
|
20
22
|
priority: tool.schema.number().int().default(0),
|
|
21
23
|
},
|
|
22
24
|
execute: async ({ title, body_md, epic_key, priority }) => {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const repoRoot = ctx.directory as string;
|
|
26
|
+
const lockPath = path.join(repoRoot, ".astro", "astro.lock");
|
|
27
|
+
const sessionId = (ctx as any).sessionID as string | undefined;
|
|
28
|
+
|
|
29
|
+
return withRepoLock({
|
|
30
|
+
lockPath,
|
|
31
|
+
repoRoot,
|
|
32
|
+
sessionId,
|
|
33
|
+
owner: "astro_story_queue",
|
|
34
|
+
fn: async () => {
|
|
35
|
+
const story_key = withTx(db, () => {
|
|
36
|
+
const key = insertStory(db, { title, body_md, epic_key: epic_key ?? null, priority: priority ?? 0, state: 'queued' });
|
|
37
|
+
return key;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return `✅ Queued story ${story_key}: ${title}`;
|
|
41
|
+
},
|
|
26
42
|
});
|
|
27
|
-
|
|
28
|
-
return `✅ Queued story ${story_key}: ${title}`;
|
|
29
43
|
},
|
|
30
44
|
});
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
export function createAstroStoryApproveTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
|
|
34
|
-
const { db } = opts;
|
|
48
|
+
const { ctx, db } = opts;
|
|
35
49
|
|
|
36
50
|
return tool({
|
|
37
51
|
description: "Approve a story so it becomes eligible to run.",
|
|
@@ -39,14 +53,26 @@ export function createAstroStoryApproveTool(opts: { ctx: any; config: AstrocodeC
|
|
|
39
53
|
story_key: tool.schema.string().min(1),
|
|
40
54
|
},
|
|
41
55
|
execute: async ({ story_key }) => {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
const repoRoot = ctx.directory as string;
|
|
57
|
+
const lockPath = path.join(repoRoot, ".astro", "astro.lock");
|
|
58
|
+
const sessionId = (ctx as any).sessionID as string | undefined;
|
|
59
|
+
|
|
60
|
+
return withRepoLock({
|
|
61
|
+
lockPath,
|
|
62
|
+
repoRoot,
|
|
63
|
+
sessionId,
|
|
64
|
+
owner: "astro_story_approve",
|
|
65
|
+
fn: async () => {
|
|
66
|
+
const now = nowISO();
|
|
67
|
+
const row = db.prepare("SELECT story_key, state, title FROM stories WHERE story_key=?").get(story_key) as any;
|
|
68
|
+
if (!row) throw new Error(`Story not found: ${story_key}`);
|
|
69
|
+
|
|
70
|
+
if (row.state === "approved") return `ℹ️ Story ${story_key} already approved.`;
|
|
71
|
+
|
|
72
|
+
db.prepare("UPDATE stories SET state='approved', approved_at=?, updated_at=? WHERE story_key=?").run(now, now, story_key);
|
|
73
|
+
return `✅ Approved story ${story_key}: ${row.title}`;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
50
76
|
},
|
|
51
77
|
});
|
|
52
78
|
}
|
package/src/tools/workflow.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/tools/workflow.ts
|
|
2
2
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
3
|
+
import path from "node:path";
|
|
3
4
|
import type { AstrocodeConfig } from "../config/schema";
|
|
4
5
|
import type { SqliteDb } from "../state/db";
|
|
5
6
|
import { withTx } from "../state/db";
|
|
@@ -19,9 +20,11 @@ import { buildStageDirective, directiveHash } from "../workflow/directives";
|
|
|
19
20
|
import { injectChatPrompt } from "../ui/inject";
|
|
20
21
|
import { nowISO } from "../shared/time";
|
|
21
22
|
import { newEventId } from "../state/ids";
|
|
23
|
+
import { withRepoLock } from "../state/repo-lock";
|
|
22
24
|
import { debug } from "../shared/log";
|
|
23
25
|
import { createToastManager } from "../ui/toasts";
|
|
24
26
|
import type { AgentConfig } from "@opencode-ai/sdk";
|
|
27
|
+
import { acquireRepoLock } from "../state/repo-lock";
|
|
25
28
|
|
|
26
29
|
// Agent name mapping for case-sensitive resolution
|
|
27
30
|
export const STAGE_TO_AGENT_MAP: Record<string, string> = {
|
|
@@ -184,8 +187,17 @@ export function createAstroWorkflowProceedTool(opts: { ctx: any; config: Astroco
|
|
|
184
187
|
max_steps: tool.schema.number().int().positive().default(config.workflow.default_max_steps),
|
|
185
188
|
},
|
|
186
189
|
execute: async ({ mode, max_steps }) => {
|
|
190
|
+
const repoRoot = (ctx as any).directory as string;
|
|
191
|
+
const lockPath = path.join(repoRoot, ".astro", "astro.lock");
|
|
187
192
|
const sessionId = (ctx as any).sessionID as string | undefined;
|
|
188
|
-
|
|
193
|
+
|
|
194
|
+
return withRepoLock({
|
|
195
|
+
lockPath,
|
|
196
|
+
repoRoot,
|
|
197
|
+
sessionId,
|
|
198
|
+
owner: "astro_workflow_proceed",
|
|
199
|
+
fn: async () => {
|
|
200
|
+
const steps = Math.min(max_steps, config.workflow.loop_max_steps_hard_cap);
|
|
189
201
|
|
|
190
202
|
const actions: string[] = [];
|
|
191
203
|
const warnings: string[] = [];
|
|
@@ -408,6 +420,8 @@ export function createAstroWorkflowProceedTool(opts: { ctx: any; config: Astroco
|
|
|
408
420
|
}
|
|
409
421
|
|
|
410
422
|
return lines.join("\n").trim();
|
|
423
|
+
},
|
|
424
|
+
});
|
|
411
425
|
},
|
|
412
426
|
});
|
|
413
427
|
}
|
package/src/ui/inject.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/ui/inject.ts
|
|
2
|
+
import { recordInjection, recordError } from "../shared/metrics";
|
|
2
3
|
type InjectionItem = {
|
|
3
4
|
ctx: any;
|
|
4
5
|
sessionId: string;
|
|
@@ -33,16 +34,26 @@ async function tryInjectOnce(item: InjectionItem): Promise<void> {
|
|
|
33
34
|
const { ctx, sessionId, text, agent = "Astro" } = item;
|
|
34
35
|
const prefixedText = `[${agent}]\n\n${text}`;
|
|
35
36
|
|
|
36
|
-
const {
|
|
37
|
+
const injectionRecorder = recordInjection({ sessionId, attempts: item.attempts + 1, agent });
|
|
38
|
+
const injectionStart = injectionRecorder.start();
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
try {
|
|
41
|
+
const { session, prompt } = getPromptInvoker(ctx);
|
|
42
|
+
|
|
43
|
+
// IMPORTANT: force correct `this` binding
|
|
44
|
+
await prompt.call(session, {
|
|
45
|
+
path: { id: sessionId },
|
|
46
|
+
body: {
|
|
47
|
+
parts: [{ type: "text", text: prefixedText }],
|
|
48
|
+
agent,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
injectionRecorder.end(injectionStart, true);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
injectionRecorder.end(injectionStart, false);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
async function runSessionQueue(sessionId: string) {
|
|
@@ -69,6 +80,7 @@ async function runSessionQueue(sessionId: string) {
|
|
|
69
80
|
console.warn(
|
|
70
81
|
`[Astrocode] Injection failed permanently after ${item.attempts} attempts: ${msg}`
|
|
71
82
|
);
|
|
83
|
+
recordError("injection_failure", `Injection failed after ${item.attempts} attempts: ${msg}`);
|
|
72
84
|
item.reject(err);
|
|
73
85
|
continue;
|
|
74
86
|
}
|