Skip to content

fix(cli): architecture rules select nothing on Windows — every path helper returns backslashes #863

Closed

Description

@VincentMenard76

Description

cli/tests/architecture/helpers.ts builds every path it hands to a rule with
relative(CLI_ROOT, full), which is platform-native. On Windows that yields
src\contexts\tools\..., while the rules themselves match on forward slashes —
startsWith("src/contexts/"), includes("/profiles/"), and similar.

Nothing throws. The rules simply select nothing, and 21 architecture tests fail on
Windows with messages that read like the repository drifted rather than like the
harness did: "the rule selects no file — its scope is stale", "no profile
directory found"
, "pattern matches nothing under src/".

The more serious half is not the noise, it is the silence on the other side: any
architecture rule a contributor adds is unverifiable on Windows, and a Windows
contributor cannot tell a real violation from a harness that selected zero files.

Affected file(s)

  • cli/tests/architecture/helpers.ts — sourceFiles(), and every helper returning a
    relative() path
  • 15 of the 25 files under cli/tests/architecture/, as consumers

Expected behaviour

The architecture project passes on Windows, or names a real violation when one exists.

Observed behaviour

On next at 126e88da, a pristine checkout, no local modification:

--project=architecture    21 failed | 79 passed (100)

The same commit is green in CI (cli CI: success), so this is platform-specific and
invisible to the pipeline.

Root cause, reproduced directly:

relative(CLI_ROOT, join(CLI_ROOT, "src", "contexts", "tools", "domain", "x.ts"))
// -> "src\contexts\tools\domain\x.ts"
//    startsWith("src/contexts/tools") === false
//    includes("/profiles/")           === false

normalize is imported in the file but is not applied at this point.

Additional context

Found while implementing #862, and it is the same defect class: a path or shell
assumption that holds on Linux and macOS, breaks on Windows, and reports nothing.
#862 is one hook that never runs; this is 21 rules that never select. Both are
invisible to a Linux-only CI, which is what #707 exists to close.

Deliberately not fixed inside the #862 branch: normalising the separators would make
21 rules run on Windows for the first time, and some may then surface genuine
findings. That is a discovery task with an unbounded cost, and how this harness should
treat Windows is a maintainer decision.

AI tool Claude Code
aidd-cli version 5.3.0
OS Windows 11 Pro (10.0.26100)
node 24.12.0

I agree to follow this project's Code of Conduct.

