@osolmaz/pi-workflows 0.10.0 → 0.11.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 (125) hide show
  1. package/README.md +23 -12
  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 +210 -11
  6. package/dist/builtins/autoimplement.workflow.js +245 -13
  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/host/rpc-bridge.js +4 -21
  37. package/dist/host/rpc-bridge.js.map +1 -1
  38. package/dist/render/graph-render.js +33 -8
  39. package/dist/render/graph-render.js.map +1 -1
  40. package/dist/viewer/render.js +36 -2
  41. package/dist/viewer/render.js.map +1 -1
  42. package/dist/workflows/composition.js +15 -1
  43. package/dist/workflows/composition.js.map +1 -1
  44. package/dist/workflows/decision-presentation.d.ts +18 -0
  45. package/dist/workflows/decision-presentation.js +417 -0
  46. package/dist/workflows/decision-presentation.js.map +1 -0
  47. package/dist/workflows/engine.d.ts +2 -1
  48. package/dist/workflows/engine.js +86 -4
  49. package/dist/workflows/engine.js.map +1 -1
  50. package/dist/workflows/human-decision.d.ts +87 -0
  51. package/dist/workflows/human-decision.js +583 -0
  52. package/dist/workflows/human-decision.js.map +1 -0
  53. package/dist/workflows/index.d.ts +3 -1
  54. package/dist/workflows/index.js +2 -0
  55. package/dist/workflows/index.js.map +1 -1
  56. package/dist/workflows/schema.js +15 -0
  57. package/dist/workflows/schema.js.map +1 -1
  58. package/dist/workflows/store.js +7 -0
  59. package/dist/workflows/store.js.map +1 -1
  60. package/dist/workflows/tool-input.d.ts +72 -0
  61. package/dist/workflows/tool-input.js +141 -0
  62. package/dist/workflows/tool-input.js.map +1 -0
  63. package/dist/workflows/types.d.ts +223 -0
  64. package/docs/HUMAN_DECISIONS.md +371 -0
  65. package/docs/HUMAN_DECISION_PRESENTATIONS.md +323 -0
  66. package/docs/MONITOR.md +17 -6
  67. package/docs/WORKFLOW_COMPOSITION.md +13 -6
  68. package/docs/plans/2026-08-19-human-decision-gates-plan.md +296 -0
  69. package/docs/plans/2026-08-19-human-decision-presentations-plan.md +172 -0
  70. package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +45 -0
  71. package/docs/plans/2026-08-19-workflow-composition-plan.md +25 -17
  72. package/docs/run-bundles.md +23 -2
  73. package/docs/workflows.md +43 -5
  74. package/examples/workflows/approved-plan.workflow.ts +58 -0
  75. package/examples/workflows/autoplan.workflow.ts +1 -0
  76. package/examples/workflows/human-decision.workflow.ts +62 -0
  77. package/herdr-plugin.toml +1 -1
  78. package/package.json +2 -1
  79. package/schemas/decision-presentation-v1.schema.json +83 -0
  80. package/schemas/human-decision-accepted-v1.schema.json +44 -0
  81. package/schemas/human-decision-accepted-v2.schema.json +50 -0
  82. package/schemas/human-decision-answer-attempt-v1.schema.json +37 -0
  83. package/schemas/human-decision-cancellation-v1.schema.json +15 -0
  84. package/schemas/human-decision-continuation-v1.schema.json +16 -0
  85. package/schemas/human-decision-delivery-v1.schema.json +28 -0
  86. package/schemas/human-decision-delivery-v2.schema.json +36 -0
  87. package/schemas/human-decision-receipt-v1.schema.json +33 -0
  88. package/schemas/human-decision-receipt-v2.schema.json +39 -0
  89. package/schemas/human-decision-request-v1.schema.json +59 -0
  90. package/schemas/human-decision-request-v2.schema.json +69 -0
  91. package/schemas/human-decision-resolution-v1.schema.json +27 -0
  92. package/schemas/human-decision-resolution-v2.schema.json +27 -0
  93. package/schemas/human-decision-settlement-v1.schema.json +28 -0
  94. package/skills/autodoc/SKILL.md +43 -0
  95. package/skills/autoimplement/SKILL.md +58 -0
  96. package/skills/autoimplement/agents/openai.yaml +4 -0
  97. package/skills/autoplan/SKILL.md +25 -0
  98. package/skills/monitor/SKILL.md +3 -1
  99. package/skills/pi-workflows/SKILL.md +3 -3
  100. package/src/builtins/autodoc.workflow.ts +325 -0
  101. package/src/builtins/autoimplement.workflow.ts +281 -14
  102. package/src/builtins/{autodevise.workflow.ts → autoplan.workflow.ts} +32 -32
  103. package/src/builtins/catalog.ts +8 -4
  104. package/src/builtins/index.ts +25 -6
  105. package/src/builtins/monitor.workflow.ts +162 -8
  106. package/src/builtins/plan-approval.workflow.ts +190 -0
  107. package/src/builtins/plan-presentation.ts +57 -0
  108. package/src/extension/decision-channels.ts +1533 -0
  109. package/src/extension/index.ts +499 -9
  110. package/src/extension/widget.ts +39 -2
  111. package/src/extension/workflow-tool.ts +5 -81
  112. package/src/host/rpc-bridge.ts +7 -30
  113. package/src/render/graph-render.ts +40 -3
  114. package/src/viewer/render.ts +56 -3
  115. package/src/workflows/composition.ts +16 -1
  116. package/src/workflows/decision-presentation.ts +501 -0
  117. package/src/workflows/engine.ts +108 -4
  118. package/src/workflows/human-decision.ts +819 -0
  119. package/src/workflows/index.ts +69 -0
  120. package/src/workflows/schema.ts +17 -0
  121. package/src/workflows/store.ts +8 -0
  122. package/src/workflows/tool-input.ts +202 -0
  123. package/src/workflows/types.ts +265 -0
  124. package/dist/builtins/autodevise.workflow.js.map +0 -1
  125. package/examples/workflows/autodevise.workflow.ts +0 -1
