@osolmaz/pi-workflows 0.10.0 → 0.11.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.
Files changed (135) hide show
  1. package/README.md +32 -14
  2. package/dist/builtins/autodoc.workflow.d.ts +58 -0
  3. package/dist/builtins/autodoc.workflow.js +266 -0
  4. package/dist/builtins/autodoc.workflow.js.map +1 -0
  5. package/dist/builtins/autoimplement.workflow.d.ts +212 -11
  6. package/dist/builtins/autoimplement.workflow.js +431 -22
  7. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  8. package/dist/builtins/{autodevise.workflow.d.ts → autoplan.workflow.d.ts} +9 -9
  9. package/dist/builtins/{autodevise.workflow.js → autoplan.workflow.js} +20 -20
  10. package/dist/builtins/autoplan.workflow.js.map +1 -0
  11. package/dist/builtins/catalog.js +8 -4
  12. package/dist/builtins/catalog.js.map +1 -1
  13. package/dist/builtins/index.d.ts +6 -3
  14. package/dist/builtins/index.js +5 -2
  15. package/dist/builtins/index.js.map +1 -1
  16. package/dist/builtins/monitor.workflow.d.ts +4 -0
  17. package/dist/builtins/monitor.workflow.js +134 -9
  18. package/dist/builtins/monitor.workflow.js.map +1 -1
  19. package/dist/builtins/plan-approval.workflow.d.ts +83 -0
  20. package/dist/builtins/plan-approval.workflow.js +148 -0
  21. package/dist/builtins/plan-approval.workflow.js.map +1 -0
  22. package/dist/builtins/plan-presentation.d.ts +7 -0
  23. package/dist/builtins/plan-presentation.js +44 -0
  24. package/dist/builtins/plan-presentation.js.map +1 -0
  25. package/dist/extension/decision-channels.d.ts +132 -0
  26. package/dist/extension/decision-channels.js +1082 -0
  27. package/dist/extension/decision-channels.js.map +1 -0
  28. package/dist/extension/index.d.ts +1 -0
  29. package/dist/extension/index.js +426 -11
  30. package/dist/extension/index.js.map +1 -1
  31. package/dist/extension/widget.js +26 -2
  32. package/dist/extension/widget.js.map +1 -1
  33. package/dist/extension/workflow-tool.d.ts +1 -37
  34. package/dist/extension/workflow-tool.js +1 -42
  35. package/dist/extension/workflow-tool.js.map +1 -1
  36. package/dist/herdr/setup.d.ts +13 -1
  37. package/dist/herdr/setup.js +349 -36
  38. package/dist/herdr/setup.js.map +1 -1
  39. package/dist/host/rpc-bridge.js +4 -21
  40. package/dist/host/rpc-bridge.js.map +1 -1
  41. package/dist/render/graph-render.js +33 -8
  42. package/dist/render/graph-render.js.map +1 -1
  43. package/dist/viewer/cli.d.ts +1 -0
  44. package/dist/viewer/cli.js +21 -10
  45. package/dist/viewer/cli.js.map +1 -1
  46. package/dist/viewer/render.js +36 -2
  47. package/dist/viewer/render.js.map +1 -1
  48. package/dist/workflows/composition.js +15 -1
  49. package/dist/workflows/composition.js.map +1 -1
  50. package/dist/workflows/decision-presentation.d.ts +18 -0
  51. package/dist/workflows/decision-presentation.js +417 -0
  52. package/dist/workflows/decision-presentation.js.map +1 -0
  53. package/dist/workflows/engine.d.ts +2 -1
  54. package/dist/workflows/engine.js +86 -4
  55. package/dist/workflows/engine.js.map +1 -1
  56. package/dist/workflows/human-decision.d.ts +87 -0
  57. package/dist/workflows/human-decision.js +583 -0
  58. package/dist/workflows/human-decision.js.map +1 -0
  59. package/dist/workflows/index.d.ts +3 -1
  60. package/dist/workflows/index.js +2 -0
  61. package/dist/workflows/index.js.map +1 -1
  62. package/dist/workflows/schema.js +15 -0
  63. package/dist/workflows/schema.js.map +1 -1
  64. package/dist/workflows/store.js +7 -0
  65. package/dist/workflows/store.js.map +1 -1
  66. package/dist/workflows/tool-input.d.ts +72 -0
  67. package/dist/workflows/tool-input.js +141 -0
  68. package/dist/workflows/tool-input.js.map +1 -0
  69. package/dist/workflows/types.d.ts +223 -0
  70. package/docs/HUMAN_DECISIONS.md +371 -0
  71. package/docs/HUMAN_DECISION_PRESENTATIONS.md +323 -0
  72. package/docs/MONITOR.md +17 -6
  73. package/docs/WORKFLOW_COMPOSITION.md +13 -6
  74. package/docs/plans/2026-08-19-human-decision-gates-plan.md +296 -0
  75. package/docs/plans/2026-08-19-human-decision-presentations-plan.md +172 -0
  76. package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +45 -0
  77. package/docs/plans/2026-08-19-workflow-composition-plan.md +25 -17
  78. package/docs/plans/2026-08-20-autoimplement-blocker-challenge-plan.md +138 -0
  79. package/docs/plans/2026-08-20-herdr-plugin-sync-plan.md +104 -0
  80. package/docs/run-bundles.md +23 -2
  81. package/docs/workflows.md +47 -5
  82. package/examples/workflows/approved-plan.workflow.ts +58 -0
  83. package/examples/workflows/autoplan.workflow.ts +1 -0
  84. package/examples/workflows/human-decision.workflow.ts +62 -0
  85. package/herdr-plugin.toml +1 -1
  86. package/package.json +2 -1
  87. package/schemas/decision-presentation-v1.schema.json +83 -0
  88. package/schemas/human-decision-accepted-v1.schema.json +44 -0
  89. package/schemas/human-decision-accepted-v2.schema.json +50 -0
  90. package/schemas/human-decision-answer-attempt-v1.schema.json +37 -0
  91. package/schemas/human-decision-cancellation-v1.schema.json +15 -0
  92. package/schemas/human-decision-continuation-v1.schema.json +16 -0
  93. package/schemas/human-decision-delivery-v1.schema.json +28 -0
  94. package/schemas/human-decision-delivery-v2.schema.json +36 -0
  95. package/schemas/human-decision-receipt-v1.schema.json +33 -0
  96. package/schemas/human-decision-receipt-v2.schema.json +39 -0
  97. package/schemas/human-decision-request-v1.schema.json +59 -0
  98. package/schemas/human-decision-request-v2.schema.json +69 -0
  99. package/schemas/human-decision-resolution-v1.schema.json +27 -0
  100. package/schemas/human-decision-resolution-v2.schema.json +27 -0
  101. package/schemas/human-decision-settlement-v1.schema.json +28 -0
  102. package/skills/autodoc/SKILL.md +43 -0
  103. package/skills/autoimplement/SKILL.md +58 -0
  104. package/skills/autoimplement/agents/openai.yaml +4 -0
  105. package/skills/autoplan/SKILL.md +25 -0
  106. package/skills/monitor/SKILL.md +3 -1
  107. package/skills/pi-workflows/SKILL.md +3 -3
  108. package/src/builtins/autodoc.workflow.ts +325 -0
  109. package/src/builtins/autoimplement.workflow.ts +493 -23
  110. package/src/builtins/{autodevise.workflow.ts → autoplan.workflow.ts} +32 -32
  111. package/src/builtins/catalog.ts +8 -4
  112. package/src/builtins/index.ts +25 -6
  113. package/src/builtins/monitor.workflow.ts +162 -8
  114. package/src/builtins/plan-approval.workflow.ts +190 -0
  115. package/src/builtins/plan-presentation.ts +57 -0
  116. package/src/extension/decision-channels.ts +1533 -0
  117. package/src/extension/index.ts +499 -9
  118. package/src/extension/widget.ts +39 -2
  119. package/src/extension/workflow-tool.ts +5 -81
  120. package/src/herdr/setup.ts +429 -39
  121. package/src/host/rpc-bridge.ts +7 -30
  122. package/src/render/graph-render.ts +40 -3
  123. package/src/viewer/cli.ts +22 -10
  124. package/src/viewer/render.ts +56 -3
  125. package/src/workflows/composition.ts +16 -1
  126. package/src/workflows/decision-presentation.ts +501 -0
  127. package/src/workflows/engine.ts +108 -4
  128. package/src/workflows/human-decision.ts +819 -0
  129. package/src/workflows/index.ts +69 -0
  130. package/src/workflows/schema.ts +17 -0
  131. package/src/workflows/store.ts +8 -0
  132. package/src/workflows/tool-input.ts +202 -0
  133. package/src/workflows/types.ts +265 -0
  134. package/dist/builtins/autodevise.workflow.js.map +0 -1
  135. package/examples/workflows/autodevise.workflow.ts +0 -1
