@wichayutdew/pi-workflows 1.0.1 → 2.0.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 (131) hide show
  1. package/README.md +312 -223
  2. package/agents/step.md +4 -11
  3. package/dist/index.js +2554 -976
  4. package/examples/mr-comments.workflow.yaml +16 -8
  5. package/examples/prompts/mr-comments/implement.md +13 -9
  6. package/examples/prompts/mr-comments/plan.md +44 -15
  7. package/examples/prompts/mr-comments/verify.md +8 -7
  8. package/examples/settings.yaml +7 -1
  9. package/examples/starter-kit/mr-comment.workflow.yaml +149 -0
  10. package/examples/starter-kit/mr-review.workflow.yaml +130 -0
  11. package/examples/starter-kit/steps/mr-comment/fetch.md +33 -0
  12. package/examples/starter-kit/steps/mr-comment/implement.md +38 -0
  13. package/examples/starter-kit/steps/mr-comment/plan.md +62 -0
  14. package/examples/starter-kit/steps/mr-comment/publish.md +36 -0
  15. package/examples/starter-kit/steps/mr-comment/verify.md +35 -0
  16. package/examples/starter-kit/steps/mr-review/fetch.md +25 -0
  17. package/examples/starter-kit/steps/mr-review/publish.md +30 -0
  18. package/examples/starter-kit/steps/mr-review/review.md +69 -0
  19. package/examples/starter-kit/steps/mr-review/verify.md +24 -0
  20. package/examples/starter-kit/steps/shared/prepare-workspace.md +57 -0
  21. package/examples/starter-kit/steps/ticket/implement.md +35 -0
  22. package/examples/starter-kit/steps/ticket/plan.md +40 -0
  23. package/examples/starter-kit/steps/ticket/verify.md +29 -0
  24. package/examples/starter-kit/steps/work/implement.md +34 -0
  25. package/examples/starter-kit/steps/work/plan.md +38 -0
  26. package/examples/starter-kit/steps/work/verify.md +30 -0
  27. package/examples/starter-kit/ticket.workflow.yaml +100 -0
  28. package/examples/starter-kit/work.workflow.yaml +97 -0
  29. package/package.json +3 -3
  30. package/schemas/workflow.schema.json +78 -44
  31. package/src/command-names.ts +1 -0
  32. package/src/commands.ts +33 -4
  33. package/src/config/ceiling.ts +6 -12
  34. package/src/config/load-workflows.ts +12 -1
  35. package/src/config/step-digests.ts +19 -0
  36. package/src/config/types.ts +17 -22
  37. package/src/config/validation/permissions.ts +5 -66
  38. package/src/config/validation/prompt.ts +1 -0
  39. package/src/config/validation/shared.ts +0 -2
  40. package/src/config/validation/step.ts +106 -2
  41. package/src/config/validation/workflow.ts +76 -0
  42. package/src/engine/create-run.ts +3 -0
  43. package/src/engine/gate-transitions.ts +58 -9
  44. package/src/engine/reconciliation-history.ts +61 -24
  45. package/src/engine/run-advance.ts +37 -4
  46. package/src/engine/run-lifecycle.ts +11 -0
  47. package/src/engine/run-reconciliation.ts +51 -21
  48. package/src/engine/run-validation.ts +235 -0
  49. package/src/engine/run-workflow-validation.ts +191 -0
  50. package/src/engine/state-types.ts +108 -5
  51. package/src/engine/state.ts +15 -1
  52. package/src/engine/step-trace.ts +495 -0
  53. package/src/engine/transitions.ts +1 -0
  54. package/src/harness/action-context.ts +31 -6
  55. package/src/harness/core-actions.ts +30 -1
  56. package/src/harness/delegation-control-actions.ts +27 -2
  57. package/src/harness/delegation-failure.ts +42 -9
  58. package/src/harness/delegation-plan.ts +96 -40
  59. package/src/harness/delegation-recovery-validation.ts +3 -1
  60. package/src/harness/delegation-response-actions.ts +73 -3
  61. package/src/harness/delegation-retry-policy.ts +5 -3
  62. package/src/harness/dependencies.ts +11 -2
  63. package/src/harness/gate-submission-action.ts +5 -31
  64. package/src/harness/lifecycle-actions.ts +4 -5
  65. package/src/harness/pause-actions.ts +10 -1
  66. package/src/harness/plannotator-result-actions.ts +20 -11
  67. package/src/harness/prompt-gate-actions.ts +40 -2
  68. package/src/harness/resume-action.ts +75 -1
  69. package/src/harness/start-actions.ts +77 -3
  70. package/src/harness/status-actions.ts +7 -17
  71. package/src/harness/step-effects.ts +61 -0
  72. package/src/harness/step-execution-actions.ts +181 -25
  73. package/src/harness/step-reporting.ts +168 -0
  74. package/src/harness/types.ts +1 -0
  75. package/src/harness/workspace-directory.ts +67 -0
  76. package/src/harness.ts +37 -9
  77. package/src/integrations/plannotator-requests.ts +40 -5
  78. package/src/integrations/subagents/child-policy-sections.ts +71 -84
  79. package/src/integrations/subagents/child-policy-types.ts +7 -7
  80. package/src/integrations/subagents/child-policy-validation.ts +10 -4
  81. package/src/integrations/subagents/child-runtime-completion.ts +1 -0
  82. package/src/integrations/subagents/child-runtime-files.ts +29 -86
  83. package/src/integrations/subagents/child-runtime-policy.ts +8 -33
  84. package/src/integrations/subagents/child-runtime.ts +3 -15
  85. package/src/integrations/subagents/delegated-result.ts +1 -0
  86. package/src/integrations/subagents/diagnostic-format.ts +5 -0
  87. package/src/integrations/subagents/diagnostic-types.ts +7 -1
  88. package/src/integrations/subagents/diagnostics.ts +2 -0
  89. package/src/integrations/subagents/failure-correlation.ts +52 -2
  90. package/src/integrations/subagents/failure-transcript.ts +60 -1
  91. package/src/integrations/subagents/replay-audit.ts +0 -1
  92. package/src/integrations/subagents/replay-safety.ts +12 -27
  93. package/src/integrations/subagents/session-diagnostics.ts +102 -3
  94. package/src/policy/bash-authorization.ts +2 -21
  95. package/src/policy/bash-types.ts +0 -5
  96. package/src/policy/bash.ts +1 -2
  97. package/src/policy/tool-call-authorization.ts +1 -7
  98. package/src/preflight.ts +0 -5
  99. package/src/prompt/retry-task.ts +11 -3
  100. package/src/prompt/step-contract.ts +10 -56
  101. package/src/prompt/step-sections.ts +8 -10
  102. package/src/prompt/step-task.ts +41 -10
  103. package/src/prompt/template.ts +3 -2
  104. package/src/runtime/completion-tool.ts +14 -0
  105. package/src/runtime/main-step-completion.ts +3 -0
  106. package/src/runtime/main-step-policy.ts +2 -1
  107. package/src/runtime/main-step-runtime-types.ts +9 -1
  108. package/src/runtime/main-step-runtime.ts +2 -0
  109. package/src/runtime/main-step-state.ts +6 -0
  110. package/src/runtime/main-step-trace.ts +45 -0
  111. package/src/runtime/step-result.ts +57 -0
  112. package/src/step-log.ts +178 -0
  113. package/src/workflow-doctor.ts +254 -0
  114. package/src/workflow-status/format-status.ts +32 -3
  115. package/src/workflow-status/render-board.ts +7 -4
  116. package/src/workflow-status/render-path.ts +51 -24
  117. package/src/workflow-status/render-step-detail.ts +379 -0
  118. package/src/workflow-status/render-summary.ts +22 -0
  119. package/src/workflow-status/transcript-reader.ts +231 -0
  120. package/src/workflow-status/types.ts +8 -0
  121. package/src/workflow-status/view.ts +161 -14
  122. package/src/workflow-status.ts +6 -0
  123. package/src/policy/approved-command-extraction.ts +0 -71
  124. package/src/policy/approved-commands.ts +0 -15
  125. package/src/policy/bash-read-only.ts +0 -161
  126. package/src/policy/restricted-git.ts +0 -30
  127. package/src/policy/reviewed-artifact.ts +0 -97
  128. package/src/policy/reviewed-command-safety.ts +0 -150
  129. package/src/policy/reviewed-command-shape.ts +0 -49
  130. package/src/policy/reviewed-repository-contract.ts +0 -138
  131. package/src/policy/reviewed-repository-cwd.ts +0 -102
