@backburner/cli 0.1.0
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/LICENSE +21 -0
- package/README.md +329 -0
- package/dist/src/agents/antigravity-launch.js +162 -0
- package/dist/src/agents/antigravity.js +251 -0
- package/dist/src/agents/claude-launch.js +117 -0
- package/dist/src/agents/claude.js +271 -0
- package/dist/src/agents/codex-launch.js +82 -0
- package/dist/src/agents/codex.js +261 -0
- package/dist/src/agents/composite-runner.js +13 -0
- package/dist/src/agents/extraction.js +442 -0
- package/dist/src/agents/failure-feedback.js +132 -0
- package/dist/src/agents/gemini-launch.js +96 -0
- package/dist/src/agents/gemini.js +392 -0
- package/dist/src/agents/job-agent-assignments.js +180 -0
- package/dist/src/agents/lifecycle-feedback.js +147 -0
- package/dist/src/agents/limit-detector.js +83 -0
- package/dist/src/agents/opencode-launch.js +138 -0
- package/dist/src/agents/opencode.js +156 -0
- package/dist/src/agents/plan-breakdown-feedback.js +97 -0
- package/dist/src/agents/pool.js +168 -0
- package/dist/src/agents/prompt.js +462 -0
- package/dist/src/agents/provider-sessions.js +105 -0
- package/dist/src/agents/review-feedback.js +255 -0
- package/dist/src/agents/selection.js +113 -0
- package/dist/src/agents/service.js +1087 -0
- package/dist/src/agents/types.js +1 -0
- package/dist/src/agents/wave-assessment-feedback.js +69 -0
- package/dist/src/attention/derive.js +337 -0
- package/dist/src/capabilities/index.js +2 -0
- package/dist/src/capabilities/projection.js +183 -0
- package/dist/src/capabilities/types.js +1 -0
- package/dist/src/capabilities/validator.js +87 -0
- package/dist/src/cli/commands/broker-smoke.js +96 -0
- package/dist/src/cli/commands/broker.js +56 -0
- package/dist/src/cli/commands/init.js +4 -0
- package/dist/src/cli/commands/journal.js +43 -0
- package/dist/src/cli/commands/run-loop.js +56 -0
- package/dist/src/cli/commands/run.js +1241 -0
- package/dist/src/cli/commands/tui.js +187 -0
- package/dist/src/cli/dispatcher.js +51 -0
- package/dist/src/cli/errors.js +2 -0
- package/dist/src/cli/init.js +92 -0
- package/dist/src/cli/options.js +86 -0
- package/dist/src/cli/output.js +231 -0
- package/dist/src/cli/run-journal.js +342 -0
- package/dist/src/cli/run-types.js +1 -0
- package/dist/src/cli/run.js +32 -0
- package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
- package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
- package/dist/src/cli/runtime/provider-runtime.js +152 -0
- package/dist/src/cli/runtime/provider-tools.js +73 -0
- package/dist/src/cli/runtime/providers/antigravity.js +26 -0
- package/dist/src/cli/runtime/providers/claude.js +20 -0
- package/dist/src/cli/runtime/providers/codex.js +20 -0
- package/dist/src/cli/runtime/providers/gemini.js +20 -0
- package/dist/src/cli/runtime/providers/opencode.js +20 -0
- package/dist/src/cli/runtime/run-context.js +131 -0
- package/dist/src/cli/tui/index.js +3 -0
- package/dist/src/cli/tui/screen.js +388 -0
- package/dist/src/cli/tui/tracker.js +558 -0
- package/dist/src/cli/tui/types.js +1 -0
- package/dist/src/cli/tui/views.js +1006 -0
- package/dist/src/config/loader.js +136 -0
- package/dist/src/config/schemas.js +403 -0
- package/dist/src/config/types.js +1 -0
- package/dist/src/context/types.js +331 -0
- package/dist/src/domain/entities.js +1 -0
- package/dist/src/git/backburner-git-tool-service.js +680 -0
- package/dist/src/git/gitops-lifecycle.js +56 -0
- package/dist/src/git/merge-prep-workspace.js +357 -0
- package/dist/src/git/types.js +484 -0
- package/dist/src/github/broker-model-content.js +215 -0
- package/dist/src/github/broker-server.js +237 -0
- package/dist/src/github/broker.js +638 -0
- package/dist/src/github/composed.js +53 -0
- package/dist/src/github/gateway.js +478 -0
- package/dist/src/github/normalize.js +222 -0
- package/dist/src/github/pr-classification.js +73 -0
- package/dist/src/github/scoped-broker.js +35 -0
- package/dist/src/github/security.js +126 -0
- package/dist/src/github/service.js +332 -0
- package/dist/src/github/types.js +1 -0
- package/dist/src/github/utils.js +16 -0
- package/dist/src/journal/index.js +4 -0
- package/dist/src/journal/reader.js +62 -0
- package/dist/src/journal/renderer.js +50 -0
- package/dist/src/journal/service.js +72 -0
- package/dist/src/journal/types.js +1 -0
- package/dist/src/journal/writer.js +19 -0
- package/dist/src/mcps/bridge.js +166 -0
- package/dist/src/mcps/index.js +5 -0
- package/dist/src/mcps/reconcile.js +113 -0
- package/dist/src/mcps/smithery-client.js +186 -0
- package/dist/src/mcps/smithery.js +164 -0
- package/dist/src/mcps/types.js +1 -0
- package/dist/src/memory/json-store.js +71 -0
- package/dist/src/memory/prompt.js +16 -0
- package/dist/src/memory/scope.js +31 -0
- package/dist/src/memory/store.js +1 -0
- package/dist/src/memory/tool-service.js +142 -0
- package/dist/src/memory/types.js +12 -0
- package/dist/src/onboarding/config-writer.js +96 -0
- package/dist/src/onboarding/engine.js +86 -0
- package/dist/src/onboarding/prompt.js +102 -0
- package/dist/src/onboarding/services/provider-discovery.js +58 -0
- package/dist/src/onboarding/services/repo-discovery.js +246 -0
- package/dist/src/onboarding/steps/check-github-auth.js +32 -0
- package/dist/src/onboarding/steps/check-requirements.js +34 -0
- package/dist/src/onboarding/steps/configure-models.js +105 -0
- package/dist/src/onboarding/steps/configure-paths.js +33 -0
- package/dist/src/onboarding/steps/discover-providers.js +38 -0
- package/dist/src/onboarding/steps/discover-repos.js +39 -0
- package/dist/src/onboarding/steps/generate-config.js +106 -0
- package/dist/src/onboarding/steps/readiness-validation.js +158 -0
- package/dist/src/onboarding/steps/select-repos.js +76 -0
- package/dist/src/onboarding/steps/show-summary.js +97 -0
- package/dist/src/onboarding/steps/welcome.js +27 -0
- package/dist/src/onboarding/types.js +1 -0
- package/dist/src/onboarding/validators/git-access.js +63 -0
- package/dist/src/onboarding/validators/github-auth.js +85 -0
- package/dist/src/onboarding/validators/tool-checker.js +153 -0
- package/dist/src/onboarding/validators/workspace.js +83 -0
- package/dist/src/prompts/resolver.js +106 -0
- package/dist/src/retrospectives/candidates.js +224 -0
- package/dist/src/retrospectives/derive.js +169 -0
- package/dist/src/retrospectives/ingest.js +321 -0
- package/dist/src/retrospectives/management-prs.js +380 -0
- package/dist/src/retrospectives/proposals.js +199 -0
- package/dist/src/retrospectives/scopes.js +272 -0
- package/dist/src/retrospectives/scoring.js +171 -0
- package/dist/src/retrospectives/types.js +1 -0
- package/dist/src/state/loader.js +473 -0
- package/dist/src/state/types.js +15 -0
- package/dist/src/tasks/derivation/builders.js +70 -0
- package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
- package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
- package/dist/src/tasks/derivation/handlers/index.js +54 -0
- package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
- package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
- package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
- package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
- package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
- package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
- package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
- package/dist/src/tasks/derivation/handlers/shared.js +24 -0
- package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
- package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
- package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
- package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
- package/dist/src/tasks/derivation/runner.js +18 -0
- package/dist/src/tasks/derivation/types.js +1 -0
- package/dist/src/tasks/derive.js +810 -0
- package/dist/src/tasks/mcp-skip.js +10 -0
- package/dist/src/tasks/retry.js +10 -0
- package/dist/src/tasks/task.js +184 -0
- package/dist/src/tasks/types.js +1 -0
- package/dist/src/utils/command-runner.js +192 -0
- package/dist/src/utils/json.js +38 -0
- package/dist/src/utils/logger.js +43 -0
- package/dist/src/utils/paths.js +38 -0
- package/dist/src/utils/slug.js +10 -0
- package/dist/src/workflows/registry.js +174 -0
- package/dist/src/workflows/triage.js +90 -0
- package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
- package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
- package/dist/src/workflows/workstream-machine/events.js +1 -0
- package/dist/src/workflows/workstream-machine/machine.js +355 -0
- package/dist/src/workflows/workstream-machine/selectors.js +19 -0
- package/dist/src/workflows/workstream-machine/types.js +1 -0
- package/dist/src/workstreams/branch-sync.js +87 -0
- package/dist/src/workstreams/derive.js +629 -0
- package/dist/src/workstreams/mcp-blockers.js +420 -0
- package/dist/src/workstreams/packet-candidates.js +42 -0
- package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
- package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
- package/dist/src/workstreams/packet-plan.js +597 -0
- package/dist/src/workstreams/packet-projections.js +190 -0
- package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
- package/dist/src/workstreams/parent-branch-sync.js +148 -0
- package/dist/src/workstreams/phases.js +4 -0
- package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
- package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
- package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
- package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
- package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
- package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
- package/dist/src/workstreams/utils.js +15 -0
- package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
- package/dist/src/workstreams/wave-assessment/index.js +3 -0
- package/dist/src/workstreams/wave-assessment/session.js +84 -0
- package/dist/src/workstreams/wave-assessment/store.js +51 -0
- package/dist/src/workstreams/wave-assessment/tools.js +419 -0
- package/dist/src/workstreams/wave-assessment/types.js +1 -0
- package/dist/src/workstreams/wave-assessment/validate.js +78 -0
- package/dist/src/worktrees/service.js +839 -0
- package/dist/src/worktrees/workspace-ownership.js +34 -0
- package/dist/src/worktrees/workspace-recovery.js +177 -0
- package/package.json +61 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { AgentsConfigFileSchema, CapabilitiesConfigFileSchema, DEFAULT_GITHUB_BROKER_SERVER_NAME, DEFAULT_GITHUB_BROKER_TOOLS, McpsConfigFileSchema, PromptsConfigFileSchema, ReposConfigFileSchema } from "./schemas.js";
|
|
3
|
+
import { readJsonFile } from "../utils/json.js";
|
|
4
|
+
import { managementRoot } from "../utils/paths.js";
|
|
5
|
+
function formatZodPath(pathParts) {
|
|
6
|
+
return pathParts
|
|
7
|
+
.map((segment, index) => {
|
|
8
|
+
if (typeof segment === "number")
|
|
9
|
+
return `[${segment}]`;
|
|
10
|
+
return index === 0 ? segment : `.${segment}`;
|
|
11
|
+
})
|
|
12
|
+
.join("");
|
|
13
|
+
}
|
|
14
|
+
function parseConfig(schema, value, filePath) {
|
|
15
|
+
const result = schema.safeParse(value);
|
|
16
|
+
if (result.success)
|
|
17
|
+
return result.data;
|
|
18
|
+
const issue = result.error.issues[0];
|
|
19
|
+
const schemaPath = issue ? formatZodPath(issue.path) : "";
|
|
20
|
+
const message = issue?.message ?? "invalid configuration";
|
|
21
|
+
const detail = schemaPath ? `${schemaPath} ${message}` : message;
|
|
22
|
+
throw new Error(`${filePath}: ${detail}`);
|
|
23
|
+
}
|
|
24
|
+
function defaultGitHubBrokerConfig() {
|
|
25
|
+
return {
|
|
26
|
+
serverName: DEFAULT_GITHUB_BROKER_SERVER_NAME,
|
|
27
|
+
toolAllowList: [...DEFAULT_GITHUB_BROKER_TOOLS]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export async function loadConfig(baseDir = managementRoot) {
|
|
31
|
+
const reposFile = path.join(baseDir, "repos.json");
|
|
32
|
+
const agentsFile = path.join(baseDir, "agents.json");
|
|
33
|
+
const reposConfig = parseConfig(ReposConfigFileSchema, await readJsonFile(reposFile), reposFile);
|
|
34
|
+
const agentsConfig = parseConfig(AgentsConfigFileSchema, await readJsonFile(agentsFile), agentsFile);
|
|
35
|
+
const prompts = await loadPromptsConfig(baseDir);
|
|
36
|
+
const mcps = await loadMcpsConfig(baseDir);
|
|
37
|
+
const capabilities = await loadCapabilitiesConfig(baseDir);
|
|
38
|
+
const maxConcurrentAgents = resolveMaxConcurrentAgents(agentsConfig.maxConcurrentAgents);
|
|
39
|
+
return {
|
|
40
|
+
allowedUsers: reposConfig.allowedUsers,
|
|
41
|
+
repos: reposConfig.repos,
|
|
42
|
+
agents: agentsConfig.agents,
|
|
43
|
+
codex: {
|
|
44
|
+
githubBroker: agentsConfig.codex?.githubBroker ?? defaultGitHubBrokerConfig()
|
|
45
|
+
},
|
|
46
|
+
gemini: {
|
|
47
|
+
githubBroker: agentsConfig.gemini?.githubBroker ?? defaultGitHubBrokerConfig()
|
|
48
|
+
},
|
|
49
|
+
claude: {
|
|
50
|
+
githubBroker: agentsConfig.claude?.githubBroker ?? defaultGitHubBrokerConfig()
|
|
51
|
+
},
|
|
52
|
+
opencode: {
|
|
53
|
+
githubBroker: agentsConfig.opencode?.githubBroker ?? defaultGitHubBrokerConfig()
|
|
54
|
+
},
|
|
55
|
+
antigravity: {
|
|
56
|
+
githubBroker: agentsConfig.antigravity?.githubBroker ?? defaultGitHubBrokerConfig()
|
|
57
|
+
},
|
|
58
|
+
...(prompts ? { prompts } : {}),
|
|
59
|
+
mcps,
|
|
60
|
+
capabilities,
|
|
61
|
+
maxConcurrentAgents
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const DEFAULT_MAX_CONCURRENT_AGENTS = 1;
|
|
65
|
+
function resolveMaxConcurrentAgents(fromConfig) {
|
|
66
|
+
const fromEnv = process.env["BACKBURNER_MAX_CONCURRENT_AGENTS"];
|
|
67
|
+
if (fromEnv !== undefined) {
|
|
68
|
+
const parsed = parseInt(fromEnv, 10);
|
|
69
|
+
if (!isNaN(parsed) && parsed >= 1) {
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return fromConfig ?? DEFAULT_MAX_CONCURRENT_AGENTS;
|
|
74
|
+
}
|
|
75
|
+
export async function loadMcpsConfig(baseDir) {
|
|
76
|
+
const mcpsFile = path.join(baseDir, "mcps.json");
|
|
77
|
+
let raw;
|
|
78
|
+
try {
|
|
79
|
+
raw = await readJsonFile(mcpsFile);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (err instanceof Error && err.message.includes("file not found")) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
const config = parseConfig(McpsConfigFileSchema, raw, mcpsFile);
|
|
88
|
+
return config.mcps.map((entry) => {
|
|
89
|
+
if (typeof entry === "string") {
|
|
90
|
+
return { handle: entry, smitheryId: entry };
|
|
91
|
+
}
|
|
92
|
+
return { handle: entry.as, smitheryId: entry.id };
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
export async function loadCapabilitiesConfig(baseDir) {
|
|
96
|
+
const capabilitiesFile = path.join(baseDir, "capabilities.json");
|
|
97
|
+
let raw;
|
|
98
|
+
try {
|
|
99
|
+
raw = await readJsonFile(capabilitiesFile);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err instanceof Error && err.message.includes("file not found")) {
|
|
103
|
+
return { sensors: [], controllers: [] };
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
const config = parseConfig(CapabilitiesConfigFileSchema, raw, capabilitiesFile);
|
|
108
|
+
const sensors = Object.entries(config.sensors).map(([name, sensor]) => ({
|
|
109
|
+
name,
|
|
110
|
+
uses: sensor.uses,
|
|
111
|
+
observes: sensor.observes,
|
|
112
|
+
...(sensor.cadence !== undefined ? { cadence: sensor.cadence } : {}),
|
|
113
|
+
...(sensor.scope !== undefined ? { scope: sensor.scope } : {})
|
|
114
|
+
}));
|
|
115
|
+
const controllers = Object.entries(config.controllers).map(([name, controller]) => ({
|
|
116
|
+
name,
|
|
117
|
+
uses: controller.uses,
|
|
118
|
+
grants: controller.grants,
|
|
119
|
+
approval: controller.approval
|
|
120
|
+
}));
|
|
121
|
+
return { sensors, controllers };
|
|
122
|
+
}
|
|
123
|
+
export async function loadPromptsConfig(baseDir) {
|
|
124
|
+
const promptsFile = path.join(baseDir, "prompts.json");
|
|
125
|
+
let raw;
|
|
126
|
+
try {
|
|
127
|
+
raw = await readJsonFile(promptsFile);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (err instanceof Error && err.message.includes("file not found")) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
return parseConfig(PromptsConfigFileSchema, raw, promptsFile);
|
|
136
|
+
}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const DEFAULT_GITHUB_BROKER_SERVER_NAME = "githubBroker";
|
|
4
|
+
export const DEFAULT_GITHUB_BROKER_TOOLS = [
|
|
5
|
+
"get_issue",
|
|
6
|
+
"get_issue_comments",
|
|
7
|
+
"get_pr",
|
|
8
|
+
"get_pr_comments",
|
|
9
|
+
"get_pr_checks",
|
|
10
|
+
"search_code",
|
|
11
|
+
"post_issue_comment",
|
|
12
|
+
"post_pr_review_comment_reply",
|
|
13
|
+
"create_pr"
|
|
14
|
+
];
|
|
15
|
+
// Helper for non-empty strings that matches existing manual validation (trim + length check)
|
|
16
|
+
const nonEmptyString = (message) => z.string({ message })
|
|
17
|
+
.trim()
|
|
18
|
+
.min(1, { message });
|
|
19
|
+
const optionalNonEmptyString = (message) => z.string({ message })
|
|
20
|
+
.trim()
|
|
21
|
+
.min(1, { message })
|
|
22
|
+
.optional();
|
|
23
|
+
const relativePath = (message, traversalMessage) => z.string({ message })
|
|
24
|
+
.trim()
|
|
25
|
+
.min(1, { message })
|
|
26
|
+
.refine((val) => !isAbsolutePathOnAnySupportedPlatform(val), { message })
|
|
27
|
+
.transform((val) => normalizeRelativePathForCurrentPlatform(val))
|
|
28
|
+
.refine((val) => val !== ".." && !val.startsWith(`..${path.sep}`), {
|
|
29
|
+
message: traversalMessage
|
|
30
|
+
});
|
|
31
|
+
function isAbsolutePathOnAnySupportedPlatform(value) {
|
|
32
|
+
return path.isAbsolute(value) || /^[A-Za-z]:/.test(value) || value.startsWith("\\");
|
|
33
|
+
}
|
|
34
|
+
function normalizeRelativePathForCurrentPlatform(value) {
|
|
35
|
+
return path.normalize(value.replaceAll("\\", path.sep));
|
|
36
|
+
}
|
|
37
|
+
export const GitHubBrokerSchema = z.object({
|
|
38
|
+
serverName: nonEmptyString("must be a non-empty string").default(DEFAULT_GITHUB_BROKER_SERVER_NAME),
|
|
39
|
+
toolAllowList: z
|
|
40
|
+
.array(z.string().trim().min(1), {
|
|
41
|
+
message: "must be an array of strings"
|
|
42
|
+
})
|
|
43
|
+
.default([...DEFAULT_GITHUB_BROKER_TOOLS])
|
|
44
|
+
});
|
|
45
|
+
const ProviderBlockSchema = z.object({
|
|
46
|
+
githubBroker: GitHubBrokerSchema.optional()
|
|
47
|
+
});
|
|
48
|
+
export const RepoLabelsSchema = z.object({
|
|
49
|
+
agentInProgress: nonEmptyString("must be a non-empty string"),
|
|
50
|
+
agentPlanApproved: nonEmptyString("must be a non-empty string"),
|
|
51
|
+
agentProductApproved: optionalNonEmptyString("must be a non-empty string when provided").default("agent-product-approved"),
|
|
52
|
+
agentReview: nonEmptyString("must be a non-empty string"),
|
|
53
|
+
planBreakdownNeeded: optionalNonEmptyString("must be a non-empty string when provided").default("plan-breakdown-needed"),
|
|
54
|
+
agentSyncParentBranch: optionalNonEmptyString("must be a non-empty string when provided").default("agent-sync-parent-branch"),
|
|
55
|
+
prepareForMerge: optionalNonEmptyString("must be a non-empty string when provided").default("prepare-for-merge")
|
|
56
|
+
});
|
|
57
|
+
export const RepoConfigSchema = z.object({
|
|
58
|
+
id: nonEmptyString("must be a non-empty string"),
|
|
59
|
+
owner: nonEmptyString("must be a non-empty string"),
|
|
60
|
+
name: nonEmptyString("must be a non-empty string"),
|
|
61
|
+
defaultBranch: optionalNonEmptyString("must be a non-empty string when provided"),
|
|
62
|
+
localPath: relativePath("must be relative", "must stay within the provided code root"),
|
|
63
|
+
enabled: z.boolean({ message: "must be a boolean" }),
|
|
64
|
+
labels: RepoLabelsSchema
|
|
65
|
+
});
|
|
66
|
+
export const ReposConfigFileSchema = z.object({
|
|
67
|
+
allowedUsers: z.array(z.string().trim().min(1), {
|
|
68
|
+
message: '"allowedUsers" must be an array of strings'
|
|
69
|
+
}),
|
|
70
|
+
repos: z.array(RepoConfigSchema, { message: '"repos" must be an array' })
|
|
71
|
+
});
|
|
72
|
+
export const AgentProviderSchema = z.enum(["codex", "gemini", "claude", "opencode", "antigravity"], {
|
|
73
|
+
message: 'must be "codex", "gemini", "claude", "opencode" or "antigravity"'
|
|
74
|
+
});
|
|
75
|
+
export const ProviderLimitRecordSchema = z.object({
|
|
76
|
+
provider: AgentProviderSchema,
|
|
77
|
+
status: z.enum(["available", "limited"]),
|
|
78
|
+
limitType: z.enum(["session", "weekly"]).optional(),
|
|
79
|
+
rawError: z.string().optional(),
|
|
80
|
+
detectedAt: z.string().datetime().optional(),
|
|
81
|
+
resetTime: z.string().datetime().optional(),
|
|
82
|
+
updatedAt: z.string().datetime()
|
|
83
|
+
}).strict();
|
|
84
|
+
export const ProviderLimitStateFileSchema = z.object({
|
|
85
|
+
providerLimits: z.array(ProviderLimitRecordSchema)
|
|
86
|
+
}).strict();
|
|
87
|
+
const ModelClassSchema = z.enum(["strong", "cheap"], {
|
|
88
|
+
message: 'must be "strong" or "cheap"'
|
|
89
|
+
});
|
|
90
|
+
const TrimmedModelClassSchema = z
|
|
91
|
+
.string()
|
|
92
|
+
.trim()
|
|
93
|
+
.pipe(ModelClassSchema);
|
|
94
|
+
const BaseAgentConfigSchema = z.object({
|
|
95
|
+
id: nonEmptyString("must be a non-empty string"),
|
|
96
|
+
modelClass: TrimmedModelClassSchema,
|
|
97
|
+
roles: z.array(z.string().trim().min(1), {
|
|
98
|
+
message: "must be an array of strings"
|
|
99
|
+
}),
|
|
100
|
+
command: nonEmptyString("must be a non-empty string"),
|
|
101
|
+
enabled: z.boolean({ message: "must be a boolean" })
|
|
102
|
+
});
|
|
103
|
+
const CodexConfigSchema = BaseAgentConfigSchema.extend({
|
|
104
|
+
provider: z.literal("codex"),
|
|
105
|
+
model: optionalNonEmptyString("must be a non-empty string when provided")
|
|
106
|
+
});
|
|
107
|
+
const GeminiConfigSchema = BaseAgentConfigSchema.extend({
|
|
108
|
+
provider: z.literal("gemini"),
|
|
109
|
+
model: optionalNonEmptyString("must be a non-empty string when provided")
|
|
110
|
+
});
|
|
111
|
+
const ClaudeConfigSchema = BaseAgentConfigSchema.extend({
|
|
112
|
+
provider: z.literal("claude"),
|
|
113
|
+
model: optionalNonEmptyString("must be a non-empty string when provided")
|
|
114
|
+
});
|
|
115
|
+
const OpenCodeConfigSchema = BaseAgentConfigSchema.extend({
|
|
116
|
+
provider: z.literal("opencode"),
|
|
117
|
+
model: nonEmptyString("must be a non-empty string")
|
|
118
|
+
});
|
|
119
|
+
const AntigravityConfigSchema = BaseAgentConfigSchema.extend({
|
|
120
|
+
provider: z.literal("antigravity"),
|
|
121
|
+
model: optionalNonEmptyString("must be a non-empty string when provided")
|
|
122
|
+
});
|
|
123
|
+
const AgentConfigDiscriminatedSchema = z.discriminatedUnion("provider", [
|
|
124
|
+
CodexConfigSchema,
|
|
125
|
+
GeminiConfigSchema,
|
|
126
|
+
ClaudeConfigSchema,
|
|
127
|
+
OpenCodeConfigSchema,
|
|
128
|
+
AntigravityConfigSchema
|
|
129
|
+
]);
|
|
130
|
+
export const AgentConfigSchema = z.preprocess((value) => {
|
|
131
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
const record = value;
|
|
135
|
+
if (typeof record.provider !== "string") {
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
return { ...record, provider: record.provider.trim() };
|
|
139
|
+
}, AgentConfigDiscriminatedSchema);
|
|
140
|
+
export const AgentsConfigFileSchema = z.object({
|
|
141
|
+
agents: z.array(AgentConfigSchema, { message: '"agents" must be an array' }),
|
|
142
|
+
codex: ProviderBlockSchema.optional(),
|
|
143
|
+
gemini: ProviderBlockSchema.optional(),
|
|
144
|
+
claude: ProviderBlockSchema.optional(),
|
|
145
|
+
opencode: ProviderBlockSchema.optional(),
|
|
146
|
+
antigravity: ProviderBlockSchema.optional(),
|
|
147
|
+
maxConcurrentAgents: z.number({ message: '"maxConcurrentAgents" must be a number' })
|
|
148
|
+
.int({ message: '"maxConcurrentAgents" must be an integer' })
|
|
149
|
+
.min(1, { message: '"maxConcurrentAgents" must be at least 1' })
|
|
150
|
+
.optional()
|
|
151
|
+
});
|
|
152
|
+
export const PromptProfileSchema = z.object({
|
|
153
|
+
plan: relativePath("must be relative", "must stay within the management directory").optional(),
|
|
154
|
+
implement: relativePath("must be relative", "must stay within the management directory").optional(),
|
|
155
|
+
review: relativePath("must be relative", "must stay within the management directory").optional(),
|
|
156
|
+
discuss: relativePath("must be relative", "must stay within the management directory").optional(),
|
|
157
|
+
product: relativePath("must be relative", "must stay within the management directory").optional(),
|
|
158
|
+
breakdown: relativePath("must be relative", "must stay within the management directory").optional(),
|
|
159
|
+
waveAssessment: relativePath("must be relative", "must stay within the management directory").optional(),
|
|
160
|
+
waveReplanning: relativePath("must be relative", "must stay within the management directory").optional(),
|
|
161
|
+
retrospectiveExtract: relativePath("must be relative", "must stay within the management directory").optional(),
|
|
162
|
+
retrospectiveInvestigate: relativePath("must be relative", "must stay within the management directory").optional(),
|
|
163
|
+
retrospectiveProposal: relativePath("must be relative", "must stay within the management directory").optional()
|
|
164
|
+
});
|
|
165
|
+
export const PromptsConfigFileSchema = z.object({
|
|
166
|
+
promptProfiles: z.object({
|
|
167
|
+
default: PromptProfileSchema
|
|
168
|
+
}, { message: '"promptProfiles" must be an object' })
|
|
169
|
+
});
|
|
170
|
+
const BANNED_MCP_FIELDS = [
|
|
171
|
+
"auth",
|
|
172
|
+
"token",
|
|
173
|
+
"key",
|
|
174
|
+
"secret",
|
|
175
|
+
"credential",
|
|
176
|
+
"credentials",
|
|
177
|
+
"password",
|
|
178
|
+
"apiKey",
|
|
179
|
+
"accessToken",
|
|
180
|
+
"provider",
|
|
181
|
+
"url"
|
|
182
|
+
];
|
|
183
|
+
const BANNED_CAPABILITY_FIELDS = [
|
|
184
|
+
"auth",
|
|
185
|
+
"token",
|
|
186
|
+
"key",
|
|
187
|
+
"secret",
|
|
188
|
+
"credential",
|
|
189
|
+
"credentials",
|
|
190
|
+
"password",
|
|
191
|
+
"apiKey",
|
|
192
|
+
"accessToken",
|
|
193
|
+
"provider",
|
|
194
|
+
"url"
|
|
195
|
+
];
|
|
196
|
+
const MCP_HANDLE_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
197
|
+
function validateMcpHandle(handle, ctx, path) {
|
|
198
|
+
if (!MCP_HANDLE_PATTERN.test(handle.trim())) {
|
|
199
|
+
ctx.addIssue({
|
|
200
|
+
code: z.ZodIssueCode.custom,
|
|
201
|
+
message: "must contain only letters, numbers, underscores, or hyphens",
|
|
202
|
+
...(path ? { path } : {})
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export const SensorObservesSchema = z.enum([
|
|
207
|
+
"issues-and-prs",
|
|
208
|
+
"package-docs",
|
|
209
|
+
"repository-context"
|
|
210
|
+
]);
|
|
211
|
+
export const ControllerGrantsSchema = z.enum([
|
|
212
|
+
"comment-on-prs",
|
|
213
|
+
"comment-on-issues",
|
|
214
|
+
"open-issues",
|
|
215
|
+
"edit-issue-labels"
|
|
216
|
+
]);
|
|
217
|
+
export const CapabilityApprovalSchema = z.enum([
|
|
218
|
+
"required",
|
|
219
|
+
"not-required",
|
|
220
|
+
"forbidden"
|
|
221
|
+
]);
|
|
222
|
+
export const CapabilityScopeSchema = z.enum([
|
|
223
|
+
"repository",
|
|
224
|
+
"task-context",
|
|
225
|
+
"workstream"
|
|
226
|
+
]);
|
|
227
|
+
export const CapabilityCadenceSchema = z.enum([
|
|
228
|
+
"every-orchestrator-run",
|
|
229
|
+
"on-demand"
|
|
230
|
+
]);
|
|
231
|
+
const BackburnerSensorCapabilitySchema = z.object({
|
|
232
|
+
uses: nonEmptyString('sensor "uses" must be a non-empty string'),
|
|
233
|
+
observes: SensorObservesSchema,
|
|
234
|
+
cadence: CapabilityCadenceSchema.optional(),
|
|
235
|
+
scope: CapabilityScopeSchema.optional(),
|
|
236
|
+
// banned credential-like / provider / URL fields
|
|
237
|
+
auth: z.never({ message: 'banned field "auth"' }).optional(),
|
|
238
|
+
token: z.never({ message: 'banned field "token"' }).optional(),
|
|
239
|
+
key: z.never({ message: 'banned field "key"' }).optional(),
|
|
240
|
+
secret: z.never({ message: 'banned field "secret"' }).optional(),
|
|
241
|
+
credential: z.never({ message: 'banned field "credential"' }).optional(),
|
|
242
|
+
credentials: z.never({ message: 'banned field "credentials"' }).optional(),
|
|
243
|
+
password: z.never({ message: 'banned field "password"' }).optional(),
|
|
244
|
+
apiKey: z.never({ message: 'banned field "apiKey"' }).optional(),
|
|
245
|
+
accessToken: z.never({ message: 'banned field "accessToken"' }).optional(),
|
|
246
|
+
provider: z.never({ message: 'banned field "provider"' }).optional(),
|
|
247
|
+
url: z.never({ message: 'banned field "url"' }).optional(),
|
|
248
|
+
// cross-type field: sensors must not have grants
|
|
249
|
+
grants: z.never({ message: 'sensor capability must not include "grants"' }).optional(),
|
|
250
|
+
}).strict();
|
|
251
|
+
const BackburnerControllerCapabilitySchema = z.object({
|
|
252
|
+
uses: nonEmptyString('controller "uses" must be a non-empty string'),
|
|
253
|
+
grants: z.array(ControllerGrantsSchema, {
|
|
254
|
+
message: 'controller "grants" must be an array of valid grant values',
|
|
255
|
+
}).min(1, { message: 'controller "grants" must have at least one value' }),
|
|
256
|
+
approval: CapabilityApprovalSchema.default("required"),
|
|
257
|
+
// banned credential-like / provider / URL fields
|
|
258
|
+
auth: z.never({ message: 'banned field "auth"' }).optional(),
|
|
259
|
+
token: z.never({ message: 'banned field "token"' }).optional(),
|
|
260
|
+
key: z.never({ message: 'banned field "key"' }).optional(),
|
|
261
|
+
secret: z.never({ message: 'banned field "secret"' }).optional(),
|
|
262
|
+
credential: z.never({ message: 'banned field "credential"' }).optional(),
|
|
263
|
+
credentials: z.never({ message: 'banned field "credentials"' }).optional(),
|
|
264
|
+
password: z.never({ message: 'banned field "password"' }).optional(),
|
|
265
|
+
apiKey: z.never({ message: 'banned field "apiKey"' }).optional(),
|
|
266
|
+
accessToken: z.never({ message: 'banned field "accessToken"' }).optional(),
|
|
267
|
+
provider: z.never({ message: 'banned field "provider"' }).optional(),
|
|
268
|
+
url: z.never({ message: 'banned field "url"' }).optional(),
|
|
269
|
+
// cross-type field: controllers must not have observes
|
|
270
|
+
observes: z.never({ message: 'controller capability must not include "observes"' }).optional(),
|
|
271
|
+
}).strict();
|
|
272
|
+
export const CapabilitiesConfigFileSchema = z
|
|
273
|
+
.object({
|
|
274
|
+
sensors: z
|
|
275
|
+
.record(z.string(), BackburnerSensorCapabilitySchema)
|
|
276
|
+
.optional()
|
|
277
|
+
.default({}),
|
|
278
|
+
controllers: z
|
|
279
|
+
.record(z.string(), BackburnerControllerCapabilitySchema)
|
|
280
|
+
.optional()
|
|
281
|
+
.default({}),
|
|
282
|
+
})
|
|
283
|
+
.strict()
|
|
284
|
+
.superRefine((data, ctx) => {
|
|
285
|
+
for (const name of Object.keys(data.sensors)) {
|
|
286
|
+
if (name.trim().length === 0) {
|
|
287
|
+
ctx.addIssue({
|
|
288
|
+
code: z.ZodIssueCode.custom,
|
|
289
|
+
message: "capability name must be a non-empty string",
|
|
290
|
+
path: ["sensors", name],
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
for (const name of Object.keys(data.controllers)) {
|
|
295
|
+
if (name.trim().length === 0) {
|
|
296
|
+
ctx.addIssue({
|
|
297
|
+
code: z.ZodIssueCode.custom,
|
|
298
|
+
message: "capability name must be a non-empty string",
|
|
299
|
+
path: ["controllers", name],
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const sensorNames = new Set(Object.keys(data.sensors));
|
|
304
|
+
for (const name of Object.keys(data.controllers)) {
|
|
305
|
+
if (sensorNames.has(name)) {
|
|
306
|
+
ctx.addIssue({
|
|
307
|
+
code: z.ZodIssueCode.custom,
|
|
308
|
+
message: `duplicate capability name "${name}" across sensors and controllers`,
|
|
309
|
+
path: ["controllers", name],
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
export const McpsConfigFileSchema = z
|
|
315
|
+
.object({
|
|
316
|
+
mcps: z.array(z.any().superRefine((val, ctx) => {
|
|
317
|
+
if (typeof val === "string") {
|
|
318
|
+
if (val.trim().length === 0) {
|
|
319
|
+
ctx.addIssue({
|
|
320
|
+
code: z.ZodIssueCode.custom,
|
|
321
|
+
message: 'must be a non-empty string or an object with "as" and "id"'
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
validateMcpHandle(val, ctx);
|
|
326
|
+
}
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (typeof val === "object" && val !== null && !Array.isArray(val)) {
|
|
330
|
+
const data = val;
|
|
331
|
+
let hasCriticalError = false;
|
|
332
|
+
if (typeof data.as !== "string" || data.as.trim().length === 0) {
|
|
333
|
+
ctx.addIssue({
|
|
334
|
+
code: z.ZodIssueCode.custom,
|
|
335
|
+
message: "must be a non-empty string",
|
|
336
|
+
path: ["as"]
|
|
337
|
+
});
|
|
338
|
+
hasCriticalError = true;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
validateMcpHandle(data.as, ctx, ["as"]);
|
|
342
|
+
}
|
|
343
|
+
if (typeof data.id !== "string" || data.id.trim().length === 0) {
|
|
344
|
+
ctx.addIssue({
|
|
345
|
+
code: z.ZodIssueCode.custom,
|
|
346
|
+
message: "must be a non-empty string",
|
|
347
|
+
path: ["id"]
|
|
348
|
+
});
|
|
349
|
+
hasCriticalError = true;
|
|
350
|
+
}
|
|
351
|
+
if (hasCriticalError)
|
|
352
|
+
return;
|
|
353
|
+
for (const key of Object.keys(data)) {
|
|
354
|
+
if (key !== "as" && key !== "id") {
|
|
355
|
+
if (BANNED_MCP_FIELDS.includes(key)) {
|
|
356
|
+
ctx.addIssue({
|
|
357
|
+
code: z.ZodIssueCode.custom,
|
|
358
|
+
message: `has unsupported field "${key}"`,
|
|
359
|
+
path: [key]
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
ctx.addIssue({
|
|
364
|
+
code: z.ZodIssueCode.custom,
|
|
365
|
+
message: `has unknown field "${key}"`,
|
|
366
|
+
path: [key]
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
ctx.addIssue({
|
|
374
|
+
code: z.ZodIssueCode.custom,
|
|
375
|
+
message: 'must be a non-empty string or an object with "as" and "id"'
|
|
376
|
+
});
|
|
377
|
+
}), { message: '"mcps" must be an array' })
|
|
378
|
+
})
|
|
379
|
+
.strict()
|
|
380
|
+
.superRefine((data, ctx) => {
|
|
381
|
+
const handles = new Set();
|
|
382
|
+
data.mcps.forEach((entry, index) => {
|
|
383
|
+
let handle;
|
|
384
|
+
if (typeof entry === "string") {
|
|
385
|
+
handle = entry;
|
|
386
|
+
}
|
|
387
|
+
else if (typeof entry === "object" &&
|
|
388
|
+
entry !== null &&
|
|
389
|
+
!Array.isArray(entry)) {
|
|
390
|
+
handle = entry.as;
|
|
391
|
+
}
|
|
392
|
+
if (typeof handle === "string" && handle.trim().length > 0) {
|
|
393
|
+
if (handles.has(handle)) {
|
|
394
|
+
ctx.addIssue({
|
|
395
|
+
code: z.ZodIssueCode.custom,
|
|
396
|
+
message: `duplicate MCP handle "${handle}"`,
|
|
397
|
+
path: ["mcps", index]
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
handles.add(handle);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|