agentweaver 0.1.14 → 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 (100) hide show
  1. package/README.md +29 -7
  2. package/dist/artifact-manifest.js +219 -0
  3. package/dist/artifacts.js +21 -1
  4. package/dist/doctor/checks/cwd-context.js +4 -3
  5. package/dist/doctor/checks/env-diagnostics.js +193 -71
  6. package/dist/doctor/checks/flow-readiness.js +212 -203
  7. package/dist/doctor/index.js +1 -1
  8. package/dist/doctor/orchestrator.js +18 -7
  9. package/dist/doctor/runner.js +9 -8
  10. package/dist/doctor/types.js +12 -0
  11. package/dist/flow-state.js +75 -15
  12. package/dist/index.js +499 -199
  13. package/dist/interactive/blessed-session.js +361 -0
  14. package/dist/interactive/controller.js +1293 -0
  15. package/dist/interactive/create-interactive-session.js +5 -0
  16. package/dist/interactive/ink/index.js +576 -0
  17. package/dist/interactive/progress.js +245 -0
  18. package/dist/interactive/selectors.js +14 -0
  19. package/dist/interactive/session.js +1 -0
  20. package/dist/interactive/state.js +34 -0
  21. package/dist/interactive/tree.js +155 -0
  22. package/dist/interactive/types.js +1 -0
  23. package/dist/interactive/view-model.js +1 -0
  24. package/dist/interactive-ui.js +159 -194
  25. package/dist/pipeline/context.js +1 -0
  26. package/dist/pipeline/declarative-flow-runner.js +212 -6
  27. package/dist/pipeline/declarative-flows.js +27 -0
  28. package/dist/pipeline/execution-routing-config.js +15 -0
  29. package/dist/pipeline/flow-catalog.js +23 -3
  30. package/dist/pipeline/flow-run-resume.js +29 -0
  31. package/dist/pipeline/flow-specs/auto-common.json +89 -360
  32. package/dist/pipeline/flow-specs/auto-golang.json +58 -363
  33. package/dist/pipeline/flow-specs/auto-simple.json +141 -0
  34. package/dist/pipeline/flow-specs/bugz/bug-analyze.json +2 -0
  35. package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
  36. package/dist/pipeline/flow-specs/design-review/design-review-loop.json +304 -0
  37. package/dist/pipeline/flow-specs/design-review.json +249 -0
  38. package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +11 -0
  39. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +2 -0
  40. package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
  41. package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +2 -0
  42. package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +2 -0
  43. package/dist/pipeline/flow-specs/implement.json +24 -5
  44. package/dist/pipeline/flow-specs/instant-task.json +177 -0
  45. package/dist/pipeline/flow-specs/normalize-task-source.json +311 -0
  46. package/dist/pipeline/flow-specs/plan-revise.json +267 -0
  47. package/dist/pipeline/flow-specs/plan.json +48 -70
  48. package/dist/pipeline/flow-specs/review/review-fix.json +24 -4
  49. package/dist/pipeline/flow-specs/review/review-loop.json +351 -45
  50. package/dist/pipeline/flow-specs/review/review-project-loop.json +590 -0
  51. package/dist/pipeline/flow-specs/review/review-project.json +12 -0
  52. package/dist/pipeline/flow-specs/review/review.json +37 -31
  53. package/dist/pipeline/flow-specs/task-describe.json +62 -2
  54. package/dist/pipeline/flow-specs/task-source/jira-fetch.json +70 -0
  55. package/dist/pipeline/flow-specs/task-source/manual-input.json +216 -0
  56. package/dist/pipeline/node-registry.js +49 -1
  57. package/dist/pipeline/node-runner.js +3 -2
  58. package/dist/pipeline/nodes/build-review-fix-prompt-node.js +5 -1
  59. package/dist/pipeline/nodes/clear-ready-to-merge-node.js +11 -0
  60. package/dist/pipeline/nodes/commit-message-form-node.js +8 -0
  61. package/dist/pipeline/nodes/design-review-verdict-node.js +36 -0
  62. package/dist/pipeline/nodes/ensure-summary-json-node.js +70 -0
  63. package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +19 -2
  64. package/dist/pipeline/nodes/fetch-gitlab-review-node.js +19 -2
  65. package/dist/pipeline/nodes/flow-run-node.js +226 -7
  66. package/dist/pipeline/nodes/git-commit-form-node.js +8 -0
  67. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +19 -2
  68. package/dist/pipeline/nodes/jira-fetch-node.js +50 -4
  69. package/dist/pipeline/nodes/llm-prompt-node.js +32 -12
  70. package/dist/pipeline/nodes/planning-bundle-node.js +10 -0
  71. package/dist/pipeline/nodes/review-verdict-node.js +86 -0
  72. package/dist/pipeline/nodes/select-files-form-node.js +8 -0
  73. package/dist/pipeline/nodes/structured-summary-node.js +24 -0
  74. package/dist/pipeline/nodes/user-input-node.js +38 -3
  75. package/dist/pipeline/nodes/write-selection-file-node.js +20 -4
  76. package/dist/pipeline/prompt-registry.js +5 -1
  77. package/dist/pipeline/prompt-runtime.js +4 -1
  78. package/dist/pipeline/review-iteration.js +26 -0
  79. package/dist/pipeline/spec-compiler.js +2 -0
  80. package/dist/pipeline/spec-types.js +5 -0
  81. package/dist/pipeline/spec-validator.js +14 -0
  82. package/dist/pipeline/value-resolver.js +84 -1
  83. package/dist/prompts.js +82 -13
  84. package/dist/review-severity.js +45 -0
  85. package/dist/runtime/artifact-registry.js +402 -0
  86. package/dist/runtime/design-review-input-contract.js +113 -0
  87. package/dist/runtime/env-loader.js +3 -0
  88. package/dist/runtime/execution-routing-store.js +134 -0
  89. package/dist/runtime/execution-routing.js +227 -0
  90. package/dist/runtime/interactive-execution-routing.js +462 -0
  91. package/dist/runtime/plan-revise-input-contract.js +147 -0
  92. package/dist/runtime/planning-bundle.js +123 -0
  93. package/dist/runtime/ready-to-merge.js +31 -0
  94. package/dist/runtime/review-input-contract.js +100 -0
  95. package/dist/scope.js +11 -2
  96. package/dist/structured-artifact-schema-registry.js +10 -0
  97. package/dist/structured-artifact-schemas.json +257 -1
  98. package/dist/structured-artifacts.js +83 -6
  99. package/dist/user-input.js +70 -3
  100. package/package.json +6 -3
