Commit Graph

5 Commits

Author SHA1 Message Date
Tom Boucher
6efd2b4016 fix(#2043): reject single-digit slug word in phase-token extraction (all sites)
A phase whose slug's first word is a bare single digit (dir
`46-6-rs-pipeline-orchestrator`, roadmap phase "6 Rs Pipeline Orchestrator" →
slug `6-rs-…`) had its phase token over-collected as `46-6` instead of `46`, so
`gsd-tools` phase-by-number lookups (init.plan-phase, init.phase-op, and
downstream execute/verify/ship) resolved phase_dir=null / has_context=false.

Root cause: an over-broad "looks numeric" test (`/^\d/`, `\d+(?:-\d+)*`)
classified token segments and could not distinguish a legitimate zero-padded
sub-phase segment (`01`, `02`) from a single-digit slug word (`6`). Zero-padded
phase/sub-phase segments are always ≥2 digits, so requiring ≥2 digits is the
structural distinguisher. Applied consistently across every same-class
implementation the triage identified (fixing extractPhaseToken alone leaves the
health-check and milestone-filter subsystems exposed):

- src/phase-id.cts extractPhaseToken — a pure-numeric leading segment continues
  only with ≥2-digit segments; a letter-prefixed milestone id (`M1`) still
  admits its single-digit sub-phase (`M1-2`).
- src/validate.cts PHASE_TOKEN_FROM_DIR_RE (W005/W006/W007 health checks) and
  canonicalPlanStem (I001 plan/summary pairing).
- src/roadmap-parser.cts isDirInMilestone numericRe (getMilestonePhaseFilter —
  highest blast radius: a false negative silently excludes a phase from the
  milestone).
- src/core-utils.cts + its verbatim duplicate src/phase.cts extractCanonicalPlanId.

Regression tests added across tests/{phase-id,health-validation,core-utils,
roadmap-parser}.test.cjs using the shared `46-6-rs-…` fixture, asserting the
token resolves to `46` (not `46-6`) while legit multi-segment tokens
(`01-02`, `02-03-04`, `M1-2`, `68-01`, `02-01`) are unchanged. The
roadmap-parser test exercises the real getMilestonePhaseFilter end-to-end.

The residual ≥2-digit-leading-slug case (e.g. `NN-2024-roadmap`) stays out of
scope — subsumed by the #612 / #565 phase-ID convention work, per the issue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 10:15:37 -04:00
Tom Boucher
2dedbdd11c fix(#1455): resolve project-code-prefixed roadmap headings
* fix: remove hardcoded phase project-code prefix cap

Centralize project-code prefix stripping/matching and replace fixed {1,6} caps so long codes (for example MANIFOLD-117) resolve across phase, roadmap parser, roadmap upgrade, and validate flows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address review feedback on prefix parsing

- allow project_code prefixes with digits and underscores while preserving milestone parsing
- use shared optional project-code prefix source in phase dir parsing
- extend regression coverage for APP1/APP_1 prefixed phases

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: resolve review nit in phase-id test comment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: resolve project-code-prefixed roadmap headings

Make getRoadmapPhaseInternal recover from drifted project-code-prefixed ROADMAP headings while preserving canonical bare-heading preference. Add init.phase-op and parser regressions for #1455 and guard gsd-roadmapper against emitting project_code in headings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: add changeset for project-code phase fix

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: update roadmapper agent size baseline

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(#1455): tighten project-code prefix regex to [A-Z] start; add boundary test; document source order

Resolves blockers from review:
- Regex changed from [A-Z_][A-Z0-9_]* to [A-Z][A-Z0-9_]* so leading
  underscores (_FOO-7, _-7) are never misread as project-code prefixes;
  adds boundary test asserting both do NOT strip.
- Adds comment to roadmapPhaseLookupSources explaining why 3 sources are
  needed (order-dependent canonical-heading preference).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Solvely-Colin <211764741+Solvely-Colin@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 18:45:41 -04:00
Tom Boucher
c53fd1f654 fix(#1324): resolve glued phase tokens (#1353) 2026-06-16 21:55:58 -04:00
Tom Boucher
c76827afbc refactor(#1291): T6 — migrate test files off the core spine ahead of deletion (#1293)
The convergence lint only scanned src/ + gsd-core/bin, so ~35 test files
still imported core.cjs. Repoint all 33 behaviour importers to the leaf
modules directly (same symbol->leaf map as the src migration; leaves are the
objects core re-exported by reference), delete the now-meaningless
shim-identity describe blocks in the 8 leaf tests, and delete tests/core.test.cjs
(forwarded-behaviour coverage now lives at the leaves; resolveWorktreeRoot
test relocated to worktree-safety in T0) and tests/lint-core-spine-imports.test.cjs
(the lint is removed in T-final). Dropped the stale core.test.cjs entries from
the allow-test-rule-refs allowlist; eslint-rules RuleTester fixture path
pointed at io.cjs.

After T6: ZERO test imports core.cjs. core.cts still builds (now fully unused);
T-final deletes it. No behaviour change.

Closes #1291

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:11:20 -04:00
Tom Boucher
2988a21c46 refactor(#865): extract pure phase-id helpers from core.cts into phase-id.cts (#868)
ADR-857 rollout phase 2a — the first cut of the core.cts decomposition.
Move the 9 pure phase-id parsing/matching helpers (escapeRegex,
normalizePhaseName, comparePhaseNum, extractPhaseToken, phaseTokenMatches,
phaseMarkdownRegexSource/Exact, getMilestoneFromPhaseId, getPhaseDirFromPhaseId)
out of core.cts into a new leaf module src/phase-id.cts. core.cts re-exports
them (behavior-preserving); its internal callers resolve the destructured
bindings.

Cycle-safe by design: phase-id depends on nothing in core, so re-export creates
no circular require (the property that made phase-1 io.cts clean). This is the
leaf-first ordering — it unblocks the roadmap-parser extraction (2b), which
imports phaseMarkdownRegexSource.

New-CLI-module checklist: .gitignore, eslint.config.mjs ignores, INVENTORY.md
count 91->92 + row, INVENTORY-MANIFEST.json, ARCHITECTURE.md row, CONTEXT.md
"Phase Id Module" glossary entry. Adds tests/phase-id.test.cjs (63 behavioral
tests incl. shim-identity + adversarial inputs).

Gates: lint, code-review, security-review, codex adversarial-review (all 0
findings), and gsd-test-both (14814 pass on Mac + Linux Docker, 0 fail).

Closes #865

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:12:08 -04:00