@@ -48,6 +48,7 @@
48
48
  "tree",
49
49
  "trust",
50
50
  "workflow-abort",
51
+ "workflow-doctor",
51
52
  "workflow-list",
52
53
  "workflow-pause",
53
54
  "workflow-reload",
@@ -94,6 +95,34 @@
94
95
  "type": "string",
95
96
  "pattern": "^[a-z][a-z0-9-]{0,63}$"
96
97
  },
98
+ "relativePath": {
99
+ "type": "string",
100
+ "minLength": 1,
101
+ "maxLength": 4096,
102
+ "pattern": "^\\S(?:.*\\S)?$",
103
+ "allOf": [
104
+ {
105
+ "not": {
106
+ "pattern": "^/"
107
+ }
108
+ },
109
+ {
110
+ "not": {
111
+ "pattern": "^[A-Za-z]:"
112
+ }
113
+ },
114
+ {
115
+ "not": {
116
+ "pattern": "^\\\\"
117
+ }
118
+ },
119
+ {
120
+ "not": {
121
+ "pattern": "\\u0000"
122
+ }
123
+ }
124
+ ]
125
+ },
97
126
  "resourceName": {
98
127
  "type": "string",
99
128
  "pattern": "^[A-Za-z0-9_@./:+-]+$"
@@ -114,8 +143,7 @@
114
143
  "required": ["file"],
115
144
  "properties": {
116
145
  "file": {
117
- "type": "string",
118
- "minLength": 1
146
+ "$ref": "#/$defs/relativePath"
119
147
  }
120
148
  }
121
149
  }
