agentweaver 0.1.2 → 0.1.4

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 (61) hide show
  1. package/README.md +58 -23
  2. package/dist/artifacts.js +58 -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/executors/verify-build-executor.js +110 -9
  9. package/dist/index.js +466 -452
  10. package/dist/interactive-ui.js +538 -194
  11. package/dist/jira.js +3 -1
  12. package/dist/pipeline/auto-flow.js +9 -0
  13. package/dist/pipeline/checks.js +5 -0
  14. package/dist/pipeline/context.js +2 -0
  15. package/dist/pipeline/declarative-flow-runner.js +262 -0
  16. package/dist/pipeline/declarative-flows.js +24 -0
  17. package/dist/pipeline/flow-specs/auto.json +485 -0
  18. package/dist/pipeline/flow-specs/bug-analyze.json +140 -0
  19. package/dist/pipeline/flow-specs/bug-fix.json +44 -0
  20. package/dist/pipeline/flow-specs/implement.json +47 -0
  21. package/dist/pipeline/flow-specs/mr-description.json +61 -0
  22. package/dist/pipeline/flow-specs/plan.json +88 -0
  23. package/dist/pipeline/flow-specs/preflight.json +174 -0
  24. package/dist/pipeline/flow-specs/review-fix.json +76 -0
  25. package/dist/pipeline/flow-specs/review.json +233 -0
  26. package/dist/pipeline/flow-specs/run-linter-loop.json +149 -0
  27. package/dist/pipeline/flow-specs/run-tests-loop.json +149 -0
  28. package/dist/pipeline/flow-specs/task-describe.json +61 -0
  29. package/dist/pipeline/flow-specs/test-fix.json +24 -0
  30. package/dist/pipeline/flow-specs/test-linter-fix.json +24 -0
  31. package/dist/pipeline/flow-specs/test.json +19 -0
  32. package/dist/pipeline/flows/implement-flow.js +3 -4
  33. package/dist/pipeline/flows/preflight-flow.js +17 -57
  34. package/dist/pipeline/flows/review-fix-flow.js +3 -4
  35. package/dist/pipeline/flows/review-flow.js +8 -4
  36. package/dist/pipeline/flows/test-fix-flow.js +3 -4
  37. package/dist/pipeline/node-registry.js +74 -0
  38. package/dist/pipeline/node-runner.js +9 -3
  39. package/dist/pipeline/nodes/build-failure-summary-node.js +4 -4
  40. package/dist/pipeline/nodes/claude-prompt-node.js +54 -0
  41. package/dist/pipeline/nodes/claude-summary-node.js +12 -6
  42. package/dist/pipeline/nodes/codex-docker-prompt-node.js +1 -0
  43. package/dist/pipeline/nodes/codex-local-prompt-node.js +32 -0
  44. package/dist/pipeline/nodes/file-check-node.js +15 -0
  45. package/dist/pipeline/nodes/flow-run-node.js +40 -0
  46. package/dist/pipeline/nodes/summary-file-load-node.js +16 -0
  47. package/dist/pipeline/nodes/task-summary-node.js +12 -6
  48. package/dist/pipeline/nodes/verify-build-node.js +1 -0
  49. package/dist/pipeline/prompt-registry.js +27 -0
  50. package/dist/pipeline/prompt-runtime.js +18 -0
  51. package/dist/pipeline/registry.js +0 -2
  52. package/dist/pipeline/spec-compiler.js +213 -0
  53. package/dist/pipeline/spec-loader.js +14 -0
  54. package/dist/pipeline/spec-types.js +1 -0
  55. package/dist/pipeline/spec-validator.js +302 -0
  56. package/dist/pipeline/value-resolver.js +217 -0
  57. package/dist/prompts.js +22 -3
  58. package/dist/runtime/process-runner.js +24 -23
  59. package/dist/structured-artifacts.js +178 -0
  60. package/dist/tui.js +39 -0
  61. package/package.json +2 -2
