harnery 0.31.5 → 0.32.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/commander.d.ts.map +1 -1
- package/dist/commander.js +2 -0
- package/dist/commands/agents.d.ts +0 -9
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +70 -95
- package/dist/commands/browse-session.d.ts +21 -0
- package/dist/commands/browse-session.d.ts.map +1 -0
- package/dist/commands/browse-session.js +157 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +205 -37
- package/dist/commands/checkpoint.js +1 -1
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +4 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +40 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +100 -17
- package/dist/commands/init.d.ts +1 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +16 -5
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -3
- package/dist/core/agents/canonical-emit.d.ts +1 -2
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +1 -2
- package/dist/core/agents/cli.js +123 -88
- package/dist/core/agents/coord-client.d.ts +11 -4
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +27 -9
- package/dist/core/agents/finalization.d.ts +68 -0
- package/dist/core/agents/finalization.d.ts.map +1 -0
- package/dist/core/agents/finalization.js +443 -0
- package/dist/core/agents/git-hook.d.ts +51 -0
- package/dist/core/agents/git-hook.d.ts.map +1 -0
- package/dist/core/agents/git-hook.js +118 -0
- package/dist/core/agents/render/prompt-context.d.ts +6 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +26 -13
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +15 -2
- package/dist/core/agents/rules/claim-conflict.js +3 -3
- package/dist/core/agents/rules/commit-conflict.d.ts +15 -6
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +21 -5
- package/dist/core/agents/rules/stop-hook.d.ts +3 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +59 -23
- package/dist/core/agents/session-events.d.ts +8 -16
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +12 -26
- package/dist/core/agents/state/heartbeat-projector.d.ts +3 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +44 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +32 -5
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +54 -17
- package/dist/core/agents/state/names.d.ts +22 -1
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +40 -2
- package/dist/core/config.d.ts +30 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +74 -11
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +12 -13
- package/dist/core/hooks/adapter/detect.d.ts +2 -4
- package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
- package/dist/core/hooks/adapter/detect.js +4 -14
- package/dist/core/hooks/adapter/output.d.ts +1 -1
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +5 -4
- package/dist/core/hooks/adapter/parse.d.ts +1 -1
- package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
- package/dist/core/hooks/adapter/parse.js +22 -5
- package/dist/core/hooks/adapter/wiring.d.ts +23 -0
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +32 -0
- package/dist/core/hooks/cli.d.ts +1 -2
- package/dist/core/hooks/cli.d.ts.map +1 -1
- package/dist/core/hooks/cli.js +169 -27
- package/dist/core/hooks/codex-wsl-bridge.d.ts +46 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -0
- package/dist/core/hooks/codex-wsl-bridge.js +137 -0
- package/dist/core/hooks/effects/index.d.ts +1 -1
- package/dist/core/hooks/effects/index.js +3 -3
- package/dist/core/hooks/events/schema.d.ts +27 -0
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.d.ts +7 -1
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +15 -0
- package/dist/core/hooks/resolve/transcript.d.ts +44 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +176 -1
- package/dist/core/hooks/session-name-presence.d.ts +30 -0
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -0
- package/dist/core/hooks/session-name-presence.js +43 -0
- package/dist/core/hooks/unsafe-cross-shell.d.ts +13 -0
- package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -0
- package/dist/core/hooks/unsafe-cross-shell.js +151 -0
- package/dist/core/work/state.d.ts +4 -5
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +5 -8
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/proof.d.ts +2 -2
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +2 -2
- package/dist/core/workflow/run-state.d.ts +2 -2
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +2 -2
- package/dist/core/workflow/types.d.ts +3 -3
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/execution.d.ts +2 -2
- package/dist/core/workflow/workspaces/execution.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/execution.js +5 -0
- package/dist/core/workflow/workspaces/index.d.ts +1 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +1 -2
- package/dist/core/workflow/workspaces/paths.d.ts +2 -2
- package/dist/core/workflow/workspaces/types.d.ts +0 -6
- package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/validate.d.ts +0 -2
- package/dist/core/workflow/workspaces/validate.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/validate.js +0 -2
- package/dist/lib/browser/client.d.ts +118 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +435 -6
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +180 -37
- package/dist/lib/browser/index.d.ts +5 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +5 -1
- package/dist/lib/browser/netscape-cookies.d.ts +6 -0
- package/dist/lib/browser/netscape-cookies.d.ts.map +1 -0
- package/dist/lib/browser/netscape-cookies.js +31 -0
- package/dist/lib/browser/proxy.d.ts +24 -0
- package/dist/lib/browser/proxy.d.ts.map +1 -0
- package/dist/lib/browser/proxy.js +84 -0
- package/dist/lib/browser/runts.d.ts +6 -0
- package/dist/lib/browser/runts.d.ts.map +1 -1
- package/dist/lib/browser/runts.js +21 -4
- package/dist/lib/browser/session-control.d.ts +112 -0
- package/dist/lib/browser/session-control.d.ts.map +1 -0
- package/dist/lib/browser/session-control.js +670 -0
- package/dist/lib/docs-links.d.ts +108 -0
- package/dist/lib/docs-links.d.ts.map +1 -0
- package/dist/lib/docs-links.js +555 -0
- package/dist/lib/exec.d.ts +1 -1
- package/dist/lib/exec.js +1 -1
- package/dist/lib/identities/assume.d.ts +4 -2
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +15 -2
- package/dist/lib/instructions/git-hooks.d.ts +75 -0
- package/dist/lib/instructions/git-hooks.d.ts.map +1 -0
- package/dist/lib/instructions/git-hooks.js +238 -0
- package/dist/lib/instructions/splice.d.ts +10 -4
- package/dist/lib/instructions/splice.d.ts.map +1 -1
- package/dist/lib/instructions/splice.js +19 -11
- package/package.json +1 -1
- package/schemas/config.schema.json +32 -0
- package/src/commander.ts +2 -0
- package/src/commands/agents.ts +96 -115
- package/src/commands/browse-session.ts +245 -0
- package/src/commands/browse.ts +287 -44
- package/src/commands/checkpoint.ts +1 -1
- package/src/commands/deinit.ts +7 -1
- package/src/commands/docs.ts +56 -0
- package/src/commands/doctor.ts +100 -18
- package/src/commands/init.ts +17 -9
- package/src/commands/tunnel.ts +2 -5
- package/src/core/agents/canonical-emit.ts +1 -2
- package/src/core/agents/cli.ts +137 -88
- package/src/core/agents/coord-client.ts +38 -9
- package/src/core/agents/finalization.ts +595 -0
- package/src/core/agents/git-hook.ts +126 -0
- package/src/core/agents/render/prompt-context.ts +28 -13
- package/src/core/agents/render/session-context.ts +15 -2
- package/src/core/agents/rules/claim-conflict.ts +3 -3
- package/src/core/agents/rules/commit-conflict.ts +35 -8
- package/src/core/agents/rules/stop-hook.ts +71 -23
- package/src/core/agents/session-events.ts +14 -41
- package/src/core/agents/state/heartbeat-projector.ts +45 -1
- package/src/core/agents/state/heartbeat-writer.ts +78 -16
- package/src/core/agents/state/names.ts +54 -2
- package/src/core/config.ts +92 -12
- package/src/core/governor/planning.ts +17 -12
- package/src/core/hooks/adapter/detect.ts +4 -13
- package/src/core/hooks/adapter/output.ts +9 -4
- package/src/core/hooks/adapter/parse.ts +24 -5
- package/src/core/hooks/adapter/wiring.ts +43 -0
- package/src/core/hooks/cli.ts +201 -23
- package/src/core/hooks/codex-wsl-bridge.ts +188 -0
- package/src/core/hooks/effects/index.ts +5 -5
- package/src/core/hooks/events/schema.ts +27 -0
- package/src/core/hooks/resolve/owner.ts +22 -1
- package/src/core/hooks/resolve/transcript.ts +164 -1
- package/src/core/hooks/session-name-presence.ts +52 -0
- package/src/core/hooks/unsafe-cross-shell.ts +160 -0
- package/src/core/work/state.ts +8 -12
- package/src/core/workflow/engine.ts +2 -2
- package/src/core/workflow/index.ts +0 -1
- package/src/core/workflow/proof.ts +4 -4
- package/src/core/workflow/run-state.ts +5 -5
- package/src/core/workflow/types.ts +3 -3
- package/src/core/workflow/workspaces/execution.ts +11 -4
- package/src/core/workflow/workspaces/index.ts +0 -3
- package/src/core/workflow/workspaces/local-git.ts +1 -2
- package/src/core/workflow/workspaces/paths.ts +2 -2
- package/src/core/workflow/workspaces/types.ts +0 -9
- package/src/core/workflow/workspaces/validate.ts +1 -5
- package/src/lib/browser/client.ts +528 -6
- package/src/lib/browser/geometry.ts +197 -37
- package/src/lib/browser/index.ts +39 -0
- package/src/lib/browser/netscape-cookies.ts +39 -0
- package/src/lib/browser/proxy.ts +105 -0
- package/src/lib/browser/runts.ts +27 -3
- package/src/lib/browser/session-control.ts +892 -0
- package/src/lib/docs-links.ts +674 -0
- package/src/lib/exec.ts +1 -1
- package/src/lib/identities/assume.ts +22 -1
- package/src/lib/instructions/git-hooks.ts +259 -0
- package/src/lib/instructions/splice.ts +41 -11
package/dist/commands/doctor.js
CHANGED
|
@@ -16,9 +16,12 @@ import os from "node:os";
|
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import { BUILTIN_ADAPTER_IDS } from "../core/adapters/index.js";
|
|
18
18
|
import { resolveBinName, ripgrepAutoInstall } from "../core/config.js";
|
|
19
|
-
import {
|
|
19
|
+
import { ADAPTER_SPECS } from "../core/hooks/adapter/events.js";
|
|
20
|
+
import { loadAdapterWiring, summarizeAdapterWiring } from "../core/hooks/adapter/wiring.js";
|
|
21
|
+
import { inspectCodexWslBridge } from "../core/hooks/codex-wsl-bridge.js";
|
|
20
22
|
import { ADAPTER_BINARIES, ADAPTER_INSTALL_HINTS, ADAPTER_LOGIN_HINTS, } from "../core/workflow/adapters.js";
|
|
21
23
|
import { probeBilling } from "../core/workflow/billing.js";
|
|
24
|
+
import { checkGitHooks, gitHooksInstalled } from "../lib/instructions/git-hooks.js";
|
|
22
25
|
import { findRg, installRg, managedRgPath, rgInstallSupported } from "../lib/tools/ripgrep.js";
|
|
23
26
|
export function registerDoctorCommand(program, emit) {
|
|
24
27
|
program
|
|
@@ -65,20 +68,70 @@ export function registerDoctorCommand(program, emit) {
|
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
70
|
export function runChecks() {
|
|
71
|
+
const codexWslBridge = checkCodexWslBridge();
|
|
72
|
+
// Probe the adapter CLIs before the hook check runs, not in output order:
|
|
73
|
+
// an unwired adapter only matters if its CLI is actually installed, and
|
|
74
|
+
// probing once here keeps that join to one spawn per CLI.
|
|
75
|
+
const workflow = BUILTIN_ADAPTER_IDS.map((id) => ({ id, ...checkWorkflowAdapter(id) }));
|
|
76
|
+
const installed = workflow.filter((w) => w.installed).map((w) => w.id);
|
|
68
77
|
return [
|
|
69
78
|
checkNode(),
|
|
70
79
|
checkGit(),
|
|
71
80
|
checkBun(),
|
|
72
81
|
checkRipgrep(),
|
|
73
82
|
checkHarneryDir(),
|
|
74
|
-
checkAdapterHooks(),
|
|
75
|
-
|
|
83
|
+
checkAdapterHooks(installed),
|
|
84
|
+
checkGitHookRegions(),
|
|
85
|
+
...(codexWslBridge ? [codexWslBridge] : []),
|
|
86
|
+
...workflow.map((w) => w.check),
|
|
76
87
|
checkRestic(),
|
|
77
88
|
checkRclone(),
|
|
78
89
|
checkPlaywright(),
|
|
79
90
|
checkPython(),
|
|
80
91
|
];
|
|
81
92
|
}
|
|
93
|
+
function checkGitHookRegions() {
|
|
94
|
+
const root = findCoordProjectRoot();
|
|
95
|
+
if (!root) {
|
|
96
|
+
return { name: "git hooks", severity: "ok", detail: "n/a (no .harnery/ above cwd)" };
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
if (!gitHooksInstalled(root)) {
|
|
100
|
+
const bin = resolveBinName(root);
|
|
101
|
+
return {
|
|
102
|
+
name: "git hooks",
|
|
103
|
+
severity: "warn",
|
|
104
|
+
detail: `not installed - commits are not guarded against peer-claim conflicts and ` +
|
|
105
|
+
`claims never auto-release on commit/checkout; run \`${bin} init\` to install`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const { status, issues } = checkGitHooks(root);
|
|
109
|
+
if (status === "fresh") {
|
|
110
|
+
return { name: "git hooks", severity: "ok", detail: "managed regions current" };
|
|
111
|
+
}
|
|
112
|
+
return { name: "git hooks", severity: "warn", detail: issues.join("; ") };
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
return {
|
|
116
|
+
name: "git hooks",
|
|
117
|
+
severity: "warn",
|
|
118
|
+
detail: `check failed: ${err.message}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function checkCodexWslBridge() {
|
|
123
|
+
const status = inspectCodexWslBridge();
|
|
124
|
+
if (!status)
|
|
125
|
+
return null;
|
|
126
|
+
return {
|
|
127
|
+
name: "codex:WSL bridge",
|
|
128
|
+
severity: status.ok ? "ok" : "warn",
|
|
129
|
+
detail: status.detail,
|
|
130
|
+
hint: status.ok
|
|
131
|
+
? undefined
|
|
132
|
+
: "forward CODEX_THREAD_ID through WSLENV in the Codex shell environment policy, then start a fresh task",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
82
135
|
function checkRipgrep() {
|
|
83
136
|
const found = findRg();
|
|
84
137
|
if (found) {
|
|
@@ -123,6 +176,9 @@ function whichVersion(bin, args = ["--version"]) {
|
|
|
123
176
|
* One workflow spawn target: is the adapter CLI installed, and how will its
|
|
124
177
|
* headless children bill (subscription login vs API key — see billing.ts)?
|
|
125
178
|
* Missing is a warn, not a fail: workflows degrade to the adapters you have.
|
|
179
|
+
*
|
|
180
|
+
* Reports `installed` alongside the check so `checkAdapterHooks` can reuse the
|
|
181
|
+
* probe instead of spawning every adapter CLI a second time.
|
|
126
182
|
*/
|
|
127
183
|
function checkWorkflowAdapter(adapter) {
|
|
128
184
|
const bin = ADAPTER_BINARIES[adapter];
|
|
@@ -130,19 +186,25 @@ function checkWorkflowAdapter(adapter) {
|
|
|
130
186
|
const r = whichVersion(bin);
|
|
131
187
|
if (!r.ok) {
|
|
132
188
|
return {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
189
|
+
check: {
|
|
190
|
+
name,
|
|
191
|
+
severity: "warn",
|
|
192
|
+
detail: `${bin} missing (workflow --adapter ${adapter} unavailable)`,
|
|
193
|
+
hint: `${ADAPTER_INSTALL_HINTS[adapter]} then: ${ADAPTER_LOGIN_HINTS[adapter]}`,
|
|
194
|
+
},
|
|
195
|
+
installed: false,
|
|
137
196
|
};
|
|
138
197
|
}
|
|
139
198
|
const probe = probeBilling(adapter);
|
|
140
199
|
if (probe.login === "absent" && !probe.apiKeyPresent) {
|
|
141
200
|
return {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
201
|
+
check: {
|
|
202
|
+
name,
|
|
203
|
+
severity: "warn",
|
|
204
|
+
detail: `${r.out} — installed, but no stored login or API key detected`,
|
|
205
|
+
hint: ADAPTER_LOGIN_HINTS[adapter],
|
|
206
|
+
},
|
|
207
|
+
installed: true,
|
|
146
208
|
};
|
|
147
209
|
}
|
|
148
210
|
const billing = probe.mode === "subscription"
|
|
@@ -150,7 +212,7 @@ function checkWorkflowAdapter(adapter) {
|
|
|
150
212
|
? "billing: subscription"
|
|
151
213
|
: "billing: subscription (login unverifiable, CLI is the authority)"
|
|
152
214
|
: `billing: ${probe.mode} (${probe.apiKeySource})`;
|
|
153
|
-
return { name, severity: "ok", detail: `${r.out} — ${billing}` };
|
|
215
|
+
return { check: { name, severity: "ok", detail: `${r.out} — ${billing}` }, installed: true };
|
|
154
216
|
}
|
|
155
217
|
function checkNode() {
|
|
156
218
|
const v = process.versions.node;
|
|
@@ -289,13 +351,21 @@ function checkHarneryDir() {
|
|
|
289
351
|
* loadAdapterWiring — so a bare settings file never false-warns. The remedy is
|
|
290
352
|
* always the same: re-run `<bin> init` (idempotent, additive).
|
|
291
353
|
*/
|
|
292
|
-
function checkAdapterHooks() {
|
|
354
|
+
function checkAdapterHooks(installedAdapters = []) {
|
|
293
355
|
const root = findCoordProjectRoot();
|
|
294
356
|
if (!root) {
|
|
295
357
|
return { name: "adapter hooks", severity: "ok", detail: "n/a (no .harnery/ above cwd)" };
|
|
296
358
|
}
|
|
297
359
|
const drift = loadAdapterWiring(root);
|
|
298
|
-
|
|
360
|
+
// A adapter with zero harnery hooks is only worth flagging when the project
|
|
361
|
+
// plainly uses harnery hooks (something else is wired) AND that adapter's CLI
|
|
362
|
+
// is installed. Both conditions matter: the first keeps a project that has
|
|
363
|
+
// never run init quiet, the second keeps a project that simply doesn't have
|
|
364
|
+
// Codex quiet. What's left is the case that reads as healthy but isn't — an
|
|
365
|
+
// agent can start a session through that adapter and register nothing.
|
|
366
|
+
const summary = summarizeAdapterWiring(root);
|
|
367
|
+
const unwired = summary.wired.length > 0 ? summary.unwired.filter((id) => installedAdapters.includes(id)) : [];
|
|
368
|
+
if (drift.length === 0 && unwired.length === 0) {
|
|
299
369
|
return { name: "adapter hooks", severity: "ok", detail: "wired + current" };
|
|
300
370
|
}
|
|
301
371
|
const bin = resolveBinName(root);
|
|
@@ -316,10 +386,23 @@ function checkAdapterHooks() {
|
|
|
316
386
|
}
|
|
317
387
|
return `${d.settingsFile}: ${bits.join("; ")}`;
|
|
318
388
|
});
|
|
389
|
+
for (const id of unwired) {
|
|
390
|
+
parts.push(`${ADAPTER_SPECS[id].settingsFile}: ${id} CLI installed, no harnery hooks wired`);
|
|
391
|
+
}
|
|
319
392
|
const needsManualRepair = drift.some((d) => d.parseError || d.invalidTopLevelKeys.length > 0 || d.invalidEventKeys.length > 0);
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
393
|
+
const wireHints = unwired.map((id) => `\`${bin} init --adapter ${id}\``);
|
|
394
|
+
let hint;
|
|
395
|
+
if (needsManualRepair) {
|
|
396
|
+
hint = `repair the invalid adapter settings, then run \`${bin} init\` to migrate harnery hooks`;
|
|
397
|
+
}
|
|
398
|
+
else if (drift.length > 0) {
|
|
399
|
+
hint = `run \`${bin} init\` to migrate the hook set (idempotent)`;
|
|
400
|
+
if (wireHints.length > 0)
|
|
401
|
+
hint += `; ${wireHints.join(" and ")} to wire the rest`;
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
hint = `run ${wireHints.join(" and ")} (idempotent, additive)`;
|
|
405
|
+
}
|
|
323
406
|
return { name: "adapter hooks", severity: "warn", detail: parts.join(" | "), hint };
|
|
324
407
|
}
|
|
325
408
|
function macOrLinux(mac, linux) {
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
import type { Command } from "commander";
|
|
19
19
|
import type { EmitContext } from "../commander.js";
|
|
20
20
|
import { type AdapterId, type AdapterSpec } from "../core/hooks/adapter/events.js";
|
|
21
|
-
import { type
|
|
22
|
-
export type { HookGroup, SettingsFile };
|
|
21
|
+
import { type SettingsFile } from "../core/hooks/adapter/wiring.js";
|
|
23
22
|
export declare function registerInitCommand(program: Command, emit: EmitContext, binName?: string): void;
|
|
24
23
|
/**
|
|
25
24
|
* Merge agent-hook entries into a adapter settings object in place, idempotently.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAClG,OAAO,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAClG,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AAmBzC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAuJ/F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,WAAW,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,SAAS,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAqCvE;AAyDD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAmB1F;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAmDhG;AASD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkCxF"}
|
package/dist/commands/init.js
CHANGED
|
@@ -23,6 +23,7 @@ import { DEFAULT_BIN_NAME, pinnedBinName, stripJsonComments } from "../core/conf
|
|
|
23
23
|
import { ADAPTER_SPECS } from "../core/hooks/adapter/events.js";
|
|
24
24
|
import { commandWiresSubcommand, groupCommands, makeEntry, } from "../core/hooks/adapter/wiring.js";
|
|
25
25
|
import { applyInstructions, checkInstructions, } from "../lib/instructions/apply.js";
|
|
26
|
+
import { applyGitHooks, checkGitHooks } from "../lib/instructions/git-hooks.js";
|
|
26
27
|
import { HostAddendumError } from "../lib/instructions/host-addendum.js";
|
|
27
28
|
// This file is src/commands/init.ts → harnery package root is two levels up.
|
|
28
29
|
const HARNERY_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
@@ -55,14 +56,18 @@ export function registerInitCommand(program, emit, binName) {
|
|
|
55
56
|
// ── --check: read-only drift report on the block + skills ──────────────
|
|
56
57
|
if (opts.check === true) {
|
|
57
58
|
const { status, issues } = checkInstructions(projectRoot, { binName: bin, adapter });
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
const gitHooks = checkGitHooks(projectRoot);
|
|
60
|
+
if (gitHooks.status !== "fresh")
|
|
61
|
+
issues.push(...gitHooks.issues);
|
|
62
|
+
const merged = status === "error" ? "error" : gitHooks.status !== "fresh" ? "drift" : status;
|
|
63
|
+
const head = merged === "fresh"
|
|
64
|
+
? "harn init --check: instructions + skills + git hooks are current"
|
|
65
|
+
: merged === "drift"
|
|
61
66
|
? "harn init --check: drift found (re-run `init` to refresh)"
|
|
62
67
|
: "harn init --check: error";
|
|
63
68
|
const lines = issues.length ? `\n${issues.map((i) => ` ✗ ${i}`).join("\n")}` : "";
|
|
64
69
|
emit.text(`${head}${lines}`);
|
|
65
|
-
emit.setExitCode(
|
|
70
|
+
emit.setExitCode(merged === "fresh" ? 0 : merged === "drift" ? 2 : 1);
|
|
66
71
|
return;
|
|
67
72
|
}
|
|
68
73
|
const dryRun = opts.dryRun === true;
|
|
@@ -152,7 +157,13 @@ export function registerInitCommand(program, emit, binName) {
|
|
|
152
157
|
return;
|
|
153
158
|
}
|
|
154
159
|
actions.push(...applied.actions);
|
|
155
|
-
|
|
160
|
+
// ── 4. git-hook managed regions ────────────────────────────────────────
|
|
161
|
+
// Same lifecycle contract as the instructions block: the coordination
|
|
162
|
+
// content of the consumer's git hooks is machine-owned and versioned;
|
|
163
|
+
// everything else in those files belongs to the host.
|
|
164
|
+
const gitHooks = applyGitHooks(projectRoot, { dryRun });
|
|
165
|
+
actions.push(...gitHooks.actions);
|
|
166
|
+
emit.text(render(projectRoot, dryRun, actions, [...applied.warnings, ...gitHooks.warnings]));
|
|
156
167
|
});
|
|
157
168
|
}
|
|
158
169
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/commands/tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAa1E,OAAO,EAYL,KAAK,WAAW,EAGjB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/commands/tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAa1E,OAAO,EAYL,KAAK,WAAW,EAGjB,MAAM,wBAAwB,CAAC;AA8rBhC;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAsE7F;AA+MD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,YAAY,CAAC,EAAE,qBAAqB,GACnC,IAAI,CA2EN"}
|
package/dist/commands/tunnel.js
CHANGED
|
@@ -763,7 +763,7 @@ function status(opts) {
|
|
|
763
763
|
return;
|
|
764
764
|
}
|
|
765
765
|
if (states.length === 1) {
|
|
766
|
-
//
|
|
766
|
+
// A single tunnel is clearer as a detail block than as a one-row table.
|
|
767
767
|
statusDetail(states[0]);
|
|
768
768
|
return;
|
|
769
769
|
}
|
|
@@ -794,7 +794,7 @@ function logs(opts) {
|
|
|
794
794
|
const name = resolveName(opts.name);
|
|
795
795
|
const state = readState(name);
|
|
796
796
|
const provider = state?.provider ?? "cloudflare";
|
|
797
|
-
const which = opts.
|
|
797
|
+
const which = opts.provider ? providerLogFile(name, provider) : gateLogFile(name);
|
|
798
798
|
const path = cachePath("tunnel", which);
|
|
799
799
|
if (!existsSync(path)) {
|
|
800
800
|
emit.error({ code: "tunnel_no_log", message: `No log file at ${path}` });
|
|
@@ -898,7 +898,6 @@ export function registerTunnelCommand(program, emitParam, contextParam) {
|
|
|
898
898
|
.option("-f, --follow", "follow the log")
|
|
899
899
|
.option("--gate", "tail the gate log (default)")
|
|
900
900
|
.option("--provider", "tail the provider log instead")
|
|
901
|
-
.option("--cloudflared", "tail the Cloudflare provider log instead (legacy alias)")
|
|
902
901
|
.action(logs);
|
|
903
902
|
const allow = cmd
|
|
904
903
|
.command("allow")
|
|
@@ -30,8 +30,7 @@ export interface CanonicalEmitInput {
|
|
|
30
30
|
export declare function resolveEmitRoot(start?: string): string | null;
|
|
31
31
|
export declare function emitCanonical(input: CanonicalEmitInput): void;
|
|
32
32
|
/**
|
|
33
|
-
* Normalize the heartbeat's `platform` field
|
|
34
|
-
* `claude_code`) to the canonical kebab-case Adapter type.
|
|
33
|
+
* Normalize the heartbeat's `platform` field to the canonical Adapter type.
|
|
35
34
|
*/
|
|
36
35
|
export declare function normalizeAdapter(platform: string | undefined): "claude-code" | "cursor" | "codex";
|
|
37
36
|
//# sourceMappingURL=canonical-emit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical-emit.d.ts","sourceRoot":"","sources":["../../../src/core/agents/canonical-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAsC7D;AAED
|
|
1
|
+
{"version":3,"file":"canonical-emit.d.ts","sourceRoot":"","sources":["../../../src/core/agents/canonical-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAsC7D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,CAIjG"}
|
|
@@ -69,8 +69,7 @@ export function emitCanonical(input) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
* Normalize the heartbeat's `platform` field
|
|
73
|
-
* `claude_code`) to the canonical kebab-case Adapter type.
|
|
72
|
+
* Normalize the heartbeat's `platform` field to the canonical Adapter type.
|
|
74
73
|
*/
|
|
75
74
|
export function normalizeAdapter(platform) {
|
|
76
75
|
if (platform === "cursor")
|
package/dist/core/agents/cli.js
CHANGED
|
@@ -91,24 +91,11 @@ async function handleVerdict(root) {
|
|
|
91
91
|
const { evaluateClaim } = await import("./rules/claim-conflict.js");
|
|
92
92
|
verdict = evaluateClaim(root, parsed);
|
|
93
93
|
}
|
|
94
|
-
else if (parsed.rule === "commit") {
|
|
95
|
-
const { evaluateCommit } = await import("./rules/commit-conflict.js");
|
|
96
|
-
const result = evaluateCommit(root, parsed);
|
|
97
|
-
// Map CommitVerdictResult → the standard verdict envelope, stash details
|
|
98
|
-
// on extra fields so the bash caller can pull conflicts + log_lines.
|
|
99
|
-
verdict = {
|
|
100
|
-
allow: result.allow,
|
|
101
|
-
exit_code: result.exit_code,
|
|
102
|
-
rule: result.rule,
|
|
103
|
-
reason: result.message,
|
|
104
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
105
|
-
};
|
|
106
|
-
verdict.conflicts = result.conflicts;
|
|
107
|
-
verdict.log_lines = result.log_lines;
|
|
108
|
-
verdict.suppressed_self_attribution =
|
|
109
|
-
result.suppressed_self_attribution ?? false;
|
|
110
|
-
}
|
|
111
94
|
else {
|
|
95
|
+
// NB: rule === "commit" was served here until 2026-08-09; the commit
|
|
96
|
+
// guard now enters through `git-hook pre-commit`, which runs the same
|
|
97
|
+
// evaluateCommit in-process. A straggler host lands in this fail-open
|
|
98
|
+
// branch, which is the pre-migration behavior for a malformed request.
|
|
112
99
|
verdict = {
|
|
113
100
|
allow: true,
|
|
114
101
|
exit_code: 0,
|
|
@@ -233,13 +220,6 @@ async function handleStateAction(root, action, rest) {
|
|
|
233
220
|
process.stdout.write(`${JSON.stringify({ instance_id: owner, task: hb.task ?? null, cleared: !task })}\n`);
|
|
234
221
|
return 0;
|
|
235
222
|
}
|
|
236
|
-
case "stamp-status-call": {
|
|
237
|
-
const hb = writer.stampStatusCheck(root, owner);
|
|
238
|
-
if (!hb)
|
|
239
|
-
return 1;
|
|
240
|
-
process.stdout.write(`${JSON.stringify({ instance_id: owner, last_status_at: hb.last_status_at })}\n`);
|
|
241
|
-
return 0;
|
|
242
|
-
}
|
|
243
223
|
case "set-turn-summary": {
|
|
244
224
|
const summary = args.join(" ");
|
|
245
225
|
const hb = writer.setTurnSummary(root, owner, summary);
|
|
@@ -313,10 +293,13 @@ async function handleStateAction(root, action, rest) {
|
|
|
313
293
|
// fragility. Positionals (sessionId, model) stay as-is once flags are
|
|
314
294
|
// filtered out.
|
|
315
295
|
const adapter = args.find((a) => a.startsWith("--adapter="))?.slice("--adapter=".length);
|
|
296
|
+
const forkedFrom = args
|
|
297
|
+
.find((a) => a.startsWith("--forked-from="))
|
|
298
|
+
?.slice("--forked-from=".length);
|
|
316
299
|
const positional = args.filter((a) => !a.startsWith("--"));
|
|
317
300
|
const sessionId = positional[0];
|
|
318
301
|
const model = positional[1];
|
|
319
|
-
const hb = writer.healHeartbeat(root, owner, sessionId, model, adapter);
|
|
302
|
+
const hb = writer.healHeartbeat(root, owner, sessionId, model, adapter, forkedFrom ? { forkedFrom } : undefined);
|
|
320
303
|
process.stdout.write(`${JSON.stringify({ instance_id: owner, recreated: !!hb })}\n`);
|
|
321
304
|
return hb ? 0 : 1;
|
|
322
305
|
}
|
|
@@ -450,70 +433,35 @@ async function handleCouncilAction(root, action, rest) {
|
|
|
450
433
|
}
|
|
451
434
|
async function handleAssignName(root, rest) {
|
|
452
435
|
const { assignName } = await import("./state/names.js");
|
|
453
|
-
|
|
436
|
+
// Optional recorded-fork-lineage flag: --forked-from <parent_instance_id>.
|
|
437
|
+
// Supplied by an adapter layer that knows (or detected) that this session
|
|
438
|
+
// was branched from another conversation. Inert on resume: assignName only
|
|
439
|
+
// stamps lineage on the row that first assigns the instance.
|
|
440
|
+
let forkedFrom;
|
|
441
|
+
const positional = [];
|
|
442
|
+
for (let i = 0; i < rest.length; i++) {
|
|
443
|
+
if (rest[i] === "--forked-from") {
|
|
444
|
+
forkedFrom = rest[++i];
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
positional.push(rest[i]);
|
|
448
|
+
}
|
|
449
|
+
const [owner, kindArg] = positional;
|
|
454
450
|
if (!owner || !kindArg) {
|
|
455
|
-
process.stderr.write("agent-coord assign-name <instance_id> <session|subagent|transient
|
|
451
|
+
process.stderr.write("agent-coord assign-name <instance_id> <session|subagent|transient> [--forked-from <instance_id>]\n");
|
|
456
452
|
return 2;
|
|
457
453
|
}
|
|
458
454
|
if (kindArg !== "session" && kindArg !== "subagent" && kindArg !== "transient") {
|
|
459
455
|
process.stderr.write(`agent-coord assign-name: invalid kind ${kindArg}\n`);
|
|
460
456
|
return 2;
|
|
461
457
|
}
|
|
462
|
-
const name = assignName(root, owner, kindArg);
|
|
463
|
-
process.stdout.write(`${JSON.stringify({
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
try {
|
|
470
|
-
req = JSON.parse(raw);
|
|
471
|
-
}
|
|
472
|
-
catch {
|
|
473
|
-
return 0;
|
|
474
|
-
}
|
|
475
|
-
const { groupUnclaim } = await import("./state/heartbeat-writer.js");
|
|
476
|
-
// Session-group-wide unclaim. `owner` is the parent's session_id which is
|
|
477
|
-
// also the group key (parent + subagents share session_id). Each actual
|
|
478
|
-
// removal also emits a durable claim.release event — the projector rebuilds
|
|
479
|
-
// files_touched from the permanent Edit/Write events, so a file-only prune
|
|
480
|
-
// would resurrect on the next replay.
|
|
481
|
-
if (req.owner && Array.isArray(req.prune)) {
|
|
482
|
-
for (const path of req.prune) {
|
|
483
|
-
try {
|
|
484
|
-
for (const hit of groupUnclaim(root, req.owner, path)) {
|
|
485
|
-
await emitClaimRelease(root, hit.instance_id, hit, path, "commit");
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
catch {
|
|
489
|
-
/* best-effort */
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return 0;
|
|
494
|
-
}
|
|
495
|
-
async function handlePostCheckout(root, _rest) {
|
|
496
|
-
const raw = await readStdin();
|
|
497
|
-
let req = {};
|
|
498
|
-
try {
|
|
499
|
-
req = JSON.parse(raw);
|
|
500
|
-
}
|
|
501
|
-
catch {
|
|
502
|
-
return 0;
|
|
503
|
-
}
|
|
504
|
-
const { groupUnclaim } = await import("./state/heartbeat-writer.js");
|
|
505
|
-
if (req.owner && Array.isArray(req.removed)) {
|
|
506
|
-
for (const path of req.removed) {
|
|
507
|
-
try {
|
|
508
|
-
for (const hit of groupUnclaim(root, req.owner, path)) {
|
|
509
|
-
await emitClaimRelease(root, hit.instance_id, hit, path, "checkout");
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
catch {
|
|
513
|
-
/* best-effort */
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
458
|
+
const name = assignName(root, owner, kindArg, forkedFrom ? { forkedFrom } : undefined);
|
|
459
|
+
process.stdout.write(`${JSON.stringify({
|
|
460
|
+
instance_id: owner,
|
|
461
|
+
name,
|
|
462
|
+
kind: kindArg,
|
|
463
|
+
...(forkedFrom ? { forked_from: forkedFrom } : {}),
|
|
464
|
+
})}\n`);
|
|
517
465
|
return 0;
|
|
518
466
|
}
|
|
519
467
|
async function handleShellMutationPaths(root, rest) {
|
|
@@ -776,6 +724,97 @@ async function handleEmitEvent(root, rest) {
|
|
|
776
724
|
process.stdout.write(`${JSON.stringify(result.envelope)}\n`);
|
|
777
725
|
return 0;
|
|
778
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* `agent-coord git-hook <event> [args]` — the single entry a host git hook
|
|
729
|
+
* invokes. Owns ALL of the coordination git plumbing that hosts used to carry
|
|
730
|
+
* as bash (staged/committed/checkout-removed collection, submodule
|
|
731
|
+
* canonicalization, gitlink probe, verdict, claim pruning), so a harnery
|
|
732
|
+
* upgrade upgrades hook behavior with no change to the host's hook file.
|
|
733
|
+
*
|
|
734
|
+
* Events: pre-commit (E-guard verdict; exit 1 blocks the commit),
|
|
735
|
+
* post-commit (prune committed paths' claims), post-checkout <old> <new>
|
|
736
|
+
* (release claims on paths the ref move rewrote).
|
|
737
|
+
*
|
|
738
|
+
* Fail-open by design: an internal error never blocks a commit (parity with
|
|
739
|
+
* the bash era, where a crashed verdict read as empty and the hook moved on).
|
|
740
|
+
* A clean conflict verdict still blocks.
|
|
741
|
+
*/
|
|
742
|
+
async function handleGitHook(fallbackRoot, rest) {
|
|
743
|
+
const event = rest[0];
|
|
744
|
+
if (process.env.HARNERY_AGENT_COORD_OFF === "1")
|
|
745
|
+
return 0;
|
|
746
|
+
const cwd = process.cwd();
|
|
747
|
+
try {
|
|
748
|
+
const gh = await import("./git-hook.js");
|
|
749
|
+
// Pin the coord root to the superproject: a submodule cwd may carry its
|
|
750
|
+
// own config-only .harnery that the generic root walk would land on.
|
|
751
|
+
const root = gh.discoverCoordRoot(cwd) ?? fallbackRoot;
|
|
752
|
+
if (event === "pre-commit") {
|
|
753
|
+
const { staged, gitlinks } = gh.collectStaged(cwd);
|
|
754
|
+
if (staged.length === 0)
|
|
755
|
+
return 0;
|
|
756
|
+
const { evaluateCommit } = await import("./rules/commit-conflict.js");
|
|
757
|
+
const result = evaluateCommit(root, {
|
|
758
|
+
staged_paths: staged,
|
|
759
|
+
staged_gitlinks: gitlinks,
|
|
760
|
+
bypass: process.env.HARNERY_AGENT_COORD_BYPASS === "1",
|
|
761
|
+
});
|
|
762
|
+
// Forensics channel shared with the stdin `verdict` path.
|
|
763
|
+
try {
|
|
764
|
+
const logPath = join(root, ".harnery", "debug", "agent-coord-verdict.ndjson");
|
|
765
|
+
await mkdir(dirname(logPath), { recursive: true });
|
|
766
|
+
await appendFile(logPath, `${JSON.stringify({ ts: new Date().toISOString(), via: "git-hook", staged, verdict: result })}\n`);
|
|
767
|
+
}
|
|
768
|
+
catch {
|
|
769
|
+
/* diagnostics never break a verdict */
|
|
770
|
+
}
|
|
771
|
+
if (result.rule !== "commit.pass") {
|
|
772
|
+
if (result.message)
|
|
773
|
+
process.stdout.write(`\n${result.message}\n\n`);
|
|
774
|
+
for (const c of result.conflicts) {
|
|
775
|
+
process.stdout.write(` ${c.staged_path} (held by ${c.short_name})\n`);
|
|
776
|
+
}
|
|
777
|
+
process.stdout.write("\n");
|
|
778
|
+
}
|
|
779
|
+
if (!result.allow) {
|
|
780
|
+
process.stdout.write(" Wait for them to finish, split the commit, or set\n" +
|
|
781
|
+
" HARNERY_AGENT_COORD_BYPASS=1 to force this commit through.\n\n");
|
|
782
|
+
return result.exit_code || 1;
|
|
783
|
+
}
|
|
784
|
+
return 0;
|
|
785
|
+
}
|
|
786
|
+
if (event === "post-commit" || event === "post-checkout") {
|
|
787
|
+
const paths = event === "post-commit"
|
|
788
|
+
? gh.collectCommitted(cwd)
|
|
789
|
+
: gh.collectCheckoutRemoved(cwd, rest[1] ?? "", rest[2] ?? "");
|
|
790
|
+
if (paths.length === 0)
|
|
791
|
+
return 0;
|
|
792
|
+
const { resolveOwner } = await import("../hooks/resolve/owner.js");
|
|
793
|
+
const owner = resolveOwner({ payload: null, coordRoot: root })?.instance_id;
|
|
794
|
+
if (!owner)
|
|
795
|
+
return 0;
|
|
796
|
+
const { groupUnclaim } = await import("./state/heartbeat-writer.js");
|
|
797
|
+
const reason = event === "post-commit" ? "commit" : "checkout";
|
|
798
|
+
for (const path of paths) {
|
|
799
|
+
try {
|
|
800
|
+
for (const hit of groupUnclaim(root, owner, path)) {
|
|
801
|
+
await emitClaimRelease(root, hit.instance_id, hit, path, reason);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
catch {
|
|
805
|
+
/* best-effort */
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return 0;
|
|
809
|
+
}
|
|
810
|
+
process.stderr.write(`agent-coord git-hook: unknown event ${event ?? "(none)"}\n`);
|
|
811
|
+
return 0;
|
|
812
|
+
}
|
|
813
|
+
catch {
|
|
814
|
+
// Fail open: coordination must never brick git.
|
|
815
|
+
return 0;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
779
818
|
async function main() {
|
|
780
819
|
const [subcommand, ...rest] = process.argv.slice(2);
|
|
781
820
|
const root = findCoordRoot(process.cwd());
|
|
@@ -791,7 +830,6 @@ async function main() {
|
|
|
791
830
|
return handleEmitEvent(root, rest);
|
|
792
831
|
}
|
|
793
832
|
if (subcommand === "set-task" ||
|
|
794
|
-
subcommand === "stamp-status-call" ||
|
|
795
833
|
subcommand === "set-turn-summary" ||
|
|
796
834
|
subcommand === "release-claim" ||
|
|
797
835
|
subcommand === "kill-heartbeat" ||
|
|
@@ -835,11 +873,8 @@ async function main() {
|
|
|
835
873
|
if (subcommand === "shell-mutation-claim-log") {
|
|
836
874
|
return handleShellMutationClaimLog(root, rest);
|
|
837
875
|
}
|
|
838
|
-
if (subcommand === "
|
|
839
|
-
return
|
|
840
|
-
}
|
|
841
|
-
if (subcommand === "post-checkout") {
|
|
842
|
-
return handlePostCheckout(root, rest);
|
|
876
|
+
if (subcommand === "git-hook") {
|
|
877
|
+
return handleGitHook(root, rest);
|
|
843
878
|
}
|
|
844
879
|
await logNoop(root, subcommand ?? "(none)", rest);
|
|
845
880
|
// Phase 1/2: silent on stdout for unknown subcommands, exit 0. Existing
|
|
@@ -40,11 +40,15 @@ export interface Heartbeat {
|
|
|
40
40
|
task?: string;
|
|
41
41
|
/** UTC ISO-8601 timestamp when task was last set/cleared. Used by adapters without Stop enforcement to compute staleness. 2026-05-24. */
|
|
42
42
|
task_updated_at?: string | null;
|
|
43
|
+
/** Session name built on the first non-empty set-task; its presence means "this session has been named". 2026-08-09. */
|
|
44
|
+
suggested_session_name?: string;
|
|
45
|
+
/** Stamped by turn.stop once the suggested name is seen in assistant reply text. 2026-08-09. */
|
|
46
|
+
session_name_seen_at?: string;
|
|
43
47
|
/** Auto-generated per-turn summary written by the Stop hook via Haiku. 2026-05-23. */
|
|
44
48
|
turn_summary?: string | null;
|
|
45
49
|
/** UTC ISO-8601 timestamp when turn_summary was last refreshed. */
|
|
46
50
|
turn_summary_updated_at?: string | null;
|
|
47
|
-
/** Hook client: `
|
|
51
|
+
/** Hook client: `claude-code` (default) or `cursor`. Cursor Phase 1. */
|
|
48
52
|
platform?: string;
|
|
49
53
|
}
|
|
50
54
|
/**
|
|
@@ -101,7 +105,7 @@ export declare function nearestCoordRoot(start: string): string | null;
|
|
|
101
105
|
export declare function rootKnowsSession(root: string): boolean;
|
|
102
106
|
/** Parse owner from a pid-map row (`owner` or `owner\tplatform`). */
|
|
103
107
|
export declare function parsePidmapRowOwner(row: string): string;
|
|
104
|
-
/** Parse platform from a pid-map row; legacy rows default to `
|
|
108
|
+
/** Parse platform from a pid-map row; legacy rows default to `claude-code`. */
|
|
105
109
|
export declare function parsePidmapRowPlatform(row: string): string;
|
|
106
110
|
/** Parse the start token from a pid-map row; rows written before it carry none. */
|
|
107
111
|
export declare function parsePidmapRowStartToken(row: string): string | undefined;
|
|
@@ -128,7 +132,7 @@ export declare function pidStartToken(pid: number): string | null;
|
|
|
128
132
|
*
|
|
129
133
|
* Pid-map files are `instance_id` or `instance_id\tplatform` (Cursor Phase 1).
|
|
130
134
|
* Prefer a row whose platform matches `HARNERY_AGENT_COORD_PLATFORM` (default
|
|
131
|
-
* `
|
|
135
|
+
* `claude-code`); otherwise return the first owner seen on the walk.
|
|
132
136
|
*
|
|
133
137
|
* Subagents intentionally do not write pid-map entries; a Bash-tool ppid-walk
|
|
134
138
|
* from inside a subagent therefore resolves to the *parent's* pid-map entry.
|
|
@@ -162,7 +166,10 @@ export declare function resolveOwnerByPidmap(root: string): {
|
|
|
162
166
|
* `instance_id`, or null if there's no session-id env var or no live heartbeat
|
|
163
167
|
* carries it. "Live" reuses the same 10-minute freshness window the singleton
|
|
164
168
|
* fallback applies, so a stale heartbeat from a prior session of the same id
|
|
165
|
-
* doesn't resolve.
|
|
169
|
+
* doesn't resolve. When legacy Cursor Glass state contains both the canonical
|
|
170
|
+
* bare id and its raw `bc-` alias, candidate order wins first; ties prefer a
|
|
171
|
+
* named heartbeat, then the newest heartbeat, with instance id as a stable
|
|
172
|
+
* final tie-breaker. Filesystem enumeration order never decides identity.
|
|
166
173
|
*
|
|
167
174
|
* Exported for unit testing with an injectable root.
|
|
168
175
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAWhE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAI5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOhF;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yIAAyI;IACzI,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mEAAmE;IACnE,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CA+B7E;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7D;AAeD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGtD;AAiED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2CxD;AAgCD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,iBAAiB,GAAG,aAAa,GAAG,kBAAkB,GAAG,MAAM,CAAC;CAC5F,CAwCA;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG;IAClD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,QAAQ,GAAG,iBAAiB,GAAG,MAAM,CAAC;CAC/C,CAyBA;
|
|
1
|
+
{"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"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"}
|