@@ -175,32 +203,17 @@
175
203
  "required": ["mode"]
176
204
  },
177
205
  "then": {
178
- "anyOf": [
179
- {
180
- "required": ["allow"],
181
- "properties": {
182
- "allow": {
183
- "minItems": 1
184
- }
185
- }
186
- },
187
- {
188
- "required": ["approvedSources"],
189
- "properties": {
190
- "approvedSources": {
191
- "minItems": 1
192
- }
193
- }
206
+ "required": ["allow"],
207
+ "properties": {
208
+ "allow": {
209
+ "minItems": 1
194
210
  }
195
- ]
211
+ }
196
212
  },
197
213
  "else": {
198
214
  "properties": {
199
215
  "allow": {
200
216
  "maxItems": 0
201
- },
202
- "approvedSources": {
203
- "maxItems": 0
204
217
  }
205
218
  }
206
219
  }
@@ -208,33 +221,13 @@
208
221
  ],
209
222
  "properties": {
210
223
  "mode": {
211
- "enum": ["deny", "read-only", "allow-list", "unrestricted"]
224
+ "enum": ["deny", "allow-list", "unrestricted"]
212
225
  },
213
226
  "allow": {
214
227
  "type": "array",
215
228
  "items": {
216
229
  "$ref": "#/$defs/bashRule"
217
230
  }
218
- },
219
- "approvedSources": {
220
- "type": "array",
221
- "uniqueItems": true,
222
- "items": {
223
- "enum": [
224
- "verification-worker",
225
- "verification-reviewer",
226
- "remote-actions",
227
- "remote-push",
228
- "remote-drafts"
229
- ]
230
- }
231
- },
232
- "handoffSources": {
233
- "type": "array",
234
- "uniqueItems": true,
235
- "items": {
236
- "enum": ["verification-worker", "verification-reviewer"]
237
- }
238
231
  }
239
232
  }
