balladeer 1.0.16 → 1.0.18

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 (58) hide show
  1. package/dist/anchor-budget.d.ts +40 -0
  2. package/dist/anchor-budget.js +110 -0
  3. package/dist/anchoring.d.ts +241 -0
  4. package/dist/anchoring.js +766 -0
  5. package/dist/anchors-client.d.ts +62 -0
  6. package/dist/anchors-client.js +197 -0
  7. package/dist/anchors-schema.d.ts +85 -0
  8. package/dist/anchors-schema.js +246 -0
  9. package/dist/cli.d.ts +1 -1
  10. package/dist/cli.js +24 -7
  11. package/dist/commands/anchor-usage.d.ts +6 -0
  12. package/dist/commands/anchor-usage.js +19 -0
  13. package/dist/commands/anchor.d.ts +127 -0
  14. package/dist/commands/anchor.js +303 -0
  15. package/dist/commands/guidance.d.ts +2 -1
  16. package/dist/commands/guidance.js +55 -0
  17. package/dist/commands/judge.d.ts +219 -12
  18. package/dist/commands/judge.js +911 -103
  19. package/dist/commands/map.d.ts +46 -2
  20. package/dist/commands/map.js +244 -5
  21. package/dist/commands/mcp.js +2 -2
  22. package/dist/guidance-hook.mjs +282 -95
  23. package/dist/guidance.d.ts +7 -0
  24. package/dist/guidance.js +10 -1
  25. package/dist/headless-agent.d.ts +19 -1
  26. package/dist/headless-agent.js +22 -5
  27. package/dist/hook-trust.d.ts +41 -9
  28. package/dist/hook-trust.js +98 -16
  29. package/dist/judge-brief.d.ts +33 -3
  30. package/dist/judge-brief.js +39 -2
  31. package/dist/judge-hook.d.ts +188 -14
  32. package/dist/judge-hook.js +919 -61
  33. package/dist/judge-said.d.ts +52 -0
  34. package/dist/judge-said.js +181 -0
  35. package/dist/promise-meaning.d.ts +25 -0
  36. package/dist/promise-meaning.js +24 -7
  37. package/dist/relay.d.ts +71 -0
  38. package/dist/relay.js +193 -0
  39. package/dist/remove-earlier.js +4 -2
  40. package/dist/risk/git.d.ts +3 -1
  41. package/dist/risk/git.js +3 -3
  42. package/dist/risk/graph-cache.d.ts +83 -0
  43. package/dist/risk/graph-cache.js +291 -0
  44. package/dist/risk/import-graph.d.ts +43 -0
  45. package/dist/risk/import-graph.js +88 -34
  46. package/dist/risk/index.d.ts +1 -1
  47. package/dist/risk/index.js +1 -1
  48. package/dist/risk/pipeline.d.ts +13 -0
  49. package/dist/risk/pipeline.js +15 -4
  50. package/dist/risk/score.d.ts +10 -0
  51. package/dist/risk/score.js +11 -2
  52. package/dist/scratch-worktree.d.ts +5 -1
  53. package/dist/scratch-worktree.js +9 -2
  54. package/dist/user-scope.d.ts +74 -3
  55. package/dist/user-scope.js +270 -9
  56. package/dist/wire.d.ts +19 -3
  57. package/dist/wire.js +2 -2
  58. package/package.json +7 -2
@@ -1,20 +1,30 @@
1
1
  /**
2
- * Where a judge run starts: a coding host's PreToolUse hook, or git's own
3
- * pre-push hook.
2
+ * Where a judge run starts: a coding host's hook around a shell command, or
3
+ * git's own pre-push hook.
4
4
  *
5
- * The host hook fires on every shell command the agent runs, so the first job
6
- * is to say "not a push" and get out of the way in milliseconds. Only `git
7
- * push`, `gh pr create` and `gh pr merge` go further. The command is read the
8
- * way a shell reads it, so a push inside a quoted commit message is not a push
9
- * and `git stash push` is not one either.
5
+ * The host hook fires on every shell command the agent runs, twice: before it
6
+ * runs (PreToolUse) and after (PostToolUse). The first job each time is to say
7
+ * "not a push" and get out of the way in milliseconds. Only `git push`, `gh pr
8
+ * create` and `gh pr merge` go further. The command is read the way a shell
9
+ * reads it, so a push inside a quoted commit message is not a push and `git
10
+ * stash push` is not one either.
11
+ *
12
+ * Report mode judges after the push, so the push is never held and the check
13
+ * sees exactly what was pushed. Block mode judges before it, because a push
14
+ * can only be held before it runs.
10
15
  */
