greprag 5.78.1 → 5.78.6

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.
@@ -1,232 +0,0 @@
1
- "use strict";
2
- /** Coordinate-GATE — the high-value half of the cross-session coordination reflex.
3
- * Fires at the moment of a risky SHARED-STATE action (git merge / push / deploy)
4
- * and, if another live session is working in THIS repo, compels the agent to
5
- * coordinate with that peer BEFORE the irreversible action runs. Automates the
6
- * manual "notice the peer, message them, then merge" handshake.
7
- *
8
- * SEAM (forward-compat — agreed with ec71d875/36ef8cb8): the EVAL+message
9
- * (runCoordinateGate) is decoupled from the trigger SOURCE. The PreToolUse
10
- * adapter (triggerFromPreToolUse) is the only piece that knows the tool-call
11
- * shape; it produces a source-agnostic CoordinateTrigger. The eval does a FRESH
12
- * watcher read and builds the directive without ever seeing the source. The
13
- * ddb7dcc8 ingress-trigger bridge will add a SECOND source (message-ingress /
14
- * stress-threshold / registry-change) that produces its own CoordinateTrigger
15
- * and calls this same eval — purely additive, no refactor of the gate.
16
- *
17
- * WHY NOT A guard.ts MATCHSET RULE: the matchset dispatcher is local-only/pure
18
- * (no network on the hot path — the D5 "ad-blocker" model) and injects STATIC
19
- * rule text. This gate needs a FRESH `inbox watchers` network read at fire time
20
- * (a stale roster at merge is the exact failure it prevents), which a static
21
- * matchset cannot express. So it is a hand-wired PreToolUse hook like
22
- * collision-check — the documented fallback in the chip spec. The bridge's
23
- * shared eval will likewise need this off-hot-path read, so this module is its
24
- * home too. docs/collision-schematic.md, docs/harness-control-point-matrix.md
25
- *
26
- * NO DEDUP: every fire does a fresh read. Unlike the SessionStart roster (which
27
- * dedups to stay quiet), the merge gate must reflect live truth each time.
28
- *
29
- * EFFECT is the adapter's choice, not the eval's: Claude/OpenCode inject the
30
- * fresh roster as context. Codex keeps delivery coordination in the native
31
- * agent/tool layer through the Delivery announce, because shell hooks cannot
32
- * observe native task coordination. An ingress-source wrapper would inject
33
- * instead. The eval below only produces the message.
34
- *
35
- * Shared-file Write was CONSIDERED (chip spec) but deferred: classifying a
36
- * write as "shared" is ambiguous, and a fresh network read on every Write/Edit
37
- * is too costly/noisy for the value — merge/push/deploy is where irreversible
38
- * shared-state mutation actually happens. The eval is ready for a future Write
39
- * adapter; only a new triggerFrom* + a `Write|Edit` matcher would be needed. */
40
- Object.defineProperty(exports, "__esModule", { value: true });
41
- exports.hasGitSubcommand = hasGitSubcommand;
42
- exports.classifyRiskyCommand = classifyRiskyCommand;
43
- exports.triggerFromPreToolUse = triggerFromPreToolUse;
44
- exports.buildCoordinateDirective = buildCoordinateDirective;
45
- exports.resolveCollisionPeers = resolveCollisionPeers;
46
- exports.runCoordinateGate = runCoordinateGate;
47
- const collision_check_1 = require("./collision-check");
48
- /** Risky shared-state command signatures. Each segment of a (possibly chained)
49
- * Bash command is tested against these. SPECIFIC to the Bash/PreToolUse source. */
50
- const RISKY_PATTERNS = [
51
- { kind: 'merge', label: 'gh pr merge', re: /^gh\s+pr\s+merge\b/ },
52
- { kind: 'deploy', label: 'wrangler deploy', re: /^(?:npx\s+)?wrangler\s+deploy\b/ },
53
- { kind: 'deploy', label: 'npm run deploy', re: /^npm\s+run\s+deploy\b/ },
54
- { kind: 'deploy', label: 'npm publish', re: /^npm\s+publish\b/ },
55
- { kind: 'deploy', label: 'gh release create', re: /^gh\s+release\s+create\b/ },
56
- ];
57
- /** Split a shell command on executable separators so `cd x && git push` is
58
- * caught, while regex/search patterns like `"before; git push; after"` stay a
59
- * single argument. */
60
- function commandSegments(command) {
61
- const segments = [];
62
- let current = '';
63
- let quote = null;
64
- let escaped = false;
65
- const flush = () => {
66
- const trimmed = current.trim();
67
- if (trimmed)
68
- segments.push(trimmed);
69
- current = '';
70
- };
71
- for (let i = 0; i < command.length; i++) {
72
- const ch = command[i];
73
- const next = command[i + 1];
74
- if (escaped) {
75
- current += ch;
76
- escaped = false;
77
- continue;
78
- }
79
- if (ch === '\\' && quote !== "'") {
80
- current += ch;
81
- escaped = true;
82
- continue;
83
- }
84
- if (quote) {
85
- current += ch;
86
- if (ch === quote)
87
- quote = null;
88
- continue;
89
- }
90
- if (ch === '"' || ch === "'") {
91
- quote = ch;
92
- current += ch;
93
- continue;
94
- }
95
- if (ch === '\n' || ch === ';' || ch === '|') {
96
- flush();
97
- if ((ch === '|' && next === '|'))
98
- i++;
99
- continue;
100
- }
101
- if (ch === '&' && next === '&') {
102
- flush();
103
- i++;
104
- continue;
105
- }
106
- current += ch;
107
- }
108
- flush();
109
- return segments;
110
- }
111
- /** Parse enough shell words to locate a git subcommand after global options.
112
- * Codex worktrees routinely use `git -C <main> merge ...`; matching only
113
- * `^git merge` left the most common Codex landing shape outside the gate. */
114
- function shellWords(segment) {
115
- const matches = segment.match(/"(?:\\.|[^"])*"|'[^']*'|[^\s]+/g) || [];
116
- return matches.map(word => {
117
- if ((word.startsWith('"') && word.endsWith('"'))
118
- || (word.startsWith("'") && word.endsWith("'"))) {
119
- return word.slice(1, -1);
120
- }
121
- return word;
122
- });
123
- }
124
- function classifyGit(tokens) {
125
- if (tokens[0] !== 'git')
126
- return null;
127
- let i = 1;
128
- const optionsWithValue = new Set([
129
- '-C', '-c', '--exec-path', '--git-dir', '--work-tree', '--namespace', '--super-prefix', '--config-env',
130
- ]);
131
- while (i < tokens.length && tokens[i].startsWith('-')) {
132
- const option = tokens[i++];
133
- if (optionsWithValue.has(option) && i < tokens.length)
134
- i++;
135
- }
136
- const subcommand = tokens[i];
137
- if (subcommand === 'merge')
138
- return { kind: 'merge', label: 'git merge' };
139
- if (subcommand === 'rebase')
140
- return { kind: 'merge', label: 'git rebase' };
141
- if (subcommand === 'push')
142
- return { kind: 'push', label: 'git push' };
143
- return null;
144
- }
145
- /** True when any executable command segment invokes the requested Git
146
- * subcommand, including `git -C <worktree> <subcommand>`. Exported so lifecycle
147
- * hooks can key off agent-generated Git actions instead of user prompt words. */
148
- function hasGitSubcommand(command, expected) {
149
- if (!command || !expected)
150
- return false;
151
- for (const seg of commandSegments(command)) {
152
- const tokens = shellWords(seg);
153
- if (tokens[0] !== 'git')
154
- continue;
155
- let i = 1;
156
- const optionsWithValue = new Set([
157
- '-C', '-c', '--exec-path', '--git-dir', '--work-tree', '--namespace', '--super-prefix', '--config-env',
158
- ]);
159
- while (i < tokens.length && tokens[i].startsWith('-')) {
160
- const option = tokens[i++];
161
- if (optionsWithValue.has(option) && i < tokens.length)
162
- i++;
163
- }
164
- if (tokens[i] === expected)
165
- return true;
166
- }
167
- return false;
168
- }
169
- /** Classify a Bash command into a risky-action trigger, or null. PURE. Matches
170
- * the FIRST risky segment (a chained command fires on whichever risky verb
171
- * appears first). */
172
- function classifyRiskyCommand(command) {
173
- if (!command)
174
- return null;
175
- for (const seg of commandSegments(command)) {
176
- const git = classifyGit(shellWords(seg));
177
- if (git)
178
- return git;
179
- for (const p of RISKY_PATTERNS) {
180
- if (p.re.test(seg))
181
- return { kind: p.kind, label: p.label };
182
- }
183
- }
184
- return null;
185
- }
186
- /** PreToolUse adapter: extract a CoordinateTrigger from a tool call, or null.
187
- * SPECIFIC to the PreToolUse event shape — the only source-aware piece. Only
188
- * Bash calls carry a risky command; everything else returns null (cheap: the
189
- * hook never reaches the network for a non-risky call). */
190
- function triggerFromPreToolUse(toolName, toolInput) {
191
- if (toolName !== 'Bash')
192
- return null;
193
- const command = typeof toolInput.command === 'string' ? toolInput.command : '';
194
- return classifyRiskyCommand(command);
195
- }
196
- /** The LOUD coordinate directive — names the action + the live same-repo peers
197
- * and tells the agent to message them BEFORE proceeding. PURE, source-agnostic
198
- * (takes a CoordinateTrigger, not a PreToolUse event). */
199
- function buildCoordinateDirective(trigger, peers, myShort, alias) {
200
- const handle = alias || '<handle>';
201
- const plural = peers.length === 1 ? '' : 's';
202
- const list = peers.map(p => p.short).join(', ');
203
- return (`COORDINATE before ${trigger.label}: ${peers.length} peer${plural} live in this repo (${list}). `
204
- + `Ping before proceeding. Codex peers: use codex_app.list_threads and codex_app.send_message_to_thread. `
205
- + `GrepRAG watcher/cross-harness peers: greprag send "heads up, about to ${trigger.label}" `
206
- + `--to ${handle}@greprag.com/<8hex> --from-session ${myShort}`);
207
- }
208
- /** FRESH watcher read → the same-repo peers live right now (the I/O half, kept out
209
- * of the pure module). NO dedup, NO local state: a fresh read every call. Empty on
210
- * any failure / no peer (fail-open). The collision Match-on-command module
211
- * (collision-reminder.ts) calls this in the hook, then detects purely on the result. */
212
- async function resolveCollisionPeers(opts) {
213
- try {
214
- const watchers = await (0, collision_check_1.fetchTenantWatchers)(opts.apiUrl, opts.apiKey);
215
- if (watchers.length === 0)
216
- return [];
217
- return (0, collision_check_1.detectCollisions)(opts.short, opts.projectId, opts.projectName, watchers);
218
- }
219
- catch {
220
- return [];
221
- }
222
- }
223
- /** Source-agnostic eval+message. Given a trigger from ANY source, resolve live
224
- * same-repo peers and return the LOUD directive — or null if none. Retained for
225
- * the existing PreToolUse path + tests; the registry now reaches the same result
226
- * via resolveCollisionPeers + the collision module. */
227
- async function runCoordinateGate(trigger, opts) {
228
- const peers = await resolveCollisionPeers(opts);
229
- if (peers.length === 0)
230
- return null;
231
- return buildCoordinateDirective(trigger, peers, opts.short, opts.alias);
232
- }