harnery 0.31.6 → 0.33.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/README.md +2 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +5 -1
- package/dist/commands/agents.d.ts +0 -9
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +280 -94
- 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/claude-desktop.d.ts +2 -2
- package/dist/commands/claude-desktop.d.ts.map +1 -1
- package/dist/commands/claude-desktop.js +74 -3
- 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 +4 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +141 -19
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +26 -5
- package/dist/commands/instructions.d.ts +4 -0
- package/dist/commands/instructions.d.ts.map +1 -0
- package/dist/commands/instructions.js +100 -0
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +4 -1
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -3
- package/dist/core/agents/canonical-emit.d.ts +2 -3
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +6 -5
- package/dist/core/agents/cli.js +128 -89
- package/dist/core/agents/coord-client.d.ts +18 -3
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +5 -4
- 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/index.d.ts +1 -0
- package/dist/core/agents/index.d.ts.map +1 -1
- package/dist/core/agents/index.js +1 -0
- package/dist/core/agents/render/prompt-context.d.ts +12 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +70 -16
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +23 -4
- 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 +14 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +98 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +47 -5
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +86 -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/agents/state/session-state.d.ts +44 -0
- package/dist/core/agents/state/session-state.d.ts.map +1 -0
- package/dist/core/agents/state/session-state.js +81 -0
- 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/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +1 -0
- 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 +2 -0
- 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 +258 -32
- package/dist/core/hooks/codex-authorization.d.ts +30 -0
- package/dist/core/hooks/codex-authorization.d.ts.map +1 -0
- package/dist/core/hooks/codex-authorization.js +199 -0
- 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 +57 -1
- 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/presence/blob.d.ts +4 -0
- package/dist/core/presence/blob.d.ts.map +1 -1
- package/dist/core/presence/blob.js +12 -0
- package/dist/core/presence/index.js +15 -1
- 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/claude-desktop.d.ts +47 -0
- package/dist/lib/claude-desktop.d.ts.map +1 -1
- package/dist/lib/claude-desktop.js +169 -1
- 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/bundle.d.ts +33 -0
- package/dist/lib/instructions/bundle.d.ts.map +1 -0
- package/dist/lib/instructions/bundle.js +149 -0
- 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 +2 -2
- package/schemas/config.schema.json +32 -0
- package/src/commander.ts +5 -1
- package/src/commands/agents.ts +341 -113
- 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/claude-desktop.ts +106 -5
- package/src/commands/deinit.ts +7 -1
- package/src/commands/docs.ts +56 -0
- package/src/commands/doctor.ts +155 -21
- package/src/commands/init.ts +28 -9
- package/src/commands/instructions.ts +129 -0
- package/src/commands/presence.ts +7 -1
- package/src/commands/tunnel.ts +2 -5
- package/src/core/agents/canonical-emit.ts +7 -6
- package/src/core/agents/cli.ts +143 -89
- package/src/core/agents/coord-client.ts +21 -5
- package/src/core/agents/finalization.ts +595 -0
- package/src/core/agents/git-hook.ts +126 -0
- package/src/core/agents/index.ts +1 -0
- package/src/core/agents/render/prompt-context.ts +96 -16
- package/src/core/agents/render/session-context.ts +29 -4
- 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 +108 -1
- package/src/core/agents/state/heartbeat-writer.ts +129 -16
- package/src/core/agents/state/names.ts +54 -2
- package/src/core/agents/state/session-state.ts +131 -0
- 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/events.ts +1 -0
- package/src/core/hooks/adapter/output.ts +9 -4
- package/src/core/hooks/adapter/parse.ts +3 -0
- package/src/core/hooks/adapter/wiring.ts +43 -0
- package/src/core/hooks/cli.ts +296 -27
- package/src/core/hooks/codex-authorization.ts +253 -0
- 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 +71 -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/presence/blob.ts +18 -0
- package/src/core/presence/index.ts +18 -1
- 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/claude-desktop.ts +232 -1
- 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/bundle.ts +199 -0
- package/src/lib/instructions/git-hooks.ts +259 -0
- package/src/lib/instructions/splice.ts +41 -11
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
-
import type { EmitContext } from "../commander.ts";
|
|
2
|
+
import type { EmitContext, HarneryProgramContext } from "../commander.ts";
|
|
3
|
+
import { monorepoRoot } from "../core/agents/index.ts";
|
|
3
4
|
import {
|
|
4
5
|
applyMirror,
|
|
6
|
+
applyTidy,
|
|
5
7
|
type DesktopAccount,
|
|
6
8
|
findDesktopDataDirs,
|
|
7
9
|
listAccounts,
|
|
8
10
|
planMirror,
|
|
11
|
+
planTidy,
|
|
9
12
|
readCliAccount,
|
|
13
|
+
readDesktopLifecycleIndex,
|
|
10
14
|
} from "../lib/claude-desktop.ts";
|
|
11
15
|
|
|
12
16
|
/**
|
|
@@ -24,12 +28,14 @@ import {
|
|
|
24
28
|
* desktop app picks the copies up, and opening one resumes with full
|
|
25
29
|
* history.
|
|
26
30
|
*/
|
|
27
|
-
export function registerClaudeDesktopCommand(
|
|
31
|
+
export function registerClaudeDesktopCommand(
|
|
32
|
+
program: Command,
|
|
33
|
+
emit: EmitContext,
|
|
34
|
+
context?: HarneryProgramContext,
|
|
35
|
+
): void {
|
|
28
36
|
const cmd = program
|
|
29
37
|
.command("claude-desktop")
|
|
30
|
-
.description(
|
|
31
|
-
"Claude desktop-app session index: list accounts/sessions, mirror sessions across accounts",
|
|
32
|
-
)
|
|
38
|
+
.description("Claude desktop-app session index: list, mirror, and archive completed sessions")
|
|
33
39
|
.action(() => {
|
|
34
40
|
const dirs = requireDataDirs(emit, undefined);
|
|
35
41
|
for (const dir of dirs) {
|
|
@@ -184,6 +190,101 @@ export function registerClaudeDesktopCommand(program: Command, emit: EmitContext
|
|
|
184
190
|
}
|
|
185
191
|
},
|
|
186
192
|
);
|
|
193
|
+
|
|
194
|
+
cmd
|
|
195
|
+
.command("tidy")
|
|
196
|
+
.description(
|
|
197
|
+
"Archive desktop sidebar entries whose mapped Harnery lifecycle is done. " +
|
|
198
|
+
"Dry-run by default; --yes applies atomic entry updates.",
|
|
199
|
+
)
|
|
200
|
+
.option("--data-dir <path>", "Explicit desktop-app data directory")
|
|
201
|
+
.option(
|
|
202
|
+
"--account <uuid-prefix>",
|
|
203
|
+
"Only entries under accounts matching this UUID prefix (repeatable)",
|
|
204
|
+
collect,
|
|
205
|
+
[] as string[],
|
|
206
|
+
)
|
|
207
|
+
.option(
|
|
208
|
+
"--include-legacy-prefix",
|
|
209
|
+
"Also archive unmatched historical entries whose title starts with [DONE]",
|
|
210
|
+
)
|
|
211
|
+
.option("--yes", "Apply the archive plan (default is dry-run)")
|
|
212
|
+
.action(
|
|
213
|
+
async (opts: {
|
|
214
|
+
dataDir?: string;
|
|
215
|
+
account: string[];
|
|
216
|
+
includeLegacyPrefix?: boolean;
|
|
217
|
+
yes?: boolean;
|
|
218
|
+
}) => {
|
|
219
|
+
const dirs = requireDataDirs(emit, opts.dataDir);
|
|
220
|
+
const coordRoot =
|
|
221
|
+
context?.resolveCoordRoot?.() ?? context?.repoRoot ?? monorepoRoot() ?? process.cwd();
|
|
222
|
+
const lifecycle = await readDesktopLifecycleIndex(coordRoot);
|
|
223
|
+
|
|
224
|
+
for (const dir of dirs) {
|
|
225
|
+
const plan = planTidy(listAccounts(dir), lifecycle, {
|
|
226
|
+
accounts: opts.account,
|
|
227
|
+
includeLegacyPrefix: opts.includeLegacyPrefix,
|
|
228
|
+
});
|
|
229
|
+
const planned = plan.actions.map((action) => ({
|
|
230
|
+
account_uuid: action.entry.accountUuid,
|
|
231
|
+
cli_session_id: action.entry.cliSessionId,
|
|
232
|
+
title: action.entry.title,
|
|
233
|
+
source: action.source,
|
|
234
|
+
lifecycle_updated_at: action.lifecycle?.updatedAt ?? null,
|
|
235
|
+
file: action.entry.file,
|
|
236
|
+
}));
|
|
237
|
+
const skipped = plan.skipped
|
|
238
|
+
.map((skip) => ({
|
|
239
|
+
account_uuid: skip.entry.accountUuid,
|
|
240
|
+
cli_session_id: skip.entry.cliSessionId,
|
|
241
|
+
title: skip.entry.title,
|
|
242
|
+
reason: skip.reason,
|
|
243
|
+
lifecycle: skip.lifecycle?.state ?? null,
|
|
244
|
+
}))
|
|
245
|
+
.filter((skip) => skip.reason !== "already_archived");
|
|
246
|
+
const skippedByReason = Object.fromEntries(
|
|
247
|
+
[
|
|
248
|
+
"already_archived",
|
|
249
|
+
"blocked_title",
|
|
250
|
+
"lifecycle_active",
|
|
251
|
+
"lifecycle_blocked",
|
|
252
|
+
"unmatched",
|
|
253
|
+
].map((reason) => [
|
|
254
|
+
reason,
|
|
255
|
+
plan.skipped.filter((skip) => skip.reason === reason).length,
|
|
256
|
+
]),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
if (!opts.yes) {
|
|
260
|
+
emit.data({
|
|
261
|
+
data_dir: dir,
|
|
262
|
+
dry_run: true,
|
|
263
|
+
planned,
|
|
264
|
+
skipped,
|
|
265
|
+
skipped_by_reason: skippedByReason,
|
|
266
|
+
hint: plan.actions.length > 0 ? "re-run with --yes to archive" : "nothing to archive",
|
|
267
|
+
});
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const result = applyTidy(plan);
|
|
272
|
+
emit.data({
|
|
273
|
+
data_dir: dir,
|
|
274
|
+
dry_run: false,
|
|
275
|
+
archived: result.archived,
|
|
276
|
+
already_archived: result.alreadyArchived,
|
|
277
|
+
planned,
|
|
278
|
+
skipped,
|
|
279
|
+
skipped_by_reason: skippedByReason,
|
|
280
|
+
hint:
|
|
281
|
+
result.archived > 0
|
|
282
|
+
? "fully quit the Claude desktop app (tray icon, not the X) and relaunch to refresh the sidebar"
|
|
283
|
+
: "nothing to archive",
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
);
|
|
187
288
|
}
|
|
188
289
|
|
|
189
290
|
function collect(value: string, previous: string[]): string[] {
|
package/src/commands/deinit.ts
CHANGED
|
@@ -34,8 +34,10 @@ import type { Command } from "commander";
|
|
|
34
34
|
import type { EmitContext } from "../commander.ts";
|
|
35
35
|
import { DEFAULT_BIN_NAME } from "../core/config.ts";
|
|
36
36
|
import { ADAPTER_SPECS, type AdapterId } from "../core/hooks/adapter/events.ts";
|
|
37
|
+
import type { SettingsFile } from "../core/hooks/adapter/wiring.ts";
|
|
37
38
|
import { removeInstructions } from "../lib/instructions/apply.ts";
|
|
38
|
-
import {
|
|
39
|
+
import { removeGitHooks } from "../lib/instructions/git-hooks.ts";
|
|
40
|
+
import { unwireHooks } from "./init.ts";
|
|
39
41
|
|
|
40
42
|
interface DeinitOpts {
|
|
41
43
|
adapter: string;
|
|
@@ -138,6 +140,10 @@ export function registerDeinitCommand(program: Command, emit: EmitContext, binNa
|
|
|
138
140
|
const removed = removeInstructions(projectRoot, { adapter, dryRun });
|
|
139
141
|
actions.push(...removed.actions, ...removed.warnings.map((w) => `! ${w}`));
|
|
140
142
|
|
|
143
|
+
// ── 1c. git-hook managed regions ───────────────────────────────────────
|
|
144
|
+
const gitHooks = removeGitHooks(projectRoot, { dryRun });
|
|
145
|
+
actions.push(...gitHooks.actions, ...gitHooks.warnings.map((w) => `! ${w}`));
|
|
146
|
+
|
|
141
147
|
// ── 2. coord root (opt-in; destructive) ────────────────────────────────
|
|
142
148
|
if (purgeState) {
|
|
143
149
|
if (!coordExists) {
|
package/src/commands/docs.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
runFrontmatterMigration,
|
|
7
7
|
} from "../lib/docs-frontmatter-migrate.ts";
|
|
8
8
|
import { initDocsContext as initDocsIndex, runIndex } from "../lib/docs-index.ts";
|
|
9
|
+
import { initDocsContext as initDocsLinks, runLinks } from "../lib/docs-links.ts";
|
|
9
10
|
import { initDocsContext as initDocsLint, runLint } from "../lib/docs-lint.ts";
|
|
10
11
|
import { readDocsMetadata, readDocsMetadataKey } from "../lib/docs-meta.ts";
|
|
11
12
|
import {
|
|
@@ -27,6 +28,7 @@ function ensureContext(context: HarneryProgramContext | undefined): void {
|
|
|
27
28
|
extraExcludedPrefixes: context.extraDocsExcludedPrefixes,
|
|
28
29
|
docsRootAllowlist: context.docsRootAllowlist,
|
|
29
30
|
});
|
|
31
|
+
initDocsLinks({ ...opts, extraExcludedPrefixes: context.extraDocsExcludedPrefixes });
|
|
30
32
|
initDocsSweep(opts);
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -115,6 +117,34 @@ export function registerDocsCommand(
|
|
|
115
117
|
}
|
|
116
118
|
});
|
|
117
119
|
|
|
120
|
+
docs
|
|
121
|
+
.command("links")
|
|
122
|
+
.description("Check internal Markdown link targets and heading fragments")
|
|
123
|
+
.option("--repo <name>", "Limit to one submodule or '.' for parent")
|
|
124
|
+
.option("--no-fragments", "Check target existence only; skip heading-fragment validation")
|
|
125
|
+
.option("--strict", "Report findings in archive/audit/changelog docs as errors, not warnings")
|
|
126
|
+
.option("--check-escapes", "Also flag links that resolve outside their own repo root")
|
|
127
|
+
.option("--fail", "Exit non-zero when errors are found (advisory by default)")
|
|
128
|
+
.option("--format <type>", "Output format: human, json", "human")
|
|
129
|
+
.action(
|
|
130
|
+
async (opts: {
|
|
131
|
+
repo?: string;
|
|
132
|
+
fragments?: boolean;
|
|
133
|
+
strict?: boolean;
|
|
134
|
+
checkEscapes?: boolean;
|
|
135
|
+
fail?: boolean;
|
|
136
|
+
format: string;
|
|
137
|
+
}) => {
|
|
138
|
+
try {
|
|
139
|
+
ensureContext(context);
|
|
140
|
+
await handleLinks(opts);
|
|
141
|
+
} catch (err: unknown) {
|
|
142
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
143
|
+
emit.error({ code: "docs_error", message: msg });
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
|
|
118
148
|
docs
|
|
119
149
|
.command("sweep")
|
|
120
150
|
.description(
|
|
@@ -232,6 +262,32 @@ async function handleLint(opts: { fast?: boolean; repo?: string; format: string
|
|
|
232
262
|
if (errors.length > 0) emit.setExitCode(1);
|
|
233
263
|
}
|
|
234
264
|
|
|
265
|
+
// --- `harn docs links` ---
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Advisory by default: a clean exit code even with findings, because turning
|
|
269
|
+
* link health into a gate is an enforcement policy decision for the host, not
|
|
270
|
+
* something this command should assume. `--fail` is the opt-in for CI or a hook.
|
|
271
|
+
*/
|
|
272
|
+
async function handleLinks(opts: {
|
|
273
|
+
repo?: string;
|
|
274
|
+
fragments?: boolean;
|
|
275
|
+
strict?: boolean;
|
|
276
|
+
checkEscapes?: boolean;
|
|
277
|
+
fail?: boolean;
|
|
278
|
+
format: string;
|
|
279
|
+
}): Promise<void> {
|
|
280
|
+
if (opts.format === "json") emit.config({ format: "json" });
|
|
281
|
+
const report = await runLinks({
|
|
282
|
+
repo: opts.repo,
|
|
283
|
+
noFragments: opts.fragments === false,
|
|
284
|
+
strict: opts.strict,
|
|
285
|
+
checkEscapes: opts.checkEscapes,
|
|
286
|
+
});
|
|
287
|
+
emit.data({ ...report, advisory: !opts.fail });
|
|
288
|
+
if (opts.fail && report.error_count > 0) emit.setExitCode(1);
|
|
289
|
+
}
|
|
290
|
+
|
|
235
291
|
// --- `harn docs sweep` ---
|
|
236
292
|
|
|
237
293
|
async function handleSweep(opts: { repo?: string; format: string }): Promise<void> {
|
package/src/commands/doctor.ts
CHANGED
|
@@ -19,7 +19,16 @@ import type { Command } from "commander";
|
|
|
19
19
|
import type { EmitContext } from "../commander.ts";
|
|
20
20
|
import { BUILTIN_ADAPTER_IDS } from "../core/adapters/index.ts";
|
|
21
21
|
import { resolveBinName, ripgrepAutoInstall } from "../core/config.ts";
|
|
22
|
-
import {
|
|
22
|
+
import { ADAPTER_SPECS, type AdapterId } from "../core/hooks/adapter/events.ts";
|
|
23
|
+
import { loadAdapterWiring, summarizeAdapterWiring } from "../core/hooks/adapter/wiring.ts";
|
|
24
|
+
import {
|
|
25
|
+
type CodexHookAuthorizationResult,
|
|
26
|
+
probeCodexHookAuthorization,
|
|
27
|
+
} from "../core/hooks/codex-authorization.ts";
|
|
28
|
+
import {
|
|
29
|
+
type CodexWslBridgeStatus,
|
|
30
|
+
inspectCodexWslBridge,
|
|
31
|
+
} from "../core/hooks/codex-wsl-bridge.ts";
|
|
23
32
|
import {
|
|
24
33
|
ADAPTER_BINARIES,
|
|
25
34
|
ADAPTER_INSTALL_HINTS,
|
|
@@ -27,11 +36,12 @@ import {
|
|
|
27
36
|
} from "../core/workflow/adapters.ts";
|
|
28
37
|
import { probeBilling } from "../core/workflow/billing.ts";
|
|
29
38
|
import type { AdapterName } from "../core/workflow/types.ts";
|
|
39
|
+
import { checkGitHooks, gitHooksInstalled } from "../lib/instructions/git-hooks.ts";
|
|
30
40
|
import { findRg, installRg, managedRgPath, rgInstallSupported } from "../lib/tools/ripgrep.ts";
|
|
31
41
|
|
|
32
42
|
type Severity = "ok" | "warn" | "fail";
|
|
33
43
|
|
|
34
|
-
interface Check {
|
|
44
|
+
export interface Check {
|
|
35
45
|
name: string;
|
|
36
46
|
severity: Severity;
|
|
37
47
|
detail: string;
|
|
@@ -63,7 +73,7 @@ export function registerDoctorCommand(program: Command, emit: EmitContext): void
|
|
|
63
73
|
emit.text(`ripgrep install failed: ${(err as Error).message}\n`);
|
|
64
74
|
}
|
|
65
75
|
}
|
|
66
|
-
const checks = runChecks();
|
|
76
|
+
const checks = await runChecks();
|
|
67
77
|
const requiredFailed = checks.some((c) => c.severity === "fail");
|
|
68
78
|
|
|
69
79
|
if (opts.json) {
|
|
@@ -96,15 +106,26 @@ export function registerDoctorCommand(program: Command, emit: EmitContext): void
|
|
|
96
106
|
});
|
|
97
107
|
}
|
|
98
108
|
|
|
99
|
-
export function runChecks(): Check[] {
|
|
109
|
+
export async function runChecks(): Promise<Check[]> {
|
|
110
|
+
const codexWslStatus = inspectCodexWslBridge();
|
|
111
|
+
const codexWslBridge = checkCodexWslBridge(codexWslStatus);
|
|
112
|
+
// Probe the adapter CLIs before the hook check runs, not in output order:
|
|
113
|
+
// an unwired adapter only matters if its CLI is actually installed, and
|
|
114
|
+
// probing once here keeps that join to one spawn per CLI.
|
|
115
|
+
const workflow = BUILTIN_ADAPTER_IDS.map((id) => ({ id, ...checkWorkflowAdapter(id) }));
|
|
116
|
+
const installed = workflow.filter((w) => w.installed).map((w) => w.id as AdapterId);
|
|
117
|
+
const codexAuthorization = await checkCodexHookAuthorization(installed, codexWslStatus);
|
|
100
118
|
return [
|
|
101
119
|
checkNode(),
|
|
102
120
|
checkGit(),
|
|
103
121
|
checkBun(),
|
|
104
122
|
checkRipgrep(),
|
|
105
123
|
checkHarneryDir(),
|
|
106
|
-
checkAdapterHooks(),
|
|
107
|
-
...
|
|
124
|
+
checkAdapterHooks(installed),
|
|
125
|
+
...(codexAuthorization ? [codexAuthorization] : []),
|
|
126
|
+
checkGitHookRegions(),
|
|
127
|
+
...(codexWslBridge ? [codexWslBridge] : []),
|
|
128
|
+
...workflow.map((w) => w.check),
|
|
108
129
|
checkRestic(),
|
|
109
130
|
checkRclone(),
|
|
110
131
|
checkPlaywright(),
|
|
@@ -112,6 +133,91 @@ export function runChecks(): Check[] {
|
|
|
112
133
|
];
|
|
113
134
|
}
|
|
114
135
|
|
|
136
|
+
function checkGitHookRegions(): Check {
|
|
137
|
+
const root = findCoordProjectRoot();
|
|
138
|
+
if (!root) {
|
|
139
|
+
return { name: "git hooks", severity: "ok", detail: "n/a (no .harnery/ above cwd)" };
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
if (!gitHooksInstalled(root)) {
|
|
143
|
+
const bin = resolveBinName(root);
|
|
144
|
+
return {
|
|
145
|
+
name: "git hooks",
|
|
146
|
+
severity: "warn",
|
|
147
|
+
detail:
|
|
148
|
+
`not installed - commits are not guarded against peer-claim conflicts and ` +
|
|
149
|
+
`claims never auto-release on commit/checkout; run \`${bin} init\` to install`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const { status, issues } = checkGitHooks(root);
|
|
153
|
+
if (status === "fresh") {
|
|
154
|
+
return { name: "git hooks", severity: "ok", detail: "managed regions current" };
|
|
155
|
+
}
|
|
156
|
+
return { name: "git hooks", severity: "warn", detail: issues.join("; ") };
|
|
157
|
+
} catch (err) {
|
|
158
|
+
return {
|
|
159
|
+
name: "git hooks",
|
|
160
|
+
severity: "warn",
|
|
161
|
+
detail: `check failed: ${(err as Error).message}`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function checkCodexWslBridge(status: CodexWslBridgeStatus | null): Check | null {
|
|
167
|
+
if (!status) return null;
|
|
168
|
+
return {
|
|
169
|
+
name: "codex:WSL bridge",
|
|
170
|
+
severity: status.ok ? "ok" : "warn",
|
|
171
|
+
detail: status.detail,
|
|
172
|
+
hint: status.ok
|
|
173
|
+
? undefined
|
|
174
|
+
: "forward CODEX_THREAD_ID through WSLENV in the Codex shell environment policy, then start a fresh task",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function codexAuthorizationCheck(result: CodexHookAuthorizationResult): Check {
|
|
179
|
+
const reviewHint =
|
|
180
|
+
"terminal UI: run `/hooks`; Codex Desktop: open Settings > Hooks; after approval, start a fresh task";
|
|
181
|
+
if (result.status === "runnable") {
|
|
182
|
+
return { name: "codex:hook authorization", severity: "ok", detail: result.detail };
|
|
183
|
+
}
|
|
184
|
+
if (result.status === "review_required" || result.status === "disabled") {
|
|
185
|
+
return {
|
|
186
|
+
name: "codex:hook authorization",
|
|
187
|
+
severity: "warn",
|
|
188
|
+
detail: result.detail,
|
|
189
|
+
hint: reviewHint,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
name: "codex:hook authorization",
|
|
194
|
+
severity: "warn",
|
|
195
|
+
detail: `authorization unverified: ${result.detail}`,
|
|
196
|
+
hint: reviewHint,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function checkCodexHookAuthorization(
|
|
201
|
+
installedAdapters: AdapterId[],
|
|
202
|
+
codexWslStatus: CodexWslBridgeStatus | null,
|
|
203
|
+
): Promise<Check | null> {
|
|
204
|
+
const root = findCoordProjectRoot();
|
|
205
|
+
if (!root || !summarizeAdapterWiring(root).wired.includes("codex")) return null;
|
|
206
|
+
|
|
207
|
+
if (codexWslStatus) {
|
|
208
|
+
return {
|
|
209
|
+
name: "codex:hook authorization",
|
|
210
|
+
severity: "warn",
|
|
211
|
+
detail:
|
|
212
|
+
"unverified from WSL; the Windows-native Codex runtime owns this workspace's trust state",
|
|
213
|
+
hint: "run the host's Windows bridge doctor, or open Codex Desktop Settings > Hooks; start a fresh task after approval",
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (!installedAdapters.includes("codex")) return null;
|
|
217
|
+
|
|
218
|
+
return codexAuthorizationCheck(await probeCodexHookAuthorization({ cwd: root }));
|
|
219
|
+
}
|
|
220
|
+
|
|
115
221
|
function checkRipgrep(): Check {
|
|
116
222
|
const found = findRg();
|
|
117
223
|
if (found) {
|
|
@@ -157,26 +263,35 @@ function whichVersion(bin: string, args: string[] = ["--version"]): { ok: boolea
|
|
|
157
263
|
* One workflow spawn target: is the adapter CLI installed, and how will its
|
|
158
264
|
* headless children bill (subscription login vs API key — see billing.ts)?
|
|
159
265
|
* Missing is a warn, not a fail: workflows degrade to the adapters you have.
|
|
266
|
+
*
|
|
267
|
+
* Reports `installed` alongside the check so `checkAdapterHooks` can reuse the
|
|
268
|
+
* probe instead of spawning every adapter CLI a second time.
|
|
160
269
|
*/
|
|
161
|
-
function checkWorkflowAdapter(adapter: AdapterName): Check {
|
|
270
|
+
function checkWorkflowAdapter(adapter: AdapterName): { check: Check; installed: boolean } {
|
|
162
271
|
const bin = ADAPTER_BINARIES[adapter];
|
|
163
272
|
const name = `workflow:${adapter}`;
|
|
164
273
|
const r = whichVersion(bin);
|
|
165
274
|
if (!r.ok) {
|
|
166
275
|
return {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
276
|
+
check: {
|
|
277
|
+
name,
|
|
278
|
+
severity: "warn",
|
|
279
|
+
detail: `${bin} missing (workflow --adapter ${adapter} unavailable)`,
|
|
280
|
+
hint: `${ADAPTER_INSTALL_HINTS[adapter]} then: ${ADAPTER_LOGIN_HINTS[adapter]}`,
|
|
281
|
+
},
|
|
282
|
+
installed: false,
|
|
171
283
|
};
|
|
172
284
|
}
|
|
173
285
|
const probe = probeBilling(adapter);
|
|
174
286
|
if (probe.login === "absent" && !probe.apiKeyPresent) {
|
|
175
287
|
return {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
288
|
+
check: {
|
|
289
|
+
name,
|
|
290
|
+
severity: "warn",
|
|
291
|
+
detail: `${r.out} — installed, but no stored login or API key detected`,
|
|
292
|
+
hint: ADAPTER_LOGIN_HINTS[adapter],
|
|
293
|
+
},
|
|
294
|
+
installed: true,
|
|
180
295
|
};
|
|
181
296
|
}
|
|
182
297
|
const billing =
|
|
@@ -185,7 +300,7 @@ function checkWorkflowAdapter(adapter: AdapterName): Check {
|
|
|
185
300
|
? "billing: subscription"
|
|
186
301
|
: "billing: subscription (login unverifiable, CLI is the authority)"
|
|
187
302
|
: `billing: ${probe.mode} (${probe.apiKeySource})`;
|
|
188
|
-
return { name, severity: "ok", detail: `${r.out} — ${billing}` };
|
|
303
|
+
return { check: { name, severity: "ok", detail: `${r.out} — ${billing}` }, installed: true };
|
|
189
304
|
}
|
|
190
305
|
|
|
191
306
|
function checkNode(): Check {
|
|
@@ -331,13 +446,22 @@ function checkHarneryDir(): Check {
|
|
|
331
446
|
* loadAdapterWiring — so a bare settings file never false-warns. The remedy is
|
|
332
447
|
* always the same: re-run `<bin> init` (idempotent, additive).
|
|
333
448
|
*/
|
|
334
|
-
function checkAdapterHooks(): Check {
|
|
449
|
+
function checkAdapterHooks(installedAdapters: AdapterId[] = []): Check {
|
|
335
450
|
const root = findCoordProjectRoot();
|
|
336
451
|
if (!root) {
|
|
337
452
|
return { name: "adapter hooks", severity: "ok", detail: "n/a (no .harnery/ above cwd)" };
|
|
338
453
|
}
|
|
339
454
|
const drift = loadAdapterWiring(root);
|
|
340
|
-
|
|
455
|
+
// A adapter with zero harnery hooks is only worth flagging when the project
|
|
456
|
+
// plainly uses harnery hooks (something else is wired) AND that adapter's CLI
|
|
457
|
+
// is installed. Both conditions matter: the first keeps a project that has
|
|
458
|
+
// never run init quiet, the second keeps a project that simply doesn't have
|
|
459
|
+
// Codex quiet. What's left is the case that reads as healthy but isn't — an
|
|
460
|
+
// agent can start a session through that adapter and register nothing.
|
|
461
|
+
const summary = summarizeAdapterWiring(root);
|
|
462
|
+
const unwired =
|
|
463
|
+
summary.wired.length > 0 ? summary.unwired.filter((id) => installedAdapters.includes(id)) : [];
|
|
464
|
+
if (drift.length === 0 && unwired.length === 0) {
|
|
341
465
|
return { name: "adapter hooks", severity: "ok", detail: "wired + current" };
|
|
342
466
|
}
|
|
343
467
|
const bin = resolveBinName(root);
|
|
@@ -356,12 +480,22 @@ function checkAdapterHooks(): Check {
|
|
|
356
480
|
}
|
|
357
481
|
return `${d.settingsFile}: ${bits.join("; ")}`;
|
|
358
482
|
});
|
|
483
|
+
for (const id of unwired) {
|
|
484
|
+
parts.push(`${ADAPTER_SPECS[id].settingsFile}: ${id} CLI installed, no harnery hooks wired`);
|
|
485
|
+
}
|
|
359
486
|
const needsManualRepair = drift.some(
|
|
360
487
|
(d) => d.parseError || d.invalidTopLevelKeys.length > 0 || d.invalidEventKeys.length > 0,
|
|
361
488
|
);
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
489
|
+
const wireHints = unwired.map((id) => `\`${bin} init --adapter ${id}\``);
|
|
490
|
+
let hint: string;
|
|
491
|
+
if (needsManualRepair) {
|
|
492
|
+
hint = `repair the invalid adapter settings, then run \`${bin} init\` to migrate harnery hooks`;
|
|
493
|
+
} else if (drift.length > 0) {
|
|
494
|
+
hint = `run \`${bin} init\` to migrate the hook set (idempotent)`;
|
|
495
|
+
if (wireHints.length > 0) hint += `; ${wireHints.join(" and ")} to wire the rest`;
|
|
496
|
+
} else {
|
|
497
|
+
hint = `run ${wireHints.join(" and ")} (idempotent, additive)`;
|
|
498
|
+
}
|
|
365
499
|
return { name: "adapter hooks", severity: "warn", detail: parts.join(" | "), hint };
|
|
366
500
|
}
|
|
367
501
|
|
package/src/commands/init.ts
CHANGED
|
@@ -36,12 +36,9 @@ import {
|
|
|
36
36
|
applyInstructions,
|
|
37
37
|
checkInstructions,
|
|
38
38
|
} from "../lib/instructions/apply.ts";
|
|
39
|
+
import { applyGitHooks, checkGitHooks } from "../lib/instructions/git-hooks.ts";
|
|
39
40
|
import { HostAddendumError } from "../lib/instructions/host-addendum.ts";
|
|
40
41
|
|
|
41
|
-
// Re-exported for back-compat: callers (deinit, tests) import these names
|
|
42
|
-
// from init.ts. The definitions now live in core/hooks/adapter/wiring.ts.
|
|
43
|
-
export type { HookGroup, SettingsFile };
|
|
44
|
-
|
|
45
42
|
// This file is src/commands/init.ts → harnery package root is two levels up.
|
|
46
43
|
const HARNERY_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
47
44
|
|
|
@@ -85,15 +82,19 @@ export function registerInitCommand(program: Command, emit: EmitContext, binName
|
|
|
85
82
|
// ── --check: read-only drift report on the block + skills ──────────────
|
|
86
83
|
if (opts.check === true) {
|
|
87
84
|
const { status, issues } = checkInstructions(projectRoot, { binName: bin, adapter });
|
|
85
|
+
const gitHooks = checkGitHooks(projectRoot);
|
|
86
|
+
if (gitHooks.status !== "fresh") issues.push(...gitHooks.issues);
|
|
87
|
+
const merged =
|
|
88
|
+
status === "error" ? "error" : gitHooks.status !== "fresh" ? "drift" : status;
|
|
88
89
|
const head =
|
|
89
|
-
|
|
90
|
-
? "harn init --check: instructions + skills are current"
|
|
91
|
-
:
|
|
90
|
+
merged === "fresh"
|
|
91
|
+
? "harn init --check: instructions + skills + git hooks are current"
|
|
92
|
+
: merged === "drift"
|
|
92
93
|
? "harn init --check: drift found (re-run `init` to refresh)"
|
|
93
94
|
: "harn init --check: error";
|
|
94
95
|
const lines = issues.length ? `\n${issues.map((i) => ` ✗ ${i}`).join("\n")}` : "";
|
|
95
96
|
emit.text(`${head}${lines}`);
|
|
96
|
-
emit.setExitCode(
|
|
97
|
+
emit.setExitCode(merged === "fresh" ? 0 : merged === "drift" ? 2 : 1);
|
|
97
98
|
return;
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -174,6 +175,8 @@ export function registerInitCommand(program: Command, emit: EmitContext, binName
|
|
|
174
175
|
`${removed} legacy hook(s) in ${rel(projectRoot, settingsPath)} (${already} already present)`,
|
|
175
176
|
);
|
|
176
177
|
}
|
|
178
|
+
const authorizationReview = codexHookReviewAction(adapter);
|
|
179
|
+
if (authorizationReview) actions.push(authorizationReview);
|
|
177
180
|
|
|
178
181
|
// ── 3. agent-facing instructions block + skills ────────────────────────
|
|
179
182
|
// A misconfigured host addendum aborts here rather than half-writing: the
|
|
@@ -190,10 +193,26 @@ export function registerInitCommand(program: Command, emit: EmitContext, binName
|
|
|
190
193
|
}
|
|
191
194
|
actions.push(...applied.actions);
|
|
192
195
|
|
|
193
|
-
|
|
196
|
+
// ── 4. git-hook managed regions ────────────────────────────────────────
|
|
197
|
+
// Same lifecycle contract as the instructions block: the coordination
|
|
198
|
+
// content of the consumer's git hooks is machine-owned and versioned;
|
|
199
|
+
// everything else in those files belongs to the host.
|
|
200
|
+
const gitHooks = applyGitHooks(projectRoot, { dryRun });
|
|
201
|
+
actions.push(...gitHooks.actions);
|
|
202
|
+
|
|
203
|
+
emit.text(render(projectRoot, dryRun, actions, [...applied.warnings, ...gitHooks.warnings]));
|
|
194
204
|
});
|
|
195
205
|
}
|
|
196
206
|
|
|
207
|
+
export function codexHookReviewAction(adapter: AdapterId): string | null {
|
|
208
|
+
if (adapter !== "codex") return null;
|
|
209
|
+
return (
|
|
210
|
+
"! Codex authorizes hook commands separately. Before starting work, review them in " +
|
|
211
|
+
"the terminal UI with `/hooks` or in Codex Desktop under Settings > Hooks. " +
|
|
212
|
+
"After approval, start a fresh task so SessionStart can run."
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
197
216
|
/**
|
|
198
217
|
* Merge agent-hook entries into a adapter settings object in place, idempotently.
|
|
199
218
|
* Preserves every existing hook; rewrites events already wired to
|