11
16
  export type HookHost = "claude" | "codex" | "git";
17
+ /** The two host events the judge is installed on, in the order a command meets them. */
18
+ export type HookEvent = "PreToolUse" | "PostToolUse";
19
+ export declare const JUDGE_HOOK_EVENTS: readonly HookEvent[];
12
20
  export type HookInput = Readonly<{
13
21
  eventName?: string;
14
22
  toolName?: string;
15
23
  command?: string;
16
24
  cwd?: string;
17
25
  model?: string;
26
+ /** The host's session, from `session_id`, which both Claude Code and Codex send. */
27
+ sessionId?: string;
18
28
  }>;
19
29
  /** A coding host's hook payload, or nothing when the text is not one. */
20
30
  export declare function parseHookInput(text: string): HookInput | undefined;
@@ -26,17 +36,118 @@ export type PushedRef = Readonly<{
26
36
  remoteSha: string;
27
37
  }>;
28
38
  export declare function parsePrePushInput(text: string): PushedRef[];
39
+ /** What separates or groups commands in a shell line. */
40
+ export type ShellOperator = ";" | "&&" | "||" | "|" | "&" | "\n" | "(" | ")" | "`";
41
+ /** One piece of a shell line: a simple command's words, or what comes between commands. */
42
+ export type ShellPiece = Readonly<{
43
+ kind: "command";
44
+ words: readonly string[];
45
+ }> | Readonly<{
46
+ kind: "operator";
47
+ operator: ShellOperator;
48
+ }>;
29
49
  /**
30
- * The simple commands a shell line runs, as word lists.
50
+ * A shell line in the order a shell reads it: simple commands and the
51
+ * operators between them.
52
+ *
53
+ * Quotes and backslashes are honored, so quoted text stays one word. `;`,
54
+ * `&&`, `||`, `|`, `&`, newlines, parentheses and backquotes come between
55
+ * commands. A comment runs to the end of its line. A redirection (`>`, `2>&1`,
56
+ * `<file`, `&>log`) and its target are left out of the command's words.
31
57
  *
32
- * Quotes and backslashes are honored, so quoted text stays one word; `;`,
33
- * `&&`, `||`, `|`, `&`, newlines and parentheses end a command, so `npm test
34
- * && git push` is two commands and `$(git push)` is found. A comment runs to
35
- * the end of its line.
58
+ * A here-document (`<<WORD`, `<<'WORD'`, `<<"WORD"`, `<<-WORD`) is data: the
59
+ * lines after its command, up to a line that is exactly WORD (leading tabs
60
+ * removed for `<<-`), are not commands. A here-string's word (`<<<`) is data
61
+ * too. Two exceptions keep a push inside one from going unseen: text fed to a
62
+ * shell that reads its script from input (`bash <<EOF`, `cat <<EOF | sh`) is
63
+ * read as that script, and the `$(...)` and backquotes in a here-document
64
+ * whose delimiter is not quoted are read as the commands the shell runs while
65
+ * expanding it. Either is read as a subshell, after the pipeline it feeds. A
66
+ * here-document that never reaches its delimiter line is not taken as one, so
67
+ * the lines after it are still read as commands.
68
+ */
69
+ export declare function scanShell(line: string, depth?: number): ShellPiece[];
70
+ /**
71
+ * The simple commands a shell line runs, as word lists, in the order
72
+ * `scanShell` reads them: `npm test && git push` is two commands, `$(git
73
+ * push)` is found, and a here-document's text is not a command.
36
74
  */
37
75
  export declare function shellCommands(line: string): string[][];
