enhance(#3618): fold fallow-runner onto the canonical binary resolver (epic #3411 Phase 2) (#3633)

* chore(#3618): fold fallow-runner onto the canonical binary resolver

Epic #3411 Phase 2. src/fallow-runner.cts was the fourth divergent
implementation of Windows binary resolution the epic enumerated —
candidateNames, isExecutableFile, findInPath, findInNodeModules, 40 lines.
All four are deleted; resolveFallowBinary is one seam call.

Two OPT-IN options were added to resolveExecutableBinary to make the fold
behavior-preserving, both defaulting off so Phase 1's callers are byte-identical:

  prependPaths      dirs searched before env.PATH, in order, through the
                    identical per-directory candidate logic. This expresses
                    node_modules/.bin-first precedence without env surgery —
                    the rejected alternative re-introduced the
                    spread-loses-the-proxy hazard the Windows lane caught in
                    Phase 1, at every future call site instead of once.
  requireExecutable POSIX-only accessSync(X_OK); a no-op on win32 where mode
                    bits do not mean execute. Opt-in rather than default
                    because unconditional X_OK breaks #3445's suite, which
                    stages candidates with plain writeFileSync and never sets
                    an exec bit — the repo bans chmod in tests — so every one
                    would resolve to null on POSIX.

Deliberate behavior change on Windows: fallow's prior candidate list ended in a
BARE fallow. The seam never tries a bare name there, so an extensionless file
beside fallow.cmd is no longer resolved. That is the fix, not a regression — the
extensionless file is npm's POSIX sh shim, which CreateProcess cannot run
(#3275). Rows 7 and 8 of the design record it.

Defect found while working, fixed inline: the resolution order was documented
BACKWARDS as PATH-then-.bin in structural-pre-pass.md, docs/INVENTORY.md and
four INVENTORY translations. The code has always been .bin first, and .bin first
is correct — a project-local tool should beat a global one. The archived
changeset is left alone as a historical record.

fallow-runner had no test file at all. tests/fallow-runner.test.cjs is new
(F1-F15) and the seam options are pinned by S1-S12 folded into the existing
dispatch suite. RED proven by execution: with both source files stashed and
build:lib re-run, 7 of 27 probe cases failed.

Refs #3411

* chore(#3618): backfill changeset pr number 3633

* fix(#3618): assert both platform contracts in F4 instead of a POSIX-only premise

Windows CI on #3633 failed F4. The test monkeypatched accessSync to throw and
asserted resolveFallowBinary returned null — but that premise, that the X_OK
check is consulted at all, is POSIX-only by design. requireExecutable is a
deliberate no-op on win32 because Windows mode bits do not mean execute, so the
staged fixture correctly resolved there.

40-design.md's negative-space section already states this carve-out verbatim.
The test contradicted the design it was written from: fixtures were made
platform-adaptive in the previous commit, and this assertion was left
platform-blind.

F4 now asserts BOTH contracts — null on POSIX, resolves on win32 — rather than
skipping either. A t.skip on one lane would have been green and would have left
the win32 carve-out unpinned by fallow's own entry point.

Audited every other row for the same class. F1-F3, F5, F6, F11-F15 hold on both
platforms; F7-F10 and S1-S12 inject platform explicitly and are unaffected. F4
was the only row with a single-platform premise.

The local probe runs on one platform and structurally cannot catch this, which
is why it was green — that limitation is now stated at the top of the probe so a
green probe is not mistaken for platform coverage. The win32 branch was proven
by injecting platform:'win32' with accessSync throwing and asserting it still
resolves.

Refs #3411

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-18 15:32:57 -04:00
committed by GitHub
parent 46f14c621e
commit ac1b6d679f
12 changed files with 614 additions and 59 deletions

View File

@@ -499,7 +499,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`.
| `eval.cjs` | Deterministic eval scoring (compiled from `src/eval.cts`, gitignored) — `computeEvalScore` (coverage*0.6 + infra*0.4, bands 80/60/40) + `cmdEvalScore` CLI domain guard; moves the gsd-eval-auditor's weighted arithmetic out of the prompt into code (#10 / #1579) |
| `estimate-cli.cjs` | I/O seam over `phase-estimation.cjs` — the `estimate-check` and `estimate-calibration` query verbs; reads the `workflow.smart_zone_tokens` budget and `.planning/estimation-calibration.json`, both degrading to defaults rather than failing planning (#2630) |
| `observability/event.cjs` | DispatchEvent shape factory for every Hub dispatch — traceId/parentTraceId/command/result/timestamp record consumed by DispatchLogger (#177, ADR-0174 P1.3/P1.4) |
| `fallow-runner.cjs` | Fallow audit adapter for `/gsd-code-review`: binary resolution (`PATH` then `node_modules/.bin`), actionable missing-binary errors, and structural findings normalization |
| `fallow-runner.cjs` | Fallow audit adapter for `/gsd-code-review`: binary resolution (`node_modules/.bin` then `PATH`), actionable missing-binary errors, and structural findings normalization |
| `federated-config.cjs` | Defensive merge of capability-declared config slices into the loadConfig return value — ADR-857 phase 3b; exports `mergeFederatedConfig({ configSchema, isCentralKey, userConfig })` → `{ values, validKeys, warnings }`; live for migrated Capability keys that are atomically removed from the central config schema |
| `frontmatter.cjs` | YAML frontmatter CRUD operations |
| `gap-checker.cjs` | Post-planning gap analysis (#2493): unified REQUIREMENTS.md + CONTEXT.md decisions vs PLAN.md coverage report (`gsd-tools gap-analysis`) |

View File

@@ -395,7 +395,7 @@
| `decisions.cjs` | CONTEXT.md の `<decisions>` ブロックを解析。数値(D-42)と英数字(D-INFRA-01)の ID を受け付け。`{id, text, category, tags, trackable}` を返す |
| `docs.cjs` | docs-update ワークフロー初期化、Markdown スキャン、モノリポ検出 |
| `drift.cjs` | 実行後のコードベース構造ドリフト検出器(#2003): ファイル変更を new-dir/barrel/migration/route カテゴリに分類し、`last_mapped_commit` フロントマターをラウンドトリップ |
| `fallow-runner.cjs` | `/gsd-code-review` 向けのファロー監査アダプター: バイナリ解決(`PATH` 次に `node_modules/.bin`)、アクション可能なバイナリ欠落エラー、構造的な調査結果の正規化 |
| `fallow-runner.cjs` | `/gsd-code-review` 向けのファロー監査アダプター: バイナリ解決(`node_modules/.bin` 次に `PATH`)、アクション可能なバイナリ欠落エラー、構造的な調査結果の正規化 |
| `frontmatter.cjs` | YAML フロントマター CRUD 操作 |
| `gap-checker.cjs` | 計画後のギャップ分析(#2493): REQUIREMENTS.md + CONTEXT.md 決定事項 vs PLAN.md カバレッジレポート(`gsd-tools gap-analysis`)の統合 |
| `graphify.cjs` | `/gsd-graphify` 向けのナレッジグラフビルド/クエリ/ステータス/差分 |

View File

@@ -395,7 +395,7 @@
| `decisions.cjs` | CONTEXT.md `<decisions>` 블록 파싱; 숫자형(D-42) 및 영숫자형(D-INFRA-01) ID 허용; `{id, text, category, tags, trackable}` 반환 |
| `docs.cjs` | 문서 업데이트 워크플로우 초기화, 마크다운 스캔, 모노레포 감지 |
| `drift.cjs` | 실행 후 코드베이스 구조 드리프트 감지기(#2003): 파일 변경을 new-dir/barrel/migration/route 카테고리로 분류하고 `last_mapped_commit` 프론트매터를 왕복 처리 |
| `fallow-runner.cjs` | `/gsd-code-review`를 위한 Fallow 감사 어댑터: 바이너리 해석(`PATH` 이후 `node_modules/.bin`), 실행 가능한 누락 바이너리 오류, 구조적 결과 정규화 |
| `fallow-runner.cjs` | `/gsd-code-review`를 위한 Fallow 감사 어댑터: 바이너리 해석(`node_modules/.bin` 이후 `PATH`), 실행 가능한 누락 바이너리 오류, 구조적 결과 정규화 |
| `frontmatter.cjs` | YAML 프론트매터 CRUD 작업 |
| `gap-checker.cjs` | 계획 후 공백 분석(#2493): REQUIREMENTS.md + CONTEXT.md 결정 대 PLAN.md 커버리지 보고서(`gsd-tools gap-analysis`) |
| `graphify.cjs` | `/gsd-graphify`를 위한 지식 그래프 빌드/쿼리/상태/비교 |

View File

@@ -395,7 +395,7 @@ Listagem completa: `gsd-core/bin/lib/*.cjs`.
| `decisions.cjs` | Analisa blocos `<decisions>` do CONTEXT.md; aceita IDs numéricos (D-42) e alfanuméricos (D-INFRA-01); retorna `{id, text, category, tags, trackable}` |
| `docs.cjs` | Inicialização do workflow docs-update, varredura de Markdown, detecção de monorepo |
| `drift.cjs` | Detector de drift estrutural pós-execução da base de código (#2003): classifica alterações de arquivo em categorias new-dir/barrel/migration/route e faz round-trip do frontmatter `last_mapped_commit` |
| `fallow-runner.cjs` | Adaptador de auditoria fallow para `/gsd-code-review`: resolução binária (`PATH` depois `node_modules/.bin`), erros acionáveis de binário ausente e normalização de descobertas estruturais |
| `fallow-runner.cjs` | Adaptador de auditoria fallow para `/gsd-code-review`: resolução binária (`node_modules/.bin` depois `PATH`), erros acionáveis de binário ausente e normalização de descobertas estruturais |
| `frontmatter.cjs` | Operações CRUD de frontmatter YAML |
| `gap-checker.cjs` | Análise de lacunas pós-planejamento (#2493): relatório unificado de cobertura de decisões do REQUIREMENTS.md + CONTEXT.md vs PLAN.md (`gsd-tools gap-analysis`) |
| `graphify.cjs` | Build/consulta/status/diff do grafo de conhecimento para `/gsd-graphify` |

View File

@@ -395,7 +395,7 @@
| `decisions.cjs` | 解析 CONTEXT.md `<decisions>` 块;接受数字(D-42)和字母数字(D-INFRA-01)ID;返回 `{id, text, category, tags, trackable}` |
| `docs.cjs` | 文档更新工作流初始化、Markdown 扫描、单体仓库检测 |
| `drift.cjs` | 执行后代码库结构漂移检测器(#2003):将文件更改分类为新目录/桶/迁移/路由类别,并循环处理 `last_mapped_commit` frontmatter |
| `fallow-runner.cjs` | `/gsd-code-review` 的 fallow 审计适配器:二进制解析(`PATH` 然后 `node_modules/.bin`)、可操作的缺少二进制错误和结构性发现规范化 |
| `fallow-runner.cjs` | `/gsd-code-review` 的 fallow 审计适配器:二进制解析(`node_modules/.bin` 然后 `PATH`)、可操作的缺少二进制错误和结构性发现规范化 |
| `frontmatter.cjs` | YAML frontmatter 增删改查操作 |
| `gap-checker.cjs` | 规划后间隙分析(#2493):REQUIREMENTS.md + CONTEXT.md 决策 vs PLAN.md 覆盖率报告(`gsd-tools gap-analysis`) |
| `graphify.cjs` | `/gsd-graphify` 的知识图谱构建/查询/状态/差异 |