Skip to content

feat(coding-agent): keep shadowed same-name skills independently selectable - #2409

Closed
yangjj-iso wants to merge 2 commits into
bastani-inc:mainfrom
yangjj-iso:fix/2328-shadowed-skills-qualified-selection
Closed

feat(coding-agent): keep shadowed same-name skills independently selectable#2409
yangjj-iso wants to merge 2 commits into
bastani-inc:mainfrom
yangjj-iso:fix/2328-shadowed-skills-qualified-selection

Conversation

@yangjj-iso

@yangjj-iso yangjj-iso commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

| Related unit tests | 18/18 pass |


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Greptile Summary

This change exposes source-qualified access to shadowed skills and updates compaction statistics. Reproduced failures show that interactive collision diagnostics can advertise selectors that do not resolve, skill catalog fallback labels can duplicate another skill’s identity, and dropped protected tails are excluded from the reported pre-compaction context size and reduction percentage. These issues should be corrected before merge.

Confidence Score: 2/5

Not safe to merge until the three reproduced correctness issues are fixed.

Executable harnesses exercised the affected production paths and reproduced three independent non-security failures: invalid displayed selectors, duplicate skill identities, and inaccurate whole-context compaction statistics.

Files Needing Attention: packages/coding-agent/src/modes/interactive/interactive-resource-disclosure.ts, packages/coding-agent/src/core/skill-catalog.ts, and packages/coding-agent/src/core/compaction/compaction-runner.ts.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the colliding-skill selector harness to validate the posted P1 finding and reviewed the resulting logs and diagnostics.
  • Executed the narrow skill catalog label collision harness to exercise control and collision scenarios, capturing results for distinct fallback and duplicate builtin labels.
  • Focused harness validated that a dropped protected tail is excluded from stats, with the focused harness source captured and runtime output confirming the exclusion.
  • Validated the skill-selector-disclosure harness results and production-diff checks, including an intentional failing assertion that verifies the expected behavior while the production file diff passed.
  • Confirmed that labels, IDs, and selectors are unique and observed how collision handling assigns the later builtin candidate when conflicts arise.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (3)

  1. General comment

    P1 Collision diagnostic prints selectors that the skill catalog cannot resolve

    • Bug
      • For two shadowed skills with the same name and SourceInfo values scope: "temporary" and source: "local", the diagnostic displays /skill:duplicate@temporary for both losers. The catalog disambiguates the same candidates as duplicate@beta and duplicate@gamma; resolving either displayed selector fails rather than selecting its displayed loser.
    • Cause
      • interactive-resource-disclosure.ts derives the qualifier directly as loserSource?.scope ?? loserSource?.source ?? "path", whereas skill-catalog.ts applies group-level uniqueness logic and replaces colliding short labels with base-directory labels.
    • Fix
      • Have collision disclosure obtain the selector/unique source label from getSkillCatalog (or share the catalog's unique-label derivation) and format the loser command from that canonical selector.

    T-Rex Ran code and verified through T-Rex

  2. General comment

    P1 Fallback source label can duplicate a later unique source label

    • Bug
      • With two initial generic path labels, a first candidate whose baseDir ends in builtin receives fallback label builtin. A later candidate with unique short source label builtin is processed afterward and receives the same label. The resulting catalog contains duplicate sourceLabel and duplicate candidate id values (tdd@builtin).
    • Cause
      • uniqueLabelInGroup records labels in used only while processing the second pass. A unique short label is not reserved before duplicate fallback labels are generated, so an earlier path-derived fallback can consume a later unique candidate's short label.
    • Fix
      • Initialize/reserve used with all labels whose count is one before generating any duplicate fallbacks, or otherwise ensure each fallback is checked against every short label in the group as well as generated labels.

    T-Rex Ran code and verified through T-Rex

  3. General comment

    P1 Dropped protected tail is omitted from pre-compaction whole-context stats

    • Bug
      • computeWholeContextStats reports tokensBefore as 1,000 and reduction as 75% for a 1,000-token region plus a dropped 300-token protected tail and 250 post-compaction tokens. A pre-compaction whole-context metric should instead report 1,300 tokens and 80.8% reduction.
    • Cause
      • At packages/coding-agent/src/core/compaction/compaction-runner.ts:117, tokensBefore conditionally adds tailEstimate only when keptTail is true, even though the tail was present before compaction.
    • Fix
      • Always include tailEstimate in tokensBefore; continue to conditionally include it only in tokensAfter based on keptTail, then calculate the percentage from those whole-context values.

    T-Rex Ran code and verified through T-Rex

Prompt To Fix All With AI
### Issue 1
packages/coding-agent/src/modes/interactive/interactive-resource-disclosure.ts:39-44
**Collision diagnostics print unresolvable selectors**

Collision disclosure derives each losing skill's qualifier from `scope` or `source`, instead of using the catalog's collision-specific `sourceLabel`. For colliding temporary/local skills, this displays `/skill:duplicate@temporary` for both losers, while the catalog only resolves unique selectors such as `duplicate@beta` and `duplicate@gamma`. Copying the displayed command therefore cannot select the identified skill. Format the command from the catalog's canonical selector, or share its unique-label calculation.

### Issue 2
packages/coding-agent/src/core/skill-catalog.ts:86-104
**Fallback labels collide with reserved source labels**

A duplicate generic-path group can generate a fallback label equal to a later candidate's unique short label. For example, a path candidate under a `builtin` directory is assigned `builtin` before a later uniquely builtin-sourced candidate is processed, so both receive `sourceLabel` and ID `tdd@builtin`. Qualified resolution selects only one of the two while transcript and catalog identity metadata cannot distinguish them. Reserve every unique short label before assigning path-derived fallbacks.

### Issue 3
packages/coding-agent/src/core/compaction/compaction-runner.ts:117
**Dropped tails are omitted from pre-compaction stats**

When fresh compaction drops a protected tail, `tokensBefore` excludes `tailEstimate` even though that tail was present in the original context. A 1,000-token region with a dropped 300-token tail and 250-token result is reported as 1,000 tokens and 75% reduction instead of 1,300 tokens and 80.8%. Include the tail in `tokensBefore` unconditionally while retaining the conditional inclusion in `tokensAfter`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(coding-agent): keep shadowed same-n..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

…ctable

When multiple skill files declare the same frontmatter name, Atomic currently keeps only the precedence winner and discards the loser. This makes the shadowed skill completely inaccessible without renaming or moving files.

This change retains collision losers in a shadowedSkills array and introduces a skill catalog (skill-catalog.ts) that exposes source-qualified selectors like /skill:tdd@user and /skill:tdd@builtin. The bare /skill:name continues to select the precedence winner. Autocomplete, pi.getCommands(), RPC get_commands, extension bindings, and collision diagnostics all share the same qualified identities.

A qualified selector is exact: an unknown or ambiguous alias reports an error and never falls back to the bare winner. The skill block parser gains an optional candidate attribute for internal identity.

The ExtensionContext surface gains an optional getSkillCatalog() method so extensions can resolve exact skill selectors programmatically.

Closes bastani-inc#2328
Comment on lines +39 to +44
const loserSource = this.findSourceInfoForPath(d.collision.loserPath, sourceInfos);
const label = loserSource?.scope ?? loserSource?.source ?? "path";
lines.push(
theme.fg(
"dim",
` ${theme.fg("warning", "✗")} ${this.formatPathWithSource(d.collision.loserPath, this.findSourceInfoForPath(d.collision.loserPath, sourceInfos))} (skipped)`,
` ${theme.fg("warning", "✗")} ${this.formatPathWithSource(d.collision.loserPath, loserSource)} (/skill:${name}@${label})`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Collision diagnostics print unresolvable selectors

Collision disclosure derives each losing skill's qualifier from scope or source, instead of using the catalog's collision-specific sourceLabel. For colliding temporary/local skills, this displays /skill:duplicate@temporary for both losers, while the catalog only resolves unique selectors such as duplicate@beta and duplicate@gamma. Copying the displayed command therefore cannot select the identified skill. Format the command from the catalog's canonical selector, or share its unique-label calculation.

Artifacts

Executable colliding-skill selector harness

  • The exact non-production Bun harness creates the three colliding skills, invokes catalog construction and diagnostic formatting, and asserts selector resolution, showing the mismatch.

Expected catalog selectors resolve to each displayed loser

  • Executed the harness with its catalog-derived expectations; `duplicate@beta` and `duplicate@gamma` each resolve to their respective loser path, establishing the correct comparison baseline.

Printed diagnostic selectors fail to resolve

  • Executed the harness assertion against the selectors actually printed by diagnostics; both `duplicate@temporary` selectors fail and the command exits 1, confirming the defect.

Production diagnostic file had no local changes

  • Executed a focused git diff/status check for the reported production file and the harness, showing no production-code modification was made.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/coding-agent/src/modes/interactive/interactive-resource-disclosure.ts
Line: 39-44

Comment:
**Collision diagnostics print unresolvable selectors**

Collision disclosure derives each losing skill's qualifier from `scope` or `source`, instead of using the catalog's collision-specific `sourceLabel`. For colliding temporary/local skills, this displays `/skill:duplicate@temporary` for both losers, while the catalog only resolves unique selectors such as `duplicate@beta` and `duplicate@gamma`. Copying the displayed command therefore cannot select the identified skill. Format the command from the catalog's canonical selector, or share its unique-label calculation.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +86 to +104
// Use a path-derived suffix for duplicates
const used = new Set<string>();
for (const skill of candidates) {
const shortLabel = labelMap.get(skill.filePath)!;
if ((labelCounts.get(shortLabel) ?? 0) <= 1) {
labelMap.set(skill.filePath, shortLabel);
used.add(shortLabel);
continue;
}
// Derive a unique label from the base directory name
const baseName = skill.baseDir.split(/[\\/]/).filter(Boolean).pop() ?? shortLabel;
let candidate = baseName;
let suffix = 2;
while (used.has(candidate)) {
candidate = `${baseName}-${suffix++}`;
}
used.add(candidate);
labelMap.set(skill.filePath, candidate);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Fallback labels collide with reserved source labels

A duplicate generic-path group can generate a fallback label equal to a later candidate's unique short label. For example, a path candidate under a builtin directory is assigned builtin before a later uniquely builtin-sourced candidate is processed, so both receive sourceLabel and ID tdd@builtin. Qualified resolution selects only one of the two while transcript and catalog identity metadata cannot distinguish them. Reserve every unique short label before assigning path-derived fallbacks.

Artifacts

Narrow skill catalog label collision harness source

  • The authored Bun harness constructs control and collision skill groups, invokes `getSkillCatalog`, and records labels, IDs, selectors, and resolution; it directly exercises the reported ordering case.

Control run with distinct fallback and builtin labels

  • Executed `bun trex-artifacts/skill_catalog_collision_harness.ts control` in `/home/user/repo`; labels, candidate IDs, and selectors are unique and `tdd@builtin` resolves to the builtin candidate.

Collision run with duplicate builtin labels and candidate IDs

  • Executed `bun trex-artifacts/skill_catalog_collision_harness.ts collision` in `/home/user/repo`; two candidates receive label and ID `tdd@builtin`, while selectors remain unique and resolution selects the qualified later candidate.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/coding-agent/src/core/skill-catalog.ts
Line: 86-104

Comment:
**Fallback labels collide with reserved source labels**

A duplicate generic-path group can generate a fallback label equal to a later candidate's unique short label. For example, a path candidate under a `builtin` directory is assigned `builtin` before a later uniquely builtin-sourced candidate is processed, so both receive `sourceLabel` and ID `tdd@builtin`. Qualified resolution selects only one of the two while transcript and catalog identity metadata cannot distinguish them. Reserve every unique short label before assigning path-derived fallbacks.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

): VerbatimCompactionStats {
const beforeTail = region.tokenEstimate;
const afterTail = keptTail ? tailEstimate : 0;
const tokensBefore = beforeTail + (keptTail ? tailEstimate : 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Dropped tails are omitted from pre-compaction stats

When fresh compaction drops a protected tail, tokensBefore excludes tailEstimate even though that tail was present in the original context. A 1,000-token region with a dropped 300-token tail and 250-token result is reported as 1,000 tokens and 75% reduction instead of 1,300 tokens and 80.8%. Include the tail in tokensBefore unconditionally while retaining the conditional inclusion in tokensAfter.

Artifacts

Focused harness source for a dropped protected tail

  • Authored TypeScript harness invokes computeWholeContextStats with a 1,000-token region and a dropped 300-token tail, asserting whole-context pre-compaction accounting; it defines the executed check.

Captured focused harness source

  • Captured output of reading the exact harness source before runtime execution; it shows the inputs and expected whole-context assertions.

Runtime output showing dropped tail excluded from stats

  • Captured Bun execution output shows observed tokensBefore 1000 and percentReduction 75 versus expected 1300 and 80.8, followed by the assertion failure; the reported behavior is confirmed.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/coding-agent/src/core/compaction/compaction-runner.ts
Line: 117

Comment:
**Dropped tails are omitted from pre-compaction stats**

When fresh compaction drops a protected tail, `tokensBefore` excludes `tailEstimate` even though that tail was present in the original context. A 1,000-token region with a dropped 300-token tail and 250-token result is reported as 1,000 tokens and 75% reduction instead of 1,300 tokens and 80.8%. Include the tail in `tokensBefore` unconditionally while retaining the conditional inclusion in `tokensAfter`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@yangjj-iso

Copy link
Copy Markdown
Contributor Author

Superseded by #2449.

This branch's headline change — source-qualified selectors for shadowed same-name skills (#2328) — has since landed on main independently via d4dc771b0 ("feat(coding-agent): keep shadowed same-name skills independently selectable"), with a more complete implementation. That made this branch's #2328 code obsolete and conflicting with main, which is why the checks kept failing.

This PR also bundled the #2052 compaction-stats fix, which is still needed. It has been rebased onto the latest main as a focused, standalone PR — #2449 — and is green (npm run check + all compaction unit/integration suites).

Closing in favor of #2449.

@yangjj-iso yangjj-iso closed this Aug 16, 2026
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