agentweaver 0.1.15 → 0.1.17
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 +76 -19
- package/dist/artifact-manifest.js +219 -0
- package/dist/artifacts.js +88 -3
- package/dist/doctor/checks/env-diagnostics.js +25 -0
- package/dist/doctor/checks/executors.js +2 -2
- package/dist/doctor/checks/flow-readiness.js +15 -18
- package/dist/flow-state.js +212 -15
- package/dist/index.js +539 -209
- package/dist/interactive/blessed-session.js +361 -0
- package/dist/interactive/controller.js +1326 -0
- package/dist/interactive/create-interactive-session.js +5 -0
- package/dist/interactive/ink/index.js +597 -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/auto-flow.js +9 -6
- package/dist/pipeline/context.js +7 -5
- package/dist/pipeline/declarative-flow-runner.js +212 -6
- package/dist/pipeline/declarative-flows.js +63 -17
- package/dist/pipeline/execution-routing-config.js +15 -0
- package/dist/pipeline/flow-catalog.js +50 -12
- package/dist/pipeline/flow-run-resume.js +29 -0
- package/dist/pipeline/flow-specs/auto-common.json +90 -360
- package/dist/pipeline/flow-specs/auto-golang.json +81 -360
- 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 +316 -0
- package/dist/pipeline/flow-specs/design-review.json +10 -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 +13 -6
- 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 +7 -1
- package/dist/pipeline/flow-specs/plan.json +51 -71
- 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 +2 -0
- 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/launch-profile-config.js +30 -18
- package/dist/pipeline/node-contract.js +1 -0
- package/dist/pipeline/node-registry.js +115 -6
- 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 +13 -2
- 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 +242 -8
- 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 +38 -36
- 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/plugin-loader.js +389 -0
- package/dist/pipeline/plugin-types.js +1 -0
- package/dist/pipeline/prompt-registry.js +3 -1
- package/dist/pipeline/prompt-runtime.js +4 -1
- package/dist/pipeline/registry.js +71 -4
- package/dist/pipeline/review-iteration.js +26 -0
- package/dist/pipeline/spec-compiler.js +3 -0
- package/dist/pipeline/spec-loader.js +14 -0
- package/dist/pipeline/spec-types.js +3 -0
- package/dist/pipeline/spec-validator.js +20 -0
- package/dist/pipeline/value-resolver.js +76 -2
- package/dist/plugin-sdk.js +1 -0
- package/dist/prompts.js +36 -14
- package/dist/review-severity.js +45 -0
- package/dist/runtime/artifact-registry.js +405 -0
- package/dist/runtime/design-review-input-contract.js +17 -16
- package/dist/runtime/env-loader.js +3 -0
- package/dist/runtime/execution-routing-store.js +134 -0
- package/dist/runtime/execution-routing.js +233 -0
- package/dist/runtime/interactive-execution-routing.js +471 -0
- package/dist/runtime/plan-revise-input-contract.js +35 -32
- package/dist/runtime/planning-bundle.js +123 -0
- package/dist/runtime/ready-to-merge.js +22 -1
- package/dist/runtime/review-input-contract.js +100 -0
- package/dist/structured-artifact-schema-registry.js +9 -0
- package/dist/structured-artifact-schemas.json +140 -1
- package/dist/structured-artifacts.js +77 -6
- package/dist/user-input.js +70 -3
- package/docs/example/.flows/examples/claude-example.json +50 -0
- package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/examples/.flows/claude-example.json +50 -0
- package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/plugin-sdk.md +731 -0
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Typical usage looks like:
|
|
|
10
10
|
|
|
11
11
|
The important part is not that exact chain. The point is that AgentWeaver lets you design, operate, and evolve durable agent harnesses instead of accumulating one-off prompts and shell glue.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
For planning-heavy work, a typical path can now include `plan -> design-review -> implement`, where `design-review` critiques planning artifacts before coding starts.
|
|
14
14
|
|
|
15
15
|
## What It Does
|
|
16
16
|
|
|
@@ -34,13 +34,21 @@ In practice, this means you can treat an agent workflow like an engineered syste
|
|
|
34
34
|
|
|
35
35
|
## Core Concepts
|
|
36
36
|
|
|
37
|
-
- `flow spec`: declarative JSON under `src/pipeline/flow-specs
|
|
37
|
+
- `flow spec`: declarative JSON under `src/pipeline/flow-specs/`, global `~/.agentweaver/.flows/`, or project-local `.agentweaver/.flows/`
|
|
38
38
|
- `node`: reusable runtime unit from `src/pipeline/nodes/`
|
|
39
39
|
- `executor`: integration layer for Jira, Codex, OpenCode, GitLab, shell/process execution, Telegram notifications, and related actions
|
|
40
40
|
- `scope`: isolated workspace key for artifacts and flow state; usually based on Jira task, otherwise derived from git context
|
|
41
41
|
- `artifact`: file produced or consumed by flows, used as the stable contract between stages
|
|
42
42
|
- `flow state`: compact persisted execution metadata used for resume/restart in long-running flows such as `auto-golang`
|
|
43
43
|
|
|
44
|
+
## Семантика Запуска
|
|
45
|
+
|
|
46
|
+
- `resume` возобновляет только реально прерванный запуск и использует сохранённое состояние исполнения без пересборки уже выполненных шагов
|
|
47
|
+
- `continue` предназначен для завершённых итерационных циклов и запускает следующую итерацию от последних валидных артефактов без удаления исторических артефактов
|
|
48
|
+
- `restart` считается новым запуском: текущая активная попытка архивируется в `.agentweaver/scopes/<scope>/.artifacts/restart-archives/attempt-XXXX`, после чего создаётся новая активная попытка
|
|
49
|
+
- Для неоднозначных запусков оператор должен явно выбрать действие: в интерактивном режиме через подтверждение, в неинтерактивном режиме через `--resume`, `--continue` или `--restart`
|
|
50
|
+
- Контракт распространяется на `auto-common`, `auto-simple`, `auto-golang`, `instant-task`, `review-loop`, `run-go-linter-loop` и `run-go-tests-loop`
|
|
51
|
+
|
|
44
52
|
## Declarative Workflow Model
|
|
45
53
|
|
|
46
54
|
The center of the system is the declarative flow spec:
|
|
@@ -62,7 +70,7 @@ This keeps workflow design in JSON while keeping implementation details in typed
|
|
|
62
70
|
- `src/pipeline/` — declarative flow loading, compilation, validation, runtime, and built-in flow specs
|
|
63
71
|
- `src/pipeline/nodes/` — reusable runtime nodes used by flow specs
|
|
64
72
|
- `src/runtime/` — shared runtime services such as command resolution and subprocess execution
|
|
65
|
-
- `src/interactive
|
|
73
|
+
- `src/interactive/` — Ink-based interactive session, controller, state, and view-model logic
|
|
66
74
|
- `src/markdown.ts` — markdown rendering for terminal output
|
|
67
75
|
- `src/structured-artifact-schemas.json` — schemas for machine-readable artifacts
|
|
68
76
|
- `tests/` — automated tests for pipeline behavior
|
|
@@ -72,7 +80,7 @@ This keeps workflow design in JSON while keeping implementation details in typed
|
|
|
72
80
|
User-invokable built-in commands currently map to these flow specs:
|
|
73
81
|
|
|
74
82
|
- `plan` — fetches Jira task with attachments, generates clarifying questions for the developer, collects answers, and produces design, implementation plan, and QA plan as structured JSON and markdown artifacts
|
|
75
|
-
- `design-review` —
|
|
83
|
+
- `design-review` — performs a structured critique of the latest planning artifacts and writes a dedicated `design-review/v1` artifact; `approved_with_warnings` is treated as ready to proceed and may still produce `ready-to-merge.md`
|
|
76
84
|
- `task-describe` — generates a brief task description from a Jira issue or from manual input; when Jira is provided, fetches the issue and summarizes it; otherwise accepts free-form text and analyzes the codebase to produce a richer description
|
|
77
85
|
- `implement` — runs LLM-backed implementation based on previously approved design and plan artifacts; executes code changes locally in the project working directory
|
|
78
86
|
- `review` — performs code review of current changes against the task design and plan; produces structured review findings with severity levels and a ready-to-merge verdict
|
|
@@ -87,7 +95,8 @@ User-invokable built-in commands currently map to these flow specs:
|
|
|
87
95
|
- `run-go-tests-loop` — runs `run_go_tests.py` and analyzes failures; if tests fail, sends the error output to LLM for a fix and retries; repeats up to 5 attempts, stopping early on success
|
|
88
96
|
- `run-go-linter-loop` — runs `run_go_linter.py` and analyzes output; if the linter reports issues, sends them to LLM for a fix and retries; repeats up to 5 attempts, stopping early on success
|
|
89
97
|
- `auto-golang` — end-to-end resumable pipeline for Go projects: plan → implement → linter loop → test loop → review loop → final linter loop → final test loop; supports `--from` to restart from a specific phase and `auto-status`/`auto-reset` for state management
|
|
90
|
-
- `auto-common` —
|
|
98
|
+
- `auto-common` — planning-aware pipeline with a mandatory design-review gate before implementation: plan → design-review loop → implement → review loop; design-review can iterate with `plan-revise` up to 3 times, and if the final verdict still requires revision the operator must explicitly choose whether to continue with the latest planning artifacts or stop
|
|
99
|
+
- `auto-simple` — preserved simplified pipeline equivalent to the legacy auto-common behavior: plan → implement → review loop; no planning review gate, suitable for projects that do not need design review before coding
|
|
91
100
|
- `doctor` — diagnostics command that runs system, executor, and flow readiness health checks; supports filtering by category or check ID and JSON output
|
|
92
101
|
|
|
93
102
|
There are also built-in nested/helper flows that are loaded declaratively but are not direct top-level CLI commands, for example `review-project` (project-level code review used internally when no prior design/plan artifacts are present).
|
|
@@ -117,6 +126,34 @@ node dist/index.js --help
|
|
|
117
126
|
|
|
118
127
|
Global install after publishing:
|
|
119
128
|
|
|
129
|
+
## Plugin SDK
|
|
130
|
+
|
|
131
|
+
AgentWeaver supports local plugins and custom declarative flows from both global and project-local `.agentweaver` directories.
|
|
132
|
+
|
|
133
|
+
Plugin authors must use only the public SDK subpath: `agentweaver/plugin-sdk`.
|
|
134
|
+
The package root `agentweaver`, internal paths such as `agentweaver/dist/*` and `agentweaver/src/*`, and repository-relative source imports are not part of the supported SDK contract.
|
|
135
|
+
|
|
136
|
+
Supported plugin manifest locations are:
|
|
137
|
+
|
|
138
|
+
- `~/.agentweaver/.plugins/<plugin-id>/plugin.json`
|
|
139
|
+
- `.agentweaver/.plugins/<plugin-id>/plugin.json`
|
|
140
|
+
|
|
141
|
+
The plugin directory name and manifest `id` must match exactly.
|
|
142
|
+
|
|
143
|
+
Use the dedicated guide at [docs/plugin-sdk.md](docs/plugin-sdk.md) for:
|
|
144
|
+
|
|
145
|
+
- the executor versus node architecture
|
|
146
|
+
- manifest and entrypoint rules
|
|
147
|
+
- optional routing metadata for plugin LLM executors
|
|
148
|
+
- runtime context APIs available to plugin code
|
|
149
|
+
- global and project-local flow wiring under `~/.agentweaver/.flows/` and `.agentweaver/.flows/`
|
|
150
|
+
- compatibility, testing, troubleshooting, and a complete end-to-end walkthrough
|
|
151
|
+
|
|
152
|
+
Repository reference examples live under `docs/examples/`, for example:
|
|
153
|
+
|
|
154
|
+
- `docs/examples/.plugins/claude-example-plugin/`
|
|
155
|
+
- `docs/examples/.flows/claude-example.json`
|
|
156
|
+
|
|
120
157
|
```bash
|
|
121
158
|
npm install -g agentweaver
|
|
122
159
|
agentweaver --help
|
|
@@ -182,7 +219,7 @@ OPENCODE_MODEL=minimax-coding-plan/MiniMax-M2.7
|
|
|
182
219
|
|
|
183
220
|
The full-screen TUI is not a cosmetic wrapper. It is the operator console for the harness:
|
|
184
221
|
|
|
185
|
-
- browse built-in and project-local workflows
|
|
222
|
+
- browse built-in, global, and project-local workflows
|
|
186
223
|
- launch flows in the current scope
|
|
187
224
|
- inspect progress by phase and step
|
|
188
225
|
- follow activity, prompts, summaries, and statuses
|
|
@@ -220,6 +257,7 @@ agentweaver run-go-tests-loop DEMO-1234
|
|
|
220
257
|
agentweaver run-go-linter-loop DEMO-1234
|
|
221
258
|
agentweaver auto-golang DEMO-1234
|
|
222
259
|
agentweaver auto-common DEMO-1234
|
|
260
|
+
agentweaver auto-simple DEMO-1234
|
|
223
261
|
agentweaver doctor
|
|
224
262
|
agentweaver doctor --json
|
|
225
263
|
agentweaver doctor <category>|<check-id>
|
|
@@ -243,6 +281,7 @@ agentweaver --help
|
|
|
243
281
|
agentweaver --version
|
|
244
282
|
agentweaver auto-golang --help-phases
|
|
245
283
|
agentweaver auto-common --help-phases
|
|
284
|
+
agentweaver auto-simple --help-phases
|
|
246
285
|
agentweaver auto-golang --from <phase> DEMO-1234
|
|
247
286
|
agentweaver auto-status DEMO-1234
|
|
248
287
|
agentweaver auto-reset DEMO-1234
|
|
@@ -257,13 +296,13 @@ Notes:
|
|
|
257
296
|
- `--prompt <text>` appends extra instructions to the prompt
|
|
258
297
|
- `--scope <name>` is supported by scope-flexible flows such as `implement`, `review`, `review-fix`, `review-loop`, `run-go-tests-loop`, `run-go-linter-loop`, `gitlab-review`, and `gitlab-diff-review`
|
|
259
298
|
- `--md-lang <en|ru>` currently applies to `plan`
|
|
260
|
-
- `--force` only affects interactive mode
|
|
299
|
+
- `--force` only affects interactive mode: it skips loading cached summary-pane content on startup so Jira-backed flows that regenerate summary artifacts can repopulate it during the run
|
|
261
300
|
- Jira-backed flows ask for Jira input interactively when it is omitted
|
|
262
301
|
- `task-describe` can also work from manual task description input without Jira
|
|
263
302
|
- `gitlab-review` and `gitlab-diff-review` ask for a GitLab merge request URL interactively
|
|
264
303
|
- `auto-status` and `auto-reset` currently operate on persisted state for `auto-golang`
|
|
265
304
|
|
|
266
|
-
## `auto-golang` and `auto-
|
|
305
|
+
## `auto-golang`, `auto-common`, and `auto-simple`
|
|
267
306
|
|
|
268
307
|
`auto-golang` is the main resumable end-to-end automation flow. It stores persisted execution state and supports:
|
|
269
308
|
|
|
@@ -273,7 +312,9 @@ Notes:
|
|
|
273
312
|
- reset via `auto-reset`
|
|
274
313
|
- resume validation against saved launch profile and required artifacts
|
|
275
314
|
|
|
276
|
-
`auto-common` is
|
|
315
|
+
`auto-common` is the planning-aware built-in automation flow. After `plan`, it runs a `design-review` loop and blocks implementation until the verdict is `approved` or `approved_with_warnings`. When the verdict is `needs_revision`, it runs `plan-revise` and then another `design-review`, for up to 3 design-review iterations total. If the final verdict still requires revision, the pipeline asks the operator whether to continue with the latest planning artifacts or stop before `implement`.
|
|
316
|
+
|
|
317
|
+
`auto-simple` is the preserved simplified pipeline: `plan → implement → review loop`, with no planning review gate and no revise rounds. It is behaviorally equivalent to the legacy `auto-common` before the planning gate was introduced.
|
|
277
318
|
|
|
278
319
|
## Launch Profiles and Resume
|
|
279
320
|
|
|
@@ -300,7 +341,12 @@ The runtime uses artifacts as the contract between stages, including markdown ou
|
|
|
300
341
|
|
|
301
342
|
## Interactive TUI
|
|
302
343
|
|
|
303
|
-
Running without a command opens the full-screen TUI. It acts as the operator console for the harness: browsing flows, launching them in scope, following
|
|
344
|
+
Running without a command opens the full-screen TUI. It acts as the operator console for the harness: browsing flows, launching them in scope, following current execution, and reviewing summaries.
|
|
345
|
+
|
|
346
|
+
Interactive mode is Ink-only. It requires:
|
|
347
|
+
|
|
348
|
+
- a real TTY for both stdin and stdout
|
|
349
|
+
- installed runtime dependencies from `npm install`
|
|
304
350
|
|
|
305
351
|
Current navigation:
|
|
306
352
|
|
|
@@ -308,34 +354,45 @@ Current navigation:
|
|
|
308
354
|
- `Left` / `Right` — collapse or expand folders
|
|
309
355
|
- `Enter` — toggle folder or run selected flow
|
|
310
356
|
- `Tab` / `Shift+Tab` — switch panes
|
|
311
|
-
- `PgUp` / `PgDn`
|
|
357
|
+
- `PgUp` / `PgDn` — scroll focused pane
|
|
312
358
|
- `h` — open help
|
|
313
359
|
- `q` or `Ctrl+C` — exit
|
|
314
360
|
|
|
361
|
+
Current layout:
|
|
362
|
+
|
|
363
|
+
- left column: `Flows`, `Flow Description`, `Status`
|
|
364
|
+
- right column: `Current Flow`, optional `Task Summary`, `Activity`
|
|
365
|
+
- `Current Flow` is intentionally tall and scrollable; in the current layout it uses the same height budget as `Flows`
|
|
366
|
+
- the `Task Summary` pane is runtime-driven and shows whichever markdown artifact the active flow publishes into summary state, such as a normalized task context or a cached task summary
|
|
367
|
+
|
|
315
368
|
Flow discovery behavior:
|
|
316
369
|
|
|
317
370
|
- built-in flows are loaded from `src/pipeline/flow-specs/`
|
|
318
|
-
-
|
|
319
|
-
-
|
|
320
|
-
-
|
|
321
|
-
-
|
|
371
|
+
- global custom flows are loaded from `~/.agentweaver/.flows/`
|
|
372
|
+
- project-local custom flows are loaded from `.agentweaver/.flows/`
|
|
373
|
+
- all discovered flow specs are validated at load time
|
|
374
|
+
- duplicate flow ids fail fast across built-in, global, and project-local sources
|
|
375
|
+
- custom flows are shown separately in the UI as global and project-local groups
|
|
322
376
|
|
|
323
|
-
##
|
|
377
|
+
## Custom Flows
|
|
324
378
|
|
|
325
|
-
You can add
|
|
379
|
+
You can add custom flow specs under either:
|
|
326
380
|
|
|
327
381
|
```bash
|
|
382
|
+
~/.agentweaver/.flows/**/*.json
|
|
328
383
|
.agentweaver/.flows/**/*.json
|
|
329
384
|
```
|
|
330
385
|
|
|
331
|
-
|
|
386
|
+
Custom flows:
|
|
332
387
|
|
|
333
388
|
- are discovered recursively
|
|
334
389
|
- get their flow id from the relative path without `.json`
|
|
335
390
|
- share the same validator and runtime as built-in flows
|
|
336
391
|
- cannot conflict with an existing built-in or other discovered flow id
|
|
337
392
|
|
|
338
|
-
|
|
393
|
+
Use the global directory for reusable personal flows and plugins across repositories, and the project-local directory for repo-specific wiring.
|
|
394
|
+
|
|
395
|
+
Nested `flow-run` steps can reference built-in, global, or project-local specs by file name, as long as the name resolves unambiguously.
|
|
339
396
|
|
|
340
397
|
## Development
|
|
341
398
|
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { TaskRunnerError } from "./errors.js";
|
|
3
|
+
import { validateStructuredArtifactValue } from "./structured-artifacts.js";
|
|
4
|
+
export const ARTIFACT_MANIFEST_SCHEMA_ID = "artifact-manifest/v1";
|
|
5
|
+
export const ARTIFACT_MANIFEST_SCHEMA_VERSION = 1;
|
|
6
|
+
export const ARTIFACT_LIFECYCLE_STATUSES = ["ready", "superseded", "stale"];
|
|
7
|
+
export const ARTIFACT_PAYLOAD_FAMILIES = [
|
|
8
|
+
"structured-json",
|
|
9
|
+
"markdown",
|
|
10
|
+
"plain-text",
|
|
11
|
+
"helper-json",
|
|
12
|
+
"opaque-file",
|
|
13
|
+
];
|
|
14
|
+
const STRUCTURED_JSON_SCHEMAS_BY_PREFIX = [
|
|
15
|
+
{ prefix: "bug-analyze-", schemaId: "bug-analysis/v1" },
|
|
16
|
+
{ prefix: "bug-fix-design-", schemaId: "bug-fix-design/v1" },
|
|
17
|
+
{ prefix: "bug-fix-plan-", schemaId: "bug-fix-plan/v1" },
|
|
18
|
+
{ prefix: "design-review-", schemaId: "design-review/v1" },
|
|
19
|
+
{ prefix: "design-", schemaId: "implementation-design/v1" },
|
|
20
|
+
{ prefix: "gitlab-diff-", schemaId: "gitlab-mr-diff/v1" },
|
|
21
|
+
{ prefix: "gitlab-review-", schemaId: "gitlab-review/v1" },
|
|
22
|
+
{ prefix: "jira-description-", schemaId: "jira-description/v1" },
|
|
23
|
+
{ prefix: "mr-description-", schemaId: "mr-description/v1" },
|
|
24
|
+
{ prefix: "plan-", schemaId: "implementation-plan/v1" },
|
|
25
|
+
{ prefix: "planning-questions-", schemaId: "planning-questions/v1" },
|
|
26
|
+
{ prefix: "qa-", schemaId: "qa-plan/v1" },
|
|
27
|
+
{ prefix: "review-assessment-", schemaId: "review-assessment/v1" },
|
|
28
|
+
{ prefix: "review-fix-", schemaId: "review-fix-report/v1" },
|
|
29
|
+
{ prefix: "review-", schemaId: "review-findings/v1" },
|
|
30
|
+
{ prefix: "task-context-", schemaId: "task-context/v1" },
|
|
31
|
+
{ prefix: "task-", schemaId: "task-summary/v1" },
|
|
32
|
+
];
|
|
33
|
+
const LOGICAL_KEY_PATTERN = "^[a-z0-9][a-z0-9._/-]*$";
|
|
34
|
+
const CONTENT_HASH_PATTERN = "^sha256:[a-f0-9]{64}$";
|
|
35
|
+
const ARTIFACT_ID_PATTERN = /^(?<scope>.+):(?<logicalKey>[a-z0-9][a-z0-9._/-]*):v(?<version>[1-9]\d*)$/;
|
|
36
|
+
const LOGICAL_REF_PATTERN = /^(?<logicalKey>[a-z0-9][a-z0-9._/-]*)@(?<selector>latest|v[1-9]\d*)$/;
|
|
37
|
+
export function parseSchemaVersion(schemaId) {
|
|
38
|
+
const match = /\/v(\d+)$/.exec(schemaId);
|
|
39
|
+
return match ? Number.parseInt(match[1] ?? "1", 10) : 1;
|
|
40
|
+
}
|
|
41
|
+
function normalizePathSeparators(value) {
|
|
42
|
+
return value.replace(/\\/g, "/");
|
|
43
|
+
}
|
|
44
|
+
function removeScopeSuffix(baseName, scopeKey) {
|
|
45
|
+
if (!scopeKey) {
|
|
46
|
+
return baseName;
|
|
47
|
+
}
|
|
48
|
+
const ext = path.extname(baseName);
|
|
49
|
+
const stem = ext ? baseName.slice(0, -ext.length) : baseName;
|
|
50
|
+
const escapedScope = scopeKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
51
|
+
const patterns = [
|
|
52
|
+
new RegExp(`-${escapedScope}-iter-\\d+$`),
|
|
53
|
+
new RegExp(`-${escapedScope}-\\d+$`),
|
|
54
|
+
new RegExp(`-${escapedScope}$`),
|
|
55
|
+
];
|
|
56
|
+
for (const pattern of patterns) {
|
|
57
|
+
if (pattern.test(stem)) {
|
|
58
|
+
return `${stem.replace(pattern, "")}${ext}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (stem === scopeKey) {
|
|
62
|
+
return `scope${ext}`;
|
|
63
|
+
}
|
|
64
|
+
return baseName;
|
|
65
|
+
}
|
|
66
|
+
export function buildLogicalKeyForPayload(scopeKey, payloadPath) {
|
|
67
|
+
const normalized = normalizePathSeparators(payloadPath);
|
|
68
|
+
const normalizedScopeKey = typeof scopeKey === "string" && scopeKey.trim().length > 0 ? scopeKey : null;
|
|
69
|
+
const scopeMarker = normalizedScopeKey
|
|
70
|
+
? normalizePathSeparators(`/.agentweaver/scopes/${normalizedScopeKey}/`)
|
|
71
|
+
: null;
|
|
72
|
+
const markerIndex = scopeMarker ? normalized.lastIndexOf(scopeMarker) : -1;
|
|
73
|
+
const artifactsMarker = normalizePathSeparators("/.artifacts/");
|
|
74
|
+
const artifactsIndex = normalized.lastIndexOf(artifactsMarker);
|
|
75
|
+
const relative = markerIndex >= 0 && scopeMarker
|
|
76
|
+
? normalized.slice(markerIndex + scopeMarker.length)
|
|
77
|
+
: artifactsIndex >= 0
|
|
78
|
+
? normalized.slice(artifactsIndex + 1)
|
|
79
|
+
: path.basename(normalized);
|
|
80
|
+
const directory = path.posix.dirname(relative);
|
|
81
|
+
const baseName = path.posix.basename(relative);
|
|
82
|
+
const normalizedBaseName = removeScopeSuffix(baseName, normalizedScopeKey)
|
|
83
|
+
.replace(/-\d+(?=\.[^.]+$)/, "")
|
|
84
|
+
.replace(/[^a-zA-Z0-9._/-]+/g, "-")
|
|
85
|
+
.replace(/-+/g, "-")
|
|
86
|
+
.toLowerCase();
|
|
87
|
+
const combined = directory === "." ? normalizedBaseName : `${directory}/${normalizedBaseName}`;
|
|
88
|
+
return combined.replace(/^\.artifacts\//, "artifacts/");
|
|
89
|
+
}
|
|
90
|
+
export function buildPublicationKey(input) {
|
|
91
|
+
return `${input.publicationRunId ?? input.runId}:${input.flowId}:${input.phaseId}:${input.stepId}:${input.logicalKey}`;
|
|
92
|
+
}
|
|
93
|
+
export function buildArtifactId(scopeKey, logicalKey, version) {
|
|
94
|
+
return `${scopeKey}:${logicalKey}:v${version}`;
|
|
95
|
+
}
|
|
96
|
+
export function parseArtifactId(value) {
|
|
97
|
+
const match = ARTIFACT_ID_PATTERN.exec(value);
|
|
98
|
+
if (!match?.groups) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const scopeKey = match.groups.scope;
|
|
102
|
+
const logicalKey = match.groups.logicalKey;
|
|
103
|
+
const versionText = match.groups.version;
|
|
104
|
+
if (!scopeKey || !logicalKey || !versionText) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
scopeKey,
|
|
109
|
+
logicalKey,
|
|
110
|
+
version: Number.parseInt(versionText, 10),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export function parseArtifactReference(value) {
|
|
114
|
+
const parsedId = parseArtifactId(value);
|
|
115
|
+
if (parsedId) {
|
|
116
|
+
return {
|
|
117
|
+
kind: "artifact-id",
|
|
118
|
+
artifactId: value,
|
|
119
|
+
parsedId,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const match = LOGICAL_REF_PATTERN.exec(value);
|
|
123
|
+
if (!match?.groups) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const logicalKey = match.groups.logicalKey;
|
|
127
|
+
const selector = match.groups.selector;
|
|
128
|
+
if (!logicalKey || !selector) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
kind: "logical-ref",
|
|
133
|
+
logicalKey,
|
|
134
|
+
version: selector === "latest" ? "latest" : Number.parseInt(selector.slice(1), 10),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function inferPayloadContract(scopeKey, payloadPath, override) {
|
|
138
|
+
if (override?.payloadFamily && override.schemaId && override.schemaVersion) {
|
|
139
|
+
return {
|
|
140
|
+
payloadFamily: override.payloadFamily,
|
|
141
|
+
schemaId: override.schemaId,
|
|
142
|
+
schemaVersion: override.schemaVersion,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const baseName = path.basename(payloadPath);
|
|
146
|
+
if (override?.schemaId) {
|
|
147
|
+
return {
|
|
148
|
+
payloadFamily: override.payloadFamily ?? "structured-json",
|
|
149
|
+
schemaId: override.schemaId,
|
|
150
|
+
schemaVersion: override.schemaVersion ?? parseSchemaVersion(override.schemaId),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (payloadPath.endsWith(".json")) {
|
|
154
|
+
if (baseName === `${scopeKey}.json` || baseName === `${scopeKey}-enriched.json`) {
|
|
155
|
+
return { payloadFamily: "helper-json", schemaId: "helper-json/v1", schemaVersion: 1 };
|
|
156
|
+
}
|
|
157
|
+
if (baseName.startsWith("jira-attachments-")) {
|
|
158
|
+
return { payloadFamily: "helper-json", schemaId: "helper-json/v1", schemaVersion: 1 };
|
|
159
|
+
}
|
|
160
|
+
if (baseName.startsWith("planning-answers-")) {
|
|
161
|
+
return { payloadFamily: "structured-json", schemaId: "user-input/v1", schemaVersion: 1 };
|
|
162
|
+
}
|
|
163
|
+
if (baseName.startsWith("instant-task-input-")) {
|
|
164
|
+
return { payloadFamily: "structured-json", schemaId: "user-input/v1", schemaVersion: 1 };
|
|
165
|
+
}
|
|
166
|
+
for (const candidate of STRUCTURED_JSON_SCHEMAS_BY_PREFIX) {
|
|
167
|
+
if (baseName.startsWith(candidate.prefix)) {
|
|
168
|
+
return {
|
|
169
|
+
payloadFamily: "structured-json",
|
|
170
|
+
schemaId: candidate.schemaId,
|
|
171
|
+
schemaVersion: parseSchemaVersion(candidate.schemaId),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { payloadFamily: "helper-json", schemaId: "helper-json/v1", schemaVersion: 1 };
|
|
176
|
+
}
|
|
177
|
+
if (payloadPath.endsWith(".md")) {
|
|
178
|
+
return { payloadFamily: "markdown", schemaId: "markdown/v1", schemaVersion: 1 };
|
|
179
|
+
}
|
|
180
|
+
if (payloadPath.endsWith(".txt")) {
|
|
181
|
+
return { payloadFamily: "plain-text", schemaId: "plain-text/v1", schemaVersion: 1 };
|
|
182
|
+
}
|
|
183
|
+
return { payloadFamily: "opaque-file", schemaId: "opaque-file/v1", schemaVersion: 1 };
|
|
184
|
+
}
|
|
185
|
+
export function createProducerSummary(input) {
|
|
186
|
+
const parts = [input.node];
|
|
187
|
+
if (input.executor) {
|
|
188
|
+
parts.push(`via ${input.executor}`);
|
|
189
|
+
}
|
|
190
|
+
if (input.model) {
|
|
191
|
+
parts.push(`model ${input.model}`);
|
|
192
|
+
}
|
|
193
|
+
return parts.join(" ");
|
|
194
|
+
}
|
|
195
|
+
export function validateArtifactManifest(manifest, label = "$") {
|
|
196
|
+
validateStructuredArtifactValue(manifest, ARTIFACT_MANIFEST_SCHEMA_ID, label);
|
|
197
|
+
if (!new RegExp(LOGICAL_KEY_PATTERN).test(manifest.logical_key)) {
|
|
198
|
+
throw new TaskRunnerError(`Structured artifact ${label} failed schema ${ARTIFACT_MANIFEST_SCHEMA_ID} validation:\n${label}.logical_key must be a string with the expected format`);
|
|
199
|
+
}
|
|
200
|
+
if (!new RegExp(CONTENT_HASH_PATTERN).test(manifest.content_hash)) {
|
|
201
|
+
throw new TaskRunnerError(`Structured artifact ${label} failed schema ${ARTIFACT_MANIFEST_SCHEMA_ID} validation:\n${label}.content_hash must be a string with the expected format`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export function diagnosticsForManifest(manifest, validatePayload) {
|
|
205
|
+
try {
|
|
206
|
+
validatePayload(manifest.schema_id, manifest.payload_path);
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const message = error.message;
|
|
211
|
+
return [
|
|
212
|
+
{
|
|
213
|
+
code: message.includes("not registered") ? "missing-schema" : "invalid-schema",
|
|
214
|
+
severity: "error",
|
|
215
|
+
message,
|
|
216
|
+
},
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
}
|
package/dist/artifacts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { TaskRunnerError } from "./errors.js";
|
|
@@ -24,6 +24,12 @@ export function scopeArtifactsDir(scopeKey) {
|
|
|
24
24
|
export function scopeArtifactsFile(scopeKey, fileName) {
|
|
25
25
|
return path.join(scopeArtifactsDir(scopeKey), fileName);
|
|
26
26
|
}
|
|
27
|
+
export function artifactManifestSidecarPath(payloadPath) {
|
|
28
|
+
return `${payloadPath}.manifest.json`;
|
|
29
|
+
}
|
|
30
|
+
export function artifactIndexFile(scopeKey) {
|
|
31
|
+
return scopeArtifactsFile(scopeKey, "artifact-index.json");
|
|
32
|
+
}
|
|
27
33
|
export function taskWorkspaceDir(taskKey) {
|
|
28
34
|
return scopeWorkspaceDir(taskKey);
|
|
29
35
|
}
|
|
@@ -151,9 +157,18 @@ export function jiraDescriptionFile(taskKey, iteration) {
|
|
|
151
157
|
export function jiraDescriptionJsonFile(taskKey, iteration) {
|
|
152
158
|
return versionedJsonArtifactFile(taskKey, "jira-description", iteration);
|
|
153
159
|
}
|
|
160
|
+
export function taskContextFile(taskKey, iteration) {
|
|
161
|
+
return versionedMarkdownArtifactFile(taskKey, "task-context", iteration);
|
|
162
|
+
}
|
|
163
|
+
export function taskContextJsonFile(taskKey, iteration) {
|
|
164
|
+
return versionedJsonArtifactFile(taskKey, "task-context", iteration);
|
|
165
|
+
}
|
|
154
166
|
export function taskDescribeInputJsonFile(taskKey) {
|
|
155
167
|
return taskArtifactsFile(taskKey, `task-describe-input-${taskKey}.json`);
|
|
156
168
|
}
|
|
169
|
+
export function instantTaskInputJsonFile(taskKey) {
|
|
170
|
+
return taskArtifactsFile(taskKey, `instant-task-input-${taskKey}.json`);
|
|
171
|
+
}
|
|
157
172
|
export function gitStatusJsonFile(taskKey) {
|
|
158
173
|
return taskArtifactsFile(taskKey, `git-status-${taskKey}.json`);
|
|
159
174
|
}
|
|
@@ -199,8 +214,78 @@ export function gitlabDiffReviewInputJsonFile(taskKey) {
|
|
|
199
214
|
export function flowStateFile(scopeKey, flowId) {
|
|
200
215
|
return scopeArtifactsFile(scopeKey, `.agentweaver-flow-state-${encodeURIComponent(flowId)}.json`);
|
|
201
216
|
}
|
|
202
|
-
export function
|
|
203
|
-
return
|
|
217
|
+
export function restartArchivesDir(scopeKey) {
|
|
218
|
+
return scopeArtifactsFile(scopeKey, "restart-archives");
|
|
219
|
+
}
|
|
220
|
+
function nextRestartArchiveName(scopeKey) {
|
|
221
|
+
const archiveRoot = restartArchivesDir(scopeKey);
|
|
222
|
+
if (!existsSync(archiveRoot)) {
|
|
223
|
+
return "attempt-0001";
|
|
224
|
+
}
|
|
225
|
+
const attemptNumbers = readdirSync(archiveRoot, { withFileTypes: true })
|
|
226
|
+
.filter((entry) => entry.isDirectory())
|
|
227
|
+
.map((entry) => /^attempt-(\d{4})$/.exec(entry.name)?.[1] ?? null)
|
|
228
|
+
.filter((value) => value !== null)
|
|
229
|
+
.map((value) => Number.parseInt(value, 10));
|
|
230
|
+
const nextNumber = (attemptNumbers.length === 0 ? 0 : Math.max(...attemptNumbers)) + 1;
|
|
231
|
+
return `attempt-${String(nextNumber).padStart(4, "0")}`;
|
|
232
|
+
}
|
|
233
|
+
export function archiveActiveAttempt(scopeKey) {
|
|
234
|
+
const workspaceDir = scopeWorkspaceDir(scopeKey);
|
|
235
|
+
if (!existsSync(workspaceDir)) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
const workspaceEntries = readdirSync(workspaceDir, { withFileTypes: true })
|
|
239
|
+
.filter((entry) => entry.name !== ".artifacts");
|
|
240
|
+
const artifactEntries = readdirSync(scopeArtifactsDir(scopeKey), { withFileTypes: true })
|
|
241
|
+
.filter((entry) => entry.name !== "restart-archives");
|
|
242
|
+
if (workspaceEntries.length === 0 && artifactEntries.length === 0) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const archiveRoot = restartArchivesDir(scopeKey);
|
|
246
|
+
mkdirSync(archiveRoot, { recursive: true });
|
|
247
|
+
const archiveDir = path.join(archiveRoot, nextRestartArchiveName(scopeKey));
|
|
248
|
+
const workspaceArchiveDir = path.join(archiveDir, "workspace");
|
|
249
|
+
const artifactsArchiveDir = path.join(archiveDir, "artifacts");
|
|
250
|
+
mkdirSync(workspaceArchiveDir, { recursive: true });
|
|
251
|
+
mkdirSync(artifactsArchiveDir, { recursive: true });
|
|
252
|
+
try {
|
|
253
|
+
for (const entry of workspaceEntries) {
|
|
254
|
+
cpSync(path.join(workspaceDir, entry.name), path.join(workspaceArchiveDir, entry.name), {
|
|
255
|
+
recursive: true,
|
|
256
|
+
errorOnExist: true,
|
|
257
|
+
force: false,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
for (const entry of artifactEntries) {
|
|
261
|
+
cpSync(path.join(scopeArtifactsDir(scopeKey), entry.name), path.join(artifactsArchiveDir, entry.name), {
|
|
262
|
+
recursive: true,
|
|
263
|
+
errorOnExist: true,
|
|
264
|
+
force: false,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
rmSync(archiveDir, { recursive: true, force: true });
|
|
270
|
+
throw new TaskRunnerError(`Failed to archive active attempt for restart: ${error.message}`);
|
|
271
|
+
}
|
|
272
|
+
for (const entry of workspaceEntries) {
|
|
273
|
+
rmSync(path.join(workspaceDir, entry.name), { recursive: true, force: true });
|
|
274
|
+
}
|
|
275
|
+
for (const entry of artifactEntries) {
|
|
276
|
+
rmSync(path.join(scopeArtifactsDir(scopeKey), entry.name), { recursive: true, force: true });
|
|
277
|
+
}
|
|
278
|
+
return archiveDir;
|
|
279
|
+
}
|
|
280
|
+
export function planArtifacts(taskKey, iteration) {
|
|
281
|
+
return [
|
|
282
|
+
designFile(taskKey, iteration),
|
|
283
|
+
designJsonFile(taskKey, iteration),
|
|
284
|
+
planFile(taskKey, iteration),
|
|
285
|
+
planJsonFile(taskKey, iteration),
|
|
286
|
+
qaFile(taskKey, iteration),
|
|
287
|
+
qaJsonFile(taskKey, iteration),
|
|
288
|
+
];
|
|
204
289
|
}
|
|
205
290
|
export function bugAnalyzeArtifacts(taskKey) {
|
|
206
291
|
return [
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { DoctorImpact, DoctorStatus } from "../types.js";
|
|
5
5
|
import { CATEGORY } from "./category.js";
|
|
6
6
|
import { detectJiraDeployment } from "../../jira.js";
|
|
7
|
+
import { AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV, parseReviewSeverityCsv, } from "../../review-severity.js";
|
|
7
8
|
const MONITORED_KEYS = [
|
|
8
9
|
"JIRA_API_KEY",
|
|
9
10
|
"JIRA_USERNAME",
|
|
@@ -11,6 +12,7 @@ const MONITORED_KEYS = [
|
|
|
11
12
|
"JIRA_BASE_URL",
|
|
12
13
|
"GITLAB_TOKEN",
|
|
13
14
|
"AGENTWEAVER_HOME",
|
|
15
|
+
"AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES",
|
|
14
16
|
"CODEX_BIN",
|
|
15
17
|
"CODEX_MODEL",
|
|
16
18
|
"OPENCODE_BIN",
|
|
@@ -23,6 +25,7 @@ const KEY_NOTES = {
|
|
|
23
25
|
JIRA_USERNAME: "Required only for Jira Cloud basic auth.",
|
|
24
26
|
GITLAB_TOKEN: "Required for GitLab-backed flows.",
|
|
25
27
|
AGENTWEAVER_HOME: "Optional override for the AgentWeaver package home.",
|
|
28
|
+
AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES: "Optional default list of merge-blocking severities for review and review-fix.",
|
|
26
29
|
CODEX_BIN: "Optional override for the codex executable path.",
|
|
27
30
|
CODEX_MODEL: "Optional fallback model override for Codex-backed executors.",
|
|
28
31
|
OPENCODE_BIN: "Optional override for the opencode executable path.",
|
|
@@ -86,6 +89,9 @@ function defaultNote(key) {
|
|
|
86
89
|
if (key === "JIRA_AUTH_MODE") {
|
|
87
90
|
return "Defaults to auto.";
|
|
88
91
|
}
|
|
92
|
+
if (key === AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV) {
|
|
93
|
+
return "Defaults to blocker,critical,high.";
|
|
94
|
+
}
|
|
89
95
|
return null;
|
|
90
96
|
}
|
|
91
97
|
function validateJiraAuthMode(value) {
|
|
@@ -94,6 +100,18 @@ function validateJiraAuthMode(value) {
|
|
|
94
100
|
}
|
|
95
101
|
return JIRA_AUTH_MODE_ALLOWED_VALUES.includes(value.trim().toLowerCase());
|
|
96
102
|
}
|
|
103
|
+
function validateReviewBlockingSeverities(value) {
|
|
104
|
+
if (value === null) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
parseReviewSeverityCsv(value);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
97
115
|
function keyNote(key) {
|
|
98
116
|
return KEY_NOTES[key];
|
|
99
117
|
}
|
|
@@ -165,6 +183,7 @@ function checkEnvDiagnostics() {
|
|
|
165
183
|
const jiraUsername = process.env.JIRA_USERNAME?.trim() || null;
|
|
166
184
|
const jiraAuthModeRaw = process.env.JIRA_AUTH_MODE?.trim() || null;
|
|
167
185
|
const jiraBaseUrl = process.env.JIRA_BASE_URL?.trim() || null;
|
|
186
|
+
const reviewBlockingSeveritiesRaw = process.env[AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV]?.trim() || null;
|
|
168
187
|
for (const key of MONITORED_KEYS) {
|
|
169
188
|
const currentValue = process.env[key]?.trim() || null;
|
|
170
189
|
const isSecret = SECRET_KEYS.has(key);
|
|
@@ -180,6 +199,9 @@ function checkEnvDiagnostics() {
|
|
|
180
199
|
if (key === "JIRA_AUTH_MODE" && currentValue !== null && !validateJiraAuthMode(currentValue)) {
|
|
181
200
|
state = "invalid";
|
|
182
201
|
}
|
|
202
|
+
if (key === AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV && currentValue !== null && !validateReviewBlockingSeverities(currentValue)) {
|
|
203
|
+
state = "invalid";
|
|
204
|
+
}
|
|
183
205
|
const note = state === "defaulted" ? defaultHint ?? undefined : keyNote(key);
|
|
184
206
|
keyInfos.push({
|
|
185
207
|
key,
|
|
@@ -197,6 +219,9 @@ function checkEnvDiagnostics() {
|
|
|
197
219
|
if (jiraAuthModeRaw !== null && !validateJiraAuthMode(jiraAuthModeRaw)) {
|
|
198
220
|
warnings.push("JIRA_AUTH_MODE must be one of: auto, basic, bearer.");
|
|
199
221
|
}
|
|
222
|
+
if (reviewBlockingSeveritiesRaw !== null && !validateReviewBlockingSeverities(reviewBlockingSeveritiesRaw)) {
|
|
223
|
+
warnings.push("AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES must be a comma-separated list of: blocker, critical, high, medium, low, info.");
|
|
224
|
+
}
|
|
200
225
|
if (jiraHasCorePair && jiraBaseUrl) {
|
|
201
226
|
const authMode = jiraAuthModeRaw?.toLowerCase() || "auto";
|
|
202
227
|
const isCloud = detectJiraDeployment(jiraBaseUrl) === "cloud";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { DoctorStatus } from "../types.js";
|
|
3
3
|
import { CATEGORY } from "./category.js";
|
|
4
|
-
import {
|
|
4
|
+
import { allowedModelsForExecutor } from "../../pipeline/launch-profile-config.js";
|
|
5
5
|
import { findCmdPath, isExecutable } from "../../runtime/command-resolution.js";
|
|
6
6
|
function getEnvVarName(executorId) {
|
|
7
7
|
return executorId === "codex" ? "CODEX_BIN" : "OPENCODE_BIN";
|
|
@@ -72,7 +72,7 @@ function checkExecutor(executorId) {
|
|
|
72
72
|
if (versionOutput === null) {
|
|
73
73
|
return createResult(executorId, DoctorStatus.Fail, `${executorId} --version check failed`, `${executorId} --version did not produce expected output`, `path: ${resolution.path}, source: ${resolution.source}`, resolution, null);
|
|
74
74
|
}
|
|
75
|
-
const allowedModels =
|
|
75
|
+
const allowedModels = allowedModelsForExecutor(executorId);
|
|
76
76
|
const modelWarnings = [];
|
|
77
77
|
for (const model of allowedModels) {
|
|
78
78
|
const modelResult = spawnSync(resolution.path, ["--model", model, "--version"], { encoding: "utf8", stdio: "pipe" });
|