acpus 0.5.2 → 0.6.0-alpha.0

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 (140) hide show
  1. package/README.md +19 -5
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/agent-progress-format.d.ts +7 -0
  4. package/dist/agent-progress-format.d.ts.map +1 -0
  5. package/dist/agent-progress-format.js +102 -0
  6. package/dist/agent-progress-format.js.map +1 -0
  7. package/dist/authoring/core.d.ts +2 -0
  8. package/dist/authoring/core.d.ts.map +1 -0
  9. package/dist/authoring/core.js +2 -0
  10. package/dist/authoring/core.js.map +1 -0
  11. package/dist/authoring/expression.d.ts +2 -0
  12. package/dist/authoring/expression.d.ts.map +1 -0
  13. package/dist/authoring/expression.js +2 -0
  14. package/dist/authoring/expression.js.map +1 -0
  15. package/dist/authoring/tasks/git.d.ts +2 -0
  16. package/dist/authoring/tasks/git.d.ts.map +1 -0
  17. package/dist/authoring/tasks/git.js +2 -0
  18. package/dist/authoring/tasks/git.js.map +1 -0
  19. package/dist/catalog.d.ts +18 -24
  20. package/dist/catalog.d.ts.map +1 -1
  21. package/dist/catalog.js +164 -174
  22. package/dist/catalog.js.map +1 -1
  23. package/dist/cli.d.ts +3 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +14 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/commands/daemon.d.ts +14 -0
  28. package/dist/commands/daemon.d.ts.map +1 -0
  29. package/dist/commands/daemon.js +89 -0
  30. package/dist/commands/daemon.js.map +1 -0
  31. package/dist/commands/doctor.d.ts +11 -0
  32. package/dist/commands/doctor.d.ts.map +1 -0
  33. package/dist/commands/doctor.js +26 -0
  34. package/dist/commands/doctor.js.map +1 -0
  35. package/dist/commands/hooks.d.ts +11 -0
  36. package/dist/commands/hooks.d.ts.map +1 -0
  37. package/dist/commands/hooks.js +64 -0
  38. package/dist/commands/hooks.js.map +1 -0
  39. package/dist/commands/json.d.ts +7 -0
  40. package/dist/commands/json.d.ts.map +1 -0
  41. package/dist/commands/json.js +43 -0
  42. package/dist/commands/json.js.map +1 -0
  43. package/dist/commands/runs-picker.d.ts +21 -0
  44. package/dist/commands/runs-picker.d.ts.map +1 -0
  45. package/dist/commands/runs-picker.js +76 -0
  46. package/dist/commands/runs-picker.js.map +1 -0
  47. package/dist/commands/runs.d.ts +12 -0
  48. package/dist/commands/runs.d.ts.map +1 -0
  49. package/dist/commands/runs.js +285 -0
  50. package/dist/commands/runs.js.map +1 -0
  51. package/dist/commands/skill.d.ts +11 -0
  52. package/dist/commands/skill.d.ts.map +1 -0
  53. package/dist/commands/skill.js +221 -0
  54. package/dist/commands/skill.js.map +1 -0
  55. package/dist/commands/web.d.ts +24 -0
  56. package/dist/commands/web.d.ts.map +1 -0
  57. package/dist/commands/web.js +57 -0
  58. package/dist/commands/web.js.map +1 -0
  59. package/dist/commands/workflow.d.ts +11 -0
  60. package/dist/commands/workflow.d.ts.map +1 -0
  61. package/dist/commands/workflow.js +324 -0
  62. package/dist/commands/workflow.js.map +1 -0
  63. package/dist/daemon-entry.d.ts +3 -0
  64. package/dist/daemon-entry.d.ts.map +1 -0
  65. package/dist/daemon-entry.js +26 -0
  66. package/dist/daemon-entry.js.map +1 -0
  67. package/dist/errors.d.ts +19 -0
  68. package/dist/errors.d.ts.map +1 -0
  69. package/dist/errors.js +28 -0
  70. package/dist/errors.js.map +1 -0
  71. package/dist/output.d.ts +91 -11
  72. package/dist/output.d.ts.map +1 -1
  73. package/dist/output.js +203 -44
  74. package/dist/output.js.map +1 -1
  75. package/dist/program.d.ts +9 -0
  76. package/dist/program.d.ts.map +1 -0
  77. package/dist/program.js +85 -0
  78. package/dist/program.js.map +1 -0
  79. package/dist/run-status-surface.d.ts +12 -0
  80. package/dist/run-status-surface.d.ts.map +1 -0
  81. package/dist/run-status-surface.js +324 -0
  82. package/dist/run-status-surface.js.map +1 -0
  83. package/dist/workflow-preparation.d.ts +3 -0
  84. package/dist/workflow-preparation.d.ts.map +1 -0
  85. package/dist/workflow-preparation.js +34 -0
  86. package/dist/workflow-preparation.js.map +1 -0
  87. package/package.json +35 -35
  88. package/skills/acpus/SKILL.md +120 -0
  89. package/skills/acpus/eval/evaluations.md +47 -0
  90. package/skills/acpus/examples/hooks.example.json +17 -0
  91. package/skills/acpus/examples/workflows/adversarial-review/workflow.ts +248 -0
  92. package/skills/acpus/examples/workflows/change-approval/workflow.ts +121 -0
  93. package/skills/acpus/examples/workflows/issue-triage/tasks.ts +14 -0
  94. package/skills/acpus/examples/workflows/issue-triage/workflow.ts +146 -0
  95. package/skills/acpus/examples/workflows/multi-aspect-brainstorm/workflow.ts +304 -0
  96. package/skills/acpus/examples/workflows/worktree-tournament/workflow.ts +194 -0
  97. package/skills/acpus/references/authoring.md +205 -0
  98. package/skills/acpus/references/cli-operations.md +94 -0
  99. package/skills/acpus/references/hooks-json.md +81 -0
  100. package/skills/acpus/references/runtime-recovery.md +97 -0
  101. package/dist/agent-activity.d.ts +0 -3
  102. package/dist/agent-activity.d.ts.map +0 -1
  103. package/dist/agent-activity.js +0 -50
  104. package/dist/agent-activity.js.map +0 -1
  105. package/dist/agent-overrides.d.ts +0 -3
  106. package/dist/agent-overrides.d.ts.map +0 -1
  107. package/dist/agent-overrides.js +0 -39
  108. package/dist/agent-overrides.js.map +0 -1
  109. package/dist/follow.d.ts +0 -23
  110. package/dist/follow.d.ts.map +0 -1
  111. package/dist/follow.js +0 -164
  112. package/dist/follow.js.map +0 -1
  113. package/dist/hooks.d.ts +0 -4
  114. package/dist/hooks.d.ts.map +0 -1
  115. package/dist/hooks.js +0 -159
  116. package/dist/hooks.js.map +0 -1
  117. package/dist/index.d.ts +0 -3
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/index.js +0 -592
  120. package/dist/index.js.map +0 -1
  121. package/dist/io.d.ts +0 -3
  122. package/dist/io.d.ts.map +0 -1
  123. package/dist/io.js +0 -27
  124. package/dist/io.js.map +0 -1
  125. package/dist/observations.d.ts +0 -39
  126. package/dist/observations.d.ts.map +0 -1
  127. package/dist/observations.js +0 -83
  128. package/dist/observations.js.map +0 -1
  129. package/dist/runs-show.d.ts +0 -40
  130. package/dist/runs-show.d.ts.map +0 -1
  131. package/dist/runs-show.js +0 -296
  132. package/dist/runs-show.js.map +0 -1
  133. package/dist/supervisor-client.d.ts +0 -9
  134. package/dist/supervisor-client.d.ts.map +0 -1
  135. package/dist/supervisor-client.js +0 -8
  136. package/dist/supervisor-client.js.map +0 -1
  137. package/dist/supervisor.d.ts +0 -18
  138. package/dist/supervisor.d.ts.map +0 -1
  139. package/dist/supervisor.js +0 -24
  140. package/dist/supervisor.js.map +0 -1
