agentweaver 0.1.14 → 0.1.15

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 CHANGED
@@ -10,6 +10,8 @@ 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
+ Для planning-heavy задач типовой путь теперь может включать и `plan -> design-review -> implement`, где `design-review` проверяет качество артефактов планирования до начала кодинга.
14
+
13
15
  ## What It Does
14
16
 
15
17
  - Fetches Jira issue context by issue key or browse URL
@@ -70,6 +72,7 @@ This keeps workflow design in JSON while keeping implementation details in typed
70
72
  User-invokable built-in commands currently map to these flow specs:
71
73
 
72
74
  - `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` — выполняет структурированную критику planning artifacts и пишет dedicated artifact `design-review/v1`; статус `approved_with_warnings` считается ready-to-proceed и по-прежнему допускает `ready-to-merge.md`
73
76
  - `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
74
77
  - `implement` — runs LLM-backed implementation based on previously approved design and plan artifacts; executes code changes locally in the project working directory
75
78
  - `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
@@ -201,6 +204,7 @@ Direct flow execution:
201
204
 
202
205
  ```bash
203
206
  agentweaver plan DEMO-1234
207
+ agentweaver design-review DEMO-1234
204
208
  agentweaver task-describe DEMO-1234
205
209
  agentweaver implement DEMO-1234
206
210
  agentweaver review DEMO-1234
@@ -225,6 +229,7 @@ From a source checkout:
225
229
 
226
230
  ```bash
227
231
  node dist/index.js plan DEMO-1234
232
+ node dist/index.js design-review DEMO-1234
228
233
  node dist/index.js implement DEMO-1234
229
234
  node dist/index.js review DEMO-1234
230
235
  node dist/index.js auto-golang DEMO-1234
package/dist/artifacts.js CHANGED
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync, readdirSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
4
  import { TaskRunnerError } from "./errors.js";
5
- export const REVIEW_FILE_RE = /^review-(.+)-(\d+)\.md$/;
6
5
  export const READY_TO_MERGE_FILE = "ready-to-merge.md";