@@ -7,12 +7,15 @@ import {
7
7
  includedResult,
8
8
  shell,
9
9
  } from "../workflows/definition.js";
10
+ import { digest } from "../workflows/human-decision.js";
10
11
  import type {
11
12
  ShellActionExecution,
12
13
  ShellActionResult,
13
14
  WorkflowNodeContext,
14
15
  } from "../workflows/types.js";
15
- import autodeviseWorkflow, { type AutodeviseInput } from "./autodevise.workflow.js";
16
+ import autodocWorkflow, { type AutodocInput } from "./autodoc.workflow.js";
17
+ import autoplanWorkflow, { type AutoplanInput } from "./autoplan.workflow.js";
18
+ import planApprovalWorkflow, { type PlanApprovalInput } from "./plan-approval.workflow.js";
16
19
 
17
20
  export type AutoimplementInput = {
18
21
  task: string;
@@ -22,6 +25,25 @@ export type AutoimplementInput = {
22
25
  repository?: string;
23
26
  baseBranch?: string;
24
27
  merge?: boolean;
28
+ documents?: string[];
29
+ documentation?: {
30
+ status: "current";
31
+ planDigest: string;
32
+ documents: string[];
33
+ };
34
+ approval?: {
35
+ audience: string;
36
+ maxReplans: number;
37
+ };
38
+ };
39
+
40
+ export type ExistingPlanDiscovery = {
41
+ route: "found" | "blocked";
42
+ plan?: unknown;
43
+ documentation?: "current" | "missing" | "stale";
44
+ documents: string[];
45
+ reason: string;
46
+ evidence: unknown;
25
47
  };
26
48
 
27
49
  type StructuredCommand = {
@@ -94,6 +116,51 @@ function parseInput(value: unknown): AutoimplementInput {
94
116
  if (input.merge !== undefined && typeof input.merge !== "boolean") {
95
117
  throw new Error("autoimplement merge must be a boolean");
96
118
  }
119
+ const documents = input.documents;
120
+ if (
121
+ documents !== undefined &&
122
+ (!Array.isArray(documents) || documents.some((item) => typeof item !== "string"))
123
+ ) {
124
+ throw new Error("autoimplement documents must be an array of strings");
125
+ }
126
+ let documentation: AutoimplementInput["documentation"];
127
+ if (input.documentation !== undefined) {
128
+ if (input.plan === undefined) {
129
+ throw new Error("autoimplement documentation requires an explicit plan");
130
+ }
131
+ const raw = requireRecord(input.documentation, "autoimplement documentation");
132
+ if (raw.status !== "current") {
133
+ throw new Error("autoimplement documentation status must be current");
134
+ }
135
+ const planDigest = requireString(raw.planDigest, "autoimplement documentation planDigest");
136
+ if (planDigest !== digest(input.plan)) {
137
+ throw new Error("autoimplement documentation planDigest does not match the explicit plan");
138
+ }
139
+ if (!Array.isArray(raw.documents) || raw.documents.some((item) => typeof item !== "string")) {
140
+ throw new Error("autoimplement documentation documents must be an array of strings");
141
+ }
142
+ documentation = {
143
+ status: "current",
144
+ planDigest,
145
+ documents: [...raw.documents] as string[],
146
+ };
147
+ }
148
+ let approval: AutoimplementInput["approval"];
149
+ if (input.approval !== undefined) {
150
+ const raw = requireRecord(input.approval, "autoimplement approval");
151
+ const maxReplans = raw.maxReplans ?? 3;
152
+ if (
153
+ !Number.isInteger(maxReplans) ||
154
+ (maxReplans as number) < 1 ||
155
+ (maxReplans as number) > 20
156
+ ) {
157
+ throw new Error("autoimplement approval maxReplans must be from 1 through 20");
158
+ }
159
+ approval = {
160
+ audience: requireString(raw.audience, "autoimplement approval audience"),
161
+ maxReplans: maxReplans as number,
162
+ };
163
+ }
97
164
  return {
98
165
  task: requireString(input.task, "autoimplement task"),
99
166
  ...(input.plan !== undefined ? { plan: input.plan } : {}),
@@ -106,6 +173,42 @@ function parseInput(value: unknown): AutoimplementInput {
106
173
  ? { baseBranch: requireString(input.baseBranch, "baseBranch") }
107
174
  : {}),
108
175
  merge: input.merge === true,
176
+ ...(documents !== undefined ? { documents: [...documents] as string[] } : {}),
177
+ ...(documentation !== undefined ? { documentation } : {}),
178
+ ...(approval !== undefined ? { approval } : {}),
179
+ };
180
+ }
181
+
182
+ function parseExistingPlan(value: unknown): ExistingPlanDiscovery {
183
+ const result = requireRecord(value, "existing plan discovery");
184
+ if (result.route !== "found" && result.route !== "blocked") {
185
+ throw new Error("existing plan discovery route must be found or blocked");
186
+ }
187
+ if (result.route === "found") {
188
+ if (result.plan === undefined) throw new Error("found plan must include plan");
189
+ if (
190
+ result.documentation !== "current" &&
191
+ result.documentation !== "missing" &&
192
+ result.documentation !== "stale"
193
+ ) {
194
+ throw new Error("found plan documentation must be current, missing, or stale");
195
+ }
196
+ }
197
+ if (
198
+ !Array.isArray(result.documents) ||
199
+ result.documents.some((item) => typeof item !== "string")
200
+ ) {
201
+ throw new Error("existing plan documents must be an array of strings");
202
+ }
203
+ return {
204
+ route: result.route,
205
+ ...(result.plan !== undefined ? { plan: result.plan } : {}),
206
+ ...(result.documentation !== undefined
207
+ ? { documentation: result.documentation as "current" | "missing" | "stale" }
208
+ : {}),
209
+ documents: [...result.documents] as string[],
210
+ reason: requireString(result.reason, "existing plan discovery reason"),
211
+ evidence: result.evidence ?? null,
109
212
  };
110
213
  }
111
214
 
@@ -210,10 +313,43 @@ function latestCiCommand(context: WorkflowNodeContext): StructuredCommand {
210
313
 
211
314
  function currentPlan(context: WorkflowNodeContext): unknown {
212
315
  const adopted = context.outputs.adoptPlan as { plan?: unknown } | undefined;
213
- return adopted?.plan ?? (context.input as AutoimplementInput).plan;
316
+ if (adopted?.plan !== undefined) return adopted.plan;
317
+ const documented = context.outputs.documentation as
318
+ | { exit?: string; output?: { plan?: unknown } }
319
+ | undefined;
320
+ if (documented?.exit === "ready" && documented.output?.plan !== undefined) {
321
+ return documented.output.plan;
322
+ }
323
+ const discovered = context.outputs.findPlan as ExistingPlanDiscovery | undefined;
324
+ if (discovered?.route === "found" && discovered.plan !== undefined) return discovered.plan;
325
+ return (context.input as AutoimplementInput).plan;
326
+ }
327
+
328
+ function currentPlanDigest(context: WorkflowNodeContext): string {
329
+ const adopted = context.outputs.adoptPlan as { planDigest?: unknown } | undefined;
330
+ if (typeof adopted?.planDigest === "string") return adopted.planDigest;
331
+ const documented = context.outputs.documentation as
332
+ | { exit?: string; output?: { planDigest?: unknown } }
333
+ | undefined;
334
+ if (documented?.exit === "ready" && typeof documented.output?.planDigest === "string") {
335
+ return documented.output.planDigest;
336
+ }
337
+ const plan = currentPlan(context);
338
+ if (plan === undefined) throw new Error("autoimplement does not have a selected plan");
339
+ return digest(plan);
214
340
  }
215
341
 
216
342
  function latestIssue(context: WorkflowNodeContext): unknown {
343
+ const approval = context.outputs.approval as
344
+ | { exit?: string; output?: { instructions?: unknown } }
345
+ | undefined;
346
+ if (approval?.exit === "replan" && typeof approval.output?.instructions === "string") {
347
+ return {
348
+ source: "human-replan",
349
+ instructions: approval.output.instructions,
350
+ priorPlanDigest: currentPlanDigest(context),
351
+ };
352
+ }
217
353
  const ids = [
218
354
  "classifyImplementation",
219
355
  "classifyVerification",
@@ -287,7 +423,11 @@ function latestBlockedReason(context: WorkflowNodeContext): { reason: string; ev
287
423
  "classifyImplementation",
288
424
  "classifyVerification",
289
425
  "triageReview",
426
+ "replanGuard",
290
427
  "adoptPlan",
428
+ "findPlan",
429
+ "documentation",
430
+ "approval",
291
431
  ];
292
432
  for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
293
433
  const step = context.state.steps[index];
@@ -311,12 +451,48 @@ export const autoimplementWorkflow = defineWorkflow({
311
451
  presentationPrompt:
312
452
  "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.",
313
453
  startAt: "prepare",
314
- maxSteps: 160,
454
+ maxSteps: 240,
315
455
  includes: {
456
+ documentation: includeWorkflow(autodocWorkflow, {
457
+ input: (context): AutodocInput => {
458
+ const request = context.input as AutoimplementInput;
459
+ const discovery = context.outputs.findPlan as ExistingPlanDiscovery | undefined;
460
+ const plan = currentPlan(context);
461
+ if (plan === undefined) throw new Error("autoimplement documentation is missing a plan");
462
+ return {
463
+ task: request.task,
464
+ plan,
465
+ ...(request.repository !== undefined ? { repository: request.repository } : {}),
466
+ documents:
467
+ request.documents ?? request.documentation?.documents ?? discovery?.documents ?? [],
468
+ evidence: latestIssue(context),
469
+ };
470
+ },
471
+ }),
472
+ approval: includeWorkflow(planApprovalWorkflow, {
473
+ input: (context): PlanApprovalInput => {
474
+ const request = context.input as AutoimplementInput;
475
+ if (request.approval === undefined) {
476
+ throw new Error("autoimplement approval was entered without an approval policy");
477
+ }
478
+ const plan = currentPlan(context);
479
+ if (plan === undefined) throw new Error("autoimplement approval is missing a plan");
480
+ const revisions = context.state.steps.filter(
481
+ (step) => step.nodeId === "approval/approve",
482
+ ).length;
483
+ return {
484
+ task: request.task,
485
+ plan,
486
+ planDigest: currentPlanDigest(context),
487
+ audience: request.approval.audience,
488
+ revision: revisions + 1,
489
+ };
490
+ },
491
+ }),
316
492
  redesign: includeWorkflow({
317
- workflow: "autodevise",
318
- contract: autodeviseWorkflow,
319
- input: (context): AutodeviseInput => {
493
+ workflow: "autoplan",
494
+ contract: autoplanWorkflow,
495
+ input: (context): AutoplanInput => {
320
496
  const request = context.input as AutoimplementInput;
321
497
  return {
322
498
  problem: request.task,
@@ -340,25 +516,87 @@ export const autoimplementWorkflow = defineWorkflow({
340
516
  },
341
517
  nodes: {
342
518
  prepare: compute({
343
- run: ({ input }) => ({
344
- route: (input as AutoimplementInput).plan === undefined ? "redesign" : "implement",
345
- }),
519
+ run: ({ input }) => {
520
+ const request = input as AutoimplementInput;
521
+ return {
522
+ route:
523
+ request.plan === undefined
524
+ ? "find"
525
+ : request.documentation?.status === "current"
526
+ ? "ready"
527
+ : "document",
528
+ };
529
+ },
530
+ }),
531
+ findPlan: agent({
532
+ statusDetail: "finding existing plan",
533
+ prompt: ({ input }) => {
534
+ const request = input as AutoimplementInput;
535
+ return [
536
+ "Find the clear plan that has already been selected for this task.",
537
+ "Use the current conversation context and referenced canonical documents.",
538
+ "Do not devise, improve, replace, document, or implement a plan.",
539
+ "Return blocked when no single clear existing plan can be found.",
540
+ "Report whether its canonical documentation is current, missing, or stale.",
541
+ `Task: ${request.task}`,
542
+ `Repository: ${request.repository ?? "current repository"}`,
543
+ `Referenced documents: ${JSON.stringify(request.documents ?? [])}`,
544
+ ].join("\n");
545
+ },
546
+ expectedOutput:
547
+ '{ "route": "found" | "blocked", "plan": {} (required when found), "documentation": "current" | "missing" | "stale" (required when found), "documents": ["canonical file"], "reason": "reason", "evidence": "evidence" }',
548
+ validate: parseExistingPlan,
549
+ }),
550
+ routeFoundPlan: compute({
551
+ run: ({ outputs }) => {
552
+ const discovered = outputs.findPlan as ExistingPlanDiscovery;
553
+ if (discovered.route !== "found" || discovered.plan === undefined) {
554
+ return { route: "blocked", reason: discovered.reason, evidence: discovered.evidence };
555
+ }
556
+ return {
557
+ route: discovered.documentation === "current" ? "ready" : "document",
558
+ plan: discovered.plan,
559
+ reason: discovered.reason,
560
+ evidence: discovered.evidence,
561
+ };
562
+ },
346
563
  }),
347
564
  adoptPlan: compute({
348
565
  run: ({ outputs }) => {
349
- const result = includedResult(autodeviseWorkflow, outputs.redesign);
566
+ const result = includedResult(autoplanWorkflow, outputs.redesign);
350
567
  if (result.exit !== "ready") throw new Error("redesign did not return a ready plan");
351
568
  return {
352
- route: result.output.changed ? "implement" : "blocked",
569
+ route: result.output.changed ? "document" : "blocked",
353
570
  plan: result.output.plan,
354
571
  planDigest: result.output.planDigest,
355
572
  changed: result.output.changed,
356
573
  reason: result.output.changed
357
- ? "The plan changed in response to new evidence."
574
+ ? "The plan changed in response to new evidence and must be documented."
358
575
  : "Redesign returned the same plan for the same unresolved evidence.",
359
576
  };
360
577
  },
361
578
  }),
579
+ maybeApproval: compute({
580
+ run: ({ input }) => ({
581
+ route: (input as AutoimplementInput).approval === undefined ? "implement" : "approve",
582
+ }),
583
+ }),
584
+ replanGuard: compute({
585
+ run: (context) => {
586
+ const request = context.input as AutoimplementInput;
587
+ const limit = request.approval?.maxReplans ?? 3;
588
+ const replans = context.state.steps.filter(
589
+ (step) => step.nodeId === "approval/replan",
590
+ ).length;
591
+ return replans > limit
592
+ ? {
593
+ route: "blocked",
594
+ reason: `Plan approval reached the ${limit}-replan safety limit.`,
595
+ evidence: context.outputs.approval,
596
+ }
597
+ : { route: "redesign", replans, limit };
598
+ },
599
+ }),
362
600
  implement: agent({
363
601
  timeoutMs: 60 * 60_000,
364
602
  statusDetail: "implementing",
@@ -367,6 +605,8 @@ export const autoimplementWorkflow = defineWorkflow({
367
605
  return [
368
606
  `Implement this task end-to-end: ${request.task}`,
369
607
  `Plan: ${JSON.stringify(currentPlan(context))}`,
608
+ `Authorized scope: ${request.scope ?? request.repository ?? "the current repository and task"}`,
609
+ `Constraints: ${JSON.stringify(request.constraints ?? [])}`,
370
610
  "Follow repository instructions and use the most elegant long-term production-ready implementation without unnecessary work.",
371
611
  "If implementation exposes a new design or scope problem, report it precisely instead of forcing the old plan.",
372
612
  "Do not merge yet.",
@@ -728,13 +968,40 @@ export const autoimplementWorkflow = defineWorkflow({
728
968
  edges: [
729
969
  {
730
970
  from: "prepare",
731
- switch: { on: "$.route", cases: { redesign: "redesign", implement: "implement" } },
971
+ switch: {
972
+ on: "$.route",
973
+ cases: { find: "findPlan", document: "documentation", ready: "maybeApproval" },
974
+ },
975
+ },
976
+ {
977
+ from: "findPlan",
978
+ switch: { on: "$.route", cases: { found: "routeFoundPlan", blocked: "blocked" } },
979
+ },
980
+ {
981
+ from: "routeFoundPlan",
982
+ switch: {
983
+ on: "$.route",
984
+ cases: { ready: "maybeApproval", document: "documentation", blocked: "blocked" },
985
+ },
732
986
  },
733
987
  { from: "redesign.ready", to: "adoptPlan" },
734
988
  { from: "redesign.blocked", to: "blocked" },
735
989
  {
736
990
  from: "adoptPlan",
737
- switch: { on: "$.route", cases: { implement: "implement", blocked: "blocked" } },
991
+ switch: { on: "$.route", cases: { document: "documentation", blocked: "blocked" } },
992
+ },
993
+ { from: "documentation.ready", to: "maybeApproval" },
994
+ { from: "documentation.blocked", to: "blocked" },
995
+ {
996
+ from: "maybeApproval",
997
+ switch: { on: "$.route", cases: { approve: "approval", implement: "implement" } },
998
+ },
999
+ { from: "approval.continue", to: "implement" },
1000
+ { from: "approval.stop", to: "blocked" },
1001
+ { from: "approval.replan", to: "replanGuard" },
1002
+ {
1003
+ from: "replanGuard",
1004
+ switch: { on: "$.route", cases: { redesign: "redesign", blocked: "blocked" } },
738
1005
  },
739
1006
  { from: "implement", to: "classifyImplementation" },
740
1007
  {
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { agent, compute, defineWorkflow } from "../workflows/definition.js";
3
3
 
4
- export type AutodeviseInput = {
4
+ export type AutoplanInput = {
5
5
  problem: string;
6
6
  scope?: string;
7
7
  constraints?: string[];
@@ -9,7 +9,7 @@ export type AutodeviseInput = {
9
9
  newEvidence?: unknown;
10
10
  };
11
11
 
12
- export type AutodeviseReady = {
12
+ export type AutoplanReady = {
13
13
  status: "ready";
14
14
  frame: unknown;
15
15
  proposal: unknown;
@@ -21,7 +21,7 @@ export type AutodeviseReady = {
21
21
  changed: boolean;
22
22
  };
23
23
 
24
- export type AutodeviseBlocked = {
24
+ export type AutoplanBlocked = {
25
25
  status: "blocked";
26
26
  frame: unknown;
27
27
  proposal: unknown;
@@ -44,18 +44,18 @@ function requireString(value: unknown, label: string): string {
44
44
  return value.trim();
45
45
  }
46
46
 
47
- function parseInput(value: unknown): AutodeviseInput {
48
- const input = requireRecord(value, "autodevise input");
47
+ function parseInput(value: unknown): AutoplanInput {
48
+ const input = requireRecord(value, "autoplan input");
49
49
  const constraints = input.constraints;
50
50
  if (
51
51
  constraints !== undefined &&
52
52
  (!Array.isArray(constraints) || constraints.some((item) => typeof item !== "string"))
53
53
  ) {
54
- throw new Error("autodevise constraints must be an array of strings");
54
+ throw new Error("autoplan constraints must be an array of strings");
55
55
  }
56
56
  return {
57
- problem: requireString(input.problem, "autodevise problem"),
58
- ...(input.scope !== undefined ? { scope: requireString(input.scope, "autodevise scope") } : {}),
57
+ problem: requireString(input.problem, "autoplan problem"),
58
+ ...(input.scope !== undefined ? { scope: requireString(input.scope, "autoplan scope") } : {}),
59
59
  ...(constraints !== undefined ? { constraints: [...constraints] as string[] } : {}),
60
60
  ...(input.previousPlan !== undefined ? { previousPlan: input.previousPlan } : {}),
61
61
  ...(input.newEvidence !== undefined ? { newEvidence: input.newEvidence } : {}),
@@ -63,13 +63,13 @@ function parseInput(value: unknown): AutodeviseInput {
63
63
  }
64
64
 
65
65
  function parseSelection(value: unknown): Record<string, unknown> {
66
- const selection = requireRecord(value, "autodevise selection");
66
+ const selection = requireRecord(value, "autoplan selection");
67
67
  if (selection.status !== "ready" && selection.status !== "blocked") {
68
- throw new Error("autodevise selection status must be ready or blocked");
68
+ throw new Error("autoplan selection status must be ready or blocked");
69
69
  }
70
- requireString(selection.selected, "autodevise selected solution");
71
- requireString(selection.why, "autodevise selection reason");
72
- if (selection.status === "blocked") requireString(selection.blocker, "autodevise blocker");
70
+ requireString(selection.selected, "autoplan selected solution");
71
+ requireString(selection.why, "autoplan selection reason");
72
+ if (selection.status === "blocked") requireString(selection.blocker, "autoplan blocker");
73
73
  return selection;
74
74
  }
75
75
 
@@ -77,12 +77,12 @@ function digest(value: unknown): string {
77
77
  return `sha256:${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
78
78
  }
79
79
 
80
- export const autodeviseWorkflow = defineWorkflow({
80
+ export const autoplanWorkflow = defineWorkflow({
81
81
  source: import.meta.url,
82
- contractId: "pi-workflows.autodevise.v1",
83
- name: "autodevise",
82
+ contractId: "pi-workflows.autoplan.v1",
83
+ name: "autoplan",
84
84
  input: parseInput,
85
- title: ({ input }) => `autodevise: ${input.problem.slice(0, 60)}`,
85
+ title: ({ input }) => `autoplan: ${input.problem.slice(0, 60)}`,
86
86
  presentationPrompt: [
87
87
  "Present the selected practical solution and its implementation plan.",
88
88
  "Briefly state how the ideal informed the choice and what was excluded as outside scope.",
@@ -93,18 +93,18 @@ export const autodeviseWorkflow = defineWorkflow({
93
93
  exits: {
94
94
  ready: {
95
95
  from: "finalize",
96
- validate: (value: unknown): AutodeviseReady => value as AutodeviseReady,
96
+ validate: (value: unknown): AutoplanReady => value as AutoplanReady,
97
97
  },
98
98
  blocked: {
99
99
  from: "blocked",
100
- validate: (value: unknown): AutodeviseBlocked => value as AutodeviseBlocked,
100
+ validate: (value: unknown): AutoplanBlocked => value as AutoplanBlocked,
101
101
  },
102
102
  },
103
103
  nodes: {
104
104
  frame: agent({
105
105
  statusDetail: "framing the problem",
106
106
  prompt: ({ input }) => {
107
- const request = input as AutodeviseInput;
107
+ const request = input as AutoplanInput;
108
108
  return [
109
109
  `Frame this problem: ${request.problem}`,
110
110
  `Authorized scope: ${request.scope ?? "infer it conservatively from the request and current project"}.`,
@@ -116,7 +116,7 @@ export const autodeviseWorkflow = defineWorkflow({
116
116
  ].join("\n");
117
117
  },
118
118
  expectedOutput: `{ "problem": "concise statement", "success": ["criterion"], "inScope": ["change"], "outOfScope": ["change"], "constraints": ["constraint"], "controlBoundary": "what can change" }`,
119
- validate: (value) => requireRecord(value, "autodevise frame"),
119
+ validate: (value) => requireRecord(value, "autoplan frame"),
120
120
  }),
121
121
  propose: agent({
122
122
  statusDetail: "devising a solution",
@@ -129,7 +129,7 @@ export const autodeviseWorkflow = defineWorkflow({
129
129
  `Problem frame: ${JSON.stringify(outputs.frame)}`,
130
130
  ].join("\n"),
131
131
  expectedOutput: `{ "solution": "proposal", "rationale": "why", "parts": ["part"], "tradeoffs": ["trade-off"] }`,
132
- validate: (value) => requireRecord(value, "autodevise proposal"),
132
+ validate: (value) => requireRecord(value, "autoplan proposal"),
133
133
  }),
134
134
  ideal: agent({
135
135
  statusDetail: "describing the ideal end state",
@@ -141,10 +141,10 @@ export const autodeviseWorkflow = defineWorkflow({
141
141
  "Explain the practical value beyond the proposal.",
142
142
  `Problem frame: ${JSON.stringify(outputs.frame)}`,
143
143
  `Proposal: ${JSON.stringify(outputs.propose)}`,
144
- `New evidence: ${JSON.stringify((input as AutodeviseInput).newEvidence ?? null)}`,
144
+ `New evidence: ${JSON.stringify((input as AutoplanInput).newEvidence ?? null)}`,
145
145
  ].join("\n"),
146
146
  expectedOutput: `{ "ideal": "ideal end state", "outsideDependencies": ["dependency"], "additionalValue": ["benefit"] }`,
147
- validate: (value) => requireRecord(value, "autodevise ideal"),
147
+ validate: (value) => requireRecord(value, "autoplan ideal"),
148
148
  }),
149
149
  choose: agent({
150
150
  statusDetail: "choosing the practical solution",
@@ -177,11 +177,11 @@ export const autodeviseWorkflow = defineWorkflow({
177
177
  "Do not implement the plan.",
178
178
  `Frame: ${JSON.stringify(outputs.frame)}`,
179
179
  `Selection: ${JSON.stringify(outputs.choose)}`,
180
- `Previous plan: ${JSON.stringify((input as AutodeviseInput).previousPlan ?? null)}`,
181
- `New evidence: ${JSON.stringify((input as AutodeviseInput).newEvidence ?? null)}`,
180
+ `Previous plan: ${JSON.stringify((input as AutoplanInput).previousPlan ?? null)}`,
181
+ `New evidence: ${JSON.stringify((input as AutoplanInput).newEvidence ?? null)}`,
182
182
  ].join("\n"),
183
183
  expectedOutput: `{ "summary": "approach", "steps": [{ "change": "change", "where": "location", "verification": "evidence" }], "contracts": ["impact"], "tests": ["test"], "risks": [{ "risk": "risk", "mitigation": "mitigation" }], "boundaries": ["excluded work"] }`,
184
- validate: (value) => requireRecord(value, "autodevise plan"),
184
+ validate: (value) => requireRecord(value, "autoplan plan"),
185
185
  }),
186
186
  blocked: compute({
187
187
  run: ({ outputs }) => {
@@ -192,13 +192,13 @@ export const autodeviseWorkflow = defineWorkflow({
192
192
  proposal: outputs.propose,
193
193
  ideal: outputs.ideal,
194
194
  selection,
195
- reason: requireString(selection.blocker, "autodevise blocker"),
196
- } satisfies AutodeviseBlocked;
195
+ reason: requireString(selection.blocker, "autoplan blocker"),
196
+ } satisfies AutoplanBlocked;
197
197
  },
198
198
  }),
199
199
  finalize: compute({
200
200
  run: ({ outputs, input }) => {
201
- const request = input as AutodeviseInput;
201
+ const request = input as AutoplanInput;
202
202
  const planDigest = digest(outputs.plan);
203
203
  const previousPlanDigest =
204
204
  request.previousPlan === undefined ? undefined : digest(request.previousPlan);
@@ -212,7 +212,7 @@ export const autodeviseWorkflow = defineWorkflow({
212
212
  planDigest,
213
213
  ...(previousPlanDigest !== undefined ? { previousPlanDigest } : {}),
214
214
  changed: previousPlanDigest === undefined || previousPlanDigest !== planDigest,
215
- } satisfies AutodeviseReady;
215
+ } satisfies AutoplanReady;
216
216
  },
217
217
  }),
218
218
  },
@@ -228,4 +228,4 @@ export const autodeviseWorkflow = defineWorkflow({
228
228
  ],
229
229
  });
230
230
 
231
- export default autodeviseWorkflow;
231
+ export default autoplanWorkflow;
@@ -1,14 +1,18 @@
1
1
  import { BuiltinWorkflowCatalog } from "../workflows/catalog.js";
2
- import autodeviseWorkflow from "./autodevise.workflow.js";
2
+ import autodocWorkflow from "./autodoc.workflow.js";
3
3
  import autoimplementWorkflow from "./autoimplement.workflow.js";
4
+ import autoplanWorkflow from "./autoplan.workflow.js";
4
5
  import monitorWorkflow from "./monitor.workflow.js";
6
+ import planApprovalWorkflow from "./plan-approval.workflow.js";
5
7
 
6
8
  export const builtinWorkflowCatalog = new BuiltinWorkflowCatalog([
7
- { id: "autodevise", revision: "1", definition: autodeviseWorkflow },
8
- { id: "autoimplement", revision: "1", definition: autoimplementWorkflow },
9
+ { id: "autoplan", revision: "1", definition: autoplanWorkflow },
10
+ { id: "autodoc", revision: "1", definition: autodocWorkflow },
11
+ { id: "autoimplement", revision: "4", definition: autoimplementWorkflow },
12
+ { id: "plan-approval", revision: "2", definition: planApprovalWorkflow },
9
13
  {
10
14
  id: "monitor",
11
- revision: "4",
15
+ revision: "7",
12
16
  definition: monitorWorkflow,
13
17
  legacySources: [
14
18
  {
@@ -1,13 +1,32 @@
1
1
  export {
2
- autodeviseWorkflow,
3
- type AutodeviseBlocked,
4
- type AutodeviseInput,
5
- type AutodeviseReady,
6
- } from "./autodevise.workflow.js";
2
+ autoplanWorkflow,
3
+ type AutoplanBlocked,
4
+ type AutoplanInput,
5
+ type AutoplanReady,
6
+ } from "./autoplan.workflow.js";
7
+ export {
8
+ autodocWorkflow,
9
+ type AutodocBlocked,
10
+ type AutodocInput,
11
+ type DocumentedPlan,
12
+ } from "./autodoc.workflow.js";
7
13
  export {
8
14
  autoimplementWorkflow,
9
15
  type AutoimplementBlocked,
10
16
  type AutoimplementCompleted,
11
17
  type AutoimplementInput,
18
+ type ExistingPlanDiscovery,
12
19
  } from "./autoimplement.workflow.js";
13
- export { default as monitorWorkflow, type MonitorInput } from "./monitor.workflow.js";
20
+ export {
21
+ default as monitorWorkflow,
22
+ type MonitorInput,
23
+ type MonitorRepairPolicy,
24
+ } from "./monitor.workflow.js";
25
+ export { presentPlan } from "./plan-presentation.js";
26
+ export {
27
+ planApprovalWorkflow,
28
+ type PlanApprovalContinue,
29
+ type PlanApprovalInput,
30
+ type PlanApprovalReplan,
31
+ type PlanApprovalStop,
32
+ } from "./plan-approval.workflow.js";