agentweaver 0.1.15 → 0.1.16

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 (94) hide show
  1. package/README.md +26 -9
  2. package/dist/artifact-manifest.js +219 -0
  3. package/dist/artifacts.js +15 -0
  4. package/dist/doctor/checks/env-diagnostics.js +25 -0
  5. package/dist/doctor/checks/flow-readiness.js +15 -18
  6. package/dist/flow-state.js +75 -15
  7. package/dist/index.js +391 -175
  8. package/dist/interactive/blessed-session.js +361 -0
  9. package/dist/interactive/controller.js +1293 -0
  10. package/dist/interactive/create-interactive-session.js +5 -0
  11. package/dist/interactive/ink/index.js +576 -0
  12. package/dist/interactive/progress.js +245 -0
  13. package/dist/interactive/selectors.js +14 -0
  14. package/dist/interactive/session.js +1 -0
  15. package/dist/interactive/state.js +34 -0
  16. package/dist/interactive/tree.js +155 -0
  17. package/dist/interactive/types.js +1 -0
  18. package/dist/interactive/view-model.js +1 -0
  19. package/dist/interactive-ui.js +159 -194
  20. package/dist/pipeline/context.js +1 -0
  21. package/dist/pipeline/declarative-flow-runner.js +212 -6
  22. package/dist/pipeline/declarative-flows.js +27 -0
  23. package/dist/pipeline/execution-routing-config.js +15 -0
  24. package/dist/pipeline/flow-catalog.js +19 -3
  25. package/dist/pipeline/flow-run-resume.js +29 -0
  26. package/dist/pipeline/flow-specs/auto-common.json +89 -360
  27. package/dist/pipeline/flow-specs/auto-golang.json +58 -363
  28. package/dist/pipeline/flow-specs/auto-simple.json +141 -0
  29. package/dist/pipeline/flow-specs/bugz/bug-analyze.json +2 -0
  30. package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
  31. package/dist/pipeline/flow-specs/design-review/design-review-loop.json +304 -0
  32. package/dist/pipeline/flow-specs/design-review.json +10 -0
  33. package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +11 -0
  34. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +2 -0
  35. package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
  36. package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +2 -0
  37. package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +2 -0
  38. package/dist/pipeline/flow-specs/implement.json +13 -6
  39. package/dist/pipeline/flow-specs/instant-task.json +177 -0
  40. package/dist/pipeline/flow-specs/normalize-task-source.json +311 -0
  41. package/dist/pipeline/flow-specs/plan-revise.json +7 -1
  42. package/dist/pipeline/flow-specs/plan.json +48 -70
  43. package/dist/pipeline/flow-specs/review/review-fix.json +24 -4
  44. package/dist/pipeline/flow-specs/review/review-loop.json +351 -45
  45. package/dist/pipeline/flow-specs/review/review-project-loop.json +590 -0
  46. package/dist/pipeline/flow-specs/review/review-project.json +12 -0
  47. package/dist/pipeline/flow-specs/review/review.json +37 -31
  48. package/dist/pipeline/flow-specs/task-describe.json +2 -0
  49. package/dist/pipeline/flow-specs/task-source/jira-fetch.json +70 -0
  50. package/dist/pipeline/flow-specs/task-source/manual-input.json +216 -0
  51. package/dist/pipeline/node-registry.js +41 -1
  52. package/dist/pipeline/node-runner.js +3 -2
  53. package/dist/pipeline/nodes/build-review-fix-prompt-node.js +5 -1
  54. package/dist/pipeline/nodes/clear-ready-to-merge-node.js +11 -0
  55. package/dist/pipeline/nodes/commit-message-form-node.js +8 -0
  56. package/dist/pipeline/nodes/design-review-verdict-node.js +36 -0
  57. package/dist/pipeline/nodes/ensure-summary-json-node.js +13 -2
  58. package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +19 -2
  59. package/dist/pipeline/nodes/fetch-gitlab-review-node.js +19 -2
  60. package/dist/pipeline/nodes/flow-run-node.js +226 -7
  61. package/dist/pipeline/nodes/git-commit-form-node.js +8 -0
  62. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +19 -2
  63. package/dist/pipeline/nodes/jira-fetch-node.js +50 -4
  64. package/dist/pipeline/nodes/llm-prompt-node.js +32 -12
  65. package/dist/pipeline/nodes/planning-bundle-node.js +10 -0
  66. package/dist/pipeline/nodes/review-verdict-node.js +86 -0
  67. package/dist/pipeline/nodes/select-files-form-node.js +8 -0
  68. package/dist/pipeline/nodes/structured-summary-node.js +24 -0
  69. package/dist/pipeline/nodes/user-input-node.js +38 -3
  70. package/dist/pipeline/nodes/write-selection-file-node.js +20 -4
  71. package/dist/pipeline/prompt-registry.js +3 -1
  72. package/dist/pipeline/prompt-runtime.js +4 -1
  73. package/dist/pipeline/review-iteration.js +26 -0
  74. package/dist/pipeline/spec-compiler.js +2 -0
  75. package/dist/pipeline/spec-types.js +3 -0
  76. package/dist/pipeline/spec-validator.js +14 -0
  77. package/dist/pipeline/value-resolver.js +74 -1
  78. package/dist/prompts.js +36 -14
  79. package/dist/review-severity.js +45 -0
  80. package/dist/runtime/artifact-registry.js +402 -0
  81. package/dist/runtime/design-review-input-contract.js +17 -16
  82. package/dist/runtime/env-loader.js +3 -0
  83. package/dist/runtime/execution-routing-store.js +134 -0
  84. package/dist/runtime/execution-routing.js +227 -0
  85. package/dist/runtime/interactive-execution-routing.js +462 -0
  86. package/dist/runtime/plan-revise-input-contract.js +35 -32
  87. package/dist/runtime/planning-bundle.js +123 -0
  88. package/dist/runtime/ready-to-merge.js +22 -1
  89. package/dist/runtime/review-input-contract.js +100 -0
  90. package/dist/structured-artifact-schema-registry.js +9 -0
  91. package/dist/structured-artifact-schemas.json +140 -1
  92. package/dist/structured-artifacts.js +77 -6
  93. package/dist/user-input.js +70 -3
  94. package/package.json +6 -3
