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,86 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { reviewJsonFile, latestArtifactIteration, readyToMergeFile } from "../../artifacts.js";
3
+ import { TaskRunnerError } from "../../errors.js";
4
+ import { resolveBlockingReviewSeverities, normalizeReviewSeverity } from "../../review-severity.js";
5
+ import { validateStructuredArtifacts } from "../../structured-artifacts.js";
6
+ import { clearReadyToMergeFile, writeReadyToMergeFile } from "../../runtime/ready-to-merge.js";
7
+ function readReviewVerdictFile(path) {
8
+ if (!existsSync(path)) {
9
+ throw new TaskRunnerError(`Review findings file not found: ${path}`);
10
+ }
11
+ try {
12
+ return JSON.parse(readFileSync(path, "utf8"));
13
+ }
14
+ catch {
15
+ throw new TaskRunnerError(`Failed to parse review findings JSON: ${path}`);
16
+ }
17
+ }
18
+ export const reviewVerdictNode = {
19
+ kind: "review-verdict",
20
+ version: 1,
21
+ async run(context, params) {
22
+ const iteration = params.iteration ?? latestArtifactIteration(params.taskKey, "review", "json") ?? 1;
23
+ const jsonPath = reviewJsonFile(params.taskKey, iteration);
24
+ validateStructuredArtifacts([{ path: jsonPath, schemaId: "review-findings/v1" }], "Review findings are invalid or missing.");
25
+ const report = readReviewVerdictFile(jsonPath);
26
+ const findings = Array.isArray(report.findings) ? report.findings : [];
27
+ const blockingSeverities = resolveBlockingReviewSeverities(params.blockingSeverities);
28
+ const blockingSeveritySet = new Set(blockingSeverities);
29
+ const blockingFindingTitles = findings
30
+ .filter((finding) => {
31
+ const severity = normalizeReviewSeverity(finding.severity);
32
+ return severity !== null && blockingSeveritySet.has(severity);
33
+ })
34
+ .map((finding) => (typeof finding.title === "string" ? finding.title.trim() : ""))
35
+ .filter((title) => title.length > 0);
36
+ const readyToMerge = blockingFindingTitles.length === 0;
37
+ const summary = typeof report.summary === "string" && report.summary.trim().length > 0
38
+ ? report.summary.trim()
39
+ : readyToMerge
40
+ ? "No blocking findings."
41
+ : `Blocking findings: ${blockingFindingTitles.join(", ")}`;
42
+ const normalizedReport = {
43
+ ...report,
44
+ ready_to_merge: readyToMerge,
45
+ };
46
+ writeFileSync(jsonPath, `${JSON.stringify(normalizedReport, null, 2)}\n`, "utf8");
47
+ if (readyToMerge) {
48
+ writeReadyToMergeFile(params.taskKey, {
49
+ ...(context.mdLang !== undefined ? { mdLang: context.mdLang } : {}),
50
+ summary,
51
+ });
52
+ }
53
+ else {
54
+ clearReadyToMergeFile(params.taskKey);
55
+ }
56
+ return {
57
+ value: {
58
+ readyToMerge,
59
+ blockingSeverities,
60
+ blockingFindingTitles,
61
+ blockingFindingsCount: blockingFindingTitles.length,
62
+ summary,
63
+ reviewJsonFile: jsonPath,
64
+ },
65
+ outputs: [
66
+ {
67
+ kind: "artifact",
68
+ path: jsonPath,
69
+ required: true,
70
+ manifest: {
71
+ publish: true,
72
+ schemaId: "review-findings/v1",
73
+ },
74
+ },
75
+ {
76
+ kind: "file",
77
+ path: readyToMergeFile(params.taskKey),
78
+ required: false,
79
+ manifest: {
80
+ publish: true,
81
+ },
82
+ },
83
+ ],
84
+ };
85
+ },
86
+ };
@@ -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
  export const selectFilesFormNode = {
@@ -65,6 +66,13 @@ export const selectFilesFormNode = {
65
66
  kind: "artifact",
66
67
  path: params.outputFile,
67
68
  required: true,
69
+ manifest: {
70
+ publish: true,
71
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputFile),
72
+ payloadFamily: "structured-json",
73
+ schemaId: "user-input/v1",
74
+ schemaVersion: 1,
75
+ },
68
76
  },