7
6
  export function scopesRootDir() {
8
7
  return path.join(process.cwd(), ".agentweaver", "scopes");
@@ -219,6 +218,12 @@ export function reviewFile(taskKey, iteration) {
219
218
  export function reviewJsonFile(taskKey, iteration) {
220
219
  return artifactJsonFile("review", taskKey, iteration);
221
220
  }
221
+ export function designReviewFile(taskKey, iteration) {
222
+ return artifactFile("design-review", taskKey, iteration);
223
+ }
224
+ export function designReviewJsonFile(taskKey, iteration) {
225
+ return artifactJsonFile("design-review", taskKey, iteration);
226
+ }
222
227
  export function reviewFixFile(taskKey, iteration) {
223
228
  return artifactFile("review-fix", taskKey, iteration);
224
229
  }
@@ -1,7 +1,6 @@
1
1
  import { accessSync, constants, existsSync } from "node:fs";
2
- import { DoctorStatus } from "../types.js";
2
+ import { DoctorImpact, DoctorStatus } from "../types.js";
3
3
  import { CATEGORY } from "./category.js";
4
- const SOFT_CHECK_ID = "cwd-context-01";
5
4
  export const cwdContextCheck = {
6
5
  id: "cwd-context-01",
7
6
  category: CATEGORY.CWD_CONTEXT,
@@ -37,6 +36,7 @@ export const cwdContextCheck = {
37
36
  if (permissionStatus === DoctorStatus.Ok && gitStatus === DoctorStatus.Ok) {
38
37
  return {
39
38
  id: "cwd-context-01",
39
+ impact: DoctorImpact.Blocking,
40
40
  status: DoctorStatus.Ok,
41
41
  title: "cwd-context",
42
42
  message: `${permissionMessage}; ${gitStatusMessage}`,
@@ -46,6 +46,7 @@ export const cwdContextCheck = {
46
46
  if (permissionStatus === DoctorStatus.Ok && gitStatus === DoctorStatus.Warn) {
47
47
  return {
48
48
  id: "cwd-context-01",
49
+ impact: DoctorImpact.Advisory,
49
50
  status: DoctorStatus.Warn,
50
51
  title: "cwd-context",
51
52
  message: `${permissionMessage}; ${gitStatusMessage} (soft warning)`,
@@ -55,6 +56,7 @@ export const cwdContextCheck = {
55
56
  }
56
57
  const result = {
57
58
  id: "cwd-context-01",
59
+ impact: DoctorImpact.Blocking,
58
60
  status: permissionStatus,
59
61
  title: "cwd-context",
60
62
  message: permissionMessage,
@@ -66,4 +68,3 @@ export const cwdContextCheck = {
66
68
  return result;
67
69
  },
68
70
  };
69
- export const SOFT_CHECK_IDS = [SOFT_CHECK_ID];
@@ -1,8 +1,9 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import { DoctorStatus } from "../types.js";
4
+ import { DoctorImpact, DoctorStatus } from "../types.js";
5
5
  import { CATEGORY } from "./category.js";
6
+ import { detectJiraDeployment } from "../../jira.js";
6
7
  const MONITORED_KEYS = [
7
8
  "JIRA_API_KEY",
8
9
  "JIRA_USERNAME",
@@ -17,19 +18,16 @@ const MONITORED_KEYS = [
17
18
  ];
18
19
  const SECRET_KEYS = new Set(["JIRA_API_KEY", "GITLAB_TOKEN"]);
19
20
  const JIRA_AUTH_MODE_ALLOWED_VALUES = ["auto", "basic", "bearer"];
20
- function maskSecret(value) {
21
- if (value.length <= 6) {
22
- if (value.length <= 3) {
23
- return "***";
24
- }
25
- const start = value.slice(0, Math.ceil(value.length / 2));
26
- const end = value.slice(Math.ceil(value.length / 2));
27
- return `${start}***${end}`;
28
- }
29
- const start = value.slice(0, 3);
30
- const end = value.slice(-3);
31
- return `${start}***${end}`;
32
- }
21
+ const KEY_NOTES = {
22
+ JIRA_API_KEY: "Required for Jira-backed flows.",
23
+ JIRA_USERNAME: "Required only for Jira Cloud basic auth.",
24
+ GITLAB_TOKEN: "Required for GitLab-backed flows.",
25
+ AGENTWEAVER_HOME: "Optional override for the AgentWeaver package home.",
26
+ CODEX_BIN: "Optional override for the codex executable path.",
27
+ CODEX_MODEL: "Optional fallback model override for Codex-backed executors.",
28
+ OPENCODE_BIN: "Optional override for the opencode executable path.",
29
+ OPENCODE_MODEL: "Optional fallback model override for OpenCode-backed executors.",
30
+ };
33
31
  function globalConfigDir() {
34
32
  return path.join(os.homedir(), ".agentweaver");
35
33
  }
@@ -69,101 +67,200 @@ function getProjectEnvPath() {
69
67
  function getGlobalEnvPath() {
70
68
  return path.join(globalConfigDir(), ".env");
71
69
  }
72
- function determineSource(key, shellSnapshot, currentValue) {
70
+ function determineSource(key, shellSnapshot, projectEnv, globalEnv, currentValue) {
73
71
  if (currentValue === null) {
74
72
  return "missing";
75
73
  }
76
- const isInShellSnapshot = shellSnapshot.hasOwnProperty(key);
77
- if (isInShellSnapshot) {
74
+ if (Object.prototype.hasOwnProperty.call(shellSnapshot, key)) {
78
75
  return "shell";
79
76
  }
80
- const globalEnv = parseEnvFileRaw(getGlobalEnvPath());
81
- if (globalEnv.hasOwnProperty(key)) {
82
- return "global";
83
- }
84
- const projectEnv = parseEnvFileRaw(getProjectEnvPath());
85
- if (projectEnv.hasOwnProperty(key)) {
77
+ if (Object.prototype.hasOwnProperty.call(projectEnv, key)) {
86
78
  return "project-local";
87
79
  }
80
+ if (Object.prototype.hasOwnProperty.call(globalEnv, key)) {
81
+ return "global";
82
+ }
88
83
  return "shell";
89
84
  }
90
- function getDefaultValue(key) {
91
- switch (key) {
92
- case "AGENTWEAVER_HOME":
93
- return path.join(os.homedir(), ".agentweaver");
94
- default:
95
- return null;
85
+ function defaultNote(key) {
86
+ if (key === "JIRA_AUTH_MODE") {
87
+ return "Defaults to auto.";
96
88
  }
89
+ return null;
97
90
  }
98
91
  function validateJiraAuthMode(value) {
99
92
  if (value === null) {
100
93
  return true;
101
94
  }
102
- return JIRA_AUTH_MODE_ALLOWED_VALUES.includes(value);
95
+ return JIRA_AUTH_MODE_ALLOWED_VALUES.includes(value.trim().toLowerCase());
96
+ }
97
+ function keyNote(key) {
98
+ return KEY_NOTES[key];
99
+ }
100
+ function formatKeyLine(info) {
101
+ const source = info.state === "unset" ? "unset" : info.source === "default" ? "default" : info.source;
102
+ const secretLabel = info.isSecret ? ", secret" : "";
103
+ const noteLabel = info.note ? `, ${info.note}` : "";
104
+ return `- ${info.key} (${source}${secretLabel}${noteLabel})`;
105
+ }
106
+ function buildDetails(configured, defaulted, unset, invalid, warnings) {
107
+ const lines = [];
108
+ if (warnings.length > 0) {
109
+ lines.push("warnings:");
110
+ for (const warning of warnings) {
111
+ lines.push(`- ${warning}`);
112
+ }
113
+ }
114
+ if (configured.length > 0) {
115
+ if (lines.length > 0) {
116
+ lines.push("");
117
+ }
118
+ lines.push("configured:");
119
+ for (const info of configured) {
120
+ lines.push(formatKeyLine(info));
121
+ }
122
+ }
123
+ if (defaulted.length > 0) {
124
+ if (lines.length > 0) {
125
+ lines.push("");
126
+ }
127
+ lines.push("defaulted:");
128
+ for (const info of defaulted) {
129
+ lines.push(formatKeyLine(info));
130
+ }
131
+ }
132
+ if (unset.length > 0) {
133
+ if (lines.length > 0) {
134
+ lines.push("");
135
+ }
136
+ lines.push("unset:");
137
+ for (const info of unset) {
138
+ lines.push(formatKeyLine(info));
139
+ }
140
+ }
141
+ if (invalid.length > 0) {
142
+ if (lines.length > 0) {
143
+ lines.push("");
144
+ }
145
+ lines.push("invalid:");
146
+ for (const info of invalid) {
147
+ lines.push(formatKeyLine(info));
148
+ }
149
+ }
150
+ return lines.join("\n");
103
151
  }
104
152
  function checkEnvDiagnostics() {
105
153
  const shellSnapshot = {};
106
154
  for (const key of Object.keys(process.env)) {
107
- shellSnapshot[key] = process.env[key];
155
+ const value = process.env[key];
156
+ if (value !== undefined) {
157
+ shellSnapshot[key] = value;
158
+ }
108
159
  }
109
160
  const projectEnv = parseEnvFileRaw(getProjectEnvPath());
110
161
  const globalEnv = parseEnvFileRaw(getGlobalEnvPath());
111
162
  const keyInfos = [];
112
- let hasWarnings = false;
163
+ const warnings = [];
164
+ const jiraApiKey = process.env.JIRA_API_KEY?.trim() || null;
165
+ const jiraUsername = process.env.JIRA_USERNAME?.trim() || null;
166
+ const jiraAuthModeRaw = process.env.JIRA_AUTH_MODE?.trim() || null;
167
+ const jiraBaseUrl = process.env.JIRA_BASE_URL?.trim() || null;
113
168
  for (const key of MONITORED_KEYS) {
114
- const currentValue = process.env[key] ?? null;
115
- const source = determineSource(key, shellSnapshot, currentValue);
169
+ const currentValue = process.env[key]?.trim() || null;
116
170
  const isSecret = SECRET_KEYS.has(key);
117
- let maskedValue = null;
118
- if (currentValue !== null && isSecret) {
119
- maskedValue = maskSecret(currentValue);
171
+ const source = determineSource(key, shellSnapshot, projectEnv, globalEnv, currentValue);
172
+ const defaultHint = currentValue === null ? defaultNote(key) : null;
173
+ let state = "configured";
174
+ if (currentValue === null && defaultHint) {
175
+ state = "defaulted";
176
+ }
177
+ else if (currentValue === null) {
178
+ state = "unset";
120
179
  }
121
- else if (currentValue !== null) {
122
- maskedValue = currentValue;
180
+ if (key === "JIRA_AUTH_MODE" && currentValue !== null && !validateJiraAuthMode(currentValue)) {
181
+ state = "invalid";
123
182
  }
124
- const keyInfo = {
183
+ const note = state === "defaulted" ? defaultHint ?? undefined : keyNote(key);
184
+ keyInfos.push({
125
185
  key,
126
- source,
127
- value: currentValue,
128
- maskedValue,
186
+ source: state === "defaulted" ? "default" : source,
187
+ state,
129
188
  isSecret,
130
- };
131
- keyInfos.push(keyInfo);
132
- if (source === "missing") {
133
- hasWarnings = true;
134
- }
135
- if (key === "JIRA_AUTH_MODE" && currentValue !== null) {
136
- if (!validateJiraAuthMode(currentValue)) {
137
- hasWarnings = true;
138
- }
139
- }
140
- }
141
- const status = hasWarnings ? DoctorStatus.Warn : DoctorStatus.Ok;
142
- const keyCount = keyInfos.length;
143
- const missingCount = keyInfos.filter(k => k.source === "missing").length;
144
- const secretCount = keyInfos.filter(k => k.isSecret).length;
145
- const summaryParts = [];
146
- summaryParts.push(`${keyCount} keys checked`);
147
- if (missingCount > 0) {
148
- summaryParts.push(`${missingCount} missing`);
149
- }
150
- if (secretCount > 0) {
151
- summaryParts.push(`${secretCount} secrets`);
152
- }
153
- const result = {
189
+ ...(note ? { note } : {}),
190
+ });
191
+ }
192
+ const jiraHasAnyConfig = !!(jiraApiKey || jiraBaseUrl || jiraUsername || jiraAuthModeRaw);
193
+ const jiraHasCorePair = !!(jiraApiKey && jiraBaseUrl);
194
+ if ((jiraApiKey && !jiraBaseUrl) || (!jiraApiKey && jiraBaseUrl)) {
195
+ warnings.push("Jira configuration is partial: set both JIRA_API_KEY and JIRA_BASE_URL for Jira-backed flows.");
196
+ }
197
+ if (jiraAuthModeRaw !== null && !validateJiraAuthMode(jiraAuthModeRaw)) {
198
+ warnings.push("JIRA_AUTH_MODE must be one of: auto, basic, bearer.");
199
+ }
200
+ if (jiraHasCorePair && jiraBaseUrl) {
201
+ const authMode = jiraAuthModeRaw?.toLowerCase() || "auto";
202
+ const isCloud = detectJiraDeployment(jiraBaseUrl) === "cloud";
203
+ const usesBasicAuth = authMode === "basic" || (authMode === "auto" && isCloud);
204
+ if (usesBasicAuth && !jiraUsername) {
205
+ warnings.push("JIRA_USERNAME is required for Jira Cloud basic auth with the current Jira URL and auth mode.");
206
+ }
207
+ }
208
+ else if (jiraHasAnyConfig && jiraUsername && !jiraApiKey && !jiraBaseUrl) {
209
+ warnings.push("JIRA_USERNAME is set without the Jira API key/base URL pair.");
210
+ }
211
+ const configured = keyInfos.filter((info) => info.state === "configured");
212
+ const defaulted = keyInfos.filter((info) => info.state === "defaulted");
213
+ const unset = keyInfos.filter((info) => info.state === "unset");
214
+ const invalid = keyInfos.filter((info) => info.state === "invalid");
215
+ const status = warnings.length > 0 ? DoctorStatus.Warn : DoctorStatus.Ok;
216
+ const summary = {
217
+ checked: keyInfos.length,
218
+ configured: configured.length,
219
+ defaulted: defaulted.length,
220
+ unset: unset.length,
221
+ invalid: invalid.length,
222
+ secrets: keyInfos.filter((info) => info.isSecret).length,
223
+ };
224
+ const messageParts = [
225
+ `${summary.checked} keys checked`,
226
+ `${summary.configured} configured`,
227
+ ];
228
+ if (summary.defaulted > 0) {
229
+ messageParts.push(`${summary.defaulted} defaulted`);
230
+ }
231
+ if (summary.unset > 0) {
232
+ messageParts.push(`${summary.unset} unset`);
233
+ }
234
+ if (summary.invalid > 0) {
235
+ messageParts.push(`${summary.invalid} invalid`);
236
+ }
237
+ const details = buildDetails(configured, defaulted, unset, invalid, warnings);
238
+ const data = {
239
+ kind: "env-config",
240
+ summary,
241
+ warnings,
242
+ keys: keyInfos,
243
+ };
244
+ return {
154
245
  id: "env-diagnostics-01",
246
+ impact: DoctorImpact.Advisory,
155
247
  status,
156
248
  title: "env-config",
157
- message: summaryParts.join(", "),
158
- ...(missingCount > 0 ? { hint: `${missingCount} configuration keys are missing` } : {}),
159
- details: JSON.stringify(keyInfos),
249
+ message: messageParts.join(", "),
250
+ ...(warnings.length > 0
251
+ ? { hint: `${warnings.length} configuration issue${warnings.length === 1 ? "" : "s"} detected` }
252
+ : unset.length > 0
253
+ ? { hint: "Unset keys are optional unless you use the related integrations or overrides" }
254
+ : {}),
255
+ details,
256
+ data,
160
257
  };
161
- return result;
162
258
  }
163
259
  export const envDiagnosticsCheck = {
164
260
  id: "env-diagnostics-01",
165
261
  category: CATEGORY.ENV_DIAGNOSTICS,
166
262
  title: "env-config",
263
+ impact: DoctorImpact.Advisory,
167
264
  dependencies: [],
168
265
  execute: async () => {
169
266
  return checkEnvDiagnostics();