package/README.md CHANGED
@@ -10,7 +10,7 @@ Typical usage looks like:
10
10
 
11
11
  The important part is not that exact chain. The point is that AgentWeaver lets you design, operate, and evolve durable agent harnesses instead of accumulating one-off prompts and shell glue.
12
12
 
13
- Для planning-heavy задач типовой путь теперь может включать и `plan -> design-review -> implement`, где `design-review` проверяет качество артефактов планирования до начала кодинга.
13
+ For planning-heavy work, a typical path can now include `plan -> design-review -> implement`, where `design-review` critiques planning artifacts before coding starts.
14
14
 
15
15
  ## What It Does
16
16
 
@@ -62,7 +62,7 @@ This keeps workflow design in JSON while keeping implementation details in typed
62
62
  - `src/pipeline/` — declarative flow loading, compilation, validation, runtime, and built-in flow specs
63
63
  - `src/pipeline/nodes/` — reusable runtime nodes used by flow specs
64
64
  - `src/runtime/` — shared runtime services such as command resolution and subprocess execution
65
- - `src/interactive-ui.ts`full-screen interactive UI
65
+ - `src/interactive/`Ink-based interactive session, controller, state, and view-model logic
66
66
  - `src/markdown.ts` — markdown rendering for terminal output
67
67
  - `src/structured-artifact-schemas.json` — schemas for machine-readable artifacts
68
68
  - `tests/` — automated tests for pipeline behavior
@@ -72,7 +72,7 @@ This keeps workflow design in JSON while keeping implementation details in typed
72
72
  User-invokable built-in commands currently map to these flow specs:
73
73
 
74
74
  - `plan` — fetches Jira task with attachments, generates clarifying questions for the developer, collects answers, and produces design, implementation plan, and QA plan as structured JSON and markdown artifacts
75
- - `design-review` — выполняет структурированную критику planning artifacts и пишет dedicated artifact `design-review/v1`; статус `approved_with_warnings` считается ready-to-proceed и по-прежнему допускает `ready-to-merge.md`
75
+ - `design-review` — performs a structured critique of the latest planning artifacts and writes a dedicated `design-review/v1` artifact; `approved_with_warnings` is treated as ready to proceed and may still produce `ready-to-merge.md`
76
76
  - `task-describe` — generates a brief task description from a Jira issue or from manual input; when Jira is provided, fetches the issue and summarizes it; otherwise accepts free-form text and analyzes the codebase to produce a richer description
77
77
  - `implement` — runs LLM-backed implementation based on previously approved design and plan artifacts; executes code changes locally in the project working directory
78
78
  - `review` — performs code review of current changes against the task design and plan; produces structured review findings with severity levels and a ready-to-merge verdict
@@ -87,7 +87,8 @@ User-invokable built-in commands currently map to these flow specs:
87
87
  - `run-go-tests-loop` — runs `run_go_tests.py` and analyzes failures; if tests fail, sends the error output to LLM for a fix and retries; repeats up to 5 attempts, stopping early on success
88
88
  - `run-go-linter-loop` — runs `run_go_linter.py` and analyzes output; if the linter reports issues, sends them to LLM for a fix and retries; repeats up to 5 attempts, stopping early on success
89
89
  - `auto-golang` — end-to-end resumable pipeline for Go projects: plan → implement → linter loop → test loop → review loop → final linter loop → final test loop; supports `--from` to restart from a specific phase and `auto-status`/`auto-reset` for state management
90
- - `auto-common` — end-to-end resumable pipeline without language-specific checks: plan → implement → review loop; simplified alternative to auto-golang for projects that do not need Go linter/test loops
90
+ - `auto-common` — planning-aware pipeline with a mandatory design-review gate before implementation: plan → design-review loop → implement → review loop; design-review can iterate with `plan-revise` up to 3 times, and if the final verdict still requires revision the operator must explicitly choose whether to continue with the latest planning artifacts or stop
91
+ - `auto-simple` — preserved simplified pipeline equivalent to the legacy auto-common behavior: plan → implement → review loop; no planning review gate, suitable for projects that do not need design review before coding
91
92
  - `doctor` — diagnostics command that runs system, executor, and flow readiness health checks; supports filtering by category or check ID and JSON output
92
93
 
93
94
  There are also built-in nested/helper flows that are loaded declaratively but are not direct top-level CLI commands, for example `review-project` (project-level code review used internally when no prior design/plan artifacts are present).
@@ -220,6 +221,7 @@ agentweaver run-go-tests-loop DEMO-1234
220
221
  agentweaver run-go-linter-loop DEMO-1234
221
222
  agentweaver auto-golang DEMO-1234
222
223
  agentweaver auto-common DEMO-1234
224
+ agentweaver auto-simple DEMO-1234
223
225
  agentweaver doctor
224
226
  agentweaver doctor --json
225
227
  agentweaver doctor <category>|<check-id>
@@ -243,6 +245,7 @@ agentweaver --help
243
245
  agentweaver --version
244
246
  agentweaver auto-golang --help-phases
245
247
  agentweaver auto-common --help-phases
248
+ agentweaver auto-simple --help-phases
246
249
  agentweaver auto-golang --from <phase> DEMO-1234
247
250
  agentweaver auto-status DEMO-1234
248
251
  agentweaver auto-reset DEMO-1234
@@ -257,13 +260,13 @@ Notes:
257
260
  - `--prompt <text>` appends extra instructions to the prompt
258
261
  - `--scope <name>` is supported by scope-flexible flows such as `implement`, `review`, `review-fix`, `review-loop`, `run-go-tests-loop`, `run-go-linter-loop`, `gitlab-review`, and `gitlab-diff-review`
259
262
  - `--md-lang <en|ru>` currently applies to `plan`