69
77
  ],
70
78
  };
@@ -0,0 +1,24 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { TaskRunnerError } from "../../errors.js";
3
+ export const structuredSummaryNode = {
4
+ kind: "structured-summary",
5
+ version: 1,
6
+ async run(_context, params) {
7
+ let parsed;
8
+ try {
9
+ parsed = JSON.parse(readFileSync(params.path, "utf8"));
10
+ }
11
+ catch (error) {
12
+ throw new TaskRunnerError(`Structured summary node could not parse JSON from ${params.path}: ${error instanceof Error ? error.message : String(error)}`);
13
+ }
14
+ const summary = typeof parsed.summary === "string" ? parsed.summary.trim() : "";
15
+ if (!summary) {
16
+ throw new TaskRunnerError(`Structured summary node did not find a non-empty summary in ${params.path}.`);
17
+ }
18
+ return {
19
+ value: {
20
+ summary,
21
+ },
22
+ };
23
+ },
24
+ };
@@ -1,7 +1,8 @@
1
1
  import { writeFileSync } from "node:fs";
2
2
  import { TaskRunnerError } from "../../errors.js";
3
+ import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
3
4
  import { printSummary } from "../../tui.js";
4
- import { requestUserInputInTerminal, validateUserInputValues, } from "../../user-input.js";
5
+ import { applyInitialUserInputValues, requestUserInputInTerminal, validateUserInputValues, } from "../../user-input.js";
5
6
  function labelForSingleValue(field, value) {
6
7
  if (field.type !== "single-select" && field.type !== "multi-select") {
7
8
  return value;
@@ -64,6 +65,23 @@ function buildTaskDescribePromptSuffix(params, values) {
64
65
  : `Task source: user-input\n\n${taskDescription}`,
65
66
  };
66
67
  }
68
+ function buildInstantTaskPromptSuffix(params, values) {
69
+ const taskDescription = typeof values.task_description === "string" ? values.task_description.trim() : "";
70
+ const additionalInstructions = typeof values.additional_instructions === "string" ? values.additional_instructions.trim() : "";
71
+ return {
72
+ promptSuffix: [
73
+ "Use the manual instant-task request below as the source of truth for task intent.",
74
+ `User input file: ${params.outputFile}`,
75
+ `Task description:\n${taskDescription}`,
76
+ additionalInstructions ? `Additional instructions:\n${additionalInstructions}` : "",
77
+ ]
78
+ .filter((item) => item.trim().length > 0)
79
+ .join("\n\n"),
80
+ summaryText: additionalInstructions
81
+ ? `Task source: instant-task\n\n${taskDescription}\n\nAdditional instructions:\n${additionalInstructions}`
82
+ : `Task source: instant-task\n\n${taskDescription}`,
83
+ };
84
+ }
67
85
  function buildPromptSuffix(params, values) {
68
86
  if (params.formId === "review-fix-selection") {
69
87
  return buildReviewFixPromptSuffix(params, values);
@@ -71,6 +89,9 @@ function buildPromptSuffix(params, values) {
71
89
  if (params.formId === "task-describe-source-input") {
72
90
  return buildTaskDescribePromptSuffix(params, values);
73
91
  }
92
+ if (params.formId === "instant-task-input") {
93
+ return buildInstantTaskPromptSuffix(params, values);
94
+ }
74
95
  if (params.fields.length === 0) {
75
96
  return {
76
97
  promptSuffix: "",
@@ -101,12 +122,13 @@ export const userInputNode = {
101
122
  kind: "user-input",
102
123
  version: 1,
103
124
  async run(context, params) {
125
+ const fields = applyInitialUserInputValues(params.fields, params.initialValues);
104
126
  const form = {
105
127
  formId: params.formId,
106
128
  title: params.title,
107
129
  ...(params.description ? { description: params.description } : {}),
108
130
  ...(params.submitLabel ? { submitLabel: params.submitLabel } : {}),
109
- fields: params.fields,
131
+ fields,
110
132
  };
111
133
  const requester = context.requestUserInput ?? requestUserInputInTerminal;
112
134
  const result = await requester(form);
@@ -130,7 +152,20 @@ export const userInputNode = {
130
152
  promptSuffix: rendered.promptSuffix,
131
153
  summaryText: rendered.summaryText,
132
154
  },
133
- outputs: [{ kind: "artifact", path: params.outputFile, required: true }],
155
+ outputs: [
156
+ {
157
+ kind: "artifact",
158
+ path: params.outputFile,
159
+ required: true,
160
+ manifest: {
161
+ publish: true,
162
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputFile),
163
+ payloadFamily: "structured-json",
164
+ schemaId: "user-input/v1",
165
+ schemaVersion: 1,
166
+ },
167
+ },
168
+ ],
134
169
  };
135
170
  },
136
171
  };
@@ -1,7 +1,8 @@
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
- const SEVERITY_AUTO_SELECT = ["blocker", "critical"];
5
+ import { normalizeReviewSeverity, resolveBlockingReviewSeverities } from "../../review-severity.js";
5
6
  export const writeSelectionFileNode = {
6
7
  kind: "write-selection-file",
7
8
  version: 1,
@@ -15,15 +16,16 @@ export const writeSelectionFileNode = {
15
16
  }
16
17
  const reviewFindings = parsed;
17
18
  const findings = Array.isArray(reviewFindings.findings) ? reviewFindings.findings : [];
19
+ const blockingSeverities = resolveBlockingReviewSeverities(params.blockingSeverities);
18
20
  const selectedFindings = findings
19
21
  .filter((finding) => {
20
- const severity = typeof finding.severity === "string" ? finding.severity.trim().toLowerCase() : "";
22
+ const severity = normalizeReviewSeverity(finding.severity);
21
23
  const disposition = typeof finding.disposition === "string" ? finding.disposition.trim().toLowerCase() : null;
22
- return SEVERITY_AUTO_SELECT.includes(severity) && disposition !== "resolved" && disposition != null;
24
+ return severity !== null && blockingSeverities.includes(severity) && disposition !== "resolved";
23
25
  })
24
26
  .map((finding) => finding.title)
25
27
  .filter((title) => typeof title === "string" && title.trim().length > 0);
26
- const applyAll = selectedFindings.length === 0;
28
+ const applyAll = false;
27
29
  const artifact = {
28
30
  form_id: "review-fix-selection",
29
31
  submitted_at: new Date().toISOString(),
@@ -41,6 +43,20 @@ export const writeSelectionFileNode = {
41
43
  selectedFindings,
42
44
  applyAll,
43
45
  },
46
+ outputs: [
47
+ {
48
+ kind: "artifact",
49
+ path: params.outputFile,
50
+ required: true,
51
+ manifest: {
52
+ publish: true,
53
+ logicalKey: buildLogicalKeyForPayload(_context.issueKey, params.outputFile),
54
+ payloadFamily: "structured-json",
55
+ schemaId: "user-input/v1",
56
+ schemaVersion: 1,
57
+ },
58
+ },
59
+ ],
44
60
  };
45
61
  },
46
62
  };
@@ -1,8 +1,9 @@
1
- import { BUG_ANALYZE_PROMPT_TEMPLATE, COMMIT_MESSAGE_PROMPT_TEMPLATE, GITLAB_DIFF_REVIEW_PROMPT_TEMPLATE, GITLAB_REVIEW_PROMPT_TEMPLATE, BUG_FIX_PROMPT_TEMPLATE, IMPLEMENT_PROMPT_TEMPLATE, JIRA_DESCRIPTION_PROMPT_TEMPLATE, MR_DESCRIPTION_PROMPT_TEMPLATE, PLAN_QUESTIONS_PROMPT_TEMPLATE, PLAN_PROMPT_TEMPLATE, REVIEW_FIX_PROMPT_TEMPLATE, REVIEW_PROJECT_PROMPT_TEMPLATE, REVIEW_PROMPT_TEMPLATE, REVIEW_SUMMARY_PROMPT_TEMPLATE, RUN_GO_LINTER_LOOP_FIX_PROMPT_TEMPLATE, RUN_GO_TESTS_LOOP_FIX_PROMPT_TEMPLATE, TASK_SUMMARY_PROMPT_TEMPLATE, } from "../prompts.js";
1
+ import { BUG_ANALYZE_PROMPT_TEMPLATE, COMMIT_MESSAGE_PROMPT_TEMPLATE, DESIGN_REVIEW_PROMPT_TEMPLATE, GITLAB_DIFF_REVIEW_PROMPT_TEMPLATE, GITLAB_REVIEW_PROMPT_TEMPLATE, BUG_FIX_PROMPT_TEMPLATE, IMPLEMENT_PROMPT_TEMPLATE, JIRA_DESCRIPTION_PROMPT_TEMPLATE, MR_DESCRIPTION_PROMPT_TEMPLATE, PLAN_PROMPT_TEMPLATE, PLAN_QUESTIONS_PROMPT_TEMPLATE, PLAN_REVISE_PROMPT_TEMPLATE, REVIEW_FIX_PROMPT_TEMPLATE, REVIEW_PROJECT_PROMPT_TEMPLATE, REVIEW_PROMPT_TEMPLATE, REVIEW_SUMMARY_PROMPT_TEMPLATE, RUN_GO_LINTER_LOOP_FIX_PROMPT_TEMPLATE, RUN_GO_TESTS_LOOP_FIX_PROMPT_TEMPLATE, TASK_CONTEXT_FROM_JIRA_PROMPT_TEMPLATE, TASK_CONTEXT_FROM_MANUAL_PROMPT_TEMPLATE, TASK_SUMMARY_PROMPT_TEMPLATE, } from "../prompts.js";
2
2
  const promptTemplates = {
3
3
  "bug-analyze": BUG_ANALYZE_PROMPT_TEMPLATE,
4
4
  "bug-fix": BUG_FIX_PROMPT_TEMPLATE,
5
5
  "commit-message": COMMIT_MESSAGE_PROMPT_TEMPLATE,
6
+ "design-review": DESIGN_REVIEW_PROMPT_TEMPLATE,
6
7
  "gitlab-diff-review": GITLAB_DIFF_REVIEW_PROMPT_TEMPLATE,
7
8
  "gitlab-review": GITLAB_REVIEW_PROMPT_TEMPLATE,
8
9
  implement: IMPLEMENT_PROMPT_TEMPLATE,
@@ -10,12 +11,15 @@ const promptTemplates = {
10
11
  "mr-description": MR_DESCRIPTION_PROMPT_TEMPLATE,
11
12
  "plan-questions": PLAN_QUESTIONS_PROMPT_TEMPLATE,
12
13
  plan: PLAN_PROMPT_TEMPLATE,
14
+ "plan-revise": PLAN_REVISE_PROMPT_TEMPLATE,
13
15
  review: REVIEW_PROMPT_TEMPLATE,
14
16
  "review-project": REVIEW_PROJECT_PROMPT_TEMPLATE,
15
17
  "review-fix": REVIEW_FIX_PROMPT_TEMPLATE,
16
18
  "review-summary": REVIEW_SUMMARY_PROMPT_TEMPLATE,
17
19
  "run-go-linter-loop-fix": RUN_GO_LINTER_LOOP_FIX_PROMPT_TEMPLATE,
18
20
  "run-go-tests-loop-fix": RUN_GO_TESTS_LOOP_FIX_PROMPT_TEMPLATE,
21
+ "task-context-from-jira": TASK_CONTEXT_FROM_JIRA_PROMPT_TEMPLATE,
22
+ "task-context-from-manual": TASK_CONTEXT_FROM_MANUAL_PROMPT_TEMPLATE,
19
23
  "task-summary": TASK_SUMMARY_PROMPT_TEMPLATE,
20
24
  };
21
25
  export function isPromptTemplateRef(value) {
@@ -2,12 +2,15 @@ import { TaskRunnerError } from "../errors.js";
2
2
  import { STRUCTURED_JSON_LANGUAGE_INSTRUCTION, formatPrompt, formatTemplate } from "../prompts.js";
3
3
  import { getPromptTemplate } from "./prompt-registry.js";
4
4
  import { resolveValue } from "./value-resolver.js";
5
+ export function resolvePromptBindingInputs(binding, context) {
6
+ return Object.fromEntries(Object.entries(binding.vars ?? {}).map(([key, value]) => [key, resolveValue(value, context)]));
7
+ }
5
8
  export function renderPrompt(binding, context) {
6
9
  const baseTemplate = binding.inlineTemplate ?? (binding.templateRef ? getPromptTemplate(binding.templateRef) : null);
7
10
  if (!baseTemplate) {
8
11
  throw new TaskRunnerError("Prompt binding must define templateRef or inlineTemplate");
9
12
  }
10
- const vars = Object.fromEntries(Object.entries(binding.vars ?? {}).map(([key, value]) => [key, String(resolveValue(value, context))]));
13
+ const vars = Object.fromEntries(Object.entries(resolvePromptBindingInputs(binding, context)).map(([key, value]) => [key, String(value)]));
11
14
  const basePrompt = formatTemplate(baseTemplate, vars);
12
15
  const resolvedExtraPrompt = binding.extraPrompt ? resolveValue(binding.extraPrompt, context) : null;
13
16
  const extraPrompt = resolvedExtraPrompt === null || resolvedExtraPrompt === undefined ? null : String(resolvedExtraPrompt);
@@ -0,0 +1,26 @@
1
+ export function parseReviewIterationCandidate(value) {
2
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
3
+ return undefined;
4
+ }
5
+ return value;
6
+ }
7
+ export function resolveReviewLoopBaseIteration(params) {
8
+ const baseIteration = parseReviewIterationCandidate(params["baseIteration"]);
9
+ if (baseIteration !== undefined) {
10
+ return baseIteration;
11
+ }
12
+ return parseReviewIterationCandidate(params["iteration"]);
13
+ }
14
+ export function withCanonicalReviewLoopParams(flowKind, params) {
15
+ if (flowKind !== "review-loop-flow" && flowKind !== "review-project-loop-flow") {
16
+ return params;
17
+ }
18
+ const baseIteration = resolveReviewLoopBaseIteration(params);
19
+ if (baseIteration === undefined) {
20
+ return params;
21
+ }
22
+ return {
23
+ ...params,
24
+ baseIteration,
25
+ };
26
+ }
@@ -177,6 +177,7 @@ function expandPhase(phase, repeatVars) {
177
177
  return {
178
178
  id: interpolateText(step.id, repeatVars),
179
179
  node: step.node,
180
+ ...(step.routingGroup ? { routingGroup: step.routingGroup } : {}),
180
181
  ...(stepWhen ? { when: stepWhen } : {}),
181
182
  ...(step.prompt ? { prompt: interpolatePrompt(step.prompt, repeatVars) } : {}),
182
183
  ...(step.params
@@ -186,6 +187,7 @@ function expandPhase(phase, repeatVars) {
186
187
  : {}),
187
188
  ...(step.expect ? { expect: step.expect.map((item) => interpolateExpectation(item, repeatVars)) } : {}),
188
189
  ...(stopFlowIf ? { stopFlowIf } : {}),
190
+ ...(step.stopFlowOutcome ? { stopFlowOutcome: step.stopFlowOutcome } : {}),
189
191
  ...(step.after ? { after: step.after.map((item) => interpolateAfterAction(item, repeatVars)) } : {}),
190
192
  repeatVars,
191
193
  };
@@ -7,6 +7,8 @@ export const ARTIFACT_REF_KINDS = [
7
7
  "bug-fix-plan-json-file",
8
8
  "design-file",
9
9
  "design-json-file",
10
+ "design-review-file",
11
+ "design-review-json-file",
10
12
  "gitlab-diff-file",
11
13
  "gitlab-diff-json-file",
12
14
  "gitlab-diff-review-input-json-file",
@@ -18,6 +20,7 @@ export const ARTIFACT_REF_KINDS = [
18
20
  "jira-description-file",
19
21
  "jira-description-json-file",
20
22
  "jira-task-file",
23
+ "instant-task-input-json-file",
21
24
  "mr-description-file",
22
25
  "mr-description-json-file",
23
26
  "planning-answers-json-file",
@@ -36,6 +39,8 @@ export const ARTIFACT_REF_KINDS = [
36
39
  "run-go-linter-result-json-file",
37
40
  "run-go-tests-result-json-file",
38
41
  "review-summary-file",
42
+ "task-context-file",
43
+ "task-context-json-file",
39
44
  "task-summary-file",
40
45
  "task-summary-json-file",
41
46
  "task-describe-input-json-file",
@@ -1,6 +1,7 @@
1
1
  import { TaskRunnerError } from "../errors.js";
2
2
  import { STRUCTURED_ARTIFACT_SCHEMA_IDS } from "../structured-artifacts.js";
3
3
  import { isPromptTemplateRef } from "./prompt-registry.js";
4
+ import { EXECUTION_ROUTING_GROUPS } from "./execution-routing-config.js";
4
5
  import { ARTIFACT_LIST_REF_KINDS as artifactListRefKinds, ARTIFACT_REF_KINDS as artifactRefKinds, } from "./spec-types.js";
5
6
  function isArtifactRefKind(value) {
6
7
  return artifactRefKinds.includes(value);
@@ -102,6 +103,10 @@ function validateStep(step, nodeRegistry, executorRegistry, path, options) {
102
103
  assert(executorRegistry.has(executorId), `Unknown executor '${executorId}' required by node '${step.node}' at ${path}.node`);
103
104
  }
104
105
  validateCondition(step.when, `${path}.when`);
106
+ if (step.routingGroup) {
107
+ assert(step.node === "llm-prompt", `routingGroup is only supported for llm-prompt steps at ${path}.routingGroup`);
108
+ assert(EXECUTION_ROUTING_GROUPS.includes(step.routingGroup), `Unknown routingGroup '${step.routingGroup}' at ${path}.routingGroup`);
109
+ }
105
110
  if (step.prompt) {
106
111
  assert(nodeMeta.prompt !== "forbidden", `Node '${step.node}' does not accept prompt binding at ${path}.prompt`);
107
112
  assert(Boolean(step.prompt.templateRef || step.prompt.inlineTemplate), `Prompt binding at ${path}.prompt must define templateRef or inlineTemplate`);
@@ -110,6 +115,9 @@ function validateStep(step, nodeRegistry, executorRegistry, path, options) {
110
115
  for (const requiredParam of nodeMeta.requiredParams ?? []) {
111
116
  assert(step.params?.[requiredParam] !== undefined, `Node '${step.node}' requires param '${requiredParam}' at ${path}.params.${requiredParam}`);
112
117
  }
118
+ if (step.node === "llm-prompt") {
119
+ assert(step.routingGroup !== undefined || step.params?.executor !== undefined, `Node 'llm-prompt' requires routingGroup or param 'executor' at ${path}`);
120
+ }
113
121
  if (step.prompt?.templateRef) {
114
122
  assert(isPromptTemplateRef(step.prompt.templateRef), `Unknown prompt template '${step.prompt.templateRef}' at ${path}.prompt.templateRef`);
115
123
  }
@@ -147,6 +155,9 @@ function validateStep(step, nodeRegistry, executorRegistry, path, options) {
147
155
  if (step.stopFlowIf) {
148
156
  validateCondition(step.stopFlowIf, `${path}.stopFlowIf`);
149
157
  }
158
+ if (step.stopFlowOutcome) {
159
+ assert(step.stopFlowOutcome === "success" || step.stopFlowOutcome === "stopped", `Unsupported stopFlowOutcome '${step.stopFlowOutcome}' at ${path}.stopFlowOutcome`);
160
+ }
150
161
  if (step.after) {
151
162
  step.after.forEach((action, index) => validateAfterAction(action, `${path}.after[${index}]`));
152
163
  }
@@ -281,6 +292,9 @@ function validateExpandedCondition(condition, phases, currentPhaseIndex, current
281
292
  }
282
293
  }
283
294
  export function validateFlowSpec(spec, nodeRegistry, executorRegistry, options = {}) {
295
+ if (spec.catalogVisibility !== undefined) {
296
+ assert(spec.catalogVisibility === "visible" || spec.catalogVisibility === "hidden", `Unsupported catalogVisibility '${spec.catalogVisibility}' at flow.catalogVisibility`);
297
+ }
284
298
  assert(spec.kind.trim().length > 0, "Flow spec kind must be non-empty");
285
299
  assert(Number.isInteger(spec.version) && spec.version > 0, "Flow spec version must be a positive integer");
286
300
  spec.phases.forEach((item, index) => {
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
- import { artifactFile, bugAnalyzeArtifacts, bugAnalyzeFile, bugAnalyzeJsonFile, bugFixDesignFile, bugFixDesignJsonFile, bugFixPlanFile, bugFixPlanJsonFile, designFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, gitlabDiffReviewInputJsonFile, gitlabReviewFile, gitlabReviewInputJsonFile, gitlabReviewJsonFile, jiraAttachmentsContextFile, jiraAttachmentsManifestFile, jiraDescriptionFile, jiraDescriptionJsonFile, jiraTaskFile, mrDescriptionFile, mrDescriptionJsonFile, planningAnswersJsonFile, planningQuestionsJsonFile, planArtifacts, planFile, planJsonFile, qaFile, qaJsonFile, readyToMergeFile, reviewAssessmentFile, reviewAssessmentJsonFile, reviewFile, reviewFixFile, reviewFixJsonFile, reviewJsonFile, runGoLinterResultJsonFile, runGoTestsResultJsonFile, taskSummaryFile, taskDescribeInputJsonFile, taskSummaryJsonFile, gitStatusJsonFile, gitCommitMessageJsonFile, gitCommitInputJsonFile, selectFilesOutputJsonFile, commitMessageOutputJsonFile, gitDiffFile as gitDiffFileHelper, } from "../artifacts.js";
2
+ import { artifactFile, bugAnalyzeArtifacts, bugAnalyzeFile, bugAnalyzeJsonFile, bugFixDesignFile, bugFixDesignJsonFile, bugFixPlanFile, bugFixPlanJsonFile, designFile, designJsonFile, designReviewFile, designReviewJsonFile, gitlabDiffFile, gitlabDiffJsonFile, gitlabDiffReviewInputJsonFile, gitlabReviewFile, gitlabReviewInputJsonFile, gitlabReviewJsonFile, jiraAttachmentsContextFile, jiraAttachmentsManifestFile, jiraDescriptionFile, jiraDescriptionJsonFile, instantTaskInputJsonFile, jiraTaskFile, mrDescriptionFile, mrDescriptionJsonFile, planningAnswersJsonFile, planningQuestionsJsonFile, planArtifacts, planFile, planJsonFile, qaFile, qaJsonFile, readyToMergeFile, reviewAssessmentFile, reviewAssessmentJsonFile, reviewFile, reviewFixFile, reviewFixJsonFile, reviewJsonFile, runGoLinterResultJsonFile, runGoTestsResultJsonFile, taskSummaryFile, taskDescribeInputJsonFile, taskSummaryJsonFile, taskContextFile, taskContextJsonFile, gitStatusJsonFile, gitCommitMessageJsonFile, gitCommitInputJsonFile, selectFilesOutputJsonFile, commitMessageOutputJsonFile, gitDiffFile as gitDiffFileHelper, } from "../artifacts.js";
3
3
  import { TaskRunnerError } from "../errors.js";
4
4
  import { formatTemplate } from "../prompts.js";
5
5
  function readStepRef(segments, context, originalPath) {
@@ -88,6 +88,16 @@ function resolveArtifact(spec, context) {
88
88
  return designFile(taskKey, iteration);
89
89
  case "design-json-file":
90
90
  return designJsonFile(taskKey, iteration);
91
+ case "design-review-file":
92
+ if (iteration === undefined) {
93
+ throw new TaskRunnerError("design-review-file requires iteration");
94
+ }
95
+ return designReviewFile(taskKey, iteration);
96
+ case "design-review-json-file":
97
+ if (iteration === undefined) {
98
+ throw new TaskRunnerError("design-review-json-file requires iteration");
99
+ }
100
+ return designReviewJsonFile(taskKey, iteration);
91
101
  case "gitlab-diff-file":
92
102
  return gitlabDiffFile(taskKey, iteration);
93
103
  case "gitlab-diff-json-file":
@@ -110,6 +120,8 @@ function resolveArtifact(spec, context) {
110
120
  return jiraDescriptionJsonFile(taskKey, iteration);
111
121
  case "jira-task-file":
112
122
  return jiraTaskFile(taskKey);
123
+ case "instant-task-input-json-file":
124
+ return instantTaskInputJsonFile(taskKey);
113
125
  case "mr-description-file":
114
126
  return mrDescriptionFile(taskKey, iteration);
115
127
  case "mr-description-json-file":
@@ -173,6 +185,10 @@ function resolveArtifact(spec, context) {
173
185
  throw new TaskRunnerError("review-summary-file requires iteration");
174
186
  }
175
187
  return artifactFile("review-summary", taskKey, iteration);
188
+ case "task-context-file":
189
+ return taskContextFile(taskKey, iteration);
190
+ case "task-context-json-file":
191
+ return taskContextJsonFile(taskKey, iteration);
176
192
  case "task-summary-file":
177
193
  return taskSummaryFile(taskKey, iteration);
178
194
  case "task-summary-json-file":
@@ -253,6 +269,73 @@ export function resolveParams(params, context) {
253
269
  }
254
270
  return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, resolveValue(value, context)]));
255
271
  }
272
+ export const ARTIFACT_LINEAGE_REF_PATHS_PARAM = "__artifactLineageRefPaths";
273
+ function collectAnnotatedRefArtifactPathCandidates(refPath, context) {
274
+ const annotatedRefPaths = context.flowParams[ARTIFACT_LINEAGE_REF_PATHS_PARAM];
275
+ if (!annotatedRefPaths || typeof annotatedRefPaths !== "object" || Array.isArray(annotatedRefPaths)) {
276
+ return [];
277
+ }
278
+ const candidate = annotatedRefPaths[refPath];
279
+ const paths = typeof candidate === "string" ? [candidate] : Array.isArray(candidate) ? candidate : [];
280
+ return paths.filter((value) => typeof value === "string" && existsSync(value));
281
+ }
282
+ export function collectResolvedArtifactPathCandidates(value, context) {
283
+ const candidates = new Set();
284
+ const addCandidate = (candidatePath) => {
285
+ if (existsSync(candidatePath)) {
286
+ candidates.add(candidatePath);
287
+ }
288
+ };
289
+ const visit = (current) => {
290
+ if (!current || "const" in current) {
291
+ return;
292
+ }
293
+ if ("ref" in current) {
294
+ collectAnnotatedRefArtifactPathCandidates(current.ref, context).forEach((candidatePath) => addCandidate(candidatePath));
295
+ return;
296
+ }
297
+ if ("artifact" in current) {
298
+ addCandidate(resolveArtifact(current.artifact, context));
299
+ return;
300
+ }
301
+ if ("artifactList" in current) {
302
+ resolveArtifactList(current.artifactList, context).forEach((candidatePath) => addCandidate(candidatePath));
303
+ return;
304
+ }
305
+ if ("template" in current) {
306
+ Object.values(current.vars ?? {}).forEach((candidate) => visit(candidate));
307
+ return;
308
+ }
309
+ if ("appendPrompt" in current) {
310
+ visit(current.appendPrompt.base);
311
+ visit(current.appendPrompt.suffix);
312
+ return;
313
+ }
314
+ if ("add" in current) {
315
+ current.add.forEach((candidate) => visit(candidate));
316
+ return;
317
+ }
318
+ if ("concat" in current) {
319
+ current.concat.forEach((candidate) => visit(candidate));
320
+ return;
321
+ }
322
+ if ("list" in current) {
323
+ current.list.forEach((candidate) => visit(candidate));
324
+ }
325
+ };
326
+ visit(value);
327
+ return Array.from(candidates).sort();
328
+ }
329
+ export function collectResolvedPromptArtifactPathCandidates(binding, context) {
330
+ const candidates = new Set();
331
+ for (const value of Object.values(binding?.vars ?? {})) {
332
+ collectResolvedArtifactPathCandidates(value, context).forEach((candidate) => candidates.add(candidate));
333
+ }
334
+ if (binding?.extraPrompt) {
335
+ collectResolvedArtifactPathCandidates(binding.extraPrompt, context).forEach((candidate) => candidates.add(candidate));
336
+ }
337
+ return Array.from(candidates).sort();
338
+ }
256
339
  function truthy(value) {
257
340
  return Boolean(value);
258
341
  }