76
+ /** Where one push, pull request creation or merge in a shell line runs. */
77
+ export type PushSite = Readonly<{
78
+ kind: "push" | "create" | "merge";
79
+ /** The absolute directory it runs in; absent when that cannot be told for certain. */
80
+ directory?: string;
81
+ /** The repository a pull request command names with `--repo`, `-R` or `GH_REPO`. */
82
+ repo?: string;
83
+ }>;
84
+ /**
85
+ * Every push, pull request creation and merge in a shell line, with the
86
+ * directory it runs in when that can be told for certain.
87
+ *
88
+ * Starting from `cwd`, each `cd` moves the line along in order, and a git
89
+ * command's own `-C` options apply to that command alone. A `cd` inside
90
+ * parentheses, backquotes or `$(...)` ends with them. Anything that makes the
91
+ * directory uncertain makes it unknown from there on rather than guessed: `cd`
92
+ * with no directory, `cd -`, a variable or pattern, `pushd` or `popd`, a `cd`
93
+ * in a pipeline or in the background, a `cd` inside `if`, a loop, `case` or a
94
+ * function, and a `cd` that ran only when an earlier command succeeded or
95
+ * failed, once the line goes past its `&&` chain. A shell's `-c` script starts
96
+ * where the shell does and moves nothing outside it.
97
+ */
98
+ export declare function pushSites(line: string, cwd: string | undefined, depth?: number): PushSite[];
99
+ /** Where a shell line's pushes run, when that is one directory that can be told for certain. */
100
+ export type PushPlace = Readonly<{
101
+ known: true;
102
+ directory: string;
103
+ sites: readonly PushSite[];
104
+ }> | Readonly<{
105
+ known: false;
106
+ }>;
107
+ /**
108
+ * The one directory a shell line's pushes run in, starting from `cwd`, or
109
+ * unknown when any of them cannot be told or they run in more than one.
110
+ */
111
+ export declare function pushPlace(line: string, cwd: string): PushPlace;
112
+ /**
113
+ * A repository as `owner/name` in lower case, from a remote URL or a `--repo`
114
+ * value: `https://github.com/o/r.git`, `git@github.com:o/r.git`,
115
+ * `ssh://git@host/o/r`, `HOST/o/r` or `o/r`. Nothing when there is no owner.
116
+ */
117
+ export declare function repositoryName(reference: string): string | undefined;
118
+ /** The repositories a checkout's remotes point at, from what `git remote -v` prints. */
119
+ export declare function remoteRepositoryNames(listing: string): Set<string>;
38
120
  /** Whether a shell line pushes: `git push`, `gh pr create`, or `gh pr merge`, anywhere in it. */