Activity

  1. moved this from Ideation to Done in AIDD Roadmapon Sep 12, 2026
  2. blafourcade commented on Sep 14, 2026

    @blafourcade
    Contributor

    This was closed without a patch.

    Current next still returns relative(CLI_ROOT, full) directly from sourceFiles(). On Windows that is a backslash-separated path, while the architecture rules intentionally match /. The reported zero-selection failures therefore remain reproducible in the harness.

    The possibility that normalisation reveals real architecture violations is not a reason to close the defect. It is the expected consequence of making the rules run on Windows for the first time.

    Reopen this as a Bug and keep the fix narrow:

    • normalise every repository-relative path emitted by the architecture helpers to /;
    • add a regression test proving Windows-style paths are accepted by the helper and its consumers;
    • run the architecture project on Windows;
    • if the now-active rules reveal real violations, fix them or create separately scoped issues. Do not preserve a broken selector to keep the current baseline green.

    Out of scope: changing the architecture rules’ intended scope or weakening their zero-selection assertions.

  3. added theissue type on Sep 14, 2026
  4. moved this from Done to Todo in AIDD Roadmapon Sep 14, 2026
  5. kevin-lozada-santos commented on Sep 16, 2026

    @kevin-lozada-santos
    Contributor

    I can reproduce this on native Windows and report the current next
    baseline, including which failures come from path formatting versus
    actual rule violations. I'll keep the selectors, baselines, and
    zero-selection assertions intact. I see the contribution guide asks for
    maintainer validation before implementation; please confirm this issue
    is ready for a focused helper-normalisation PR. I'll start with the
    runtime test evidence in the meantime.

  6. kevin-lozada-santos commented on Sep 16, 2026

    @kevin-lozada-santos
    Contributor

    Native Windows reproduction on current next revision 83b0246e532d3f76491baabbc8b0b266f00e3590 confirms the path issue. I have not changed the source or debt baselines.

    Environment: Windows 11 build 26200, Node v22.23.1, Vitest 3.2.6, Biome 2.5.11. I used pnpm 11.19.0 with the existing frozen lockfile and lifecycle scripts disabled; the package declares pnpm 12.3.4.

    From cli/:

    pnpm install --frozen-lockfile --ignore-scripts
    pnpm exec vitest run --project=architecture
    Tests: 21 failed | 79 passed (100)
    

    There are also two collection errors: docs-do-not-lie raises “no command found”; mutation-covers-source raises “Invalid or unexpected token”. I have not root-caused the latter, or attributed every failed assertion to path separators.

    The actual sourceFiles() helper returns 381 paths, all containing backslashes. Comparing its output with a read-only slash-normalized view:

    Prefix Actual helper matches Slash-normalized view
    src/contexts/ 0 272
    src/contexts/tools/ 0 60
    src/presentation/commands/ 0 15
    src/kernel/ 0 27

    All eight pluginReadmes() paths also contain backslashes. The actual declaredCommands() raises the stale-scope error despite those 15 command files being present. This diagnostic can be reproduced from cli/ with Node 22:

    node --experimental-strip-types --input-type=module -e 'import {sourceFiles,declaredCommands} from "./tests/architecture/helpers.ts"; const files=sourceFiles(); const prefix="src/presentation/commands/"; console.log({total:files.length,actual:files.filter(p=>p.startsWith(prefix)).length,normalized:files.filter(p=>p.replaceAll(String.fromCharCode(92),"/").startsWith(prefix)).length}); console.log(declaredCommands());'

    A narrow fix needs more than sourceFiles(). Source tracing identifies these boundaries:

    • helpers.ts: normalize sourceFiles(), pluginReadmes(), and resolveImportTarget() outputs consistently. Otherwise relative imports will fail the canonical-key lookup after sourceFiles() changes.
    • catches-that-swallow.arch.test.ts: its independent testFiles() output is compared with slash-based BASELINE and SELF keys.
    • ports-are-called.arch.test.ts: its independent portFiles() output must match the source-body map. A partial fix would make bodies.get(port) ?? "" silently miss Windows ports. This is a static trace of the partial-fix risk, not a claim that I ran a patched suite.
    • Normalize paths used for diagnostics in comments and no-shared-binary too; keep filesystem roots/operations native.

    There is also an independent Windows launcher failure. The direct spawnSync(node_modules/.bin/biome, ...) used by biome-guards-bite returns error.code = "ENOENT" and status = null in this environment. A control invocation of the installed package through Node returns status 0 and Version: 2.5.11:

    node node_modules/@biomejs/biome/bin/biome --version
    

    biomeFindings() ignores result.error, so a failed launch becomes an empty diagnostic list. Its three planted guard assertions fail in the baseline. A supported cross-platform launcher plus an explicit spawn-error check is needed before judging those rules; the version control above does not establish that their lint behavior passes.

    For regression coverage, I would assert canonical real helper outputs, an exact relative-import edge, and that every discovered port has a source-body entry, then run the whole architecture project on Windows with existing scopes and zero-selection assertions intact. The current hand-authored slash-path probes miss the producer boundary. Any remaining debt should be assessed separately after the harness works.

    I organized the focused code relationships and findings with Brain Scanner; the behavior evidence above comes from native Node probes and Vitest. Implementation remains pending the maintainer validation requested above.

  7. blafourcade commented on Sep 17, 2026

    @blafourcade
    Contributor

    @kevin-lozada-santos Validated — go ahead, and thank you for the native Windows baseline. That is the evidence this issue was missing, and it corrects me on the size of the fix.

    I had labelled this good first issue on the strength of helpers.ts:19 alone. Your trace shows that was wrong, and I am removing the label: the producer boundary is wider than sourceFiles(), and a partial fix fails silently, which is the worst outcome available here. Your reading of the boundaries matches ours — normalize sourceFiles(), pluginReadmes() and resolveImportTarget() together, or the canonical-key lookup for relative imports breaks after the first change.

    On scope, three rulings so you do not have to guess:

    In scope for your PR. Path normalization in tests/architecture/helpers.ts, plus the independent producers in catches-that-swallow.arch.test.ts and ports-are-called.arch.test.ts, plus the diagnostic paths in comments and no-shared-binary. Your instinct is right: normalize what is compared, keep filesystem roots and operations native. A rule matching on / against a string built by join() is the bug; changing how we touch the disk is not the fix.

    In scope, and please keep it. The zero-selection assertions. A rule that selects nothing must fail, on every platform — that is the guard this whole issue exists for, and it is the one thing that would have caught this on day one.

    Separate issue, please, not this PR. The Biome launcher. spawnSync on node_modules/.bin/biome returning ENOENT while node node_modules/@biomejs/biome/bin/biome returns 0 is a real defect and your control invocation proves it, but it is a different failure — a launcher that cannot start, not a path that cannot match. That biomeFindings() swallows result.error and reports an empty finding list is the more serious half of it: a guard that reports nothing looks identical to a guard that passed. Open it with the evidence you already have and I will treat it as a bug.

    Out of scope entirely. The two collection errors (docs-do-not-lie, mutation-covers-source) and any debt baseline. Leave both untouched, exactly as you proposed.

    Two notes on your environment, neither blocking: you ran pnpm 11.19.0 against a 12.3.4 declaration with --ignore-scripts, so if a result surprises you, re-check it under the declared version before concluding. And --experimental-strip-types on a .ts import is fine for a probe, but the regression tests should go through the normal vitest path.

    For the regression coverage, your three assertions are the right ones. Add the platform dimension explicitly rather than relying on the runner's separator: a test that builds a backslash path and asserts the rule still selects it fails on Linux CI today and would have made this issue unnecessary.


    Generated by Claude Code

  8. kevin-lozada-santos commented on Sep 17, 2026

    @kevin-lozada-santos
    Contributor
  9. blafourcade commented on Sep 24, 2026

    @blafourcade
    Contributor

    Resolved by #880, merged into next as 16c7c650.

    The path producers and import lookup now use canonical / comparison keys while filesystem operations remain native. Explicit backslash regressions pass, the Windows CI job is green, and the current architecture suite passes 139/139 locally.

    #881 separately owns the Windows Biome launcher failure. No remaining #863 scope is open.

  10. moved this from Todo to Done in AIDD Roadmapon Sep 24, 2026
  11. closed this as completedby moving to Done in AIDD Roadmapon Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    Priority

    High

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions