@h-rig/core 0.0.6-alpha.17 → 0.0.6-alpha.171
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/src/agent-role-registry.d.ts +4 -0
- package/dist/src/agent-role-registry.js +27 -0
- package/dist/src/authority-paths.d.ts +15 -0
- package/dist/src/authority-paths.js +80 -0
- package/dist/src/baked-secrets.d.ts +6 -0
- package/dist/src/baked-secrets.js +121 -0
- package/dist/src/build-time-config.d.ts +12 -0
- package/dist/src/build-time-config.js +25 -0
- package/dist/src/build-time-config.macro.d.ts +1 -0
- package/dist/src/capability-loaders.d.ts +51 -0
- package/dist/src/capability-loaders.js +758 -0
- package/dist/src/capability.d.ts +79 -0
- package/dist/src/capability.js +63 -0
- package/dist/src/checkout-root.d.ts +1 -0
- package/dist/src/checkout-root.js +30 -0
- package/dist/src/config-env.d.ts +4 -0
- package/dist/src/config-env.js +23 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +44 -0
- package/dist/src/declarative-config.d.ts +14 -0
- package/dist/src/declarative-config.js +82 -0
- package/dist/src/default-kernel.d.ts +1 -0
- package/dist/src/default-kernel.js +12 -0
- package/dist/src/define-config.d.ts +20 -0
- package/dist/src/define-config.js +28 -15
- package/dist/src/define-plugin.d.ts +13 -0
- package/dist/src/define-plugin.js +4 -43
- package/dist/src/embedded-plugins.d.ts +59 -0
- package/dist/src/embedded-plugins.js +22 -0
- package/dist/src/exec.d.ts +13 -0
- package/dist/src/exec.js +101 -0
- package/dist/src/harness-paths.d.ts +18 -0
- package/dist/src/harness-paths.js +141 -0
- package/dist/src/hook-materializer.d.ts +72 -0
- package/dist/src/hook-materializer.js +281 -0
- package/dist/src/hook-protocol.d.ts +2 -0
- package/dist/src/hook-protocol.js +462 -0
- package/dist/src/hook-runner.d.ts +48 -0
- package/dist/src/hook-runner.js +756 -0
- package/dist/src/hook-runtime.d.ts +52 -0
- package/dist/src/hook-runtime.js +462 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +210 -2499
- package/dist/src/json-files.d.ts +9 -0
- package/dist/src/json-files.js +125 -0
- package/dist/src/kernel-boot.d.ts +2 -0
- package/dist/src/kernel-boot.js +10 -0
- package/dist/src/kernel-entrypoint.d.ts +22 -0
- package/dist/src/kernel-entrypoint.js +548 -0
- package/dist/src/kernel-plugin-abi.d.ts +1 -0
- package/dist/src/kernel-plugin-abi.js +1 -0
- package/dist/src/kernel-resolver.d.ts +2 -0
- package/dist/src/kernel-resolver.js +6 -0
- package/dist/src/layout.d.ts +10 -0
- package/dist/src/layout.js +144 -0
- package/dist/src/load-config.d.ts +2 -0
- package/dist/src/load-config.js +423 -30
- package/dist/src/placement.d.ts +50 -0
- package/dist/src/placement.js +996 -0
- package/dist/src/plugin-host-context.d.ts +66 -0
- package/dist/src/plugin-host-context.js +1292 -0
- package/dist/src/plugin-host-registries.d.ts +31 -0
- package/dist/src/plugin-host-registries.js +79 -0
- package/dist/src/plugin-host.d.ts +77 -0
- package/dist/src/plugin-host.js +127 -63
- package/dist/src/plugin-runtime.d.ts +173 -0
- package/dist/src/profile-ops.d.ts +9 -0
- package/dist/src/profile-ops.js +252 -0
- package/dist/src/project-plugins.d.ts +63 -0
- package/dist/src/project-plugins.js +793 -0
- package/dist/src/remote-config.d.ts +183 -0
- package/dist/src/remote-config.js +574 -0
- package/dist/src/root-resolver.d.ts +5 -0
- package/dist/src/root-resolver.js +69 -0
- package/dist/src/run-provisioning.d.ts +58 -0
- package/dist/src/run-provisioning.js +128 -0
- package/dist/src/runtime-context.d.ts +20 -0
- package/dist/src/runtime-context.js +257 -0
- package/dist/src/runtime-events.d.ts +44 -0
- package/dist/src/runtime-events.js +212 -0
- package/dist/src/runtime-overlay.d.ts +11 -0
- package/dist/src/runtime-overlay.js +71 -0
- package/dist/src/runtime-paths.d.ts +21 -0
- package/dist/src/runtime-paths.js +181 -0
- package/dist/src/runtime-provisioning-env.d.ts +5 -0
- package/dist/src/runtime-provisioning-env.js +217 -0
- package/dist/src/runtime-runner-context.d.ts +12 -0
- package/dist/src/runtime-runner-context.js +1 -0
- package/dist/src/safe-identifiers.d.ts +44 -0
- package/dist/src/safe-identifiers.js +96 -0
- package/dist/src/scope-rules.d.ts +4 -0
- package/dist/src/scope-rules.js +21 -0
- package/dist/src/server-paths.d.ts +26 -0
- package/dist/src/server-paths.js +308 -0
- package/dist/src/setup-version.d.ts +3 -0
- package/dist/src/setup-version.js +14 -0
- package/dist/src/task-record-reader.d.ts +3 -0
- package/dist/src/task-record-reader.js +9 -0
- package/dist/src/validator-registry.d.ts +27 -0
- package/dist/src/validator-registry.js +64 -0
- package/package.json +166 -10
- package/dist/src/engineReadModelReducer.js +0 -1780
- package/dist/src/rig-init-builder.js +0 -57
- package/dist/src/rigSelectors.js +0 -293
- package/dist/src/taskGraph.js +0 -64
- package/dist/src/taskGraphCodes.js +0 -26
- package/dist/src/taskGraphLayout.js +0 -374
package/dist/src/exec.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/exec.ts
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
function runCapture(command, cwd, env) {
|
|
5
|
+
const result = Bun.spawnSync(command, {
|
|
6
|
+
cwd,
|
|
7
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
8
|
+
stdout: "pipe",
|
|
9
|
+
stderr: "pipe"
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
exitCode: result.exitCode,
|
|
13
|
+
stdout: result.stdout.toString(),
|
|
14
|
+
stderr: result.stderr.toString()
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
var DEFAULT_VALIDATION_TIMEOUT_MS = 30000;
|
|
18
|
+
function getValidationTimeoutMs() {
|
|
19
|
+
const envVal = process.env.RIG_VALIDATION_TIMEOUT_MS;
|
|
20
|
+
if (envVal) {
|
|
21
|
+
const parsed = Number(envVal);
|
|
22
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
23
|
+
return parsed;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return DEFAULT_VALIDATION_TIMEOUT_MS;
|
|
27
|
+
}
|
|
28
|
+
async function runCaptureAsync(command, cwd, env, timeoutMs) {
|
|
29
|
+
const timeout = timeoutMs ?? getValidationTimeoutMs();
|
|
30
|
+
const proc = Bun.spawn(command, {
|
|
31
|
+
cwd,
|
|
32
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
33
|
+
stdout: "pipe",
|
|
34
|
+
stderr: "pipe"
|
|
35
|
+
});
|
|
36
|
+
const stdoutPromise = new Response(proc.stdout).text();
|
|
37
|
+
const stderrPromise = new Response(proc.stderr).text();
|
|
38
|
+
const timeoutResult = Symbol("timeout");
|
|
39
|
+
const exitOrTimeout = await Promise.race([
|
|
40
|
+
proc.exited,
|
|
41
|
+
Bun.sleep(timeout).then(() => timeoutResult)
|
|
42
|
+
]);
|
|
43
|
+
if (exitOrTimeout === timeoutResult) {
|
|
44
|
+
proc.kill("SIGKILL");
|
|
45
|
+
const [stdout2, stderr2] = await Promise.all([
|
|
46
|
+
Promise.race([stdoutPromise, Bun.sleep(2000).then(() => "")]),
|
|
47
|
+
Promise.race([stderrPromise, Bun.sleep(2000).then(() => "")])
|
|
48
|
+
]);
|
|
49
|
+
return {
|
|
50
|
+
exitCode: 124,
|
|
51
|
+
stdout: stdout2,
|
|
52
|
+
stderr: `${stderr2}
|
|
53
|
+
[TIMEOUT] Process killed after ${timeout}ms`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]);
|
|
57
|
+
const exitCode = exitOrTimeout;
|
|
58
|
+
return { exitCode, stdout, stderr };
|
|
59
|
+
}
|
|
60
|
+
function runInherit(command, cwd, env) {
|
|
61
|
+
const proc = Bun.spawnSync(command, {
|
|
62
|
+
cwd,
|
|
63
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
64
|
+
stdin: "inherit",
|
|
65
|
+
stdout: "inherit",
|
|
66
|
+
stderr: "inherit"
|
|
67
|
+
});
|
|
68
|
+
return proc.exitCode;
|
|
69
|
+
}
|
|
70
|
+
function readJsonFile(path, fallback) {
|
|
71
|
+
if (!existsSync(path)) {
|
|
72
|
+
return fallback;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
76
|
+
} catch {
|
|
77
|
+
return fallback;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function nowIso() {
|
|
81
|
+
return new Date().toISOString();
|
|
82
|
+
}
|
|
83
|
+
function unique(values) {
|
|
84
|
+
return [...new Set(values)];
|
|
85
|
+
}
|
|
86
|
+
function fileLines(path) {
|
|
87
|
+
if (!existsSync(path)) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
return readFileSync(path, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
unique,
|
|
94
|
+
runInherit,
|
|
95
|
+
runCaptureAsync,
|
|
96
|
+
runCapture,
|
|
97
|
+
readJsonFile,
|
|
98
|
+
nowIso,
|
|
99
|
+
getValidationTimeoutMs,
|
|
100
|
+
fileLines
|
|
101
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function resolveHarnessPaths(projectRoot: string): {
|
|
2
|
+
harnessRoot: string;
|
|
3
|
+
stateDir: string;
|
|
4
|
+
artifactsDir: string;
|
|
5
|
+
logsDir: string;
|
|
6
|
+
binDir: string;
|
|
7
|
+
hooksDir: string;
|
|
8
|
+
validationDir: string;
|
|
9
|
+
taskConfigPath: string;
|
|
10
|
+
sessionPath: string;
|
|
11
|
+
monorepoRoot: string;
|
|
12
|
+
tsApiTestsDir: string;
|
|
13
|
+
taskRepoCommitsPath: string;
|
|
14
|
+
baseRepoPinsPath: string;
|
|
15
|
+
failedApproachesPath: string;
|
|
16
|
+
agentProfilePath: string;
|
|
17
|
+
reviewProfilePath: string;
|
|
18
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/harness-paths.ts
|
|
3
|
+
import { resolve as resolve3 } from "path";
|
|
4
|
+
|
|
5
|
+
// packages/core/src/layout.ts
|
|
6
|
+
import { resolve as resolve2 } from "path";
|
|
7
|
+
import {
|
|
8
|
+
RIG_ARTIFACTS_DIRNAME,
|
|
9
|
+
RIG_DEFINITION_DIRNAME,
|
|
10
|
+
RIG_STATE_DIRNAME
|
|
11
|
+
} from "@rig/contracts";
|
|
12
|
+
|
|
13
|
+
// packages/core/src/checkout-root.ts
|
|
14
|
+
import { dirname, resolve } from "path";
|
|
15
|
+
import { existsSync } from "fs";
|
|
16
|
+
function findNearestGitCheckoutRoot(startDir) {
|
|
17
|
+
let current = resolve(startDir);
|
|
18
|
+
for (;; ) {
|
|
19
|
+
if (existsSync(resolve(current, ".git")))
|
|
20
|
+
return current;
|
|
21
|
+
const parent = dirname(current);
|
|
22
|
+
if (parent === current)
|
|
23
|
+
return null;
|
|
24
|
+
current = parent;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function resolveCheckoutRoot(projectRoot) {
|
|
28
|
+
const normalizedProjectRoot = resolve(projectRoot);
|
|
29
|
+
const explicit = process.env.MONOREPO_ROOT?.trim();
|
|
30
|
+
if (explicit) {
|
|
31
|
+
const explicitRoot = resolve(explicit);
|
|
32
|
+
const gitRoot = findNearestGitCheckoutRoot(explicitRoot);
|
|
33
|
+
if (gitRoot)
|
|
34
|
+
return gitRoot;
|
|
35
|
+
throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but no git checkout was found there or above it.`);
|
|
36
|
+
}
|
|
37
|
+
return findNearestGitCheckoutRoot(normalizedProjectRoot) ?? normalizedProjectRoot;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// packages/core/src/layout.ts
|
|
41
|
+
var resolveMonorepoRoot = resolveCheckoutRoot;
|
|
42
|
+
function resolveRuntimeWorkspaceLayout(workspaceDir) {
|
|
43
|
+
const root = resolve2(workspaceDir);
|
|
44
|
+
const rigRoot = resolve2(root, ".rig");
|
|
45
|
+
const logsDir = resolve2(rigRoot, "logs");
|
|
46
|
+
const stateDir = resolve2(rigRoot, "state");
|
|
47
|
+
const runtimeDir = resolve2(rigRoot, "runtime");
|
|
48
|
+
const binDir = resolve2(rigRoot, "bin");
|
|
49
|
+
return {
|
|
50
|
+
workspaceDir: root,
|
|
51
|
+
rigRoot,
|
|
52
|
+
stateDir,
|
|
53
|
+
logsDir,
|
|
54
|
+
artifactsRoot: resolve2(root, RIG_ARTIFACTS_DIRNAME),
|
|
55
|
+
runtimeDir,
|
|
56
|
+
homeDir: resolve2(rigRoot, "home"),
|
|
57
|
+
tmpDir: resolve2(rigRoot, "tmp"),
|
|
58
|
+
cacheDir: resolve2(rigRoot, "cache"),
|
|
59
|
+
sessionDir: resolve2(rigRoot, "session"),
|
|
60
|
+
binDir,
|
|
61
|
+
distDir: resolve2(rigRoot, "dist"),
|
|
62
|
+
pluginBinDir: resolve2(binDir, "plugins"),
|
|
63
|
+
contextPath: resolve2(rigRoot, "runtime-context.json"),
|
|
64
|
+
controlPlaneEventsFile: resolve2(logsDir, "control-plane.events.jsonl")
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function resolveActiveRuntimeWorkspaceRoot(_monorepoRoot) {
|
|
68
|
+
const explicit = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
69
|
+
if (!explicit) {
|
|
70
|
+
throw new Error("No active runtime workspace. Set RIG_TASK_WORKSPACE or provision a task runtime first.");
|
|
71
|
+
}
|
|
72
|
+
return resolve2(explicit);
|
|
73
|
+
}
|
|
74
|
+
function resolveRigLayout(projectRoot) {
|
|
75
|
+
const monorepoRoot = resolveMonorepoRoot(projectRoot);
|
|
76
|
+
const definitionRoot = resolve2(projectRoot, RIG_DEFINITION_DIRNAME);
|
|
77
|
+
const runtimeWorkspaceRoot = resolveActiveRuntimeWorkspaceRoot(monorepoRoot);
|
|
78
|
+
const runtimeLayout = resolveRuntimeWorkspaceLayout(runtimeWorkspaceRoot);
|
|
79
|
+
const policyDir = resolve2(definitionRoot, "policy");
|
|
80
|
+
return {
|
|
81
|
+
projectRoot,
|
|
82
|
+
monorepoRoot,
|
|
83
|
+
definitionRoot,
|
|
84
|
+
runtimeWorkspaceRoot,
|
|
85
|
+
stateRoot: runtimeLayout.rigRoot,
|
|
86
|
+
artifactsRoot: runtimeLayout.artifactsRoot,
|
|
87
|
+
configPath: resolve2(definitionRoot, "config.sh"),
|
|
88
|
+
taskConfigPath: resolve2(runtimeWorkspaceRoot, ".rig", "task-config.json"),
|
|
89
|
+
policyDir,
|
|
90
|
+
policyFile: resolve2(policyDir, "policy.json"),
|
|
91
|
+
pluginsDir: resolve2(definitionRoot, "plugins"),
|
|
92
|
+
hooksDir: resolve2(definitionRoot, "hooks"),
|
|
93
|
+
toolsDir: resolve2(definitionRoot, "tools"),
|
|
94
|
+
templatesDir: resolve2(definitionRoot, "templates"),
|
|
95
|
+
validationDir: resolve2(definitionRoot, "validation"),
|
|
96
|
+
stateDir: runtimeLayout.stateDir,
|
|
97
|
+
logsDir: runtimeLayout.logsDir,
|
|
98
|
+
notificationsDir: resolve2(definitionRoot, "notifications"),
|
|
99
|
+
runtimeDir: runtimeLayout.runtimeDir,
|
|
100
|
+
distDir: runtimeLayout.distDir,
|
|
101
|
+
binDir: runtimeLayout.binDir,
|
|
102
|
+
pluginBinDir: runtimeLayout.pluginBinDir,
|
|
103
|
+
keybindingsPath: resolve2(definitionRoot, "keybindings.json"),
|
|
104
|
+
controlPlaneEventsFile: runtimeLayout.controlPlaneEventsFile
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// packages/core/src/harness-paths.ts
|
|
109
|
+
function resolveHarnessPaths(projectRoot) {
|
|
110
|
+
const hasRuntimeWorkspace = Boolean(process.env.RIG_TASK_WORKSPACE?.trim());
|
|
111
|
+
const monorepoRoot = resolveCheckoutRoot(projectRoot);
|
|
112
|
+
const harnessRoot = resolve3(projectRoot, "rig");
|
|
113
|
+
const stateRoot = resolve3(projectRoot, ".rig");
|
|
114
|
+
const layout = hasRuntimeWorkspace ? resolveRigLayout(projectRoot) : null;
|
|
115
|
+
const stateDir = layout?.stateDir ?? resolve3(stateRoot, "state");
|
|
116
|
+
const logsDir = layout?.logsDir ?? resolve3(stateRoot, "logs");
|
|
117
|
+
const artifactsDir = layout?.artifactsRoot ?? resolve3(monorepoRoot, "artifacts");
|
|
118
|
+
const taskConfigPath = layout?.taskConfigPath ?? resolve3(monorepoRoot, ".rig", "task-config.json");
|
|
119
|
+
const binDir = layout?.binDir ?? resolve3(stateRoot, "bin");
|
|
120
|
+
return {
|
|
121
|
+
harnessRoot,
|
|
122
|
+
stateDir: process.env.RIG_STATE_DIR || stateDir,
|
|
123
|
+
artifactsDir,
|
|
124
|
+
logsDir: process.env.RIG_LOGS_DIR || logsDir,
|
|
125
|
+
binDir,
|
|
126
|
+
hooksDir: resolve3(harnessRoot, "hooks"),
|
|
127
|
+
validationDir: resolve3(harnessRoot, "validation"),
|
|
128
|
+
taskConfigPath,
|
|
129
|
+
sessionPath: process.env.RIG_SESSION_FILE || resolve3(stateRoot, "session", "session.json"),
|
|
130
|
+
monorepoRoot,
|
|
131
|
+
tsApiTestsDir: process.env.TS_API_TESTS_DIR || resolve3(monorepoRoot, "TSAPITests"),
|
|
132
|
+
taskRepoCommitsPath: resolve3(stateDir, "task-repo-commits.json"),
|
|
133
|
+
baseRepoPinsPath: resolve3(stateDir, "base-repo-pins.json"),
|
|
134
|
+
failedApproachesPath: resolve3(stateDir, "failed_approaches.md"),
|
|
135
|
+
agentProfilePath: resolve3(stateDir, "agent-profile.json"),
|
|
136
|
+
reviewProfilePath: resolve3(stateDir, "review-profile.json")
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
resolveHarnessPaths
|
|
141
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { HookEvent, HookRegistration } from "@rig/contracts";
|
|
2
|
+
export interface PluginHookEntry {
|
|
3
|
+
/** The plugin that owns this hook. Used as the `_rigPlugin` marker. */
|
|
4
|
+
pluginName: string;
|
|
5
|
+
/** The plugin's hook registration. Either `command` must be set or the
|
|
6
|
+
* entry must be marked `typed`; hooks with neither (pure metadata) are
|
|
7
|
+
* skipped during materialization. */
|
|
8
|
+
hook: HookRegistration;
|
|
9
|
+
/** True when the plugin ships a typed implementation for this hook via
|
|
10
|
+
* definePlugin runtime fields (`{ hooks: { [id]: fn } }`). The
|
|
11
|
+
* materializer then writes a shim command that routes the invocation
|
|
12
|
+
* through `control-plane/hook-runner.ts` instead of requiring a raw
|
|
13
|
+
* `command` string. */
|
|
14
|
+
typed?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the path to the typed-hook runner entrypoint.
|
|
18
|
+
*
|
|
19
|
+
* Workspace/dev checkouts (and consumer projects with `@rig/runtime` in
|
|
20
|
+
* node_modules of THIS process) resolve the sibling source file directly.
|
|
21
|
+
* When that file isn't on disk (e.g. running from a compiled binary whose
|
|
22
|
+
* modules live under the virtual $bunfs root), fall back to the consumer
|
|
23
|
+
* project's own node_modules via `$CLAUDE_PROJECT_DIR` — Claude Code expands
|
|
24
|
+
* that variable when it runs the hook command.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveHookRunnerPath(): string;
|
|
27
|
+
/**
|
|
28
|
+
* Build the shell shim command for a typed hook. The command invokes the
|
|
29
|
+
* hook-runner, which loads the project's rig config, finds the plugin's
|
|
30
|
+
* typed implementation, and bridges stdin/stdout to the hook protocol.
|
|
31
|
+
* `--event` is informational (the runner trusts the metadata registration);
|
|
32
|
+
* `--plugin`/`--hook`/`--project-root` are the runner's actual inputs.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildTypedHookShimCommand(pluginName: string, hook: HookRegistration, projectRoot: string): string;
|
|
35
|
+
export type SessionHookAdapterResult = {
|
|
36
|
+
readonly adapterId: "claude-code";
|
|
37
|
+
readonly status: "materialized";
|
|
38
|
+
readonly path: string;
|
|
39
|
+
readonly events: readonly HookEvent[];
|
|
40
|
+
} | {
|
|
41
|
+
readonly adapterId: "pi";
|
|
42
|
+
readonly status: "skipped";
|
|
43
|
+
readonly reason: string;
|
|
44
|
+
};
|
|
45
|
+
export interface AgentSessionHookAdapter {
|
|
46
|
+
readonly id: "claude-code" | "pi";
|
|
47
|
+
materialize(projectRoot: string, entries: readonly PluginHookEntry[]): SessionHookAdapterResult;
|
|
48
|
+
}
|
|
49
|
+
export declare function createPiNoopSessionHookAdapter(): AgentSessionHookAdapter;
|
|
50
|
+
export declare function createClaudeCodeSessionHookAdapter(): AgentSessionHookAdapter;
|
|
51
|
+
export type AgentSessionHookAdapterEnv = Record<string, string | undefined>;
|
|
52
|
+
export declare function defaultAgentSessionHookAdapters(env?: AgentSessionHookAdapterEnv): readonly AgentSessionHookAdapter[];
|
|
53
|
+
export declare function materializeSessionHookAdapters(projectRoot: string, entries: readonly PluginHookEntry[], adapters?: readonly AgentSessionHookAdapter[]): readonly SessionHookAdapterResult[];
|
|
54
|
+
/**
|
|
55
|
+
* Materialize plugin-contributed hooks into `.claude/settings.json` via the
|
|
56
|
+
* Claude Code session-hook adapter.
|
|
57
|
+
*
|
|
58
|
+
* Removes any previously-materialized plugin entries (marked with
|
|
59
|
+
* `_rigPlugin`) before re-inserting the current set. Operator-authored
|
|
60
|
+
* entries (without the marker) are preserved untouched. The function is
|
|
61
|
+
* idempotent — running it twice with the same input is a no-op.
|
|
62
|
+
*
|
|
63
|
+
* @returns the absolute path to the settings file that was written.
|
|
64
|
+
*/
|
|
65
|
+
export declare function materializeHooks(projectRoot: string, entries: readonly PluginHookEntry[]): string;
|
|
66
|
+
export type ApplyClaudeCodeSessionHooksOptions = {
|
|
67
|
+
readonly replacePluginOwned?: boolean;
|
|
68
|
+
};
|
|
69
|
+
export declare function applyClaudeCodeSessionHooksToSettings(existing: Record<string, unknown>, entries: readonly PluginHookEntry[], projectRoot: string, options?: ApplyClaudeCodeSessionHooksOptions): {
|
|
70
|
+
readonly settings: Record<string, unknown>;
|
|
71
|
+
readonly events: readonly HookEvent[];
|
|
72
|
+
};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/hook-materializer.ts
|
|
3
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
4
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
5
|
+
|
|
6
|
+
// packages/core/src/hook-runtime.ts
|
|
7
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, writeSync } from "fs";
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME } from "@rig/contracts";
|
|
10
|
+
function bunRuntime() {
|
|
11
|
+
const runtime = globalThis;
|
|
12
|
+
return runtime.Bun;
|
|
13
|
+
}
|
|
14
|
+
function normalizeBuildConfig(value) {
|
|
15
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
|
|
19
|
+
}
|
|
20
|
+
function readBuildConfigForCoreHooks() {
|
|
21
|
+
if (typeof __RIG_BUILD_CONFIG__ !== "undefined") {
|
|
22
|
+
return normalizeBuildConfig(__RIG_BUILD_CONFIG__);
|
|
23
|
+
}
|
|
24
|
+
const raw = process.env.RIG_BUILD_CONFIG_JSON?.trim();
|
|
25
|
+
if (!raw) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return normalizeBuildConfig(JSON.parse(raw));
|
|
30
|
+
} catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
var BUILD_CONFIG = readBuildConfigForCoreHooks();
|
|
35
|
+
var BAKED_PROJECT_ROOT = BUILD_CONFIG.AGENT_PROJECT_ROOT ?? "";
|
|
36
|
+
var BAKED_TASK_ID = BUILD_CONFIG.AGENT_TASK_ID ?? "";
|
|
37
|
+
var BAKED_STATE_DIR = BUILD_CONFIG.AGENT_STATE_DIR ?? "";
|
|
38
|
+
var BAKED_BUN_PATH = BUILD_CONFIG.AGENT_BUN_PATH ?? "";
|
|
39
|
+
var BAKED_TASK_CONFIG = BUILD_CONFIG.AGENT_TASK_CONFIG ?? "";
|
|
40
|
+
var BAKED_POLICY_CONTENT = BUILD_CONFIG.AGENT_POLICY_CONTENT ?? "";
|
|
41
|
+
var BAKED_TASK_SCOPES = BUILD_CONFIG.AGENT_TASK_SCOPES ?? "";
|
|
42
|
+
function normalizeExecutablePath(candidate) {
|
|
43
|
+
if (!candidate) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
const normalized = resolve(candidate);
|
|
47
|
+
if (!existsSync(normalized)) {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return realpathSync(normalized);
|
|
52
|
+
} catch {
|
|
53
|
+
return normalized;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function looksLikeRuntimeGateway(candidate) {
|
|
57
|
+
const normalized = resolve(candidate).replace(/\\/g, "/");
|
|
58
|
+
return normalized.includes("/.rig/bin/") || normalized.endsWith("/rig-shell") || normalized.endsWith("/rig-agent");
|
|
59
|
+
}
|
|
60
|
+
function resolveBunBinaryPath() {
|
|
61
|
+
const explicit = normalizeExecutablePath(process.env.RIG_BUN_PATH?.trim());
|
|
62
|
+
if (explicit) {
|
|
63
|
+
return explicit;
|
|
64
|
+
}
|
|
65
|
+
const bunWhich = bunRuntime()?.which?.("bun");
|
|
66
|
+
const pathBun = normalizeExecutablePath(bunWhich?.trim());
|
|
67
|
+
if (pathBun && !looksLikeRuntimeGateway(pathBun)) {
|
|
68
|
+
return pathBun;
|
|
69
|
+
}
|
|
70
|
+
const home = process.env.HOME?.trim();
|
|
71
|
+
const fallbackCandidates = [
|
|
72
|
+
home ? resolve(home, ".bun/bin/bun") : "",
|
|
73
|
+
"/opt/homebrew/bin/bun",
|
|
74
|
+
"/usr/local/bin/bun",
|
|
75
|
+
"/usr/bin/bun"
|
|
76
|
+
];
|
|
77
|
+
for (const candidate of fallbackCandidates) {
|
|
78
|
+
const normalized = normalizeExecutablePath(candidate);
|
|
79
|
+
if (normalized) {
|
|
80
|
+
return normalized;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const execPath = normalizeExecutablePath(process.execPath?.trim());
|
|
84
|
+
if (execPath && !looksLikeRuntimeGateway(execPath)) {
|
|
85
|
+
return execPath;
|
|
86
|
+
}
|
|
87
|
+
throw new Error("bun not found in PATH");
|
|
88
|
+
}
|
|
89
|
+
function resolveBunCliInvocation() {
|
|
90
|
+
if (BAKED_BUN_PATH) {
|
|
91
|
+
return {
|
|
92
|
+
command: BAKED_BUN_PATH,
|
|
93
|
+
env: {}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (process.env.RIG_BUN_PATH?.trim()) {
|
|
97
|
+
return {
|
|
98
|
+
command: process.env.RIG_BUN_PATH.trim(),
|
|
99
|
+
env: {}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const systemBun = resolveBunBinaryPath();
|
|
104
|
+
return {
|
|
105
|
+
command: systemBun,
|
|
106
|
+
env: {}
|
|
107
|
+
};
|
|
108
|
+
} catch {}
|
|
109
|
+
if (process.execPath?.trim()) {
|
|
110
|
+
return {
|
|
111
|
+
command: process.execPath,
|
|
112
|
+
env: { BUN_BE_BUN: "1" }
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return { command: "bun", env: {} };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// packages/core/src/hook-materializer.ts
|
|
119
|
+
var MARKER_PLUGIN = "_rigPlugin";
|
|
120
|
+
var MARKER_HOOK_ID = "_rigHookId";
|
|
121
|
+
function matcherToString(matcher) {
|
|
122
|
+
if (matcher.kind === "all")
|
|
123
|
+
return;
|
|
124
|
+
if (matcher.kind === "tool")
|
|
125
|
+
return matcher.name;
|
|
126
|
+
return matcher.pattern;
|
|
127
|
+
}
|
|
128
|
+
function isPluginOwned(cmd) {
|
|
129
|
+
return typeof cmd[MARKER_PLUGIN] === "string";
|
|
130
|
+
}
|
|
131
|
+
function shellQuote(value) {
|
|
132
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
133
|
+
}
|
|
134
|
+
function resolveHookRunnerPath() {
|
|
135
|
+
const sibling = resolve2(import.meta.dirname, "hook-runner.ts");
|
|
136
|
+
if (existsSync2(sibling)) {
|
|
137
|
+
return sibling;
|
|
138
|
+
}
|
|
139
|
+
return "$CLAUDE_PROJECT_DIR/node_modules/@rig/core/src/hook-runner.ts";
|
|
140
|
+
}
|
|
141
|
+
function buildTypedHookShimCommand(pluginName, hook, projectRoot) {
|
|
142
|
+
const runnerPath = resolveHookRunnerPath();
|
|
143
|
+
const runnerArg = runnerPath.startsWith("$CLAUDE_PROJECT_DIR") ? `"${runnerPath}"` : shellQuote(runnerPath);
|
|
144
|
+
const bun = resolveBunCliInvocation();
|
|
145
|
+
const envPrefix = Object.entries(bun.env).map(([key, value]) => `${key}=${shellQuote(value)}`).join(" ");
|
|
146
|
+
const parts = [
|
|
147
|
+
envPrefix,
|
|
148
|
+
shellQuote(bun.command),
|
|
149
|
+
runnerArg,
|
|
150
|
+
"--plugin",
|
|
151
|
+
shellQuote(pluginName),
|
|
152
|
+
"--hook",
|
|
153
|
+
shellQuote(hook.id),
|
|
154
|
+
"--event",
|
|
155
|
+
shellQuote(hook.event),
|
|
156
|
+
"--project-root",
|
|
157
|
+
shellQuote(projectRoot)
|
|
158
|
+
].filter(Boolean);
|
|
159
|
+
return parts.join(" ");
|
|
160
|
+
}
|
|
161
|
+
function createPiNoopSessionHookAdapter() {
|
|
162
|
+
return {
|
|
163
|
+
id: "pi",
|
|
164
|
+
materialize() {
|
|
165
|
+
return {
|
|
166
|
+
adapterId: "pi",
|
|
167
|
+
status: "skipped",
|
|
168
|
+
reason: "Pi sessions do not consume Claude Code settings hooks."
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function createClaudeCodeSessionHookAdapter() {
|
|
174
|
+
return {
|
|
175
|
+
id: "claude-code",
|
|
176
|
+
materialize(projectRoot, entries) {
|
|
177
|
+
return writeClaudeCodeHookSettings(projectRoot, entries);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function defaultAgentSessionHookAdapters(env = process.env) {
|
|
182
|
+
if (env.RIG_AGENT_SESSION_HOOK_ADAPTER === "claude-code") {
|
|
183
|
+
return [createClaudeCodeSessionHookAdapter()];
|
|
184
|
+
}
|
|
185
|
+
if (env.RIG_AGENT_SESSION_HOOK_ADAPTER === "pi" || typeof env.PI_CODING_AGENT_SESSION_DIR === "string" && env.PI_CODING_AGENT_SESSION_DIR.trim().length > 0 || env.RIG_RUN_PROCESS === "1") {
|
|
186
|
+
return [createPiNoopSessionHookAdapter()];
|
|
187
|
+
}
|
|
188
|
+
return [createClaudeCodeSessionHookAdapter()];
|
|
189
|
+
}
|
|
190
|
+
function materializeSessionHookAdapters(projectRoot, entries, adapters = [createClaudeCodeSessionHookAdapter()]) {
|
|
191
|
+
return adapters.map((adapter) => adapter.materialize(projectRoot, entries));
|
|
192
|
+
}
|
|
193
|
+
function materializeHooks(projectRoot, entries) {
|
|
194
|
+
const result = createClaudeCodeSessionHookAdapter().materialize(projectRoot, entries);
|
|
195
|
+
return result.status === "materialized" ? result.path : resolve2(projectRoot, ".claude", "settings.json");
|
|
196
|
+
}
|
|
197
|
+
function applyClaudeCodeSessionHooksToSettings(existing, entries, projectRoot, options = {}) {
|
|
198
|
+
const hooks = typeof existing.hooks === "object" && existing.hooks !== null && !Array.isArray(existing.hooks) ? existing.hooks : {};
|
|
199
|
+
const replacePluginOwned = options.replacePluginOwned ?? true;
|
|
200
|
+
if (replacePluginOwned) {
|
|
201
|
+
for (const event of Object.keys(hooks)) {
|
|
202
|
+
const groups = hooks[event] ?? [];
|
|
203
|
+
const cleaned = [];
|
|
204
|
+
for (const group of groups) {
|
|
205
|
+
const operatorHooks = group.hooks.filter((h) => !isPluginOwned(h));
|
|
206
|
+
if (operatorHooks.length > 0) {
|
|
207
|
+
cleaned.push({ ...group, hooks: operatorHooks });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (cleaned.length > 0) {
|
|
211
|
+
hooks[event] = cleaned;
|
|
212
|
+
} else {
|
|
213
|
+
delete hooks[event];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const materializedEvents = new Set;
|
|
218
|
+
for (const { pluginName, hook, typed } of entries) {
|
|
219
|
+
const command = hook.command ?? (typed ? buildTypedHookShimCommand(pluginName, hook, projectRoot) : undefined);
|
|
220
|
+
if (!command) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const event = hook.event;
|
|
224
|
+
materializedEvents.add(event);
|
|
225
|
+
const matcherString = matcherToString(hook.matcher);
|
|
226
|
+
const groups = hooks[event] ??= [];
|
|
227
|
+
let group = groups.find((g) => g.matcher === matcherString);
|
|
228
|
+
if (!group) {
|
|
229
|
+
group = matcherString === undefined ? { hooks: [] } : { matcher: matcherString, hooks: [] };
|
|
230
|
+
groups.push(group);
|
|
231
|
+
}
|
|
232
|
+
const alreadyPresent = group.hooks.some((candidate) => candidate[MARKER_PLUGIN] === pluginName && candidate[MARKER_HOOK_ID] === hook.id);
|
|
233
|
+
if (alreadyPresent) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
group.hooks.push({
|
|
237
|
+
type: "command",
|
|
238
|
+
command,
|
|
239
|
+
[MARKER_PLUGIN]: pluginName,
|
|
240
|
+
[MARKER_HOOK_ID]: hook.id
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
const next = { ...existing };
|
|
244
|
+
if (Object.keys(hooks).length > 0) {
|
|
245
|
+
next.hooks = hooks;
|
|
246
|
+
} else {
|
|
247
|
+
delete next.hooks;
|
|
248
|
+
}
|
|
249
|
+
return { settings: next, events: [...materializedEvents].sort() };
|
|
250
|
+
}
|
|
251
|
+
function writeClaudeCodeHookSettings(projectRoot, entries) {
|
|
252
|
+
const settingsPath = resolve2(projectRoot, ".claude", "settings.json");
|
|
253
|
+
const existing = existsSync2(settingsPath) ? safeReadJson(settingsPath) : {};
|
|
254
|
+
const { settings, events } = applyClaudeCodeSessionHooksToSettings(existing, entries, projectRoot);
|
|
255
|
+
mkdirSync2(dirname(settingsPath), { recursive: true });
|
|
256
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
257
|
+
`, "utf-8");
|
|
258
|
+
return {
|
|
259
|
+
adapterId: "claude-code",
|
|
260
|
+
status: "materialized",
|
|
261
|
+
path: settingsPath,
|
|
262
|
+
events
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function safeReadJson(path) {
|
|
266
|
+
try {
|
|
267
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
268
|
+
} catch {
|
|
269
|
+
return {};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
export {
|
|
273
|
+
resolveHookRunnerPath,
|
|
274
|
+
materializeSessionHookAdapters,
|
|
275
|
+
materializeHooks,
|
|
276
|
+
defaultAgentSessionHookAdapters,
|
|
277
|
+
createPiNoopSessionHookAdapter,
|
|
278
|
+
createClaudeCodeSessionHookAdapter,
|
|
279
|
+
buildTypedHookShimCommand,
|
|
280
|
+
applyClaudeCodeSessionHooksToSettings
|
|
281
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export type { HookInput, ParsedHookInput, TypedHookOptions } from "./hook-runtime";
|
|
2
|
+
export { BAKED_BUN_PATH, BAKED_POLICY_CONTENT, BAKED_PROJECT_ROOT, BAKED_STATE_DIR, BAKED_TASK_CONFIG, BAKED_TASK_ID, BAKED_TASK_SCOPES, block, buildPluginHookContext, escapeRegExp, extractToolFilePaths, hookResultToProtocol, isTestFilePath, readHookInput, resolveBunCli, resolveBunCliInvocation, resolvePolicyContent, resolveProjectRoot, resolveTaskConfig, resolveTaskIdForHook, resolveTaskScopes, runTypedHook, } from "./hook-runtime";
|