* test(#4014): add failing-first coverage for unreadable-vs-empty directory scope (epic #3473 B4) * fix(#4014): an unreadable directory must not report as an empty one (epic #3473 B4) * test(#4014): update hardcoded generateSlugInternal closing-brace line after import shift src/core-utils.cts's new #4014 import block shifted every subsequent line by 6, moving generateSlugInternal's real closing brace from line 193 to 199. tests/slug-derivation-drift-guard.test.cjs's MAJOR-1 fixture hardcodes that line number to plant a synthetic violation immediately after the function's real body; the guard script itself locates the boundary dynamically via brace-matching and needed no change. * docs(#4014): document the unreadable-directory scope signal and add changeset * docs(#4014): backfill changeset PR number to #4163 * test(#4014): kill pre-existing core-utils.cjs mutation-score gap, unrelated to this issue's diff --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -206,6 +206,7 @@
|
||||
- [gsd-tools Declares Outcomes, Pinned at v1](#3912-gsd-tools-declares-outcomes-pinned-at-v1)
|
||||
- [Reachable Lint Rules and a Non-Destructive Quick-Task Append](#3951-reachable-lint-rules-and-a-non-destructive-quick-task-append)
|
||||
- [Per-Task External-Tracker Content-Resolution Seam](#3970-per-task-external-tracker-content-resolution-seam)
|
||||
- [Unreadable-Directory Scope Signal](#4014-unreadable-directory-scope-signal)
|
||||
|
||||
---
|
||||
|
||||
@@ -4196,6 +4197,52 @@ for how `execute:task` differs from the twelve prose-dispatched points.
|
||||
|
||||
---
|
||||
|
||||
### 4014. Unreadable-Directory Scope Signal
|
||||
|
||||
**Purpose:** ADR-3473 §8.4 ("failure is a value") applies to filesystem
|
||||
listings, not only command argv. `#3885` (B5) gave `roadmap analyze`,
|
||||
`gap-checker`, and `init`'s JSON bundles a `context_read_error` /
|
||||
`phase_dir_read_error` string naming an unreadable phase directory — but the
|
||||
underlying `has_context` / `hasContext` boolean stayed `false` either way,
|
||||
so a consumer branching on that boolean alone still cannot tell "genuinely
|
||||
no context file" from "could not read the directory at all." This closes
|
||||
that gap with a typed signal, reusing ADR-3180's existing frozen `SCOPE`
|
||||
enum rather than a new vocabulary.
|
||||
|
||||
**`findContextMdIn` (`src/planning-workspace.cts`) now reports its own
|
||||
scope.** Called with a directory path, it returns `{ file, files, scope }`
|
||||
instead of a bare filename-or-null, and never throws — an unreadable
|
||||
directory reports `scope: 'unreadable'` (previously it threw, forcing every
|
||||
caller to hand-roll its own `try`/`catch`); a genuinely absent directory
|
||||
(`ENOENT`) reports `scope: 'complete'`, the same "real empty" answer as
|
||||
today. The array-input call form (an already-read listing) is unchanged.
|
||||
|
||||
**Five downstream call sites gain an additive `scope` field, none renamed
|
||||
or removed:** `roadmap analyze`'s `AnalyzePhase.context_scope`,
|
||||
`gap-checker`'s `phase_dir_scope`, and `init`'s `context_scope` on all three
|
||||
JSON bundles (`init plan-phase`, `init phase-op`, `init manager`) —
|
||||
including `cmdInitManager`, whose own read failure previously vanished into
|
||||
a bare empty `catch {}` with no signal of any kind. `getPhaseFileStats`
|
||||
(`src/core-utils.cts`) — the shared listing owner behind `roadmap analyze`
|
||||
and `init`'s `has_context` — no longer lets its own failed read get masked
|
||||
by an unrelated, already-successful `scanPhasePlans` scope on the same
|
||||
phase directory.
|
||||
|
||||
**Known limits:**
|
||||
- `context_read_error` / `phase_dir_read_error`'s message text is now a
|
||||
fixed "Could not read phase directory `<path>`" rather than embedding the
|
||||
underlying OS errno text — `findContextMdIn`'s directory-string form
|
||||
reports only the `SCOPE` discriminator, not the raw caught error. The
|
||||
field's presence and type are unchanged; only its message detail is
|
||||
coarser than before #4014.
|
||||
- `init.cts`'s three call sites call `findContextMdIn` for the scope signal
|
||||
and then still run their own, pre-existing `fs.readdirSync` on the same
|
||||
path for the rest of their output — an intentional, additive-only choice
|
||||
to avoid altering already-complex failure control-flow at those sites,
|
||||
not a performance optimization.
|
||||
|
||||
---
|
||||
|
||||
_Generated by `scripts/gen-features.cjs` — add a fragment under `docs/features/` and run `--write`._
|
||||
|
||||
<!-- FEATURES:END -->
|
||||
|
||||
47
docs/features/unreadable-directory-scope-signal.md
Normal file
47
docs/features/unreadable-directory-scope-signal.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: 4014
|
||||
title: Unreadable-Directory Scope Signal
|
||||
group: v1.7.0 Features
|
||||
---
|
||||
|
||||
**Purpose:** ADR-3473 §8.4 ("failure is a value") applies to filesystem
|
||||
listings, not only command argv. `#3885` (B5) gave `roadmap analyze`,
|
||||
`gap-checker`, and `init`'s JSON bundles a `context_read_error` /
|
||||
`phase_dir_read_error` string naming an unreadable phase directory — but the
|
||||
underlying `has_context` / `hasContext` boolean stayed `false` either way,
|
||||
so a consumer branching on that boolean alone still cannot tell "genuinely
|
||||
no context file" from "could not read the directory at all." This closes
|
||||
that gap with a typed signal, reusing ADR-3180's existing frozen `SCOPE`
|
||||
enum rather than a new vocabulary.
|
||||
|
||||
**`findContextMdIn` (`src/planning-workspace.cts`) now reports its own
|
||||
scope.** Called with a directory path, it returns `{ file, files, scope }`
|
||||
instead of a bare filename-or-null, and never throws — an unreadable
|
||||
directory reports `scope: 'unreadable'` (previously it threw, forcing every
|
||||
caller to hand-roll its own `try`/`catch`); a genuinely absent directory
|
||||
(`ENOENT`) reports `scope: 'complete'`, the same "real empty" answer as
|
||||
today. The array-input call form (an already-read listing) is unchanged.
|
||||
|
||||
**Five downstream call sites gain an additive `scope` field, none renamed
|
||||
or removed:** `roadmap analyze`'s `AnalyzePhase.context_scope`,
|
||||
`gap-checker`'s `phase_dir_scope`, and `init`'s `context_scope` on all three
|
||||
JSON bundles (`init plan-phase`, `init phase-op`, `init manager`) —
|
||||
including `cmdInitManager`, whose own read failure previously vanished into
|
||||
a bare empty `catch {}` with no signal of any kind. `getPhaseFileStats`
|
||||
(`src/core-utils.cts`) — the shared listing owner behind `roadmap analyze`
|
||||
and `init`'s `has_context` — no longer lets its own failed read get masked
|
||||
by an unrelated, already-successful `scanPhasePlans` scope on the same
|
||||
phase directory.
|
||||
|
||||
**Known limits:**
|
||||
- `context_read_error` / `phase_dir_read_error`'s message text is now a
|
||||
fixed "Could not read phase directory `<path>`" rather than embedding the
|
||||
underlying OS errno text — `findContextMdIn`'s directory-string form
|
||||
reports only the `SCOPE` discriminator, not the raw caught error. The
|
||||
field's presence and type are unchanged; only its message detail is
|
||||
coarser than before #4014.
|
||||
- `init.cts`'s three call sites call `findContextMdIn` for the scope signal
|
||||
and then still run their own, pre-existing `fs.readdirSync` on the same
|
||||
path for the rest of their output — an intentional, additive-only choice
|
||||
to avoid altering already-complex failure control-flow at those sites,
|
||||
not a performance optimization.
|
||||
Reference in New Issue
Block a user