260
- - `--force` only affects interactive mode and forces regeneration of task summary in Jira-backed flows
263
+ - `--force` only affects interactive mode: it skips loading cached summary-pane content on startup so Jira-backed flows that regenerate summary artifacts can repopulate it during the run
261
264
  - Jira-backed flows ask for Jira input interactively when it is omitted
262
265
  - `task-describe` can also work from manual task description input without Jira
263
266
  - `gitlab-review` and `gitlab-diff-review` ask for a GitLab merge request URL interactively
264
267
  - `auto-status` and `auto-reset` currently operate on persisted state for `auto-golang`
265
268
 
266
- ## `auto-golang` and `auto-common`
269
+ ## `auto-golang`, `auto-common`, and `auto-simple`
267
270
 
268
271
  `auto-golang` is the main resumable end-to-end automation flow. It stores persisted execution state and supports:
269
272
 
@@ -273,7 +276,9 @@ Notes:
273
276
  - reset via `auto-reset`
274
277
  - resume validation against saved launch profile and required artifacts
275
278
 
276
- `auto-common` is a separate built-in automation flow with its own phase list, but it does not currently have dedicated `auto-status` or `auto-reset` commands.
279
+ `auto-common` is the planning-aware built-in automation flow. After `plan`, it runs a `design-review` loop and blocks implementation until the verdict is `approved` or `approved_with_warnings`. When the verdict is `needs_revision`, it runs `plan-revise` and then another `design-review`, for up to 3 design-review iterations total. If the final verdict still requires revision, the pipeline asks the operator whether to continue with the latest planning artifacts or stop before `implement`.
280
+
281
+ `auto-simple` is the preserved simplified pipeline: `plan → implement → review loop`, with no planning review gate and no revise rounds. It is behaviorally equivalent to the legacy `auto-common` before the planning gate was introduced.
277
282
 
278
283
  ## Launch Profiles and Resume
279
284
 
@@ -300,7 +305,12 @@ The runtime uses artifacts as the contract between stages, including markdown ou
300
305
 
301
306
  ## Interactive TUI
302
307
 
303
- Running without a command opens the full-screen TUI. It acts as the operator console for the harness: browsing flows, launching them in scope, following activity, and reviewing summaries.
308
+ Running without a command opens the full-screen TUI. It acts as the operator console for the harness: browsing flows, launching them in scope, following current execution, and reviewing summaries.
309
+
310
+ Interactive mode is Ink-only. It requires:
311
+
312
+ - a real TTY for both stdin and stdout
313
+ - installed runtime dependencies from `npm install`
304
314
 
305
315
  Current navigation:
306
316
 
@@ -308,10 +318,17 @@ Current navigation:
308
318
  - `Left` / `Right` — collapse or expand folders
309
319
  - `Enter` — toggle folder or run selected flow
310
320
  - `Tab` / `Shift+Tab` — switch panes
311
- - `PgUp` / `PgDn` / `Home` / `End` — scroll focused pane
321
+ - `PgUp` / `PgDn` — scroll focused pane
312
322
  - `h` — open help
313
323
  - `q` or `Ctrl+C` — exit
314
324
 
325
+ Current layout:
326
+
327
+ - left column: `Flows`, `Flow Description`, `Status`
328
+ - right column: `Current Flow`, optional `Task Summary`, `Activity`
329
+ - `Current Flow` is intentionally tall and scrollable; in the current layout it uses the same height budget as `Flows`
330
+ - the `Task Summary` pane is runtime-driven and shows whichever markdown artifact the active flow publishes into summary state, such as a normalized task context or a cached task summary
331
+
315
332
  Flow discovery behavior:
316
333
 
317
334
  - built-in flows are loaded from `src/pipeline/flow-specs/`