package/README.md CHANGED
@@ -11,26 +11,30 @@ The package is designed to run as an npm CLI and includes an interactive termina
11
11
  ## What It Does
12
12
 
13
13
  - Fetches a Jira issue by key or browse URL
14
- - Generates workflow artifacts such as design, implementation plan, QA plan, reviews, and summaries
15
- - Runs workflow stages like `plan`, `implement`, `review`, `review-fix`, `test`, and `auto`
16
- - Persists `auto` pipeline state on disk so runs can resume
14
+ - Generates workflow artifacts such as design, implementation plan, QA plan, bug analysis, reviews, and summaries
15
+ - For bug-analysis flows, structured JSON artifacts are the machine-readable source of truth, while Markdown artifacts are for human inspection
16
+ - Runs workflow stages like `bug-analyze`, `bug-fix`, `mr-description`, `plan`, `task-describe`, `implement`, `review`, `review-fix`, `test`, and `auto`
17
+ - Persists compact `auto` pipeline state on disk so runs can resume without storing large agent outputs
17
18
  - Uses Docker runtime services for isolated Codex execution and build verification
18
19
 
19
20
  ## Architecture
20
21
 
21
- The CLI now uses an executor-based architecture.
22
+ The CLI now uses an executor + node + declarative flow architecture.
22
23
 
23
- - `src/index.ts` remains the orchestration layer for CLI commands and `auto` state transitions
24
- - `src/executors/` contains first-class executors for external actions such as Jira fetch, local Codex, Docker Codex, Claude, Claude summaries, process execution, and build verification
25
- - `src/executors/configs/` contains data-only default configs for executors with JSON-compatible structure
24
+ - `src/index.ts` remains the CLI entrypoint and high-level orchestration layer
25
+ - `src/executors/` contains first-class executors for external actions such as Jira fetch, local Codex, Docker-based build verification, Claude, Claude summaries, and process execution
26
+ - `src/pipeline/nodes/` contains reusable runtime nodes built on top of executors
27
+ - `src/pipeline/flow-specs/` contains declarative JSON flow specs for `preflight`, `bug-analyze`, `bug-fix`, `mr-description`, `plan`, `task-describe`, `implement`, `review`, `review-fix`, `test`, `test-fix`, `test-linter-fix`, `run-tests-loop`, `run-linter-loop`, and `auto`
26
28
  - `src/runtime/` contains shared runtime services such as command resolution, Docker runtime environment setup, and subprocess execution
27
29
 
28
- This keeps command handlers focused on workflow composition instead of inline subprocess wiring.
30
+ This keeps command handlers focused on choosing a flow and providing parameters instead of assembling prompts and subprocess wiring inline.
29
31
 
30
32
  ## Repository Layout
31
33
 
32
34
  - `src/` — main TypeScript sources
33
35
  - `src/index.ts` — CLI entrypoint and workflow orchestration
36
+ - `src/pipeline/flow-specs/` — declarative JSON specs for workflow stages
37
+ - `src/pipeline/nodes/` — reusable pipeline nodes executed by the declarative runner
34
38
  - `src/interactive-ui.ts` — interactive TUI built with `neo-blessed`
35
39
  - `src/markdown.ts` — markdown-to-terminal renderer for the TUI
36
40
  - `src/executors/` — executor modules for concrete execution families
@@ -38,7 +42,9 @@ This keeps command handlers focused on workflow composition instead of inline su
38
42
  - `src/runtime/` — shared runtime services used by executors
39
43
  - `docker-compose.yml` — runtime services for Codex and build verification
40
44
  - `Dockerfile.codex` — container image for Codex runtime
41
- - `verify_build.sh` — project-specific verification entrypoint used by `verify-build`
45
+ - `verify_build.sh` — aggregated verification entrypoint used by `verify-build`
46
+ - `run_tests.sh` — isolated test and coverage verification entrypoint
47
+ - `run_linter.sh` — isolated generate + lint verification entrypoint
42
48
  - `package.json` — npm package metadata and scripts
43
49
  - `tsconfig.json` — TypeScript configuration
