@oisincoveney/pipeline 2.11.1 → 3.0.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/dist/cli/program.js +6 -6
- package/dist/install-commands/shared.js +42 -1
- package/dist/install-commands.js +27 -14
- package/dist/moka-submit.d.ts +6 -6
- package/dist/pipeline-init.js +22 -6
- package/dist/run-control/run-state-lock.js +41 -0
- package/dist/run-control/runtime-reporter.js +2 -1
- package/dist/runner-event-schema.d.ts +6 -6
- package/dist/runtime/builtins/builtins.js +2 -1
- package/package.json +1 -1
package/dist/cli/program.js
CHANGED
|
@@ -330,26 +330,26 @@ function createCliProgram(options = {}) {
|
|
|
330
330
|
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
331
331
|
console.log(await localGatewayStatus(cwd));
|
|
332
332
|
});
|
|
333
|
-
program.command("init").description("Initialize package-owned pipeline support without repo-local config").addOption(new Option$1("--
|
|
333
|
+
program.command("init").description("Initialize package-owned pipeline support without repo-local config").addOption(new Option$1("--scope <scope>", "where to install the harness: global (one per-machine install in ~/.claude, ~/.config/opencode, ~/.codex; inherited by every repo) or project (repo-local copy)").choices(["global", "project"]).default("global")).action(async (flags) => {
|
|
334
334
|
const result = await initPipelineProject({
|
|
335
335
|
cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd(),
|
|
336
|
-
scope: flags.
|
|
336
|
+
scope: flags.scope
|
|
337
337
|
});
|
|
338
338
|
console.log(formatPipelineInitResult(result));
|
|
339
339
|
});
|
|
340
|
-
program.command("refresh-harnesses").description("Force-refresh generated agent harnesses
|
|
340
|
+
program.command("refresh-harnesses").description("Force-refresh generated agent harnesses (global by default; commits owned resource changes only for project scope)").addOption(new Option$1("--scope <scope>", "where to refresh the harness: global (per-machine install) or project (repo-local copy, committed)").choices(["global", "project"]).default("global")).option("--message <message>", "git commit message for refreshed harness changes (project scope only)", "chore: update agent harnesses").action(async (flags) => {
|
|
341
341
|
const result = await refreshAgentHarnesses({
|
|
342
342
|
commitMessage: flags.message,
|
|
343
343
|
cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd(),
|
|
344
|
-
scope: flags.
|
|
344
|
+
scope: flags.scope
|
|
345
345
|
});
|
|
346
346
|
console.log(formatRefreshAgentHarnessesResult(result));
|
|
347
347
|
});
|
|
348
|
-
program.command("install-commands").description("Install generated slash-command adapters
|
|
348
|
+
program.command("install-commands").description("Install generated slash-command adapters (global per-machine by default)").addOption(new Option$1("--host <host>", "host command set to install").choices([
|
|
349
349
|
"all",
|
|
350
350
|
"opencode",
|
|
351
351
|
"claude-code"
|
|
352
|
-
]).default("all").argParser(parseCommandHost)).option("--dry-run", "show planned changes without writing files").option("--check", "fail if generated command files are missing or stale").option("--force", "overwrite manually edited command files").action(async (flags) => {
|
|
352
|
+
]).default("all").argParser(parseCommandHost)).addOption(new Option$1("--scope <scope>", "where to install: global (~/.claude, ~/.config/opencode, ~/.codex) or project (repo-local)").choices(["global", "project"]).default("global")).option("--dry-run", "show planned changes without writing files").option("--check", "fail if generated command files are missing or stale").option("--force", "overwrite manually edited command files").action(async (flags) => {
|
|
353
353
|
const result = await installCommands({
|
|
354
354
|
...flags,
|
|
355
355
|
cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { homedir } from "node:os";
|
|
1
3
|
//#region src/install-commands/shared.ts
|
|
2
4
|
const GENERATED_MARKER = "<!-- Generated by @oisincoveney/pipeline. -->";
|
|
3
5
|
const GENERATED_TS_MARKER = "// Generated by @oisincoveney/pipeline.";
|
|
@@ -16,6 +18,45 @@ const ENTRYPOINT_PATH_PATTERNS = {
|
|
|
16
18
|
"claude-code": [/^\.claude\/commands\/(?:moka-)?([^/]+)\.md$/]
|
|
17
19
|
};
|
|
18
20
|
const COMMAND_HOSTS = ["opencode", "claude-code"];
|
|
21
|
+
const DEFAULT_HARNESS_SCOPE = "global";
|
|
22
|
+
/**
|
|
23
|
+
* The per-machine host config dirs. Each honors the same env var the host tool
|
|
24
|
+
* itself uses to relocate its config (so containers/CI can redirect, and so
|
|
25
|
+
* does our test suite): Claude Code reads `CLAUDE_CONFIG_DIR`, Codex reads
|
|
26
|
+
* `CODEX_HOME`, OpenCode reads `OPENCODE_CONFIG_DIR`/`XDG_CONFIG_HOME`.
|
|
27
|
+
*/
|
|
28
|
+
function claudeGlobalConfigDir() {
|
|
29
|
+
return process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude");
|
|
30
|
+
}
|
|
31
|
+
function codexGlobalConfigDir() {
|
|
32
|
+
return process.env.CODEX_HOME ?? join(homedir(), ".codex");
|
|
33
|
+
}
|
|
34
|
+
function opencodeGlobalConfigDir() {
|
|
35
|
+
return process.env.OPENCODE_CONFIG_DIR ?? join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "opencode");
|
|
36
|
+
}
|
|
37
|
+
function stripPrefix(value, prefix) {
|
|
38
|
+
if (value === prefix) return "";
|
|
39
|
+
return value.startsWith(`${prefix}/`) ? value.slice(prefix.length + 1) : value;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a repo-relative harness path (e.g. ".claude/agents/x.md",
|
|
43
|
+
* ".opencode/opencode.json", "AGENTS.md") to its absolute on-disk target for
|
|
44
|
+
* the given scope.
|
|
45
|
+
*
|
|
46
|
+
* - project: rooted at `cwd` (the repo) — the historical behaviour.
|
|
47
|
+
* - global: rooted at the host-native per-machine config dirs. `.claude/*` →
|
|
48
|
+
* the Claude config dir, `.codex/*` → the Codex config dir, and `.opencode/*`
|
|
49
|
+
* plus bare root files (e.g. AGENTS.md, which OpenCode reads globally) → the
|
|
50
|
+
* OpenCode config dir. The global `.opencode` and `~/.config/opencode`
|
|
51
|
+
* layouts use identical subdir names, so this is a pure prefix rebase.
|
|
52
|
+
*/
|
|
53
|
+
function resolveHarnessTarget(scope, cwd, relPath) {
|
|
54
|
+
if (scope === "project") return join(cwd, relPath);
|
|
55
|
+
const normalized = relPath.replaceAll("\\", "/");
|
|
56
|
+
if (normalized === ".claude" || normalized.startsWith(".claude/")) return join(claudeGlobalConfigDir(), stripPrefix(normalized, ".claude"));
|
|
57
|
+
if (normalized === ".codex" || normalized.startsWith(".codex/")) return join(codexGlobalConfigDir(), stripPrefix(normalized, ".codex"));
|
|
58
|
+
return join(opencodeGlobalConfigDir(), stripPrefix(normalized, ".opencode"));
|
|
59
|
+
}
|
|
19
60
|
function profileEntries(config) {
|
|
20
61
|
return Object.entries(config.profiles).sort(([a], [b]) => a.localeCompare(b));
|
|
21
62
|
}
|
|
@@ -47,4 +88,4 @@ function commandIdForHost(host, entrypointId) {
|
|
|
47
88
|
return entrypointId;
|
|
48
89
|
}
|
|
49
90
|
//#endregion
|
|
50
|
-
export { AGENTS_MD_END, AGENTS_MD_START, CLAUDE_PROJECT_CONFIG_PATH, COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, GENERATED_MARKER, GENERATED_TS_MARKER, GENERATED_YAML_MARKER, OPENCODE_PROJECT_CONFIG_PATH, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, OWNER_YAML_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost, profileEntries };
|
|
91
|
+
export { AGENTS_MD_END, AGENTS_MD_START, CLAUDE_PROJECT_CONFIG_PATH, COMMAND_HOSTS, DEFAULT_HARNESS_SCOPE, ENTRYPOINT_PATH_PATTERNS, GENERATED_MARKER, GENERATED_TS_MARKER, GENERATED_YAML_MARKER, OPENCODE_PROJECT_CONFIG_PATH, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, OWNER_YAML_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost, profileEntries, resolveHarnessTarget };
|
package/dist/install-commands.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadPipelineConfig } from "./config/load.js";
|
|
2
2
|
import "./config.js";
|
|
3
|
-
import { COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, invocationForHost } from "./install-commands/shared.js";
|
|
3
|
+
import { COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, invocationForHost, resolveHarnessTarget } from "./install-commands/shared.js";
|
|
4
4
|
import { opencodeAdapter } from "./install-commands/opencode.js";
|
|
5
5
|
import { claudeCodeAdapter } from "./install-commands/claude-code.js";
|
|
6
6
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
@@ -40,13 +40,24 @@ async function listFiles(root) {
|
|
|
40
40
|
function generatedHostFor(content) {
|
|
41
41
|
return COMMAND_HOSTS.find((host) => content.includes(`<!-- @oisincoveney/pipeline:host=${host} -->`) || content.includes(`// @oisincoveney/pipeline:host=${host}`) || content.includes(`# @oisincoveney/pipeline:host=${host}`));
|
|
42
42
|
}
|
|
43
|
-
async function obsoleteGeneratedItems(cwd, host, wantedPaths) {
|
|
43
|
+
async function obsoleteGeneratedItems(cwd, host, wantedPaths, scope) {
|
|
44
44
|
const hosts = new Set(selectedHosts(host));
|
|
45
45
|
const roots = selectedHosts(host).flatMap((selectedHost) => resourceRootsFor(selectedHost));
|
|
46
|
-
return (await Promise.all(roots.map(
|
|
47
|
-
const
|
|
46
|
+
return (await Promise.all(roots.map(async (root) => {
|
|
47
|
+
const absRoot = resolveHarnessTarget(scope, cwd, root);
|
|
48
|
+
return (await listFiles(absRoot)).map((absolutePath) => ({
|
|
49
|
+
absolutePath,
|
|
50
|
+
path: join(root, relative(absRoot, absolutePath)).replaceAll("\\", "/")
|
|
51
|
+
}));
|
|
52
|
+
}))).flat().flatMap(({ absolutePath, path }) => {
|
|
53
|
+
let content;
|
|
54
|
+
try {
|
|
55
|
+
content = readFileSync(absolutePath, "utf8");
|
|
56
|
+
} catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const generatedHost = generatedHostFor(content);
|
|
48
60
|
if (!(generatedHost && hosts.has(generatedHost))) return [];
|
|
49
|
-
const path = relative(cwd, absolutePath).replaceAll("\\", "/");
|
|
50
61
|
if (wantedPaths.has(path)) return [];
|
|
51
62
|
return [{
|
|
52
63
|
action: "delete",
|
|
@@ -127,8 +138,8 @@ async function writeDefinition(definition, target, content) {
|
|
|
127
138
|
function shouldSkipInstallWrite(options, action) {
|
|
128
139
|
return Boolean(options.check || options.dryRun || action === "unchanged" || action === "conflict");
|
|
129
140
|
}
|
|
130
|
-
async function installDefinition(cwd, definition, options) {
|
|
131
|
-
const target =
|
|
141
|
+
async function installDefinition(cwd, definition, options, scope) {
|
|
142
|
+
const target = resolveHarnessTarget(scope, cwd, definition.path);
|
|
132
143
|
const resolved = resolveDefinitionContent(definition, target);
|
|
133
144
|
const action = installActionForDefinition(definition, target, resolved, Boolean(options.force));
|
|
134
145
|
const item = commandInstallPlanItem(definition, action);
|
|
@@ -146,25 +157,27 @@ function commandInstallPlanItem(definition, action) {
|
|
|
146
157
|
function installCommandsContext(options) {
|
|
147
158
|
const cwd = options.cwd ?? process.cwd();
|
|
148
159
|
const host = options.host ?? "all";
|
|
160
|
+
const scope = options.scope ?? "global";
|
|
149
161
|
const definitions = definitionsFor(host, loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }), cwd);
|
|
150
162
|
return {
|
|
151
163
|
cwd,
|
|
152
164
|
definitions,
|
|
153
165
|
host,
|
|
166
|
+
scope,
|
|
154
167
|
wantedPaths: new Set(definitions.map((definition) => definition.path))
|
|
155
168
|
};
|
|
156
169
|
}
|
|
157
|
-
async function installDefinitions(cwd, definitions, options) {
|
|
170
|
+
async function installDefinitions(cwd, definitions, options, scope) {
|
|
158
171
|
const items = [];
|
|
159
|
-
for (const definition of definitions) items.push(await installDefinition(cwd, definition, options));
|
|
172
|
+
for (const definition of definitions) items.push(await installDefinition(cwd, definition, options, scope));
|
|
160
173
|
return items;
|
|
161
174
|
}
|
|
162
175
|
function shouldRemoveObsoleteItems(options) {
|
|
163
176
|
return !(options.check || options.dryRun);
|
|
164
177
|
}
|
|
165
|
-
async function removeObsoleteItems(cwd, items, options) {
|
|
178
|
+
async function removeObsoleteItems(cwd, items, options, scope) {
|
|
166
179
|
if (!shouldRemoveObsoleteItems(options)) return;
|
|
167
|
-
for (const item of items) await rm(
|
|
180
|
+
for (const item of items) await rm(resolveHarnessTarget(scope, cwd, item.path), { force: true });
|
|
168
181
|
}
|
|
169
182
|
function actionIsConflict(item) {
|
|
170
183
|
return item.action === "conflict";
|
|
@@ -190,10 +203,10 @@ function assertInstallCheckCurrent(options, items) {
|
|
|
190
203
|
}
|
|
191
204
|
async function installCommands(options = {}) {
|
|
192
205
|
const context = installCommandsContext(options);
|
|
193
|
-
const items = await installDefinitions(context.cwd, context.definitions, options);
|
|
194
|
-
const obsoleteItems = await obsoleteGeneratedItems(context.cwd, context.host, context.wantedPaths);
|
|
206
|
+
const items = await installDefinitions(context.cwd, context.definitions, options, context.scope);
|
|
207
|
+
const obsoleteItems = await obsoleteGeneratedItems(context.cwd, context.host, context.wantedPaths, context.scope);
|
|
195
208
|
items.push(...obsoleteItems);
|
|
196
|
-
await removeObsoleteItems(context.cwd, obsoleteItems, options);
|
|
209
|
+
await removeObsoleteItems(context.cwd, obsoleteItems, options, context.scope);
|
|
197
210
|
assertNoInstallConflicts(options, items);
|
|
198
211
|
assertInstallCheckCurrent(options, items);
|
|
199
212
|
return { items };
|
package/dist/moka-submit.d.ts
CHANGED
|
@@ -5,13 +5,13 @@ import { z } from "zod";
|
|
|
5
5
|
//#region src/moka-submit.d.ts
|
|
6
6
|
declare const mokaSubmitDirectHooksSchema: z.ZodRecord<z.ZodEnum<{
|
|
7
7
|
"workflow.start": "workflow.start";
|
|
8
|
+
"node.finish": "node.finish";
|
|
9
|
+
"node.start": "node.start";
|
|
8
10
|
"workflow.success": "workflow.success";
|
|
9
11
|
"workflow.failure": "workflow.failure";
|
|
10
12
|
"workflow.complete": "workflow.complete";
|
|
11
|
-
"node.start": "node.start";
|
|
12
13
|
"node.success": "node.success";
|
|
13
14
|
"node.error": "node.error";
|
|
14
|
-
"node.finish": "node.finish";
|
|
15
15
|
"gate.failure": "gate.failure";
|
|
16
16
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
17
17
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -94,13 +94,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
94
94
|
}, z.core.$strict>>;
|
|
95
95
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
96
96
|
"workflow.start": "workflow.start";
|
|
97
|
+
"node.finish": "node.finish";
|
|
98
|
+
"node.start": "node.start";
|
|
97
99
|
"workflow.success": "workflow.success";
|
|
98
100
|
"workflow.failure": "workflow.failure";
|
|
99
101
|
"workflow.complete": "workflow.complete";
|
|
100
|
-
"node.start": "node.start";
|
|
101
102
|
"node.success": "node.success";
|
|
102
103
|
"node.error": "node.error";
|
|
103
|
-
"node.finish": "node.finish";
|
|
104
104
|
"gate.failure": "gate.failure";
|
|
105
105
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
106
106
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -206,13 +206,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
206
206
|
}, z.core.$strict>>;
|
|
207
207
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
208
208
|
"workflow.start": "workflow.start";
|
|
209
|
+
"node.finish": "node.finish";
|
|
210
|
+
"node.start": "node.start";
|
|
209
211
|
"workflow.success": "workflow.success";
|
|
210
212
|
"workflow.failure": "workflow.failure";
|
|
211
213
|
"workflow.complete": "workflow.complete";
|
|
212
|
-
"node.start": "node.start";
|
|
213
214
|
"node.success": "node.success";
|
|
214
215
|
"node.error": "node.error";
|
|
215
|
-
"node.finish": "node.finish";
|
|
216
216
|
"gate.failure": "gate.failure";
|
|
217
217
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
218
218
|
failure: z.ZodDefault<z.ZodEnum<{
|
package/dist/pipeline-init.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import "./install-commands/shared.js";
|
|
1
2
|
import { installCommands } from "./install-commands.js";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import { execa } from "execa";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
//#region src/pipeline-init.ts
|
|
7
|
+
/**
|
|
8
|
+
* The harness scope drives every generated resource at once: skills, host
|
|
9
|
+
* commands/agents, and config. "global" (default) installs the single
|
|
10
|
+
* per-machine harness (user/global skills + ~/.claude, ~/.config/opencode,
|
|
11
|
+
* ~/.codex host config); "project" vendors everything repo-local.
|
|
12
|
+
*/
|
|
13
|
+
function skillScopeFor(scope) {
|
|
14
|
+
return scope === "global" ? "personal" : "project";
|
|
15
|
+
}
|
|
6
16
|
const DEFAULT_SKILL_INSTALL_SOURCE = "oisin-ee/skills";
|
|
7
17
|
const SKILL_INSTALL_AGENT_ARGS = [
|
|
8
18
|
"--agent",
|
|
@@ -49,13 +59,14 @@ async function installDefaultSkills(cwd, scope) {
|
|
|
49
59
|
}
|
|
50
60
|
async function initPipelineProject(options = {}) {
|
|
51
61
|
const cwd = options.cwd ?? process.cwd();
|
|
52
|
-
const scope = options.scope ?? "
|
|
53
|
-
await (options.skillInstaller ?? ((target) => installDefaultSkills(target, scope)))(cwd);
|
|
62
|
+
const scope = options.scope ?? "global";
|
|
63
|
+
await (options.skillInstaller ?? ((target) => installDefaultSkills(target, skillScopeFor(scope))))(cwd);
|
|
54
64
|
return {
|
|
55
65
|
files: (await installCommands({
|
|
56
66
|
cwd,
|
|
57
67
|
force: true,
|
|
58
|
-
host: "all"
|
|
68
|
+
host: "all",
|
|
69
|
+
scope
|
|
59
70
|
})).items.map((item) => item.path),
|
|
60
71
|
scope
|
|
61
72
|
};
|
|
@@ -67,7 +78,7 @@ async function refreshAgentHarnesses(options = {}) {
|
|
|
67
78
|
scope: options.scope,
|
|
68
79
|
skillInstaller: options.skillInstaller
|
|
69
80
|
});
|
|
70
|
-
const committed = await refreshAgentHarnessesCommitResult(context);
|
|
81
|
+
const committed = init.scope === "project" ? await refreshAgentHarnessesCommitResult(context) : false;
|
|
71
82
|
return {
|
|
72
83
|
...init,
|
|
73
84
|
commitMessage: context.commitMessage,
|
|
@@ -88,13 +99,18 @@ async function refreshAgentHarnessesCommitResult(context) {
|
|
|
88
99
|
function formatPipelineInitResult(result) {
|
|
89
100
|
return [
|
|
90
101
|
"Initialized package-owned pipeline support:",
|
|
91
|
-
result.scope === "
|
|
102
|
+
result.scope === "global" ? "installed the per-machine harness globally (user/global skills + ~/.claude, ~/.config/opencode, ~/.codex); inherited by every repo with no per-repo copy" : "installed default skills and host config repo-local",
|
|
92
103
|
...result.files.map((path) => `generated ${path}`),
|
|
93
104
|
"no repo-local pipeline config files were created"
|
|
94
105
|
].join("\n");
|
|
95
106
|
}
|
|
107
|
+
function refreshCommitSummary(result) {
|
|
108
|
+
if (result.scope === "global") return "global harness refreshed; no repo commit (per-machine install)";
|
|
109
|
+
if (result.committed) return `committed refreshed harnesses: ${result.commitMessage}`;
|
|
110
|
+
return "refreshed harnesses already current; no commit created";
|
|
111
|
+
}
|
|
96
112
|
function formatRefreshAgentHarnessesResult(result) {
|
|
97
|
-
return [formatPipelineInitResult(result), result
|
|
113
|
+
return [formatPipelineInitResult(result), refreshCommitSummary(result)].join("\n");
|
|
98
114
|
}
|
|
99
115
|
async function runCommand(command, args, options) {
|
|
100
116
|
const result = await execa(command, args, {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/run-control/run-state-lock.ts
|
|
2
|
+
/**
|
|
3
|
+
* Process-wide serialization for critical sections that read, write, or
|
|
4
|
+
* temporarily relocate the `.pipeline/runs` run-state directory.
|
|
5
|
+
*
|
|
6
|
+
* The mechanical-check builtins (`lint`, `fallow`) hide `.pipeline/runs` for the
|
|
7
|
+
* duration of their command so those tools do not scan supervisor run-state
|
|
8
|
+
* (see builtins.ts `hidePipelineRunsDirectory`). Under the parallel
|
|
9
|
+
* mechanical-checks fan-out that hide window overlapped the run-control
|
|
10
|
+
* reporter's persistence of sibling node-status events, which then observed a
|
|
11
|
+
* momentarily-missing run directory and failed the whole run with
|
|
12
|
+
* "Run <id> does not exist". This lock makes the hide window and run-state
|
|
13
|
+
* persistence mutually exclusive without serializing any unrelated parallel
|
|
14
|
+
* work: only the run-state critical sections contend for it.
|
|
15
|
+
*/
|
|
16
|
+
let chain = Promise.resolve();
|
|
17
|
+
/**
|
|
18
|
+
* Acquire the lock, resolving with a release function once every previously
|
|
19
|
+
* queued holder has released. Callers MUST invoke the returned release exactly
|
|
20
|
+
* once (use `withRunStateLock` unless you must span non-promise boundaries, as
|
|
21
|
+
* the builtin hide/restore does).
|
|
22
|
+
*/
|
|
23
|
+
function acquireRunStateLock() {
|
|
24
|
+
const previous = chain;
|
|
25
|
+
let release = () => {};
|
|
26
|
+
chain = new Promise((resolve) => {
|
|
27
|
+
release = resolve;
|
|
28
|
+
});
|
|
29
|
+
return previous.then(() => release);
|
|
30
|
+
}
|
|
31
|
+
/** Run `fn` while holding the run-state lock, releasing on success or failure. */
|
|
32
|
+
async function withRunStateLock(fn) {
|
|
33
|
+
const release = await acquireRunStateLock();
|
|
34
|
+
try {
|
|
35
|
+
return await fn();
|
|
36
|
+
} finally {
|
|
37
|
+
release();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { acquireRunStateLock, withRunStateLock };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withRunStateLock } from "./run-state-lock.js";
|
|
1
2
|
import { updateNodeSessionEffect, updateNodeStatusEffect, updateRunStatusEffect } from "./store.js";
|
|
2
3
|
import { Effect } from "effect";
|
|
3
4
|
import { isAbsolute, join } from "node:path";
|
|
@@ -23,7 +24,7 @@ function createRunStoreRuntimeReporterRuntime(input) {
|
|
|
23
24
|
activeHookPreviousStatuses,
|
|
24
25
|
observedNodeStatuses
|
|
25
26
|
});
|
|
26
|
-
writeChain = writeChain.then(() => Effect.runPromise(persistRuntimeEventEffect(input, event, projection, now)));
|
|
27
|
+
writeChain = writeChain.then(() => withRunStateLock(() => Effect.runPromise(persistRuntimeEventEffect(input, event, projection, now))));
|
|
27
28
|
};
|
|
28
29
|
const flushEffect = () => Effect.tryPromise({
|
|
29
30
|
catch: (error) => error,
|
|
@@ -10,8 +10,8 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
10
10
|
at: z.ZodOptional<z.ZodString>;
|
|
11
11
|
sequence: z.ZodNumber;
|
|
12
12
|
type: z.ZodEnum<{
|
|
13
|
-
"workflow.start": "workflow.start";
|
|
14
13
|
"workflow.planned": "workflow.planned";
|
|
14
|
+
"workflow.start": "workflow.start";
|
|
15
15
|
}>;
|
|
16
16
|
workflowPlan: z.ZodObject<{
|
|
17
17
|
edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -55,10 +55,10 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
55
55
|
}>;
|
|
56
56
|
}, z.core.$strip>;
|
|
57
57
|
type: z.ZodEnum<{
|
|
58
|
-
"node.start": "node.start";
|
|
59
|
-
"node.finish": "node.finish";
|
|
60
58
|
"agent.finish": "agent.finish";
|
|
61
59
|
"agent.start": "agent.start";
|
|
60
|
+
"node.finish": "node.finish";
|
|
61
|
+
"node.start": "node.start";
|
|
62
62
|
}>;
|
|
63
63
|
}, z.core.$strip>, z.ZodObject<{
|
|
64
64
|
at: z.ZodOptional<z.ZodString>;
|
|
@@ -180,8 +180,8 @@ declare const runnerEventBatchSchema: z.ZodObject<{
|
|
|
180
180
|
at: z.ZodOptional<z.ZodString>;
|
|
181
181
|
sequence: z.ZodNumber;
|
|
182
182
|
type: z.ZodEnum<{
|
|
183
|
-
"workflow.start": "workflow.start";
|
|
184
183
|
"workflow.planned": "workflow.planned";
|
|
184
|
+
"workflow.start": "workflow.start";
|
|
185
185
|
}>;
|
|
186
186
|
workflowPlan: z.ZodObject<{
|
|
187
187
|
edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -225,10 +225,10 @@ declare const runnerEventBatchSchema: z.ZodObject<{
|
|
|
225
225
|
}>;
|
|
226
226
|
}, z.core.$strip>;
|
|
227
227
|
type: z.ZodEnum<{
|
|
228
|
-
"node.start": "node.start";
|
|
229
|
-
"node.finish": "node.finish";
|
|
230
228
|
"agent.finish": "agent.finish";
|
|
231
229
|
"agent.start": "agent.start";
|
|
230
|
+
"node.finish": "node.finish";
|
|
231
|
+
"node.start": "node.start";
|
|
232
232
|
}>;
|
|
233
233
|
}, z.core.$strip>, z.ZodObject<{
|
|
234
234
|
at: z.ZodOptional<z.ZodString>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseJson } from "../../safe-json.js";
|
|
2
|
+
import { acquireRunStateLock } from "../../run-control/run-state-lock.js";
|
|
2
3
|
import { executeDrainMergeBuiltin } from "../drain-merge/drain-merge.js";
|
|
3
4
|
import "../drain-merge/index.js";
|
|
4
5
|
import { CommandExecutor, CommandExecutorLive } from "../services/command-executor-service.js";
|
|
@@ -95,7 +96,7 @@ function executeVisibleProjectCommand(command, context) {
|
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
98
|
function withHiddenPipelineRuns(worktreePath, effect) {
|
|
98
|
-
return Effect.acquireUseRelease(Effect.sync(() => hidePipelineRunsDirectory(worktreePath)), () => effect, (hiddenRuns) => Effect.sync(() => hiddenRuns?.restore()));
|
|
99
|
+
return Effect.acquireUseRelease(Effect.promise(() => acquireRunStateLock()), () => Effect.acquireUseRelease(Effect.sync(() => hidePipelineRunsDirectory(worktreePath)), () => effect, (hiddenRuns) => Effect.sync(() => hiddenRuns?.restore())), (release) => Effect.sync(() => release()));
|
|
99
100
|
}
|
|
100
101
|
function resolveRequiredScriptCommand(worktreePath, envName, scriptName) {
|
|
101
102
|
return Effect.gen(function* () {
|
package/package.json
CHANGED
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"prepack": "bun run build:cli"
|
|
128
128
|
},
|
|
129
129
|
"type": "module",
|
|
130
|
-
"version": "
|
|
130
|
+
"version": "3.0.0",
|
|
131
131
|
"description": "Config-driven multi-agent pipeline runner for repository work",
|
|
132
132
|
"main": "./dist/index.js",
|
|
133
133
|
"types": "./dist/index.d.ts",
|