@@ -0,0 +1,219 @@
1
+ import path from "node:path";
2
+ import { TaskRunnerError } from "./errors.js";
3
+ import { validateStructuredArtifactValue } from "./structured-artifacts.js";
4
+ export const ARTIFACT_MANIFEST_SCHEMA_ID = "artifact-manifest/v1";
5
+ export const ARTIFACT_MANIFEST_SCHEMA_VERSION = 1;
6
+ export const ARTIFACT_LIFECYCLE_STATUSES = ["ready", "superseded", "stale"];
7
+ export const ARTIFACT_PAYLOAD_FAMILIES = [
8
+ "structured-json",
9
+ "markdown",
10
+ "plain-text",
11
+ "helper-json",
12
+ "opaque-file",
13
+ ];
14
+ const STRUCTURED_JSON_SCHEMAS_BY_PREFIX = [
15
+ { prefix: "bug-analyze-", schemaId: "bug-analysis/v1" },
16
+ { prefix: "bug-fix-design-", schemaId: "bug-fix-design/v1" },
17
+ { prefix: "bug-fix-plan-", schemaId: "bug-fix-plan/v1" },
18
+ { prefix: "design-review-", schemaId: "design-review/v1" },
19
+ { prefix: "design-", schemaId: "implementation-design/v1" },
20
+ { prefix: "gitlab-diff-", schemaId: "gitlab-mr-diff/v1" },
21
+ { prefix: "gitlab-review-", schemaId: "gitlab-review/v1" },
22
+ { prefix: "jira-description-", schemaId: "jira-description/v1" },
23
+ { prefix: "mr-description-", schemaId: "mr-description/v1" },
24
+ { prefix: "plan-", schemaId: "implementation-plan/v1" },
25
+ { prefix: "planning-questions-", schemaId: "planning-questions/v1" },
26
+ { prefix: "qa-", schemaId: "qa-plan/v1" },
27
+ { prefix: "review-assessment-", schemaId: "review-assessment/v1" },
28
+ { prefix: "review-fix-", schemaId: "review-fix-report/v1" },
29
+ { prefix: "review-", schemaId: "review-findings/v1" },
30
+ { prefix: "task-context-", schemaId: "task-context/v1" },
31
+ { prefix: "task-", schemaId: "task-summary/v1" },
32
+ ];
33
+ const LOGICAL_KEY_PATTERN = "^[a-z0-9][a-z0-9._/-]*$";
34
+ const CONTENT_HASH_PATTERN = "^sha256:[a-f0-9]{64}$";
35
+ const ARTIFACT_ID_PATTERN = /^(?<scope>.+):(?<logicalKey>[a-z0-9][a-z0-9._/-]*):v(?<version>[1-9]\d*)$/;
36
+ const LOGICAL_REF_PATTERN = /^(?<logicalKey>[a-z0-9][a-z0-9._/-]*)@(?<selector>latest|v[1-9]\d*)$/;
37
+ export function parseSchemaVersion(schemaId) {
38
+ const match = /\/v(\d+)$/.exec(schemaId);
39
+ return match ? Number.parseInt(match[1] ?? "1", 10) : 1;
40
+ }
41
+ function normalizePathSeparators(value) {
42
+ return value.replace(/\\/g, "/");
43
+ }
44
+ function removeScopeSuffix(baseName, scopeKey) {
45
+ if (!scopeKey) {
46
+ return baseName;
47
+ }
48
+ const ext = path.extname(baseName);
49
+ const stem = ext ? baseName.slice(0, -ext.length) : baseName;
50
+ const escapedScope = scopeKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
51
+ const patterns = [
52
+ new RegExp(`-${escapedScope}-iter-\\d+$`),
53
+ new RegExp(`-${escapedScope}-\\d+$`),
54
+ new RegExp(`-${escapedScope}$`),
55
+ ];
56
+ for (const pattern of patterns) {
57
+ if (pattern.test(stem)) {
58
+ return `${stem.replace(pattern, "")}${ext}`;
59
+ }
60
+ }
61
+ if (stem === scopeKey) {
62
+ return `scope${ext}`;
63
+ }
64
+ return baseName;
65
+ }
66
+ export function buildLogicalKeyForPayload(scopeKey, payloadPath) {
67
+ const normalized = normalizePathSeparators(payloadPath);
68
+ const normalizedScopeKey = typeof scopeKey === "string" && scopeKey.trim().length > 0 ? scopeKey : null;
69
+ const scopeMarker = normalizedScopeKey
70
+ ? normalizePathSeparators(`/.agentweaver/scopes/${normalizedScopeKey}/`)
71
+ : null;
72
+ const markerIndex = scopeMarker ? normalized.lastIndexOf(scopeMarker) : -1;
73
+ const artifactsMarker = normalizePathSeparators("/.artifacts/");
74
+ const artifactsIndex = normalized.lastIndexOf(artifactsMarker);
75
+ const relative = markerIndex >= 0 && scopeMarker
76
+ ? normalized.slice(markerIndex + scopeMarker.length)
77
+ : artifactsIndex >= 0
78
+ ? normalized.slice(artifactsIndex + 1)
79
+ : path.basename(normalized);
80
+ const directory = path.posix.dirname(relative);
81
+ const baseName = path.posix.basename(relative);
82
+ const normalizedBaseName = removeScopeSuffix(baseName, normalizedScopeKey)
83
+ .replace(/-\d+(?=\.[^.]+$)/, "")
84
+ .replace(/[^a-zA-Z0-9._/-]+/g, "-")
85
+ .replace(/-+/g, "-")
86
+ .toLowerCase();
87
+ const combined = directory === "." ? normalizedBaseName : `${directory}/${normalizedBaseName}`;
88
+ return combined.replace(/^\.artifacts\//, "artifacts/");
89
+ }
90
+ export function buildPublicationKey(input) {
91
+ return `${input.publicationRunId ?? input.runId}:${input.flowId}:${input.phaseId}:${input.stepId}:${input.logicalKey}`;
92
+ }
93
+ export function buildArtifactId(scopeKey, logicalKey, version) {
94
+ return `${scopeKey}:${logicalKey}:v${version}`;
95
+ }
96
+ export function parseArtifactId(value) {
97
+ const match = ARTIFACT_ID_PATTERN.exec(value);
98
+ if (!match?.groups) {
99
+ return null;
100
+ }
101
+ const scopeKey = match.groups.scope;
102
+ const logicalKey = match.groups.logicalKey;
103
+ const versionText = match.groups.version;
104
+ if (!scopeKey || !logicalKey || !versionText) {
105
+ return null;
106
+ }
107
+ return {
108
+ scopeKey,
109
+ logicalKey,
110
+ version: Number.parseInt(versionText, 10),
111
+ };
112
+ }
113
+ export function parseArtifactReference(value) {
114
+ const parsedId = parseArtifactId(value);
115
+ if (parsedId) {
116
+ return {
117
+ kind: "artifact-id",
118
+ artifactId: value,
119
+ parsedId,
120
+ };
121
+ }
122
+ const match = LOGICAL_REF_PATTERN.exec(value);
123
+ if (!match?.groups) {
124
+ return null;
125
+ }
126
+ const logicalKey = match.groups.logicalKey;
127
+ const selector = match.groups.selector;
128
+ if (!logicalKey || !selector) {
129
+ return null;
130
+ }
131
+ return {
132
+ kind: "logical-ref",
133
+ logicalKey,
134
+ version: selector === "latest" ? "latest" : Number.parseInt(selector.slice(1), 10),
135
+ };
136
+ }
137
+ export function inferPayloadContract(scopeKey, payloadPath, override) {
138
+ if (override?.payloadFamily && override.schemaId && override.schemaVersion) {
139
+ return {
140
+ payloadFamily: override.payloadFamily,
141
+ schemaId: override.schemaId,
142
+ schemaVersion: override.schemaVersion,
143
+ };
144
+ }
145
+ const baseName = path.basename(payloadPath);
146
+ if (override?.schemaId) {
147
+ return {
148
+ payloadFamily: override.payloadFamily ?? "structured-json",
149
+ schemaId: override.schemaId,
150
+ schemaVersion: override.schemaVersion ?? parseSchemaVersion(override.schemaId),
151
+ };
152
+ }
153
+ if (payloadPath.endsWith(".json")) {
154
+ if (baseName === `${scopeKey}.json` || baseName === `${scopeKey}-enriched.json`) {
155
+ return { payloadFamily: "helper-json", schemaId: "helper-json/v1", schemaVersion: 1 };
156
+ }
157
+ if (baseName.startsWith("jira-attachments-")) {
158
+ return { payloadFamily: "helper-json", schemaId: "helper-json/v1", schemaVersion: 1 };
159
+ }
160
+ if (baseName.startsWith("planning-answers-")) {
161
+ return { payloadFamily: "structured-json", schemaId: "user-input/v1", schemaVersion: 1 };
162
+ }
163
+ if (baseName.startsWith("instant-task-input-")) {
164
+ return { payloadFamily: "structured-json", schemaId: "user-input/v1", schemaVersion: 1 };
165
+ }
166
+ for (const candidate of STRUCTURED_JSON_SCHEMAS_BY_PREFIX) {
167
+ if (baseName.startsWith(candidate.prefix)) {
168
+ return {
169
+ payloadFamily: "structured-json",
170
+ schemaId: candidate.schemaId,
171
+ schemaVersion: parseSchemaVersion(candidate.schemaId),
172
+ };
173
+ }
174
+ }
175
+ return { payloadFamily: "helper-json", schemaId: "helper-json/v1", schemaVersion: 1 };
176
+ }
177
+ if (payloadPath.endsWith(".md")) {
178
+ return { payloadFamily: "markdown", schemaId: "markdown/v1", schemaVersion: 1 };
179
+ }
180
+ if (payloadPath.endsWith(".txt")) {
181
+ return { payloadFamily: "plain-text", schemaId: "plain-text/v1", schemaVersion: 1 };
182
+ }
183
+ return { payloadFamily: "opaque-file", schemaId: "opaque-file/v1", schemaVersion: 1 };
184
+ }
185
+ export function createProducerSummary(input) {
186
+ const parts = [input.node];
187
+ if (input.executor) {
188
+ parts.push(`via ${input.executor}`);
189
+ }
190
+ if (input.model) {
191
+ parts.push(`model ${input.model}`);
192
+ }
193
+ return parts.join(" ");
194
+ }
195
+ export function validateArtifactManifest(manifest, label = "$") {
196
+ validateStructuredArtifactValue(manifest, ARTIFACT_MANIFEST_SCHEMA_ID, label);
197
+ if (!new RegExp(LOGICAL_KEY_PATTERN).test(manifest.logical_key)) {
198
+ throw new TaskRunnerError(`Structured artifact ${label} failed schema ${ARTIFACT_MANIFEST_SCHEMA_ID} validation:\n${label}.logical_key must be a string with the expected format`);
199
+ }
200
+ if (!new RegExp(CONTENT_HASH_PATTERN).test(manifest.content_hash)) {
201
+ throw new TaskRunnerError(`Structured artifact ${label} failed schema ${ARTIFACT_MANIFEST_SCHEMA_ID} validation:\n${label}.content_hash must be a string with the expected format`);
202
+ }
203
+ }
204
+ export function diagnosticsForManifest(manifest, validatePayload) {
205
+ try {
206
+ validatePayload(manifest.schema_id, manifest.payload_path);
207
+ return [];
208
+ }
209
+ catch (error) {
210
+ const message = error.message;
211
+ return [
212
+ {
213
+ code: message.includes("not registered") ? "missing-schema" : "invalid-schema",
214
+ severity: "error",
215
+ message,
216
+ },
217
+ ];
218
+ }
219
+ }
package/dist/artifacts.js CHANGED
@@ -24,6 +24,12 @@ export function scopeArtifactsDir(scopeKey) {
24
24
  export function scopeArtifactsFile(scopeKey, fileName) {
25
25
  return path.join(scopeArtifactsDir(scopeKey), fileName);
26
26
  }
27
+ export function artifactManifestSidecarPath(payloadPath) {
28
+ return `${payloadPath}.manifest.json`;
29
+ }
30
+ export function artifactIndexFile(scopeKey) {
31
+ return scopeArtifactsFile(scopeKey, "artifact-index.json");
32
+ }
27
33
  export function taskWorkspaceDir(taskKey) {
28
34
  return scopeWorkspaceDir(taskKey);
29
35
  }
@@ -151,9 +157,18 @@ export function jiraDescriptionFile(taskKey, iteration) {
151
157
  export function jiraDescriptionJsonFile(taskKey, iteration) {
152
158
  return versionedJsonArtifactFile(taskKey, "jira-description", iteration);
153
159
  }
160
+ export function taskContextFile(taskKey, iteration) {
161
+ return versionedMarkdownArtifactFile(taskKey, "task-context", iteration);
162
+ }
163
+ export function taskContextJsonFile(taskKey, iteration) {
164
+ return versionedJsonArtifactFile(taskKey, "task-context", iteration);
165
+ }
154
166
  export function taskDescribeInputJsonFile(taskKey) {
155
167
  return taskArtifactsFile(taskKey, `task-describe-input-${taskKey}.json`);
156
168
  }
169
+ export function instantTaskInputJsonFile(taskKey) {
170
+ return taskArtifactsFile(taskKey, `instant-task-input-${taskKey}.json`);
171
+ }
157
172
  export function gitStatusJsonFile(taskKey) {
158
173
  return taskArtifactsFile(taskKey, `git-status-${taskKey}.json`);
159
174
  }
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { DoctorImpact, DoctorStatus } from "../types.js";
5
5
  import { CATEGORY } from "./category.js";
6
6
  import { detectJiraDeployment } from "../../jira.js";
7
+ import { AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV, parseReviewSeverityCsv, } from "../../review-severity.js";
7
8
  const MONITORED_KEYS = [
8
9
  "JIRA_API_KEY",
9
10
  "JIRA_USERNAME",
@@ -11,6 +12,7 @@ const MONITORED_KEYS = [
11
12
  "JIRA_BASE_URL",
12
13
  "GITLAB_TOKEN",
13
14
  "AGENTWEAVER_HOME",
15
+ "AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES",
14
16
  "CODEX_BIN",
15
17
  "CODEX_MODEL",
16
18
  "OPENCODE_BIN",
@@ -23,6 +25,7 @@ const KEY_NOTES = {
23
25
  JIRA_USERNAME: "Required only for Jira Cloud basic auth.",
24
26
  GITLAB_TOKEN: "Required for GitLab-backed flows.",
25
27
  AGENTWEAVER_HOME: "Optional override for the AgentWeaver package home.",
28
+ AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES: "Optional default list of merge-blocking severities for review and review-fix.",
26
29
  CODEX_BIN: "Optional override for the codex executable path.",
27
30
  CODEX_MODEL: "Optional fallback model override for Codex-backed executors.",
28
31
  OPENCODE_BIN: "Optional override for the opencode executable path.",
@@ -86,6 +89,9 @@ function defaultNote(key) {
86
89
  if (key === "JIRA_AUTH_MODE") {
87
90
  return "Defaults to auto.";
88
91
  }
92
+ if (key === AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV) {
93
+ return "Defaults to blocker,critical,high.";
94
+ }
89
95
  return null;
90
96
  }
91
97
  function validateJiraAuthMode(value) {
@@ -94,6 +100,18 @@ function validateJiraAuthMode(value) {
94
100
  }
95
101
  return JIRA_AUTH_MODE_ALLOWED_VALUES.includes(value.trim().toLowerCase());
96
102
  }
103
+ function validateReviewBlockingSeverities(value) {
104
+ if (value === null) {
105
+ return true;
106
+ }
107
+ try {
108
+ parseReviewSeverityCsv(value);
109
+ return true;
110
+ }
111
+ catch {
112
+ return false;
113
+ }
114
+ }
97
115
  function keyNote(key) {
98
116
  return KEY_NOTES[key];
99
117
  }
@@ -165,6 +183,7 @@ function checkEnvDiagnostics() {
165
183
  const jiraUsername = process.env.JIRA_USERNAME?.trim() || null;
166
184
  const jiraAuthModeRaw = process.env.JIRA_AUTH_MODE?.trim() || null;
167
185
  const jiraBaseUrl = process.env.JIRA_BASE_URL?.trim() || null;
186
+ const reviewBlockingSeveritiesRaw = process.env[AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV]?.trim() || null;
168
187
  for (const key of MONITORED_KEYS) {
169
188
  const currentValue = process.env[key]?.trim() || null;
170
189
  const isSecret = SECRET_KEYS.has(key);
@@ -180,6 +199,9 @@ function checkEnvDiagnostics() {
180
199
  if (key === "JIRA_AUTH_MODE" && currentValue !== null && !validateJiraAuthMode(currentValue)) {
181
200
  state = "invalid";
182
201
  }
202
+ if (key === AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV && currentValue !== null && !validateReviewBlockingSeverities(currentValue)) {
203
+ state = "invalid";
204
+ }
183
205
  const note = state === "defaulted" ? defaultHint ?? undefined : keyNote(key);
184
206
  keyInfos.push({
185
207
  key,
@@ -197,6 +219,9 @@ function checkEnvDiagnostics() {
197
219
  if (jiraAuthModeRaw !== null && !validateJiraAuthMode(jiraAuthModeRaw)) {
198
220
  warnings.push("JIRA_AUTH_MODE must be one of: auto, basic, bearer.");
199
221
  }
222
+ if (reviewBlockingSeveritiesRaw !== null && !validateReviewBlockingSeverities(reviewBlockingSeveritiesRaw)) {
223
+ warnings.push("AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES must be a comma-separated list of: blocker, critical, high, medium, low, info.");
224
+ }
200
225
  if (jiraHasCorePair && jiraBaseUrl) {
201
226
  const authMode = jiraAuthModeRaw?.toLowerCase() || "auto";
202
227
  const isCloud = detectJiraDeployment(jiraBaseUrl) === "cloud";
@@ -4,7 +4,8 @@ import { DoctorImpact, DoctorStatus, WorkflowContinuityState } from "../types.js
4
4
  import { CATEGORY } from "./category.js";
5
5
  import { BUILT_IN_COMMAND_FLOW_IDS } from "../../pipeline/flow-catalog.js";
6
6
  import { validateStructuredArtifact } from "../../structured-artifacts.js";
7
- import { designJsonFile, jiraDescriptionJsonFile, latestArtifactIteration, planJsonFile, } from "../../artifacts.js";
7
+ import { designJsonFile, latestArtifactIteration, planJsonFile, taskContextJsonFile, } from "../../artifacts.js";
8
+ import { inspectLatestPlanningBundle } from "../../runtime/planning-bundle.js";
8
9
  const GO_BINARY_PATTERNS = ["go", "go.exe"];
9
10
  const GIT_BINARY_PATTERNS = ["git", "git.exe"];
10
11
  function statePriority(state) {
@@ -84,19 +85,15 @@ function checkStructuredArtifactFile(entry, artifactPath, schemaId, missingReaso
84
85
  }
85
86
  function checkImplementWorkflowContinuity(scopeKey) {
86
87
  const entry = createEntry("implement");
87
- const latestDesignIteration = getLatestJsonArtifactIteration(scopeKey, "design");
88
- const latestPlanIteration = getLatestJsonArtifactIteration(scopeKey, "plan");
89
- if (latestDesignIteration === null) {
90
- setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing design artifact from the planning stage.");
88
+ const planningBundle = inspectLatestPlanningBundle(scopeKey);
89
+ if (planningBundle.status === "missing") {
90
+ setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, planningBundle.errorMessage);
91
91
  }
92
- else {
93
- checkStructuredArtifactFile(entry, designJsonFile(scopeKey, latestDesignIteration), "implementation-design/v1", "Missing design artifact from the planning stage.", "Design artifact schema is invalid");
92
+ else if (planningBundle.status === "incomplete") {
93
+ setEntryState(entry, WorkflowContinuityState.InvalidState, planningBundle.errorMessage);
94
94
  }
95
- if (latestPlanIteration === null) {
96
- setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing plan artifact from the planning stage.");
97
- }
98
- else {
99
- checkStructuredArtifactFile(entry, planJsonFile(scopeKey, latestPlanIteration), "implementation-plan/v1", "Missing plan artifact from the planning stage.", "Plan artifact schema is invalid");
95
+ else if (planningBundle.status === "invalid") {
96
+ setEntryState(entry, WorkflowContinuityState.InvalidState, planningBundle.errorMessage);
100
97
  }
101
98
  if (entry.state === WorkflowContinuityState.NeedsPreviousStage) {
102
99
  entry.nextStep = "Run plan to generate design, plan, and QA artifacts for this scope.";
@@ -154,18 +151,18 @@ function checkPlanJiraWorkflowContinuity(scopeKey) {
154
151
  }
155
152
  function checkPlanProjectWorkflowContinuity(scopeKey) {
156
153
  const entry = createEntry("plan", "project");
157
- const latestDescriptionIteration = getLatestJsonArtifactIteration(scopeKey, "jira-description");
158
- if (latestDescriptionIteration === null) {
159
- setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing task description artifact from task-describe.");
154
+ const latestTaskContextIteration = getLatestJsonArtifactIteration(scopeKey, "task-context");
155
+ if (latestTaskContextIteration === null) {
156
+ setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing normalized task-context artifact.");
160
157
  }
161
158
  else {
162
- checkStructuredArtifactFile(entry, jiraDescriptionJsonFile(scopeKey, latestDescriptionIteration), "jira-description/v1", "Missing task description artifact from task-describe.", "Task description artifact schema is invalid");
159
+ checkStructuredArtifactFile(entry, taskContextJsonFile(scopeKey, latestTaskContextIteration), "task-context/v1", "Missing normalized task-context artifact.", "Task-context artifact schema is invalid");
163
160
  }
164
161
  if (entry.state === WorkflowContinuityState.NeedsPreviousStage) {
165
- entry.nextStep = "Run task-describe first to create a task description artifact for this scope.";
162
+ entry.nextStep = "Run a source flow that produces task-context for this scope before running plan:project.";
166
163
  }
167
164
  else if (entry.state === WorkflowContinuityState.InvalidState) {
168
- entry.nextStep = "Regenerate the task description artifact before running plan:project.";
165
+ entry.nextStep = "Regenerate the task-context artifact before running plan:project.";
169
166
  }
170
167
  return entry;
171
168
  }
@@ -1,16 +1,27 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
3
  import { ensureScopeWorkspaceDir, flowStateFile } from "./artifacts.js";
3
4
  import { TaskRunnerError } from "./errors.js";
4
- const FLOW_STATE_SCHEMA_VERSION = 1;
5
+ import { isFlowRunResumeEnvelope } from "./pipeline/flow-run-resume.js";
6
+ import { resolveStoredExecutionRoutingSnapshot, singleLaunchProfileExecutionRouting } from "./runtime/execution-routing.js";
7
+ const FLOW_STATE_SCHEMA_VERSION = 2;
5
8
  function nowIso8601() {
6
9
  return new Date().toISOString();
7
10
  }
11
+ function ensurePublicationRunId(executionState) {
12
+ executionState.publicationRunId ??= randomUUID();
13
+ return executionState.publicationRunId;
14
+ }
8
15
  export function stripExecutionStatePayload(executionState) {
16
+ ensurePublicationRunId(executionState);
9
17
  return {
18
+ ...(executionState.runId ? { runId: executionState.runId } : {}),
19
+ ...(executionState.publicationRunId ? { publicationRunId: executionState.publicationRunId } : {}),
10
20
  flowKind: executionState.flowKind,
11
21
  flowVersion: executionState.flowVersion,
12
22
  terminated: executionState.terminated,
13
23
  ...(executionState.terminationReason ? { terminationReason: executionState.terminationReason } : {}),
24
+ ...(executionState.terminationOutcome ? { terminationOutcome: executionState.terminationOutcome } : {}),
14
25
  phases: executionState.phases.map((phase) => ({
15
26
  id: phase.id,
16
27
  status: phase.status,
@@ -22,6 +33,7 @@ export function stripExecutionStatePayload(executionState) {
22
33
  status: step.status,
23
34
  ...(step.outputs ? { outputs: step.outputs } : {}),
24
35
  ...(step.value !== undefined ? { value: step.value } : {}),
36
+ ...(step.publishedArtifacts ? { publishedArtifacts: step.publishedArtifacts } : {}),
25
37
  ...(step.startedAt ? { startedAt: step.startedAt } : {}),
26
38
  ...(step.finishedAt ? { finishedAt: step.finishedAt } : {}),
27
39
  ...(step.stopFlow !== undefined ? { stopFlow: step.stopFlow } : {}),
@@ -29,7 +41,10 @@ export function stripExecutionStatePayload(executionState) {
29
41
  })),
30
42
  };
31
43
  }
32
- export function createFlowRunState(scopeKey, flowId, executionState, jiraRef, launchProfile) {
44
+ export function createFlowRunState(scopeKey, flowId, executionState, jiraRef, launchProfile, executionRouting, selectedRoutingPreset) {
45
+ ensurePublicationRunId(executionState);
46
+ const effectiveExecutionRouting = executionRouting ?? (launchProfile ? singleLaunchProfileExecutionRouting(launchProfile) : undefined);
47
+ const effectiveLaunchProfile = launchProfile ?? effectiveExecutionRouting?.defaultRoute;
33
48
  return {
34
49
  schemaVersion: FLOW_STATE_SCHEMA_VERSION,
35
50
  flowId,
@@ -38,10 +53,52 @@ export function createFlowRunState(scopeKey, flowId, executionState, jiraRef, la
38
53
  status: "pending",
39
54
  currentStep: null,
40
55
  updatedAt: nowIso8601(),
41
- ...(launchProfile ? { launchProfile } : {}),
56
+ ...(effectiveLaunchProfile ? { launchProfile: effectiveLaunchProfile } : {}),
57
+ ...(effectiveExecutionRouting ? { executionRouting: effectiveExecutionRouting, routingFingerprint: effectiveExecutionRouting.fingerprint } : {}),
58
+ ...(selectedRoutingPreset ? { selectedRoutingPreset } : {}),
42
59
  executionState: stripExecutionStatePayload(executionState),
43
60
  };
44
61
  }
62
+ function upgradeFlowRunStateV1(state) {
63
+ const executionRouting = state.launchProfile ? singleLaunchProfileExecutionRouting(state.launchProfile) : undefined;
64
+ return {
65
+ ...state,
66
+ schemaVersion: FLOW_STATE_SCHEMA_VERSION,
67
+ ...(executionRouting ? { executionRouting, routingFingerprint: executionRouting.fingerprint } : {}),
68
+ ...(executionRouting ? { selectedRoutingPreset: { kind: "custom", label: "Legacy launch profile" } } : {}),
69
+ };
70
+ }
71
+ function normalizeFlowRunState(raw, flowId, filePath) {
72
+ if (!raw || typeof raw !== "object") {
73
+ throw new TaskRunnerError(`Invalid flow state file format: ${filePath}`);
74
+ }
75
+ const schemaVersion = raw.schemaVersion;
76
+ let state;
77
+ if (schemaVersion === 1) {
78
+ state = upgradeFlowRunStateV1(raw);
79
+ }
80
+ else if (schemaVersion === FLOW_STATE_SCHEMA_VERSION) {
81
+ state = raw;
82
+ }
83
+ else {
84
+ throw new TaskRunnerError(`Unsupported flow state schema in ${filePath}: ${String(schemaVersion ?? "unknown")}`);
85
+ }
86
+ if (state.flowId !== flowId) {
87
+ throw new TaskRunnerError(`Flow state ${filePath} belongs to flow '${state.flowId}', expected '${flowId}'`);
88
+ }
89
+ if (state.executionRouting) {
90
+ const executionRouting = resolveStoredExecutionRoutingSnapshot(state.executionRouting);
91
+ state.executionRouting = executionRouting;
92
+ state.routingFingerprint = executionRouting.fingerprint;
93
+ state.launchProfile = executionRouting.defaultRoute;
94
+ }
95
+ else if (state.launchProfile) {
96
+ const executionRouting = singleLaunchProfileExecutionRouting(state.launchProfile);
97
+ state.executionRouting = executionRouting;
98
+ state.routingFingerprint = executionRouting.fingerprint;
99
+ }
100
+ return state;
101
+ }
45
102
  export function loadFlowRunState(scopeKey, flowId) {
46
103
  const filePath = flowStateFile(scopeKey, flowId);
47
104
  if (!existsSync(filePath)) {
@@ -54,20 +111,20 @@ export function loadFlowRunState(scopeKey, flowId) {
54
111
  catch (error) {
55
112
  throw new TaskRunnerError(`Failed to parse flow state file ${filePath}: ${error.message}`);
56
113
  }
57
- if (!raw || typeof raw !== "object") {
58
- throw new TaskRunnerError(`Invalid flow state file format: ${filePath}`);
59
- }
60
- const state = raw;
61
- if (state.schemaVersion !== FLOW_STATE_SCHEMA_VERSION) {
62
- throw new TaskRunnerError(`Unsupported flow state schema in ${filePath}: ${state.schemaVersion}`);
63
- }
64
- if (state.flowId !== flowId) {
65
- throw new TaskRunnerError(`Flow state ${filePath} belongs to flow '${state.flowId}', expected '${flowId}'`);
66
- }
67
- return state;
114
+ return normalizeFlowRunState(raw, flowId, filePath);
68
115
  }
69
116
  export function saveFlowRunState(state) {
70
117
  state.updatedAt = nowIso8601();
118
+ state.schemaVersion = FLOW_STATE_SCHEMA_VERSION;
119
+ if (state.executionRouting) {
120
+ state.executionRouting = resolveStoredExecutionRoutingSnapshot(state.executionRouting);
121
+ state.routingFingerprint = state.executionRouting.fingerprint;
122
+ state.launchProfile = state.executionRouting.defaultRoute;
123
+ }
124
+ else if (state.launchProfile) {
125
+ state.executionRouting = singleLaunchProfileExecutionRouting(state.launchProfile);
126
+ state.routingFingerprint = state.executionRouting.fingerprint;
127
+ }
71
128
  ensureScopeWorkspaceDir(state.scopeKey);
72
129
  writeFileSync(flowStateFile(state.scopeKey, state.flowId), `${JSON.stringify({
73
130
  ...state,
@@ -101,10 +158,12 @@ function normalizeStepState(step) {
101
158
  if (step.status !== "running") {
102
159
  return step;
103
160
  }
104
- const { finishedAt: _finishedAt, outputs: _outputs, value: _value, stopFlow: _stopFlow, ...rest } = step;
161
+ const resumeValue = isFlowRunResumeEnvelope(step.value) ? step.value : undefined;
162
+ const { finishedAt: _finishedAt, outputs: _outputs, value: _value, publishedArtifacts: _publishedArtifacts, stopFlow: _stopFlow, ...rest } = step;
105
163
  return {
106
164
  ...rest,
107
165
  status: "pending",
166
+ ...(resumeValue ? { value: resumeValue } : {}),
108
167
  };
109
168
  }
110
169
  function normalizePhaseState(phase) {
@@ -171,6 +230,7 @@ export function prepareFlowStateForResume(state) {
171
230
  state.currentStep = null;
172
231
  state.executionState = {
173
232
  ...state.executionState,
233
+ publicationRunId: randomUUID(),
174
234
  terminated: false,
175
235
  phases: state.executionState.phases.map(normalizePhaseState),
176
236
  };