@davesheffer/hunch 1.39.2 → 1.40.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.
Files changed (69) hide show
  1. package/dist/cli/index.js +154 -64
  2. package/dist/cli/serve.d.ts +14 -0
  3. package/dist/cli/serve.js +18 -4
  4. package/dist/constitution/g2BehaviorCandidates.js +6 -1
  5. package/dist/constitution/g2Candidates.js +11 -1
  6. package/dist/constitution/structural.js +10 -0
  7. package/dist/core/correction.js +12 -7
  8. package/dist/core/delivery.d.ts +34 -0
  9. package/dist/core/delivery.js +60 -1
  10. package/dist/core/docanchors.js +195 -11
  11. package/dist/core/format.js +6 -1
  12. package/dist/core/glob.d.ts +25 -0
  13. package/dist/core/glob.js +43 -0
  14. package/dist/core/hookcache.d.ts +9 -2
  15. package/dist/core/hookcache.js +10 -3
  16. package/dist/core/paths.d.ts +48 -5
  17. package/dist/core/paths.js +89 -6
  18. package/dist/core/procstart.d.ts +94 -0
  19. package/dist/core/procstart.js +244 -0
  20. package/dist/core/served.d.ts +6 -3
  21. package/dist/core/served.js +10 -6
  22. package/dist/core/taskDelivery.js +27 -1
  23. package/dist/core/taskReport.d.ts +2 -0
  24. package/dist/core/taskReport.js +46 -11
  25. package/dist/core/taskReportEvidence.js +17 -1
  26. package/dist/core/taskReportHook.d.ts +19 -0
  27. package/dist/core/taskReportHook.js +54 -6
  28. package/dist/core/verifyLauncher.d.ts +21 -0
  29. package/dist/core/verifyLauncher.js +37 -0
  30. package/dist/core/workspace.d.ts +9 -0
  31. package/dist/core/workspace.js +15 -0
  32. package/dist/extractors/git.d.ts +5 -0
  33. package/dist/extractors/git.js +87 -10
  34. package/dist/extractors/indexer.d.ts +10 -0
  35. package/dist/extractors/indexer.js +56 -10
  36. package/dist/extractors/k8sManifest.d.ts +13 -0
  37. package/dist/extractors/k8sManifest.js +207 -23
  38. package/dist/extractors/landscapeDiscovery.js +9 -1
  39. package/dist/extractors/nativeTreeSitter.d.ts +24 -0
  40. package/dist/extractors/nativeTreeSitter.js +54 -1
  41. package/dist/extractors/parse.js +6 -1
  42. package/dist/integrations/claudemd.js +3 -3
  43. package/dist/integrations/hookmatch.d.ts +25 -0
  44. package/dist/integrations/hookmatch.js +58 -0
  45. package/dist/integrations/hooks.d.ts +79 -9
  46. package/dist/integrations/hooks.js +559 -46
  47. package/dist/integrations/providers.js +2 -17
  48. package/dist/integrations/scaffold.d.ts +1 -1
  49. package/dist/integrations/scaffold.js +24 -21
  50. package/dist/integrations/workspaceLedger.d.ts +10 -0
  51. package/dist/integrations/workspaceLedger.js +9 -1
  52. package/dist/mcp/server.d.ts +10 -0
  53. package/dist/mcp/server.js +32 -16
  54. package/dist/mcp/taskReportTools.d.ts +5 -9
  55. package/dist/mcp/taskReportTools.js +9 -20
  56. package/dist/serve/writelock.d.ts +21 -1
  57. package/dist/serve/writelock.js +157 -16
  58. package/dist/store/changeLedger.d.ts +9 -3
  59. package/dist/store/changeLedger.js +36 -10
  60. package/dist/store/hunchStore.d.ts +42 -2
  61. package/dist/store/hunchStore.js +105 -17
  62. package/dist/store/jsonStore.d.ts +6 -4
  63. package/dist/store/jsonStore.js +225 -32
  64. package/dist/store/merge.d.ts +7 -4
  65. package/dist/store/merge.js +65 -5
  66. package/dist/store/privateMigrate.js +10 -0
  67. package/dist/wiki/adopt.js +13 -2
  68. package/package.json +1 -1
  69. package/server.json +2 -2
@@ -1,10 +1,15 @@
1
1
  import { languageFor } from "./languages.js";
