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.
Files changed (110) hide show
  1. package/README.md +76 -19
  2. package/dist/artifact-manifest.js +219 -0
  3. package/dist/artifacts.js +88 -3
  4. package/dist/doctor/checks/env-diagnostics.js +25 -0
  5. package/dist/doctor/checks/executors.js +2 -2
  6. package/dist/doctor/checks/flow-readiness.js +15 -18
  7. package/dist/flow-state.js +212 -15
  8. package/dist/index.js +539 -209
  9. package/dist/interactive/blessed-session.js +361 -0
  10. package/dist/interactive/controller.js +1326 -0
  11. package/dist/interactive/create-interactive-session.js +5 -0
  12. package/dist/interactive/ink/index.js +597 -0
  13. package/dist/interactive/progress.js +245 -0
  14. package/dist/interactive/selectors.js +14 -0
  15. package/dist/interactive/session.js +1 -0
  16. package/dist/interactive/state.js +34 -0
  17. package/dist/interactive/tree.js +155 -0
  18. package/dist/interactive/types.js +1 -0
  19. package/dist/interactive/view-model.js +1 -0
  20. package/dist/interactive-ui.js +159 -194
  21. package/dist/pipeline/auto-flow.js +9 -6
  22. package/dist/pipeline/context.js +7 -5
  23. package/dist/pipeline/declarative-flow-runner.js +212 -6
  24. package/dist/pipeline/declarative-flows.js +63 -17
  25. package/dist/pipeline/execution-routing-config.js +15 -0
  26. package/dist/pipeline/flow-catalog.js +50 -12
  27. package/dist/pipeline/flow-run-resume.js +29 -0
  28. package/dist/pipeline/flow-specs/auto-common.json +90 -360
  29. package/dist/pipeline/flow-specs/auto-golang.json +81 -360
  30. package/dist/pipeline/flow-specs/auto-simple.json +141 -0
  31. package/dist/pipeline/flow-specs/bugz/bug-analyze.json +2 -0
  32. package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
  33. package/dist/pipeline/flow-specs/design-review/design-review-loop.json +316 -0
  34. package/dist/pipeline/flow-specs/design-review.json +10 -0
  35. package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +11 -0
  36. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +2 -0
  37. package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
  38. package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +2 -0
  39. package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +2 -0
  40. package/dist/pipeline/flow-specs/implement.json +13 -6
  41. package/dist/pipeline/flow-specs/instant-task.json +177 -0
  42. package/dist/pipeline/flow-specs/normalize-task-source.json +311 -0
  43. package/dist/pipeline/flow-specs/plan-revise.json +7 -1
  44. package/dist/pipeline/flow-specs/plan.json +51 -71
  45. package/dist/pipeline/flow-specs/review/review-fix.json +24 -4
  46. package/dist/pipeline/flow-specs/review/review-loop.json +351 -45
  47. package/dist/pipeline/flow-specs/review/review-project-loop.json +590 -0
  48. package/dist/pipeline/flow-specs/review/review-project.json +12 -0
  49. package/dist/pipeline/flow-specs/review/review.json +37 -31
  50. package/dist/pipeline/flow-specs/task-describe.json +2 -0
  51. package/dist/pipeline/flow-specs/task-source/jira-fetch.json +70 -0
  52. package/dist/pipeline/flow-specs/task-source/manual-input.json +216 -0
  53. package/dist/pipeline/launch-profile-config.js +30 -18
  54. package/dist/pipeline/node-contract.js +1 -0
  55. package/dist/pipeline/node-registry.js +115 -6
  56. package/dist/pipeline/node-runner.js +3 -2
  57. package/dist/pipeline/nodes/build-review-fix-prompt-node.js +5 -1
  58. package/dist/pipeline/nodes/clear-ready-to-merge-node.js +11 -0
  59. package/dist/pipeline/nodes/commit-message-form-node.js +8 -0
  60. package/dist/pipeline/nodes/design-review-verdict-node.js +36 -0
  61. package/dist/pipeline/nodes/ensure-summary-json-node.js +13 -2
  62. package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +19 -2
  63. package/dist/pipeline/nodes/fetch-gitlab-review-node.js +19 -2
  64. package/dist/pipeline/nodes/flow-run-node.js +242 -8
  65. package/dist/pipeline/nodes/git-commit-form-node.js +8 -0
  66. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +19 -2
  67. package/dist/pipeline/nodes/jira-fetch-node.js +50 -4
  68. package/dist/pipeline/nodes/llm-prompt-node.js +38 -36
  69. package/dist/pipeline/nodes/planning-bundle-node.js +10 -0
  70. package/dist/pipeline/nodes/review-verdict-node.js +86 -0
  71. package/dist/pipeline/nodes/select-files-form-node.js +8 -0
  72. package/dist/pipeline/nodes/structured-summary-node.js +24 -0
  73. package/dist/pipeline/nodes/user-input-node.js +38 -3
  74. package/dist/pipeline/nodes/write-selection-file-node.js +20 -4
  75. package/dist/pipeline/plugin-loader.js +389 -0
  76. package/dist/pipeline/plugin-types.js +1 -0
  77. package/dist/pipeline/prompt-registry.js +3 -1
  78. package/dist/pipeline/prompt-runtime.js +4 -1
  79. package/dist/pipeline/registry.js +71 -4
  80. package/dist/pipeline/review-iteration.js +26 -0
  81. package/dist/pipeline/spec-compiler.js +3 -0
  82. package/dist/pipeline/spec-loader.js +14 -0
  83. package/dist/pipeline/spec-types.js +3 -0
  84. package/dist/pipeline/spec-validator.js +20 -0
  85. package/dist/pipeline/value-resolver.js +76 -2
  86. package/dist/plugin-sdk.js +1 -0
  87. package/dist/prompts.js +36 -14
  88. package/dist/review-severity.js +45 -0
  89. package/dist/runtime/artifact-registry.js +405 -0
  90. package/dist/runtime/design-review-input-contract.js +17 -16
  91. package/dist/runtime/env-loader.js +3 -0
  92. package/dist/runtime/execution-routing-store.js +134 -0
  93. package/dist/runtime/execution-routing.js +233 -0
  94. package/dist/runtime/interactive-execution-routing.js +471 -0
  95. package/dist/runtime/plan-revise-input-contract.js +35 -32
  96. package/dist/runtime/planning-bundle.js +123 -0
  97. package/dist/runtime/ready-to-merge.js +22 -1
  98. package/dist/runtime/review-input-contract.js +100 -0
  99. package/dist/structured-artifact-schema-registry.js +9 -0
  100. package/dist/structured-artifact-schemas.json +140 -1
  101. package/dist/structured-artifacts.js +77 -6
  102. package/dist/user-input.js +70 -3
  103. package/docs/example/.flows/examples/claude-example.json +50 -0
  104. package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
  105. package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
  106. package/docs/examples/.flows/claude-example.json +50 -0
  107. package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
  108. package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
  109. package/docs/plugin-sdk.md +731 -0
  110. package/package.json +11 -4
@@ -2,50 +2,52 @@ import { TaskRunnerError } from "../../errors.js";
2
2
  import { printInfo, printPrompt } from "../../tui.js";
3
3
  import { isAllowedModelForExecutor, isLlmExecutorId, } from "../launch-profile-config.js";
4
4
  import { toExecutorContext } from "../types.js";
5
+ function outputsForArtifacts(requiredArtifacts) {
6
+ return Array.from(new Set(requiredArtifacts ?? [])).map((outputPath) => ({
7
+ kind: "artifact",
8
+ path: outputPath,
9
+ required: true,
10
+ manifest: {
11
+ publish: true,
12
+ },
13
+ }));
14
+ }
5
15
  export const llmPromptNode = {
6
16
  kind: "llm-prompt",
7
17
  version: 1,
8
18
  async run(context, params) {
9
- if (!isLlmExecutorId(params.executor)) {
10
- throw new TaskRunnerError(`Unsupported llm executor '${params.executor}'.`);
19
+ const routedProfile = params.routingGroup
20
+ ? context.executionRouting?.groups[params.routingGroup]
21
+ : undefined;
22
+ const fallbackProfile = context.executionRouting?.defaultRoute;
23
+ const executor = params.routingGroup
24
+ ? routedProfile?.executor ?? params.executor ?? fallbackProfile?.executor
25
+ : params.executor ?? fallbackProfile?.executor;
26
+ const model = params.routingGroup
27
+ ? routedProfile?.model ?? params.model ?? fallbackProfile?.model
28
+ : params.model ?? fallbackProfile?.model;
29
+ if (!executor || !isLlmExecutorId(executor, context.executors)) {
30
+ throw new TaskRunnerError(`Unsupported llm executor '${String(executor ?? params.executor ?? "undefined")}'.`);
11
31
  }
12
- if (params.model && !isAllowedModelForExecutor(params.executor, params.model)) {
13
- throw new TaskRunnerError(`Model '${params.model}' is not allowed for executor '${params.executor}'.`);
32
+ if (model && !isAllowedModelForExecutor(executor, model, context.executors)) {
33
+ throw new TaskRunnerError(`Model '${model}' is not allowed for executor '${executor}'.`);
14
34
  }
15
35
  printInfo(params.labelText);
16
- printPrompt(`LLM:${params.executor}`, params.prompt);
36
+ printPrompt(`LLM:${executor}`, params.prompt);
17
37
  const executorContext = toExecutorContext(context);
18
- if (params.executor === "codex") {
19
- const executor = context.executors.get("codex");
20
- const value = await executor.execute(executorContext, {
21
- prompt: params.prompt,
22
- ...(params.model ? { model: params.model } : {}),
23
- env: { ...context.env },
24
- }, executor.defaultConfig);
25
- return {
26
- value: {
27
- ...value,
28
- executor: "codex",
29
- },
30
- outputs: (params.requiredArtifacts ?? []).map((path) => ({ kind: "artifact", path, required: true })),
31
- };
32
- }
33
- if (params.executor === "opencode") {
34
- const executor = context.executors.get("opencode");
35
- const value = await executor.execute(executorContext, {
36
- prompt: params.prompt,
37
- ...(params.model ? { model: params.model } : {}),
38
- env: { ...context.env },
39
- }, executor.defaultConfig);
40
- return {
41
- value: {
42
- ...value,
43
- executor: "opencode",
44
- },
45
- outputs: (params.requiredArtifacts ?? []).map((path) => ({ kind: "artifact", path, required: true })),
46
- };
47
- }
48
- throw new TaskRunnerError(`Unsupported llm executor '${params.executor}'.`);
38
+ const resolvedExecutor = context.executors.get(executor);
39
+ const value = await resolvedExecutor.execute(executorContext, {
40
+ prompt: params.prompt,
41
+ ...(model ? { model } : {}),
42
+ env: { ...context.env },
43
+ }, resolvedExecutor.defaultConfig);
44
+ return {
45
+ value: {
46
+ ...value,
47
+ executor,
48
+ },
49
+ outputs: outputsForArtifacts(params.requiredArtifacts),
50
+ };
49
51
  },
50
52
  checks(_context, params) {
51
53
  if (!params.requiredArtifacts || params.requiredArtifacts.length === 0) {
@@ -0,0 +1,10 @@
1
+ import { resolveLatestPlanningBundle } from "../../runtime/planning-bundle.js";
2
+ export const planningBundleNode = {
3
+ kind: "planning-bundle",
4
+ version: 1,
5
+ async run(_context, params) {
6
+ return {
7
+ value: resolveLatestPlanningBundle(params.taskKey),
8
+ };
9
+ },
10
+ };
@@ -0,0 +1,86 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { reviewJsonFile, latestArtifactIteration, readyToMergeFile } from "../../artifacts.js";
3
+ import { TaskRunnerError } from "../../errors.js";
4
+ import { resolveBlockingReviewSeverities, normalizeReviewSeverity } from "../../review-severity.js";
5
+ import { validateStructuredArtifacts } from "../../structured-artifacts.js";
6
+ import { clearReadyToMergeFile, writeReadyToMergeFile } from "../../runtime/ready-to-merge.js";
7
+ function readReviewVerdictFile(path) {
8
+ if (!existsSync(path)) {
9
+ throw new TaskRunnerError(`Review findings file not found: ${path}`);
10
+ }
11
+ try {
12
+ return JSON.parse(readFileSync(path, "utf8"));
13
+ }
14
+ catch {
15
+ throw new TaskRunnerError(`Failed to parse review findings JSON: ${path}`);
16
+ }
17
+ }
18
+ export const reviewVerdictNode = {
19
+ kind: "review-verdict",
20
+ version: 1,
21
+ async run(context, params) {
22
+ const iteration = params.iteration ?? latestArtifactIteration(params.taskKey, "review", "json") ?? 1;
23
+ const jsonPath = reviewJsonFile(params.taskKey, iteration);
24
+ validateStructuredArtifacts([{ path: jsonPath, schemaId: "review-findings/v1" }], "Review findings are invalid or missing.");
25
+ const report = readReviewVerdictFile(jsonPath);
26
+ const findings = Array.isArray(report.findings) ? report.findings : [];
27
+ const blockingSeverities = resolveBlockingReviewSeverities(params.blockingSeverities);
28
+ const blockingSeveritySet = new Set(blockingSeverities);
29
+ const blockingFindingTitles = findings
30
+ .filter((finding) => {
31
+ const severity = normalizeReviewSeverity(finding.severity);
32
+ return severity !== null && blockingSeveritySet.has(severity);
33
+ })
34
+ .map((finding) => (typeof finding.title === "string" ? finding.title.trim() : ""))
35
+ .filter((title) => title.length > 0);
36
+ const readyToMerge = blockingFindingTitles.length === 0;
37
+ const summary = typeof report.summary === "string" && report.summary.trim().length > 0
38
+ ? report.summary.trim()
39
+ : readyToMerge
40
+ ? "No blocking findings."
41
+ : `Blocking findings: ${blockingFindingTitles.join(", ")}`;
42
+ const normalizedReport = {
43
+ ...report,
44
+ ready_to_merge: readyToMerge,
45
+ };
46
+ writeFileSync(jsonPath, `${JSON.stringify(normalizedReport, null, 2)}\n`, "utf8");
47
+ if (readyToMerge) {
48
+ writeReadyToMergeFile(params.taskKey, {
49
+ ...(context.mdLang !== undefined ? { mdLang: context.mdLang } : {}),
50
+ summary,
51
+ });
52
+ }
53
+ else {
54
+ clearReadyToMergeFile(params.taskKey);
55
+ }
56
+ return {
57
+ value: {
58
+ readyToMerge,
59
+ blockingSeverities,
60
+ blockingFindingTitles,
61
+ blockingFindingsCount: blockingFindingTitles.length,
62
+ summary,
63
+ reviewJsonFile: jsonPath,
64
+ },
65
+ outputs: [
66
+ {
67
+ kind: "artifact",
68
+ path: jsonPath,
69
+ required: true,
70
+ manifest: {
71
+ publish: true,
72
+ schemaId: "review-findings/v1",
73
+ },
74
+ },
75
+ {
76
+ kind: "file",
77
+ path: readyToMergeFile(params.taskKey),
78
+ required: false,
79
+ manifest: {
80
+ publish: true,
81
+ },
82
+ },
83
+ ],
84
+ };
85
+ },
86
+ };
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
2
  import path from "node:path";
3
+ import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
3
4
  import { TaskRunnerError } from "../../errors.js";
4
5
  import { validateUserInputValues } from "../../user-input.js";
5
6
  export const selectFilesFormNode = {
@@ -65,6 +66,13 @@ export const selectFilesFormNode = {
65
66
  kind: "artifact",
66
67
  path: params.outputFile,
67
68
  required: true,
69
+ manifest: {
70
+ publish: true,
71
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputFile),
72
+ payloadFamily: "structured-json",
73
+ schemaId: "user-input/v1",
74
+ schemaVersion: 1,
75
+ },
68
76
  },
69
77
  ],
70
78
  };
@@ -0,0 +1,24 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { TaskRunnerError } from "../../errors.js";
3
+ export const structuredSummaryNode = {
4
+ kind: "structured-summary",
5
+ version: 1,
6
+ async run(_context, params) {
7
+ let parsed;
8
+ try {
9
+ parsed = JSON.parse(readFileSync(params.path, "utf8"));
10
+ }
11
+ catch (error) {
12
+ throw new TaskRunnerError(`Structured summary node could not parse JSON from ${params.path}: ${error instanceof Error ? error.message : String(error)}`);
13
+ }
14
+ const summary = typeof parsed.summary === "string" ? parsed.summary.trim() : "";
15
+ if (!summary) {
16
+ throw new TaskRunnerError(`Structured summary node did not find a non-empty summary in ${params.path}.`);
17
+ }
18
+ return {
19
+ value: {
20
+ summary,
21
+ },
22
+ };
23
+ },
24
+ };
@@ -1,7 +1,8 @@
1
1
  import { writeFileSync } from "node:fs";
2
2
  import { TaskRunnerError } from "../../errors.js";
3
+ import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
3
4
  import { printSummary } from "../../tui.js";
4
- import { requestUserInputInTerminal, validateUserInputValues, } from "../../user-input.js";
5
+ import { applyInitialUserInputValues, requestUserInputInTerminal, validateUserInputValues, } from "../../user-input.js";
5
6
  function labelForSingleValue(field, value) {
6
7
  if (field.type !== "single-select" && field.type !== "multi-select") {
7
8
  return value;
@@ -64,6 +65,23 @@ function buildTaskDescribePromptSuffix(params, values) {
64
65
  : `Task source: user-input\n\n${taskDescription}`,
65
66
  };
66
67
  }
68
+ function buildInstantTaskPromptSuffix(params, values) {
69
+ const taskDescription = typeof values.task_description === "string" ? values.task_description.trim() : "";
70
+ const additionalInstructions = typeof values.additional_instructions === "string" ? values.additional_instructions.trim() : "";
71
+ return {
72
+ promptSuffix: [
73
+ "Use the manual instant-task request below as the source of truth for task intent.",
74
+ `User input file: ${params.outputFile}`,
75
+ `Task description:\n${taskDescription}`,
76
+ additionalInstructions ? `Additional instructions:\n${additionalInstructions}` : "",
77
+ ]
78
+ .filter((item) => item.trim().length > 0)
79
+ .join("\n\n"),
80
+ summaryText: additionalInstructions
81
+ ? `Task source: instant-task\n\n${taskDescription}\n\nAdditional instructions:\n${additionalInstructions}`
82
+ : `Task source: instant-task\n\n${taskDescription}`,
83
+ };
84
+ }
67
85
  function buildPromptSuffix(params, values) {
68
86
  if (params.formId === "review-fix-selection") {
69
87
  return buildReviewFixPromptSuffix(params, values);
@@ -71,6 +89,9 @@ function buildPromptSuffix(params, values) {
71
89
  if (params.formId === "task-describe-source-input") {
72
90
  return buildTaskDescribePromptSuffix(params, values);
73
91
  }
92
+ if (params.formId === "instant-task-input") {
93
+ return buildInstantTaskPromptSuffix(params, values);
94
+ }
74
95
  if (params.fields.length === 0) {
75
96
  return {
76
97
  promptSuffix: "",
@@ -101,12 +122,13 @@ export const userInputNode = {
101
122
  kind: "user-input",
102
123
  version: 1,
103
124
  async run(context, params) {
125
+ const fields = applyInitialUserInputValues(params.fields, params.initialValues);
104
126
  const form = {
105
127
  formId: params.formId,
106
128
  title: params.title,
107
129
  ...(params.description ? { description: params.description } : {}),
108
130
  ...(params.submitLabel ? { submitLabel: params.submitLabel } : {}),
109
- fields: params.fields,
131
+ fields,
110
132
  };
111
133
  const requester = context.requestUserInput ?? requestUserInputInTerminal;
112
134
  const result = await requester(form);
@@ -130,7 +152,20 @@ export const userInputNode = {
130
152
  promptSuffix: rendered.promptSuffix,
131
153
  summaryText: rendered.summaryText,
132
154
  },
133
- outputs: [{ kind: "artifact", path: params.outputFile, required: true }],
155
+ outputs: [
156
+ {
157
+ kind: "artifact",
158
+ path: params.outputFile,
159
+ required: true,
160
+ manifest: {
161
+ publish: true,
162
+ logicalKey: buildLogicalKeyForPayload(context.issueKey, params.outputFile),
163
+ payloadFamily: "structured-json",
164
+ schemaId: "user-input/v1",
165
+ schemaVersion: 1,
166
+ },
167
+ },
168
+ ],
134
169
  };
135
170
  },
136
171
  };
@@ -1,7 +1,8 @@
1
1
  import { writeFileSync } from "node:fs";
2
2
  import { readFileSync } from "node:fs";
3
+ import { buildLogicalKeyForPayload } from "../../artifact-manifest.js";
3
4
  import { TaskRunnerError } from "../../errors.js";
4
- const SEVERITY_AUTO_SELECT = ["blocker", "critical"];
5
+ import { normalizeReviewSeverity, resolveBlockingReviewSeverities } from "../../review-severity.js";
5
6
  export const writeSelectionFileNode = {
6
7
  kind: "write-selection-file",
7
8
  version: 1,
@@ -15,15 +16,16 @@ export const writeSelectionFileNode = {
15
16
  }
16
17
  const reviewFindings = parsed;
17
18
  const findings = Array.isArray(reviewFindings.findings) ? reviewFindings.findings : [];
19
+ const blockingSeverities = resolveBlockingReviewSeverities(params.blockingSeverities);
18
20
  const selectedFindings = findings
19
21
  .filter((finding) => {
20
- const severity = typeof finding.severity === "string" ? finding.severity.trim().toLowerCase() : "";
22
+ const severity = normalizeReviewSeverity(finding.severity);
21
23
  const disposition = typeof finding.disposition === "string" ? finding.disposition.trim().toLowerCase() : null;
22
- return SEVERITY_AUTO_SELECT.includes(severity) && disposition !== "resolved" && disposition != null;
24
+ return severity !== null && blockingSeverities.includes(severity) && disposition !== "resolved";
23
25
  })
24
26
  .map((finding) => finding.title)
25
27
  .filter((title) => typeof title === "string" && title.trim().length > 0);
26
- const applyAll = selectedFindings.length === 0;
28
+ const applyAll = false;
27
29
  const artifact = {
28
30
  form_id: "review-fix-selection",
29
31
  submitted_at: new Date().toISOString(),
@@ -41,6 +43,20 @@ export const writeSelectionFileNode = {
41
43
  selectedFindings,
42
44
  applyAll,
43
45
  },
46
+ outputs: [
47
+ {
48
+ kind: "artifact",
49
+ path: params.outputFile,
50
+ required: true,
51
+ manifest: {
52
+ publish: true,
53
+ logicalKey: buildLogicalKeyForPayload(_context.issueKey, params.outputFile),
54
+ payloadFamily: "structured-json",
55
+ schemaId: "user-input/v1",
56
+ schemaVersion: 1,
57
+ },
58
+ },
59
+ ],
44
60
  };
45
61
  },
46
62
  };