@coworker-jp/aidr 0.1.298 → 0.1.299
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/package.json +1 -1
- package/src/merge.mjs +67 -0
- package/src/templates.mjs +27 -2
package/package.json
CHANGED
package/src/merge.mjs
CHANGED
|
@@ -150,6 +150,73 @@ export function buildCursorHooksJson(scope = "project", absHome = "") {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
// Windsurf hooks.json — flat entries, one command per event (no matcher).
|
|
153
|
+
//
|
|
154
|
+
// NO `timeout` HERE, AND THAT IS DELIBERATE (#1896). Windsurf's hook entry
|
|
155
|
+
// schema has no such field: the documented entry keys are `command`,
|
|
156
|
+
// `powershell`, `show_output` and `working_directory`, and the page says
|
|
157
|
+
// nothing about any time limit at all (docs.windsurf.com/windsurf/cascade/hooks
|
|
158
|
+
// → docs.devin.ai/desktop/cascade/hooks, read 2026-08-25). The other four
|
|
159
|
+
// agents each declare one because their schemas accept it; Windsurf cannot,
|
|
160
|
+
// so this is a DECLARED inequality rather than an oversight — which is the
|
|
161
|
+
// whole reason this comment exists. Emitting an unknown key would not buy a
|
|
162
|
+
// cap and could be rejected by a future schema validation.
|
|
163
|
+
//
|
|
164
|
+
// What bounds a Windsurf hook instead: the in-script `run_scanner` cap
|
|
165
|
+
// (`AI_SCANNER_HOOK_TIMEOUT`, default 30s) plus `ensure_scanner.sh`'s
|
|
166
|
+
// `flock -w 120`. Those are OUR caps, so they hold on every editor — but
|
|
167
|
+
// there is no second, editor-side net here the way there is elsewhere.
|
|
168
|
+
// `tests/merge-hooks-json.test.mjs` pins both directions of that statement.
|
|
169
|
+
//
|
|
170
|
+
// NO `powershell` HERE EITHER, AND THAT IS A DECLARED INEQUALITY (#1907) —
|
|
171
|
+
// it is NOT a claim that Windows is covered.
|
|
172
|
+
//
|
|
173
|
+
// What the vendor actually documents (docs.windsurf.com/windsurf/cascade/hooks
|
|
174
|
+
// → docs.devin.ai/desktop/cascade/hooks, read 2026-08-25, quoted verbatim):
|
|
175
|
+
//
|
|
176
|
+
// command "The shell command to execute on **macOS/Linux** (run via
|
|
177
|
+
// `bash -c`). At least one of `command` or `powershell` must
|
|
178
|
+
// be specified."
|
|
179
|
+
// powershell "Optional. The command to execute on **Windows** (run via
|
|
180
|
+
// `powershell -Command`). If omitted on Windows, `command` is
|
|
181
|
+
// used as a fallback."
|
|
182
|
+
//
|
|
183
|
+
// and its cross-platform table, for the row we are actually in:
|
|
184
|
+
// | Windows | `command` ✓ | `powershell` ✗ | Falls back to `command` via `powershell -Command` |
|
|
185
|
+
//
|
|
186
|
+
// So on Windows the entry is NOT skipped. #1907 was filed on the assumption
|
|
187
|
+
// that it would be; the primary source says otherwise, and this comment is
|
|
188
|
+
// the corrected record. The real question is narrower and still open: what
|
|
189
|
+
// gets handed to `powershell -Command` is THE PATH OF A POSIX SHELL SCRIPT
|
|
190
|
+
// (`.windsurf/coworker-ai/pre_command.sh`), and every hook adapter this
|
|
191
|
+
// product ships — all five agents, 42 files — is a `.sh`. There is no
|
|
192
|
+
// Windows-native adapter anywhere in this tree to point a `powershell` key at.
|
|
193
|
+
//
|
|
194
|
+
// Why the outcome is invisible either way: the same page's exit-code table
|
|
195
|
+
// says `0` proceeds, `2` blocks, and "Any other | Error | Action proceeds
|
|
196
|
+
// normally". A fallback that fires and cannot run therefore produces the same
|
|
197
|
+
// user-visible result as a scan that ran and found nothing. Fail-open is
|
|
198
|
+
// deliberate, and it is exactly what hides this.
|
|
199
|
+
//
|
|
200
|
+
// UNMEASURED: whether that fallback reaches ai-scanner on a real Windows
|
|
201
|
+
// machine. This development host has no Windows machine with Windsurf on it.
|
|
202
|
+
//
|
|
203
|
+
// Two wrong moves, spelled out so nobody makes them from this comment alone:
|
|
204
|
+
// * emit `powershell: "bash <path>.sh"` — plausible, and unverified. If Git
|
|
205
|
+
// Bash is not on PATH it exits non-zero, which the table above turns into
|
|
206
|
+
// "proceeds normally" — the same silent gap, now with a tree that CLAIMS
|
|
207
|
+
// Windows is handled. A bare `bash` on Windows can also resolve to WSL's
|
|
208
|
+
// `C:\Windows\System32\bash.exe`, which sees a different filesystem than
|
|
209
|
+
// the path we wrote.
|
|
210
|
+
// * delete this comment because "the docs say it falls back" — the fallback
|
|
211
|
+
// is documented; the fallback WORKING is not.
|
|
212
|
+
//
|
|
213
|
+
// HOW THIS INEQUALITY RETIRES (so it cannot sit here forever): install
|
|
214
|
+
// Windsurf on a Windows machine, install these hooks, and drive a pre_* hook
|
|
215
|
+
// with an input the scanner MUST block. Judge on the block (exit 2), not on a
|
|
216
|
+
// log line or a telemetry field — fail-open makes "ran and allowed" and
|
|
217
|
+
// "never ran" byte-identical. Then either delete this block (it reaches the
|
|
218
|
+
// scanner) or add the `powershell` value the measurement showed to be right.
|
|
219
|
+
// `tests/merge-hooks-json.test.mjs` pins both directions until that day.
|
|
153
220
|
export function buildWindsurfHooksJson(scope = "project", absHome = "") {
|
|
154
221
|
const p = scope === "user" ? `${absHome}/.windsurf/coworker-ai` : ".windsurf/coworker-ai";
|
|
155
222
|
return {
|
package/src/templates.mjs
CHANGED
|
@@ -55,6 +55,14 @@ mkdir -p "$BIN_DIR"
|
|
|
55
55
|
|
|
56
56
|
# macOS has no base \`timeout\` (it's in GNU coreutils). Shim it so hooks work
|
|
57
57
|
# on fresh Macs without brew install coreutils.
|
|
58
|
+
#
|
|
59
|
+
# NOTE (issue #1879): the last branch — a bare macOS with neither \`timeout\` nor
|
|
60
|
+
# \`gtimeout\` — DROPS the duration and execs directly. On such a machine every
|
|
61
|
+
# \`timeout N "$BIN" ...\` below is unbounded, and the only remaining safety net
|
|
62
|
+
# is the editor's own hook timeout (Claude Code / Codex: 60s, written by
|
|
63
|
+
# merge.mjs; Cursor / Windsurf / Kiro: unverified). Installing coreutils is the
|
|
64
|
+
# documented remedy. The shim is deliberately kept — a hook that refused to run
|
|
65
|
+
# without coreutils would be worse than one that runs unbounded.
|
|
58
66
|
if ! command -v timeout >/dev/null 2>&1; then
|
|
59
67
|
if command -v gtimeout >/dev/null 2>&1; then
|
|
60
68
|
timeout() { gtimeout "$@"; }
|
|
@@ -63,6 +71,23 @@ if ! command -v timeout >/dev/null 2>&1; then
|
|
|
63
71
|
fi
|
|
64
72
|
fi
|
|
65
73
|
|
|
74
|
+
# Bounds for the two ai-scanner invocations this script makes. Both sit
|
|
75
|
+
# STRICTLY ABOVE the binary's own internal network bound for that subcommand,
|
|
76
|
+
# so the wrapper only ever fires on a genuinely hung binary and never pre-empts
|
|
77
|
+
# a slow-but-working one. (A bound that fires on healthy-but-slow would print
|
|
78
|
+
# the "we did NOT check" message spuriously — a false positive, which is how
|
|
79
|
+
# guards stop being read.)
|
|
80
|
+
#
|
|
81
|
+
# version: load_plan -> /verify, reqwest timeout 10s
|
|
82
|
+
# (docker/scanner/src/main.rs). 20s leaves 10s for process start.
|
|
83
|
+
# update: /verify (10s) THEN the binary download (30s), sequential
|
|
84
|
+
# (docker/scanner/src/main.rs cmd_update) = 40s internal worst case.
|
|
85
|
+
# 60s leaves 20s for hashing and the rename. Killing an update
|
|
86
|
+
# mid-flight is safe: it writes to a temp file in the same directory
|
|
87
|
+
# and only renames over \$BIN once the advertised hash matches.
|
|
88
|
+
VERSION_TIMEOUT=20
|
|
89
|
+
UPDATE_TIMEOUT=60
|
|
90
|
+
|
|
66
91
|
# Generic throttle: true (0) if stamp missing or older than PULL_INTERVAL
|
|
67
92
|
should_check_update() {
|
|
68
93
|
[ ! -f "$PULL_STAMP" ] && return 0
|
|
@@ -248,7 +273,7 @@ rm -f "$UNSCANNED_STAMP"
|
|
|
248
273
|
# operator can act on; "|| true" turned it into silence.
|
|
249
274
|
if should_check_update; then
|
|
250
275
|
want_host=$(printf '%s' "$DOWNLOAD_BASE" | sed -E 's#^[a-z]+://([^/]+).*#\\1#')
|
|
251
|
-
have_host=$("$BIN" version 2>/dev/null | sed -n 's#^server: [a-z]*://\\([^/]*\\).*#\\1#p' | head -1)
|
|
276
|
+
have_host=$(timeout "$VERSION_TIMEOUT" "$BIN" version 2>/dev/null | sed -n 's#^server: [a-z]*://\\([^/]*\\).*#\\1#p' | head -1)
|
|
252
277
|
if [ -n "$have_host" ] && [ -n "$want_host" ] && [ "$have_host" != "$want_host" ]; then
|
|
253
278
|
echo "ai-scanner: the installed binary talks to $have_host, but this installation provisions from $want_host, so its access key can never be accepted (detection logs are refused and discarded). Re-downloading the matching binary." >&2
|
|
254
279
|
download_binary "\${DOWNLOAD_BASE}/\${PLATFORM_SLUG}" || \\
|
|
@@ -256,7 +281,7 @@ if should_check_update; then
|
|
|
256
281
|
elif [ -z "$have_host" ]; then
|
|
257
282
|
echo "ai-scanner: could not read which license server the installed binary uses, so the environment match was NOT checked. If detection logs are being refused, re-run the installer." >&2
|
|
258
283
|
fi
|
|
259
|
-
"$BIN" update 2>/dev/null || \\
|
|
284
|
+
timeout "$UPDATE_TIMEOUT" "$BIN" update 2>/dev/null || \\
|
|
260
285
|
echo "ai-scanner: the scanner could not update itself just now. It keeps scanning with the rules it already has; if this repeats, re-run the installer or contact your IT administrator." >&2
|
|
261
286
|
date +%s > "$PULL_STAMP"
|
|
262
287
|
fi
|