agentweaver 0.1.2 → 0.1.3

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 +11 -10
  2. package/dist/artifacts.js +24 -2
  3. package/dist/executors/claude-executor.js +12 -2
  4. package/dist/executors/claude-summary-executor.js +1 -1
  5. package/dist/executors/codex-docker-executor.js +1 -1
  6. package/dist/executors/codex-local-executor.js +1 -1
  7. package/dist/executors/configs/claude-config.js +2 -1
  8. package/dist/index.js +388 -451
  9. package/dist/interactive-ui.js +451 -194
  10. package/dist/jira.js +3 -1
  11. package/dist/pipeline/auto-flow.js +9 -0
  12. package/dist/pipeline/context.js +2 -0
  13. package/dist/pipeline/declarative-flow-runner.js +246 -0
  14. package/dist/pipeline/declarative-flows.js +24 -0
  15. package/dist/pipeline/flow-specs/auto.json +471 -0
  16. package/dist/pipeline/flow-specs/implement.json +47 -0
  17. package/dist/pipeline/flow-specs/plan.json +88 -0
  18. package/dist/pipeline/flow-specs/preflight.json +174 -0
  19. package/dist/pipeline/flow-specs/review-fix.json +76 -0
  20. package/dist/pipeline/flow-specs/review.json +233 -0
  21. package/dist/pipeline/flow-specs/test-fix.json +24 -0
  22. package/dist/pipeline/flow-specs/test-linter-fix.json +24 -0
  23. package/dist/pipeline/flow-specs/test.json +19 -0
  24. package/dist/pipeline/flows/implement-flow.js +3 -4
  25. package/dist/pipeline/flows/preflight-flow.js +17 -57
  26. package/dist/pipeline/flows/review-fix-flow.js +3 -4
  27. package/dist/pipeline/flows/review-flow.js +8 -4
  28. package/dist/pipeline/flows/test-fix-flow.js +3 -4
  29. package/dist/pipeline/node-registry.js +71 -0
  30. package/dist/pipeline/node-runner.js +9 -3
  31. package/dist/pipeline/nodes/build-failure-summary-node.js +4 -4
  32. package/dist/pipeline/nodes/claude-prompt-node.js +54 -0
  33. package/dist/pipeline/nodes/claude-summary-node.js +12 -6
  34. package/dist/pipeline/nodes/codex-docker-prompt-node.js +1 -0
  35. package/dist/pipeline/nodes/codex-local-prompt-node.js +32 -0
  36. package/dist/pipeline/nodes/file-check-node.js +15 -0
  37. package/dist/pipeline/nodes/summary-file-load-node.js +16 -0
  38. package/dist/pipeline/nodes/task-summary-node.js +12 -6
  39. package/dist/pipeline/prompt-registry.js +22 -0
  40. package/dist/pipeline/prompt-runtime.js +18 -0
  41. package/dist/pipeline/registry.js +0 -2
  42. package/dist/pipeline/spec-compiler.js +200 -0
  43. package/dist/pipeline/spec-loader.js +14 -0
  44. package/dist/pipeline/spec-types.js +1 -0
  45. package/dist/pipeline/spec-validator.js +290 -0
  46. package/dist/pipeline/value-resolver.js +199 -0
  47. package/dist/prompts.js +1 -3
  48. package/dist/runtime/process-runner.js +24 -23
  49. package/dist/tui.js +39 -0
  50. package/package.json +2 -2
package/dist/jira.js CHANGED
@@ -1,5 +1,6 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, mkdirSync } from "node:fs";
2
2
  import { writeFile } from "node:fs/promises";
3
+ import path from "node:path";
3
4
  import { TaskRunnerError } from "./errors.js";
4
5
  const ISSUE_KEY_RE = /^[A-Z][A-Z0-9_]*-[0-9]+$/;
5
6
  export function extractIssueKey(jiraRef) {
@@ -47,6 +48,7 @@ export async function fetchJiraIssue(jiraApiUrl, jiraTaskFile) {
47
48
  throw new TaskRunnerError(`Failed to fetch Jira issue: HTTP ${response.status}`);
48
49
  }
49
50
  const body = Buffer.from(await response.arrayBuffer());
51
+ mkdirSync(path.dirname(jiraTaskFile), { recursive: true });
50
52
  await writeFile(jiraTaskFile, body);
51
53
  }
