Issue 204 Portable Commit Gates Research Report
Issue 204 Portable Commit Gates Research Report
Scope and lanes
Two independent read-only non-Astra lanes inspected the commit-gate producer and the generated runner/process boundary. The coordinator checked their claims against the current issue #203 parent tree and issue #204 report. No research lane edited files.
Primary-source facts
packages/scaffold/src/context-plan.ts:QualityCommandContractcontainslintandformatCheck, not a checkout root.apps/cli/src/scaffold/output.ts:planCommitGateOutputemits each quality contract with an owner and repository-relativepath; current output does not emitroot.- Historical CLI 4.0.4 output emitted
root: outputDirectory, which explains the machine-specific value retained by the reported consumer repository. apps/cli/src/data/scripts/commit-gate-runner.mjscurrently resolves the owner cwd withpath.resolve(contract.root ?? process.cwd(), relativePath). A stale legacy root therefore overrides the active checkout.- The runner obtains staged paths from
git diff --cached, so its Git command and owner commands must operate against the active worktree and its index. - Linked worktrees share a Git common directory but have distinct top-level paths and indexes.
git rev-parse --show-toplevelselects the active worktree root;--git-common-dirdoes not. - A missing
cwdcauses Node's process launch to fail beforeshor the quality command starts. The launch error carries a string code such asENOENTand an error message, while stdout and stderr can both be empty. - The runner catch path keeps only numeric error codes and command output. Its aggregate formatter consequently hides the launch message and resolved cwd.
apps/cli/src/data/scripts/commit-gate-runner.d.tsstill requires a legacyroot, even though the current producer schema does not.- Existing runner tests supply absolute temporary roots. Existing generated-gate portability checks exercise staged-file behavior but do not prove stale-root relocation or linked-worktree execution.
- Canonical runner bytes flow from
apps/cli/src/data/scripts/commit-gate-runner.mjsthrough baseline and npm builds into generated consumer.agents/scripts/commit-gate-runner.mjs.
Synthesized conclusions
The current generator is already portable, but the runtime remains backward-incompatible with moved legacy contracts. The smallest complete repair is at the runner seam: discover the active worktree top level once, resolve every relative owner path beneath it, and treat legacy root as non-authoritative. This fixes old committed contracts as soon as their runner is updated without extending the contract schema.
Launch failure evidence must be retained separately from command output. The owner, command kind, resolved cwd, and underlying error code/message are the observable minimum needed to diagnose an absent owner directory.
The implementation is local to one packaged runtime module and its declaration/tests. No new external dependency, service boundary, or cross-domain ownership is justified.
Risks and controls
- Hand-authored contracts that intentionally depend on absolute
rootwill change behavior. No current generated or typed quality contract supports that authority; the accepted compatibility boundary explicitly makes it obsolete. - Arbitrary absolute or traversing
pathvalues are not currently confined. General path-hardening is parked outside issue #204 so this repair does not silently widen scope. - Direct invocation from a nested directory can make a relative
--contractsargument ambiguous. Acceptance should exercise the supported Lefthook/root invocation and separately prove active-worktree discovery; changing contract-file lookup is not required by the issue. - A linked-worktree regression must clean up only its unique temporary repository/worktree paths after the process exits.
Conflicts and uncertainty
The issue permits either checkout-relative serialized roots or runtime Git-root discovery. Current source evidence resolves that alternative in favor of runtime discovery because the producer is already rootless and legacy contracts still exist. No unresolved product decision remains.
Release packaging is affected in both npm and baseline artifacts, but publication is not part of issue #204. The parent issue #203 branch already carries unreleased changelog state for the larger release stack; final release classification must compare this child against its accepted parent and preserve the stack's later release owner.
Planning recommendation
Use one TDD implementation task for the canonical runner, declaration, and executable tests because production and tests share one tight behavior seam. Follow with one dependent documentation/evidence task after runtime proof. Run focused runner and generated-gate tests, CLI typecheck/check/build, a real linked-worktree scenario, release classification against the issue #203 parent, and a frozen diff review.