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.
- package/README.md +29 -7
- package/dist/artifact-manifest.js +219 -0
- package/dist/artifacts.js +21 -1
- package/dist/doctor/checks/cwd-context.js +4 -3
- package/dist/doctor/checks/env-diagnostics.js +193 -71
- package/dist/doctor/checks/flow-readiness.js +212 -203
- package/dist/doctor/index.js +1 -1
- package/dist/doctor/orchestrator.js +18 -7
- package/dist/doctor/runner.js +9 -8
- package/dist/doctor/types.js +12 -0
- package/dist/flow-state.js +75 -15
- package/dist/index.js +499 -199
- package/dist/interactive/blessed-session.js +361 -0
- package/dist/interactive/controller.js +1293 -0
- package/dist/interactive/create-interactive-session.js +5 -0
- package/dist/interactive/ink/index.js +576 -0
- package/dist/interactive/progress.js +245 -0
- package/dist/interactive/selectors.js +14 -0
- package/dist/interactive/session.js +1 -0
- package/dist/interactive/state.js +34 -0
- package/dist/interactive/tree.js +155 -0
- package/dist/interactive/types.js +1 -0
- package/dist/interactive/view-model.js +1 -0
- package/dist/interactive-ui.js +159 -194
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/declarative-flow-runner.js +212 -6
- package/dist/pipeline/declarative-flows.js +27 -0
- package/dist/pipeline/execution-routing-config.js +15 -0
- package/dist/pipeline/flow-catalog.js +23 -3
- package/dist/pipeline/flow-run-resume.js +29 -0
- package/dist/pipeline/flow-specs/auto-common.json +89 -360
- package/dist/pipeline/flow-specs/auto-golang.json +58 -363
- package/dist/pipeline/flow-specs/auto-simple.json +141 -0
- package/dist/pipeline/flow-specs/bugz/bug-analyze.json +2 -0
- package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +304 -0
- package/dist/pipeline/flow-specs/design-review.json +249 -0
- package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +11 -0
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +2 -0
- package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
- package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +2 -0
- package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +2 -0
- package/dist/pipeline/flow-specs/implement.json +24 -5
- package/dist/pipeline/flow-specs/instant-task.json +177 -0
- package/dist/pipeline/flow-specs/normalize-task-source.json +311 -0
- package/dist/pipeline/flow-specs/plan-revise.json +267 -0
- package/dist/pipeline/flow-specs/plan.json +48 -70
- package/dist/pipeline/flow-specs/review/review-fix.json +24 -4
- package/dist/pipeline/flow-specs/review/review-loop.json +351 -45
- package/dist/pipeline/flow-specs/review/review-project-loop.json +590 -0
- package/dist/pipeline/flow-specs/review/review-project.json +12 -0
- package/dist/pipeline/flow-specs/review/review.json +37 -31
- package/dist/pipeline/flow-specs/task-describe.json +62 -2
- package/dist/pipeline/flow-specs/task-source/jira-fetch.json +70 -0
- package/dist/pipeline/flow-specs/task-source/manual-input.json +216 -0
- package/dist/pipeline/node-registry.js +49 -1
- package/dist/pipeline/node-runner.js +3 -2
- package/dist/pipeline/nodes/build-review-fix-prompt-node.js +5 -1
- package/dist/pipeline/nodes/clear-ready-to-merge-node.js +11 -0
- package/dist/pipeline/nodes/commit-message-form-node.js +8 -0
- package/dist/pipeline/nodes/design-review-verdict-node.js +36 -0
- package/dist/pipeline/nodes/ensure-summary-json-node.js +70 -0
- package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +19 -2
- package/dist/pipeline/nodes/fetch-gitlab-review-node.js +19 -2
- package/dist/pipeline/nodes/flow-run-node.js +226 -7
- package/dist/pipeline/nodes/git-commit-form-node.js +8 -0
- package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +19 -2
- package/dist/pipeline/nodes/jira-fetch-node.js +50 -4
- package/dist/pipeline/nodes/llm-prompt-node.js +32 -12
- package/dist/pipeline/nodes/planning-bundle-node.js +10 -0
- package/dist/pipeline/nodes/review-verdict-node.js +86 -0
- package/dist/pipeline/nodes/select-files-form-node.js +8 -0
- package/dist/pipeline/nodes/structured-summary-node.js +24 -0
- package/dist/pipeline/nodes/user-input-node.js +38 -3
- package/dist/pipeline/nodes/write-selection-file-node.js +20 -4
- package/dist/pipeline/prompt-registry.js +5 -1
- package/dist/pipeline/prompt-runtime.js +4 -1
- package/dist/pipeline/review-iteration.js +26 -0
- package/dist/pipeline/spec-compiler.js +2 -0
- package/dist/pipeline/spec-types.js +5 -0
- package/dist/pipeline/spec-validator.js +14 -0
- package/dist/pipeline/value-resolver.js +84 -1
- package/dist/prompts.js +82 -13
- package/dist/review-severity.js +45 -0
- package/dist/runtime/artifact-registry.js +402 -0
- package/dist/runtime/design-review-input-contract.js +113 -0
- package/dist/runtime/env-loader.js +3 -0
- package/dist/runtime/execution-routing-store.js +134 -0
- package/dist/runtime/execution-routing.js +227 -0
- package/dist/runtime/interactive-execution-routing.js +462 -0
- package/dist/runtime/plan-revise-input-contract.js +147 -0
- package/dist/runtime/planning-bundle.js +123 -0
- package/dist/runtime/ready-to-merge.js +31 -0
- package/dist/runtime/review-input-contract.js +100 -0
- package/dist/scope.js +11 -2
- package/dist/structured-artifact-schema-registry.js +10 -0
- package/dist/structured-artifact-schemas.json +257 -1
- package/dist/structured-artifacts.js +83 -6
- package/dist/user-input.js +70 -3
- package/package.json +6 -3
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { buildLogicalKeyForPayload } from "../artifact-manifest.js";
|
|
1
5
|
import { TaskRunnerError } from "../errors.js";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
6
|
import { runNodeChecks } from "./checks.js";
|
|
4
7
|
import { runNodeByKind } from "./node-runner.js";
|
|
5
|
-
import { renderPrompt } from "./prompt-runtime.js";
|
|
6
|
-
import { evaluateCondition, resolveParams, resolveValue } from "./value-resolver.js";
|
|
8
|
+
import { renderPrompt, resolvePromptBindingInputs } from "./prompt-runtime.js";
|
|
9
|
+
import { collectResolvedArtifactPathCandidates, collectResolvedPromptArtifactPathCandidates, evaluateCondition, resolveParams, resolveValue, } from "./value-resolver.js";
|
|
7
10
|
function nowIso8601() {
|
|
8
11
|
return new Date().toISOString();
|
|
9
12
|
}
|
|
@@ -24,12 +27,17 @@ function toJsonValue(value) {
|
|
|
24
27
|
}
|
|
25
28
|
function ensureExecutionState(options) {
|
|
26
29
|
if (options.executionState) {
|
|
30
|
+
options.executionState.runId ??= randomUUID();
|
|
31
|
+
options.executionState.publicationRunId ??= randomUUID();
|
|
27
32
|
return options.executionState;
|
|
28
33
|
}
|
|
29
34
|
return {
|
|
35
|
+
runId: randomUUID(),
|
|
36
|
+
publicationRunId: randomUUID(),
|
|
30
37
|
flowKind: options.flowKind ?? "declarative-flow",
|
|
31
38
|
flowVersion: options.flowVersion ?? 1,
|
|
32
39
|
terminated: false,
|
|
40
|
+
terminationOutcome: "success",
|
|
33
41
|
phases: [],
|
|
34
42
|
};
|
|
35
43
|
}
|
|
@@ -138,6 +146,129 @@ function runAfterAction(action, pipelineContext, context) {
|
|
|
138
146
|
}
|
|
139
147
|
throw new TaskRunnerError(`Unsupported after action kind: ${action.kind ?? "unknown"}`);
|
|
140
148
|
}
|
|
149
|
+
function shouldPublishManifest(output) {
|
|
150
|
+
return output.manifest?.publish === true;
|
|
151
|
+
}
|
|
152
|
+
function resolveOutputLogicalKey(scopeKey, output) {
|
|
153
|
+
return output.manifest?.logicalKey ?? buildLogicalKeyForPayload(scopeKey, output.path);
|
|
154
|
+
}
|
|
155
|
+
function validateDistinctLogicalKeys(scopeKey, phaseId, stepId, outputs) {
|
|
156
|
+
const pathsByLogicalKey = new Map();
|
|
157
|
+
for (const output of outputs) {
|
|
158
|
+
const logicalKey = resolveOutputLogicalKey(scopeKey, output);
|
|
159
|
+
const existingPath = pathsByLogicalKey.get(logicalKey);
|
|
160
|
+
if (existingPath && existingPath !== output.path) {
|
|
161
|
+
throw new TaskRunnerError(`Step '${phaseId}:${stepId}' produced duplicate logical_key '${logicalKey}' for outputs '${existingPath}' and '${output.path}'.`);
|
|
162
|
+
}
|
|
163
|
+
pathsByLogicalKey.set(logicalKey, output.path);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function producerMetadata(resultValue) {
|
|
167
|
+
if (!resultValue || typeof resultValue !== "object" || Array.isArray(resultValue)) {
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
const record = resultValue;
|
|
171
|
+
return {
|
|
172
|
+
...(typeof record["executor"] === "string" ? { executor: record["executor"] } : {}),
|
|
173
|
+
...(typeof record["model"] === "string" ? { model: record["model"] } : {}),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function collectNestedPublishedArtifacts(resultValue) {
|
|
177
|
+
if (!resultValue || typeof resultValue !== "object" || Array.isArray(resultValue)) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
const rawArtifacts = resultValue["publishedArtifacts"];
|
|
181
|
+
if (!Array.isArray(rawArtifacts)) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
return rawArtifacts.filter((artifact) => {
|
|
185
|
+
if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const record = artifact;
|
|
189
|
+
return typeof record["artifact_id"] === "string" && typeof record["payload_path"] === "string";
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function mergePublishedArtifacts(...groups) {
|
|
193
|
+
const merged = [];
|
|
194
|
+
const seen = new Set();
|
|
195
|
+
for (const group of groups) {
|
|
196
|
+
for (const artifact of group) {
|
|
197
|
+
const key = artifact.artifact_id;
|
|
198
|
+
if (seen.has(key)) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
seen.add(key);
|
|
202
|
+
merged.push(artifact);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return merged;
|
|
206
|
+
}
|
|
207
|
+
function normalizeCandidatePath(value) {
|
|
208
|
+
return path.resolve(value);
|
|
209
|
+
}
|
|
210
|
+
function collectLineageInputs(values, artifactPaths, pipelineContext, excludedPaths = []) {
|
|
211
|
+
const seen = new Set();
|
|
212
|
+
const inputs = [];
|
|
213
|
+
const excluded = new Set(Array.from(excludedPaths, (candidate) => normalizeCandidatePath(candidate)));
|
|
214
|
+
const addInput = (input) => {
|
|
215
|
+
if (excluded.has(normalizeCandidatePath(input.path))) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const key = `${input.source}:${input.artifact_id ?? ""}:${normalizeCandidatePath(input.path)}`;
|
|
219
|
+
if (seen.has(key)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
seen.add(key);
|
|
223
|
+
inputs.push(input);
|
|
224
|
+
};
|
|
225
|
+
for (const artifactPath of artifactPaths) {
|
|
226
|
+
addInput(pipelineContext.runtime.artifactRegistry.resolveLineageInputFromPath(pipelineContext.issueKey, artifactPath));
|
|
227
|
+
}
|
|
228
|
+
const visit = (value) => {
|
|
229
|
+
if (Array.isArray(value)) {
|
|
230
|
+
value.forEach((item) => visit(item));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!value || typeof value !== "object") {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const record = value;
|
|
237
|
+
if (typeof record["artifact_id"] === "string" && typeof record["payload_path"] === "string") {
|
|
238
|
+
const payloadPath = String(record["payload_path"]);
|
|
239
|
+
if (!excluded.has(normalizeCandidatePath(payloadPath))) {
|
|
240
|
+
addInput({
|
|
241
|
+
source: "manifest",
|
|
242
|
+
path: payloadPath,
|
|
243
|
+
artifact_id: String(record["artifact_id"]),
|
|
244
|
+
...(typeof record["logical_key"] === "string" ? { logical_key: String(record["logical_key"]) } : {}),
|
|
245
|
+
...(typeof record["schema_id"] === "string" ? { schema_id: String(record["schema_id"]) } : {}),
|
|
246
|
+
...(typeof record["schema_version"] === "number" ? { schema_version: Number(record["schema_version"]) } : {}),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
for (const candidate of Object.values(record)) {
|
|
251
|
+
visit(candidate);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
values.forEach((value) => visit(value));
|
|
255
|
+
return inputs.sort((left, right) => left.path.localeCompare(right.path));
|
|
256
|
+
}
|
|
257
|
+
function mergeLineageInputs(...groups) {
|
|
258
|
+
const seen = new Set();
|
|
259
|
+
const merged = [];
|
|
260
|
+
for (const group of groups) {
|
|
261
|
+
for (const input of group) {
|
|
262
|
+
const key = `${input.source}:${input.artifact_id ?? ""}:${normalizeCandidatePath(input.path)}`;
|
|
263
|
+
if (seen.has(key)) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
seen.add(key);
|
|
267
|
+
merged.push(input);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return merged.sort((left, right) => left.path.localeCompare(right.path));
|
|
271
|
+
}
|
|
141
272
|
export async function runExpandedPhase(phase, pipelineContext, flowParams, flowConstants, options = {}) {
|
|
142
273
|
const executionState = ensureExecutionState(options);
|
|
143
274
|
const phaseState = ensurePhaseState(executionState, phase);
|
|
@@ -153,6 +284,9 @@ export async function runExpandedPhase(phase, pipelineContext, flowParams, flowC
|
|
|
153
284
|
id: step.id,
|
|
154
285
|
status: phaseState.steps[stepIndex]?.status === "skipped" ? "skipped" : "done",
|
|
155
286
|
...(phaseState.steps[stepIndex]?.outputs ? { outputs: phaseState.steps[stepIndex]?.outputs } : {}),
|
|
287
|
+
...(phaseState.steps[stepIndex]?.publishedArtifacts
|
|
288
|
+
? { publishedArtifacts: phaseState.steps[stepIndex]?.publishedArtifacts }
|
|
289
|
+
: {}),
|
|
156
290
|
})),
|
|
157
291
|
};
|
|
158
292
|
}
|
|
@@ -206,7 +340,12 @@ export async function runExpandedPhase(phase, pipelineContext, flowParams, flowC
|
|
|
206
340
|
throw new TaskRunnerError(`Missing execution state for step '${step.id}' in phase '${phase.id}'`);
|
|
207
341
|
}
|
|
208
342
|
if (stepState.status === "done" || stepState.status === "skipped") {
|
|
209
|
-
steps.push({
|
|
343
|
+
steps.push({
|
|
344
|
+
id: step.id,
|
|
345
|
+
status: stepState.status,
|
|
346
|
+
...(stepState.outputs ? { outputs: stepState.outputs } : {}),
|
|
347
|
+
...(stepState.publishedArtifacts ? { publishedArtifacts: stepState.publishedArtifacts } : {}),
|
|
348
|
+
});
|
|
210
349
|
continue;
|
|
211
350
|
}
|
|
212
351
|
stepState.status = "running";
|
|
@@ -220,10 +359,23 @@ export async function runExpandedPhase(phase, pipelineContext, flowParams, flowC
|
|
|
220
359
|
continue;
|
|
221
360
|
}
|
|
222
361
|
const params = resolveParams(step.params, stepContext);
|
|
362
|
+
if (step.routingGroup) {
|
|
363
|
+
params.routingGroup = step.routingGroup;
|
|
364
|
+
}
|
|
365
|
+
const promptInputs = step.prompt ? resolvePromptBindingInputs(step.prompt, stepContext) : {};
|
|
223
366
|
if (step.prompt) {
|
|
224
367
|
params.prompt = renderPrompt(step.prompt, stepContext);
|
|
225
368
|
}
|
|
226
|
-
const result = await runNodeByKind(step.node, pipelineContext, params, {
|
|
369
|
+
const result = await runNodeByKind(step.node, pipelineContext, params, {
|
|
370
|
+
skipChecks: step.expect !== undefined,
|
|
371
|
+
contextOverrides: {
|
|
372
|
+
...(stepState.value !== undefined ? { resumeStepValue: stepState.value } : {}),
|
|
373
|
+
persistRunningStepValue: async (value) => {
|
|
374
|
+
stepState.value = value;
|
|
375
|
+
await options.onStateChange?.(executionState);
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
});
|
|
227
379
|
stepState.value = toJsonValue(result.value);
|
|
228
380
|
const stepOutputs = toStepOutputs(result.value);
|
|
229
381
|
if (stepOutputs) {
|
|
@@ -249,15 +401,69 @@ export async function runExpandedPhase(phase, pipelineContext, flowParams, flowC
|
|
|
249
401
|
runAfterAction(action, pipelineContext, stepContext);
|
|
250
402
|
});
|
|
251
403
|
}
|
|
404
|
+
const publishedArtifacts = [];
|
|
405
|
+
const nestedPublishedArtifacts = collectNestedPublishedArtifacts(result.value);
|
|
406
|
+
const publishableOutputs = (result.outputs ?? []).filter((output) => shouldPublishManifest(output));
|
|
407
|
+
if (!pipelineContext.dryRun && publishableOutputs.length > 0) {
|
|
408
|
+
const resolvedPublishableOutputs = [];
|
|
409
|
+
for (const output of publishableOutputs) {
|
|
410
|
+
if (!existsSync(output.path)) {
|
|
411
|
+
if (!output.required) {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
throw new TaskRunnerError(`Cannot publish manifest for missing output ${output.path}.`);
|
|
415
|
+
}
|
|
416
|
+
resolvedPublishableOutputs.push(output);
|
|
417
|
+
}
|
|
418
|
+
validateDistinctLogicalKeys(pipelineContext.issueKey, phase.id, step.id, resolvedPublishableOutputs);
|
|
419
|
+
const publishedOutputPaths = resolvedPublishableOutputs.map((output) => output.path);
|
|
420
|
+
const lineageInputs = collectLineageInputs([result.value], [
|
|
421
|
+
...Object.values(step.params ?? {}).flatMap((value) => collectResolvedArtifactPathCandidates(value, stepContext)),
|
|
422
|
+
...collectResolvedPromptArtifactPathCandidates(step.prompt, stepContext),
|
|
423
|
+
], pipelineContext, publishedOutputPaths);
|
|
424
|
+
for (const output of resolvedPublishableOutputs) {
|
|
425
|
+
publishedArtifacts.push(pipelineContext.runtime.artifactRegistry.publish({
|
|
426
|
+
scopeKey: pipelineContext.issueKey,
|
|
427
|
+
runId: executionState.runId ?? randomUUID(),
|
|
428
|
+
publicationRunId: executionState.publicationRunId ?? randomUUID(),
|
|
429
|
+
flowId: executionState.flowKind,
|
|
430
|
+
phaseId: phase.id,
|
|
431
|
+
stepId: step.id,
|
|
432
|
+
nodeKind: step.node,
|
|
433
|
+
nodeVersion: 1,
|
|
434
|
+
kind: output.kind,
|
|
435
|
+
payloadPath: output.path,
|
|
436
|
+
...(output.manifest?.logicalKey ? { logicalKey: output.manifest.logicalKey } : {}),
|
|
437
|
+
...(output.manifest?.schemaId ? { schemaId: output.manifest.schemaId } : {}),
|
|
438
|
+
...(output.manifest?.schemaVersion ? { schemaVersion: output.manifest.schemaVersion } : {}),
|
|
439
|
+
...(output.manifest?.payloadFamily ? { payloadFamily: output.manifest.payloadFamily } : {}),
|
|
440
|
+
inputs: mergeLineageInputs(lineageInputs, output.manifest?.inputRefs ?? []),
|
|
441
|
+
...producerMetadata(result.value),
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const allPublishedArtifacts = mergePublishedArtifacts(publishedArtifacts, nestedPublishedArtifacts);
|
|
252
446
|
const stopFlow = step.stopFlowIf ? evaluateCondition(step.stopFlowIf, stepContext) : false;
|
|
253
447
|
stepState.status = "done";
|
|
254
448
|
stepState.finishedAt = nowIso8601();
|
|
255
449
|
stepState.stopFlow = stopFlow;
|
|
450
|
+
if (allPublishedArtifacts.length > 0) {
|
|
451
|
+
stepState.publishedArtifacts = allPublishedArtifacts;
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
delete stepState.publishedArtifacts;
|
|
455
|
+
}
|
|
256
456
|
await options.onStateChange?.(executionState);
|
|
257
|
-
steps.push({
|
|
457
|
+
steps.push({
|
|
458
|
+
id: step.id,
|
|
459
|
+
status: "done",
|
|
460
|
+
...(stepState.outputs ? { outputs: stepState.outputs } : {}),
|
|
461
|
+
...(stepState.publishedArtifacts ? { publishedArtifacts: stepState.publishedArtifacts } : {}),
|
|
462
|
+
});
|
|
258
463
|
if (stopFlow) {
|
|
259
464
|
executionState.terminated = true;
|
|
260
465
|
executionState.terminationReason = `Stopped by ${phase.id}:${step.id}`;
|
|
466
|
+
executionState.terminationOutcome = step.stopFlowOutcome ?? "success";
|
|
261
467
|
phaseState.status = "done";
|
|
262
468
|
phaseState.finishedAt = nowIso8601();
|
|
263
469
|
await options.onStateChange?.(executionState);
|
|
@@ -29,6 +29,7 @@ export function loadDeclarativeFlow(flow) {
|
|
|
29
29
|
kind: spec.kind,
|
|
30
30
|
version: spec.version,
|
|
31
31
|
...(spec.description !== undefined ? { description: spec.description } : {}),
|
|
32
|
+
...(spec.catalogVisibility !== undefined ? { catalogVisibility: spec.catalogVisibility } : {}),
|
|
32
33
|
constants: spec.constants ?? {},
|
|
33
34
|
phases,
|
|
34
35
|
source: ref.source,
|
|
@@ -61,3 +62,29 @@ export function resolveNamedDeclarativeFlowRef(fileName, cwd) {
|
|
|
61
62
|
export function loadNamedDeclarativeFlow(fileName, cwd) {
|
|
62
63
|
return loadDeclarativeFlow(resolveNamedDeclarativeFlowRef(fileName, cwd));
|
|
63
64
|
}
|
|
65
|
+
export function collectFlowRoutingGroups(flow, cwd, visited = new Set()) {
|
|
66
|
+
if (visited.has(flow.absolutePath)) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
visited.add(flow.absolutePath);
|
|
70
|
+
const groups = new Set();
|
|
71
|
+
for (const phase of flow.phases) {
|
|
72
|
+
for (const step of phase.steps) {
|
|
73
|
+
if (step.routingGroup) {
|
|
74
|
+
groups.add(step.routingGroup);
|
|
75
|
+
}
|
|
76
|
+
if (step.node !== "flow-run") {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const nestedFlowName = step.params?.fileName;
|
|
80
|
+
if (!nestedFlowName || !("const" in nestedFlowName) || typeof nestedFlowName.const !== "string") {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const nestedFlow = loadNamedDeclarativeFlow(nestedFlowName.const, cwd);
|
|
84
|
+
for (const nestedGroup of collectFlowRoutingGroups(nestedFlow, cwd, visited)) {
|
|
85
|
+
groups.add(nestedGroup);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return [...groups];
|
|
90
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const EXECUTION_ROUTING_GROUPS = [
|
|
2
|
+
"planning",
|
|
3
|
+
"design-review",
|
|
4
|
+
"implementation",
|
|
5
|
+
"review",
|
|
6
|
+
"repair-loop",
|
|
7
|
+
"local-fix-loop",
|
|
8
|
+
];
|
|
9
|
+
export const BUILT_IN_EXECUTION_PRESET_IDS = [
|
|
10
|
+
"balanced",
|
|
11
|
+
"quality-first",
|
|
12
|
+
"cheap-first",
|
|
13
|
+
"codex-only",
|
|
14
|
+
"opencode-only",
|
|
15
|
+
];
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { TaskRunnerError } from "../errors.js";
|
|
3
3
|
import { loadAutoGolangFlow } from "./auto-flow.js";
|
|
4
|
-
import { loadDeclarativeFlow } from "./declarative-flows.js";
|
|
4
|
+
import { collectFlowRoutingGroups, loadDeclarativeFlow } from "./declarative-flows.js";
|
|
5
5
|
import { listBuiltInFlowSpecFiles, listProjectFlowSpecFiles, projectFlowSpecsDir } from "./spec-loader.js";
|
|
6
6
|
export const BUILT_IN_COMMAND_FLOW_IDS = [
|
|
7
7
|
"auto-golang",
|
|
8
8
|
"auto-common",
|
|
9
|
+
"auto-simple",
|
|
9
10
|
"bug-analyze",
|
|
10
11
|
"bug-fix",
|
|
12
|
+
"design-review",
|
|
11
13
|
"git-commit",
|
|
12
14
|
"gitlab-diff-review",
|
|
13
15
|
"gitlab-review",
|
|
16
|
+
"instant-task",
|
|
14
17
|
"mr-description",
|
|
15
18
|
"plan",
|
|
19
|
+
"plan-revise",
|
|
16
20
|
"task-describe",
|
|
17
21
|
"implement",
|
|
18
22
|
"review",
|
|
@@ -24,13 +28,17 @@ export const BUILT_IN_COMMAND_FLOW_IDS = [
|
|
|
24
28
|
const BUILT_IN_COMMAND_FLOW_FILES = {
|
|
25
29
|
"auto-golang": "auto-golang.json",
|
|
26
30
|
"auto-common": "auto-common.json",
|
|
31
|
+
"auto-simple": "auto-simple.json",
|
|
27
32
|
"bug-analyze": "bugz/bug-analyze.json",
|
|
28
33
|
"bug-fix": "bugz/bug-fix.json",
|
|
34
|
+
"design-review": "design-review.json",
|
|
29
35
|
"git-commit": "git-commit.json",
|
|
30
36
|
"gitlab-diff-review": "gitlab/gitlab-diff-review.json",
|
|
31
37
|
"gitlab-review": "gitlab/gitlab-review.json",
|
|
38
|
+
"instant-task": "instant-task.json",
|
|
32
39
|
"mr-description": "gitlab/mr-description.json",
|
|
33
40
|
plan: "plan.json",
|
|
41
|
+
"plan-revise": "plan-revise.json",
|
|
34
42
|
"task-describe": "task-describe.json",
|
|
35
43
|
implement: "implement.json",
|
|
36
44
|
review: "review/review.json",
|
|
@@ -39,6 +47,9 @@ const BUILT_IN_COMMAND_FLOW_FILES = {
|
|
|
39
47
|
"run-go-tests-loop": "go/run-go-tests-loop.json",
|
|
40
48
|
"run-go-linter-loop": "go/run-go-linter-loop.json",
|
|
41
49
|
};
|
|
50
|
+
export function builtInCommandFlowFile(flowId) {
|
|
51
|
+
return BUILT_IN_COMMAND_FLOW_FILES[flowId] ?? null;
|
|
52
|
+
}
|
|
42
53
|
function builtInCommandIdForFile(fileName) {
|
|
43
54
|
for (const [flowId, candidate] of Object.entries(BUILT_IN_COMMAND_FLOW_FILES)) {
|
|
44
55
|
if (candidate === fileName) {
|
|
@@ -80,15 +91,16 @@ export function loadInteractiveFlowCatalog(cwd) {
|
|
|
80
91
|
for (const filePath of listProjectFlowSpecFiles(cwd)) {
|
|
81
92
|
entries.push(loadProjectCatalogEntry(cwd, filePath));
|
|
82
93
|
}
|
|
94
|
+
const visibleEntries = entries.filter((entry) => entry.flow.catalogVisibility !== "hidden");
|
|
83
95
|
const byId = new Map();
|
|
84
|
-
for (const entry of
|
|
96
|
+
for (const entry of visibleEntries) {
|
|
85
97
|
const duplicate = byId.get(entry.id);
|
|
86
98
|
if (duplicate) {
|
|
87
99
|
throw new TaskRunnerError(`Flow id '${entry.id}' conflicts between ${duplicate.absolutePath} and ${entry.absolutePath}. Rename one of the flow files.`);
|
|
88
100
|
}
|
|
89
101
|
byId.set(entry.id, entry);
|
|
90
102
|
}
|
|
91
|
-
return
|
|
103
|
+
return visibleEntries;
|
|
92
104
|
}
|
|
93
105
|
export function findCatalogEntry(flowId, entries) {
|
|
94
106
|
return entries.find((entry) => entry.id === flowId);
|
|
@@ -101,3 +113,11 @@ export function toDeclarativeFlowRef(entry) {
|
|
|
101
113
|
? { source: "built-in", fileName: entry.fileName }
|
|
102
114
|
: { source: "project-local", filePath: entry.absolutePath };
|
|
103
115
|
}
|
|
116
|
+
export function flowRoutingKey(entry) {
|
|
117
|
+
return entry.source === "project-local"
|
|
118
|
+
? `project-local:${entry.absolutePath}`
|
|
119
|
+
: `built-in:${entry.id}`;
|
|
120
|
+
}
|
|
121
|
+
export function flowRoutingGroups(entry, cwd) {
|
|
122
|
+
return collectFlowRoutingGroups(entry.flow, cwd);
|
|
123
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function isFlowExecutionState(value) {
|
|
2
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
const record = value;
|
|
6
|
+
return (typeof record["flowKind"] === "string"
|
|
7
|
+
&& typeof record["flowVersion"] === "number"
|
|
8
|
+
&& typeof record["terminated"] === "boolean"
|
|
9
|
+
&& Array.isArray(record["phases"]));
|
|
10
|
+
}
|
|
11
|
+
function isPublishedArtifactRecord(value) {
|
|
12
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const record = value;
|
|
16
|
+
return typeof record["artifact_id"] === "string" && typeof record["payload_path"] === "string";
|
|
17
|
+
}
|
|
18
|
+
export function isFlowRunResumeEnvelope(value) {
|
|
19
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const record = value;
|
|
23
|
+
return (record["resumeKind"] === "flow-run"
|
|
24
|
+
&& typeof record["flowKind"] === "string"
|
|
25
|
+
&& typeof record["flowVersion"] === "number"
|
|
26
|
+
&& isFlowExecutionState(record["executionState"])
|
|
27
|
+
&& Array.isArray(record["publishedArtifacts"])
|
|
28
|
+
&& record["publishedArtifacts"].every((artifact) => isPublishedArtifactRecord(artifact)));
|
|
29
|
+
}
|