@@ -0,0 +1,36 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { TaskRunnerError } from "../../errors.js";
3
+ import { validateStructuredArtifacts } from "../../structured-artifacts.js";
4
+ import { designReviewJsonFile, latestArtifactIteration } from "../../artifacts.js";
5
+ function readVerdictFile(path) {
6
+ if (!existsSync(path)) {
7
+ throw new TaskRunnerError(`Design review verdict file not found: ${path}`);
8
+ }
9
+ try {
10
+ return JSON.parse(readFileSync(path, "utf8"));
11
+ }
12
+ catch {
13
+ throw new TaskRunnerError(`Failed to parse design review verdict JSON: ${path}`);
14
+ }
15
+ }
16
+ export const designReviewVerdictNode = {
17
+ kind: "design-review-verdict",
18
+ version: 1,
19
+ async run(_context, params) {
20
+ const iteration = params.iteration ?? latestArtifactIteration(params.taskKey, "design-review", "json") ?? 1;
21
+ const jsonPath = designReviewJsonFile(params.taskKey, iteration);
22
+ validateStructuredArtifacts([{ path: jsonPath, schemaId: "design-review/v1" }], "Design review verdict is invalid or missing.");
23
+ const verdict = readVerdictFile(jsonPath);
24
+ const status = (verdict?.status ?? "needs_revision");
25
+ const canProceed = status === "approved" || status === "approved_with_warnings";
26
+ const needsRevision = status === "needs_revision";
27
+ return {
28
+ value: {
29
+ status,
30
+ canProceed,
31
+ needsRevision,
32
+ verdict: verdict?.summary ?? `Design review status: ${status}`,
33
+ },
34
+ };
35
+ },
36
+ };
@@ -0,0 +1,70 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { TaskRunnerError } from "../../errors.js";
4
+ import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
5
+ function toSummaryText(markdown) {
6
+ return markdown
7
+ .replace(/\r\n/g, "\n")
8
+ .replace(/^#{1,6}\s+/gm, "")
9
+ .replace(/^\s*[-*+]\s+/gm, "- ")
10
+ .replace(/`([^`]*)`/g, "$1")
11
+ .replace(/\*\*(.*?)\*\*/g, "$1")
12
+ .replace(/__(.*?)__/g, "$1")
13
+ .replace(/\n{3,}/g, "\n\n")
14
+ .trim();
15
+ }
16
+ function hasValidSummaryArtifact(outputFile) {
17
+ if (!existsSync(outputFile)) {
18
+ return false;
19
+ }
20
+ try {
21
+ const parsed = JSON.parse(readFileSync(outputFile, "utf8"));
22
+ return typeof parsed.summary === "string" && parsed.summary.trim().length > 0;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ export const ensureSummaryJsonNode = {
29
+ kind: "ensure-summary-json",
30
+ version: 1,
31
+ async run(context, params) {
32
+ if (hasValidSummaryArtifact(params.outputFile)) {
33
+ return {
34
+ value: {
35
+ outputFile: params.outputFile,
36
+ created: false,
37
+ repaired: false,
38
+ },
39
+ };
40
+ }
41
+ if (!existsSync(params.markdownFile)) {
42
+ throw new TaskRunnerError(`Cannot create summary JSON ${params.outputFile}: markdown source ${params.markdownFile} was not found.`);
43
+ }
44
+ const summary = toSummaryText(readFileSync(params.markdownFile, "utf8"));
45
+ if (!summary) {
46
+ throw new TaskRunnerError(`Cannot create summary JSON ${params.outputFile}: markdown source ${params.markdownFile} is empty.`);
47
+ }
48
+ mkdirSync(path.dirname(params.outputFile), { recursive: true });
49
+ const repaired = existsSync(params.outputFile);
50
+ writeFileSync(params.outputFile, `${JSON.stringify({ summary }, null, 2)}\n`, "utf8");
51
+ return {
52
+ value: {
53
+ outputFile: params.outputFile,
54
+ created: !repaired,
55
+ repaired,
56
+ },
57
+ outputs: [
58
+ {
59
+ kind: "artifact",
60
+ path: params.outputFile,
61
+ required: true,
62
+ manifest: {
63
+ publish: true,
64
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputFile),
65
+ },
66
+ },
67
+ ],
68
+ };
69
+ },
70
+ };
@@ -1,3 +1,4 @@
1
+ import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
1
2
  import { toExecutorContext } from "../types.js";
2
3
  export const fetchGitLabDiffNode = {
3
4
  kind: "fetch-gitlab-diff",
@@ -12,8 +13,24 @@ export const fetchGitLabDiffNode = {
12
13
  return {
13
14
  value,
14
15
  outputs: [
15
- { kind: "artifact", path: params.outputFile, required: true },
16
- { kind: "artifact", path: params.outputJsonFile, required: true },
16
+ {
17
+ kind: "artifact",
18
+ path: params.outputFile,
19
+ required: true,
20
+ manifest: {
21
+ publish: true,
22
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputFile),
23
+ },
24
+ },
25
+ {
26
+ kind: "artifact",
27
+ path: params.outputJsonFile,
28
+ required: true,
29
+ manifest: {
30
+ publish: true,
31
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputJsonFile),
32
+ },
33
+ },
17
34
  ],
18
35
  };
19
36
  },
@@ -1,3 +1,4 @@
1
+ import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
1
2
  import { toExecutorContext } from "../types.js";
2
3
  export const fetchGitLabReviewNode = {
3
4
  kind: "fetch-gitlab-review",
@@ -12,8 +13,24 @@ export const fetchGitLabReviewNode = {
12
13
  return {
13
14
  value,
14
15
  outputs: [
15
- { kind: "artifact", path: params.outputFile, required: true },
16
- { kind: "artifact", path: params.outputJsonFile, required: true },
16
+ {
17
+ kind: "artifact",
18
+ path: params.outputFile,
19
+ required: true,
20
+ manifest: {
21
+ publish: true,
22
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputFile),
23
+ },
24
+ },
25
+ {
26
+ kind: "artifact",
27
+ path: params.outputJsonFile,
28
+ required: true,
29
+ manifest: {
30
+ publish: true,
31
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputJsonFile),
32
+ },
33
+ },
17
34
  ],
18
35
  };
19
36
  },
@@ -1,6 +1,25 @@
1
1
  import { printInfo } from "../../tui.js";
2
+ import { resolveDesignReviewInputContract } from "../../runtime/design-review-input-contract.js";
3
+ import { resolvePlanReviseInputContract } from "../../runtime/plan-revise-input-contract.js";
4
+ import { inspectReviewInputContract } from "../../runtime/review-input-contract.js";
2
5
  import { runExpandedPhase } from "../declarative-flow-runner.js";
3
6
  import { loadNamedDeclarativeFlow } from "../declarative-flows.js";
7
+ import { isFlowRunResumeEnvelope } from "../flow-run-resume.js";
8
+ import { withCanonicalReviewLoopParams } from "../review-iteration.js";
9
+ import { ARTIFACT_LINEAGE_REF_PATHS_PARAM } from "../value-resolver.js";
10
+ function withArtifactLineageRefPaths(params, lineageRefs) {
11
+ if (Object.keys(lineageRefs).length === 0) {
12
+ return params;
13
+ }
14
+ const existing = params[ARTIFACT_LINEAGE_REF_PATHS_PARAM];
15
+ const merged = existing && typeof existing === "object" && !Array.isArray(existing)
16
+ ? { ...existing, ...lineageRefs }
17
+ : lineageRefs;
18
+ return {
19
+ ...params,
20
+ [ARTIFACT_LINEAGE_REF_PATHS_PARAM]: merged,
21
+ };
22
+ }
4
23
  export const flowRunNode = {
5
24
  kind: "flow-run",
6
25
  version: 1,
@@ -13,28 +32,228 @@ export const flowRunNode = {
13
32
  printInfo(String(labelText));
14
33
  }
15
34
  const flow = loadNamedDeclarativeFlow(fileName, context.cwd);
16
- const executionState = {
35
+ let resolvedFlowParams = withCanonicalReviewLoopParams(flow.kind, flowParams);
36
+ if (flow.kind === "design-review-flow") {
37
+ const taskKey = String(flowParams["taskKey"] ?? "");
38
+ if (taskKey) {
39
+ const contract = resolveDesignReviewInputContract(taskKey);
40
+ resolvedFlowParams = withArtifactLineageRefPaths({
41
+ ...flowParams,
42
+ iteration: contract.planningIteration,
43
+ planningIteration: contract.planningIteration,
44
+ designFile: contract.designFile,
45
+ designJsonFile: contract.designJsonFile,
46
+ planFile: contract.planFile,
47
+ planJsonFile: contract.planJsonFile,
48
+ hasQaArtifacts: contract.hasQaArtifacts,
49
+ qaFilePath: contract.qaFilePath,
50
+ qaJsonFilePath: contract.qaJsonFilePath,
51
+ qaFile: contract.qaFile,
52
+ qaJsonFile: contract.qaJsonFile,
53
+ hasTaskContextJsonFile: contract.hasTaskContextJsonFile,
54
+ taskContextJsonFilePath: contract.taskContextJsonFilePath,
55
+ taskContextJsonFile: contract.taskContextJsonFile,
56
+ hasJiraTaskFile: contract.hasJiraTaskFile,
57
+ jiraTaskFilePath: contract.jiraTaskFilePath,
58
+ jiraTaskFile: contract.jiraTaskFile,
59
+ hasJiraAttachmentsManifestFile: contract.hasJiraAttachmentsManifestFile,
60
+ jiraAttachmentsManifestFilePath: contract.jiraAttachmentsManifestFilePath,
61
+ jiraAttachmentsManifestFile: contract.jiraAttachmentsManifestFile,
62
+ hasJiraAttachmentsContextFile: contract.hasJiraAttachmentsContextFile,
63
+ jiraAttachmentsContextFilePath: contract.jiraAttachmentsContextFilePath,
64
+ jiraAttachmentsContextFile: contract.jiraAttachmentsContextFile,
65
+ hasPlanningAnswersJsonFile: contract.hasPlanningAnswersJsonFile,
66
+ planningAnswersJsonFilePath: contract.planningAnswersJsonFilePath,
67
+ planningAnswersJsonFile: contract.planningAnswersJsonFile,
68
+ hasTaskInputJsonFile: contract.hasTaskInputJsonFile,
69
+ taskInputJsonFilePath: contract.taskInputJsonFilePath,
70
+ taskInputJsonFile: contract.taskInputJsonFile,
71
+ }, {
72
+ "params.designFile": contract.designFile,
73
+ "params.designJsonFile": contract.designJsonFile,
74
+ "params.planFile": contract.planFile,
75
+ "params.planJsonFile": contract.planJsonFile,
76
+ ...(contract.qaFilePath ? { "params.qaFile": contract.qaFilePath } : {}),
77
+ ...(contract.qaJsonFilePath ? { "params.qaJsonFile": contract.qaJsonFilePath } : {}),
78
+ ...(contract.taskContextJsonFilePath
79
+ ? { "params.taskContextJsonFile": contract.taskContextJsonFilePath }
80
+ : {}),
81
+ ...(contract.jiraTaskFilePath ? { "params.jiraTaskFile": contract.jiraTaskFilePath } : {}),
82
+ ...(contract.jiraAttachmentsManifestFilePath
83
+ ? { "params.jiraAttachmentsManifestFile": contract.jiraAttachmentsManifestFilePath }
84
+ : {}),
85
+ ...(contract.jiraAttachmentsContextFilePath
86
+ ? { "params.jiraAttachmentsContextFile": contract.jiraAttachmentsContextFilePath }
87
+ : {}),
88
+ ...(contract.planningAnswersJsonFilePath
89
+ ? { "params.planningAnswersJsonFile": contract.planningAnswersJsonFilePath }
90
+ : {}),
91
+ ...(contract.taskInputJsonFilePath
92
+ ? { "params.taskInputJsonFile": contract.taskInputJsonFilePath }
93
+ : {}),
94
+ });
95
+ }
96
+ }
97
+ else if (flow.kind === "plan-revise-flow") {
98
+ const taskKey = String(flowParams["taskKey"] ?? "");
99
+ if (taskKey) {
100
+ const contract = resolvePlanReviseInputContract(taskKey);
101
+ resolvedFlowParams = withArtifactLineageRefPaths({
102
+ ...flowParams,
103
+ reviewIteration: contract.reviewIteration,
104
+ reviewFile: contract.reviewFile,
105
+ reviewJsonFile: contract.reviewJsonFile,
106
+ sourcePlanningIteration: contract.sourcePlanningIteration,
107
+ outputIteration: contract.outputIteration,
108
+ designFile: contract.designFile,
109
+ designJsonFile: contract.designJsonFile,
110
+ planFile: contract.planFile,
111
+ planJsonFile: contract.planJsonFile,
112
+ hasQaArtifacts: contract.hasQaArtifacts,
113
+ qaFilePath: contract.qaFilePath,
114
+ qaJsonFilePath: contract.qaJsonFilePath,
115
+ qaFile: contract.qaFile,
116
+ qaJsonFile: contract.qaJsonFile,
117
+ revisedDesignFile: contract.revisedDesignFile,
118
+ revisedDesignJsonFile: contract.revisedDesignJsonFile,
119
+ revisedPlanFile: contract.revisedPlanFile,
120
+ revisedPlanJsonFile: contract.revisedPlanJsonFile,
121
+ revisedQaFile: contract.revisedQaFile,
122
+ revisedQaJsonFile: contract.revisedQaJsonFile,
123
+ hasTaskContextJsonFile: contract.hasTaskContextJsonFile,
124
+ taskContextJsonFilePath: contract.taskContextJsonFilePath,
125
+ taskContextJsonFile: contract.taskContextJsonFile,
126
+ hasJiraTaskFile: contract.hasJiraTaskFile,
127
+ jiraTaskFilePath: contract.jiraTaskFilePath,
128
+ jiraTaskFile: contract.jiraTaskFile,
129
+ hasJiraAttachmentsManifestFile: contract.hasJiraAttachmentsManifestFile,
130
+ jiraAttachmentsManifestFilePath: contract.jiraAttachmentsManifestFilePath,
131
+ jiraAttachmentsManifestFile: contract.jiraAttachmentsManifestFile,
132
+ hasJiraAttachmentsContextFile: contract.hasJiraAttachmentsContextFile,
133
+ jiraAttachmentsContextFilePath: contract.jiraAttachmentsContextFilePath,
134
+ jiraAttachmentsContextFile: contract.jiraAttachmentsContextFile,
135
+ hasPlanningAnswersJsonFile: contract.hasPlanningAnswersJsonFile,
136
+ planningAnswersJsonFilePath: contract.planningAnswersJsonFilePath,
137
+ planningAnswersJsonFile: contract.planningAnswersJsonFile,
138
+ hasTaskInputJsonFile: contract.hasTaskInputJsonFile,
139
+ taskInputJsonFilePath: contract.taskInputJsonFilePath,
140
+ taskInputJsonFile: contract.taskInputJsonFile,
141
+ }, {
142
+ "params.reviewFile": contract.reviewFile,
143
+ "params.reviewJsonFile": contract.reviewJsonFile,
144
+ "params.designFile": contract.designFile,
145
+ "params.designJsonFile": contract.designJsonFile,
146
+ "params.planFile": contract.planFile,
147
+ "params.planJsonFile": contract.planJsonFile,
148
+ ...(contract.qaFilePath ? { "params.qaFile": contract.qaFilePath } : {}),
149
+ ...(contract.qaJsonFilePath ? { "params.qaJsonFile": contract.qaJsonFilePath } : {}),
150
+ ...(contract.taskContextJsonFilePath
151
+ ? { "params.taskContextJsonFile": contract.taskContextJsonFilePath }
152
+ : {}),
153
+ ...(contract.jiraTaskFilePath ? { "params.jiraTaskFile": contract.jiraTaskFilePath } : {}),
154
+ ...(contract.jiraAttachmentsManifestFilePath
155
+ ? { "params.jiraAttachmentsManifestFile": contract.jiraAttachmentsManifestFilePath }
156
+ : {}),
157
+ ...(contract.jiraAttachmentsContextFilePath
158
+ ? { "params.jiraAttachmentsContextFile": contract.jiraAttachmentsContextFilePath }
159
+ : {}),
160
+ ...(contract.planningAnswersJsonFilePath
161
+ ? { "params.planningAnswersJsonFile": contract.planningAnswersJsonFilePath }
162
+ : {}),
163
+ ...(contract.taskInputJsonFilePath
164
+ ? { "params.taskInputJsonFile": contract.taskInputJsonFilePath }
165
+ : {}),
166
+ });
167
+ }
168
+ }
169
+ else if (flow.kind === "review-flow") {
170
+ const taskKey = String(flowParams["taskKey"] ?? "");
171
+ if (taskKey) {
172
+ const inspection = inspectReviewInputContract(taskKey);
173
+ if (inspection.status === "ready") {
174
+ const { contract } = inspection;
175
+ resolvedFlowParams = withArtifactLineageRefPaths({
176
+ ...flowParams,
177
+ planningIteration: contract.planningIteration,
178
+ designFile: contract.designFile,
179
+ designJsonFile: contract.designJsonFile,
180
+ planFile: contract.planFile,
181
+ planJsonFile: contract.planJsonFile,
182
+ hasTaskContextJsonFile: contract.hasTaskContextJsonFile,
183
+ taskContextJsonFilePath: contract.taskContextJsonFilePath,
184
+ taskContextJsonFile: contract.taskContextJsonFile,
185
+ hasJiraTaskFile: contract.hasJiraTaskFile,
186
+ jiraTaskFilePath: contract.jiraTaskFilePath,
187
+ jiraTaskFile: contract.jiraTaskFile,
188
+ hasTaskInputJsonFile: contract.hasTaskInputJsonFile,
189
+ taskInputJsonFilePath: contract.taskInputJsonFilePath,
190
+ taskInputJsonFile: contract.taskInputJsonFile,
191
+ }, {
192
+ "params.designFile": contract.designFile,
193
+ "params.designJsonFile": contract.designJsonFile,
194
+ "params.planFile": contract.planFile,
195
+ "params.planJsonFile": contract.planJsonFile,
196
+ ...(contract.taskContextJsonFilePath
197
+ ? { "params.taskContextJsonFile": contract.taskContextJsonFilePath }
198
+ : {}),
199
+ ...(contract.jiraTaskFilePath ? { "params.jiraTaskFile": contract.jiraTaskFilePath } : {}),
200
+ ...(contract.taskInputJsonFilePath
201
+ ? { "params.taskInputJsonFile": contract.taskInputJsonFilePath }
202
+ : {}),
203
+ });
204
+ }
205
+ }
206
+ }
207
+ const resumeValue = isFlowRunResumeEnvelope(context.resumeStepValue)
208
+ && context.resumeStepValue.flowKind === flow.kind
209
+ && context.resumeStepValue.flowVersion === flow.version
210
+ ? context.resumeStepValue
211
+ : null;
212
+ const executionState = resumeValue?.executionState ?? {
17
213
  flowKind: flow.kind,
18
214
  flowVersion: flow.version,
19
215
  terminated: false,
216
+ terminationOutcome: "success",
20
217
  phases: [],
21
218
  };
219
+ const buildResumeEnvelope = (nextExecutionState) => ({
220
+ resumeKind: "flow-run",
221
+ flowKind: flow.kind,
222
+ flowVersion: flow.version,
223
+ executionState: nextExecutionState,
224
+ publishedArtifacts: collectPublishedArtifacts(nextExecutionState),
225
+ });
22
226
  for (const phase of flow.phases) {
23
- await runExpandedPhase(phase, context, flowParams, flow.constants, {
227
+ await runExpandedPhase(phase, context, resolvedFlowParams, flow.constants, {
24
228
  executionState,
25
229
  flowKind: flow.kind,
26
230
  flowVersion: flow.version,
231
+ onStateChange: async (nextExecutionState) => {
232
+ await context.persistRunningStepValue?.(buildResumeEnvelope(nextExecutionState));
233
+ },
27
234
  });
28
235
  if (executionState.terminated) {
29
236
  break;
30
237
  }
31
238
  }
32
239
  return {
33
- value: {
34
- flowKind: flow.kind,
35
- flowVersion: flow.version,
36
- executionState,
37
- },
240
+ value: buildResumeEnvelope(executionState),
38
241
  };
39
242
  },
40
243
  };
244
+ function collectPublishedArtifacts(executionState) {
245
+ const merged = [];
246
+ const seen = new Set();
247
+ for (const phase of executionState.phases) {
248
+ for (const step of phase.steps) {
249
+ for (const artifact of step.publishedArtifacts ?? []) {
250
+ if (seen.has(artifact.artifact_id)) {
251
+ continue;
252
+ }
253
+ seen.add(artifact.artifact_id);
254
+ merged.push(artifact);
255
+ }
256
+ }
257
+ }
258
+ return merged;
259
+ }
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
2
  import path from "node:path";
3
+ import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
3
4
  import { TaskRunnerError } from "../../errors.js";
4
5
  import { validateUserInputValues } from "../../user-input.js";
5
6
  function parseCommitMessage(content) {
@@ -131,6 +132,13 @@ export const gitCommitFormNode = {
131
132
  kind: "artifact",
132
133
  path: params.outputFile,
133
134
  required: true,
135
+ manifest: {
136
+ publish: true,
137
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputFile),
138
+ payloadFamily: "structured-json",
139
+ schemaId: "user-input/v1",
140
+ schemaVersion: 1,
141
+ },
134
142
  },
135
143
  ],
136
144
  };
@@ -1,5 +1,6 @@
1
1
  import { writeFileSync } from "node:fs";
2
2
  import { readFileSync } from "node:fs";
3
+ import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
3
4
  import { TaskRunnerError } from "../../errors.js";
4
5
  function normalizeMarkdownLanguage(mdLang) {
5
6
  return mdLang === "en" ? "en" : "ru";
@@ -101,8 +102,24 @@ export const gitlabReviewArtifactsNode = {
101
102
  readyToMerge: artifact.ready_to_merge,
102
103
  },
103
104
  outputs: [
104
- { kind: "artifact", path: params.reviewFile, required: true },
105
- { kind: "artifact", path: params.reviewJsonFile, required: true },
105
+ {
106
+ kind: "artifact",
107
+ path: params.reviewFile,
108
+ required: true,
109
+ manifest: {
110
+ publish: true,
111
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.reviewFile),
112
+ },
113
+ },
114
+ {
115
+ kind: "artifact",
116
+ path: params.reviewJsonFile,
117
+ required: true,
118
+ manifest: {
119
+ publish: true,
120
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.reviewJsonFile),
121
+ },
122
+ },
106
123
  ],
107
124
  };
108
125
  },
@@ -10,15 +10,61 @@ export const jiraFetchNode = {
10
10
  ...(params.attachmentsManifestFile ? { attachmentsManifestFile: params.attachmentsManifestFile } : {}),
11
11
  ...(params.attachmentsContextFile ? { attachmentsContextFile: params.attachmentsContextFile } : {}),
12
12
  }, executor.defaultConfig);
13
- const outputs = [{ kind: "file", path: params.outputFile, required: true }];
13
+ const outputs = [
14
+ {
15
+ kind: "file",
16
+ path: params.outputFile,
17
+ required: true,
18
+ manifest: {
19
+ publish: true,
20
+ logicalKey: "artifacts/jira-task.json",
21
+ payloadFamily: "helper-json",
22
+ schemaId: "helper-json/v1",
23
+ schemaVersion: 1,
24
+ },
25
+ },
26
+ ];
14
27
  if (params.attachmentsManifestFile) {
15
- outputs.push({ kind: "artifact", path: params.attachmentsManifestFile, required: true });
28
+ outputs.push({
29
+ kind: "artifact",
30
+ path: params.attachmentsManifestFile,
31
+ required: true,
32
+ manifest: {
33
+ publish: true,
34
+ logicalKey: "artifacts/jira-attachments.json",
35
+ payloadFamily: "helper-json",
36
+ schemaId: "helper-json/v1",
37
+ schemaVersion: 1,
38
+ },
39
+ });
16
40
  }
17
41
  if (params.attachmentsContextFile) {
18
- outputs.push({ kind: "artifact", path: params.attachmentsContextFile, required: true });
42
+ outputs.push({
43
+ kind: "artifact",
44
+ path: params.attachmentsContextFile,
45
+ required: true,
46
+ manifest: {
47
+ publish: true,
48
+ logicalKey: "jira-attachments-context.txt",
49
+ payloadFamily: "plain-text",
50
+ schemaId: "plain-text/v1",
51
+ schemaVersion: 1,
52
+ },
53
+ });
19
54
  }
20
55
  if (value.enrichedFile) {
21
- outputs.push({ kind: "artifact", path: value.enrichedFile, required: false });
56
+ outputs.push({
57
+ kind: "artifact",
58
+ path: value.enrichedFile,
59
+ required: false,
60
+ manifest: {
61
+ publish: true,
62
+ logicalKey: "artifacts/jira-task-enriched.json",
63
+ payloadFamily: "helper-json",
64
+ schemaId: "helper-json/v1",
65
+ schemaVersion: 1,
66
+ },
67
+ });
22
68
  }
23
69
  return {
24
70
  value,
@@ -2,24 +2,44 @@ import { TaskRunnerError } from "../../errors.js";
2
2
  import { printInfo, printPrompt } from "../../tui.js";
3
3
  import { isAllowedModelForExecutor, isLlmExecutorId, } from "../launch-profile-config.js";
4
4
  import { toExecutorContext } from "../types.js";
5
+ function outputsForArtifacts(requiredArtifacts) {
6
+ return Array.from(new Set(requiredArtifacts ?? [])).map((outputPath) => ({
7
+ kind: "artifact",
8
+ path: outputPath,
9
+ required: true,
10
+ manifest: {
11
+ publish: true,
12
+ },
13
+ }));
14
+ }
5
15
  export const llmPromptNode = {
6
16
  kind: "llm-prompt",
7
17
  version: 1,
8
18
  async run(context, params) {
9
- if (!isLlmExecutorId(params.executor)) {
10
- throw new TaskRunnerError(`Unsupported llm executor '${params.executor}'.`);
19
+ const routedProfile = params.routingGroup
20
+ ? context.executionRouting?.groups[params.routingGroup]
21
+ : undefined;
22
+ const fallbackProfile = context.executionRouting?.defaultRoute;
23
+ const executor = params.routingGroup
24
+ ? routedProfile?.executor ?? params.executor ?? fallbackProfile?.executor
25
+ : params.executor ?? fallbackProfile?.executor;
26
+ const model = params.routingGroup
27
+ ? routedProfile?.model ?? params.model ?? fallbackProfile?.model
28
+ : params.model ?? fallbackProfile?.model;
29
+ if (!executor || !isLlmExecutorId(executor)) {
30
+ throw new TaskRunnerError(`Unsupported llm executor '${String(executor ?? params.executor ?? "undefined")}'.`);
11
31
  }
12
- if (params.model && !isAllowedModelForExecutor(params.executor, params.model)) {
13
- throw new TaskRunnerError(`Model '${params.model}' is not allowed for executor '${params.executor}'.`);
32
+ if (model && !isAllowedModelForExecutor(executor, model)) {
33
+ throw new TaskRunnerError(`Model '${model}' is not allowed for executor '${executor}'.`);
14
34
  }
15
35
  printInfo(params.labelText);
16
- printPrompt(`LLM:${params.executor}`, params.prompt);
36
+ printPrompt(`LLM:${executor}`, params.prompt);
17
37
  const executorContext = toExecutorContext(context);
18
- if (params.executor === "codex") {
38
+ if (executor === "codex") {
19
39
  const executor = context.executors.get("codex");
20
40
  const value = await executor.execute(executorContext, {
21
41
  prompt: params.prompt,
22
- ...(params.model ? { model: params.model } : {}),
42
+ ...(model ? { model } : {}),
23
43
  env: { ...context.env },
24
44
  }, executor.defaultConfig);
25
45
  return {
@@ -27,14 +47,14 @@ export const llmPromptNode = {
27
47
  ...value,
28
48
  executor: "codex",
29
49
  },
30
- outputs: (params.requiredArtifacts ?? []).map((path) => ({ kind: "artifact", path, required: true })),
50
+ outputs: outputsForArtifacts(params.requiredArtifacts),
31
51
  };
32
52
  }
33
- if (params.executor === "opencode") {
53
+ if (executor === "opencode") {
34
54
  const executor = context.executors.get("opencode");
35
55
  const value = await executor.execute(executorContext, {
36
56
  prompt: params.prompt,
37
- ...(params.model ? { model: params.model } : {}),
57
+ ...(model ? { model } : {}),
38
58
  env: { ...context.env },
39
59
  }, executor.defaultConfig);
40
60
  return {
@@ -42,10 +62,10 @@ export const llmPromptNode = {
42
62
  ...value,
43
63
  executor: "opencode",
44
64
  },
45
- outputs: (params.requiredArtifacts ?? []).map((path) => ({ kind: "artifact", path, required: true })),
65
+ outputs: outputsForArtifacts(params.requiredArtifacts),
46
66
  };
47
67
  }
48
- throw new TaskRunnerError(`Unsupported llm executor '${params.executor}'.`);
68
+ throw new TaskRunnerError(`Unsupported llm executor '${executor}'.`);
49
69
  },
50
70
  checks(_context, params) {
51
71
  if (!params.requiredArtifacts || params.requiredArtifacts.length === 0) {
@@ -0,0 +1,10 @@
1
+ import { resolveLatestPlanningBundle } from "../../runtime/planning-bundle.js";
2
+ export const planningBundleNode = {
3
+ kind: "planning-bundle",
4
+ version: 1,
5
+ async run(_context, params) {
6
+ return {
7
+ value: resolveLatestPlanningBundle(params.taskKey),
8
+ };
9
+ },
10
+ };