v1.91.1.0 fix: harden Impeccable plugin discovery (#2978)

* fix(design-detect): find impeccable installed as a Claude Code plugin

The design-detector probe only ever checked <root>/<SKILL_ROOTS>/skills/impeccable/,
never the Claude Code plugin-cache layout
(<root>/.claude/plugins/cache/<marketplace>/<plugin>/<version>/skills/impeccable/).
A plugin-installed impeccable was therefore invisible: IMPECCABLE_SKILL stayed
absent, the launcher was never found (so the NOT_CACHED run hint never fired),
and its sibling engine was never considered.

Add a plugin-cache walk alongside the existing SKILL_ROOTS walk, sharing the
same presence/launcher/repo-local-exclusion/sibling-engine logic via an
extracted checkSkillDir() helper so both paths stay behaviorally identical.

Fixes #2838

* refactor(design-detect): consolidate newestSemverDir onto safeReaddir

Both did the identical try/catch-around-readdirSync; newestSemverDir now
reuses the new safeReaddir helper instead of duplicating it.

* fix: harden Impeccable plugin discovery and regression fixtures

* test: supply eval mode to the integrated detector callback adapter

---------

Co-authored-by: Som Samantray <som.samantray@gmail.com>
This commit is contained in:
Garry Tan
2026-09-25 14:32:12 -04:00
committed by GitHub
co-authored by Som Samantray
parent 7b534d3e90
commit 2a113ae7e6
18 changed files with 938 additions and 40 deletions
+31
View File
@@ -25,6 +25,17 @@ const BIN = path.join(ROOT, 'bin', 'gstack-gbrain-repo-policy');
let tmpHome: string;
function isolateGitRemote(repo: string, url: string): void {
const git = (...args: string[]) => {
const result = spawnSync('git', args, { cwd: repo, encoding: 'utf8', timeout: 10_000 });
expect(result.status).toBe(0);
return result.stdout.trim();
};
expect(git('config', '--get', 'remote.origin.url')).toBe(url);
git('config', '--local', `url.${url}.insteadOf`, url);
expect(git('remote', 'get-url', 'origin')).toBe(url);
}
function run(args: string[], opts: { env?: Record<string, string> } = {}) {
const res = spawnSync(BIN, args, {
env: { ...process.env, GSTACK_HOME: tmpHome, ...(opts.env || {}) },
@@ -54,6 +65,25 @@ afterEach(() => {
fs.rmSync(tmpHome, { recursive: true, force: true });
});
test('fixture origin isolation overrides a controlled URL rewrite without changing the stored remote', () => {
const repo = path.join(tmpHome, 'repo');
fs.mkdirSync(repo);
const git = (...args: string[]) => {
const result = spawnSync('git', args, { cwd: repo, encoding: 'utf8', timeout: 10_000 });
expect(result.status).toBe(0);
return result.stdout.trim();
};
const url = 'https://fixture.invalid/org/repo.git';
git('init', '-q');
git('remote', 'add', 'origin', url);
git('config', '--local', 'url.https://mirror.invalid/.insteadOf', 'https://fixture.invalid/');
expect(git('config', '--get', 'remote.origin.url')).toBe(url);
expect(git('remote', 'get-url', 'origin')).toBe('https://mirror.invalid/org/repo.git');
isolateGitRemote(repo, url);
expect(git('config', '--get', 'remote.origin.url')).toBe(url);
expect(git('remote', 'get-url', 'origin')).toBe(url);
});
describe('normalize', () => {
test('strips https:// and .git', () => {
const r = run(['normalize', 'https://github.com/foo/bar.git']);
@@ -293,6 +323,7 @@ describe('gstack-gbrain-sync code stage honors the repo policy (#2140 sync path)
spawnSync('git', args, { cwd: repoDir, encoding: 'utf-8', timeout: 30_000 });
git('init', '-q', '.');
git('remote', 'add', 'origin', REPO_URL);
isolateGitRemote(repoDir, REPO_URL);
fs.writeFileSync(path.join(repoDir, 'README.md'), 'fixture\n');
git('add', '-A');
git('-c', 'user.email=t@t', '-c', 'user.name=t', 'commit', '-qm', 'fixture');