Skip to content

fix(migrate): drop generic and numeric inferred triggers; match module paths case-insensitively (#349) - #386

Merged
stackbilt-admin merged 5 commits into
mainfrom
fix/349-trigger-inference
Oct 18, 2026
Merged

stackbilt-admin merged 5 commits into
mainfrom
fix/349-trigger-inference

Conversation

@stackbilt-admin

@stackbilt-admin stackbilt-admin commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Closes #349

Problem

Trigger inference in packages/cli/src/commands/adf-manifest-register.ts removed only STOPWORDS and a six-word GENERIC_HEADING_WORDS list. Heading words like images, components, workflow, release, apps and via, and tokens made only of digits, got through and were registered as ON_DEMAND triggers. The router matches each trigger against every word of the task (exactly, or as a long enough prefix stem), so these weak triggers loaded the module for almost every request.

The manifest writer also compared module paths case-sensitively. Infra.adf and an existing infra.adf entry could therefore get two entries for what is one file on Windows and macOS.

Fix

  • A wider GENERIC_HEADING_WORDS list, grouped by kind:

    • document structure: guide, overview, usage, getting started, ...
    • process and repo shape: workflow, release, steps, setup, commands, files, code, apps, components, images, ...
    • directive and filler words: via, using, when, how, all, always, never, ...

    Domain words such as deployment, testing, database, auth, docker, api, docs, config, scripts and assets are deliberately not on the list.

  • Numbers and version numbers are dropped: 1, 2024, 1.2, v2.1, v20.x, v2.0-beta.

  • No over-pruning of a module's own name. A generic word that is part of the module's file stem is still used, so release.adf keeps release.

  • Module paths are compared by moduleKey(path, caseInsensitive), which lowercases the normalized path only where the filesystem is case-insensitive. That is detected by isCaseInsensitiveDir(dir): it looks up manifest.adf with its case swapped, and RegisterOptions.caseInsensitive overrides it in tests.

    • It is used by the writer's existing-entry lookup and its same-call dedupe, and by bootstrap's orphan detection.
    • registerModules plans two spellings of one file once, with triggers inferred from the items of both.
    • When the manifest spells the module differently, the "already registered" reason gives the manifest's spelling (already registered as infra.adf under ON_DEMAND).
    • On Linux the two spellings are two files and the loader opens the exact name, so they are still registered separately.
    • A required DEFAULT_LOAD module (core.adf, state.adf) is always matched by its exact name, because findMissingDefaultLoad and doctor check it that way.
    • Earlier commits on this branch folded case on every platform. The review commit narrowed that: folding everywhere hid a differently spelled module on Linux and stopped bootstrap from repairing a miscased Core.adf.

Existing manifest entries are never rewritten. Only new registrations change.

Tests

  • adf-manifest-register.test.ts:
    • 16 fixture headings taken from real CLAUDE.md files (Build & Test Commands, Development Workflow, Release Process (v2.1), Step 1: Install Dependencies, Images and Components, Apps via Turborepo, Node 20 / pnpm 9 Setup, ...), each checked against its exact expected triggers.
    • digit and version tokens are dropped;
    • the module-stem exemption;
    • case-insensitive self-matching in inferTriggers;
    • case-insensitive mode: the writer matches an existing entry, reports its spelling and does not write; two spellings in one call give one entry; registerModules merges the items of both spellings;
    • case-sensitive mode: the other spelling is registered as its own module;
    • in both modes, core.adf is added when the manifest only has Core.adf, and findMissingDefaultLoad is then satisfied;
    • version tokens (v20.x, 1.x, v2.0-beta, v2.1.0-rc.1) are dropped and ES2022 is kept.
    • Two existing tests used workflow, release and apps as example triggers; they now use domain words.
  • bootstrap.test.ts: Infra.adf on disk with an infra.adf manifest entry. On a case-insensitive filesystem it is not reported as an orphan and gets no second entry; on a case-sensitive one it is reported and registered. So CI on Linux covers the other branch.
  • Full suite run serially: 1784 passed. The 3 failures are the known Windows-only ones (bootstrap bootstrap: phases 6/7 collect warnings without printing them; absoluteAiDirArg misses Windows paths on POSIX #307, context-refresh, serve-context). pnpm run benchmark:context passes.

🤖 Generated with Claude Code

kovermier and others added 5 commits October 17, 2026 20:16
… match module paths case-insensitively (#349)

Trigger inference kept tokens like images, components, workflow, release,
apps, via and all-digit tokens, which then loaded the module for almost any
task. GENERIC_HEADING_WORDS now covers the structure, process and filler
words of real CLAUDE.md headings; numbers and version numbers are dropped;
a generic word that is part of the module's own stem is still kept.

The manifest writer, the dry-run dedupe and bootstrap's orphan detection
compare module paths by moduleKey (normalized, lowercased), so Infra.adf and
infra.adf are one module and never get two entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ems (#349)

Review fixes. Folding everywhere hid a differently spelled module on Linux,
where the loader opens the exact name, and stopped bootstrap repairing a
miscased Core.adf that doctor checks by exact name. Now:
- moduleKey folds only when isCaseInsensitiveDir(manifest dir) (overridable
  via RegisterOptions.caseInsensitive); bootstrap's orphan check does the same
- a required DEFAULT_LOAD module is always matched by its exact name
- registerModules plans two spellings of one file once, from both item sets
- inferTriggers and the dry-run dedupe go back to exact matching
- version tokens like v20.x and v2.0-beta are dropped too
- correct the trigger-matching wording; no blank line in the CHANGELOG list

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d of adding a second one (#349)

On a case-insensitive filesystem, a manifest listing `Core.adf` with
`core.adf` on disk got a second `core.adf` bullet, and `adf bundle` loaded
the file twice. The DEFAULT_LOAD bullet is now respelled in place to the
name on disk, through the same re-parse guard; when the guard refuses, the
entry is kept with a warning. Under ON_DEMAND it is reported, not added.

- isCaseInsensitiveDir compares inode numbers (directory listing when the
  filesystem reports 0), so a MANIFEST.ADF is detected.
- Generic trigger words: task verbs (add, fix, create, ...) and generic
  nouns (known, requirements, examples, support, ...).
- v2.0-beta-2 is dropped as a version token.
- Already-registered detail names the matched spelling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t spelling in the ON_DEMAND move hint (#349)

`respelled` is now 'written' or 'planned', so a dry-run detail no longer
reports a respelling that was not made. The ON_DEMAND warning for a
miscased Core.adf says to move it `as core.adf`, the name doctor checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@stackbilt-admin
stackbilt-admin merged commit bfe7fe5 into main Oct 18, 2026
7 checks passed
@stackbilt-admin
stackbilt-admin deleted the fix/349-trigger-inference branch October 18, 2026 02:16
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.

adf migrate: trigger inference keeps generic and numeric tokens

2 participants