@@ -0,0 +1,47 @@
1
+ # Evaluation Scenarios
2
+
3
+ Use these scenarios to test that the Skill guides agents toward current Acpus behavior.
4
+
5
+ ## Evaluation 1: Author a minimal workflow
6
+
7
+ **Query**: "Write an Acpus workflow that takes `{ name }`, runs a local task, and returns a greeting."
8
+
9
+ **Expected behavior**:
10
+
11
+ - Uses `defineWorkflow` from `acpus/core`.
12
+ - Uses a TypeScript `workflow.ts`.
13
+ - Uses `step("...").task({ run: { input, exec } })`.
14
+ - Does not add `outputSchema` to the task.
15
+ - Suggests `acpus workflow check workflow.ts` before `run`.
16
+
17
+ ## Evaluation 2: Recover a failed run after source fix
18
+
19
+ **Query**: "My Acpus run failed, I fixed workflow.ts, should I retry it?"
20
+
21
+ **Expected behavior**:
22
+
23
+ - Asks to inspect the run first or uses `acpus runs inspect <run-id>` if run id is given.
24
+ - Explains retry reuses the original frozen workflow.
25
+ - Recommends `acpus runs fork <run-id> --workflow workflow.ts` when source changed.
26
+
27
+ ## Evaluation 3: Configure hooks
28
+
29
+ **Query**: "Add an Acpus hook when an agent node fails."
30
+
31
+ **Expected behavior**:
32
+
33
+ - Uses `.acpus/hooks.json`.
34
+ - Produces an event-map JSON file with `node.failed` top-level key.
35
+ - Uses `match.kind` or `match.nodeId` as regex match fields.
36
+ - Mentions hook context arrives on stdin.
37
+ - Recommends `acpus hooks validate`.
38
+
39
+ ## Evaluation 4: Fix expression bug
40
+
41
+ **Query**: "Why does `if (input.ready)` fail in my workflow build callback?"
42
+
43
+ **Expected behavior**:
44
+
45
+ - Explains `input.ready` is an expression token, not runtime boolean.
46
+ - Replaces JavaScript control flow with graph-level `step().if` or expression helpers such as `and`, `eq`, `ifElse`.
47
+ - Mentions `workflow check` catches Expr truthiness misuse.
@@ -0,0 +1,17 @@
1
+ {
2
+ "run.completed": [
3
+ {
4
+ "id": "record-completion",
5
+ "command": "mkdir -p .acpus/.local/hook-samples && cat > .acpus/.local/hook-samples/last-run-completed.json",
6
+ "timeout": "10s"
7
+ }
8
+ ],
9
+ "node.failed": [
10
+ {
11
+ "id": "record-failed-agent",
12
+ "match": { "kind": "agent" },
13
+ "command": "mkdir -p .acpus/.local/hook-samples && cat > .acpus/.local/hook-samples/last-agent-failure.json",
14
+ "timeout": "10s"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,248 @@
1
+ import { defineWorkflow, z } from "acpus/core";
2
+ import { md, template } from "acpus/expression";
3
+
4
+ const Verdict = z.enum(["pass", "pass-with-nits", "needs-work", "block"]);
5
+
6
+ export default defineWorkflow({
7
+ name: "adversarial-review",
8
+ inputSchema: z.object({
9
+ subject: z.string().describe(
10
+ "The proposal, design, code change, workflow, plan, or decision to review adversarially.",
11
+ ),
12
+ rubric: z.string().describe(
13
+ "The review standard: what good looks like, what must be true, and which qualities matter most.",
14
+ ),
15
+ criteria: z.string().default("").describe(
16
+ "Optional comma-separated or natural-language review criteria, for example: correctness, DX, runtime safety.",
17
+ ),
18
+ context: z.string().default("").describe(
19
+ "Optional background the agents should consider, such as constraints, prior decisions, known risks, or local workspace context.",
20
+ ),
21
+ maxLenses: z.number().default(4).describe(
22
+ "Maximum number of distinct adversarial review lenses to create.",
23
+ ),
24
+ }),
25
+ agents: {
26
+ planner: {
27
+ use: "pi",
28
+ },
29
+ reviewer: {
30
+ use: "pi",
31
+ },
32
+ critic: {
33
+ use: "claude",
34
+ },
35
+ synthesizer: {
36
+ use: "claude",
37
+ },
38
+ },
39
+ }).build(({ input, agents, meta, step }) => {
40
+ const plan = step("plan_lenses").agent({
41
+ outputSchema: z.object({
42
+ lenses: z.array(z.object({
43
+ id: z.string(),
44
+ prompt: z.string(),
45
+ })),
46
+ }),
47
+ run: {
48
+ agent: agents.planner,
49
+ cwd: meta.workspaceDir,
50
+ prompt: md`
51
+ You are planning a dynamic adversarial review.
52
+
53
+ Subject:
54
+ ${input.subject}
55
+
56
+ Context:
57
+ ${input.context}
58
+
59
+ Rubric:
60
+ ${input.rubric}
61
+
62
+ Criteria:
63
+ ${input.criteria}
64
+
65
+ Maximum lenses:
66
+ ${input.maxLenses}
67
+
68
+ Create the smallest useful set of review lenses.
69
+
70
+ Rules:
71
+ - Use only the subject, context, rubric, criteria, and readable local workspace context.
72
+ - Prefer 3-5 lenses, but never exceed the maximum.
73
+ - Avoid duplicate lenses.
74
+ - Each lens must represent a distinct way the subject could fail.
75
+ - Each lens id must be short, lowercase, and stable enough for a run key.
76
+ - Each lens prompt must be a complete natural-language reviewer role, including focus and attack style.
77
+ - Good lenses include correctness, feasibility, risk, edge cases, rubric compliance, maintainability, authoring fit, runtime contract, testing, migration risk, and DX.`,
78
+ },
79
+ timeout: "15m",
80
+ });
81
+
82
+ const reviews = step("blind_reviews").fanout({
83
+ over: plan.output.lenses,
84
+ maxConcurrency: 4,
85
+ key: ({ item }) => template`review-${item.id}`,
86
+ do: ({ item, step }) => {
87
+ const review = step("blind_review").agent({
88
+ run: {
89
+ agent: agents.reviewer,
90
+ cwd: meta.workspaceDir,
91
+ prompt: md`
92
+ You are a blind reviewer.
93
+
94
+ Your lens:
95
+ ${item.prompt}
96
+
97
+ Subject:
98
+ ${input.subject}
99
+
100
+ Context:
101
+ ${input.context}
102
+
103
+ Rubric:
104
+ ${input.rubric}
105
+
106
+ Criteria:
107
+ ${input.criteria}
108
+
109
+ Rules:
110
+ - Work independently.
111
+ - Do not assume what other reviewers will say.
112
+ - Judge only from the given subject, context, rubric, criteria, and readable local workspace context.
113
+ - Be adversarial but fair.
114
+ - Prefer concrete issues over broad complaints.
115
+ - Mark uncertainty explicitly.
116
+ - Do not block on nits.
117
+ - Do not try to match a JSON schema.
118
+
119
+ Return Markdown with these sections:
120
+ - Summary
121
+ - Strengths
122
+ - Issues
123
+ - Hidden assumptions
124
+ - Verdict`,
125
+ },
126
+ timeout: "30m",
127
+ });
128
+
129
+ return {
130
+ review: review.output,
131
+ };
132
+ },
133
+ });
134
+
135
+ const critiques = step("cross_critiques").fanout({
136
+ over: plan.output.lenses,
137
+ maxConcurrency: 4,
138
+ key: ({ item }) => template`critique-${item.id}`,
139
+ do: ({ item, step }) => {
140
+ const critique = step("critique_reviews").agent({
141
+ run: {
142
+ agent: agents.critic,
143
+ cwd: meta.workspaceDir,
144
+ prompt: md`
145
+ You are now a red-team critic.
146
+
147
+ Your lens:
148
+ ${item.prompt}
149
+
150
+ Subject:
151
+ ${input.subject}
152
+
153
+ Rubric:
154
+ ${input.rubric}
155
+
156
+ Criteria:
157
+ ${input.criteria}
158
+
159
+ Blind reviews:
160
+ ${reviews.output}
161
+
162
+ Attack the reviews, not the author.
163
+
164
+ Look for:
165
+ - Unsupported claims.
166
+ - Missed risks.
167
+ - Overconfidence.
168
+ - Contradictions.
169
+ - Rubric gaps.
170
+ - Recommendations that do not follow from the issue.
171
+ - Issues that are mislabeled as blocking or under-labeled as minor.
172
+
173
+ Rules:
174
+ - Do not merely summarize.
175
+ - Concede points that are already strong.
176
+ - Return only substantial objections.
177
+ - Do not try to match a JSON schema.
178
+
179
+ Return Markdown with these sections:
180
+ - Objections
181
+ - Contradictions
182
+ - Concessions
183
+ - Stronger alternatives`,
184
+ },
185
+ timeout: "30m",
186
+ });
187
+
188
+ return {
189
+ critique: critique.output,
190
+ };
191
+ },
192
+ });
193
+
194
+ const synthesis = step("synthesize_result").agent({
195
+ outputSchema: z.object({
196
+ verdict: Verdict,
197
+ report: z.string(),
198
+ }),
199
+ run: {
200
+ agent: agents.synthesizer,
201
+ cwd: meta.workspaceDir,
202
+ prompt: md`
203
+ You are the judge for a dynamic adversarial review.
204
+
205
+ Subject:
206
+ ${input.subject}
207
+
208
+ Context:
209
+ ${input.context}
210
+
211
+ Rubric:
212
+ ${input.rubric}
213
+
214
+ Criteria:
215
+ ${input.criteria}
216
+
217
+ Lens plan:
218
+ ${plan.output}
219
+
220
+ Blind reviews:
221
+ ${reviews.output}
222
+
223
+ Cross critiques:
224
+ ${critiques.output}
225
+
226
+ Rules:
227
+ - Do not average opinions mechanically.
228
+ - Weigh concrete basis over reviewer confidence.
229
+ - In the report, separate consensus from unresolved disagreement.
230
+ - Blocking issues must be explicit.
231
+ - Nits must not block approval.
232
+ - Required actions must be concrete and actionable.
233
+ - Nice-to-have items must not be mixed into required actions.
234
+ - If the context is insufficient, say so directly instead of inventing evidence.
235
+ - Do not search the internet.
236
+ - The report should be natural-language Markdown containing: assessment, consensus, blocking issues, non-blocking issues, unresolved disagreements, required actions, and nice-to-have items.
237
+ - Return JSON matching the schema.`,
238
+ },
239
+ timeout: "30m",
240
+ });
241
+
242
+ return {
243
+ plan: plan.output,
244
+ reviews: reviews.output,
245
+ critiques: critiques.output,
246
+ synthesis: synthesis.output,
247
+ };
248
+ });
@@ -0,0 +1,121 @@
1
+ import { defineWorkflow, z } from "acpus/core";
2
+ import { md, template } from "acpus/expression";
3
+
4
+ const PlanOut = z.object({
5
+ ready: z.boolean(),
6
+ summary: z.string(),
7
+ nextDraft: z.string(),
8
+ });
9
+
10
+ export default defineWorkflow({
11
+ name: "change-approval",
12
+ description: "Draft and refine an implementation plan, then optionally wait for human approval.",
13
+ inputSchema: z.object({
14
+ repoPath: z.path().describe("Repository path where the planning agent should inspect context."),
15
+ request: z.string().describe("The implementation request or change proposal to turn into a plan."),
16
+ requireApproval: z.boolean().default(true).describe(
17
+ "Whether to pause for a human approval signal before the workflow can complete.",
18
+ ),
19
+ }),
20
+ agents: {
21
+ planner: { use: "codex" },
22
+ },
23
+ }).build(({ input, agents, meta, step }) => {
24
+ const initial = step("draft_plan").agent({
25
+ outputSchema: PlanOut,
26
+ run: {
27
+ agent: agents.planner,
28
+ cwd: input.repoPath,
29
+ prompt: template`Draft an implementation plan for: ${input.request}`,
30
+ },
31
+ retry: { max: 1 },
32
+ timeout: "20m",
33
+ });
34
+
35
+ const refined = step("refine_plan").loop({
36
+ initial: {
37
+ ready: initial.output.ready,
38
+ round: 0,
39
+ summary: initial.output.summary,
40
+ draft: initial.output.nextDraft,
41
+ },
42
+ maxIterations: 2,
43
+ do: ({ iter, previous, step }) => {
44
+ const review = step("refine_round").agent({
45
+ outputSchema: PlanOut,
46
+ run: {
47
+ agent: agents.planner,
48
+ cwd: input.repoPath,
49
+ prompt: md`
50
+ Refine implementation plan round ${iter}.
51
+
52
+ Original request: ${input.request}
53
+ Previous draft: ${previous.draft}
54
+ Previous summary: ${previous.summary}
55
+
56
+ Return a ready flag, concise summary, and next draft.
57
+ `,
58
+ },
59
+ retry: { max: 1 },
60
+ timeout: "20m",
61
+ });
62
+ return {
63
+ ready: review.output.ready,
64
+ round: iter,
65
+ summary: review.output.summary,
66
+ draft: review.output.nextDraft,
67
+ };
68
+ },
69
+ stopWhen: ({ result }) => result.ready,
70
+ onExhausted: "returnLast",
71
+ });
72
+
73
+ const approval = step("approval").if({
74
+ condition: input.requireApproval,
75
+ then: ({ step }) => {
76
+ const human = step("human_approval").signal({
77
+ outputSchema: z.object({
78
+ approved: z.boolean(),
79
+ notes: z.string().default(""),
80
+ }),
81
+ run: {
82
+ prompt: md`
83
+ Approve the implementation plan for run ${meta.runId}.
84
+
85
+ Request: ${input.request}
86
+ Plan: ${refined.output.draft}
87
+ `,
88
+ },
89
+ timeout: "24h",
90
+ onTimeout: { action: "fail", message: "approval timed out" },
91
+ });
92
+ return { approved: human.output.approved, notes: human.output.notes };
93
+ },
94
+ else: ({ step }) => {
95
+ const automatic = step("auto_approval").task({
96
+ run: {
97
+ input: { ready: refined.output.ready },
98
+ exec: async ({ input }) => ({
99
+ approved: input.ready,
100
+ notes: input.ready ? "auto-approved ready plan" : "plan not ready",
101
+ }),
102
+ },
103
+ });
104
+ return { approved: automatic.output.approved, notes: automatic.output.notes };
105
+ },
106
+ });
107
+
108
+ step("require_approval").assert({
109
+ condition: approval.output.approved,
110
+ message: template`Change approval failed: ${approval.output.notes}`,
111
+ });
112
+
113
+ return {
114
+ runId: meta.runId,
115
+ approved: approval.output.approved,
116
+ notes: approval.output.notes,
117
+ ready: refined.output.ready,
118
+ summary: refined.output.summary,
119
+ plan: refined.output.draft,
120
+ };
121
+ });
@@ -0,0 +1,14 @@
1
+ import { task, z } from "acpus/core";
2
+
3
+ export const summarizeIssue = task.define({
4
+ // Reusable task modules may import third-party dependencies installed with the workflow package.
5
+ inputSchema: z.object({
6
+ id: z.string(),
7
+ title: z.string(),
8
+ labels: z.array(z.string()),
9
+ }),
10
+ exec: async ({ input }) => ({
11
+ labelCount: input.labels.length,
12
+ titleLine: `${input.id}: ${input.title}`,
13
+ }),
14
+ });
@@ -0,0 +1,146 @@
1
+ import { defineWorkflow, z } from "acpus/core";
2
+ import { eq, md, template } from "acpus/expression";
3
+ import { summarizeIssue } from "./tasks.js";
4
+
5
+ export default defineWorkflow({
6
+ name: "issue-triage",
7
+ description: "Triage repository issues with metadata summarization, agent review, and routing decisions.",
8
+ inputSchema: z.object({
9
+ issues: z.array(z.object({
10
+ id: z.string().describe("Stable issue identifier used for fanout keys and route messages."),
11
+ title: z.string().describe("Issue title to show the triage agent and queue actions."),
12
+ body: z.string().describe("Issue body or description to triage."),
13
+ labels: z.array(z.string()).default([]).describe("Optional issue labels available to metadata and triage steps."),
14
+ })).describe("Issues to triage in parallel."),
15
+ repoPath: z.path().describe("Repository path where the triage agent should inspect context."),
16
+ }),
17
+ agents: {
18
+ triager: { use: "codex" },
19
+ },
20
+ }).build(({ input, agents, meta, step }) => {
21
+ const triaged = step("triage_issues").fanout({
22
+ over: input.issues,
23
+ key: ({ item }) => template`issue-${item.id}`,
24
+ maxConcurrency: 3,
25
+ do: ({ item, step }) => {
26
+ const lane = step("triage_lane").parallel({
27
+ branches: {
28
+ metadata: {
29
+ do: ({ step }) => {
30
+ const metadata = step("summarize_issue").task({
31
+ run: {
32
+ task: summarizeIssue,
33
+ input: { id: item.id, title: item.title, labels: item.labels },
34
+ cwd: input.repoPath,
35
+ },
36
+ });
37
+ return {
38
+ labelCount: metadata.output.labelCount,
39
+ titleLine: metadata.output.titleLine,
40
+ };
41
+ },
42
+ },
43
+ review: {
44
+ do: ({ step }) => {
45
+ const review = step("review_issue").agent({
46
+ outputSchema: z.object({
47
+ route: z.enum(["now", "later", "escalate"]),
48
+ priority: z.number(),
49
+ summary: z.string(),
50
+ }),
51
+ run: {
52
+ agent: agents.triager,
53
+ cwd: input.repoPath,
54
+ prompt: md`
55
+ Triage this issue for the current repository.
56
+
57
+ ID: ${item.id}
58
+ Title: ${item.title}
59
+ Body: ${item.body}
60
+ Labels: ${item.labels}
61
+
62
+ Choose route "now", "later", or "escalate".
63
+ `,
64
+ },
65
+ timeout: "20m",
66
+ });
67
+ return {
68
+ route: review.output.route,
69
+ priority: review.output.priority,
70
+ summary: review.output.summary,
71
+ };
72
+ },
73
+ },
74
+ },
75
+ });
76
+
77
+ const routed = step("route_issue").switch({
78
+ cases: [
79
+ {
80
+ when: eq(lane.output.review.route, "escalate"),
81
+ then: ({ step }) => {
82
+ const escalation = step("prepare_escalation").task({
83
+ run: {
84
+ input: {
85
+ id: item.id,
86
+ priority: lane.output.review.priority,
87
+ summary: lane.output.review.summary,
88
+ },
89
+ exec: async ({ input }) => ({
90
+ owner: "maintainer",
91
+ action: `Escalate ${input.id} with priority ${input.priority}: ${input.summary}`,
92
+ }),
93
+ },
94
+ });
95
+ return {
96
+ owner: escalation.output.owner,
97
+ action: escalation.output.action,
98
+ };
99
+ },
100
+ },
101
+ {
102
+ when: eq(lane.output.review.route, "now"),
103
+ then: ({ step }) => {
104
+ const queue = step("queue_now").task({
105
+ run: {
106
+ input: { id: item.id, title: lane.output.metadata.titleLine },
107
+ exec: async ({ input }) => ({
108
+ owner: "oncall",
109
+ action: `Queue ${input.title} for this sprint`,
110
+ }),
111
+ },
112
+ });
113
+ return { owner: queue.output.owner, action: queue.output.action };
114
+ },
115
+ },
116
+ ],
117
+ default: ({ step }) => {
118
+ const backlog = step("backlog_later").task({
119
+ run: {
120
+ input: { id: item.id, labelCount: lane.output.metadata.labelCount },
121
+ exec: async ({ input }) => ({
122
+ owner: "backlog",
123
+ action: `Backlog ${input.id} with ${input.labelCount} labels`,
124
+ }),
125
+ },
126
+ });
127
+ return { owner: backlog.output.owner, action: backlog.output.action };
128
+ },
129
+ });
130
+
131
+ return {
132
+ id: item.id,
133
+ route: lane.output.review.route,
134
+ priority: lane.output.review.priority,
135
+ owner: routed.output.owner,
136
+ action: routed.output.action,
137
+ summary: lane.output.review.summary,
138
+ };
139
+ },
140
+ });
141
+
142
+ return {
143
+ runId: meta.runId,
144
+ triaged: triaged.output,
145
+ };
146
+ });