Skip to content

design: TDDs 0068–0069 (format-and-lint honors config; /build-tdds hardening) - #188

Merged
cahenesy merged 1 commit into
masterfrom
docs/design/hook-config-and-build-hardening
Oct 6, 2026
Merged

cahenesy merged 1 commit into
masterfrom
docs/design/hook-config-and-build-hardening

Conversation

@cahenesy

@cahenesy cahenesy commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Gap-closure pass with no PRD change (PRD-rev f6ef178). Refs #180.

  • TDD 0068: the format-and-lint hook honors the repo's configuration
    (FR-21). It supersedes only the hook component of TDD 0001.
    • Fixes issue format-and-lint.sh formats and --fix-es files in repos that never configured a formatter (contradicts FR-21 / TDD 0001) #180, reproduced on 3.49.0 with ruff 0.16.10: a
      lint-only config triggered a whole-file reformat; no config led to
      --fix deleting imports; blocking diagnostics went to stdout, which
      the agent never sees.
    • Config is found from the edited file's directory upward, not $PWD.
    • Lint is report-only (no --fix) and writes its diagnostics to stderr.
    • Formatting needs an explicit opt-in for Python/JS (a top-level
      package.json key, not a devDependency). Rust/Go follow the language
      convention, guarded by "HEAD is already clean" (rustfmt runs via
      stdin, so it never reformats child modules).
    • JS tools are resolved from local node_modules/.bin.
    • /bootstrap-project writes the formatter opt-in on every path that
      creates a formatter config, and the README carries an upgrade note.
    • The runtime-verify gate runs real ruff/gofmt/rustfmt controls. If it
      can't install them, the gate reports BLOCKED, never PASS.
  • TDD 0069: /build-tdds hardening.
    • FR-18/FR-43: the run lock recorded the short-lived Bash shell's
      PID, so it never held. It now records the session process plus its
      start time, defeating PID reuse. Creation uses noclobber; a
      partially written lock counts as held; reclaim and unlock are
      serialized by a guard with a compare-before-remove; a session can
      re-enter its own lock.
    • Follow-ups from the TDD 0067 review:
      • the fall-back reason now travels via a file written by the
        file-write tool, so stream text never reaches a shell command line;
      • the implementer's rate-limit rule points to the credits_required
        exception;
      • the probe cleans up its harness session folder;
      • tl_run_retry_begin owns the report copy;
      • the base sha is validated.

Every TDD carries a co-created 7-row evaluation rubric. It includes a
regression-safety row: repos that already configured tools must keep
working, and a live lock must still refuse a second run.

Design critique (design-reviewer, two fresh passes):

  • Pass 1: BLOCK.
    • M1: lock reclaim and partial-write were not atomic.
    • M2: prettier detection matched devDependencies.
    • M3: the rustfmt/gofmt clean-guard was unsound (child modules, cwd
      config lookup, gofmt's always-0 exit code) and not checked against
      real tools.
    • M4: already-bootstrapped repos would stop formatting.
      All four fixed, plus 13 minors.
  • Pass 2: DESIGN_REVIEW: PASS. Its 4 minors and 2 nits were applied:
    • Rust write-back preserves the file mode and skips symlinks;
    • a lock that vanishes on re-read goes back to the create step;
    • unlock takes the reclaim guard;
    • a stale guard is broken by an atomic mv;
    • the second-parent liveness note was added to observation 8;
    • the ruff regex is anchored.
      ADR evaluation: none recommended; both TDDs correct implementations to
      match existing requirements.

Open assumptions & waivers:

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ki3TDnP2bNqqpEkFoZaEXq

…rdening)

Gap-closure pass with no PRD change (PRD-rev f6ef178). Refs #180.

- TDD 0068: the format-and-lint hook honors the repo's configuration
  (FR-21). It supersedes only the hook component of TDD 0001.
  - Fixes issue #180, reproduced on 3.49.0 with ruff 0.16.10: a
    lint-only config triggered a whole-file reformat; no config led to
    --fix deleting imports; blocking diagnostics went to stdout, which
    the agent never sees.
  - Config is found from the edited file's directory upward, not $PWD.
  - Lint is report-only (no --fix) and writes its diagnostics to stderr.
  - Formatting needs an explicit opt-in for Python/JS (a top-level
    package.json key, not a devDependency). Rust/Go follow the language
    convention, guarded by "HEAD is already clean" (rustfmt runs via
    stdin, so it never reformats child modules).
  - JS tools are resolved from local node_modules/.bin.
  - /bootstrap-project writes the formatter opt-in on every path that
    creates a formatter config, and the README carries an upgrade note.
  - The runtime-verify gate runs real ruff/gofmt/rustfmt controls. If it
    can't install them, the gate reports BLOCKED, never PASS.
- TDD 0069: /build-tdds hardening.
  - FR-18/FR-43: the run lock recorded the short-lived Bash shell's
    PID, so it never held. It now records the session process plus its
    start time, defeating PID reuse. Creation uses noclobber; a
    partially written lock counts as held; reclaim and unlock are
    serialized by a guard with a compare-before-remove; a session can
    re-enter its own lock.
  - Follow-ups from the TDD 0067 review:
    - the fall-back reason now travels via a file written by the
      file-write tool, so stream text never reaches a shell command line;
    - the implementer's rate-limit rule points to the credits_required
      exception;
    - the probe cleans up its harness session folder;
    - tl_run_retry_begin owns the report copy;
    - the base sha is validated.

Every TDD carries a co-created 7-row evaluation rubric. It includes a
regression-safety row: repos that already configured tools must keep
working, and a live lock must still refuse a second run.

Design critique (design-reviewer, two fresh passes):
- Pass 1: BLOCK.
  - M1: lock reclaim and partial-write were not atomic.
  - M2: prettier detection matched devDependencies.
  - M3: the rustfmt/gofmt clean-guard was unsound (child modules, cwd
    config lookup, gofmt's always-0 exit code) and not checked against
    real tools.
  - M4: already-bootstrapped repos would stop formatting.
  All four fixed, plus 13 minors.
- Pass 2: DESIGN_REVIEW: PASS. Its 4 minors and 2 nits were applied:
  - Rust write-back preserves the file mode and skips symlinks;
  - a lock that vanishes on re-read goes back to the create step;
  - unlock takes the reclaim guard;
  - a stale guard is broken by an atomic mv;
  - the second-parent liveness note was added to observation 8;
  - the ruff regex is anchored.
ADR evaluation: none recommended; both TDDs correct implementations to
match existing requirements.

Open assumptions & waivers:
- convention formatting re-creates #180 on unformatted rs/go repos — resolved: rustfmt/gofmt run only when HEAD's copy of the file is already clean (rc 0 and empty stdout); new-to-git files are formatted; files outside git are never formatted.
- lock owner must outlive the Bash shell — resolved: tl_session_pid walks past shell/wrapper ancestors to the harness process; the lock records pid + start time and is live only if both match; creation is atomic via noclobber, with a serialized reclaim.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ki3TDnP2bNqqpEkFoZaEXq
@cahenesy
cahenesy merged commit fd42dc9 into master Oct 6, 2026
1 check passed
@cahenesy
cahenesy deleted the docs/design/hook-config-and-build-hardening branch October 6, 2026 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant