supipowers 1.5.3 → 2.0.1
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 +14 -8
- package/bin/install.mjs +20 -5
- package/bin/install.ts +95 -0
- package/package.json +8 -4
- package/skills/context-mode/SKILL.md +17 -10
- package/skills/harness/SKILL.md +94 -0
- package/skills/ui-design/SKILL.md +63 -0
- package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
- package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
- package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
- package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
- package/skills/ultraplan-discover/SKILL.md +96 -0
- package/skills/ultraplan-intake/SKILL.md +89 -0
- package/skills/ultraplan-research/SKILL.md +129 -0
- package/skills/ultraplan-review/SKILL.md +86 -0
- package/skills/ultraplan-review-scope/SKILL.md +111 -0
- package/skills/ultraplan-review-structure/SKILL.md +120 -0
- package/skills/ultraplan-review-tdd/SKILL.md +142 -0
- package/skills/ultraplan-scout/SKILL.md +110 -0
- package/skills/ultraplan-synthesize/SKILL.md +124 -0
- package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
- package/src/ai/schema-text.ts +129 -0
- package/src/ai/structured-output.ts +274 -0
- package/src/ai/template.ts +27 -0
- package/src/bootstrap.ts +63 -28
- package/src/commands/agents.ts +131 -42
- package/src/commands/ai-review.ts +251 -30
- package/src/commands/clear.ts +434 -0
- package/src/commands/commit.ts +1 -0
- package/src/commands/config.ts +242 -44
- package/src/commands/context.ts +55 -28
- package/src/commands/doctor.ts +234 -6
- package/src/commands/fix-pr.ts +306 -131
- package/src/commands/generate.ts +111 -21
- package/src/commands/memory.ts +192 -0
- package/src/commands/model-picker.ts +28 -21
- package/src/commands/model.ts +18 -8
- package/src/commands/optimize-context.ts +408 -29
- package/src/commands/plan.ts +2 -0
- package/src/commands/qa.ts +312 -137
- package/src/commands/release.ts +259 -76
- package/src/commands/review.ts +293 -59
- package/src/commands/status.ts +200 -13
- package/src/commands/supi.ts +3 -35
- package/src/commands/ui-design.ts +394 -0
- package/src/commands/ultraplan.ts +1518 -0
- package/src/commands/update.ts +86 -0
- package/src/config/defaults.ts +62 -0
- package/src/config/loader.ts +448 -60
- package/src/config/schema.ts +108 -2
- package/src/context/optimizer.ts +25 -33
- package/src/context/rule-renderer.ts +223 -0
- package/src/context/savings.ts +258 -0
- package/src/context/startup-check.ts +380 -0
- package/src/context/startup-optimizer.ts +355 -0
- package/src/context/tokenignore.ts +146 -0
- package/src/context-mode/cache-handle.ts +49 -0
- package/src/context-mode/cache-preview.ts +71 -0
- package/src/context-mode/cache-store.ts +738 -0
- package/src/context-mode/compressor.ts +131 -26
- package/src/context-mode/dedup.ts +108 -0
- package/src/context-mode/detector.ts +35 -4
- package/src/context-mode/event-extractor.ts +14 -12
- package/src/context-mode/event-store.ts +91 -36
- package/src/context-mode/hooks.ts +798 -56
- package/src/context-mode/knowledge/store.ts +255 -11
- package/src/context-mode/memory-store.ts +325 -0
- package/src/context-mode/metrics-recorder.ts +158 -0
- package/src/context-mode/metrics-store.ts +765 -0
- package/src/context-mode/model.ts +24 -0
- package/src/context-mode/processor-keys.ts +29 -0
- package/src/context-mode/processors/build.ts +66 -0
- package/src/context-mode/processors/docker.ts +57 -0
- package/src/context-mode/processors/git.ts +111 -0
- package/src/context-mode/processors/json.ts +112 -0
- package/src/context-mode/processors/k8s.ts +67 -0
- package/src/context-mode/processors/lint.ts +67 -0
- package/src/context-mode/processors/log.ts +86 -0
- package/src/context-mode/processors/registry.ts +116 -0
- package/src/context-mode/processors/test-runner.ts +102 -0
- package/src/context-mode/processors/types.ts +20 -0
- package/src/context-mode/repomap.ts +400 -0
- package/src/context-mode/routing.ts +97 -24
- package/src/context-mode/sandbox/runners.ts +5 -1
- package/src/context-mode/snapshot-builder.ts +106 -11
- package/src/context-mode/source-hash.ts +173 -0
- package/src/context-mode/tool-name.ts +11 -0
- package/src/context-mode/tools.ts +654 -22
- package/src/context-mode/web/fetcher.ts +31 -12
- package/src/debug/logger.ts +2 -1
- package/src/deps/registry.ts +1 -1
- package/src/discipline/failure-summarizer.ts +170 -0
- package/src/discipline/failure-taxonomy.ts +131 -0
- package/src/discipline/workflow-invariants.ts +125 -0
- package/src/discovery/index.ts +31 -0
- package/src/discovery/lsp.ts +87 -0
- package/src/discovery/rank.ts +144 -0
- package/src/discovery/sources.ts +89 -0
- package/src/discovery/workflow.ts +87 -0
- package/src/docs/contracts.ts +39 -0
- package/src/docs/drift.ts +117 -87
- package/src/fix-pr/assessment.ts +200 -0
- package/src/fix-pr/contracts.ts +47 -0
- package/src/fix-pr/fetch-comments.ts +80 -0
- package/src/fix-pr/prompt-builder.ts +58 -40
- package/src/fix-pr/scripts/exec.ts +34 -0
- package/src/fix-pr/scripts/trigger-review.ts +106 -0
- package/src/fix-pr/scripts/wait-and-check.ts +108 -0
- package/src/fix-pr/types.ts +4 -0
- package/src/git/branch-finish.ts +5 -0
- package/src/git/commit-contract.ts +83 -0
- package/src/git/commit.ts +121 -184
- package/src/git/status.ts +62 -8
- package/src/harness/anti_slop/architecture-parser.ts +210 -0
- package/src/harness/anti_slop/backend-factory.ts +30 -0
- package/src/harness/anti_slop/backend.ts +140 -0
- package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
- package/src/harness/anti_slop/fallow-adapter.ts +305 -0
- package/src/harness/anti_slop/installer.ts +227 -0
- package/src/harness/anti_slop/queue.ts +216 -0
- package/src/harness/anti_slop/recommend.ts +84 -0
- package/src/harness/anti_slop/score.ts +180 -0
- package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
- package/src/harness/artifacts/agents-md.ts +88 -0
- package/src/harness/artifacts/checks-wiring.ts +57 -0
- package/src/harness/artifacts/docs-tree.ts +79 -0
- package/src/harness/artifacts/lint-configs.ts +136 -0
- package/src/harness/artifacts/review-agents.ts +67 -0
- package/src/harness/bare-entry.ts +108 -0
- package/src/harness/command.ts +1010 -0
- package/src/harness/default-agents/design.md +23 -0
- package/src/harness/default-agents/discover.md +18 -0
- package/src/harness/default-agents/implement.md +24 -0
- package/src/harness/default-agents/plan.md +19 -0
- package/src/harness/default-agents/research.md +21 -0
- package/src/harness/default-agents/validate.md +22 -0
- package/src/harness/gc/reporter.ts +28 -0
- package/src/harness/gc/runner.ts +136 -0
- package/src/harness/hooks/layer-context-inject.ts +155 -0
- package/src/harness/hooks/post-session-sweep.ts +130 -0
- package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
- package/src/harness/hooks/register.ts +118 -0
- package/src/harness/model.ts +117 -0
- package/src/harness/pipeline.ts +348 -0
- package/src/harness/project-paths.ts +235 -0
- package/src/harness/stage-runner.ts +107 -0
- package/src/harness/stages/design.ts +386 -0
- package/src/harness/stages/discover.ts +454 -0
- package/src/harness/stages/implement.ts +162 -0
- package/src/harness/stages/plan.ts +335 -0
- package/src/harness/stages/research.ts +263 -0
- package/src/harness/stages/validate.ts +684 -0
- package/src/harness/storage.ts +467 -0
- package/src/harness/tools.ts +426 -0
- package/src/lsp/bridge.ts +56 -95
- package/src/lsp/capabilities.ts +108 -0
- package/src/lsp/contracts.ts +35 -0
- package/src/lsp/detector.ts +8 -12
- package/src/markdown-frontmatter.ts +68 -0
- package/src/mempalace/bridge.ts +135 -0
- package/src/mempalace/config.ts +75 -0
- package/src/mempalace/format.ts +163 -0
- package/src/mempalace/hooks.ts +370 -0
- package/src/mempalace/installer-helper.ts +194 -0
- package/src/mempalace/python/mempalace_bridge.py +440 -0
- package/src/mempalace/runtime.ts +565 -0
- package/src/mempalace/schema.ts +268 -0
- package/src/mempalace/session-summary.ts +198 -0
- package/src/mempalace/tool.ts +186 -0
- package/src/mempalace/uv.ts +256 -0
- package/src/migrate/runner.ts +354 -0
- package/src/planning/approval-flow.ts +206 -9
- package/src/planning/plan-writer-prompt.ts +4 -3
- package/src/planning/planning-ask-tool.ts +39 -0
- package/src/planning/render-markdown.ts +74 -0
- package/src/planning/spec.ts +42 -0
- package/src/planning/system-prompt.ts +11 -8
- package/src/planning/validate.ts +84 -0
- package/src/platform/omp.ts +15 -2
- package/src/platform/system-prompt.ts +37 -0
- package/src/platform/test-utils.ts +3 -0
- package/src/platform/types.ts +6 -1
- package/src/qa/config.ts +12 -6
- package/src/qa/detect-app-type.ts +13 -6
- package/src/qa/matrix.ts +12 -6
- package/src/qa/prompt-builder.ts +28 -30
- package/src/qa/scripts/dev-server-utils.ts +72 -0
- package/src/qa/scripts/run-e2e-tests.ts +226 -0
- package/src/qa/scripts/start-dev-server.ts +138 -0
- package/src/qa/scripts/stop-dev-server.ts +77 -0
- package/src/qa/session.ts +13 -7
- package/src/quality/ai-setup.ts +27 -25
- package/src/quality/contracts.ts +34 -0
- package/src/quality/gates/ai-review.ts +20 -58
- package/src/quality/gates/command.ts +249 -46
- package/src/quality/review-gates.ts +18 -2
- package/src/quality/runner.ts +63 -22
- package/src/quality/schemas.ts +37 -2
- package/src/quality/setup.ts +96 -16
- package/src/release/changelog.ts +1 -1
- package/src/release/channels/custom.ts +13 -3
- package/src/release/channels/types.ts +5 -0
- package/src/release/contracts.ts +90 -0
- package/src/release/executor.ts +122 -45
- package/src/release/prompt.ts +18 -2
- package/src/release/targets.ts +86 -0
- package/src/release/version.ts +96 -71
- package/src/review/agent-loader.ts +221 -109
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +114 -13
- package/src/review/output.ts +12 -139
- package/src/review/runner.ts +12 -6
- package/src/review/scope.ts +144 -24
- package/src/review/types.ts +1 -20
- package/src/review/validator.ts +12 -6
- package/src/storage/fix-pr-sessions.ts +21 -14
- package/src/storage/plans.ts +14 -5
- package/src/storage/qa-sessions.ts +25 -19
- package/src/storage/reliability-metrics.ts +180 -0
- package/src/storage/reports.ts +8 -7
- package/src/storage/review-sessions.ts +55 -20
- package/src/tool-catalog/active-tool-controller.ts +164 -0
- package/src/tool-catalog/active-tool-planner.ts +212 -0
- package/src/tool-catalog/tool-groups.ts +102 -0
- package/src/types.ts +1399 -5
- package/src/ui-design/backend-adapter.ts +78 -0
- package/src/ui-design/backends/local-html.ts +82 -0
- package/src/ui-design/backends/pencil-mcp.ts +111 -0
- package/src/ui-design/components-scanner.ts +124 -0
- package/src/ui-design/config.ts +55 -0
- package/src/ui-design/pen-scanner.ts +95 -0
- package/src/ui-design/pen-selector.ts +72 -0
- package/src/ui-design/prompt-builder.ts +73 -0
- package/src/ui-design/scanner.ts +136 -0
- package/src/ui-design/session.ts +974 -0
- package/src/ui-design/system-prompt.ts +312 -0
- package/src/ui-design/tokens-scanner.ts +181 -0
- package/src/ui-design/types.ts +96 -0
- package/src/ultraplan/agent-catalog.ts +522 -0
- package/src/ultraplan/authoring/agent-catalog.ts +310 -0
- package/src/ultraplan/authoring/authoring-tools.ts +552 -0
- package/src/ultraplan/authoring/command-handlers.ts +339 -0
- package/src/ultraplan/authoring/markdown.ts +510 -0
- package/src/ultraplan/authoring/model.ts +162 -0
- package/src/ultraplan/authoring/pipeline.ts +319 -0
- package/src/ultraplan/authoring/stage-runner.ts +141 -0
- package/src/ultraplan/authoring/stages/approve.ts +249 -0
- package/src/ultraplan/authoring/stages/discover.ts +289 -0
- package/src/ultraplan/authoring/stages/intake.ts +203 -0
- package/src/ultraplan/authoring/stages/research.ts +399 -0
- package/src/ultraplan/authoring/stages/review.ts +333 -0
- package/src/ultraplan/authoring/stages/scout.ts +188 -0
- package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
- package/src/ultraplan/authoring/storage.ts +594 -0
- package/src/ultraplan/authoring/synth-gate.ts +165 -0
- package/src/ultraplan/authoring-draft.ts +653 -0
- package/src/ultraplan/authoring-persist.ts +180 -0
- package/src/ultraplan/authoring-tool.ts +608 -0
- package/src/ultraplan/authoring-wizard.ts +587 -0
- package/src/ultraplan/batch/merge.ts +98 -0
- package/src/ultraplan/batch/planner.ts +150 -0
- package/src/ultraplan/batch/presenter.ts +97 -0
- package/src/ultraplan/batch/storage.ts +420 -0
- package/src/ultraplan/batch/supervisor.ts +317 -0
- package/src/ultraplan/batch/worker.ts +26 -0
- package/src/ultraplan/batch/worktree.ts +110 -0
- package/src/ultraplan/contracts.ts +1593 -0
- package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
- package/src/ultraplan/default-agents/authoring/intake.md +12 -0
- package/src/ultraplan/default-agents/authoring/planner.md +12 -0
- package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
- package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/scout.md +12 -0
- package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
- package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-executor.md +10 -0
- package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-tester.md +10 -0
- package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-executor.md +10 -0
- package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-tester.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
- package/src/ultraplan/execution/contract.ts +71 -0
- package/src/ultraplan/execution/policy.ts +217 -0
- package/src/ultraplan/execution/runtime-tools.ts +107 -0
- package/src/ultraplan/execution/session-runner.ts +281 -0
- package/src/ultraplan/next-router.ts +85 -0
- package/src/ultraplan/presenter.ts +359 -0
- package/src/ultraplan/project-paths.ts +342 -0
- package/src/ultraplan/runtime/active-execution.ts +72 -0
- package/src/ultraplan/runtime/apply-mutation.ts +416 -0
- package/src/ultraplan/runtime/blockers.ts +243 -0
- package/src/ultraplan/runtime/hook-bridge.ts +486 -0
- package/src/ultraplan/runtime/launch-context.ts +207 -0
- package/src/ultraplan/runtime/migration.ts +524 -0
- package/src/ultraplan/runtime/normalize.ts +281 -0
- package/src/ultraplan/runtime/proof.ts +260 -0
- package/src/ultraplan/runtime/reducer.ts +416 -0
- package/src/ultraplan/runtime/repair.ts +251 -0
- package/src/ultraplan/runtime/tracker-storage.ts +368 -0
- package/src/ultraplan/session-selection.ts +291 -0
- package/src/ultraplan/storage.ts +374 -0
- package/src/utils/editor.ts +38 -0
- package/src/utils/executable.ts +80 -0
- package/src/utils/paths.ts +1 -20
- package/src/utils/shell.ts +31 -0
- package/src/visual/companion.ts +2 -1
- package/src/visual/scripts/frame-template.html +60 -0
- package/src/visual/scripts/index.js +59 -13
- package/src/visual/scripts/package.json +3 -0
- package/src/visual/start-server.ts +2 -1
- package/src/workspace/git-scope.ts +64 -0
- package/src/workspace/locks.ts +23 -0
- package/src/workspace/package-manager.ts +117 -0
- package/src/workspace/path-mapping.ts +75 -0
- package/src/workspace/project-slug.ts +92 -0
- package/src/workspace/repo-root.ts +137 -0
- package/src/workspace/selector.ts +115 -0
- package/src/workspace/state-paths.ts +118 -0
- package/src/workspace/targets.ts +313 -0
- package/src/fix-pr/scripts/diff-comments.sh +0 -33
- package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
- package/src/fix-pr/scripts/trigger-review.sh +0 -36
- package/src/fix-pr/scripts/wait-and-check.sh +0 -37
- package/src/qa/scripts/detect-app-type.sh +0 -68
- package/src/qa/scripts/discover-routes.sh +0 -143
- package/src/qa/scripts/run-e2e-tests.sh +0 -131
- package/src/qa/scripts/start-dev-server.sh +0 -46
- package/src/qa/scripts/stop-dev-server.sh +0 -36
- package/src/review/prompts/fix-output-schema.md +0 -18
- package/src/review/prompts/review-output-schema.md +0 -38
- package/src/review/template.ts +0 -15
- /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
|
@@ -11,12 +11,18 @@ import type {
|
|
|
11
11
|
ReviewAgentDefinition,
|
|
12
12
|
ReviewAgentsConfig,
|
|
13
13
|
} from "../types.js";
|
|
14
|
-
import {
|
|
14
|
+
import { parseMarkdownFrontmatter } from "../markdown-frontmatter.js";
|
|
15
|
+
import { resolvePackageManager } from "../workspace/package-manager.js";
|
|
16
|
+
import { resolveRepoRootFromFs } from "../workspace/repo-root.js";
|
|
17
|
+
import {
|
|
18
|
+
getRootStateDir,
|
|
19
|
+
getWorkspaceStateDir,
|
|
20
|
+
} from "../workspace/state-paths.js";
|
|
21
|
+
import { discoverWorkspaceTargets } from "../workspace/targets.js";
|
|
22
|
+
import { collectValidationErrors, formatValidationErrors } from "../ai/structured-output.js";
|
|
15
23
|
import {
|
|
16
24
|
ReviewAgentFrontmatterSchema,
|
|
17
25
|
ReviewAgentsConfigSchema,
|
|
18
|
-
collectReviewValidationErrors,
|
|
19
|
-
formatReviewValidationErrors,
|
|
20
26
|
} from "./types.js";
|
|
21
27
|
|
|
22
28
|
const REVIEW_AGENTS_DIR = "review-agents";
|
|
@@ -41,6 +47,16 @@ export interface LoadedReviewAgents {
|
|
|
41
47
|
agents: ConfiguredReviewAgent[];
|
|
42
48
|
}
|
|
43
49
|
|
|
50
|
+
export interface ReviewAgentLoadOptions {
|
|
51
|
+
repoRoot?: string;
|
|
52
|
+
workspaceRelativeDir?: string | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ResolvedReviewAgentContext {
|
|
56
|
+
repoRoot: string;
|
|
57
|
+
workspaceRelativeDir: string | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
const CONFIG_HEADER = [
|
|
45
61
|
"# Review Agents Configuration",
|
|
46
62
|
"#",
|
|
@@ -50,6 +66,7 @@ const CONFIG_HEADER = [
|
|
|
50
66
|
"# data: string - markdown file name in the agents directory",
|
|
51
67
|
"# model: string - model id (e.g. \"anthropic/claude-sonnet-4-20250514\") or null to inherit",
|
|
52
68
|
"# thinkingLevel: string - off | minimal | low | medium | high | xhigh | null to inherit",
|
|
69
|
+
"# peerCoordination: boolean - opt this agent into IRC peer coordination (multi-agent reviews only); omit or set false to disable",
|
|
53
70
|
"#",
|
|
54
71
|
].join("\n");
|
|
55
72
|
|
|
@@ -65,6 +82,7 @@ function serializeConfigYaml(agents: ReviewAgentConfig[]): string {
|
|
|
65
82
|
` data: ${a.data}`,
|
|
66
83
|
` model: ${a.model ?? "null"}`,
|
|
67
84
|
` thinkingLevel: ${a.thinkingLevel ?? "null"}`,
|
|
85
|
+
...(a.peerCoordination === true ? [` peerCoordination: true`] : []),
|
|
68
86
|
]),
|
|
69
87
|
"",
|
|
70
88
|
].join("\n");
|
|
@@ -113,6 +131,13 @@ function migrateConfigIfNeeded(configPath: string): void {
|
|
|
113
131
|
} else if (current && trimmed.startsWith("thinkingLevel:")) {
|
|
114
132
|
const val = trimmed.slice("thinkingLevel:".length).trim();
|
|
115
133
|
current.thinkingLevel = val === "null" ? null : (val as any);
|
|
134
|
+
} else if (current && trimmed.startsWith("peerCoordination:")) {
|
|
135
|
+
const val = trimmed.slice("peerCoordination:".length).trim();
|
|
136
|
+
if (val === "true") {
|
|
137
|
+
current.peerCoordination = true;
|
|
138
|
+
} else if (val === "false") {
|
|
139
|
+
current.peerCoordination = false;
|
|
140
|
+
}
|
|
116
141
|
}
|
|
117
142
|
}
|
|
118
143
|
if (current?.name) agents.push(current as ReviewAgentConfig);
|
|
@@ -128,7 +153,7 @@ function migrateConfigIfNeeded(configPath: string): void {
|
|
|
128
153
|
}
|
|
129
154
|
|
|
130
155
|
function validateReviewAgentsConfig(data: unknown): ReviewAgentsConfig {
|
|
131
|
-
const errors =
|
|
156
|
+
const errors = formatValidationErrors(collectValidationErrors(ReviewAgentsConfigSchema, data));
|
|
132
157
|
if (errors.length > 0) {
|
|
133
158
|
throw new Error(`Invalid review-agents config: ${errors.join("; ")}`);
|
|
134
159
|
}
|
|
@@ -142,57 +167,27 @@ async function importYamlFile(filePath: string): Promise<unknown> {
|
|
|
142
167
|
return imported.default;
|
|
143
168
|
}
|
|
144
169
|
|
|
145
|
-
function
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const trimmed = line.trim();
|
|
150
|
-
if (!trimmed) {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const separator = trimmed.indexOf(":");
|
|
155
|
-
if (separator === -1) {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const key = trimmed.slice(0, separator).trim();
|
|
160
|
-
const value = trimmed.slice(separator + 1).trim();
|
|
161
|
-
metadata[key] = value;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const errors = formatReviewValidationErrors(
|
|
165
|
-
collectReviewValidationErrors(ReviewAgentFrontmatterSchema, metadata),
|
|
170
|
+
export function parseReviewAgentMarkdown(content: string, filePath: string): ReviewAgentDefinition {
|
|
171
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(content, filePath);
|
|
172
|
+
const errors = formatValidationErrors(
|
|
173
|
+
collectValidationErrors(ReviewAgentFrontmatterSchema, frontmatter),
|
|
166
174
|
);
|
|
167
175
|
if (errors.length > 0) {
|
|
168
176
|
throw new Error(`Invalid agent frontmatter in ${filePath}: ${errors.join("; ")}`);
|
|
169
177
|
}
|
|
170
178
|
|
|
171
|
-
|
|
172
|
-
name:
|
|
173
|
-
description:
|
|
174
|
-
focus
|
|
175
|
-
prompt: "",
|
|
176
|
-
filePath,
|
|
179
|
+
const parsed = frontmatter as {
|
|
180
|
+
name: string;
|
|
181
|
+
description: string;
|
|
182
|
+
focus?: string;
|
|
177
183
|
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export function parseReviewAgentMarkdown(content: string, filePath: string): ReviewAgentDefinition {
|
|
181
|
-
const normalized = normalizeLineEndings(content);
|
|
182
|
-
const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
183
|
-
if (!match) {
|
|
184
|
-
throw new Error(`Review agent file ${filePath} is missing YAML frontmatter.`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const definition = parseFrontmatter(match[1], filePath);
|
|
188
|
-
const prompt = match[2]?.trim();
|
|
189
|
-
if (!prompt) {
|
|
190
|
-
throw new Error(`Review agent file ${filePath} has an empty prompt body.`);
|
|
191
|
-
}
|
|
192
184
|
|
|
193
185
|
return {
|
|
194
|
-
|
|
195
|
-
|
|
186
|
+
name: parsed.name,
|
|
187
|
+
description: parsed.description,
|
|
188
|
+
focus: parsed.focus ?? null,
|
|
189
|
+
prompt: body,
|
|
190
|
+
filePath,
|
|
196
191
|
};
|
|
197
192
|
}
|
|
198
193
|
|
|
@@ -204,37 +199,71 @@ export function getReviewAgentsConfigPath(paths: PlatformPaths, cwd: string): st
|
|
|
204
199
|
return path.join(getReviewAgentsDir(paths, cwd), CONFIG_FILE);
|
|
205
200
|
}
|
|
206
201
|
|
|
207
|
-
export function
|
|
208
|
-
|
|
209
|
-
|
|
202
|
+
export function getRootReviewAgentsDir(paths: PlatformPaths, repoRoot: string): string {
|
|
203
|
+
return path.join(getRootStateDir(paths, repoRoot), REVIEW_AGENTS_DIR);
|
|
204
|
+
}
|
|
210
205
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
migrateConfigIfNeeded(getReviewAgentsConfigPath(paths, cwd));
|
|
206
|
+
export function getRootReviewAgentsConfigPath(paths: PlatformPaths, repoRoot: string): string {
|
|
207
|
+
return path.join(getRootReviewAgentsDir(paths, repoRoot), CONFIG_FILE);
|
|
214
208
|
}
|
|
215
209
|
|
|
216
|
-
export
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
210
|
+
export function getWorkspaceReviewAgentsDir(
|
|
211
|
+
paths: PlatformPaths,
|
|
212
|
+
repoRoot: string,
|
|
213
|
+
workspaceRelativeDir: string,
|
|
214
|
+
): string {
|
|
215
|
+
return path.join(getWorkspaceStateDir(paths, repoRoot, workspaceRelativeDir), REVIEW_AGENTS_DIR);
|
|
220
216
|
}
|
|
221
217
|
|
|
222
|
-
export
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
218
|
+
export function getWorkspaceReviewAgentsConfigPath(
|
|
219
|
+
paths: PlatformPaths,
|
|
220
|
+
repoRoot: string,
|
|
221
|
+
workspaceRelativeDir: string,
|
|
222
|
+
): string {
|
|
223
|
+
return path.join(getWorkspaceReviewAgentsDir(paths, repoRoot, workspaceRelativeDir), CONFIG_FILE);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
export function resolveReviewAgentContext(
|
|
228
|
+
cwd: string,
|
|
229
|
+
options?: ReviewAgentLoadOptions,
|
|
230
|
+
): ResolvedReviewAgentContext {
|
|
231
|
+
const repoRoot = options?.repoRoot ?? resolveRepoRootFromFs(cwd);
|
|
232
|
+
const explicitWorkspaceRelativeDir = options?.workspaceRelativeDir;
|
|
233
|
+
if (explicitWorkspaceRelativeDir !== undefined) {
|
|
234
|
+
return {
|
|
235
|
+
repoRoot,
|
|
236
|
+
workspaceRelativeDir: explicitWorkspaceRelativeDir && explicitWorkspaceRelativeDir !== "."
|
|
237
|
+
? explicitWorkspaceRelativeDir
|
|
238
|
+
: null,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const packageManager = resolvePackageManager(repoRoot);
|
|
243
|
+
const workspaceTarget = discoverWorkspaceTargets(repoRoot, packageManager.id)
|
|
244
|
+
.filter((target) => cwd === target.packageDir || cwd.startsWith(`${target.packageDir}${path.sep}`))
|
|
245
|
+
.sort((left, right) => right.packageDir.length - left.packageDir.length)[0];
|
|
227
246
|
|
|
228
|
-
|
|
247
|
+
return {
|
|
248
|
+
repoRoot,
|
|
249
|
+
workspaceRelativeDir: workspaceTarget?.kind === "workspace" ? workspaceTarget.relativeDir : null,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function loadAgentsFromConfig(
|
|
254
|
+
config: ReviewAgentsConfig,
|
|
255
|
+
lookupDirs: string[],
|
|
256
|
+
missingScopeLabel: string,
|
|
257
|
+
scope?: ConfiguredReviewAgent["scope"],
|
|
258
|
+
): ConfiguredReviewAgent[] {
|
|
259
|
+
return config.agents
|
|
229
260
|
.filter((agent) => agent.enabled)
|
|
230
261
|
.map((agent) => {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (!
|
|
235
|
-
throw new Error(
|
|
236
|
-
`Configured review agent file does not exist in project or global scope: ${agent.data}`
|
|
237
|
-
);
|
|
262
|
+
const filePath = lookupDirs
|
|
263
|
+
.map((dir) => path.join(dir, agent.data))
|
|
264
|
+
.find((candidatePath) => fs.existsSync(candidatePath));
|
|
265
|
+
if (!filePath) {
|
|
266
|
+
throw new Error(`Configured review agent file does not exist in ${missingScopeLabel}: ${agent.data}`);
|
|
238
267
|
}
|
|
239
268
|
|
|
240
269
|
const definition = parseReviewAgentMarkdown(fs.readFileSync(filePath, "utf-8"), filePath);
|
|
@@ -250,8 +279,55 @@ export async function loadReviewAgents(paths: PlatformPaths, cwd: string): Promi
|
|
|
250
279
|
data: agent.data,
|
|
251
280
|
model: agent.model,
|
|
252
281
|
thinkingLevel: agent.thinkingLevel ?? null,
|
|
282
|
+
...(agent.peerCoordination === true ? { peerCoordination: true } : {}),
|
|
283
|
+
...(scope ? { scope } : {}),
|
|
253
284
|
} satisfies ConfiguredReviewAgent;
|
|
254
285
|
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function mergeAgentLayers(
|
|
289
|
+
lowerAgents: ConfiguredReviewAgent[],
|
|
290
|
+
higherAgents: ConfiguredReviewAgent[],
|
|
291
|
+
higherConfig: ReviewAgentsConfig,
|
|
292
|
+
): ConfiguredReviewAgent[] {
|
|
293
|
+
const higherConfigNames = new Set(higherConfig.agents.map((agent) => agent.name));
|
|
294
|
+
return [
|
|
295
|
+
...lowerAgents.filter((agent) => !higherConfigNames.has(agent.name)),
|
|
296
|
+
...higherAgents,
|
|
297
|
+
];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function ensureDefaultReviewAgents(paths: PlatformPaths, cwd: string): void {
|
|
301
|
+
const agentsDir = getReviewAgentsDir(paths, cwd);
|
|
302
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
303
|
+
|
|
304
|
+
// Default agent markdown files are installed globally only.
|
|
305
|
+
writeIfMissing(getReviewAgentsConfigPath(paths, cwd), buildDefaultConfigText());
|
|
306
|
+
migrateConfigIfNeeded(getReviewAgentsConfigPath(paths, cwd));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function loadReviewAgentsConfig(
|
|
310
|
+
paths: PlatformPaths,
|
|
311
|
+
cwd: string,
|
|
312
|
+
options?: ReviewAgentLoadOptions,
|
|
313
|
+
): Promise<ReviewAgentsConfig> {
|
|
314
|
+
const context = resolveReviewAgentContext(cwd, options);
|
|
315
|
+
ensureGlobalDefaultReviewAgents(paths);
|
|
316
|
+
ensureDefaultReviewAgents(paths, context.repoRoot);
|
|
317
|
+
return validateReviewAgentsConfig(await importYamlFile(getRootReviewAgentsConfigPath(paths, context.repoRoot)));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function loadReviewAgents(
|
|
321
|
+
paths: PlatformPaths,
|
|
322
|
+
cwd: string,
|
|
323
|
+
options?: ReviewAgentLoadOptions,
|
|
324
|
+
): Promise<LoadedReviewAgents> {
|
|
325
|
+
const context = resolveReviewAgentContext(cwd, options);
|
|
326
|
+
const agentsDir = getRootReviewAgentsDir(paths, context.repoRoot);
|
|
327
|
+
const configPath = getRootReviewAgentsConfigPath(paths, context.repoRoot);
|
|
328
|
+
const globalAgentsDir = getGlobalReviewAgentsDir(paths);
|
|
329
|
+
const config = await loadReviewAgentsConfig(paths, cwd, context);
|
|
330
|
+
const agents = loadAgentsFromConfig(config, [agentsDir, globalAgentsDir], "root or global scope");
|
|
255
331
|
|
|
256
332
|
return {
|
|
257
333
|
agentsDir,
|
|
@@ -292,31 +368,64 @@ export async function loadGlobalReviewAgents(paths: PlatformPaths): Promise<Load
|
|
|
292
368
|
const agentsDir = getGlobalReviewAgentsDir(paths);
|
|
293
369
|
const configPath = getGlobalReviewAgentsConfigPath(paths);
|
|
294
370
|
const config = await loadGlobalReviewAgentsConfig(paths);
|
|
371
|
+
const agents = loadAgentsFromConfig(config, [agentsDir], "global scope", "global");
|
|
295
372
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
373
|
+
return {
|
|
374
|
+
agentsDir,
|
|
375
|
+
configPath,
|
|
376
|
+
config,
|
|
377
|
+
agents,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
303
380
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
381
|
+
async function loadWorkspaceReviewAgentsConfig(
|
|
382
|
+
paths: PlatformPaths,
|
|
383
|
+
cwd: string,
|
|
384
|
+
options: ReviewAgentLoadOptions,
|
|
385
|
+
): Promise<ReviewAgentsConfig> {
|
|
386
|
+
const context = resolveReviewAgentContext(cwd, options);
|
|
387
|
+
if (!context.workspaceRelativeDir) {
|
|
388
|
+
return { agents: [] };
|
|
389
|
+
}
|
|
310
390
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
391
|
+
const configPath = getWorkspaceReviewAgentsConfigPath(
|
|
392
|
+
paths,
|
|
393
|
+
context.repoRoot,
|
|
394
|
+
context.workspaceRelativeDir,
|
|
395
|
+
);
|
|
396
|
+
if (!fs.existsSync(configPath)) {
|
|
397
|
+
return { agents: [] };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
migrateConfigIfNeeded(configPath);
|
|
401
|
+
return validateReviewAgentsConfig(await importYamlFile(configPath));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function loadWorkspaceReviewAgents(
|
|
405
|
+
paths: PlatformPaths,
|
|
406
|
+
cwd: string,
|
|
407
|
+
options: ReviewAgentLoadOptions,
|
|
408
|
+
): Promise<LoadedReviewAgents | null> {
|
|
409
|
+
const context = resolveReviewAgentContext(cwd, options);
|
|
410
|
+
if (!context.workspaceRelativeDir) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const agentsDir = getWorkspaceReviewAgentsDir(paths, context.repoRoot, context.workspaceRelativeDir);
|
|
415
|
+
const configPath = getWorkspaceReviewAgentsConfigPath(
|
|
416
|
+
paths,
|
|
417
|
+
context.repoRoot,
|
|
418
|
+
context.workspaceRelativeDir,
|
|
419
|
+
);
|
|
420
|
+
const rootAgentsDir = getRootReviewAgentsDir(paths, context.repoRoot);
|
|
421
|
+
const globalAgentsDir = getGlobalReviewAgentsDir(paths);
|
|
422
|
+
const config = await loadWorkspaceReviewAgentsConfig(paths, cwd, context);
|
|
423
|
+
const agents = loadAgentsFromConfig(
|
|
424
|
+
config,
|
|
425
|
+
[agentsDir, rootAgentsDir, globalAgentsDir],
|
|
426
|
+
"workspace, root, or global scope",
|
|
427
|
+
"workspace",
|
|
428
|
+
);
|
|
320
429
|
|
|
321
430
|
return {
|
|
322
431
|
agentsDir,
|
|
@@ -326,31 +435,34 @@ export async function loadGlobalReviewAgents(paths: PlatformPaths): Promise<Load
|
|
|
326
435
|
};
|
|
327
436
|
}
|
|
328
437
|
|
|
329
|
-
// ── Merged Loading (Global +
|
|
438
|
+
// ── Merged Loading (Global + Root + Workspace) ───────────────
|
|
330
439
|
|
|
331
440
|
export async function loadMergedReviewAgents(
|
|
332
441
|
paths: PlatformPaths,
|
|
333
442
|
cwd: string,
|
|
443
|
+
options?: ReviewAgentLoadOptions,
|
|
334
444
|
): Promise<LoadedReviewAgents> {
|
|
445
|
+
const context = resolveReviewAgentContext(cwd, options);
|
|
335
446
|
const globalResult = await loadGlobalReviewAgents(paths);
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
447
|
+
const rootResult = await loadReviewAgents(paths, cwd, context);
|
|
448
|
+
const rootAgents = rootResult.agents.map((agent) => ({ ...agent, scope: "root" as const }));
|
|
449
|
+
const mergedRootAgents = mergeAgentLayers(globalResult.agents, rootAgents, rootResult.config);
|
|
450
|
+
|
|
451
|
+
const workspaceResult = await loadWorkspaceReviewAgents(paths, cwd, context);
|
|
452
|
+
if (!workspaceResult) {
|
|
453
|
+
return {
|
|
454
|
+
agentsDir: rootResult.agentsDir,
|
|
455
|
+
configPath: rootResult.configPath,
|
|
456
|
+
config: rootResult.config,
|
|
457
|
+
agents: mergedRootAgents,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
348
460
|
|
|
349
461
|
return {
|
|
350
|
-
agentsDir:
|
|
351
|
-
configPath:
|
|
352
|
-
config:
|
|
353
|
-
agents:
|
|
462
|
+
agentsDir: workspaceResult.agentsDir,
|
|
463
|
+
configPath: workspaceResult.configPath,
|
|
464
|
+
config: workspaceResult.config,
|
|
465
|
+
agents: mergeAgentLayers(mergedRootAgents, workspaceResult.agents, workspaceResult.config),
|
|
354
466
|
};
|
|
355
467
|
}
|
|
356
468
|
|
package/src/review/fixer.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fixFindingsPrompt from "./prompts/fix-findings.md" with { type: "text" };
|
|
2
|
-
import fixOutputSchemaPrompt from "./prompts/fix-output-schema.md" with { type: "text" };
|
|
3
2
|
import type {
|
|
4
3
|
GateExecutionContext,
|
|
5
4
|
ReviewFinding,
|
|
@@ -8,9 +7,12 @@ import type {
|
|
|
8
7
|
ReviewOutput,
|
|
9
8
|
ReviewScope,
|
|
10
9
|
} from "../types.js";
|
|
11
|
-
import { parseStructuredOutput, runWithOutputValidation } from "
|
|
10
|
+
import { parseStructuredOutput, runWithOutputValidation, type ReliabilityReporter } from "../ai/structured-output.js";
|
|
11
|
+
import { renderSchemaText } from "../ai/schema-text.js";
|
|
12
12
|
import { ReviewFixOutputSchema } from "./types.js";
|
|
13
|
-
import {
|
|
13
|
+
import { renderTemplate } from "../ai/template.js";
|
|
14
|
+
|
|
15
|
+
const REVIEW_FIX_OUTPUT_SCHEMA_TEXT = renderSchemaText(ReviewFixOutputSchema);
|
|
14
16
|
|
|
15
17
|
export interface ReviewFixInput {
|
|
16
18
|
cwd: string;
|
|
@@ -20,6 +22,7 @@ export interface ReviewFixInput {
|
|
|
20
22
|
model?: string;
|
|
21
23
|
thinkingLevel?: string | null;
|
|
22
24
|
timeoutMs?: number;
|
|
25
|
+
reliability?: ReliabilityReporter;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
export interface ReviewFixRunResult {
|
|
@@ -127,10 +130,10 @@ function normalizeFixOutput(
|
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
export function buildFixPrompt(scope: ReviewScope, findings: ReviewFinding[]): string {
|
|
130
|
-
return
|
|
133
|
+
return renderTemplate(fixFindingsPrompt, {
|
|
131
134
|
scope,
|
|
132
135
|
findingsJson: JSON.stringify(findings, null, 2),
|
|
133
|
-
fixOutputSchema:
|
|
136
|
+
fixOutputSchema: REVIEW_FIX_OUTPUT_SCHEMA_TEXT,
|
|
134
137
|
});
|
|
135
138
|
}
|
|
136
139
|
|
|
@@ -158,13 +161,14 @@ export async function runAutoFix(input: ReviewFixInput): Promise<ReviewFixRunRes
|
|
|
158
161
|
const result = await runWithOutputValidation(input.createAgentSession, {
|
|
159
162
|
cwd: input.cwd,
|
|
160
163
|
prompt: buildFixPrompt(input.scope, fixableFindings),
|
|
161
|
-
schema:
|
|
164
|
+
schema: REVIEW_FIX_OUTPUT_SCHEMA_TEXT,
|
|
162
165
|
parse(raw) {
|
|
163
166
|
return parseStructuredOutput<ReviewFixOutput>(raw, ReviewFixOutputSchema);
|
|
164
167
|
},
|
|
165
168
|
model: input.model,
|
|
166
169
|
thinkingLevel: input.thinkingLevel ?? null,
|
|
167
170
|
timeoutMs: input.timeoutMs ?? 180_000,
|
|
171
|
+
reliability: input.reliability,
|
|
168
172
|
});
|
|
169
173
|
|
|
170
174
|
if (result.status === "blocked") {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import agentReviewWrapperPrompt from "./prompts/agent-review-wrapper.md" with { type: "text" };
|
|
2
2
|
import outputInstructionsPrompt from "./prompts/output-instructions.md" with { type: "text" };
|
|
3
|
-
import reviewOutputSchema from "./prompts/review-output-schema.md" with { type: "text" };
|
|
4
3
|
import type { ConfiguredReviewAgent, GateExecutionContext, ReviewOutput, ReviewScope } from "../types.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { runWithOutputValidation, type ReliabilityReporter } from "../ai/structured-output.js";
|
|
5
|
+
import { renderSchemaText } from "../ai/schema-text.js";
|
|
6
|
+
import { explainReviewOutputFailure, parseReviewOutput } from "./output.js";
|
|
7
|
+
import { renderTemplate } from "../ai/template.js";
|
|
8
|
+
import { ReviewOutputSchema } from "./types.js";
|
|
9
|
+
|
|
10
|
+
const REVIEW_OUTPUT_SCHEMA_TEXT = renderSchemaText(ReviewOutputSchema);
|
|
7
11
|
|
|
8
12
|
export interface MultiAgentReviewInput {
|
|
9
13
|
cwd: string;
|
|
@@ -13,8 +17,11 @@ export interface MultiAgentReviewInput {
|
|
|
13
17
|
model?: string;
|
|
14
18
|
thinkingLevel?: string | null;
|
|
15
19
|
timeoutMs?: number;
|
|
20
|
+
/** Tool ids active in the host runtime. Used to gate `peerCoordination` on `irc`. */
|
|
21
|
+
activeTools?: string[];
|
|
16
22
|
onAgentStart?: (agent: ConfiguredReviewAgent) => void;
|
|
17
23
|
onAgentComplete?: (result: MultiAgentAgentResult) => void;
|
|
24
|
+
reliability?: ReliabilityReporter;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
export interface MultiAgentAgentResult {
|
|
@@ -30,8 +37,8 @@ export interface MultiAgentReviewResult {
|
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
function renderOutputInstructions(): string {
|
|
33
|
-
return
|
|
34
|
-
outputSchema:
|
|
40
|
+
return renderTemplate(outputInstructionsPrompt, {
|
|
41
|
+
outputSchema: REVIEW_OUTPUT_SCHEMA_TEXT,
|
|
35
42
|
});
|
|
36
43
|
}
|
|
37
44
|
|
|
@@ -41,18 +48,95 @@ export function buildConfiguredAgentPrompt(agent: ConfiguredReviewAgent, scope:
|
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
const outputInstructions = renderOutputInstructions();
|
|
44
|
-
const agentPrompt =
|
|
45
|
-
|
|
46
|
-
{ outputInstructions },
|
|
47
|
-
);
|
|
51
|
+
const agentPrompt = renderTemplate(agent.prompt.replaceAll("{output_instructions}", "{{outputInstructions}}"),
|
|
52
|
+
{ outputInstructions },);
|
|
48
53
|
|
|
49
|
-
return
|
|
54
|
+
return renderTemplate(agentReviewWrapperPrompt, {
|
|
50
55
|
agent,
|
|
51
56
|
agentPrompt,
|
|
52
57
|
scope,
|
|
53
58
|
});
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
interface PeerCoordinationIdentity {
|
|
62
|
+
agent: ConfiguredReviewAgent;
|
|
63
|
+
id: string;
|
|
64
|
+
displayName: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const PEER_COORDINATION_AGENT_ID_PREFIX = "supi-review";
|
|
68
|
+
|
|
69
|
+
function normalizePeerCoordinationIdSegment(name: string, fallbackIndex: number): string {
|
|
70
|
+
const normalized = name
|
|
71
|
+
.trim()
|
|
72
|
+
.toLowerCase()
|
|
73
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
74
|
+
.replace(/^-+|-+$/g, "");
|
|
75
|
+
return normalized || `agent-${fallbackIndex + 1}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizePeerCoordinationDisplayName(name: string, fallbackIndex: number): string {
|
|
79
|
+
const normalized = name.trim().replace(/\s+/g, " ");
|
|
80
|
+
return normalized || `review-agent-${fallbackIndex + 1}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function buildPeerCoordinationIdentities(
|
|
84
|
+
agents: ConfiguredReviewAgent[],
|
|
85
|
+
): PeerCoordinationIdentity[] {
|
|
86
|
+
const countsByBaseId = new Map<string, number>();
|
|
87
|
+
return agents.map((agent, index) => {
|
|
88
|
+
const segment = normalizePeerCoordinationIdSegment(agent.name, index);
|
|
89
|
+
const baseId = `${PEER_COORDINATION_AGENT_ID_PREFIX}-${segment}`;
|
|
90
|
+
const previousCount = countsByBaseId.get(baseId) ?? 0;
|
|
91
|
+
countsByBaseId.set(baseId, previousCount + 1);
|
|
92
|
+
return {
|
|
93
|
+
agent,
|
|
94
|
+
id: previousCount === 0 ? baseId : `${baseId}-${previousCount + 1}`,
|
|
95
|
+
displayName: normalizePeerCoordinationDisplayName(agent.name, index),
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build the IRC peer-coordination prompt block for one agent.
|
|
102
|
+
*
|
|
103
|
+
* Returns `null` when peer coordination is disabled, the host runtime does not
|
|
104
|
+
* expose the `irc` tool, or the agent has no peers (single-agent run, or only
|
|
105
|
+
* itself opted in).
|
|
106
|
+
*/
|
|
107
|
+
export function buildPeerCoordinationPromptBlock(
|
|
108
|
+
agent: ConfiguredReviewAgent,
|
|
109
|
+
peers: ConfiguredReviewAgent[],
|
|
110
|
+
activeTools: string[],
|
|
111
|
+
identities: PeerCoordinationIdentity[] = buildPeerCoordinationIdentities(peers),
|
|
112
|
+
): string | null {
|
|
113
|
+
if (agent.peerCoordination !== true) return null;
|
|
114
|
+
if (!activeTools.includes("irc")) return null;
|
|
115
|
+
|
|
116
|
+
const self = identities.find((identity) => identity.agent === agent);
|
|
117
|
+
if (!self) return null;
|
|
118
|
+
|
|
119
|
+
const otherPeers = identities.filter(
|
|
120
|
+
(identity) => identity.agent.peerCoordination === true && identity.agent !== agent,
|
|
121
|
+
);
|
|
122
|
+
if (otherPeers.length === 0) return null;
|
|
123
|
+
|
|
124
|
+
return [
|
|
125
|
+
"## IRC peer coordination",
|
|
126
|
+
"",
|
|
127
|
+
`You are running as \`${self.id}\` in a multi-agent review. Other reviewers also opted into peer coordination:`,
|
|
128
|
+
...otherPeers.map((peer) => `- \`${peer.id}\` — ${peer.displayName}`),
|
|
129
|
+
"",
|
|
130
|
+
"Use the OMP `irc` tool when continuing alone is wasteful or wrong:",
|
|
131
|
+
"- Send a DM with `irc({ op: \"send\", to: \"<peer-id>\", message: \"...\" })` when you spot work a peer is already filing, when you need their finding for context, or when you would otherwise duplicate analysis.",
|
|
132
|
+
"- Reply in plain prose. Do **NOT** send JSON status payloads. Do **NOT** quote the message you are replying to.",
|
|
133
|
+
"- One DM is one round-trip. Do **NOT** follow up with \"did you get my message?\".",
|
|
134
|
+
"- Use exactly the peer ids listed above. Do **NOT** invent ids from agent names, and do **NOT** broadcast unless you genuinely need every peer.",
|
|
135
|
+
"- If a peer has already filed an equivalent finding, do **NOT** file a duplicate; defer to theirs.",
|
|
136
|
+
"",
|
|
137
|
+
].join("\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
56
140
|
function aggregateAgentOutputs(results: MultiAgentAgentResult[]): ReviewOutput {
|
|
57
141
|
const findings = results.flatMap((result) =>
|
|
58
142
|
result.output.findings.map((finding) => ({
|
|
@@ -73,13 +157,25 @@ function aggregateAgentOutputs(results: MultiAgentAgentResult[]): ReviewOutput {
|
|
|
73
157
|
async function runConfiguredAgent(
|
|
74
158
|
input: Omit<MultiAgentReviewInput, "agents">,
|
|
75
159
|
agent: ConfiguredReviewAgent,
|
|
160
|
+
peers: ConfiguredReviewAgent[],
|
|
161
|
+
peerIdentities: PeerCoordinationIdentity[],
|
|
76
162
|
): Promise<MultiAgentAgentResult> {
|
|
77
163
|
input.onAgentStart?.(agent);
|
|
78
164
|
|
|
165
|
+
const identity = peerIdentities.find((peer) => peer.agent === agent);
|
|
166
|
+
const basePrompt = buildConfiguredAgentPrompt(agent, input.scope);
|
|
167
|
+
const peerBlock = buildPeerCoordinationPromptBlock(
|
|
168
|
+
agent,
|
|
169
|
+
peers,
|
|
170
|
+
input.activeTools ?? [],
|
|
171
|
+
peerIdentities,
|
|
172
|
+
);
|
|
173
|
+
const prompt = peerBlock ? `${peerBlock}\n${basePrompt}` : basePrompt;
|
|
174
|
+
|
|
79
175
|
const result = await runWithOutputValidation(input.createAgentSession, {
|
|
80
176
|
cwd: input.cwd,
|
|
81
|
-
prompt
|
|
82
|
-
schema:
|
|
177
|
+
prompt,
|
|
178
|
+
schema: REVIEW_OUTPUT_SCHEMA_TEXT,
|
|
83
179
|
parse(raw) {
|
|
84
180
|
const output = parseReviewOutput(raw);
|
|
85
181
|
return {
|
|
@@ -89,7 +185,11 @@ async function runConfiguredAgent(
|
|
|
89
185
|
},
|
|
90
186
|
model: agent.model ?? input.model,
|
|
91
187
|
thinkingLevel: agent.thinkingLevel ?? input.thinkingLevel ?? null,
|
|
188
|
+
...(peerBlock && identity
|
|
189
|
+
? { agentId: identity.id, agentDisplayName: identity.displayName }
|
|
190
|
+
: {}),
|
|
92
191
|
timeoutMs: input.timeoutMs ?? 120_000,
|
|
192
|
+
reliability: input.reliability,
|
|
93
193
|
});
|
|
94
194
|
|
|
95
195
|
if (result.status === "blocked") {
|
|
@@ -124,8 +224,9 @@ async function runConfiguredAgent(
|
|
|
124
224
|
}
|
|
125
225
|
|
|
126
226
|
export async function runMultiAgentReview(input: MultiAgentReviewInput): Promise<MultiAgentReviewResult> {
|
|
227
|
+
const peerIdentities = buildPeerCoordinationIdentities(input.agents);
|
|
127
228
|
const results = await Promise.all(
|
|
128
|
-
input.agents.map((agent) => runConfiguredAgent(input, agent)),
|
|
229
|
+
input.agents.map((agent) => runConfiguredAgent(input, agent, input.agents, peerIdentities)),
|
|
129
230
|
);
|
|
130
231
|
|
|
131
232
|
return {
|