#4894·gsd-core

bug(verification): --project-dir is ignored by verification.fingerprint and the staleness recompute (both re-derive root from phaseDir)

Author: helenkwokCreated Sep 20, 2026Updated Sep 21, 2026
Labelsbugconfirmed-bug

Summary

--project-dir is validated and honored by the dispatcher (#3881: it sets cwd and skips the ancestor walk-up), but verification.fingerprint and the staleness recompute in readVerificationStatus never consult it. Both re-derive the project root from the phase directory alone via findProjectRoot(phaseDir) (src/verification.cts, the fingerprint verb and the computeCoveredDigest(findProjectRoot(phaseDir), …) call in the status path).

This is acceptance criterion (4) from the #4815 triage brief — "--project-dir is either honored or consistently documented behavior for this path" — which #4819 deliberately does not address.

Reproduction

.planning symlinked to an external git-managed store (the layout from #4815), phase directory passed by its real store path — e.g. after a caller has resolved the symlink:

bash
T=$(mktemp -d); mkdir -p $T/proj/src $T/proj/.git $T/store/phases/01-x $T/store/.git
echo hi > $T/proj/src/a.txt; echo '{}' > $T/store/config.json
ln -s $T/store $T/proj/.planning
G="node gsd-core/bin/gsd-tools.cjs"

# via the symlink path: works (with the #4819 fix)
(cd $T/proj && $G verification.fingerprint .planning/phases/01-x src/a.txt)

# via the real store path, no flag: fails (walk-up from the store never reaches proj)
(cd $T/proj && $G verification.fingerprint $T/store/phases/01-x src/a.txt)
#   Error: could not compute fingerprint — a covered file is missing, unreadable, or escapes the project root

# via the real store path WITH --project-dir: still fails — flag ignored
(cd $T && $G verification.fingerprint $T/store/phases/01-x src/a.txt --project-dir $T/proj)
#   Error: could not compute fingerprint — (same)

Why it can't be fixed in one place

Honoring the flag in cmdVerificationFingerprint alone (one site; cwd is already the explicit root there) would emit a digest computed under the explicit root while readVerificationStatus recomputes under findProjectRoot(phaseDir) — so verification.status / phase.complete would still read stale. Both must change together, or neither.

The status side is reached through readVerificationStatusisPhaseComplete and ~9 callers (roadmap, init, state, planning-inspect, phase, planning-snapshot, uat-predicate, workstream-inventory).

Possible approaches

  1. Thread an optional projectRoot through readVerificationStatus / isPhaseComplete, populated only when --project-dir was explicit; fingerprint uses cwd in that case. Non-explicit behavior unchanged.
  2. Document only: state that verification derives its root from the phase directory and does not consult --project-dir. Satisfies "consistently documented" but leaves the flag a trap for realpath callers.

Must not weaken covered-file containment or the fail-closed staleness contract (out of scope per the #4815 brief).

Happy to send a PR for (1) once a direction is confirmed. Follow-up to #4815 / #4819.