52
54
  export function requireJiraTaskFile(jiraTaskFile) {
@@ -0,0 +1,9 @@
1
+ import { loadDeclarativeFlow } from "./declarative-flows.js";
2
+ let cachedAutoFlow = null;
3
+ export function loadAutoFlow() {
4
+ if (cachedAutoFlow) {
5
+ return cachedAutoFlow;
6
+ }
7
+ cachedAutoFlow = loadDeclarativeFlow("auto.json");
8
+ return cachedAutoFlow;
9
+ }
@@ -1,5 +1,6 @@
1
1
  import process from "node:process";
2
2
  import { getOutputAdapter } from "../tui.js";
3
+ import { createNodeRegistry } from "./node-registry.js";
3
4
  import { createExecutorRegistry } from "./registry.js";
4
5
  export function createPipelineContext(input) {
5
6
  return {
@@ -12,6 +13,7 @@ export function createPipelineContext(input) {
12
13
  verbose: input.verbose,
13
14
  runtime: input.runtime,
14
15
  executors: createExecutorRegistry(),
16
+ nodes: createNodeRegistry(),
15
17
  ...(input.setSummary ? { setSummary: input.setSummary } : {}),
16
18
  };
17
19
  }
@@ -0,0 +1,246 @@
1
+ import { TaskRunnerError } from "../errors.js";
2
+ import { readFileSync } from "node:fs";
3
+ import { runNodeChecks } from "./checks.js";
4
+ import { runNodeByKind } from "./node-runner.js";
5
+ import { renderPrompt } from "./prompt-runtime.js";
6
+ import { evaluateCondition, resolveParams, resolveValue } from "./value-resolver.js";
7
+ function nowIso8601() {
8
+ return new Date().toISOString();
9
+ }
10
+ function toJsonValue(value) {
11
+ if (value === null ||
12
+ typeof value === "string" ||
13
+ typeof value === "number" ||
14
+ typeof value === "boolean") {
15
+ return value;
16
+ }
17
+ if (Array.isArray(value)) {
18
+ return value.map((candidate) => toJsonValue(candidate));
19
+ }
20
+ if (typeof value === "object") {
21
+ return Object.fromEntries(Object.entries(value).map(([key, candidate]) => [key, toJsonValue(candidate)]));
22
+ }
23
+ return String(value);
24
+ }
25
+ function ensureExecutionState(options) {
26
+ if (options.executionState) {
27
+ return options.executionState;
28
+ }
29
+ return {
30
+ flowKind: options.flowKind ?? "declarative-flow",
31
+ flowVersion: options.flowVersion ?? 1,
32
+ terminated: false,
33
+ phases: [],
34
+ };
35
+ }
36
+ function ensurePhaseState(executionState, phase) {
37
+ let phaseState = executionState.phases.find((candidate) => candidate.id === phase.id);
38
+ if (!phaseState) {
39
+ phaseState = {
40
+ id: phase.id,
41
+ status: "pending",
42
+ repeatVars: { ...phase.repeatVars },
43
+ steps: phase.steps.map((step) => ({
44
+ id: step.id,
45
+ status: "pending",
46
+ })),
47
+ };
48
+ executionState.phases.push(phaseState);
49
+ }
50
+ return phaseState;
51
+ }
52
+ function toStepOutputs(value) {
53
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
54
+ return undefined;
55
+ }
56
+ return Object.fromEntries(Object.entries(value).map(([key, candidate]) => [key, toJsonValue(candidate)]));
57
+ }
58
+ function createResolverContext(pipelineContext, flowParams, flowConstants, repeatVars, executionState) {
59
+ return {
60
+ flowParams,
61
+ flowConstants,
62
+ pipelineContext,
63
+ repeatVars,
64
+ ...(executionState ? { executionState } : {}),
65
+ };
66
+ }
67
+ function resolveExpectation(expectation, context) {
68
+ if (expectation.kind === "require-artifacts") {
69
+ const value = resolveValue(expectation.paths, context);
70
+ if (!Array.isArray(value) || value.some((candidate) => typeof candidate !== "string")) {
71
+ throw new TaskRunnerError("Expectation 'require-artifacts' must resolve to string[]");
72
+ }
73
+ return {
74
+ kind: "require-artifacts",
75
+ paths: value,
76
+ message: expectation.message,
77
+ };
78
+ }
79
+ if (expectation.kind === "require-file") {
80
+ const value = resolveValue(expectation.path, context);
81
+ if (typeof value !== "string") {
82
+ throw new TaskRunnerError("Expectation 'require-file' must resolve to string");
83
+ }
84
+ return {
85
+ kind: "require-file",
86
+ path: value,
87
+ message: expectation.message,
88
+ };
89
+ }
90
+ if (expectation.kind === "step-output") {
91
+ const value = resolveValue(expectation.value, context);
92
+ if (expectation.equals !== undefined) {
93
+ const expected = resolveValue(expectation.equals, context);
94
+ if (value !== expected) {
95
+ throw new TaskRunnerError(expectation.message);
96
+ }
97
+ return {
98
+ kind: "require-file",
99
+ path: "",
100
+ message: expectation.message,
101
+ };
102
+ }
103
+ if (!value) {
104
+ throw new TaskRunnerError(expectation.message);
105
+ }
106
+ return {
107
+ kind: "require-file",
108
+ path: "",
109
+ message: expectation.message,
110
+ };
111
+ }
112
+ throw new TaskRunnerError(`Unsupported expectation kind: ${expectation.kind ?? "unknown"}`);
113
+ }
114
+ function runAfterAction(action, pipelineContext, context) {
115
+ if (action.kind === "set-summary-from-file") {
116
+ const value = resolveValue(action.path, context);
117
+ if (typeof value !== "string") {
118
+ throw new TaskRunnerError("After action 'set-summary-from-file' must resolve to string path");
119
+ }
120
+ pipelineContext.setSummary?.(readFileSync(value, "utf8").trim());
121
+ return;
122
+ }
123
+ throw new TaskRunnerError(`Unsupported after action kind: ${action.kind ?? "unknown"}`);
124
+ }
125
+ export async function runExpandedPhase(phase, pipelineContext, flowParams, flowConstants, options = {}) {
126
+ const executionState = ensureExecutionState(options);
127
+ const phaseState = ensurePhaseState(executionState, phase);
128
+ const phaseContext = createResolverContext(pipelineContext, flowParams, flowConstants, phase.repeatVars, executionState);
129
+ if (executionState.terminated) {
130
+ phaseState.status = "skipped";
131
+ await options.onStateChange?.(executionState);
132
+ return {
133
+ id: phase.id,
134
+ status: "skipped",
135
+ stopped: true,
136
+ executionState,
137
+ steps: phase.steps.map((step) => ({ id: step.id, status: "skipped" })),
138
+ };
139
+ }
140
+ if (!evaluateCondition(phase.when, phaseContext)) {
141
+ phaseState.status = "skipped";
142
+ phaseState.startedAt ??= nowIso8601();
143
+ phaseState.finishedAt = nowIso8601();
144
+ phaseState.steps.forEach((step) => {
145
+ step.status = "skipped";
146
+ step.finishedAt = nowIso8601();
147
+ });
148
+ await options.onStateChange?.(executionState);
149
+ return {
150
+ id: phase.id,
151
+ status: "skipped",
152
+ stopped: false,
153
+ executionState,
154
+ steps: phase.steps.map((step) => ({ id: step.id, status: "skipped" })),
155
+ };
156
+ }
157
+ phaseState.status = "running";
158
+ phaseState.startedAt ??= nowIso8601();
159
+ await options.onStateChange?.(executionState);
160
+ const steps = [];
161
+ for (const [stepIndex, step] of phase.steps.entries()) {
162
+ await options.onStepStart?.(phase, step);
163
+ const stepContext = createResolverContext(pipelineContext, flowParams, flowConstants, step.repeatVars, executionState);
164
+ const stepState = phaseState.steps[stepIndex];
165
+ if (!stepState) {
166
+ throw new TaskRunnerError(`Missing execution state for step '${step.id}' in phase '${phase.id}'`);
167
+ }
168
+ stepState.status = "running";
169
+ stepState.startedAt ??= nowIso8601();
170
+ await options.onStateChange?.(executionState);
171
+ if (!evaluateCondition(step.when, stepContext)) {
172
+ stepState.status = "skipped";
173
+ stepState.finishedAt = nowIso8601();
174
+ await options.onStateChange?.(executionState);
175
+ steps.push({ id: step.id, status: "skipped" });
176
+ continue;
177
+ }
178
+ const params = resolveParams(step.params, stepContext);
179
+ if (step.prompt) {
180
+ params.prompt = renderPrompt(step.prompt, stepContext);
181
+ }
182
+ const result = await runNodeByKind(step.node, pipelineContext, params, { skipChecks: step.expect !== undefined });
183
+ stepState.value = toJsonValue(result.value);
184
+ const stepOutputs = toStepOutputs(result.value);
185
+ if (stepOutputs) {
186
+ stepState.outputs = stepOutputs;
187
+ }
188
+ else {
189
+ delete stepState.outputs;
190
+ }
191
+ if (step.expect) {
192
+ const nodeChecks = step.expect
193
+ .filter((expectation) => evaluateCondition(expectation.when, stepContext))
194
+ .flatMap((expectation) => {
195
+ if (expectation.kind === "step-output") {
196
+ resolveExpectation(expectation, stepContext);
197
+ return [];
198
+ }
199
+ return [resolveExpectation(expectation, stepContext)];
200
+ });
201
+ runNodeChecks(nodeChecks);
202
+ }
203
+ if (step.after) {
204
+ step.after.filter((action) => evaluateCondition(action.when, stepContext)).forEach((action) => {
205
+ runAfterAction(action, pipelineContext, stepContext);
206
+ });
207
+ }
208
+ const stopFlow = step.stopFlowIf ? evaluateCondition(step.stopFlowIf, stepContext) : false;
209
+ stepState.status = "done";
210
+ stepState.finishedAt = nowIso8601();
211
+ stepState.stopFlow = stopFlow;
212
+ await options.onStateChange?.(executionState);
213
+ steps.push({ id: step.id, status: "done", ...(stepState.outputs ? { outputs: stepState.outputs } : {}) });
214
+ if (stopFlow) {
215
+ executionState.terminated = true;
216
+ executionState.terminationReason = `Stopped by ${phase.id}:${step.id}`;
217
+ phaseState.status = "done";
218
+ phaseState.finishedAt = nowIso8601();
219
+ await options.onStateChange?.(executionState);
220
+ return {
221
+ id: phase.id,
222
+ status: "stopped",
223
+ stopped: true,
224
+ executionState,
225
+ steps,
226
+ };
227
+ }
228
+ }
229
+ phaseState.status = "done";
230
+ phaseState.finishedAt = nowIso8601();
231
+ await options.onStateChange?.(executionState);
232
+ return {
233
+ id: phase.id,
234
+ status: "done",
235
+ stopped: false,
236
+ executionState,
237
+ steps,
238
+ };
239
+ }
240
+ export function findPhaseById(phases, phaseId) {
241
+ const phase = phases.find((candidate) => candidate.id === phaseId);
242
+ if (!phase) {
243
+ throw new TaskRunnerError(`Unknown expanded phase id: ${phaseId}`);
244
+ }
245
+ return phase;
246
+ }
@@ -0,0 +1,24 @@
1
+ import { createNodeRegistry } from "./node-registry.js";
2
+ import { compileFlowSpec } from "./spec-compiler.js";
3
+ import { loadFlowSpecSync } from "./spec-loader.js";
4
+ import { validateExpandedPhases, validateFlowSpec } from "./spec-validator.js";
5
+ const cache = new Map();
6
+ export function loadDeclarativeFlow(fileName) {
7
+ const cached = cache.get(fileName);
8
+ if (cached) {
9
+ return cached;
10
+ }
11
+ const spec = loadFlowSpecSync(fileName);
12
+ const nodeRegistry = createNodeRegistry();
13
+ validateFlowSpec(spec, nodeRegistry);
14
+ const phases = compileFlowSpec(spec);
15
+ validateExpandedPhases(phases);
16
+ const loaded = {
17
+ kind: spec.kind,
18
+ version: spec.version,
19
+ constants: spec.constants ?? {},
20
+ phases,
21
+ };
22
+ cache.set(fileName, loaded);
23
+ return loaded;
24
+ }