agentweaver 0.1.19 → 0.1.20

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 (50) hide show
  1. package/README.md +47 -7
  2. package/dist/artifacts.js +9 -0
  3. package/dist/executors/git-commit-executor.js +24 -6
  4. package/dist/flow-state.js +3 -8
  5. package/dist/git/git-diff-parser.js +223 -0
  6. package/dist/git/git-service.js +562 -0
  7. package/dist/git/git-stage-selection.js +24 -0
  8. package/dist/git/git-status-parser.js +171 -0
  9. package/dist/git/git-types.js +1 -0
  10. package/dist/index.js +450 -108
  11. package/dist/interactive/auto-flow.js +644 -0
  12. package/dist/interactive/controller.js +417 -9
  13. package/dist/interactive/progress.js +194 -1
  14. package/dist/interactive/state.js +25 -0
  15. package/dist/interactive/web/index.js +97 -12
  16. package/dist/interactive/web/protocol.js +216 -1
  17. package/dist/interactive/web/server.js +72 -14
  18. package/dist/interactive/web/static/app.js +1603 -49
  19. package/dist/interactive/web/static/index.html +76 -11
  20. package/dist/interactive/web/static/styles.css +1 -1
  21. package/dist/interactive/web/static/styles.input.css +901 -47
  22. package/dist/pipeline/auto-flow-blocks.js +307 -0
  23. package/dist/pipeline/auto-flow-config.js +273 -0
  24. package/dist/pipeline/auto-flow-identity.js +49 -0
  25. package/dist/pipeline/auto-flow-presets.js +52 -0
  26. package/dist/pipeline/auto-flow-resolver.js +830 -0
  27. package/dist/pipeline/auto-flow-types.js +17 -0
  28. package/dist/pipeline/context.js +1 -0
  29. package/dist/pipeline/declarative-flows.js +27 -1
  30. package/dist/pipeline/flow-specs/auto-common-guided.json +11 -0
  31. package/dist/pipeline/flow-specs/auto-golang.json +12 -1
  32. package/dist/pipeline/flow-specs/bugz/bug-analyze.json +54 -1
  33. package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +19 -1
  34. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +33 -1
  35. package/dist/pipeline/flow-specs/review/review-project.json +19 -1
  36. package/dist/pipeline/flow-specs/task-source/manual-jira-input.json +70 -0
  37. package/dist/pipeline/node-registry.js +9 -0
  38. package/dist/pipeline/nodes/codex-prompt-node.js +8 -1
  39. package/dist/pipeline/nodes/flow-run-node.js +5 -3
  40. package/dist/pipeline/nodes/git-status-node.js +2 -168
  41. package/dist/pipeline/nodes/manual-jira-task-input-node.js +146 -0
  42. package/dist/pipeline/nodes/opencode-prompt-node.js +8 -1
  43. package/dist/pipeline/nodes/plan-codex-node.js +8 -1
  44. package/dist/pipeline/spec-loader.js +14 -4
  45. package/dist/runtime/artifact-catalog.js +29 -5
  46. package/dist/runtime/settings.js +114 -0
  47. package/dist/scope.js +14 -4
  48. package/package.json +1 -1
  49. package/dist/pipeline/flow-specs/auto-common.json +0 -179
  50. package/dist/pipeline/flow-specs/auto-simple.json +0 -141