39
- export declare function isPushCommand(line: string, depth?: number): boolean;
121
+ export declare function isPushCommand(line: string): boolean;
122
+ /**
123
+ * Whether a shell line sends this checkout's commits: a `git push`, or a `gh pr
124
+ * create`, which pushes the branch it opens a pull request for. A line whose
125
+ * only push is `gh pr merge` sends none; that pull request's commits were on
126
+ * the remote before the line ran.
127
+ */
128
+ export declare function sendsCommits(line: string): boolean;
129
+ /**
130
+ * The first command in the line that makes a commit before it pushes, as
131
+ * `git <subcommand>`, or nothing.
132
+ *
133
+ * A host's PreToolUse hook sees the checkout before the line runs, so in
134
+ * `git commit -am x && git push` the check judges the code as it was before
135
+ * the commit, and the commit the push carries is one it never saw. Only a
136
+ * commit made before the push matters: one made after it is not in what was
137
+ * pushed, and `gh pr merge` merges a pull request's head, not this checkout's.
138
+ */
139
+ export declare function commitBeforePush(line: string): string | undefined;
140
+ /**
141
+ * What a `gh pr merge` names. Neither field means the current branch's pull
142
+ * request, which is the change in this checkout; either one can name a pull
143
+ * request whose change is somewhere else entirely.
144
+ */
145
+ export type MergeTarget = Readonly<{
146
+ selector?: string;
147
+ repo?: string;
148
+ }>;
149
+ /** Every `gh pr merge` in a shell line, with the pull request and repository it names. */
150
+ export declare function mergeTargets(line: string): MergeTarget[];
40
151
  export type HookResponse = Readonly<{
41
152
  stdout: string;
42
153
  stderr: string;
@@ -47,11 +158,14 @@ export type HookResponse = Readonly<{
47
158
  * and the push goes ahead; a block is exit 2 with the reason, which both hosts
48
159
  * treat as a refusal of that one command. Claude Code also gets the reason as
49
160
  * `permissionDecision: "deny"`, which it prefers to stderr; Codex is given the
50
- * stderr form its documentation names.
161
+ * stderr form its documentation names. After the command ran (PostToolUse)
162
+ * there is nothing left to hold, so the lines are only ever context, named for
163
+ * the event that asked.
51
164
  */
52
165
  export declare function hookResponse(host: HookHost, outcome: Readonly<{
53
166
  block: boolean;
54
167
  lines: readonly string[];
168
+ event?: HookEvent;
55
169
  }>): HookResponse;
56
170
  /** The status line both hosts show while the hook runs, and the mark of the entry this command owns. */
57
171
  export declare const JUDGE_HOOK_MARKER = "Balladeer judge";
@@ -78,7 +192,67 @@ export type HookInstallResult = Readonly<{
78
192
  status: "written" | "unchanged" | "refused";
79
193
  reason?: string;
80
194
  }>;
195
+ /** The hook file the installer writes for a host, relative to the repository root. */
196
+ export declare function judgeHookFile(host: "claude" | "codex"): string;
197
+ /**
198
+ * Whether the hook file the installer writes has this command's entry for one
199
+ * event. It reads the file rather than guessing, so a checkout installed by an
200
+ * earlier release, which wrote only the PreToolUse entry, is told apart from a
201
+ * current one. Anything unreadable counts as no entry.
202
+ */
203
+ export declare function hasJudgeEntry(root: string, host: "claude" | "codex", event: HookEvent): boolean;
204
+ /**
205
+ * Whether this checkout has the push check's own project entry for a host,
206
+ * under either event. Where it does, that entry owns the check in this
207
+ * checkout and the one setup writes at the host's user scope stands aside, so
208
+ * a push is judged once. Either event counts: a checkout an earlier release
209
+ * installed has only the entry before the push, and it keeps judging there.
210
+ */
211
+ export declare function hasProjectJudgeHook(root: string, host: "claude" | "codex"): boolean;
212
+ /**
213
+ * The checkout that holds a folder, found by walking up to its `.git` entry (a
214
+ * directory, or a file in a worktree). No process is started, so a folder that
215
+ * is not in a checkout at all is known in well under a millisecond.
216
+ */
217
+ export declare function checkoutRoot(start: string): string | undefined;
218
+ /**
219
+ * The push check's switch for this laptop, in Balladeer's own config home.
220
+ * Absent means on. Written only by `balladeer judge --push-check off`, and
221
+ * read by the user-scope entry on each push, so turning it off takes effect in
222
+ * sessions that are already open.
223
+ */
224
+ export declare const PUSH_CHECK_SETTING_FILE = "push-check.json";
225
+ export declare function pushCheckSettingPath(environment: NodeJS.ProcessEnv): string;
226
+ /** True only when this laptop's switch says off; anything unreadable leaves it on. */
227
+ export declare function pushCheckOff(environment: NodeJS.ProcessEnv): boolean;
228
+ /** Turn the user-scope push check off or back on for this laptop. */
229
+ export declare function setPushCheck(environment: NodeJS.ProcessEnv, on: boolean): void;
81
230
  /** Where git looks for hooks in this checkout, which is not `.git/hooks` in a worktree. */
82
231
  export declare function gitHooksDirectory(root: string): string;
83
232
  /** Install the judge hook for one host. Running it twice changes nothing the second time. */
84
233
  export declare function installJudgeHook(root: string, target: HookHost): HookInstallResult;
234
+ /**
235
+ * Whether a commit is on a remote now: reachable from a remote-tracking ref,
236
+ * or else the tip of a branch some remote reports.
237
+ *
238
+ * `git push`, `git push origin HEAD:branch` and the push `gh pr create` makes
239
+ * all move the remote-tracking ref of what they pushed, so the first check is
240
+ * local and immediate. The second is there because a push can land without
241
+ * that ref moving: Codex's workspace sandbox keeps `.git` read-only, so its
242
+ * pushes reach the remote and then fail to update `refs/remotes/...` (hook
243
+ * canary, 29 September 2026). Asking each remote for its branch tips settles
244
+ * it without fetching anything, and a push that failed leaves its commit on
245
+ * neither.
246
+ */
247
+ export declare function pushLanded(root: string, sha: string): Promise<boolean>;
248
+ /**
249
+ * Where the remote default branch stood before this push, when this push is
250
+ * what moved it to `head`; otherwise nothing, and the ordinary fork-point rule
251
+ * applies.
252
+ *
253
+ * Judged after a push straight to the default branch, the fork point with that
254
+ * branch's remote-tracking ref is `head` itself, so the change would shrink to
255
+ * its last commit. git records the push in the ref's reflog ("update by
256
+ * push"), and the entry before it is where the branch stood.
257
+ */
258
+ export declare function baseBeforePush(root: string, head: string): Promise<string | undefined>;