44
50
 
@@ -47,7 +53,7 @@ This keeps command handlers focused on workflow composition instead of inline su
47
53
  - Node.js `>= 18.19.0`
48
54
  - npm
49
55
  - Docker with `docker compose` or `docker-compose`
50
- - `codex` CLI for `plan` and Codex-driven steps
56
+ - `codex` CLI for `bug-analyze`, `bug-fix`, `mr-description`, `plan`, and other Codex-driven steps
51
57
  - `claude` CLI for review and summary steps
52
58
 
53
59
  ## Installation
@@ -84,9 +90,8 @@ Common optional variables:
84
90
  - `DOCKER_COMPOSE_BIN` — override compose command, for example `docker compose`
85
91
  - `CODEX_BIN` — override `codex` executable path
86
92
  - `CLAUDE_BIN` — override `claude` executable path
87
- - `CODEX_MODEL` — defaults to `gpt-5.4`
88
- - `CLAUDE_REVIEW_MODEL` — defaults to `opus`
89
- - `CLAUDE_SUMMARY_MODEL` — defaults to `haiku`
93
+ - `CODEX_MODEL` — fallback model for Codex executors when the flow spec does not set `params.model`
94
+ - `CLAUDE_MODEL` — fallback Claude model when the flow spec does not set `params.model`
90
95
 
91
96
  Example `.env`:
92
97
 
@@ -97,8 +102,7 @@ AGENTWEAVER_HOME=/absolute/path/to/AgentWeaver
97
102
  CODEX_BIN=codex
98
103
  CLAUDE_BIN=claude
99
104
  CODEX_MODEL=gpt-5.4