2
2
  import { loadNativeTreeSitter } from "./nativeTreeSitter.js";
3
- const { Parser } = loadNativeTreeSitter();
3
+ /** Resolved on first parse, not at import: loading the native addons copies six
4
+ * `.node` files into a temp dir and dlopens them (~1.5s+ cold), which every CLI
5
+ * command and every editor hook would otherwise pay just for importing this
6
+ * module. loadNativeTreeSitter() memoizes the runtime itself. */
7
+ const parserRuntime = () => loadNativeTreeSitter().Parser;
4
8
  const cache = new Map();
5
9
  function bundleFor(spec) {
6
10
  let b = cache.get(spec.grammarKey);
7
11
  if (!b) {
12
+ const Parser = parserRuntime();
8
13
  const parser = new Parser();
9
14
  const grammar = spec.loadGrammar();
10
15
  parser.setLanguage(grammar);
@@ -47,7 +47,7 @@ export function renderHunchSection(store, root) {
47
47
  lines.push("**Consult Hunch via the `hunch_*` MCP tools — pick by MOMENT, not from memory:**");
48
48
  lines.push("");
49
49
  lines.push("**Orient (session/task start):**");
50
- lines.push("- For a new user task, call `hunch_task(action: \"start\", title: <short task title>)` once and retain its `task_id`. Claude Code's prompt hook supplies a task ID natively — reuse its exact start arguments instead of creating another task (each new prompt has its own ID). Codex supplies one the same way once its `.codex/hooks.json` is trusted (`/hooks`). Hosts without prompt hooks (Windsurf, Cursor) never receive one: start the task yourself. Reuse the ID for follow-up work on the same task; never borrow another task's ID. This is task bookkeeping; `hunch_context` remains the first memory lookup. If reporting fails, continue the work and disclose the gap.");
50
+ lines.push("- If the host's prompt hook already opened the task and printed a task ID plus a `task verify` command, reuse that exact ID and command — do NOT call `hunch_task(action: \"start\")` for it. Otherwise start the task yourself: call `hunch_task(action: \"start\", title: <short task title>)` once and take `verification_argv` from its result. Each new prompt has its own ID; reuse the ID for follow-up work on the same task and never borrow another task's ID. This is task bookkeeping; `hunch_context` remains the first memory lookup. If reporting fails, continue the work and disclose the gap.");
51
51
  lines.push("- When the user asks to **update Hunch**, run `hunch update` from this repository root. It updates to the latest release and repairs all configured harness pins. Use `hunch update --global` to also update a global CLI alongside a repository dependency; reconnect active MCP sessions afterward.");
52
52
  lines.push("- `hunch_context(target, task_id)` — the minimal relevant slice for what you're about to do; a task phrase falls back to the closest graph matches. **Call FIRST** for memory. Include the current task ID on each context call so its contribution is inspectable.");
53
53
  lines.push("- `hunch_structure(target?)` — the indexed shape of the repo/dir/file/symbol — orient from the graph, not grep rounds.");
@@ -73,10 +73,10 @@ export function renderHunchSection(store, root) {
73
73
  lines.push("- `hunch_pr_impact(base?)` / `hunch_merge_verdict(...)` — a change's memory surface; would it re-open a closed bug?");
74
74
  lines.push("");
75
75
  lines.push("**Before the final response — make Hunch's contribution visible:**");
76
- lines.push("- When running a relevant check, use the exact verification_argv launcher returned by hunch_task start, followed by the check command and its arguments, from this worktree. It runs `hunch task verify <task_id> -- <command> [arguments]` using the same installation as MCP, avoiding stale global binaries. This retains the actual exit result and source snapshot; raw output is not stored. Do not rerun an expensive check solely for reporting; missing evidence stays unverified.");
76
+ lines.push("- When running a relevant check, use the exact launcher the prompt hook printed — or, on a host without one, the `verification_argv` returned by hunch_task start — followed by the check command and its arguments, from this worktree. It runs `hunch task verify <task_id> -- <command> [arguments]` using the same installation as MCP, avoiding stale global binaries. This retains the actual exit result and source snapshot; raw output is not stored. Do not rerun an expensive check solely for reporting; missing evidence stays unverified.");
77
77
  lines.push("- Include the current task_id when calling hunch_record_decision, hunch_record_correction, or hunch_record_finding. The save path records its actual memory home and verifies exact Git revisions when committing or pushing; never infer publication from a successful capture alone.");
78
78
  lines.push("- Before claiming an application, call `hunch_report(task_id)` and copy the exact occurrence_id, record_id and content_hash from application_references, adding an action you actually took. Never derive an occurrence ID by replacing a receipt prefix or use the task's scope hash as a record hash. If you did not apply a lesson, omit applications.");
79
- lines.push("- Call `hunch_task(action: \"finish\", task_id, applications?)` and include the returned contribution_card in your final response without the user asking. Copy the card verbatim, including its Evidence line (the command that renders the local report on demand) and the agent-reported label; the structured result contains the card even when the host hides text blocks. Do not replace it with a generic claim that Hunch helped. If presentation_enabled is false, omit the card. A delivered lesson or passing command alone does not prove causal impact.");
79
+ lines.push("- When this task actually used Hunch (a `hunch_*` call carrying the task_id, a verified check, Hunch hook context you acted on, or an application to claim), call `hunch_task(action: \"finish\", task_id, applications?)` and include the returned contribution_card in your final response without the user asking. Skip the finish call only when it used none of those AND the host's own stop hook closes the task and shows the evidence for you (its prompt-hook instruction says so); where no host hook closes the task, and for a task you started yourself with `hunch_task(action: \"start\")`, always finish it yourself. Copy the card verbatim, including its Evidence line (the command that renders the local report on demand) and the agent-reported label; the structured result contains the card even when the host hides text blocks. Do not replace it with a generic claim that Hunch helped. If presentation_enabled is false, omit the card. A delivered lesson or passing command alone does not prove causal impact.");
80
80
  lines.push("- If interrupted, finish with `outcome: \"interrupted\"` when possible. `hunch_report(task_id, html: true)` opens the evidence trail by generating a local file; it may contain private memory and is not a public export. If report tools are unavailable after an update, say so and reconnect the host rather than inventing a report.");
81
81
  lines.push("");
82
82
  lines.push("**Build the Constitution review queue:**");
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The single rule for "is this hook command Hunch's own?", shared by every hook
3
+ * writer (scaffold.ts for Claude Code's .claude/settings.json, providers.ts for
4
+ * Cursor/Codex/VS Code/Windsurf/Antigravity). One rule, because each writer
5
+ * deletes what it classifies as ours, and a writer that guesses wide deletes the
6
+ * user's hooks (con_8460b6770f).
7
+ */
8
+ /** Does one command string invoke Hunch's own hook?
9
+ *
10
+ * Anchored to the shapes hookCommand() / resolveInvocation() write — a Hunch
11
+ * launcher (the pinned npm package spec, or a …/dist|src/cli/index.js|ts path for
12
+ * source installs) plus a `hook` tail, tokens quoted or bare. The old unanchored
13
+ * /index\.(js|ts)/ + /\bhook\b/ pair classified FOREIGN entries like
14
+ * `node ./hook/index.js` as ours and silently deleted them, violating the
15
+ * leave-every-foreign-hook-in-place contract (con_8460b6770f, issue #41).
16
+ *
17
+ * `requireProvider` distinguishes the two writers. Provider configs always carry
18
+ * `hook --provider <name>`, so their tail must too; only the LEGACY fully-quoted
19
+ * form (written before the quoting fix, and by hunch versions that predate
20
+ * --provider) may omit it, and its quotes keep it unambiguous. Claude Code's own
21
+ * settings.json never carries --provider, so a bare `hook` tail is all that is
22
+ * left to match on — and a bare tail alone is far too weak, so there the WHOLE
23
+ * command must be one of the two shapes `hunch init` has ever written (see
24
+ * isClaudeCodeHookCommand). */
25
+ export declare function isHunchHookCommand(command: string, requireProvider: boolean): boolean;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The single rule for "is this hook command Hunch's own?", shared by every hook
3
+ * writer (scaffold.ts for Claude Code's .claude/settings.json, providers.ts for
4
+ * Cursor/Codex/VS Code/Windsurf/Antigravity). One rule, because each writer
5
+ * deletes what it classifies as ours, and a writer that guesses wide deletes the
6
+ * user's hooks (con_8460b6770f).
7
+ */
8
+ /** Does one command string invoke Hunch's own hook?
9
+ *
10
+ * Anchored to the shapes hookCommand() / resolveInvocation() write — a Hunch
11
+ * launcher (the pinned npm package spec, or a …/dist|src/cli/index.js|ts path for
12
+ * source installs) plus a `hook` tail, tokens quoted or bare. The old unanchored
13
+ * /index\.(js|ts)/ + /\bhook\b/ pair classified FOREIGN entries like
14
+ * `node ./hook/index.js` as ours and silently deleted them, violating the
15
+ * leave-every-foreign-hook-in-place contract (con_8460b6770f, issue #41).
16
+ *
17
+ * `requireProvider` distinguishes the two writers. Provider configs always carry
18
+ * `hook --provider <name>`, so their tail must too; only the LEGACY fully-quoted
19
+ * form (written before the quoting fix, and by hunch versions that predate
20
+ * --provider) may omit it, and its quotes keep it unambiguous. Claude Code's own
21
+ * settings.json never carries --provider, so a bare `hook` tail is all that is
22
+ * left to match on — and a bare tail alone is far too weak, so there the WHOLE
23
+ * command must be one of the two shapes `hunch init` has ever written (see
24
+ * isClaudeCodeHookCommand). */
25
+ export function isHunchHookCommand(command, requireProvider) {
26
+ if (!requireProvider)
27
+ return isClaudeCodeHookCommand(command);
28
+ const published = /@davesheffer\/hunch/.test(command);
29
+ const launcher = published || /(?:dist|src)[\\/]+cli[\\/]+index\.(?:js|ts)(?=["\s]|$)/.test(command);
30
+ const legacyTail = /\s"hook"(?:\s+"--provider"\s+"[a-z]+")?\s*$/.test(command);
31
+ return launcher && (legacyTail || /\s"?hook"?\s+"?--provider"?\s+"?[a-z]+"?\s*$/.test(command));
32
+ }
33
+ const ABSOLUTE = String.raw `(?:\/|[A-Za-z]:[\\/]|\\\\)`;
34
+ const CLI_ENTRY = String.raw `[\\/](?:dist|src)[\\/]+cli[\\/]+index\.(?:js|ts)`;
35
+ const SOURCE_HOOK = new RegExp(String.raw `^\s*(?:"?npx(?:\.cmd)?"?\s+"?tsx"?|"[^"]+"|\S+)\s+(?:"${ABSOLUTE}[^"]*${CLI_ENTRY}"|${ABSOLUTE}\S*${CLI_ENTRY})\s+"?hook"?\s*$`);
36
+ /** The bare-`hook` shapes written into .claude/settings.json, matched as a whole
37
+ * command so nothing chained before or after one can be swept out with it:
38
+ *
39
+ * - published: `npx -y --package=[hunch-exact@npm:]@davesheffer/hunch[@v] hunch hook`
40
+ * - source / `npm link` / dev: `<runtime> <ABSOLUTE …/dist|src/cli/index.js|ts> hook`
41
+ *
42
+ * Quoting is NOT the discriminator. shellInvocation() leaves a safe token bare, so
43
+ * since v1.21.1 a POSIX source install writes `/usr/bin/node /abs/dist/cli/index.js
44
+ * hook` with no quotes at all (quotes appear only for a space or a Windows
45
+ * backslash, and on every token before v1.21.1). Requiring them left our own hook
46
+ * in place and appended another on each `hunch init`. What every version shares is
47
+ * an absolute entry path — resolveInvocation() derives it from import.meta.url —
48
+ * and that is what keeps a tool which merely shares the layout
49
+ * (`node tools/lint/dist/cli/index.js hook`) foreign; deleting it was issue #310.
50
+ * A foreign tool invoked by ABSOLUTE path with this exact layout and a lone `hook`
51
+ * argument is still indistinguishable by string alone. */
52
+ function isClaudeCodeHookCommand(command) {
53
+ const published = /^\s*"?npx(?:\.cmd)?"?\s+/i.test(command)
54
+ && /--package=(?:hunch-exact@npm:)?@davesheffer\/hunch(?:@[^"\s]+)?(?=["\s])/.test(command)
55
+ && /\s"?hunch"?\s+"?hook"?\s*$/.test(command);
56
+ return published || SOURCE_HOOK.test(command);
57
+ }
58
+ //# sourceMappingURL=hookmatch.js.map
@@ -10,14 +10,45 @@ export interface HookInstall {
10
10
  /** The hook file written; for a non-writing result, the file the user should edit. */
11
11
  path: string;
12
12
  /** created/appended/updated/unchanged: the block is (now) in a file git reaches.
13
+ * kept-shared: the shared hook already runs a Hunch that is not inside a
14
+ * linked worktree, so it was deliberately left as it is rather than
15
+ * re-pointed at one (issue #316); a success, not a failure.
13
16
  * managed-elsewhere: a hook manager owns the hook — nothing was written.
14
- * unreachable: the existing hook ends in exec/exit before our block — nothing was written. */
15
- action: "created" | "appended" | "updated" | "unchanged" | "managed-elsewhere" | "unreachable";
17
+ * unreachable: the existing hook ends in exec/exit before our block, or the
18
+ * block in it has no closing marker and so cannot be replaced in place —
19
+ * nothing was written. */
20
+ action: "created" | "appended" | "updated" | "unchanged" | "kept-shared" | "managed-elsewhere" | "unreachable";
16
21
  manager?: HookManagerKind;
17
22
  /** Why nothing was written (non-writing actions only). */
18
23
  reason?: string;
19
24
  /** What to add to `path` by hand (non-writing actions only). */
20
25
  snippet?: string;
26
+ /** The file already carries a Hunch block — a dead one, or one that cannot be
27
+ * updated in place: the snippet REPLACES it rather than being added
28
+ * (non-writing actions only). */
29
+ stale?: boolean;
30
+ /** The block that could not be updated still RUNS as it is (it only lacks its
31
+ * closing marker), so the failure is this run's update, not the install. */
32
+ live?: boolean;
33
+ /** The block in the file deliberately runs the repo's EXISTING shared
34
+ * invocation instead of the caller's worktree-bound one (issue #316). Set on
35
+ * kept-shared, and on created/appended/updated when that substitution happened. */
36
+ sharedInvocation?: string;
37
+ /** The options the existing SHARED block carries that this run did not ask for
38
+ * and that were kept anyway — its own flags (BLOCK_FLAGS), e.g. a strict
39
+ * pre-commit guard that a worktree's advisory re-run must not silently
40
+ * downgrade, and the post-commit block's local-only provider line (named
41
+ * `HUNCH_SYNTH_PROVIDER=deterministic`). Set on kept-shared, and on updated
42
+ * when the run also added an option of its own. */
43
+ keptFlags?: string[];
44
+ /** The options this run ADDED to the existing shared block (updated only). */
45
+ addedFlags?: string[];
46
+ /** Set when a DEAD block was rebuilt from the repo's shared launcher and the
47
+ * previous block's own option flags were not requested again. Reported, not
48
+ * restored: the flags come from the caller's options, so re-adding one would
49
+ * invent a request nobody made — but losing a strict guard in silence is
50
+ * exactly the issue-#316 failure mode. */
51
+ droppedFlags?: string[];
21
52
  }
22
53
  /** Portable invocation for text that lands in a TRACKED file (a husky script, a
23
54
  * committed hooks dir, .pre-commit-config.yaml): the same exact-version npx
@@ -30,7 +61,23 @@ export declare const PORTABLE_HOOK_INVOCATION: string;
30
61
  * or a trailing top-level `if … else … fi` whose every branch ends in
31
62
  * `exec`/`exit` — the shape of the pre-commit framework's generated hook. */
32
63
  export declare function terminalExitLine(content: string): string | null;
33
- type BlockState = "installed" | "unreachable" | "missing";
64
+ type BlockState = "installed" | "unreachable" | "stale" | "missing";
65
+ /** The result of inspecting the command a managed block actually runs (issue #315). */
66
+ export type HookInvocationHealth = {
67
+ ok: true;
68
+ invocation: string;
69
+ } | {
70
+ ok: false;
71
+ invocation: string;
72
+ reason: string;
73
+ };
74
+ /** Whether the command in front of a managed block's subcommand is a Hunch
75
+ * launcher that could actually run (issue #315): a block whose command was
76
+ * hand-edited into something else, or whose absolute node/CLI path no longer
77
+ * exists after a reinstall, is present but dead. `command` is the text BEFORE
78
+ * the subcommand; env assignments and a leading `(` belong to the block's own
79
+ * shape and are stripped. Never spawns anything, never touches the network. */
80
+ export declare function hookInvocationHealth(command: string, root: string): HookInvocationHealth;
34
81
  export declare function installPostCommitHook(root: string, invocation: string, opts?: {
35
82
  private?: boolean;
36
83
  commit?: boolean;
@@ -64,8 +111,13 @@ export interface HookReportEntry {
64
111
  manager: HookManagerKind;
65
112
  /** The file where the block lives (or should live). */
66
113
  path: string;
67
- /** Why an `unreachable` block never runs. */
114
+ /** Why an `unreachable` block never runs, or why a `stale` one could not work. */
68
115
  reason?: string;
116
+ /** Where the hook points: the distinct invocations of its marks' blocks. */
117
+ invocation?: string;
118
+ /** Hunch's own option flags the hook's blocks carry (`--private`, `--commit`,
119
+ * `--strict`) — what a plain `hunch init` re-run would drop. Omitted when none. */
120
+ flags?: string[];
69
121
  }
70
122
  export interface HookReport {
71
123
  postCommit: HookReportEntry;
@@ -74,24 +126,42 @@ export interface HookReport {
74
126
  postCheckout: HookReportEntry;
75
127
  }
76
128
  /** Read-only diagnostic (used by `hunch doctor`): each managed hook's state —
77
- * `installed` (present where git will run it), `unreachable` (present, but
78
- * after an exec/exit or in a manager-owned file git never reaches), or
129
+ * `installed` (present where git will run it, running a Hunch launcher that
130
+ * exists), `unreachable` (present, but after an exec/exit or in a
131
+ * manager-owned file git never reaches), `stale` (present where git runs it,
132
+ * but its command is not a working Hunch launcher — issue #315), or
79
133
  * `missing`. postMerge requires BOTH halves (grounding-refresh and
80
134
  * repair-provenance) — a repo carrying only one is a partial install, same as
81
135
  * `installPostMergeHook` self-healing it. Never writes anything. */
82
136
  export declare function hookReport(root: string): HookReport;
83
137
  /** Boolean view of `hookReport`: a hook counts as installed only when its
84
- * managed block is present where git will actually run it, regardless of
85
- * whether the invocation inside it happens to be stale. An unreachable block
86
- * (issue #311) is NOT installed. */
138
+ * managed block is present where git will actually run it AND the command
139
+ * inside it is a Hunch launcher that exists. An unreachable block (issue #311)
140
+ * and a stale one (issue #315) are both NOT installed. */
87
141
  export declare function hookStatus(root: string): {
88
142
  postCommit: boolean;
89
143
  preCommit: boolean;
90
144
  postMerge: boolean;
91
145
  postCheckout: boolean;
92
146
  };
147
+ /** Doctor lines for where each installed/stale hook points (issue #315): hooks
148
+ * sharing one invocation share one line, and a line whose invocation is not the
149
+ * Hunch currently running says so. Informational — a hook may legitimately
150
+ * point at a different install. Pure; the caller adds indentation and dim(). */
151
+ export declare function hookInvocationLines(report: HookReport, running: string): string[];
93
152
  /** CLI lines for one install result: the usual ✓ line when the block is in a
94
153
  * file git runs, otherwise a warning with the reason and the snippet to add to
95
154
  * the manager's own file. */
96
155
  export declare function formatHookInstall(root: string, label: string, h: HookInstall, detail?: string): string[];
156
+ /** The closing line `hunch init` prints about the SHARED hooks dir (issue #316).
157
+ * Setup used to claim "sharing the repo's hooks + memory" unconditionally —
158
+ * true of memory, but the hooks dir is genuinely shared, so a block pointing
159
+ * inside a linked worktree dies for every checkout when that worktree goes.
160
+ *
161
+ * The verdict is read from what the shared blocks ACTUALLY run, not from this
162
+ * run's actions: a second `hunch init` writes nothing yet the hooks are just as
163
+ * broken, and the main checkout — which never used to get a note at all — is
164
+ * where the user can most easily fix it. `installs` only decides between the
165
+ * healthy variants. `[]` when the repo has no linked worktree. Read-only. */
166
+ export declare function sharedHooksNote(root: string, installs: HookInstall[]): string[];
97
167
  export {};