agentweaver 0.1.8 → 0.1.9
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.
- package/README.md +20 -6
- package/dist/artifacts.js +24 -0
- package/dist/executors/configs/fetch-gitlab-diff-config.js +3 -0
- package/dist/executors/configs/opencode-config.js +6 -0
- package/dist/executors/fetch-gitlab-diff-executor.js +26 -0
- package/dist/executors/jira-fetch-executor.js +8 -2
- package/dist/executors/opencode-executor.js +35 -0
- package/dist/gitlab.js +199 -5
- package/dist/index.js +155 -116
- package/dist/interactive-ui.js +21 -2
- package/dist/jira.js +116 -14
- package/dist/pipeline/auto-flow.js +1 -1
- package/dist/pipeline/declarative-flows.js +41 -6
- package/dist/pipeline/flow-catalog.js +66 -0
- package/dist/pipeline/flow-specs/auto.json +183 -1
- package/dist/pipeline/flow-specs/gitlab-diff-review.json +226 -0
- package/dist/pipeline/flow-specs/gitlab-review.json +1 -31
- package/dist/pipeline/flow-specs/plan-opencode.json +603 -0
- package/dist/pipeline/flow-specs/plan.json +183 -1
- package/dist/pipeline/node-registry.js +80 -8
- package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +34 -0
- package/dist/pipeline/nodes/flow-run-node.js +2 -2
- package/dist/pipeline/nodes/jira-fetch-node.js +26 -2
- package/dist/pipeline/nodes/opencode-prompt-node.js +32 -0
- package/dist/pipeline/nodes/planning-questions-form-node.js +69 -0
- package/dist/pipeline/nodes/user-input-node.js +9 -1
- package/dist/pipeline/prompt-registry.js +3 -1
- package/dist/pipeline/registry.js +10 -0
- package/dist/pipeline/spec-loader.js +37 -3
- package/dist/pipeline/spec-types.js +43 -1
- package/dist/pipeline/spec-validator.js +53 -7
- package/dist/pipeline/value-resolver.js +15 -1
- package/dist/prompts.js +47 -12
- package/dist/scope.js +24 -32
- package/dist/structured-artifact-schemas.json +154 -1
- package/dist/structured-artifacts.js +2 -0
- package/dist/user-input.js +7 -0
- package/package.json +1 -1
package/dist/jira.js
CHANGED
|
@@ -3,6 +3,54 @@ import { writeFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { TaskRunnerError } from "./errors.js";
|
|
5
5
|
const ISSUE_KEY_RE = /^[A-Z][A-Z0-9_]*-[0-9]+$/;
|
|
6
|
+
const TEXT_ATTACHMENT_EXTENSIONS = new Set([".md", ".json", ".txt"]);
|
|
7
|
+
const DOWNLOAD_ONLY_ATTACHMENT_EXTENSIONS = new Set([".doc"]);
|
|
8
|
+
function sanitizeAttachmentFileName(fileName) {
|
|
9
|
+
const parsed = path.parse(fileName);
|
|
10
|
+
const baseName = parsed.name
|
|
11
|
+
.trim()
|
|
12
|
+
.replaceAll(/[^a-zA-Z0-9._-]+/g, "-")
|
|
13
|
+
.replaceAll(/-+/g, "-")
|
|
14
|
+
.replaceAll(/^-|-$/g, "");
|
|
15
|
+
const safeBaseName = baseName.length > 0 ? baseName : "attachment";
|
|
16
|
+
const extension = parsed.ext.replaceAll(/[^a-zA-Z0-9.]+/g, "").toLowerCase();
|
|
17
|
+
return `${safeBaseName}${extension}`;
|
|
18
|
+
}
|
|
19
|
+
function attachmentExtension(fileName) {
|
|
20
|
+
return path.extname(fileName).toLowerCase();
|
|
21
|
+
}
|
|
22
|
+
function parseJiraAttachments(issueBody) {
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(issueBody.toString("utf8"));
|
|
25
|
+
return Array.isArray(parsed.fields?.attachment) ? parsed.fields.attachment : [];
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function fetchAuthorizedBuffer(url, accept) {
|
|
32
|
+
const jiraApiKey = process.env.JIRA_API_KEY;
|
|
33
|
+
if (!jiraApiKey) {
|
|
34
|
+
throw new TaskRunnerError("JIRA_API_KEY is required for Jira fetch.");
|
|
35
|
+
}
|
|
36
|
+
const response = await fetch(url, {
|
|
37
|
+
headers: {
|
|
38
|
+
Authorization: `Bearer ${jiraApiKey}`,
|
|
39
|
+
Accept: accept,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new TaskRunnerError(`Failed to fetch Jira resource: HTTP ${response.status}`);
|
|
44
|
+
}
|
|
45
|
+
return Buffer.from(await response.arrayBuffer());
|
|
46
|
+
}
|
|
47
|
+
function toTextAttachmentContent(fileName, body) {
|
|
48
|
+
return [
|
|
49
|
+
`=== Attachment: ${fileName} ===`,
|
|
50
|
+
body.toString("utf8").trimEnd(),
|
|
51
|
+
"",
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
|
6
54
|
export function extractIssueKey(jiraRef) {
|
|
7
55
|
const normalizedRef = jiraRef.replace(/\/+$/, "");
|
|
8
56
|
if (normalizedRef.includes("://")) {
|
|
@@ -33,23 +81,77 @@ export function buildJiraApiUrl(jiraRef) {
|
|
|
33
81
|
const baseUrl = browseUrl.split("/browse/")[0];
|
|
34
82
|
return `${baseUrl}/rest/api/2/issue/${issueKey}`;
|
|
35
83
|
}
|
|
36
|
-
export async function fetchJiraIssue(jiraApiUrl, jiraTaskFile) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
84
|
+
export async function fetchJiraIssue(jiraApiUrl, jiraTaskFile, attachmentsManifestFile, attachmentsContextFile) {
|
|
85
|
+
const body = await fetchAuthorizedBuffer(jiraApiUrl, "application/json");
|
|
86
|
+
mkdirSync(path.dirname(jiraTaskFile), { recursive: true });
|
|
87
|
+
await writeFile(jiraTaskFile, body);
|
|
88
|
+
const attachments = parseJiraAttachments(body);
|
|
89
|
+
const manifestItems = [];
|
|
90
|
+
const planningContextChunks = [];
|
|
91
|
+
const attachmentsDir = attachmentsManifestFile ? path.join(path.dirname(attachmentsManifestFile), "jira-attachments") : null;
|
|
92
|
+
if (attachmentsManifestFile) {
|
|
93
|
+
mkdirSync(path.dirname(attachmentsManifestFile), { recursive: true });
|
|
40
94
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
95
|
+
if (attachmentsContextFile) {
|
|
96
|
+
mkdirSync(path.dirname(attachmentsContextFile), { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
if (attachmentsDir) {
|
|
99
|
+
mkdirSync(attachmentsDir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
for (const [index, attachment] of attachments.entries()) {
|
|
102
|
+
const fileName = typeof attachment.filename === "string" ? attachment.filename.trim() : "";
|
|
103
|
+
const downloadUrl = typeof attachment.content === "string" ? attachment.content.trim() : "";
|
|
104
|
+
if (!fileName || !downloadUrl) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const extension = attachmentExtension(fileName);
|
|
108
|
+
const shouldDownload = TEXT_ATTACHMENT_EXTENSIONS.has(extension) || DOWNLOAD_ONLY_ATTACHMENT_EXTENSIONS.has(extension);
|
|
109
|
+
if (!shouldDownload || !attachmentsDir) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const safeFileName = `${String(index + 1).padStart(3, "0")}-${sanitizeAttachmentFileName(fileName)}`;
|
|
113
|
+
const savedPath = path.join(attachmentsDir, safeFileName);
|
|
114
|
+
const attachmentBody = await fetchAuthorizedBuffer(downloadUrl, "*/*");
|
|
115
|
+
await writeFile(savedPath, attachmentBody);
|
|
116
|
+
const includedInPlanningContext = TEXT_ATTACHMENT_EXTENSIONS.has(extension);
|
|
117
|
+
if (includedInPlanningContext) {
|
|
118
|
+
planningContextChunks.push(toTextAttachmentContent(fileName, attachmentBody));
|
|
119
|
+
}
|
|
120
|
+
manifestItems.push({
|
|
121
|
+
id: typeof attachment.id === "string" ? attachment.id : String(attachment.id ?? ""),
|
|
122
|
+
fileName,
|
|
123
|
+
extension,
|
|
124
|
+
mimeType: typeof attachment.mimeType === "string" ? attachment.mimeType : null,
|
|
125
|
+
sizeBytes: typeof attachment.size === "number" ? attachment.size : null,
|
|
126
|
+
createdAt: typeof attachment.created === "string" ? attachment.created : null,
|
|
127
|
+
downloadUrl,
|
|
128
|
+
savedPath,
|
|
129
|
+
includedInPlanningContext,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const manifest = {
|
|
133
|
+
summary: {
|
|
134
|
+
downloadedCount: manifestItems.length,
|
|
135
|
+
planningContextCount: manifestItems.filter((item) => item.includedInPlanningContext).length,
|
|
45
136
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
137
|
+
attachments: manifestItems,
|
|
138
|
+
};
|
|
139
|
+
if (attachmentsManifestFile) {
|
|
140
|
+
await writeFile(attachmentsManifestFile, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
49
141
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
142
|
+
if (attachmentsContextFile) {
|
|
143
|
+
const contextBody = planningContextChunks.length > 0
|
|
144
|
+
? planningContextChunks.join("\n")
|
|
145
|
+
: "No supported text attachments were downloaded from Jira.\n";
|
|
146
|
+
await writeFile(attachmentsContextFile, contextBody, "utf8");
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
issueFile: jiraTaskFile,
|
|
150
|
+
downloadedAttachments: manifest.summary.downloadedCount,
|
|
151
|
+
planningContextAttachments: manifest.summary.planningContextCount,
|
|
152
|
+
...(attachmentsManifestFile ? { attachmentsManifestFile } : {}),
|
|
153
|
+
...(attachmentsContextFile ? { attachmentsContextFile } : {}),
|
|
154
|
+
};
|
|
53
155
|
}
|
|
54
156
|
export function requireJiraTaskFile(jiraTaskFile) {
|
|
55
157
|
if (!existsSync(jiraTaskFile)) {
|
|
@@ -1,16 +1,28 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { createNodeRegistry } from "./node-registry.js";
|
|
3
|
+
import { createExecutorRegistry } from "./registry.js";
|
|
2
4
|
import { compileFlowSpec } from "./spec-compiler.js";
|
|
3
|
-
import { loadFlowSpecSync } from "./spec-loader.js";
|
|
5
|
+
import { listBuiltInFlowSpecFiles, listProjectFlowSpecFiles, loadFlowSpecSync, projectFlowSpecsDir, resolveBuiltInFlowSpecPath } from "./spec-loader.js";
|
|
4
6
|
import { validateExpandedPhases, validateFlowSpec } from "./spec-validator.js";
|
|
5
7
|
const cache = new Map();
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
function toFlowSpecSource(ref) {
|
|
9
|
+
return ref.source === "built-in" ? { source: "built-in", fileName: ref.fileName } : { source: "project-local", filePath: ref.filePath };
|
|
10
|
+
}
|
|
11
|
+
function cacheKey(ref) {
|
|
12
|
+
return ref.source === "built-in" ? `built-in:${ref.fileName}` : `project-local:${path.resolve(ref.filePath)}`;
|
|
13
|
+
}
|
|
14
|
+
export function loadDeclarativeFlow(flow) {
|
|
15
|
+
const ref = typeof flow === "string" ? { source: "built-in", fileName: flow } : flow;
|
|
16
|
+
const cached = cache.get(cacheKey(ref));
|
|
8
17
|
if (cached) {
|
|
9
18
|
return cached;
|
|
10
19
|
}
|
|
11
|
-
const spec = loadFlowSpecSync(
|
|
20
|
+
const spec = loadFlowSpecSync(toFlowSpecSource(ref));
|
|
12
21
|
const nodeRegistry = createNodeRegistry();
|
|
13
|
-
|
|
22
|
+
const executorRegistry = createExecutorRegistry();
|
|
23
|
+
validateFlowSpec(spec, nodeRegistry, executorRegistry, {
|
|
24
|
+
resolveFlowByName: (fileName) => resolveNamedDeclarativeFlowRef(fileName, process.cwd()),
|
|
25
|
+
});
|
|
14
26
|
const phases = compileFlowSpec(spec);
|
|
15
27
|
validateExpandedPhases(phases);
|
|
16
28
|
const loaded = {
|
|
@@ -18,7 +30,30 @@ export function loadDeclarativeFlow(fileName) {
|
|
|
18
30
|
version: spec.version,
|
|
19
31
|
constants: spec.constants ?? {},
|
|
20
32
|
phases,
|
|
33
|
+
source: ref.source,
|
|
34
|
+
fileName: ref.source === "built-in" ? ref.fileName : path.basename(ref.filePath),
|
|
35
|
+
absolutePath: ref.source === "built-in" ? resolveBuiltInFlowSpecPath(ref.fileName) : path.resolve(ref.filePath),
|
|
21
36
|
};
|
|
22
|
-
cache.set(
|
|
37
|
+
cache.set(cacheKey(ref), loaded);
|
|
23
38
|
return loaded;
|
|
24
39
|
}
|
|
40
|
+
export function resolveNamedDeclarativeFlowRef(fileName, cwd) {
|
|
41
|
+
const projectMatches = listProjectFlowSpecFiles(cwd).filter((candidate) => path.basename(candidate) === fileName);
|
|
42
|
+
const builtInExists = listBuiltInFlowSpecFiles().includes(fileName);
|
|
43
|
+
if (projectMatches.length > 0 && builtInExists) {
|
|
44
|
+
throw new Error(`Ambiguous nested flow '${fileName}': both built-in and project-local specs exist in ${projectFlowSpecsDir(cwd)}.`);
|
|
45
|
+
}
|
|
46
|
+
if (projectMatches.length > 1) {
|
|
47
|
+
throw new Error(`Ambiguous project-local flow '${fileName}' in ${projectFlowSpecsDir(cwd)}.`);
|
|
48
|
+
}
|
|
49
|
+
if (projectMatches[0]) {
|
|
50
|
+
return { source: "project-local", filePath: projectMatches[0] };
|
|
51
|
+
}
|
|
52
|
+
if (builtInExists) {
|
|
53
|
+
return { source: "built-in", fileName };
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`Nested flow '${fileName}' was not found.`);
|
|
56
|
+
}
|
|
57
|
+
export function loadNamedDeclarativeFlow(fileName, cwd) {
|
|
58
|
+
return loadDeclarativeFlow(resolveNamedDeclarativeFlowRef(fileName, cwd));
|
|
59
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { TaskRunnerError } from "../errors.js";
|
|
3
|
+
import { loadAutoFlow } from "./auto-flow.js";
|
|
4
|
+
import { loadDeclarativeFlow } from "./declarative-flows.js";
|
|
5
|
+
import { listProjectFlowSpecFiles } from "./spec-loader.js";
|
|
6
|
+
export const INTERACTIVE_BUILT_IN_FLOWS = [
|
|
7
|
+
{ id: "auto", fileName: "auto.json" },
|
|
8
|
+
{ id: "bug-analyze", fileName: "bug-analyze.json" },
|
|
9
|
+
{ id: "bug-fix", fileName: "bug-fix.json" },
|
|
10
|
+
{ id: "gitlab-diff-review", fileName: "gitlab-diff-review.json" },
|
|
11
|
+
{ id: "gitlab-review", fileName: "gitlab-review.json" },
|
|
12
|
+
{ id: "mr-description", fileName: "mr-description.json" },
|
|
13
|
+
{ id: "plan", fileName: "plan.json" },
|
|
14
|
+
{ id: "task-describe", fileName: "task-describe.json" },
|
|
15
|
+
{ id: "implement", fileName: "implement.json" },
|
|
16
|
+
{ id: "review", fileName: "review.json" },
|
|
17
|
+
{ id: "review-fix", fileName: "review-fix.json" },
|
|
18
|
+
{ id: "run-go-tests-loop", fileName: "run-go-tests-loop.json" },
|
|
19
|
+
{ id: "run-go-linter-loop", fileName: "run-go-linter-loop.json" },
|
|
20
|
+
];
|
|
21
|
+
function loadBuiltInCatalogEntry(id, fileName) {
|
|
22
|
+
const flow = id === "auto" ? loadAutoFlow() : loadDeclarativeFlow({ source: "built-in", fileName });
|
|
23
|
+
return {
|
|
24
|
+
id,
|
|
25
|
+
source: "built-in",
|
|
26
|
+
fileName,
|
|
27
|
+
absolutePath: flow.absolutePath,
|
|
28
|
+
flow,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function loadProjectCatalogEntry(filePath) {
|
|
32
|
+
const flow = loadDeclarativeFlow({ source: "project-local", filePath });
|
|
33
|
+
return {
|
|
34
|
+
id: path.basename(filePath, path.extname(filePath)),
|
|
35
|
+
source: "project-local",
|
|
36
|
+
fileName: path.basename(filePath),
|
|
37
|
+
absolutePath: path.resolve(filePath),
|
|
38
|
+
flow,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function loadInteractiveFlowCatalog(cwd) {
|
|
42
|
+
const entries = INTERACTIVE_BUILT_IN_FLOWS.map((entry) => loadBuiltInCatalogEntry(entry.id, entry.fileName));
|
|
43
|
+
for (const filePath of listProjectFlowSpecFiles(cwd)) {
|
|
44
|
+
entries.push(loadProjectCatalogEntry(filePath));
|
|
45
|
+
}
|
|
46
|
+
const byId = new Map();
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
const duplicate = byId.get(entry.id);
|
|
49
|
+
if (duplicate) {
|
|
50
|
+
throw new TaskRunnerError(`Flow id '${entry.id}' conflicts between ${duplicate.absolutePath} and ${entry.absolutePath}. Rename one of the flow files.`);
|
|
51
|
+
}
|
|
52
|
+
byId.set(entry.id, entry);
|
|
53
|
+
}
|
|
54
|
+
return entries;
|
|
55
|
+
}
|
|
56
|
+
export function findCatalogEntry(flowId, entries) {
|
|
57
|
+
return entries.find((entry) => entry.id === flowId);
|
|
58
|
+
}
|
|
59
|
+
export function isBuiltInCommandFlowId(flowId) {
|
|
60
|
+
return INTERACTIVE_BUILT_IN_FLOWS.some((entry) => entry.id === flowId);
|
|
61
|
+
}
|
|
62
|
+
export function toDeclarativeFlowRef(entry) {
|
|
63
|
+
return entry.source === "built-in"
|
|
64
|
+
? { source: "built-in", fileName: entry.fileName }
|
|
65
|
+
: { source: "project-local", filePath: entry.absolutePath };
|
|
66
|
+
}
|
|
@@ -18,6 +18,18 @@
|
|
|
18
18
|
"kind": "jira-task-file",
|
|
19
19
|
"taskKey": { "ref": "params.taskKey" }
|
|
20
20
|
}
|
|
21
|
+
},
|
|
22
|
+
"attachmentsManifestFile": {
|
|
23
|
+
"artifact": {
|
|
24
|
+
"kind": "jira-attachments-manifest-file",
|
|
25
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"attachmentsContextFile": {
|
|
29
|
+
"artifact": {
|
|
30
|
+
"kind": "jira-attachments-context-file",
|
|
31
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
32
|
+
}
|
|
21
33
|
}
|
|
22
34
|
},
|
|
23
35
|
"expect": [
|
|
@@ -30,6 +42,26 @@
|
|
|
30
42
|
}
|
|
31
43
|
},
|
|
32
44
|
"message": "Jira fetch node did not produce the Jira task file."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"kind": "require-file",
|
|
48
|
+
"path": {
|
|
49
|
+
"artifact": {
|
|
50
|
+
"kind": "jira-attachments-manifest-file",
|
|
51
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"message": "Jira fetch node did not produce the Jira attachments manifest file."
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"kind": "require-file",
|
|
58
|
+
"path": {
|
|
59
|
+
"artifact": {
|
|
60
|
+
"kind": "jira-attachments-context-file",
|
|
61
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"message": "Jira fetch node did not produce the Jira attachments context file."
|
|
33
65
|
}
|
|
34
66
|
]
|
|
35
67
|
},
|
|
@@ -166,6 +198,139 @@
|
|
|
166
198
|
}
|
|
167
199
|
]
|
|
168
200
|
},
|
|
201
|
+
{
|
|
202
|
+
"id": "generate_planning_questions",
|
|
203
|
+
"node": "codex-local-prompt",
|
|
204
|
+
"prompt": {
|
|
205
|
+
"templateRef": "plan-questions",
|
|
206
|
+
"vars": {
|
|
207
|
+
"jira_task_file": {
|
|
208
|
+
"artifact": {
|
|
209
|
+
"kind": "jira-task-file",
|
|
210
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"jira_attachments_manifest_file": {
|
|
214
|
+
"artifact": {
|
|
215
|
+
"kind": "jira-attachments-manifest-file",
|
|
216
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"jira_attachments_context_file": {
|
|
220
|
+
"artifact": {
|
|
221
|
+
"kind": "jira-attachments-context-file",
|
|
222
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
"planning_questions_json_file": {
|
|
226
|
+
"artifact": {
|
|
227
|
+
"kind": "planning-questions-json-file",
|
|
228
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
"extraPrompt": { "ref": "params.extraPrompt" },
|
|
233
|
+
"format": "task-prompt"
|
|
234
|
+
},
|
|
235
|
+
"params": {
|
|
236
|
+
"labelText": { "const": "Generating planning questions" },
|
|
237
|
+
"model": { "const": "gpt-5.4" },
|
|
238
|
+
"requiredArtifacts": {
|
|
239
|
+
"list": [
|
|
240
|
+
{
|
|
241
|
+
"artifact": {
|
|
242
|
+
"kind": "planning-questions-json-file",
|
|
243
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
"expect": [
|
|
250
|
+
{
|
|
251
|
+
"kind": "require-artifacts",
|
|
252
|
+
"when": { "not": { "ref": "context.dryRun" } },
|
|
253
|
+
"paths": {
|
|
254
|
+
"list": [
|
|
255
|
+
{
|
|
256
|
+
"artifact": {
|
|
257
|
+
"kind": "planning-questions-json-file",
|
|
258
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
"message": "Planning questions step did not produce the questions artifact."
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"kind": "require-structured-artifacts",
|
|
267
|
+
"when": { "not": { "ref": "context.dryRun" } },
|
|
268
|
+
"items": [
|
|
269
|
+
{
|
|
270
|
+
"path": {
|
|
271
|
+
"artifact": {
|
|
272
|
+
"kind": "planning-questions-json-file",
|
|
273
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
"schemaId": "planning-questions/v1"
|
|
277
|
+
}
|
|
278
|
+
],
|
|
279
|
+
"message": "Planning questions step produced invalid structured artifacts."
|
|
280
|
+
}
|
|
281
|
+
]
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"id": "build_planning_questions_form",
|
|
285
|
+
"node": "planning-questions-form",
|
|
286
|
+
"params": {
|
|
287
|
+
"planningQuestionsJsonFile": {
|
|
288
|
+
"artifact": {
|
|
289
|
+
"kind": "planning-questions-json-file",
|
|
290
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
"formId": { "const": "planning-questions" },
|
|
294
|
+
"title": { "const": "Planning Questions" },
|
|
295
|
+
"description": {
|
|
296
|
+
"const": "Ответьте на вопросы модели, если они нужны для точного design/plan. Если вопросов нет, шаг завершится автоматически."
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"id": "collect_planning_answers",
|
|
302
|
+
"node": "user-input",
|
|
303
|
+
"params": {
|
|
304
|
+
"formId": { "ref": "steps.plan.build_planning_questions_form.value.formId" },
|
|
305
|
+
"title": { "ref": "steps.plan.build_planning_questions_form.value.title" },
|
|
306
|
+
"description": { "ref": "steps.plan.build_planning_questions_form.value.description" },
|
|
307
|
+
"submitLabel": { "const": "Continue planning" },
|
|
308
|
+
"fields": { "ref": "steps.plan.build_planning_questions_form.value.fields" },
|
|
309
|
+
"outputFile": {
|
|
310
|
+
"artifact": {
|
|
311
|
+
"kind": "planning-answers-json-file",
|
|
312
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
"expect": [
|
|
317
|
+
{
|
|
318
|
+
"kind": "require-structured-artifacts",
|
|
319
|
+
"items": [
|
|
320
|
+
{
|
|
321
|
+
"path": {
|
|
322
|
+
"artifact": {
|
|
323
|
+
"kind": "planning-answers-json-file",
|
|
324
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
"schemaId": "user-input/v1"
|
|
328
|
+
}
|
|
329
|
+
],
|
|
330
|
+
"message": "Planning answers input is missing or invalid."
|
|
331
|
+
}
|
|
332
|
+
]
|
|
333
|
+
},
|
|
169
334
|
{
|
|
170
335
|
"id": "run_codex_plan",
|
|
171
336
|
"node": "codex-local-prompt",
|
|
@@ -178,6 +343,18 @@
|
|
|
178
343
|
"taskKey": { "ref": "params.taskKey" }
|
|
179
344
|
}
|
|
180
345
|
},
|
|
346
|
+
"jira_attachments_manifest_file": {
|
|
347
|
+
"artifact": {
|
|
348
|
+
"kind": "jira-attachments-manifest-file",
|
|
349
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
"jira_attachments_context_file": {
|
|
353
|
+
"artifact": {
|
|
354
|
+
"kind": "jira-attachments-context-file",
|
|
355
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
356
|
+
}
|
|
357
|
+
},
|
|
181
358
|
"design_file": {
|
|
182
359
|
"artifact": {
|
|
183
360
|
"kind": "design-file",
|
|
@@ -215,7 +392,12 @@
|
|
|
215
392
|
}
|
|
216
393
|
}
|
|
217
394
|
},
|
|
218
|
-
"extraPrompt": {
|
|
395
|
+
"extraPrompt": {
|
|
396
|
+
"appendPrompt": {
|
|
397
|
+
"base": { "ref": "params.extraPrompt" },
|
|
398
|
+
"suffix": { "ref": "steps.plan.collect_planning_answers.value.promptSuffix" }
|
|
399
|
+
}
|
|
400
|
+
},
|
|
219
401
|
"format": "task-prompt"
|
|
220
402
|
},
|
|
221
403
|
"params": {
|