@@ -0,0 +1,146 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { printSummary } from "../../tui.js";
3
+ import { requestUserInputInTerminal, validateUserInputValues, } from "../../user-input.js";
4
+ function summarizeDescription(description) {
5
+ return description
6
+ .split(/\r?\n/)
7
+ .map((line) => line.trim())
8
+ .find((line) => line.length > 0)
9
+ ?.slice(0, 140) || "Manual Jira task description";
10
+ }
11
+ function buildManualJiraPayload(taskKey, description) {
12
+ const summary = summarizeDescription(description);
13
+ return {
14
+ id: `manual-${taskKey}`,
15
+ key: taskKey,
16
+ self: null,
17
+ source: "manual-jira-fallback",
18
+ fields: {
19
+ summary,
20
+ description,
21
+ issuetype: {
22
+ name: "Manual task",
23
+ },
24
+ labels: ["manual-jira-fallback"],
25
+ attachment: [],
26
+ comment: {
27
+ comments: [],
28
+ },
29
+ },
30
+ manual_input: {
31
+ description,
32
+ captured_at: new Date().toISOString(),
33
+ },
34
+ };
35
+ }
36
+ export const manualJiraTaskInputNode = {
37
+ kind: "manual-jira-task-input",
38
+ version: 1,
39
+ async run(context, params) {
40
+ const form = {
41
+ formId: "manual-jira-task-input",
42
+ title: "Manual Jira Task",
43
+ description: "Paste the Jira task description when Jira access is unavailable.",
44
+ submitLabel: "Continue",
45
+ fields: [
46
+ {
47
+ id: "task_description",
48
+ type: "text",
49
+ label: "Task description",
50
+ help: "Paste the Jira task text here. This will be stored as the raw Jira task artifact for this flow.",
51
+ required: true,
52
+ multiline: true,
53
+ rows: 10,
54
+ placeholder: "Paste Jira task title, description, acceptance criteria, comments, and links here.",
55
+ },
56
+ ],
57
+ };
58
+ const requester = context.requestUserInput ?? requestUserInputInTerminal;
59
+ const result = await requester(form);
60
+ validateUserInputValues(form, result.values);
61
+ const description = String(result.values.task_description ?? "").trim();
62
+ writeFileSync(params.outputFile, `${JSON.stringify(buildManualJiraPayload(params.taskKey, description), null, 2)}\n`, "utf8");
63
+ if (params.attachmentsManifestFile) {
64
+ writeFileSync(params.attachmentsManifestFile, `${JSON.stringify({ source: "manual-jira-fallback", issueKey: params.taskKey, attachments: [] }, null, 2)}\n`, "utf8");
65
+ }
66
+ if (params.attachmentsContextFile) {
67
+ writeFileSync(params.attachmentsContextFile, "No Jira attachments were provided for the manual Jira fallback.\n", "utf8");
68
+ }
69
+ printSummary("Manual Jira Task", description);
70
+ const outputs = [
71
+ {
72
+ kind: "file",
73
+ path: params.outputFile,
74
+ required: true,
75
+ manifest: {
76
+ publish: true,
77
+ logicalKey: "artifacts/jira-task.json",
78
+ payloadFamily: "helper-json",
79
+ schemaId: "helper-json/v1",
80
+ schemaVersion: 1,
81
+ },
82
+ },
83
+ ];
84
+ if (params.attachmentsManifestFile) {
85
+ outputs.push({
86
+ kind: "artifact",
87
+ path: params.attachmentsManifestFile,
88
+ required: true,
89
+ manifest: {
90
+ publish: true,
91
+ logicalKey: "artifacts/jira-attachments.json",
92
+ payloadFamily: "helper-json",
93
+ schemaId: "helper-json/v1",
94
+ schemaVersion: 1,
95
+ },
96
+ });
97
+ }
98
+ if (params.attachmentsContextFile) {
99
+ outputs.push({
100
+ kind: "artifact",
101
+ path: params.attachmentsContextFile,
102
+ required: true,
103
+ manifest: {
104
+ publish: true,
105
+ logicalKey: "jira-attachments-context.txt",
106
+ payloadFamily: "plain-text",
107
+ schemaId: "plain-text/v1",
108
+ schemaVersion: 1,
109
+ },
110
+ });
111
+ }
112
+ return {
113
+ value: {
114
+ outputFile: params.outputFile,
115
+ ...(params.attachmentsManifestFile ? { attachmentsManifestFile: params.attachmentsManifestFile } : {}),
116
+ ...(params.attachmentsContextFile ? { attachmentsContextFile: params.attachmentsContextFile } : {}),
117
+ descriptionLength: description.length,
118
+ },
119
+ outputs,
120
+ };
121
+ },
122
+ checks(_context, params) {
123
+ const checks = [
124
+ {
125
+ kind: "require-file",
126
+ path: params.outputFile,
127
+ message: `Manual Jira task input did not produce ${params.outputFile}.`,
128
+ },
129
+ ];
130
+ if (params.attachmentsManifestFile) {
131
+ checks.push({
132
+ kind: "require-file",
133
+ path: params.attachmentsManifestFile,
134
+ message: `Manual Jira task input did not produce ${params.attachmentsManifestFile}.`,
135
+ });
136
+ }
137
+ if (params.attachmentsContextFile) {
138
+ checks.push({
139
+ kind: "require-file",
140
+ path: params.attachmentsContextFile,
141
+ message: `Manual Jira task input did not produce ${params.attachmentsContextFile}.`,
142
+ });
143
+ }
144
+ return checks;
145
+ },
146
+ };
@@ -14,7 +14,14 @@ export const opencodePromptNode = {
14
14
  }, executor.defaultConfig);