@@ -1,8 +1,14 @@
1
1
  import path from "node:path";
2
2
  import { agent, compute, defineWorkflow, includeWorkflow, includedResult, shell, } from "../workflows/definition.js";
3
- import autodeviseWorkflow, {} from "./autodevise.workflow.js";
3
+ import { digest } from "../workflows/human-decision.js";
4
+ import autodocWorkflow, {} from "./autodoc.workflow.js";
5
+ import autoplanWorkflow, {} from "./autoplan.workflow.js";
6
+ import planApprovalWorkflow, {} from "./plan-approval.workflow.js";
4
7
  const FIVE_MINUTES_MS = 5 * 60_000;
5
8
  const TEN_MINUTES_MS = 10 * 60_000;
9
+ const MAX_BLOCKER_CHALLENGES = 3;
10
+ const MAX_CHALLENGE_ITEMS = 5;
11
+ const MAX_CHALLENGE_TEXT = 500;
6
12
  function requireRecord(value, label) {
7
13
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
8
14
  throw new Error(`${label} must be an object`);
@@ -15,6 +21,68 @@ function requireString(value, label) {
15
21
  }
16
22
  return value.trim();
17
23
  }
24
+ function boundedChallengeItems(value, label) {
25
+ if (!Array.isArray(value) || value.length > MAX_CHALLENGE_ITEMS) {
26
+ throw new Error(`${label} must be an array with at most ${MAX_CHALLENGE_ITEMS} items`);
27
+ }
28
+ return value.map((item, index) => {
29
+ const text = requireString(item, `${label}[${index}]`);
30
+ if (text.length > MAX_CHALLENGE_TEXT) {
31
+ throw new Error(`${label}[${index}] must be at most ${MAX_CHALLENGE_TEXT} characters`);
32
+ }
33
+ return text;
34
+ });
35
+ }
36
+ function parseBlockerChallenge(value) {
37
+ const result = requireRecord(value, "blocker challenge");
38
+ if (result.route !== "continue" && result.route !== "blocked") {
39
+ throw new Error("blocker challenge route must be continue or blocked");
40
+ }
41
+ for (const key of ["blockingNow", "outsideAuthority", "canProceed"]) {
42
+ if (typeof result[key] !== "boolean") {
43
+ throw new Error(`blocker challenge ${key} must be a boolean`);
44
+ }
45
+ }
46
+ const blockingNow = result.blockingNow;
47
+ const outsideAuthority = result.outsideAuthority;
48
+ const canProceed = result.canProceed;
49
+ const reason = requireString(result.reason, "blocker challenge reason");
50
+ if (reason.length > MAX_CHALLENGE_TEXT) {
51
+ throw new Error(`blocker challenge reason must be at most ${MAX_CHALLENGE_TEXT} characters`);
52
+ }
53
+ if (typeof result.nextAction !== "string") {
54
+ throw new Error("blocker challenge nextAction must be a string");
55
+ }
56
+ const nextAction = result.nextAction.trim();
57
+ if (nextAction.length > MAX_CHALLENGE_TEXT) {
58
+ throw new Error(`blocker challenge nextAction must be at most ${MAX_CHALLENGE_TEXT} characters`);
59
+ }
60
+ const alternativesChecked = boundedChallengeItems(result.alternativesChecked, "blocker challenge alternativesChecked");
61
+ const evidence = boundedChallengeItems(result.evidence, "blocker challenge evidence");
62
+ if (result.route === "blocked") {
63
+ if (blockingNow !== true ||
64
+ outsideAuthority !== true ||
65
+ canProceed !== false ||
66
+ nextAction.length > 0 ||
67
+ alternativesChecked.length === 0 ||
68
+ evidence.length === 0) {
69
+ throw new Error("blocked challenge requires blockingNow=true, outsideAuthority=true, canProceed=false, an empty nextAction, and concrete alternatives and evidence");
70
+ }
71
+ }
72
+ else if (canProceed !== true || nextAction.length === 0) {
73
+ throw new Error("continue challenge requires canProceed=true and a practical nextAction");
74
+ }
75
+ return {
76
+ route: result.route,
77
+ blockingNow,
78
+ outsideAuthority,
79
+ canProceed,
80
+ reason,
81
+ nextAction,
82
+ alternativesChecked,
83
+ evidence,
84
+ };
85
+ }
18
86
  function parseInput(value) {
19
87
  const input = requireRecord(value, "autoimplement input");
20
88
  const constraints = input.constraints;
@@ -25,6 +93,47 @@ function parseInput(value) {
25
93
  if (input.merge !== undefined && typeof input.merge !== "boolean") {
26
94
  throw new Error("autoimplement merge must be a boolean");
27
95
  }
96
+ const documents = input.documents;
97
+ if (documents !== undefined &&
98
+ (!Array.isArray(documents) || documents.some((item) => typeof item !== "string"))) {
99
+ throw new Error("autoimplement documents must be an array of strings");
100
+ }
101
+ let documentation;
102
+ if (input.documentation !== undefined) {
103
+ if (input.plan === undefined) {
104
+ throw new Error("autoimplement documentation requires an explicit plan");
105
+ }
106
+ const raw = requireRecord(input.documentation, "autoimplement documentation");
107
+ if (raw.status !== "current") {
108
+ throw new Error("autoimplement documentation status must be current");
109
+ }
110
+ const planDigest = requireString(raw.planDigest, "autoimplement documentation planDigest");
111
+ if (planDigest !== digest(input.plan)) {
112
+ throw new Error("autoimplement documentation planDigest does not match the explicit plan");
113
+ }
114
+ if (!Array.isArray(raw.documents) || raw.documents.some((item) => typeof item !== "string")) {
115
+ throw new Error("autoimplement documentation documents must be an array of strings");
116
+ }
117
+ documentation = {
118
+ status: "current",
119
+ planDigest,
120
+ documents: [...raw.documents],
121
+ };
122
+ }
123
+ let approval;
124
+ if (input.approval !== undefined) {
125
+ const raw = requireRecord(input.approval, "autoimplement approval");
126
+ const maxReplans = raw.maxReplans ?? 3;
127
+ if (!Number.isInteger(maxReplans) ||
128
+ maxReplans < 1 ||
129
+ maxReplans > 20) {
130
+ throw new Error("autoimplement approval maxReplans must be from 1 through 20");
131
+ }
132
+ approval = {
133
+ audience: requireString(raw.audience, "autoimplement approval audience"),
134
+ maxReplans: maxReplans,
135
+ };
136
+ }
28
137
  return {
29
138
  task: requireString(input.task, "autoimplement task"),
30
139
  ...(input.plan !== undefined ? { plan: input.plan } : {}),
@@ -37,6 +146,38 @@ function parseInput(value) {
37
146
  ? { baseBranch: requireString(input.baseBranch, "baseBranch") }
38
147
  : {}),
39
148
  merge: input.merge === true,
149
+ ...(documents !== undefined ? { documents: [...documents] } : {}),
150
+ ...(documentation !== undefined ? { documentation } : {}),
151
+ ...(approval !== undefined ? { approval } : {}),
152
+ };
153
+ }
154
+ function parseExistingPlan(value) {
155
+ const result = requireRecord(value, "existing plan discovery");
156
+ if (result.route !== "found" && result.route !== "blocked") {
157
+ throw new Error("existing plan discovery route must be found or blocked");
158
+ }
159
+ if (result.route === "found") {
160
+ if (result.plan === undefined)
161
+ throw new Error("found plan must include plan");
162
+ if (result.documentation !== "current" &&
163
+ result.documentation !== "missing" &&
164
+ result.documentation !== "stale") {
165
+ throw new Error("found plan documentation must be current, missing, or stale");
166
+ }
167
+ }
168
+ if (!Array.isArray(result.documents) ||
169
+ result.documents.some((item) => typeof item !== "string")) {
170
+ throw new Error("existing plan documents must be an array of strings");
171
+ }
172
+ return {
173
+ route: result.route,
174
+ ...(result.plan !== undefined ? { plan: result.plan } : {}),
175
+ ...(result.documentation !== undefined
176
+ ? { documentation: result.documentation }
177
+ : {}),
178
+ documents: [...result.documents],
179
+ reason: requireString(result.reason, "existing plan discovery reason"),
180
+ evidence: result.evidence ?? null,
40
181
  };
41
182
  }
42
183
  function parseRoute(value, routes, label) {
@@ -124,10 +265,73 @@ function latestCiCommand(context) {
124
265
  }
125
266
  function currentPlan(context) {
126
267
  const adopted = context.outputs.adoptPlan;
127
- return adopted?.plan ?? context.input.plan;
268
+ if (adopted?.plan !== undefined)
269
+ return adopted.plan;
270
+ const documented = context.outputs.documentation;
271
+ if (documented?.exit === "ready" && documented.output?.plan !== undefined) {
272
+ return documented.output.plan;
273
+ }
274
+ const discovered = context.outputs.findPlan;
275
+ if (discovered?.route === "found" && discovered.plan !== undefined)
276
+ return discovered.plan;
277
+ return context.input.plan;
278
+ }
279
+ function currentPlanDigest(context) {
280
+ const adopted = context.outputs.adoptPlan;
281
+ if (typeof adopted?.planDigest === "string")
282
+ return adopted.planDigest;
283
+ const documented = context.outputs.documentation;
284
+ if (documented?.exit === "ready" && typeof documented.output?.planDigest === "string") {
285
+ return documented.output.planDigest;
286
+ }
287
+ const plan = currentPlan(context);
288
+ if (plan === undefined)
289
+ throw new Error("autoimplement does not have a selected plan");
290
+ return digest(plan);
291
+ }
292
+ function blockerChallenges(context) {
293
+ return context.state.steps
294
+ .filter((step) => step.nodeId === "challengeBlocker" && step.outcome === "ok")
295
+ .map((step) => step.output);
296
+ }
297
+ function latestBlockerClaim(context) {
298
+ const ids = [
299
+ "classifyImplementation",
300
+ "classifyVerification",
301
+ "repairReviewCommand",
302
+ "inspectComments",
303
+ "inspectCi",
304
+ "repairCiCommand",
305
+ "assessTrackedCi",
306
+ "classifyCi",
307
+ "finalizeDelivery",
308
+ ];
309
+ for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
310
+ const step = context.state.steps[index];
311
+ if (step && ids.includes(step.nodeId)) {
312
+ return { source: step.nodeId, result: step.output };
313
+ }
314
+ }
315
+ throw new Error("No model-generated blocker claim is available to challenge");
316
+ }
317
+ function recentWorkflowAttempts(context) {
318
+ return context.state.steps.slice(-12).map((step) => ({
319
+ nodeId: step.nodeId,
320
+ outcome: step.outcome,
321
+ output: step.output,
322
+ }));
128
323
  }
129
324
  function latestIssue(context) {
325
+ const approval = context.outputs.approval;
326
+ if (approval?.exit === "replan" && typeof approval.output?.instructions === "string") {
327
+ return {
328
+ source: "human-replan",
329
+ instructions: approval.output.instructions,
330
+ priorPlanDigest: currentPlanDigest(context),
331
+ };
332
+ }
130
333
  const ids = [
334
+ "challengeBlocker",
131
335
  "classifyImplementation",
132
336
  "classifyVerification",
133
337
  "triageReview",
@@ -190,6 +394,8 @@ function reviewRounds(context) {
190
394
  }
191
395
  function latestBlockedReason(context) {
192
396
  const candidates = [
397
+ "challengeBlockerGuard",
398
+ "challengeBlocker",
193
399
  "finalizeDelivery",
194
400
  "inspectCi",
195
401
  "assessTrackedCi",
@@ -198,7 +404,11 @@ function latestBlockedReason(context) {
198
404
  "classifyImplementation",
199
405
  "classifyVerification",
200
406
  "triageReview",
407
+ "replanGuard",
201
408
  "adoptPlan",
409
+ "findPlan",
410
+ "documentation",
411
+ "approval",
202
412
  ];
203
413
  for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
204
414
  const step = context.state.steps[index];
@@ -222,11 +432,46 @@ export const autoimplementWorkflow = defineWorkflow({
222
432
  title: ({ input }) => `autoimplement: ${input.task.slice(0, 60)}`,
223
433
  presentationPrompt: "Summarize what was implemented, the review rounds by severity, the CI result, the PR or merge result, and any remaining limitation. Include exact validation commands.",
224
434
  startAt: "prepare",
225
- maxSteps: 160,
435
+ maxSteps: 240,
226
436
  includes: {
437
+ documentation: includeWorkflow(autodocWorkflow, {
438
+ input: (context) => {
439
+ const request = context.input;
440
+ const discovery = context.outputs.findPlan;
441
+ const plan = currentPlan(context);
442
+ if (plan === undefined)
443
+ throw new Error("autoimplement documentation is missing a plan");
444
+ return {
445
+ task: request.task,
446
+ plan,
447
+ ...(request.repository !== undefined ? { repository: request.repository } : {}),
448
+ documents: request.documents ?? request.documentation?.documents ?? discovery?.documents ?? [],
449
+ evidence: latestIssue(context),
450
+ };
451
+ },
452
+ }),
453
+ approval: includeWorkflow(planApprovalWorkflow, {
454
+ input: (context) => {
455
+ const request = context.input;
456
+ if (request.approval === undefined) {
457
+ throw new Error("autoimplement approval was entered without an approval policy");
458
+ }
459
+ const plan = currentPlan(context);
460
+ if (plan === undefined)
461
+ throw new Error("autoimplement approval is missing a plan");
462
+ const revisions = context.state.steps.filter((step) => step.nodeId === "approval/approve").length;
463
+ return {
464
+ task: request.task,
465
+ plan,
466
+ planDigest: currentPlanDigest(context),
467
+ audience: request.approval.audience,
468
+ revision: revisions + 1,
469
+ };
470
+ },
471
+ }),
227
472
  redesign: includeWorkflow({
228
- workflow: "autodevise",
229
- contract: autodeviseWorkflow,
473
+ workflow: "autoplan",
474
+ contract: autoplanWorkflow,
230
475
  input: (context) => {
231
476
  const request = context.input;
232
477
  return {
@@ -251,26 +496,84 @@ export const autoimplementWorkflow = defineWorkflow({
251
496
  },
252
497
  nodes: {
253
498
  prepare: compute({
254
- run: ({ input }) => ({
255
- route: input.plan === undefined ? "redesign" : "implement",
256
- }),
499
+ run: ({ input }) => {
500
+ const request = input;
501
+ return {
502
+ route: request.plan === undefined
503
+ ? "find"
504
+ : request.documentation?.status === "current"
505
+ ? "ready"
506
+ : "document",
507
+ };
508
+ },
509
+ }),
510
+ findPlan: agent({
511
+ statusDetail: "finding existing plan",
512
+ prompt: ({ input }) => {
513
+ const request = input;
514
+ return [
515
+ "Find the clear plan that has already been selected for this task.",
516
+ "Use the current conversation context and referenced canonical documents.",
517
+ "Do not devise, improve, replace, document, or implement a plan.",
518
+ "Return blocked when no single clear existing plan can be found.",
519
+ "Report whether its canonical documentation is current, missing, or stale.",
520
+ `Task: ${request.task}`,
521
+ `Repository: ${request.repository ?? "current repository"}`,
522
+ `Referenced documents: ${JSON.stringify(request.documents ?? [])}`,
523
+ ].join("\n");
524
+ },
525
+ expectedOutput: '{ "route": "found" | "blocked", "plan": {} (required when found), "documentation": "current" | "missing" | "stale" (required when found), "documents": ["canonical file"], "reason": "reason", "evidence": "evidence" }',
526
+ validate: parseExistingPlan,
527
+ }),
528
+ routeFoundPlan: compute({
529
+ run: ({ outputs }) => {
530
+ const discovered = outputs.findPlan;
531
+ if (discovered.route !== "found" || discovered.plan === undefined) {
532
+ return { route: "blocked", reason: discovered.reason, evidence: discovered.evidence };
533
+ }
534
+ return {
535
+ route: discovered.documentation === "current" ? "ready" : "document",
536
+ plan: discovered.plan,
537
+ reason: discovered.reason,
538
+ evidence: discovered.evidence,
539
+ };
540
+ },
257
541
  }),
258
542
  adoptPlan: compute({
259
543
  run: ({ outputs }) => {
260
- const result = includedResult(autodeviseWorkflow, outputs.redesign);
544
+ const result = includedResult(autoplanWorkflow, outputs.redesign);
261
545
  if (result.exit !== "ready")
262
546
  throw new Error("redesign did not return a ready plan");
263
547
  return {
264
- route: result.output.changed ? "implement" : "blocked",
548
+ route: result.output.changed ? "document" : "blocked",
265
549
  plan: result.output.plan,
266
550
  planDigest: result.output.planDigest,
267
551
  changed: result.output.changed,
268
552
  reason: result.output.changed
269
- ? "The plan changed in response to new evidence."
553
+ ? "The plan changed in response to new evidence and must be documented."
270
554
  : "Redesign returned the same plan for the same unresolved evidence.",
271
555
  };
272
556
  },
273
557
  }),
558
+ maybeApproval: compute({
559
+ run: ({ input }) => ({
560
+ route: input.approval === undefined ? "implement" : "approve",
561
+ }),
562
+ }),
563
+ replanGuard: compute({
564
+ run: (context) => {
565
+ const request = context.input;
566
+ const limit = request.approval?.maxReplans ?? 3;
567
+ const replans = context.state.steps.filter((step) => step.nodeId === "approval/replan").length;
568
+ return replans > limit
569
+ ? {
570
+ route: "blocked",
571
+ reason: `Plan approval reached the ${limit}-replan safety limit.`,
572
+ evidence: context.outputs.approval,
573
+ }
574
+ : { route: "redesign", replans, limit };
575
+ },
576
+ }),
274
577
  implement: agent({
275
578
  timeoutMs: 60 * 60_000,
276
579
  statusDetail: "implementing",
@@ -279,6 +582,8 @@ export const autoimplementWorkflow = defineWorkflow({
279
582
  return [
280
583
  `Implement this task end-to-end: ${request.task}`,
281
584
  `Plan: ${JSON.stringify(currentPlan(context))}`,
585
+ `Authorized scope: ${request.scope ?? request.repository ?? "the current repository and task"}`,
586
+ `Constraints: ${JSON.stringify(request.constraints ?? [])}`,
282
587
  "Follow repository instructions and use the most elegant long-term production-ready implementation without unnecessary work.",
283
588
  "If implementation exposes a new design or scope problem, report it precisely instead of forcing the old plan.",
284
589
  "Do not merge yet.",
@@ -300,6 +605,51 @@ export const autoimplementWorkflow = defineWorkflow({
300
605
  expectedOutput: `{ "route": "verify" | "redesign" | "fix" | "blocked", "summary": "reason", "evidence": "evidence" }`,
301
606
  validate: (value) => parseRoute(value, ["verify", "redesign", "fix", "blocked"], "implementation assessment"),
302
607
  }),
608
+ challengeBlockerGuard: compute({
609
+ run: (context) => {
610
+ const challenges = blockerChallenges(context);
611
+ return challenges.length >= MAX_BLOCKER_CHALLENGES
612
+ ? {
613
+ route: "blocked",
614
+ reason: `Blocker challenge reached the ${MAX_BLOCKER_CHALLENGES}-attempt workflow safety limit.`,
615
+ evidence: { attempts: challenges.length, challenges },
616
+ }
617
+ : {
618
+ route: "challenge",
619
+ attempt: challenges.length + 1,
620
+ limit: MAX_BLOCKER_CHALLENGES,
621
+ };
622
+ },
623
+ }),
624
+ challengeBlocker: agent({
625
+ statusDetail: "challenging blocker claim",
626
+ prompt: (context) => {
627
+ const request = context.input;
628
+ return [
629
+ "Independently challenge the latest claim that autoimplement is blocked.",
630
+ "Are you really blocked?",
631
+ "Is this really a blocker right now?",
632
+ "Can you find a safe way to move forward and finish this?",
633
+ "Are you getting stuck on something trivial, procedural, reversible, or already authorized?",
634
+ "Inspect the task, approved plan, current result, evidence, scope, authority, previous attempts, and viable alternatives.",
635
+ "Distinguish a true external blocker from ordinary rollout work, local implementation work, a design adjustment, a missing verification step, or a reversible operational task.",
636
+ "A local test failure, stale package, packaging or artifact mismatch, rollback preparation, or deployment procedure is not by itself outside authority.",
637
+ "If a safe deployment and rollback path is already authorized, a supported cutover is work to do, not a blocker.",
638
+ "Confirm blocked only when the issue blocks progress now, is outside authority, and has no safe practical path forward.",
639
+ "Return continue with the next practical action when work can proceed. Keep text concise, with at most five alternatives and five evidence items.",
640
+ `Task: ${request.task}`,
641
+ `Approved plan: ${JSON.stringify(currentPlan(context))}`,
642
+ `Current result and claimed blocker: ${JSON.stringify(latestBlockerClaim(context))}`,
643
+ `Authorized scope: ${request.scope ?? request.repository ?? "the current repository and task"}`,
644
+ `Constraints and authority: ${JSON.stringify(request.constraints ?? [])}`,
645
+ `Merge authorized: ${request.merge === true}`,
646
+ `Previous blocker challenges: ${JSON.stringify(blockerChallenges(context))}`,
647
+ `Recent workflow attempts: ${JSON.stringify(recentWorkflowAttempts(context))}`,
648
+ ].join("\n");
649
+ },
650
+ expectedOutput: `{ "route": "continue" | "blocked", "blockingNow": true | false, "outsideAuthority": true | false, "canProceed": true | false, "reason": "concise reason", "nextAction": "practical action or empty when blocked", "alternativesChecked": ["checked alternative"], "evidence": ["concrete evidence"] }`,
651
+ validate: parseBlockerChallenge,
652
+ }),
303
653
  verify: agent({
304
654
  timeoutMs: 45 * 60_000,
305
655
  statusDetail: "verifying",
@@ -601,28 +951,73 @@ export const autoimplementWorkflow = defineWorkflow({
601
951
  edges: [
602
952
  {
603
953
  from: "prepare",
604
- switch: { on: "$.route", cases: { redesign: "redesign", implement: "implement" } },
954
+ switch: {
955
+ on: "$.route",
956
+ cases: { find: "findPlan", document: "documentation", ready: "maybeApproval" },
957
+ },
958
+ },
959
+ {
960
+ from: "findPlan",
961
+ switch: { on: "$.route", cases: { found: "routeFoundPlan", blocked: "blocked" } },
962
+ },
963
+ {
964
+ from: "routeFoundPlan",
965
+ switch: {
966
+ on: "$.route",
967
+ cases: { ready: "maybeApproval", document: "documentation", blocked: "blocked" },
968
+ },
605
969
  },
606
970
  { from: "redesign.ready", to: "adoptPlan" },
607
971
  { from: "redesign.blocked", to: "blocked" },
608
972
  {
609
973
  from: "adoptPlan",
610
- switch: { on: "$.route", cases: { implement: "implement", blocked: "blocked" } },
974
+ switch: { on: "$.route", cases: { document: "documentation", blocked: "blocked" } },
975
+ },
976
+ { from: "documentation.ready", to: "maybeApproval" },
977
+ { from: "documentation.blocked", to: "blocked" },
978
+ {
979
+ from: "maybeApproval",
980
+ switch: { on: "$.route", cases: { approve: "approval", implement: "implement" } },
981
+ },
982
+ { from: "approval.continue", to: "implement" },
983
+ { from: "approval.stop", to: "blocked" },
984
+ { from: "approval.replan", to: "replanGuard" },
985
+ {
986
+ from: "replanGuard",
987
+ switch: { on: "$.route", cases: { redesign: "redesign", blocked: "blocked" } },
611
988
  },
612
989
  { from: "implement", to: "classifyImplementation" },
613
990
  {
614
991
  from: "classifyImplementation",
615
992
  switch: {
616
993
  on: "$.route",
617
- cases: { verify: "verify", redesign: "redesign", fix: "fix", blocked: "blocked" },
994
+ cases: {
995
+ verify: "verify",
996
+ redesign: "redesign",
997
+ fix: "fix",
998
+ blocked: "challengeBlockerGuard",
999
+ },
618
1000
  },
619
1001
  },
1002
+ {
1003
+ from: "challengeBlockerGuard",
1004
+ switch: { on: "$.route", cases: { challenge: "challengeBlocker", blocked: "blocked" } },
1005
+ },
1006
+ {
1007
+ from: "challengeBlocker",
1008
+ switch: { on: "$.route", cases: { continue: "redesign", blocked: "blocked" } },
1009
+ },
620
1010
  { from: "verify", to: "classifyVerification" },
621
1011
  {
622
1012
  from: "classifyVerification",
623
1013
  switch: {
624
1014
  on: "$.route",
625
- cases: { publish: "publish", redesign: "redesign", fix: "fix", blocked: "blocked" },
1015
+ cases: {
1016
+ publish: "publish",
1017
+ redesign: "redesign",
1018
+ fix: "fix",
1019
+ blocked: "challengeBlockerGuard",
1020
+ },
626
1021
  },
627
1022
  },
628
1023
  { from: "fix", to: "verify" },
@@ -641,7 +1036,10 @@ export const autoimplementWorkflow = defineWorkflow({
641
1036
  },
642
1037
  {
643
1038
  from: "repairReviewCommand",
644
- switch: { on: "$.route", cases: { retry: "runReview", blocked: "blocked" } },
1039
+ switch: {
1040
+ on: "$.route",
1041
+ cases: { retry: "runReview", blocked: "challengeBlockerGuard" },
1042
+ },
645
1043
  },
646
1044
  {
647
1045
  from: "assessReview",
@@ -668,7 +1066,12 @@ export const autoimplementWorkflow = defineWorkflow({
668
1066
  from: "inspectComments",
669
1067
  switch: {
670
1068
  on: "$.route",
671
- cases: { redesign: "redesign", fix: "fix", ci: "inspectCi", blocked: "blocked" },
1069
+ cases: {
1070
+ redesign: "redesign",
1071
+ fix: "fix",
1072
+ ci: "inspectCi",
1073
+ blocked: "challengeBlockerGuard",
1074
+ },
672
1075
  },
673
1076
  },
674
1077
  {
@@ -679,7 +1082,7 @@ export const autoimplementWorkflow = defineWorkflow({
679
1082
  green: "finalizeDelivery",
680
1083
  failed: "classifyCi",
681
1084
  pending: "trackCi",
682
- unavailable: "blocked",
1085
+ unavailable: "challengeBlockerGuard",
683
1086
  },
684
1087
  },
685
1088
  },
@@ -692,7 +1095,10 @@ export const autoimplementWorkflow = defineWorkflow({
692
1095
  },
693
1096
  {
694
1097
  from: "repairCiCommand",
695
- switch: { on: "$.route", cases: { retry: "trackCi", blocked: "blocked" } },
1098
+ switch: {
1099
+ on: "$.route",
1100
+ cases: { retry: "trackCi", blocked: "challengeBlockerGuard" },
1101
+ },
696
1102
  },
697
1103
  {
698
1104
  from: "assessTrackedCi",
@@ -702,7 +1108,7 @@ export const autoimplementWorkflow = defineWorkflow({
702
1108
  green: "finalizeDelivery",
703
1109
  failed: "classifyCi",
704
1110
  pending: "opportunisticTest",
705
- unavailable: "blocked",
1111
+ unavailable: "challengeBlockerGuard",
706
1112
  },
707
1113
  },
708
1114
  },
@@ -715,13 +1121,16 @@ export const autoimplementWorkflow = defineWorkflow({
715
1121
  redesign: "redesign",
716
1122
  fix: "fix",
717
1123
  unrelated: "finalizeDelivery",
718
- blocked: "blocked",
1124
+ blocked: "challengeBlockerGuard",
719
1125
  },
720
1126
  },
721
1127
  },
722
1128
  {
723
1129
  from: "finalizeDelivery",
724
- switch: { on: "$.status", cases: { completed: "finalize", blocked: "blocked" } },
1130
+ switch: {
1131
+ on: "$.status",
1132
+ cases: { completed: "finalize", blocked: "challengeBlockerGuard" },
1133
+ },
725
1134
  },
726
1135
  ],
727
1136
  });