@oisincoveney/pipeline 3.15.1 → 3.15.3
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/defaults/profiles.yaml +2 -0
- package/dist/argo-graph.js +5 -4
- package/dist/argo-submit.js +1 -1
- package/dist/cli/bootstrap-commands.js +34 -0
- package/dist/cli/loop-commands.js +49 -0
- package/dist/cli/mcp-gateway-commands.js +60 -0
- package/dist/cli/plan-commands.js +156 -0
- package/dist/cli/program.d.ts +1 -26
- package/dist/cli/program.js +39 -592
- package/dist/cli/run-commands.js +58 -0
- package/dist/cli/run-service.d.ts +29 -0
- package/dist/cli/run-service.js +349 -0
- package/dist/commands/ticket/create.js +2 -1
- package/dist/config/schema/catalog.d.ts +11 -0
- package/dist/config/schema/catalog.js +68 -0
- package/dist/config/schema/mcp.js +90 -0
- package/dist/config/schema/reference-validation.js +83 -0
- package/dist/config/schemas.d.ts +13 -20
- package/dist/config/schemas.js +5 -217
- package/dist/config/validate.js +2 -1
- package/dist/{broker-auth.d.ts → credentials/broker.d.ts} +2 -3
- package/dist/credentials/broker.js +34 -0
- package/dist/credentials/codex-config.js +32 -0
- package/dist/credentials/file-targets.js +26 -0
- package/dist/{codex-auth-sync.js → credentials/local-codex-auth-sync.js} +21 -9
- package/dist/credentials/opencode-config.js +65 -0
- package/dist/credentials/runner.js +31 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/install-commands/claude-code.js +1 -1
- package/dist/install-commands/opencode.js +3 -3
- package/dist/install-commands.js +3 -3
- package/dist/install-hooks.js +1 -1
- package/dist/mcp/gateway-config.js +47 -0
- package/dist/mcp/gateway-doctor.js +192 -0
- package/dist/mcp/gateway-error.js +2 -3
- package/dist/mcp/gateway-reconcile.js +54 -0
- package/dist/mcp/gateway-runtime.js +8 -0
- package/dist/mcp/host-config.js +69 -0
- package/dist/mcp/host-renderers.js +48 -0
- package/dist/moka-global-config.js +1 -1
- package/dist/moka-submit.d.ts +7 -7
- package/dist/moka-submit.js +1 -1
- package/dist/pipeline-runtime.d.ts +7 -27
- package/dist/pipeline-runtime.js +11 -869
- package/dist/planning/compile.d.ts +2 -2
- package/dist/planning/compile.js +18 -74
- package/dist/planning/generate.d.ts +20 -20
- package/dist/planning/generate.js +4 -3
- package/dist/planning/graph.d.ts +6 -0
- package/dist/planning/graph.js +108 -1
- package/dist/run-control/command-context.js +15 -0
- package/dist/run-control/commands.js +15 -334
- package/dist/run-control/contracts.js +1 -1
- package/dist/run-control/file-errors.js +6 -0
- package/dist/run-control/logical-segment.js +15 -0
- package/dist/run-control/next-node.js +2 -21
- package/dist/run-control/resume-command.js +39 -0
- package/dist/run-control/run-artifacts-command.js +138 -0
- package/dist/run-control/run-command-domain.js +31 -0
- package/dist/run-control/run-control-store.js +2 -1
- package/dist/run-control/run-query-command.js +129 -0
- package/dist/run-control/runtime-reporter.js +1 -1
- package/dist/run-control/stop-command.js +60 -0
- package/dist/run-control/store-fs-effects.js +48 -0
- package/dist/run-control/store-manifest.js +82 -0
- package/dist/run-control/store-paths.js +47 -0
- package/dist/run-control/store.js +22 -175
- package/dist/run-control/submit-result.js +1 -1
- package/dist/run-state/git-refs.js +28 -10
- package/dist/runner/opencode-excludes.js +23 -0
- package/dist/runner/subprocess-result.js +62 -0
- package/dist/runner/subprocess.js +72 -0
- package/dist/runner/timeouts.js +19 -0
- package/dist/runner-command/lifecycle-context.js +1 -1
- package/dist/runner-command/run.js +1 -1
- package/dist/runner-event-schema.d.ts +10 -10
- package/dist/runner.d.ts +14 -32
- package/dist/runner.js +102 -263
- package/dist/runtime/agent-node/agent-node.js +83 -545
- package/dist/runtime/agent-node/handoff-finalization.js +55 -0
- package/dist/runtime/agent-node/model-selection.js +38 -0
- package/dist/runtime/agent-node/output-finalization.js +263 -0
- package/dist/runtime/agent-node/prompt-rendering.js +302 -0
- package/dist/runtime/agent-node/session-execution.js +48 -0
- package/dist/runtime/builtins/builtins.js +1 -1
- package/dist/runtime/config-error.js +6 -0
- package/dist/runtime/context/context.js +1 -1
- package/dist/runtime/detached-race.js +27 -0
- package/dist/runtime/durable-store/acquisition.js +10 -0
- package/dist/runtime/hooks/hooks.js +1 -1
- package/dist/runtime/journal-acquisition.d.ts +9 -0
- package/dist/runtime/journal-acquisition.js +28 -0
- package/dist/runtime/node-execution.js +560 -0
- package/dist/runtime/opencode-runtime.js +47 -1
- package/dist/runtime/opencode-session-executor.js +2 -7
- package/dist/runtime/runtime-results.js +75 -0
- package/dist/runtime/scheduled-dependencies.d.ts +4 -0
- package/dist/runtime/scheduled-dependencies.js +98 -0
- package/dist/runtime/services/runner-command-io-service.js +1 -1
- package/dist/runtime/workflow-execution.js +84 -0
- package/dist/schedule/backlog-context.js +6 -8
- package/dist/tickets/ticket-graph-dto.js +2 -4
- package/dist/tickets/ticket-graph.d.ts +1 -2
- package/dist/tickets/ticket-graph.js +13 -30
- package/dist/tickets/ticket-selection.js +1 -1
- package/package.json +1 -1
- package/dist/broker-auth.js +0 -173
- package/dist/mcp/gateway.js +0 -386
- package/dist/run-state/opencode-accounts.js +0 -47
package/defaults/profiles.yaml
CHANGED
|
@@ -154,6 +154,7 @@ profiles:
|
|
|
154
154
|
runner: opencode
|
|
155
155
|
scheduling_roles: [implementation]
|
|
156
156
|
description: Add focused failing tests for the requested behavior.
|
|
157
|
+
timeout_ms: 900000
|
|
157
158
|
instructions: { inline: "Add focused failing tests for the requested behavior only. Do not change production code. Only edit files matching test paths such as **/*.test.*, **/*.spec.*, **/*_test.*, **/__tests__/**, test/**, or tests/**. NEVER silence lint, type, complexity, or dead-code findings with suppression comments (no // fallow-ignore, // biome-ignore, eslint-disable, oxlint-disable, @ts-ignore, or @ts-expect-error); fix the underlying cause — if a gate flags your test, restructure the test (e.g. move restricted imports into shared support/fixture helpers) rather than suppressing it. Return only valid JSON with top-level changes and verification. Every changes entry must include summary, why, and files. Include risks, followups, and lessons when present. Do not use Markdown fences or prose outside the JSON object." }
|
|
158
159
|
skills: [test]
|
|
159
160
|
mcp_servers: [pipeline-gateway]
|
|
@@ -168,6 +169,7 @@ profiles:
|
|
|
168
169
|
runner: opencode
|
|
169
170
|
scheduling_roles: [implementation]
|
|
170
171
|
description: Implement production code until the failing tests pass.
|
|
172
|
+
timeout_ms: 900000
|
|
171
173
|
instructions: { inline: "Implement the smallest production change that satisfies the failing tests. NEVER silence lint, type, complexity, or dead-code findings with suppression comments (no // fallow-ignore, // biome-ignore, eslint-disable, oxlint-disable, @ts-ignore, or @ts-expect-error); fix the underlying cause — reduce complexity by extracting helpers, remove genuinely dead code, and migrate off deprecated APIs rather than suppressing the warning. Return only valid JSON with top-level changes and verification. Every changes entry must include summary, why, and files. Include risks, followups, and lessons when present. Do not use Markdown fences or prose outside the JSON object." }
|
|
172
174
|
skills: [trace, test, fix, library-first-development]
|
|
173
175
|
mcp_servers: [pipeline-gateway]
|
package/dist/argo-graph.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { uniqueStrings } from "./strings.js";
|
|
2
2
|
import { resolveExecutableDependencyIds } from "./planning/dependency-refs.js";
|
|
3
|
+
import { terminalDependencyItems } from "./planning/graph.js";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { Data } from "effect";
|
|
5
6
|
//#region src/argo-graph.ts
|
|
@@ -43,10 +44,11 @@ var ArgoGraphCompiler = class {
|
|
|
43
44
|
}
|
|
44
45
|
compile() {
|
|
45
46
|
this.compileNodes(this.plan.topologicalOrder, []);
|
|
47
|
+
const terminalTasks = this.terminalTasks();
|
|
46
48
|
return {
|
|
47
|
-
terminalNodeIds:
|
|
49
|
+
terminalNodeIds: terminalTasks.map((task) => task.nodeId),
|
|
48
50
|
tasks: this.tasks,
|
|
49
|
-
terminalTaskNames:
|
|
51
|
+
terminalTaskNames: terminalTasks.map((task) => task.taskName),
|
|
50
52
|
workflowId: this.plan.workflowId
|
|
51
53
|
};
|
|
52
54
|
}
|
|
@@ -92,8 +94,7 @@ var ArgoGraphCompiler = class {
|
|
|
92
94
|
return uniqueStrings(resolveExecutableDependencyIds(this.nodeById, nodeIds).map((id) => argoTaskName(id)));
|
|
93
95
|
}
|
|
94
96
|
terminalTasks() {
|
|
95
|
-
|
|
96
|
-
return this.tasks.filter((task) => !dependedOn.has(task.taskName));
|
|
97
|
+
return terminalDependencyItems(this.tasks, (task) => task.taskName, (task) => task.dependencies);
|
|
97
98
|
}
|
|
98
99
|
};
|
|
99
100
|
function argoTaskName(nodeId) {
|
package/dist/argo-submit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ArgoGraphCompilerError, compileArgoExecutionGraph } from "./argo-graph.js";
|
|
2
|
-
import { brokerAuthOptionSchema } from "./broker
|
|
2
|
+
import { brokerAuthOptionSchema } from "./credentials/broker.js";
|
|
3
3
|
import { compileScheduleArtifact, parseScheduleArtifact } from "./planning/generate.js";
|
|
4
4
|
import { parseRunnerCommandPayload, runnerCommandPayloadSchema } from "./runner-command-contract.js";
|
|
5
5
|
import { buildRunnerTaskDescriptor } from "./runner-command/task-descriptor.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { formatCodexAuthSyncResult, syncLocalCodexAuth } from "../credentials/local-codex-auth-sync.js";
|
|
2
|
+
import { formatPipelineInitResult, initPipelineProject } from "../pipeline-init.js";
|
|
3
|
+
import { runDoctor } from "./doctor.js";
|
|
4
|
+
import { formatDoctorResult } from "./format.js";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
//#region src/cli/bootstrap-commands.ts
|
|
7
|
+
function registerBootstrapCommands(program) {
|
|
8
|
+
program.command("doctor").description("Check local prerequisites for pipeline init and execution").option("--cluster [namespace]", "also check runner-job Kubernetes prerequisites").option("--json", "print machine-readable readiness results").option("--kube-context <context>", "kubectl context for cluster checks").option("--kubeconfig <path>", "kubeconfig path for cluster checks").action(async (flags) => {
|
|
9
|
+
const result = await runDoctor(process.env.PIPELINE_TARGET_PATH ?? process.cwd(), flags);
|
|
10
|
+
console.log(flags.json ? JSON.stringify(result) : formatDoctorResult(result));
|
|
11
|
+
if (!result.passed) throw new Error("Doctor checks failed.");
|
|
12
|
+
});
|
|
13
|
+
program.command("init").description("Install or refresh package-owned pipeline support: per-machine harness (skills + slash-command adapters + agent hooks + global instruction files) installed globally to ~/.claude, ~/.config/opencode, ~/.codex with no repo-local config").option("--check", "verify the generated harness is current; fail if stale").option("--dry-run", "show planned changes without writing files").option("--force", "overwrite manually edited harness files").action(async (flags) => {
|
|
14
|
+
const result = await initPipelineProject({
|
|
15
|
+
...flags,
|
|
16
|
+
cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd()
|
|
17
|
+
});
|
|
18
|
+
console.log(formatPipelineInitResult(result, {
|
|
19
|
+
check: flags.check,
|
|
20
|
+
dryRun: flags.dryRun
|
|
21
|
+
}));
|
|
22
|
+
});
|
|
23
|
+
program.command("codex-auth").description("Manage local Codex broker auth integration").command("sync-local").description("Point local dev repos' opencode openai provider at the central CLIProxyAPI broker").option("--root <path>", "directory containing repositories to sync").option("--dry-run", "show planned changes without writing files").option("--check", "fail if local Codex auth config is not synced").action((flags) => {
|
|
24
|
+
const result = syncLocalCodexAuth({
|
|
25
|
+
check: flags.check,
|
|
26
|
+
dryRun: flags.dryRun,
|
|
27
|
+
root: resolve(flags.root ?? process.env.PIPELINE_TARGET_PATH ?? process.cwd())
|
|
28
|
+
});
|
|
29
|
+
console.log(formatCodexAuthSyncResult(result));
|
|
30
|
+
if (!result.ok) process.exitCode = 1;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { registerBootstrapCommands };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { loadPipelineConfig } from "../config/load.js";
|
|
2
|
+
import "../config.js";
|
|
3
|
+
import { loadMokaGlobalConfig } from "../moka-global-config.js";
|
|
4
|
+
import { parseLoopFlags, runLoopSubmit } from "../loop/loop-command.js";
|
|
5
|
+
import { runLoopControllerEntrypoint } from "../loop/loop-controller-entrypoint.js";
|
|
6
|
+
import { Option } from "commander";
|
|
7
|
+
//#region src/cli/loop-commands.ts
|
|
8
|
+
function registerLoopCommand(program) {
|
|
9
|
+
program.command("loop").description("Submit a long-running cloud controller that drains the backlog ticket-by-ticket").addOption(new Option("--strategy <strategy>", "ready-ticket selection strategy").choices([
|
|
10
|
+
"priority",
|
|
11
|
+
"bfs",
|
|
12
|
+
"dfs"
|
|
13
|
+
]).default("priority")).option("--root <epic-id>", "restrict traversal to this epic subtree").option("--max-remediation-attempts <n>", "bounded fix-up submits before a PR is declared blocked").option("--merge-timeout <n>", "bounded merge polls before an indeterminate PR is declared blocked").action(async (options) => {
|
|
14
|
+
const result = await runLoopSubmit(buildLoopSubmitInput(options));
|
|
15
|
+
console.log(`Loop controller submitted: ${result.workflowName} in ${result.namespace}`);
|
|
16
|
+
});
|
|
17
|
+
program.command("loop-controller", { hidden: true }).description("Internal in-cluster loop controller process").requiredOption("--payload-file <path>", "Path to the runner payload JSON").addOption(new Option("--strategy <strategy>", "ready-ticket selection strategy").choices([
|
|
18
|
+
"priority",
|
|
19
|
+
"bfs",
|
|
20
|
+
"dfs"
|
|
21
|
+
]).default("priority")).option("--root <epic-id>", "restrict traversal to this epic subtree").option("--max-remediation-attempts <n>", "bounded fix-up submits").option("--merge-timeout <n>", "bounded merge polls").action(async (flags) => {
|
|
22
|
+
await runLoopControllerEntrypoint({
|
|
23
|
+
flags: parseLoopFlags(flags),
|
|
24
|
+
payloadFile: flags.payloadFile,
|
|
25
|
+
worktreePath: process.env.PIPELINE_TARGET_PATH ?? process.cwd()
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function buildLoopSubmitInput(options) {
|
|
30
|
+
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
31
|
+
const config = loadPipelineConfig(cwd, { allowMissingLintFileReferences: true });
|
|
32
|
+
const momokaya = loadMokaGlobalConfig()?.momokaya;
|
|
33
|
+
const brokerAuth = momokaya?.submit.brokerAuth;
|
|
34
|
+
if (!brokerAuth) throw new Error("momokaya.submit.brokerAuth is required for moka loop submit");
|
|
35
|
+
return {
|
|
36
|
+
brokerAuth,
|
|
37
|
+
config,
|
|
38
|
+
eventUrl: momokaya?.submit.eventUrl,
|
|
39
|
+
flags: parseLoopFlags(options),
|
|
40
|
+
gitCredentialsSecretName: momokaya?.submit.gitCredentialsSecretName,
|
|
41
|
+
githubAuthSecretName: momokaya?.submit.githubAuthSecretName,
|
|
42
|
+
kubeconfigPath: momokaya?.kubernetes.kubeconfig,
|
|
43
|
+
namespace: momokaya?.kubernetes.namespace,
|
|
44
|
+
serviceAccountName: momokaya?.submit.serviceAccountName,
|
|
45
|
+
worktreePath: cwd
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { registerLoopCommand };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { loadPipelineConfig } from "../config/load.js";
|
|
2
|
+
import "../config.js";
|
|
3
|
+
import { renderGatewayConfig } from "../mcp/gateway-config.js";
|
|
4
|
+
import { formatDoctorResult } from "./format.js";
|
|
5
|
+
import { runGatewayDoctor } from "../mcp/gateway-doctor.js";
|
|
6
|
+
import { localGatewayStatus, reconcileGateway, startLocalGateway } from "../mcp/gateway-reconcile.js";
|
|
7
|
+
import { configureGatewayHosts } from "../mcp/host-config.js";
|
|
8
|
+
import { Option } from "commander";
|
|
9
|
+
//#region src/cli/mcp-gateway-commands.ts
|
|
10
|
+
function registerMcpGatewayCommands(program) {
|
|
11
|
+
const gatewayCommand = program.command("mcp").description("Manage the hosted-first MCP gateway").command("gateway").description("Inspect and configure the pipeline MCP gateway");
|
|
12
|
+
gatewayCommand.command("doctor").description("Check MCP gateway configuration and legacy direct MCP entries").action(async () => {
|
|
13
|
+
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
14
|
+
const result = await runGatewayDoctor(loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }), cwd);
|
|
15
|
+
console.log(formatDoctorResult(result));
|
|
16
|
+
if (!result.passed) throw new Error("MCP gateway doctor checks failed.");
|
|
17
|
+
});
|
|
18
|
+
gatewayCommand.command("config").description("Print resolved MCP gateway client configuration").action(() => {
|
|
19
|
+
const config = loadPipelineConfig(process.env.PIPELINE_TARGET_PATH ?? process.cwd(), { allowMissingLintFileReferences: true });
|
|
20
|
+
console.log(renderGatewayConfig(config));
|
|
21
|
+
});
|
|
22
|
+
gatewayCommand.command("configure-host").description("Rewrite host MCP config to the singleton pipeline gateway").addOption(new Option("--host <host>", "host config to update").choices(["all", "opencode"]).default("all").argParser(parseGatewayHost)).addOption(new Option("--scope <scope>", "config scope to update").choices(["project", "global"]).default("project").argParser(parseGatewayHostScope)).action((flags) => {
|
|
23
|
+
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
24
|
+
const result = configureGatewayHosts(loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }), {
|
|
25
|
+
cwd,
|
|
26
|
+
host: flags.host ?? "all",
|
|
27
|
+
scope: flags.scope ?? "project"
|
|
28
|
+
});
|
|
29
|
+
console.log(result.map((item) => [`${item.host}: ${item.path}`, item.backupPath ? `backup=${item.backupPath}` : "backup=none"].join(" ")).join("\n"));
|
|
30
|
+
});
|
|
31
|
+
gatewayCommand.command("reconcile").description("Apply the current workspace gateway backend inventory").action(async () => {
|
|
32
|
+
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
33
|
+
const result = await reconcileGateway(loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }), cwd);
|
|
34
|
+
console.log([
|
|
35
|
+
`workspace=${result.workspacePath}`,
|
|
36
|
+
`config=${result.configPath}`,
|
|
37
|
+
`backends=${result.backendCount}`,
|
|
38
|
+
result.readinessFailures.length > 0 ? `readiness_failures=${result.readinessFailures.join("; ")}` : "readiness_failures=none"
|
|
39
|
+
].join("\n"));
|
|
40
|
+
});
|
|
41
|
+
gatewayCommand.command("local-start").description("Start a local ToolHive vMCP gateway for local mode").option("--detach", "reserved for future background startup", false).action(async (flags) => {
|
|
42
|
+
if (flags.detach) throw new Error("Detached local gateway startup is not implemented.");
|
|
43
|
+
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
44
|
+
await startLocalGateway(loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }), cwd);
|
|
45
|
+
});
|
|
46
|
+
gatewayCommand.command("local-status").description("Show local ToolHive MCP server status").action(async () => {
|
|
47
|
+
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
48
|
+
console.log(await localGatewayStatus(cwd));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function parseGatewayHostScope(value) {
|
|
52
|
+
if (value === "project" || value === "global") return value;
|
|
53
|
+
throw new Error("scope must be project or global");
|
|
54
|
+
}
|
|
55
|
+
function parseGatewayHost(value) {
|
|
56
|
+
if (value === "all" || value === "opencode") return value;
|
|
57
|
+
throw new Error("host must be all or opencode");
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { registerMcpGatewayCommands };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { loadPipelineConfig } from "../config/load.js";
|
|
2
|
+
import "../config.js";
|
|
3
|
+
import { compileWorkflowPlan } from "../planning/compile.js";
|
|
4
|
+
import { resolveWorkflowSelection } from "../runtime/context/context.js";
|
|
5
|
+
import "../runtime/context/index.js";
|
|
6
|
+
import { createOrchestratorLaunchPlan, createRunnerLaunchPlan } from "../runner.js";
|
|
7
|
+
import { compileScheduleArtifact, parseScheduleArtifact } from "../planning/generate.js";
|
|
8
|
+
import { formatConfigLintWarning, lintPipelineConfig } from "../config/lint.js";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
//#region src/cli/plan-commands.ts
|
|
11
|
+
function registerPlanCommands(program) {
|
|
12
|
+
program.command("validate").description("Validate package-owned @oisincoveney/pipeline config and compile the workflow plan").option("--entrypoint <entrypoint>", "entrypoint id from package config").option("--schedule <schedule>", "approved schedule YAML to validate").option("--strict", "fail when validation lint warnings are emitted").option("--no-lint", "skip validation lint warnings").option("--workflow <workflow>", "workflow id from package config").action((flags) => runValidateCommand(flags));
|
|
13
|
+
program.command("explain-plan").description("Explain nodes, runners, gates, hooks, and artifacts").option("--entrypoint <entrypoint>", "entrypoint id from package config").option("--schedule <schedule>", "approved schedule YAML to explain").option("--workflow <workflow>", "workflow id from package config").action((flags) => runExplainPlanCommand(flags));
|
|
14
|
+
}
|
|
15
|
+
function runValidateCommand(flags) {
|
|
16
|
+
const context = loadPlanConfigContext();
|
|
17
|
+
const selected = selectWorkflowPlan(context, flags);
|
|
18
|
+
const warnings = lintWarnings(context, flags);
|
|
19
|
+
emitLintWarnings(warnings);
|
|
20
|
+
assertStrictLintPass(flags, warnings);
|
|
21
|
+
console.log(formatValidationResult(selected.plan));
|
|
22
|
+
}
|
|
23
|
+
function runExplainPlanCommand(flags) {
|
|
24
|
+
const context = loadPlanConfigContext();
|
|
25
|
+
const selected = selectWorkflowPlan(context, flags);
|
|
26
|
+
console.log(formatCompiledWorkflowPlan(selected.config, context.cwd, selected.plan));
|
|
27
|
+
}
|
|
28
|
+
function loadPlanConfigContext() {
|
|
29
|
+
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
30
|
+
return {
|
|
31
|
+
config: loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }),
|
|
32
|
+
cwd
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function selectWorkflowPlan(context, flags) {
|
|
36
|
+
if (flags.schedule) return scheduledWorkflowPlan(context, flags.schedule);
|
|
37
|
+
return configuredWorkflowPlan(context, flags);
|
|
38
|
+
}
|
|
39
|
+
function scheduledWorkflowPlan(context, schedulePath) {
|
|
40
|
+
const compiled = compileScheduleArtifact(context.config, parseScheduleArtifact(readFileSync(schedulePath, "utf8"), schedulePath), context.cwd);
|
|
41
|
+
return {
|
|
42
|
+
config: compiled.config,
|
|
43
|
+
plan: compiled.plan
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function configuredWorkflowPlan(context, flags) {
|
|
47
|
+
return {
|
|
48
|
+
config: context.config,
|
|
49
|
+
plan: compileWorkflowPlan(context.config, resolveWorkflowSelection(context.config, flags.workflow, flags.entrypoint))
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function lintWarnings(context, flags) {
|
|
53
|
+
return flags.lint === false ? [] : lintPipelineConfig(context.config, context.cwd);
|
|
54
|
+
}
|
|
55
|
+
function emitLintWarnings(warnings) {
|
|
56
|
+
for (const warning of warnings) console.error(formatConfigLintWarning(warning));
|
|
57
|
+
}
|
|
58
|
+
function assertStrictLintPass(flags, warnings) {
|
|
59
|
+
if (flags.strict && warnings.length > 0) throw new Error(`Validation failed with ${warnings.length} ${warningNoun(warnings.length)}.`);
|
|
60
|
+
}
|
|
61
|
+
function warningNoun(count) {
|
|
62
|
+
return count === 1 ? "warning" : "warnings";
|
|
63
|
+
}
|
|
64
|
+
function formatValidationResult(plan) {
|
|
65
|
+
return `OK: ${plan.workflowId} (${plan.topologicalOrder.length} nodes)`;
|
|
66
|
+
}
|
|
67
|
+
function formatCompiledWorkflowPlan(config, worktreePath, plan) {
|
|
68
|
+
return [
|
|
69
|
+
`Workflow: ${plan.workflowId}`,
|
|
70
|
+
formatOrchestratorPlan(config, worktreePath),
|
|
71
|
+
formatParallelBatches(plan),
|
|
72
|
+
...formatWorkflowNodes(plan, config, worktreePath),
|
|
73
|
+
...formatWorkflowHooks(config, plan.workflowId)
|
|
74
|
+
].join("\n");
|
|
75
|
+
}
|
|
76
|
+
function formatParallelBatches(plan) {
|
|
77
|
+
return `Batches: ${plan.parallelBatches.map(formatParallelBatch).join(" -> ")}`;
|
|
78
|
+
}
|
|
79
|
+
function formatParallelBatch(batch) {
|
|
80
|
+
return `[${batch.map((node) => node.id).join(", ")}]`;
|
|
81
|
+
}
|
|
82
|
+
function formatWorkflowNodes(plan, config, worktreePath) {
|
|
83
|
+
return plan.topologicalOrder.flatMap((node) => formatWorkflowNodeLines(node, config, worktreePath));
|
|
84
|
+
}
|
|
85
|
+
function formatWorkflowNodeLines(node, config, worktreePath) {
|
|
86
|
+
return [formatParallelChildrenLine(node), formatWorkflowPlanNode(node, config, worktreePath)].filter(isNonEmptyString);
|
|
87
|
+
}
|
|
88
|
+
function formatParallelChildrenLine(node) {
|
|
89
|
+
if (node.kind !== "parallel" || !node.children?.length) return "";
|
|
90
|
+
return `${node.id}(parallel: ${node.children.map((child) => child.id).join(", ")})`;
|
|
91
|
+
}
|
|
92
|
+
function formatWorkflowHooks(config, workflowId) {
|
|
93
|
+
const hooks = workflowHookIds(config, workflowId);
|
|
94
|
+
return hooks.length > 0 ? [`Workflow hooks: ${hooks.join(", ")}`] : [];
|
|
95
|
+
}
|
|
96
|
+
function workflowHookIds(config, workflowId) {
|
|
97
|
+
return Object.entries(config.hooks.on).flatMap(([event, bindings]) => bindings.filter((binding) => binding.where?.workflow === workflowId).map((binding) => `${event}:${binding.id}`));
|
|
98
|
+
}
|
|
99
|
+
function formatWorkflowPlanNode(node, config, worktreePath) {
|
|
100
|
+
return [
|
|
101
|
+
`- ${node.id}`,
|
|
102
|
+
`kind=${node.kind}`,
|
|
103
|
+
formatNeeds(node),
|
|
104
|
+
formatRunner(node, config, worktreePath),
|
|
105
|
+
formatGateCount(node),
|
|
106
|
+
formatArtifacts(node)
|
|
107
|
+
].filter(isNonEmptyString).join(" ");
|
|
108
|
+
}
|
|
109
|
+
function formatNeeds(node) {
|
|
110
|
+
return `needs=${node.needs.join(",") || "none"}`;
|
|
111
|
+
}
|
|
112
|
+
function formatRunner(node, config, worktreePath) {
|
|
113
|
+
const launch = runnerLaunchPlan(node, config, worktreePath);
|
|
114
|
+
return launch ? `runner=${launch.runnerId}` : "";
|
|
115
|
+
}
|
|
116
|
+
function runnerLaunchPlan(node, config, worktreePath) {
|
|
117
|
+
if (!node.profile) return null;
|
|
118
|
+
if (!config.profiles[node.profile]) return null;
|
|
119
|
+
return createRunnerLaunchPlan(config, {
|
|
120
|
+
nodeId: node.id,
|
|
121
|
+
profileId: node.profile,
|
|
122
|
+
prompt: "<task>",
|
|
123
|
+
worktreePath
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function formatGateCount(node) {
|
|
127
|
+
return `gates=${node.gates?.length ?? 0}`;
|
|
128
|
+
}
|
|
129
|
+
function formatArtifacts(node) {
|
|
130
|
+
const paths = node.artifacts?.map((artifact) => artifact.path) ?? [];
|
|
131
|
+
return paths.length > 0 ? `artifacts=${paths.join(",")}` : "artifacts=none";
|
|
132
|
+
}
|
|
133
|
+
function formatOrchestratorPlan(config, worktreePath) {
|
|
134
|
+
if (!config.orchestrator) return "Orchestrator: not configured";
|
|
135
|
+
const orchestrator = config.profiles[config.orchestrator.profile];
|
|
136
|
+
return [
|
|
137
|
+
`Orchestrator: runner=${createOrchestratorLaunchPlan(config, {
|
|
138
|
+
nodeId: "orchestrator",
|
|
139
|
+
prompt: "<task>",
|
|
140
|
+
worktreePath
|
|
141
|
+
}).runnerId}`,
|
|
142
|
+
orchestrator.model ? `model=${orchestrator.model}` : "",
|
|
143
|
+
formatList("rules", orchestrator.rules),
|
|
144
|
+
formatList("skills", orchestrator.skills),
|
|
145
|
+
formatList("mcp_servers", orchestrator.mcp_servers),
|
|
146
|
+
formatList("hooks", Object.keys(config.hooks.functions))
|
|
147
|
+
].filter(Boolean).join(" ");
|
|
148
|
+
}
|
|
149
|
+
function formatList(label, items) {
|
|
150
|
+
return items?.length ? `${label}=${items.join(",")}` : "";
|
|
151
|
+
}
|
|
152
|
+
function isNonEmptyString(value) {
|
|
153
|
+
return value.length > 0;
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
export { registerPlanCommands };
|
package/dist/cli/program.d.ts
CHANGED
|
@@ -1,34 +1,9 @@
|
|
|
1
|
-
import { RunEffort, RunMode, RunTarget } from "../run-control/contracts.js";
|
|
2
1
|
import { RunCommand } from "./run-command.js";
|
|
3
2
|
import { TicketCommandOptions } from "../commands/ticket/shared.js";
|
|
4
|
-
import { runPipelineFromConfig } from "../pipeline-runtime.js";
|
|
5
3
|
import { Effect } from "effect";
|
|
6
4
|
import { Command } from "commander";
|
|
7
5
|
|
|
8
6
|
//#region src/cli/program.d.ts
|
|
9
|
-
interface ExecuteOptions {
|
|
10
|
-
entrypoint?: string;
|
|
11
|
-
pipelineRunner?: typeof runPipelineFromConfig;
|
|
12
|
-
runControl?: RunControlOptions;
|
|
13
|
-
runId?: string;
|
|
14
|
-
runStoreMode?: RunStoreMode;
|
|
15
|
-
schedule?: string;
|
|
16
|
-
supervised?: boolean;
|
|
17
|
-
supervisor?: boolean;
|
|
18
|
-
workflow?: string;
|
|
19
|
-
}
|
|
20
|
-
type RunStoreMode = "create" | "reuse";
|
|
21
|
-
interface RunControlOptions {
|
|
22
|
-
effort?: RunEffort;
|
|
23
|
-
mode?: RunMode;
|
|
24
|
-
target?: RunTarget;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Config-driven `execute` entrypoint. Package-owned defaults are the source of
|
|
28
|
-
* truth; repo-local pipeline files are ignored by runtime loading.
|
|
29
|
-
*/
|
|
30
|
-
declare function execute(description: string, options?: ExecuteOptions): Promise<void>;
|
|
31
|
-
declare function quick(description: string, options?: Omit<ExecuteOptions, "entrypoint">): Promise<void>;
|
|
32
7
|
interface CliProgramOptions {
|
|
33
8
|
readonly runCommand?: RunCommand;
|
|
34
9
|
readonly ticketCommand?: TicketCommandOptions;
|
|
@@ -36,4 +11,4 @@ interface CliProgramOptions {
|
|
|
36
11
|
declare function createCliProgram(options?: CliProgramOptions): Command;
|
|
37
12
|
declare function runCli(argv: string[]): Promise<void>;
|
|
38
13
|
//#endregion
|
|
39
|
-
export { createCliProgram,
|
|
14
|
+
export { createCliProgram, runCli };
|