15
15
  return {
16
16
  value,
17
- outputs: (params.requiredArtifacts ?? []).map((path) => ({ kind: "artifact", path, required: true })),
17
+ outputs: (params.requiredArtifacts ?? []).map((path) => ({
18
+ kind: "artifact",
19
+ path,
20
+ required: true,
21
+ manifest: {
22
+ publish: true,
23
+ },
24
+ })),
18
25
  };
19
26
  },
20
27
  checks(_context, params) {
@@ -17,7 +17,14 @@ export const planCodexNode = {
17
17
  const value = await executor.execute(toExecutorContext(context), input, executor.defaultConfig);
18
18
  return {
19
19
  value,
20
- outputs: params.requiredArtifacts.map((path) => ({ kind: "artifact", path, required: true })),
20
+ outputs: params.requiredArtifacts.map((path) => ({
21
+ kind: "artifact",
22
+ path,
23
+ required: true,
24
+ manifest: {
25
+ publish: true,
26
+ },
27
+ })),
21
28
  };
22
29
  },
23
30
  checks(_context, params) {
@@ -3,6 +3,8 @@ import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { TaskRunnerError } from "../errors.js";
5
5
  import { agentweaverConfigDir } from "../runtime/env-loader.js";
6
+ import { resolveBuiltInAutoFlowSpecByFileName } from "./auto-flow-resolver.js";
7
+ import { VIRTUAL_BUILT_IN_AUTO_FLOW_FILE_NAMES } from "./auto-flow-presets.js";
6
8
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
7
9
  const BUILT_IN_FLOW_SPECS_DIR = path.join(MODULE_DIR, "flow-specs");
8
10
  function parseFlowSpec(filePath) {
@@ -23,12 +25,14 @@ export function globalFlowSpecsDir() {
23
25
  return path.join(agentweaverConfigDir(), ".flows");
24
26
  }
25
27
  export function listBuiltInFlowSpecFiles() {
28
+ const files = new Set(VIRTUAL_BUILT_IN_AUTO_FLOW_FILE_NAMES);
26
29
  if (!existsSync(BUILT_IN_FLOW_SPECS_DIR)) {
27
- return [];
30
+ return [...files].sort((left, right) => left.localeCompare(right));
31
+ }
32
+ for (const filePath of collectJsonFilesRecursively(BUILT_IN_FLOW_SPECS_DIR)) {
33
+ files.add(path.relative(BUILT_IN_FLOW_SPECS_DIR, filePath));
28
34
  }
29
- return collectJsonFilesRecursively(BUILT_IN_FLOW_SPECS_DIR)
30
- .map((filePath) => path.relative(BUILT_IN_FLOW_SPECS_DIR, filePath))
31
- .sort((left, right) => left.localeCompare(right));
35
+ return [...files].sort((left, right) => left.localeCompare(right));
32
36
  }
33
37
  function collectJsonFilesRecursively(directory) {
34
38
  const entries = readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
@@ -60,6 +64,12 @@ export function listGlobalFlowSpecFiles() {
60
64
  return collectJsonFilesRecursively(directory);
61
65
  }
62
66
  export function loadFlowSpecSync(source) {
67
+ if (source.source === "built-in") {
68
+ const resolvedAutoFlowSpec = resolveBuiltInAutoFlowSpecByFileName(source.fileName);
69
+ if (resolvedAutoFlowSpec) {
70
+ return resolvedAutoFlowSpec;
71
+ }
72
+ }
63
73
  return parseFlowSpec(source.source === "built-in" ? resolveBuiltInFlowSpecPath(source.fileName) : source.filePath);
64
74
  }
65
75
  export function loadBuiltInFlowSpecSync(fileName) {
@@ -323,6 +323,9 @@ function roleRank(role) {
323
323
  function phaseSortKey(phaseId) {
324
324
  return phaseId ?? UNCLASSIFIED_PHASE_ID;
325
325
  }
326
+ function typeGroupKey(item) {
327
+ return `${item.role}:${item.title}`;
328
+ }
326
329
  function compareCatalogItems(left, right) {
327
330
  const phaseComparison = phaseSortKey(left.phaseId).localeCompare(phaseSortKey(right.phaseId));
328
331
  if (phaseComparison !== 0) {
@@ -336,6 +339,10 @@ function compareCatalogItems(left, right) {
336
339
  if (titleComparison !== 0) {
337
340
  return titleComparison;
338
341
  }
342
+ const updatedComparison = right.updatedAt.localeCompare(left.updatedAt);
343
+ if (updatedComparison !== 0) {
344
+ return updatedComparison;
345
+ }
339
346
  return left.relativePath.localeCompare(right.relativePath);
340
347
  }
341
348
  export function groupArtifactCatalog(items) {
@@ -348,11 +355,28 @@ export function groupArtifactCatalog(items) {
348
355
  }
349
356
  return Array.from(grouped.entries())
350
357
  .sort(([left], [right]) => left.localeCompare(right))
351
- .map(([phaseId, phaseItems]) => ({
352
- phaseId,
353
- title: phaseId === UNCLASSIFIED_PHASE_ID ? "Unclassified" : toTitleCase(phaseId),
354
- items: phaseItems.slice().sort(compareCatalogItems),
355
- }));
358
+ .map(([phaseId, phaseItems]) => {
359
+ const sortedPhaseItems = phaseItems.slice().sort(compareCatalogItems);
360
+ const typeGroups = new Map();
361
+ for (const item of sortedPhaseItems) {
362
+ const key = typeGroupKey(item);
363
+ const groupItems = typeGroups.get(key) ?? [];
364
+ groupItems.push(item);
365
+ typeGroups.set(key, groupItems);
366
+ }
367
+ const nestedGroups = Array.from(typeGroups.entries())
368
+ .map(([key, groupItems]) => ({
369
+ phaseId: `${phaseId}:${key}`,
370
+ title: groupItems[0]?.title ?? "Artifacts",
371
+ items: groupItems.slice().sort(compareCatalogItems),
372
+ }));
373
+ return {
374
+ phaseId,
375
+ title: phaseId === UNCLASSIFIED_PHASE_ID ? "Unclassified" : toTitleCase(phaseId),
376
+ items: sortedPhaseItems,
377
+ ...(nestedGroups.length > 1 || nestedGroups.some((group) => group.items.length > 1) ? { groups: nestedGroups } : {}),
378
+ };
379
+ });
356
380
  }
357
381
  export function listArtifactCatalog(input) {
358
382
  const seenPayloadPaths = new Set();
@@ -0,0 +1,114 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { agentweaverConfigDir } from "./env-loader.js";
4
+ export const WEB_UI_AUTO_FLOW_HEIGHT_MIN = 120;
5
+ export const WEB_UI_AUTO_FLOW_HEIGHT_MAX = 640;
6
+ export const WEB_UI_WORKSPACE_SPLIT_MIN = 24;
7
+ export const WEB_UI_WORKSPACE_SPLIT_MAX = 58;
8
+ export const DEFAULT_AGENTWEAVER_WEB_UI_SETTINGS = {
9
+ theme: "light",
10
+ autoFlowHeight: null,
11
+ workspaceSplit: 36,
12
+ logAutoscroll: true,
13
+ };
14
+ export function agentweaverSettingsPath() {
15
+ return path.join(agentweaverConfigDir(), "settings.json");
16
+ }
17
+ function isRecord(value) {
18
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19
+ }
20
+ function clampInteger(value, min, max, fallback) {
21
+ const numeric = Number(value);
22
+ if (!Number.isFinite(numeric)) {
23
+ return fallback;
24
+ }
25
+ return Math.min(max, Math.max(min, Math.round(numeric)));
26
+ }
27
+ function normalizeTheme(value, fallback) {
28
+ return value === "dark" || value === "light" ? value : fallback;
29
+ }
30
+ export function normalizeWebUiSettings(value) {
31
+ const raw = isRecord(value) ? value : {};
32
+ const defaults = DEFAULT_AGENTWEAVER_WEB_UI_SETTINGS;
33
+ const rawHeight = raw["autoFlowHeight"];
34
+ const autoFlowHeight = rawHeight === null
35
+ ? null
36
+ : Number.isFinite(Number(rawHeight))
37
+ ? clampInteger(rawHeight, WEB_UI_AUTO_FLOW_HEIGHT_MIN, WEB_UI_AUTO_FLOW_HEIGHT_MAX, defaults.autoFlowHeight ?? WEB_UI_AUTO_FLOW_HEIGHT_MAX)
38
+ : defaults.autoFlowHeight;
39
+ return {
40
+ theme: normalizeTheme(raw["theme"], defaults.theme),
41
+ autoFlowHeight,
42
+ workspaceSplit: clampInteger(raw["workspaceSplit"], WEB_UI_WORKSPACE_SPLIT_MIN, WEB_UI_WORKSPACE_SPLIT_MAX, defaults.workspaceSplit),
43
+ logAutoscroll: typeof raw["logAutoscroll"] === "boolean" ? raw["logAutoscroll"] : defaults.logAutoscroll,
44
+ };
45
+ }
46
+ export function normalizeWebUiSettingsPatch(patch) {
47
+ const normalized = {};
48
+ if (patch.theme !== undefined) {
49
+ normalized.theme = normalizeTheme(patch.theme, DEFAULT_AGENTWEAVER_WEB_UI_SETTINGS.theme);
50
+ }
51
+ if ("autoFlowHeight" in patch) {
52
+ normalized.autoFlowHeight = patch.autoFlowHeight === null || patch.autoFlowHeight === undefined
53
+ ? null
54
+ : clampInteger(patch.autoFlowHeight, WEB_UI_AUTO_FLOW_HEIGHT_MIN, WEB_UI_AUTO_FLOW_HEIGHT_MAX, WEB_UI_AUTO_FLOW_HEIGHT_MAX);
55
+ }
56
+ if (patch.workspaceSplit !== undefined) {
57
+ normalized.workspaceSplit = clampInteger(patch.workspaceSplit, WEB_UI_WORKSPACE_SPLIT_MIN, WEB_UI_WORKSPACE_SPLIT_MAX, DEFAULT_AGENTWEAVER_WEB_UI_SETTINGS.workspaceSplit);
58
+ }
59
+ if (patch.logAutoscroll !== undefined) {
60
+ normalized.logAutoscroll = Boolean(patch.logAutoscroll);
61
+ }
62
+ return normalized;
63
+ }
64
+ function readRawSettings() {
65
+ const filePath = agentweaverSettingsPath();
66
+ if (!existsSync(filePath)) {
67
+ return {};
68
+ }
69
+ try {
70
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
71
+ return isRecord(parsed) ? parsed : {};
72
+ }
73
+ catch {
74
+ return {};
75
+ }
76
+ }
77
+ function writeJsonAtomic(filePath, value) {
78
+ mkdirSync(path.dirname(filePath), { recursive: true });
79
+ const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
80
+ writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
81
+ renameSync(tempPath, filePath);
82
+ }
83
+ export function loadAgentWeaverSettings() {
84
+ const raw = readRawSettings();
85
+ return {
86
+ kind: "agentweaver-settings",
87
+ version: 1,
88
+ webUi: normalizeWebUiSettings(raw["webUi"]),
89
+ };
90
+ }
91
+ export function saveAgentWeaverSettings(settings) {
92
+ const raw = readRawSettings();
93
+ const normalized = {
94
+ kind: "agentweaver-settings",
95
+ version: 1,
96
+ webUi: normalizeWebUiSettings(settings.webUi),
97
+ };
98
+ writeJsonAtomic(agentweaverSettingsPath(), {
99
+ ...raw,
100
+ ...normalized,
101
+ });
102
+ return normalized;
103
+ }
104
+ export function updateWebUiSettings(patch) {
105
+ const current = loadAgentWeaverSettings();
106
+ const next = saveAgentWeaverSettings({
107
+ ...current,
108
+ webUi: {
109
+ ...current.webUi,
110
+ ...normalizeWebUiSettingsPatch(patch),
111
+ },
112
+ });
113
+ return next.webUi;
114
+ }
package/dist/scope.js CHANGED
@@ -109,19 +109,24 @@ export function attachJiraContext(scope, jiraRef) {
109
109
  jiraTaskFile: jiraTaskFile(scope.scopeKey),
110
110
  };
111
111
  }
112
- export function buildJiraTaskInputForm() {
112
+ export function buildJiraTaskInputForm(options = {}) {
113
+ const required = options.required ?? true;
113
114
  return {
114
115
  formId: "jira-task-input",
115
116
  title: "Jira Task",
116
- description: "Provide a Jira issue key or browse URL for a task-driven flow.",
117
+ description: required
118
+ ? "Provide a Jira issue key or browse URL for a task-driven flow."
119
+ : "Provide a Jira issue key or browse URL, or leave it empty to paste the task description manually.",
117
120
  submitLabel: "Continue",
118
121
  fields: [
119
122
  {
120
123
  id: "jira_ref",
121
124
  type: "text",
122
125
  label: "Jira issue key or browse URL",
123
- help: "Example: DEMO-3288 or https://jira.example.com/browse/DEMO-3288",
124
- required: true,
126
+ help: required
127
+ ? "Example: DEMO-3288 or https://jira.example.com/browse/DEMO-3288"
128
+ : "Leave empty to enter the task description manually in the next step.",
129
+ required,
125
130
  },
126
131
  ],
127
132
  };
@@ -134,3 +139,8 @@ export async function requestJiraContext(requestUserInput) {
134
139
  }
135
140
  return parseJiraContext(jiraRef);
136
141
  }
142
+ export async function requestOptionalJiraContext(requestUserInput) {
143
+ const result = await requestUserInput(buildJiraTaskInputForm({ required: false }));
144
+ const jiraRef = String(result.values.jira_ref ?? "").trim();
145
+ return jiraRef ? parseJiraContext(jiraRef) : null;
146
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentweaver",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "CLI orchestrator for Jira/Codex engineering workflows",
5
5
  "keywords": [
6
6
  "agent",
@@ -1,179 +0,0 @@
1
- {
2
- "kind": "auto-flow",
3
- "version": 1,
4
- "description": "End-to-end resumable pipeline without language-specific checks. Runs: Jira fetch → task source normalization → plan → design-review loop → implement → review loop. Planning gate requires design-review approval, or an explicit user decision to continue after the maximum revision attempts, before implementation.",
5
- "phases": [
6
- {
7
- "id": "source",
8
- "steps": [
9
- {
10
- "id": "fetch_jira_source",
11
- "node": "flow-run",
12
- "params": {
13
- "fileName": { "const": "jira-fetch.json" },
14
- "labelText": { "const": "Fetching Jira task source" },
15
- "jiraApiUrl": { "ref": "params.jiraApiUrl" },
16
- "taskKey": { "ref": "params.taskKey" }
17
- }
18
- }
19
- ]
20
- },
21
- {
22
- "id": "normalize",
23
- "steps": [
24
- {
25
- "id": "run_normalize_source",
26
- "node": "flow-run",
27
- "params": {
28
- "fileName": { "const": "normalize-task-source.json" },
29
- "labelText": { "const": "Normalizing task source" },
30
- "taskKey": { "ref": "params.taskKey" },
31
- "iteration": { "ref": "params.taskContextIteration" },
32
- "llmExecutor": { "ref": "params.llmExecutor" },
33
- "llmModel": { "ref": "params.llmModel" },
34
- "extraPrompt": { "ref": "params.extraPrompt" }
35
- }
36
- }
37
- ]
38
- },
39
- {
40
- "id": "plan",
41
- "steps": [
42
- {
43
- "id": "run_plan_flow",
44
- "node": "flow-run",
45
- "params": {
46
- "fileName": { "const": "plan.json" },
47
- "labelText": { "const": "Running planning flow" },
48
- "taskKey": { "ref": "params.taskKey" },
49
- "taskContextIteration": { "ref": "params.taskContextIteration" },
50
- "designIteration": { "ref": "params.designIteration" },
51
- "planIteration": { "ref": "params.planIteration" },
52
- "qaIteration": { "ref": "params.qaIteration" },
53
- "llmExecutor": { "ref": "params.llmExecutor" },
54
- "llmModel": { "ref": "params.llmModel" },
55
- "extraPrompt": { "ref": "params.extraPrompt" },
56
- "mdLang": { "ref": "params.mdLang" }
57
- }
58
- }
59
- ]
60
- },
61
- {
62
- "id": "design_review_loop",
63
- "steps": [
64
- {
65
- "id": "run_design_review_loop",
66
- "node": "flow-run",
67
- "params": {
68
- "fileName": { "const": "design-review-loop.json" },
69
- "labelText": { "const": "Running design-review loop" },
70
- "taskKey": { "ref": "params.taskKey" },
71
- "baseIteration": { "ref": "params.designReviewBaseIteration" },
72
- "workspaceDir": { "ref": "params.workspaceDir" },
73
- "extraPrompt": { "ref": "params.extraPrompt" },
74
- "llmExecutor": { "ref": "params.llmExecutor" },
75
- "llmModel": { "ref": "params.llmModel" }
76
- },
77
- "stopFlowIf": {
78
- "equals": [
79
- { "ref": "steps.design_review_loop.run_design_review_loop.value.executionState.terminationOutcome" },
80
- { "const": "stopped" }
81
- ]
82
- },
83
- "stopFlowOutcome": "stopped"
84
- }
85
- ]
86
- },
87
- {
88
- "id": "implement",
89
- "steps": [
90
- {
91
- "id": "resolve_planning_bundle",
92
- "node": "planning-bundle",
93
- "params": {
94
- "taskKey": { "ref": "params.taskKey" }
95
- }
96
- },
97
- {
98
- "id": "run_implement",
99
- "node": "llm-prompt",
100
- "routingGroup": "implementation",
101
- "prompt": {
102
- "templateRef": "implement",
103
- "vars": {
104
- "design_file": { "ref": "steps.implement.resolve_planning_bundle.value.designFile" },
105
- "design_json_file": { "ref": "steps.implement.resolve_planning_bundle.value.designJsonFile" },
106
- "plan_file": { "ref": "steps.implement.resolve_planning_bundle.value.planFile" },
107
- "plan_json_file": { "ref": "steps.implement.resolve_planning_bundle.value.planJsonFile" },
108
- "qa_file": { "ref": "steps.implement.resolve_planning_bundle.value.qaFile" },
109
- "qa_json_file": { "ref": "steps.implement.resolve_planning_bundle.value.qaJsonFile" },
110
- "project_guidance_file": { "const": "not provided" },
111
- "project_guidance_json_file": { "const": "not provided" }
112
- },
113
- "extraPrompt": { "ref": "params.extraPrompt" },
114
- "format": "task-prompt"
115
- },
116
- "params": {
117
- "labelText": { "const": "Running implementation mode locally" },
118
- "model": { "ref": "params.llmModel" },
119
- "executor": { "ref": "params.llmExecutor" }
120
- }
121
- },
122
- {
123
- "id": "notify_implement_complete",
124
- "node": "telegram-notify",
125
- "params": {
126
- "message": {
127
- "template": "Implementation phase for {taskKey} complete.",
128
- "vars": {
129
- "taskKey": { "ref": "params.taskKey" }
130
- }
131
- }
132
- }
133
- }
134
- ]
135
- },
136
- {
137
- "id": "review-loop",
138
- "steps": [
139
- {
140
- "id": "run_review_loop",
141
- "node": "flow-run",
142
- "stopFlowIf": {
143
- "not": {
144
- "equals": [
145
- { "ref": "steps.review-loop.run_review_loop.value.executionState.terminationOutcome" },
146
- { "const": "success" }
147
- ]
148
- }
149
- },
150
- "stopFlowOutcome": "stopped",
151
- "params": {
152
- "fileName": { "const": "review-loop.json" },
153
- "labelText": { "const": "Running review-loop" },
154
- "taskKey": { "ref": "params.taskKey" },
155
- "baseIteration": { "ref": "params.baseIteration" },
156
- "workspaceDir": { "ref": "params.workspaceDir" },
157
- "extraPrompt": { "ref": "params.extraPrompt" },
158
- "reviewFixPoints": { "ref": "params.reviewFixPoints" },
159
- "reviewBlockingSeverities": { "ref": "params.reviewBlockingSeverities" },
160
- "llmExecutor": { "ref": "params.llmExecutor" },
161
- "llmModel": { "ref": "params.llmModel" }
162
- }
163
- },
164
- {
165
- "id": "notify_task_complete",
166
- "node": "telegram-notify",
167
- "params": {
168
- "message": {
169
- "template": "Task {taskKey} complete.",
170
- "vars": {
171
- "taskKey": { "ref": "params.taskKey" }
172
- }
173
- }
174
- }
175
- }
176
- ]
177
- }
178
- ]
179
- }