240
233
  },
@@ -555,16 +548,54 @@
555
548
  "type": "integer",
556
549
  "minimum": 1000,
557
550
  "maximum": 30000,
558
- "default": 5000
551
+ "default": 30000
559
552
  }
560
553
  }
561
554
  }
562
555
  ]
563
556
  },
557
+ "workspace": {
558
+ "type": "object",
559
+ "additionalProperties": false,
560
+ "required": ["bindOn"],
561
+ "properties": {
562
+ "bindOn": {
563
+ "type": "array",
564
+ "minItems": 1,
565
+ "uniqueItems": true,
566
+ "items": {
567
+ "$ref": "#/$defs/identifier"
568
+ }
569
+ },
570
+ "allowedRoots": {
571
+ "type": "array",
572
+ "minItems": 1,
573
+ "maxItems": 32,
574
+ "uniqueItems": true,
575
+ "default": ["."],
576
+ "items": {
577
+ "$ref": "#/$defs/relativePath"
578
+ }
579
+ }
580
+ }
581
+ },
564
582
  "step": {
565
583
  "type": "object",
566
584
  "additionalProperties": false,
567
585
  "required": ["prompt", "transitions"],
586
+ "allOf": [
587
+ {
588
+ "if": {
589
+ "required": ["workspace"]
590
+ },
591
+ "then": {
592
+ "required": ["subagent"],
593
+ "not": {
594
+ "required": ["gate"]
595
+ }
596
+ }
597
+ }
598
+ ],
568
599
  "properties": {
569
600
  "title": {
570
601
  "type": "string",
@@ -595,6 +626,9 @@
595
626
  },
596
627
  "gate": {
597
628
  "$ref": "#/$defs/gate"
629
+ },
630
+ "workspace": {
631
+ "$ref": "#/$defs/workspace"
598
632
  }
599
633
  }
600
634
  }
@@ -3,6 +3,7 @@
3
3
  */