100
- CLAUDE_REVIEW_MODEL=opus
101
- CLAUDE_SUMMARY_MODEL=haiku
105
+ CLAUDE_MODEL=opus
102
106
  GOPRIVATE=gitlab.example.org/*
103
107
  GONOSUMDB=gitlab.example.org/*
104
108
  GONOPROXY=gitlab.example.org/*
@@ -111,8 +115,14 @@ Direct CLI usage:
111
115
 
112
116
  ```bash
113
117
  agentweaver plan DEMO-3288
118
+ agentweaver bug-analyze DEMO-3288
119
+ agentweaver bug-fix DEMO-3288
120
+ agentweaver mr-description DEMO-3288
121
+ agentweaver task-describe DEMO-3288
114
122
  agentweaver implement DEMO-3288
115
123
  agentweaver review DEMO-3288
124
+ agentweaver run-tests-loop DEMO-3288
125
+ agentweaver run-linter-loop DEMO-3288
116
126
  agentweaver auto DEMO-3288
117
127
  ```
118
128
 
@@ -120,6 +130,10 @@ From source checkout:
120
130
 
121
131
  ```bash
122
132
  node dist/index.js plan DEMO-3288
133
+ node dist/index.js bug-analyze DEMO-3288
134
+ node dist/index.js bug-fix DEMO-3288
135
+ node dist/index.js mr-description DEMO-3288
136
+ node dist/index.js task-describe DEMO-3288
123
137
  node dist/index.js auto DEMO-3288
124
138
  ```
125
139
 
@@ -144,28 +158,35 @@ agentweaver auto-status DEMO-3288
144
158
  agentweaver auto-reset DEMO-3288
145
159
  ```
146
160
 
161
+ Notes:
162
+
163
+ - `--verbose` streams child process `stdout/stderr` in direct CLI mode
164
+ - the interactive `Activity` pane is intentionally structured: it shows launch separators, prompts, summaries, and short status messages instead of raw Codex/Claude logs by default
165
+
147
166
  ## Interactive TUI
148
167
 
149
168
  Interactive mode opens a full-screen terminal UI with:
150
169
 
151
- - command input
170
+ - flow list
171
+ - current flow progress
152
172
  - activity log
153
173
  - task summary pane
154
- - command list/help
155
174
  - keyboard navigation between panes
156
175
 
157
176
  Current navigation:
158
177
 
159
- - `Enter` — run command
178
+ - `Enter` — run selected flow
160
179
  - `Tab` / `Shift+Tab` — switch panes
161
- - `Ctrl+J` — focus activity log
162
- - `Ctrl+K` — focus command input
163
- - `Ctrl+U` — focus task summary
164
- - `Ctrl+H` — focus commands pane
165
180
  - `PgUp` / `PgDn` / `Home` / `End` — scroll focused panes
166
- - `?` or `F1` — help overlay
181
+ - `h` — help overlay
167
182
  - `q` or `Ctrl+C` — exit
168
183
 
184
+ Activity pane behavior:
185
+
186
+ - each external launch is separated with a framed block that shows the current `node`, `executor`, and `model` when available
187
+ - prompts and summaries are rendered as plain text for readability
188
+ - live raw Codex/Claude output is not shown there in normal mode
189
+
169
190
  ## Docker Runtime
170
191
 
171
192
  Docker is used as an isolated execution environment for Codex and build/test verification.
@@ -175,6 +196,8 @@ Main services:
175
196
  - `codex` — interactive Codex container
176
197
  - `codex-exec` — non-interactive `codex exec`
177
198
  - `verify-build` — project verification script inside container
199
+ - `run-tests` — isolated `run_tests.sh` execution inside container
200
+ - `run-linter` — isolated `run_linter.sh` execution inside container
178
201
  - `codex-login` — interactive login container
179
202
  - `dockerd` — internal Docker daemon for testcontainers/build flows
180
203
 
@@ -204,6 +227,18 @@ Build verification:
204
227
  PROJECT_DIR="$PWD" docker compose -f "$AGENTWEAVER_HOME/docker-compose.yml" run --rm verify-build
205
228
  ```
206
229
 
230
+ Tests only:
231
+
232
+ ```bash
233
+ PROJECT_DIR="$PWD" docker compose -f "$AGENTWEAVER_HOME/docker-compose.yml" run --rm run-tests
234
+ ```
235
+
236
+ Linter only:
237
+
238
+ ```bash
239
+ PROJECT_DIR="$PWD" docker compose -f "$AGENTWEAVER_HOME/docker-compose.yml" run --rm run-linter
240
+ ```
241
+
207
242
  ## Development
208
243
 
209
244
  Install dependencies and build:
package/dist/artifacts.js CHANGED
@@ -1,10 +1,23 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import process from "node:process";
2
4
  import { TaskRunnerError } from "./errors.js";
3
5
  export const REVIEW_FILE_RE = /^review-(.+)-(\d+)\.md$/;
4
6
  export const REVIEW_REPLY_FILE_RE = /^review-reply-(.+)-(\d+)\.md$/;
5
7
  export const READY_TO_MERGE_FILE = "ready-to-merge.md";
8
+ export function taskWorkspaceDir(taskKey) {
9
+ return path.join(process.cwd(), `.agentweaver-${taskKey}`);
10
+ }
11
+ export function ensureTaskWorkspaceDir(taskKey) {
12
+ const workspaceDir = taskWorkspaceDir(taskKey);
13
+ mkdirSync(workspaceDir, { recursive: true });
14
+ return workspaceDir;
15
+ }
16
+ export function taskWorkspaceFile(taskKey, fileName) {
17
+ return path.join(taskWorkspaceDir(taskKey), fileName);
18
+ }
6
19
  export function artifactFile(prefix, taskKey, iteration) {
7
- return `${prefix}-${taskKey}-${iteration}.md`;
20
+ return taskWorkspaceFile(taskKey, `${prefix}-${taskKey}-${iteration}.md`);
8
21
  }
9
22
  export function designFile(taskKey) {
10
23
  return artifactFile("design", taskKey, 1);
@@ -12,15 +25,58 @@ export function designFile(taskKey) {
12
25
  export function planFile(taskKey) {
13
26
  return artifactFile("plan", taskKey, 1);
14
27
  }
28
+ export function bugAnalyzeFile(taskKey) {
29
+ return taskWorkspaceFile(taskKey, `bug-analyze-${taskKey}.md`);
30
+ }
31
+ export function bugAnalyzeJsonFile(taskKey) {
32
+ return taskWorkspaceFile(taskKey, `bug-analyze-${taskKey}.json`);
33
+ }
34
+ export function bugFixDesignFile(taskKey) {
35
+ return taskWorkspaceFile(taskKey, `bug-fix-design-${taskKey}.md`);
36
+ }
37
+ export function bugFixDesignJsonFile(taskKey) {
38
+ return taskWorkspaceFile(taskKey, `bug-fix-design-${taskKey}.json`);
39
+ }
40
+ export function bugFixPlanFile(taskKey) {
41
+ return taskWorkspaceFile(taskKey, `bug-fix-plan-${taskKey}.md`);
42
+ }
43
+ export function bugFixPlanJsonFile(taskKey) {
44
+ return taskWorkspaceFile(taskKey, `bug-fix-plan-${taskKey}.json`);
45
+ }
15
46
  export function qaFile(taskKey) {
16
47
  return artifactFile("qa", taskKey, 1);
17
48
  }
18
49
  export function taskSummaryFile(taskKey) {
19
50
  return artifactFile("task", taskKey, 1);
20
51
  }
52
+ export function readyToMergeFile(taskKey) {
53
+ return taskWorkspaceFile(taskKey, READY_TO_MERGE_FILE);
54
+ }
55
+ export function jiraTaskFile(taskKey) {
56
+ return taskWorkspaceFile(taskKey, `${taskKey}.json`);
57
+ }
58
+ export function jiraDescriptionFile(taskKey) {
59
+ return taskWorkspaceFile(taskKey, `jira-${taskKey}-description.md`);
60
+ }
61
+ export function mrDescriptionFile(taskKey) {
62
+ return taskWorkspaceFile(taskKey, `mr-description-${taskKey}.md`);
63
+ }
64
+ export function autoStateFile(taskKey) {
65
+ return taskWorkspaceFile(taskKey, `.agentweaver-state-${taskKey}.json`);
66
+ }
21
67
  export function planArtifacts(taskKey) {
22
68
  return [designFile(taskKey), planFile(taskKey), qaFile(taskKey)];
23
69
  }
70
+ export function bugAnalyzeArtifacts(taskKey) {
71
+ return [
72
+ bugAnalyzeFile(taskKey),
73
+ bugAnalyzeJsonFile(taskKey),
74
+ bugFixDesignFile(taskKey),
75
+ bugFixDesignJsonFile(taskKey),
76
+ bugFixPlanFile(taskKey),
77
+ bugFixPlanJsonFile(taskKey),
78
+ ];
79
+ }
24
80
  export function requireArtifacts(paths, message) {
25
81
  const missing = paths.filter((filePath) => !existsSync(filePath));
26
82
  if (missing.length > 0) {
@@ -1,7 +1,17 @@
1
1
  import { claudeExecutorDefaultConfig } from "./configs/claude-config.js";
2
2
  import { processExecutor } from "./process-executor.js";
3
3
  function resolveModel(config, env) {
4
- return env[config.modelEnvVar]?.trim() || config.defaultModel;
4
+ const primaryModel = env[config.modelEnvVar]?.trim();
5
+ if (primaryModel) {
6
+ return primaryModel;
7
+ }
8
+ for (const envVarName of config.legacyModelEnvVars ?? []) {
9
+ const legacyModel = env[envVarName]?.trim();
10
+ if (legacyModel) {
11
+ return legacyModel;
12
+ }
13
+ }
14
+ return config.defaultModel;
5
15
  }
6
16
  export const claudeExecutor = {
7
17
  kind: "claude",
@@ -10,7 +20,7 @@ export const claudeExecutor = {
10
20
  async execute(context, input, config) {
11
21
  const env = input.env ?? context.env;
12
22
  const command = input.command ?? context.runtime.resolveCmd(config.defaultCommand, config.commandEnvVar);
13
- const model = resolveModel(config, env);
23
+ const model = input.model?.trim() || resolveModel(config, env);
14
24
  const argv = [command, "--model", model, config.promptFlag, `--allowedTools=${config.allowedTools}`];
15
25
  if (config.outputFormat) {
16
26
  argv.push("--output-format", config.outputFormat);
@@ -12,7 +12,7 @@ export const claudeSummaryExecutor = {
12
12
  async execute(context, input, config) {
13
13
  const env = input.env ?? context.env;
14
14
  const command = input.command ?? context.runtime.resolveCmd(config.defaultCommand, config.commandEnvVar);
15
- const model = resolveModel(config, env);
15
+ const model = input.model?.trim() || resolveModel(config, env);
16
16
  const argv = [command, "--model", model, config.promptFlag, `--allowedTools=${config.allowedTools}`, input.prompt];
17
17
  const processInput = {
18
18
  argv,
@@ -10,7 +10,7 @@ export const codexDockerExecutor = {
10
10
  async execute(context, input, config) {
11
11
  const composeCommand = context.runtime.resolveDockerComposeCmd();
12
12
  const env = context.runtime.dockerRuntimeEnv();
13
- const model = resolveModel(config, env);
13
+ const model = input.model?.trim() || resolveModel(config, env);
14
14
  env[config.promptEnvVar] = input.prompt;
15
15
  env[config.flagsEnvVar] = config.execFlagsTemplate.replace("{model}", model);
16
16
  const result = await processExecutor.execute(context, {
@@ -10,7 +10,7 @@ export const codexLocalExecutor = {
10
10
  async execute(context, input, config) {
11
11
  const env = input.env ?? context.env;
12
12
  const command = input.command ?? context.runtime.resolveCmd(config.defaultCommand, config.commandEnvVar);
13
- const model = resolveModel(config, env);
13
+ const model = input.model?.trim() || resolveModel(config, env);
14
14
  const result = await processExecutor.execute(context, {
15
15
  argv: [command, config.subcommand, "--model", model, config.fullAutoFlag, input.prompt],
16
16
  env,
@@ -1,7 +1,8 @@
1
1
  export const claudeExecutorDefaultConfig = {
2
2
  commandEnvVar: "CLAUDE_BIN",
3
3
  defaultCommand: "claude",
4
- modelEnvVar: "CLAUDE_REVIEW_MODEL",
4
+ modelEnvVar: "CLAUDE_MODEL",
5
+ legacyModelEnvVars: ["CLAUDE_REVIEW_MODEL"],
5
6
  defaultModel: "opus",
6
7
  promptFlag: "-p",
7
8
  allowedTools: "Read,Write,Edit",
@@ -1,22 +1,123 @@
1
1
  import { verifyBuildExecutorDefaultConfig } from "./configs/verify-build-config.js";
2
+ import { TaskRunnerError } from "../errors.js";
2
3
  import { processExecutor } from "./process-executor.js";
4
+ function parseStructuredResult(output, service) {
5
+ const lines = output
6
+ .split(/\r?\n/)
7
+ .map((line) => line.replace(/\u001b\[[0-9;]*m/g, "").trim())
8
+ .filter(Boolean);
9
+ if (lines.length === 0) {
10
+ throw new TaskRunnerError(`Structured result is missing from service '${service}' output.`);
11
+ }
12
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
13
+ const line = lines[index];
14
+ if (!line) {
15
+ continue;
16
+ }
17
+ const candidates = [];
18
+ if (line.startsWith("{") && line.endsWith("}")) {
19
+ candidates.push(line);
20
+ }
21
+ const firstBrace = line.indexOf("{");
22
+ const lastBrace = line.lastIndexOf("}");
23
+ if (firstBrace >= 0 && lastBrace > firstBrace) {
24
+ const slice = line.slice(firstBrace, lastBrace + 1).trim();
25
+ if (slice && !candidates.includes(slice)) {
26
+ candidates.push(slice);
27
+ }
28
+ }
29
+ for (const rawJson of candidates) {
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(rawJson);
33
+ }
34
+ catch {
35
+ continue;
36
+ }
37
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
38
+ continue;
39
+ }
40
+ const candidate = parsed;
41
+ if (typeof candidate.ok !== "boolean" ||
42
+ typeof candidate.kind !== "string" ||
43
+ typeof candidate.stage !== "string" ||
44
+ typeof candidate.exitCode !== "number" ||
45
+ typeof candidate.summary !== "string" ||
46
+ typeof candidate.command !== "string") {
47
+ continue;
48
+ }
49
+ const details = candidate.details;
50
+ if (details !== undefined && (!details || typeof details !== "object" || Array.isArray(details))) {
51
+ continue;
52
+ }
53
+ return {
54
+ ok: candidate.ok,
55
+ kind: candidate.kind,
56
+ stage: candidate.stage,
57
+ exitCode: candidate.exitCode,
58
+ summary: candidate.summary,
59
+ command: candidate.command,
60
+ details: details ?? {},
61
+ };
62
+ }
63
+ }
64
+ throw new TaskRunnerError(`Structured result is missing or invalid in service '${service}' output.`);
65
+ }
3
66
  export const verifyBuildExecutor = {
4
67
  kind: "verify-build",
5
68
  version: 1,
6
69
  defaultConfig: verifyBuildExecutorDefaultConfig,
7
70
  async execute(context, input, config) {
8
71
  const composeCommand = context.runtime.resolveDockerComposeCmd();
9
- const result = await processExecutor.execute(context, {
10
- argv: [...composeCommand, config.composeFileFlag, input.dockerComposeFile, ...config.runArgs, config.service],
11
- env: context.runtime.dockerRuntimeEnv(),
12
- verbose: config.verbose,
13
- label: config.service,
14
- }, {
15
- printFailureOutput: config.printFailureOutput,
16
- });
72
+ const service = input.service ?? config.service;
73
+ if (context.dryRun) {
74
+ await processExecutor.execute(context, {
75
+ argv: [...composeCommand, config.composeFileFlag, input.dockerComposeFile, ...config.runArgs, service],
76
+ env: context.runtime.dockerRuntimeEnv(),
77
+ verbose: config.verbose,
78
+ label: service,
79
+ }, {
80
+ printFailureOutput: config.printFailureOutput,
81
+ });
82
+ return {
83
+ output: "",
84
+ composeCommand,
85
+ parsed: {
86
+ ok: true,
87
+ kind: service,
88
+ stage: "dry_run",
89
+ exitCode: 0,
90
+ summary: `Dry run for service '${service}'`,
91
+ command: [...composeCommand, config.composeFileFlag, input.dockerComposeFile, ...config.runArgs, service].join(" "),
92
+ details: {},
93
+ },
94
+ };
95
+ }
96
+ let output = "";
97
+ let exitCode = 0;
98
+ try {
99
+ const result = await processExecutor.execute(context, {
100
+ argv: [...composeCommand, config.composeFileFlag, input.dockerComposeFile, ...config.runArgs, service],
101
+ env: context.runtime.dockerRuntimeEnv(),
102
+ verbose: config.verbose,
103
+ label: service,
104
+ }, {
105
+ printFailureOutput: config.printFailureOutput,
106
+ });
107
+ output = result.output;
108
+ }
109
+ catch (error) {
110
+ output = String(error.output ?? "");
111
+ exitCode = Number(error.returnCode ?? 1);
112
+ }
113
+ const parsed = parseStructuredResult(output, service);
114
+ if (parsed.exitCode !== exitCode && exitCode !== 0) {
115
+ throw new TaskRunnerError(`Structured result exit code mismatch for service '${service}': script=${parsed.exitCode}, runtime=${exitCode}.`);
116
+ }
17
117
  return {
18
- output: result.output,
118
+ output,
19
119
  composeCommand,
120
+ parsed,
20
121
  };
21
122
  },
22
123
  };