Fold all 10 residual isWindsurf branches in bin/install.js onto descriptor-driven hostBehaviors (byte-parity — no fold changes any install output): - 2 dead destructures dropped (uninstall, finishInstall); the dead `else if (isWindsurf)` legacy agent-loop arm removed (windsurf ∈ _DESCRIPTOR_AGENTS_RUNTIMES → unreachable). - skipSharedHooksInstall:true folds the two `!isWindsurf` shared-hooks exclusions. - legacyDevinSkillsCleanup:true folds the `.devin`→`.windsurf` one-time cleanup gate. - installsCommandBodiesForWorkflowDelegation:true folds the #1629 command-body copy (workflow-delegation target — load-bearing; local-install verified intact). - verificationStyle:"windsurf-workflows" folds the workflow-count report. - Corrected stale _LEGACY_SCAN_SUBDIR_NAMES + hooks-json manifest comments (cursor + windsurf). Zero live runtime==='windsurf'/isWindsurf branches remain across bin/install.js, install-engine.cts, surface.cts, runtime-artifact-conversion.cts (AC2 guard scans all four). UPGRADE (Cascade hook bus): wire GSD's write/command safety guards into Windsurf's native hook bus. New hooksSurface 'windsurf-hooks-json' (VALID_HOOKS_SURFACES 7→8, GATE A profile-marker-only allowlist, the HooksSurface union) + writeWindsurfHooksJson (Cursor-templated, Cascade's flat {hooks:{<event>:[{command}]}} shape) writing .windsurf/hooks.json with two BLOCKING pre-hooks: - pre_write_code → gsd-windsurf-pre-write.js: blocks writes to a file outside the active git worktree / into .git internals. - pre_run_command → gsd-windsurf-pre-command.js: conservative destructive-command deny-list (rm -rf of root/home incl. sudo/env/path-prefixed forms; fork bombs; force-push refspec forms — HEAD:main, +main, --force/-f — to main/master/next). Both use Cascade's protocol (stdin JSON, exit 2 + stderr to block, exit 0 to allow, fail-open on error/timeout). Tokenize-based classifier (no catastrophic-backtracking regex; 4096-char cap) with the fail-closed false-positives fixed post-review. The 4 advisory GSD guards + pre_mcp_tool_use + 5 post_* logging events are deliberately NOT wired: Cascade has no context-injection channel for advisory hooks and GSD has no MCP guard — porting them would be non-functional padding (documented; codebuddy #2098 / copilot #2099 faithful-subset precedent). extendedHookEvents stays []. Golden: the 2 guard scripts ship in the shared hook bundle (HOOKS_TO_COPY + the shared managed-hooks-registry), exactly like cursor's 6 gsd-cursor-*.js scripts — so the 8 shared-bundle runtimes' fixtures gain the 2 inert windsurf scripts + the registry hash (functionally inert for non-windsurf; the established cursor pattern). No install-output change beyond that (the folds are byte-parity; skip-bundle runtimes untouched). New scripts registered in managed-hooks-registry + build-hooks + INVENTORY. Tests: declarative-reference- windsurf (adapter/axes/fail-closed + AC2 guard) + windsurf-hooks-bridge (live exit-2 blocking + allow/fail-open + ReDoS-bound + writer/reconcile/remove idempotency); VALID_HOOKS_SURFACES pin updated to 8. Matrix hookBus delta + changeset (Changed). capability-registry regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
72 lines
6.0 KiB
Markdown
72 lines
6.0 KiB
Markdown
## Linked Issue
|
|
|
|
Closes #2100
|
|
|
|
The linked issue carries the `approved-feature` label.
|
|
|
|
---
|
|
|
|
## Feature summary
|
|
|
|
Migrates **Windsurf** onto the ADR-1239 Embeddable Orchestration System — the largest of the EoS migrations. Folds all 10 residual `isWindsurf` branches onto the capability descriptor **and** wires GSD's write/command safety guards into Windsurf/Cascade's native blocking hook bus.
|
|
|
|
## What changed (highlights)
|
|
|
|
| File | What changed |
|
|
|------|-------------|
|
|
| `bin/install.js` | Folded 10 `isWindsurf` sites onto `hostBehaviors` (skipSharedHooksInstall, legacyDevinSkillsCleanup, installsCommandBodiesForWorkflowDelegation [#1629], verificationStyle); dropped 2 dead destructures + the dead `else if (isWindsurf)` agent arm; wired the Cascade hook-bridge install/uninstall; corrected stale comments |
|
|
| `capabilities/windsurf/capability.json` | `hostBehaviors` block; `hooksSurface: "none"` → `"windsurf-hooks-json"` |
|
|
| `src/runtime-hooks-surface.cts` | `writeWindsurfHooksJson`/`reconcileWindsurfHooksJson`/`removeWindsurfHooksJson` (Cursor-templated, Cascade's flat `{hooks:{<event>:[{command}]}}` shape) + event/script constants |
|
|
| `hooks/gsd-windsurf-pre-write.js`, `gsd-windsurf-pre-command.js` | **New** Cascade-native blocking guard scripts (stdin JSON, exit-code-2 blocking) |
|
|
| `gsd-core/bin/lib/capability-validator.cjs`, `src/runtime-config-adapter-registry.cts` | `windsurf-hooks-json` added to `VALID_HOOKS_SURFACES`, GATE A's `profile-marker-only` allowlist, and the `HooksSurface` union |
|
|
| managed-hooks-registry / build-hooks / INVENTORY | registered the 2 new guard scripts |
|
|
| tests / docs / changeset | `declarative-reference-windsurf` + `windsurf-hooks-bridge` (live blocking); matrix hookBus delta; changeset (`Changed`) |
|
|
|
|
## Implementation notes
|
|
|
|
- **Byte-parity concretely verified** (via the review): a real windsurf install rebuilt through the golden-parity harness → 327 files, 0 drift; only `windsurf.json` gains the 2 new script hashes. cursor/trae re-verified 0 drift. The load-bearing #1629 command-body copy (`.windsurf/gsd-core/commands/gsd/*.md` for local installs) is intact.
|
|
- **The hook-bridge is faithful, not padding.** Cascade's `hooks.json` genuinely supports blocking via exit code 2 (confirmed against docs.windsurf.com / docs.devin.ai). Only **2 of GSD's 6 guards** faithfully map — the worktree-path guard (→ `pre_write_code`) and a destructive-command guard (→ `pre_run_command`). The 4 advisory guards + `pre_mcp_tool_use` + the 5 `post_*` logging events are **deliberately not wired**: Cascade's hook bus has no context-injection channel to carry GSD's advisory reminders faithfully, and GSD has no MCP-tool policy — porting them would be non-functional padding. This is the same faithful-subset pattern used for codebuddy #2098 / copilot #2099, and it satisfies AC4's testable requirement ("a real blocking hook rejecting a disallowed write/command").
|
|
- **Security (reviewed, clean).** The guard scripts parse untrusted stdin and spawn `git rev-parse` — command injection via `file_path` was **refuted** (argv array, no `shell:true`, PATH-resolved git). No traversal / prototype-pollution (frozen 2-event set, fixed script names). The pre-command guard was **hardened post-review**: a tokenize-based classifier (no catastrophic-backtracking regex — a 200k-char pathological input now completes in ~32ms via a 4096-char cap), catching prefixed `rm -rf` forms (`sudo`/`env`/`/bin/rm`) and refspec force-pushes (`HEAD:main`, `+main`), and a fail-closed false-positive fixed (a `feature/main-fix` branch or a trailing-`# ...main` comment no longer wrongly blocks a legit force-push). Guards fail-open (never wedge Cascade) by design.
|
|
- **Golden mechanics.** `.windsurf/hooks.json` is golden-excluded by basename (like settings.json); the 2 guard scripts under `hooks/` are windsurf-specific → only windsurf.json regenerates, additively.
|
|
|
|
## Spec compliance (acceptance criteria)
|
|
|
|
- [x] Golden parity: byte-identical for the folds across all 16 runtimes (windsurf.json regen is the additive hook-script delta only)
|
|
- [x] Driven through the descriptor — zero live `runtime==='windsurf'`/`isWindsurf` branches (AC2 guard over 4 files)
|
|
- [x] Every axis populated + `capability-validator`-clean (`runtime`/dispatch stay `undocumented` per the cited search trail)
|
|
- [x] UPGRADE implemented AND exercised by a test driving a real blocking hook (exit-2 on a disallowed write/command)
|
|
- [x] `negotiateHostCapabilities` fail-closes for windsurf (test)
|
|
- [x] `gsd-test` green (linux node22/24); no other-runtime regression (cursor.json byte-identical)
|
|
- [x] Docs (matrix hookBus delta) + changeset (`Changed`)
|
|
|
|
## Testing
|
|
|
|
- [x] macOS (real install byte-parity harness + live guard-script exit-2 probing + ReDoS timing)
|
|
- [x] Windows (backslash; Windows destructive-command forms handled) — GitHub CI
|
|
- [x] Linux (`gsd-test`)
|
|
- [x] Runtimes: Windsurf (primary) + all 16 golden fixtures (only windsurf's 2 new scripts)
|
|
|
|
---
|
|
|
|
## Scope confirmation
|
|
|
|
- [x] Windsurf only; other runtimes byte-identical. The hook-bridge's faithful 2-guard scope (vs. the AC's fuller event list) is disclosed above — the unbridged events have no faithful GSD logic / Cascade channel.
|
|
- [x] Cascade envelope/schema is best-effort per the official docs (guards fail-open if the live schema differs, never breaking Cascade); flagged for a live-Cascade schema confirmation follow-up.
|
|
|
|
## Documentation
|
|
|
|
- [x] matrix (## windsurf hookBus/hooksSurface delta + the not-ported-guards rationale); English
|
|
|
|
## Checklist
|
|
|
|
- [x] `Closes #2100`; issue has `approved-feature`
|
|
- [x] Acceptance criteria met (faithful hook-bridge scope disclosed)
|
|
- [x] `gsd-test` green
|
|
- [x] New tests cover the folds (AC2 guard) + the blocking hook bus (live exit-2) + fail-closed negotiation
|
|
- [x] `.changeset/` fragment (`Changed`)
|
|
- [x] No new dependencies
|
|
|
|
## Breaking changes
|
|
|
|
None at landing. New Windsurf install output is additive: 2 guard scripts + a `.windsurf/hooks.json` registering blocking pre-hooks. No skill, agent, workflow, or path is removed or altered; the guards fail-open.
|