4
4
  export const HARNESS_COMMAND_NAMES = [
5
5
  'workflow-abort',
6
+ 'workflow-doctor',
6
7
  'workflow-list',
7
8
  'workflow-pause',
8
9
  'workflow-reload',
package/src/commands.ts CHANGED
@@ -12,6 +12,11 @@ export type WorkflowCommandController = {
12
12
  readonly workflowIds: () => ReadonlyArray<string>;
13
13
  /** Lists loaded workflows. */
14
14
  readonly list: (context: ExtensionCommandContext) => Promise<void>;
15
+ /** Diagnoses workflow graph liveness. */
16
+ readonly doctor: (
17
+ workflowId: string,
18
+ context: ExtensionCommandContext,
19
+ ) => Promise<void>;
15
20
  /** Starts a workflow. */
16
21
  readonly start: (
17
22
  workflowId: string,
@@ -23,8 +28,11 @@ export type WorkflowCommandController = {
23
28
  reason: string,
24
29
  context: ExtensionCommandContext,
25
30
  ) => Promise<void>;
26
- /** Resumes the paused workflow. */
27
- readonly resume: (context: ExtensionCommandContext) => Promise<void>;
31
+ /** Resumes the paused workflow with optional user guidance. */
32
+ readonly resume: (
33
+ input: string,
34
+ context: ExtensionCommandContext,
35
+ ) => Promise<void>;
28
36
  /** Aborts the active workflow. */
29
37
  readonly abort: (
30
38
  reason: string,
@@ -98,6 +106,25 @@ export function createHarnessCommands(
98
106
  handler: async (_args, context) => controller.list(context),
99
107
  },
100
108
  },
109
+ {
110
+ name: 'workflow-doctor',
111
+ options: {
112
+ description:
113
+ 'Check workflow completion paths, unreachable steps, and cycles',
114
+ getArgumentCompletions: (prefix) => {
115
+ const completions = controller
116
+ .workflowIds()
117
+ .filter((workflowId) => workflowId.startsWith(prefix))
118
+ .map((workflowId) => ({
119
+ value: workflowId,
120
+ label: workflowId,
121
+ }));
122
+ return completions.length > 0 ? completions : null;
123
+ },
124
+ handler: async (args, context) =>
125
+ controller.doctor(args.trim(), context),
126
+ },
127
+ },
101
128
  createStartCommand(controller),
102
129
  {
103
130
  name: 'workflow-pause',
@@ -110,8 +137,10 @@ export function createHarnessCommands(
110
137
  {
111
138
  name: 'workflow-resume',
112
139
  options: {
113
- description: 'Reload configuration and resume the paused workflow',
114
- handler: async (_args, context) => controller.resume(context),
140
+ description:
141
+ 'Reload configuration and resume: /workflow-resume [guidance]',
142
+ handler: async (input, context) =>
143
+ controller.resume(input.trim(), context),
115
144
  },
116
145
  },
117
146
  {
@@ -28,21 +28,10 @@ function bashWithinCeiling(
28
28
  if (ceiling.mode === 'unrestricted') return true;
29
29
  if (requested.mode === 'deny') return true;
30
30
  if (ceiling.mode === 'deny') return false;
31
- if (ceiling.mode === 'read-only') return requested.mode === 'read-only';
32
31
  if (requested.mode !== 'allow-list') return false;
33
32
 
34
33
  const allowedRules = new Set(ceiling.allow.map(ruleKey));
35
- const allowedSources = new Set(ceiling.approvedSources ?? []);
36
- const allowedHandoffSources = new Set(ceiling.handoffSources ?? []);
37
- return (
38
- requested.allow.every((rule) => allowedRules.has(ruleKey(rule))) &&
39
- (requested.approvedSources ?? []).every((source) =>
40
- allowedSources.has(source),
41
- ) &&
42
- (requested.handoffSources ?? []).every((source) =>
43
- allowedHandoffSources.has(source),
44
- )
45
- );
34
+ return requested.allow.every((rule) => allowedRules.has(ruleKey(rule)));
46
35
  }
47
36
 
48
37
  function turnBudgetErrors(
@@ -128,6 +117,11 @@ export function checkWorkflowAgainstCeiling(
128
117
  return Object.entries(workflow.steps).flatMap(([stepId, step]) => {
129
118
  const path = `workflow.steps.${stepId}.permissions`;
130
119
  return [
120
+ ...(Object.hasOwn(step, 'workspace')
121
+ ? [
122
+ `workflow.steps.${stepId}.workspace: workspace binding is unavailable to project workflows`,
123
+ ]
124
+ : []),
131
125
  ...step.permissions.tools
132
126
  .filter((tool) => !ceiling.tools.includes(tool))
133
127
  .map(
@@ -2,6 +2,10 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
2
2
  import { digest } from '../digest.ts';
3
3
  import { createDiagnostic, errorCode, errorMessage } from './diagnostics.ts';
4
4
  import type { ConfigFileSystem, LoadedDirectory } from './load-types.ts';
5
+ import {
6
+ digestWorkflowStep,
7
+ digestWorkflowStepStructure,
8
+ } from './step-digests.ts';
5
9
  import type {
6
10
  LoadedWorkflow,
7
11
  WorkflowDefinition,
@@ -81,7 +85,13 @@ async function loadWorkflowFile(
81
85
  const stepDigests = Object.fromEntries(
82
86
  Object.entries(definition.steps).map(([stepId, step]) => [
83
87
  stepId,
84
- digest({ step, prompt: prompts[stepId] }),
88
+ digestWorkflowStep(step, prompts[stepId] ?? ''),
89
+ ]),
90
+ );
91
+ const stepStructuralDigests = Object.fromEntries(
92
+ Object.entries(definition.steps).map(([stepId, step]) => [
93
+ stepId,
94
+ digestWorkflowStepStructure(step),
85
95
  ]),
86
96
  );
87
97
  return {
@@ -89,6 +99,7 @@ async function loadWorkflowFile(
89
99
  prompts,
90
100
  digest: digest({ definition, prompts }),
91
101
  stepDigests,
102
+ stepStructuralDigests,
92
103
  sourcePath,
93
104
  sourceKind,
94
105
  };
@@ -0,0 +1,19 @@
1
+ import { digest } from '../digest.ts';
2
+ import type { WorkflowStep } from './types.ts';
3
+
4
+ /**
5
+ * Digests a normalized workflow step together with its resolved prompt text.
6
+ */
7
+ export function digestWorkflowStep(step: WorkflowStep, prompt: string): string {
8
+ return digest({ step, prompt });
9
+ }
10
+
11
+ /**
12
+ * Digests every normalized workflow-step field except the prompt specification.
13
+ */
14
+ export function digestWorkflowStepStructure(step: WorkflowStep): string {
15
+ const structure = Object.fromEntries(
16
+ Object.entries(step).filter(([key]) => key !== 'prompt'),
17
+ );
18
+ return digest(structure);
19
+ }
@@ -4,18 +4,14 @@ export const WORKFLOW_SCHEMA_VERSION = 1 as const;
4
4
  export const DEFAULT_STATUS_SHORTCUT = 'ctrl+alt+w' as const satisfies KeyId;
5
5
  export const SUBAGENT_RUNTIME_NAME_PATTERN =
6
6
  /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)*$/;
7
+ export const MAX_WORKSPACE_PATH_CHARS = 4_096;
8
+ export const MAX_WORKSPACE_ALLOWED_ROOTS = 32;
7
9
 
8
10
  export const TERMINAL_TARGETS = ['$done', '$pause'] as const;
9
11
  export type TerminalTarget = (typeof TERMINAL_TARGETS)[number];
10
12
  export type StepTarget = string;
11
13
 
12
- export type BashMode = 'deny' | 'read-only' | 'allow-list' | 'unrestricted';
13
- export type BashApprovalSource =
14
- | 'verification-worker'
15
- | 'verification-reviewer'
16
- | 'remote-actions'
17
- | 'remote-push'
18
- | 'remote-drafts';
14
+ export type BashMode = 'deny' | 'allow-list' | 'unrestricted';
19
15
 
20
16
  export type BashRule = {
21
17
  readonly executable: string;
@@ -25,18 +21,6 @@ export type BashRule = {
25
21
  export type BashPermission = {
26
22
  readonly mode: BashMode;
27
23
  readonly allow: ReadonlyArray<BashRule>;
28
- /**
29
- * Exact commands extracted from the most recent human-approved artifact.
30
- * They supplement `allow` only inside the correlated step execution.
31
- */
32
- readonly approvedSources?: ReadonlyArray<BashApprovalSource>;
33
- /**
34
- * Sources whose command-only retry handoffs may refine an already approved
35
- * command. The original reviewed identity and safety envelope remain fixed.
36
- */
37
- readonly handoffSources?: ReadonlyArray<
38
- 'verification-worker' | 'verification-reviewer'
39
- >;
40
24
  };
41
25
 
42
26
  export type StepPermissions = {
@@ -81,9 +65,9 @@ export type StepSubagent = {
81
65
  readonly toolBudget?: SubagentToolBudget;
82
66
  readonly artifacts: boolean;
83
67
  /**
84
- * Authorizes the bounded automatic recovery sequence in Bash modes broader
85
- * than read-only. Every failed attempt must still have a complete,
86
- * mutation-safe replay audit.
68
+ * Authorizes bounded automatic recovery when Bash can execute commands.
69
+ * Every failed attempt must still have a complete, mutation-safe replay
70
+ * audit.
87
71
  */
88
72
  readonly retryToolFailures: boolean;
89
73
  };
@@ -108,6 +92,13 @@ export type PlannotatorGate = GateDefinition & {
108
92
 
109
93
  export type WorkflowGate = PromptGate | PlannotatorGate;
110
94
 
95
+ export type StepWorkspaceBinding = {
96
+ /** Outcomes whose result establishes the workspace for later steps. */
97
+ readonly bindOn: ReadonlyArray<string>;
98
+ /** Paths relative to the run-start directory that may contain the workspace. */
99
+ readonly allowedRoots: ReadonlyArray<string>;
100
+ };
101
+
111
102
  export type WorkflowStep = {
112
103
  readonly title: string;
113
104
  readonly prompt: PromptSpec;
@@ -117,6 +108,8 @@ export type WorkflowStep = {
117
108
  readonly requires: StepRequirements;
118
109
  readonly transitions: Readonly<Record<string, StepTarget>>;
119
110
  readonly gate?: WorkflowGate;
111
+ /** Optional one-time workspace binding produced by this delegated step. */
112
+ readonly workspace?: StepWorkspaceBinding;
120
113
  };
121
114
 
122
115
  export type WorkflowDefinition = {
@@ -137,6 +130,8 @@ export type LoadedWorkflow = {
137
130
  readonly prompts: Readonly<Record<string, string>>;
138
131
  readonly digest: string;
139
132
  readonly stepDigests: Readonly<Record<string, string>>;
133
+ /** Per-step digest excluding only the prompt specification and resolved text. */
134
+ readonly stepStructuralDigests: Readonly<Record<string, string>>;
140
135
  readonly sourcePath: string;
141
136
  readonly sourceKind: WorkflowSourceKind;
142
137
  };
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  EMPTY_PERMISSIONS,
3
- type BashApprovalSource,
4
3
  type BashMode,
5
4
  type BashPermission,
6
5
  type BashRule,
@@ -8,7 +7,6 @@ import {
8
7
  type StepRequirements,
9
8
  } from '../types.ts';
10
9
  import {
11
- BASH_APPROVAL_SOURCE_PATTERN,
12
10
  EXECUTABLE_PATTERN,
13
11
  isJsonObject,
14
12
  MCP_SELECTOR_PATTERN,
@@ -20,18 +18,6 @@ import {
20
18
  type ValidationErrors,
21
19
  } from './shared.ts';
22
20
 
23
- const BASH_APPROVAL_SOURCES = [
24
- 'verification-worker',
25
- 'verification-reviewer',
26
- 'remote-actions',
27
- 'remote-push',
28
- 'remote-drafts',
29
- ] as const satisfies ReadonlyArray<BashApprovalSource>;
30
-
31
- function isBashApprovalSource(value: string): value is BashApprovalSource {
32
- return BASH_APPROVAL_SOURCES.some((source) => source === value);
33
- }
34
-
35
21
  function emptyPermissions(): StepPermissions {
36
22
  return {
37
23
  tools: [],
@@ -131,23 +117,13 @@ function parseBashPermission(
131
117
  errors.push(`${path}: expected an object`);
132
118
  return { ...EMPTY_PERMISSIONS.bash, allow: [] };
133
119
  }
134
- rejectUnknownKeys(
135
- value,
136
- ['mode', 'allow', 'approvedSources', 'handoffSources'],
137
- path,
138
- errors,
139
- );
120
+ rejectUnknownKeys(value, ['mode', 'allow'], path, errors);
140
121
 
141
122
  const mode = readString(value.mode, `${path}.mode`, errors);
142
123
  const isValidMode =
143
- mode === 'deny' ||
144
- mode === 'read-only' ||
145
- mode === 'allow-list' ||
146
- mode === 'unrestricted';
124
+ mode === 'deny' || mode === 'allow-list' || mode === 'unrestricted';
147
125
  if (!isValidMode) {
148
- errors.push(
149
- `${path}.mode: expected deny, read-only, allow-list, or unrestricted`,
150
- );
126
+ errors.push(`${path}.mode: expected deny, allow-list, or unrestricted`);
151
127
  }
152
128
 
153
129
  const allow = Array.isArray(value.allow)
@@ -159,54 +135,17 @@ function parseBashPermission(
159
135
  errors.push(`${path}.allow: expected an array`);
160
136
  }
161
137
 
162
- const approvedSources = (
163
- value.approvedSources === undefined
164
- ? []
165
- : readStringList(
166
- value.approvedSources,
167
- `${path}.approvedSources`,
168
- errors,
169
- BASH_APPROVAL_SOURCE_PATTERN,
170
- )
171
- ).filter(isBashApprovalSource);
172
- const handoffSources = (
173
- value.handoffSources === undefined
174
- ? []
175
- : readStringList(
176
- value.handoffSources,
177
- `${path}.handoffSources`,
178
- errors,
179
- /^(verification-worker|verification-reviewer)$/,
180
- )
181
- ) as Array<'verification-worker' | 'verification-reviewer'>;
182
-
183
138
  const normalizedMode: BashMode = isValidMode ? mode : 'deny';
184
139
  if (normalizedMode !== 'allow-list' && allow.length > 0) {
185
140
  errors.push(`${path}.allow: only valid when mode is "allow-list"`);
186
141
  }
187
- if (
188
- normalizedMode === 'allow-list' &&
189
- allow.length === 0 &&
190
- approvedSources.length === 0
191
- ) {
192
- errors.push(
193
- `${path}: allow-list mode requires an allow rule or an approved command source`,
194
- );
195
- }
196
- if (normalizedMode !== 'allow-list' && approvedSources.length > 0) {
197
- errors.push(
198
- `${path}.approvedSources: only valid when mode is "allow-list"`,
199
- );
200
- }
201
- if (normalizedMode !== 'allow-list' && handoffSources.length > 0) {
202
- errors.push(`${path}.handoffSources: only valid when mode is "allow-list"`);
142
+ if (normalizedMode === 'allow-list' && allow.length === 0) {
143
+ errors.push(`${path}: allow-list mode requires an allow rule`);
203
144
  }
204
145
 
205
146
  return {
206
147
  mode: normalizedMode,
207
148
  allow,
208
- ...(approvedSources.length > 0 ? { approvedSources } : {}),
209
- ...(handoffSources.length > 0 ? { handoffSources } : {}),
210
149
  };
211
150
  }
212
151
 
@@ -8,6 +8,7 @@ const PROMPT_VARIABLES = new Set([
8
8
  'reviewed.artifact',
9
9
  'reviewed.feedback',
10
10
  'gate.feedback',
11
+ 'resume.input',
11
12
  ]);
12
13
 
13
14
  /** Validate template variables embedded in resolved prompt text. */
@@ -12,8 +12,6 @@ export const TOOL_PATTERN = /^[A-Za-z0-9_.:-]+$/;
12
12
  export const RESOURCE_SELECTOR_PATTERN = /^[A-Za-z0-9_@./:+-]+$/;
13
13
  export const MCP_SELECTOR_PATTERN = /^[A-Za-z0-9_.:-]+(?:\/[A-Za-z0-9_.:-]+)?$/;
14
14
  export const EXECUTABLE_PATTERN = /^[A-Za-z0-9_./+-]+$/;
15
- export const BASH_APPROVAL_SOURCE_PATTERN =
16
- /^(verification-worker|verification-reviewer|remote-actions|remote-push|remote-drafts)$/;
17
15
 
18
16
  type StringOptions = {
19
17
  readonly pattern?: RegExp;