diff --git a/README.md b/README.md index 8198451..e4dd766 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ pnpm --filter asil-runners auto grind --max-tasks 3 See [`examples/quickstart.md`](examples/quickstart.md) for a five-minute integration walkthrough. +Lessons from running these skills unsupervised (what fought automation, fuzzy boundaries under load) are in [`docs/automation-friction.md`](docs/automation-friction.md). + ## What it looks like ``` diff --git a/docs/automation-friction.md b/docs/automation-friction.md new file mode 100644 index 0000000..0524d04 --- /dev/null +++ b/docs/automation-friction.md @@ -0,0 +1,78 @@ +# Lessons from wiring agent-skills into an autonomous loop + +Reply draft for [addyosmani/agent-skills#97](https://github.com/addyosmani/agent-skills/issues/97) — what fought automation, and where boundaries went fuzzy under load. Grounded in production use on an 80+ agent platform and in the ASIL open-source extract. + +--- + +## Draft reply (ready to paste) + +@addyosmani Glad the write-up was useful — here is the gold you asked for. After running 14 skills + the 3 personas unsupervised at production scale, these are the places the skills fought the automation or a boundary went fuzzy. + +### 1. Personas assumed a human merge step (biggest friction) + +`agents/code-reviewer.md`, `security-auditor.md`, and `test-engineer.md` are excellent for human-in-the-loop review. Unattended, they over-reject. + +- The code reviewer audited the *file*, not the *diff* — pre-existing issues in unchanged lines became blockers. +- The security auditor treated dead-code / unused-export cleanups as attack-surface changes. +- The test engineer demanded new tests for pure deletions and visibility tweaks. + +**Fix that stuck:** a hard scoping preamble on every persona prompt — "review the DIFF only; pre-existing issues are out of scope; calibrate scrutiny to change risk." Same preamble on the adversarial (Codex) gate. Without it, trivial tasks never cleared the gate. + +### 2. Section title mismatch silently dropped the anti-rationalization tables + +Your skills title the table **Common Rationalizations**. Our executor originally looked for **Anti-Rationalization Table**. Under automation that meant the tables — the piece we called out as most important — were never injected into the enforcement block. Agents fell through to a generic one-liner fallback. + +**Fix:** accept both headers. (Shipped in this repo's skill loader.) + +### 3. Skill path / name layout was not automation-friendly + +Submodule layout is `skills//SKILL.md`. Early ASIL expected `skills/.md`, and several category/thinker names did not match upstream folders 1:1 (`security-review` vs `security-and-hardening`, `testing-strategy` / `test-coverage-improvement` vs `test-driven-development`, etc.). Missing files failed open into inline fallbacks — so the loop "worked" while ignoring the submodule. + +**Fix:** resolve flat file → `SKILL.md` → alias chain, in that order. + +### 4. Skills written for feature work fought cleanup categories + +| Skill / persona | Fought when… | +|---|---| +| `test-driven-development` | Applied to dead-code / simplification — insisted on RED-GREEN for no behavior change | +| `security-and-hardening` | Applied to one-line visibility changes — invented auth concerns | +| `code-simplification` | Fine alone; combined with TDD persona → thrash on "simplify but also add tests" | +| `incremental-implementation` | Wanted thin slices; the scanner queued whole-file TODO clusters | + +**Pattern:** skills encode *how a human should work a feature*. Autonomous categories are often *mechanical cleanup*. Mapping category → skill needs an explicit "no behavior change → skip TDD ceremony" delta, not just "load the skill." + +### 5. Priority queues let one skill monopolize the run + +Strict priority (`test-failure` → … → `documentation`) meant a noisy test suite starved security / type-error work for an entire grind. Round-robin across categories fixed starvation without abandoning severity ordering inside a category. + +### 6. Scanner false-positives became skill false-confidence + +Dead-code skill + shallow "no in-repo references" detection = the loop confidently proposed deleting public API surface from package entry points. The skill did its job; the *task* was wrong. We had to exempt entry points and put honest uncertainty in the task description before the skill ran. + +### 7. What held up under load + +- **Common Rationalizations / Red Flags** — once actually injected, they were the highest-leverage content. Unsupervised agents rationalize exactly the rows in those tables. +- **Fail-closed JSON** from personas and the adversarial gate (unparseable → reject). +- **Cost checkpoints around the review fan-out** — three personas + Codex after a fat executor call is where budgets die; force-check before the fan-out. +- **Domain-question markers** — the cleanest "skill stops here" boundary we found. Skills should say when to emit one instead of inventing domain logic. + +Happy to go deeper on any of these. The reference implementation is [TelivityAI/asil](https://github.com/TelivityAI/asil); the skill-loader / rationalization fixes from this write-up live there too. + +--- + +## Mapping: ASIL names → upstream skills + +| ASIL category / thinker | Primary name | Upstream alias | +|---|---|---| +| vulnerability | `security-review` | `security-and-hardening` | +| test-failure / coverage-gap | `test-coverage-improvement` | `test-driven-development` | +| test-strategist thinker | `test-driven-development` | (`testing-strategy` still aliases) | +| security thinker | `security-and-hardening` | (`security-review` still aliases) | +| dead-code | `dead-code-removal` | `code-simplification` | +| dependency-update | `dependency-update` | `deprecation-and-migration` | +| documentation | `documentation-generation` | `documentation-and-adrs` | +| todo-resolution | `todo-resolution` | `incremental-implementation`, `debugging-and-error-recovery` | +| type-error / complexity | `code-simplification` | (exact match) | +| planner / spec-writer | `planning-and-task-breakdown` / `spec-driven-development` | (exact match) | + +Resolution order for every name: `skills/.md` → `skills//SKILL.md` → same for each alias. diff --git a/examples/quickstart.md b/examples/quickstart.md index 4ec6552..a2210c0 100644 --- a/examples/quickstart.md +++ b/examples/quickstart.md @@ -39,13 +39,22 @@ REPO_ROOT=/absolute/path/to/your/repo ## 3. Drop in the runner skills -ASIL's thinkers expect Markdown skill files at `/.asil/skills/`. The simplest setup: +ASIL's thinkers and executor load Markdown skills from `ASIL_SKILLS_PATH` (default `/.asil/skills`). Two layouts are supported: + +```bash +# A) Flat files (early ASIL installs / hand-copied skills) +.asil/skills/skills/planning-and-task-breakdown.md + +# B) Upstream agent-skills layout (git submodule or clone) +.asil/skills/skills/planning-and-task-breakdown/SKILL.md +``` ```bash mkdir -p .asil/skills -# Copy or symlink any Markdown skills (e.g. from a public skill library) you want to use. -# A minimal install is fine — the thinkers fall back to inline defaults -# if a skill file is missing. +# Option: submodule an upstream skill library, then point ASIL_SKILLS_PATH at it. +# Historical ASIL names (security-review, testing-strategy, …) alias onto +# upstream folder names (security-and-hardening, test-driven-development, …). +# Thinkers fall back to inline defaults if a skill file is missing. ``` Override the location with `ASIL_SKILLS_PATH` if you want them somewhere else. diff --git a/packages/asil-improvement-loop/src/__tests__/executor.test.ts b/packages/asil-improvement-loop/src/__tests__/executor.test.ts index 6cbccc0..a143ab0 100644 --- a/packages/asil-improvement-loop/src/__tests__/executor.test.ts +++ b/packages/asil-improvement-loop/src/__tests__/executor.test.ts @@ -456,6 +456,23 @@ describe('executor', () => { it('returns empty string when the section is missing', () => { expect(extractAntiRationalization('# Just a skill\n\nNo table here.')).toBe(''); }); + + it('also extracts upstream "Common Rationalizations" sections', () => { + const skill = [ + '# Skill', + '', + '## Common Rationalizations', + '| Rationalization | Reality |', + '| Tests later | Write them first |', + '', + '## Red Flags', + '- x', + ].join('\n'); + const section = extractAntiRationalization(skill); + expect(section).toMatch(/Common Rationalizations/); + expect(section).toMatch(/Tests later/); + expect(section).not.toMatch(/Red Flags/); + }); }); describe('buildExecutionPrompt', () => { diff --git a/packages/asil-improvement-loop/src/__tests__/skill-loader.test.ts b/packages/asil-improvement-loop/src/__tests__/skill-loader.test.ts new file mode 100644 index 0000000..cb99248 --- /dev/null +++ b/packages/asil-improvement-loop/src/__tests__/skill-loader.test.ts @@ -0,0 +1,93 @@ +import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { + extractAntiRationalization, + loadSkillMarkdown, + skillCandidatePaths, +} from '../skill-loader.js'; + +describe('skillCandidatePaths', () => { + it('lists flat .md then SKILL.md, then aliases', () => { + expect(skillCandidatePaths('security-review')).toEqual([ + join('skills', 'security-review.md'), + join('skills', 'security-review', 'SKILL.md'), + join('skills', 'security-and-hardening.md'), + join('skills', 'security-and-hardening', 'SKILL.md'), + ]); + }); +}); + +describe('loadSkillMarkdown', () => { + it('loads upstream agent-skills layout via alias', () => { + const root = mkdtempSync(join(tmpdir(), 'asil-skills-')); + const dir = join(root, 'skills', 'security-and-hardening'); + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, 'SKILL.md'), '# Security\n\nHardening rules.\n'); + + expect(loadSkillMarkdown(root, 'security-review')).toContain('Hardening'); + }); + + it('prefers flat primary name when present', () => { + const root = mkdtempSync(join(tmpdir(), 'asil-skills-')); + mkdirSync(join(root, 'skills'), { recursive: true }); + writeFileSync( + join(root, 'skills', 'security-review.md'), + '# Flat security review\n', + ); + const dir = join(root, 'skills', 'security-and-hardening'); + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, 'SKILL.md'), '# Upstream\n'); + + expect(loadSkillMarkdown(root, 'security-review')).toContain('Flat security'); + }); + + it('returns empty string when nothing matches', () => { + expect(loadSkillMarkdown('/nonexistent', 'nope')).toBe(''); + }); +}); + +describe('extractAntiRationalization', () => { + it('extracts Anti-Rationalization Table sections', () => { + const skill = [ + '# Skill', + '', + '## Anti-Rationalization Table', + '| Rationalization | Reality |', + '| --- | --- |', + '| Skip tests | Never |', + '', + '## Red Flags', + '- x', + ].join('\n'); + const section = extractAntiRationalization(skill); + expect(section).toMatch(/Anti-Rationalization/i); + expect(section).toMatch(/Skip tests/); + expect(section).not.toMatch(/Red Flags/); + }); + + it('extracts Common Rationalizations sections from upstream skills', () => { + const skill = [ + '# Test-Driven Development', + '', + '## Common Rationalizations', + '| Rationalization | Reality |', + '| --- | --- |', + '| Tests later | Tests first |', + '', + '## Red Flags', + '- shipping without tests', + ].join('\n'); + const section = extractAntiRationalization(skill); + expect(section).toMatch(/Common Rationalizations/); + expect(section).toMatch(/Tests later/); + expect(section).not.toMatch(/Red Flags/); + }); + + it('returns empty string when the section is missing', () => { + expect(extractAntiRationalization('# Just a skill\n\nNo table here.')).toBe( + '', + ); + }); +}); diff --git a/packages/asil-improvement-loop/src/executor.ts b/packages/asil-improvement-loop/src/executor.ts index e8db27a..3c77d67 100644 --- a/packages/asil-improvement-loop/src/executor.ts +++ b/packages/asil-improvement-loop/src/executor.ts @@ -9,9 +9,20 @@ import type { LLMCaller, LLMResponse, } from './types.js'; +import { + extractAntiRationalization, + loadSkillMarkdown, +} from './skill-loader.js'; import { CATEGORY_SKILL_MAP } from './types.js'; import type { CommandRunner } from './scanner.js'; +export { extractAntiRationalization } from './skill-loader.js'; +export { + loadSkillMarkdown, + skillCandidatePaths, + SKILL_NAME_ALIASES, +} from './skill-loader.js'; + /** * Structured logger contract. Defaults to stderr via `console.error`, * but tests (and the CLI) can inject a spy/mock to capture events. @@ -467,11 +478,7 @@ async function defaultSkillLoader( skillsPath: string, skillName: string, ): Promise { - try { - return readFileSync(join(skillsPath, 'skills', `${skillName}.md`), 'utf8'); - } catch { - return ''; - } + return loadSkillMarkdown(skillsPath, skillName); } function defaultReadFile(absPath: string): string | null { @@ -482,27 +489,6 @@ function defaultReadFile(absPath: string): string | null { } } -/** Extracts the anti-rationalization table — a section commonly titled - * "Anti-Rationalization Table" in the configured skills. Returns the skill - * content unchanged if we can't locate it. */ -export function extractAntiRationalization(skill: string): string { - if (!skill) return ''; - const lines = skill.split(/\r?\n/); - const startIdx = lines.findIndex((l) => - /^#{1,6}\s*anti[- ]?rationalization/i.test(l), - ); - if (startIdx === -1) return ''; - const afterStart = lines.slice(startIdx); - const nextHeaderIdx = afterStart - .slice(1) - .findIndex((l) => /^#{1,6}\s+/.test(l)); - const slice = - nextHeaderIdx === -1 - ? afterStart - : afterStart.slice(0, nextHeaderIdx + 1); - return slice.join('\n').trim(); -} - export function buildExecutionPrompt( skillContent: string, antiRationalization: string, diff --git a/packages/asil-improvement-loop/src/index.ts b/packages/asil-improvement-loop/src/index.ts index 2d89465..580dfad 100644 --- a/packages/asil-improvement-loop/src/index.ts +++ b/packages/asil-improvement-loop/src/index.ts @@ -51,6 +51,11 @@ export { type FileBlock, type FileFetcher, } from './executor.js'; +export { + loadSkillMarkdown, + skillCandidatePaths, + SKILL_NAME_ALIASES, +} from './skill-loader.js'; export { selfReview } from './self-review.js'; export { adversarialReview, diff --git a/packages/asil-improvement-loop/src/skill-loader.ts b/packages/asil-improvement-loop/src/skill-loader.ts new file mode 100644 index 0000000..d3aa3c5 --- /dev/null +++ b/packages/asil-improvement-loop/src/skill-loader.ts @@ -0,0 +1,83 @@ +/** + * Resolve Markdown skill content from either a flat file layout or the + * agent-skills directory layout (`skills//SKILL.md`), with aliases + * for historical ASIL names that map onto upstream skill names. + */ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * When ASIL's category / thinker names don't match upstream skill folder + * names 1:1, try these fallbacks in order after the primary name. + */ +export const SKILL_NAME_ALIASES: Readonly> = { + 'security-review': ['security-and-hardening'], + 'testing-strategy': ['test-driven-development'], + 'test-coverage-improvement': ['test-driven-development'], + 'dead-code-removal': ['code-simplification'], + 'dependency-update': ['deprecation-and-migration'], + 'documentation-generation': ['documentation-and-adrs'], + 'todo-resolution': [ + 'incremental-implementation', + 'debugging-and-error-recovery', + ], +}; + +/** Candidate relative paths under `skillsPath` for a single skill name. */ +export function skillCandidatePaths(skillName: string): string[] { + const names = [skillName, ...(SKILL_NAME_ALIASES[skillName] ?? [])]; + const paths: string[] = []; + for (const name of names) { + // Flat layout used by early ASIL installs and symlinked copies. + paths.push(join('skills', `${name}.md`)); + // Upstream agent-skills submodule / clone layout. + paths.push(join('skills', name, 'SKILL.md')); + } + return paths; +} + +/** + * Load skill Markdown from disk. Returns empty string when nothing matches + * so callers can fall back to inline defaults. + */ +export function loadSkillMarkdown( + skillsPath: string, + skillName: string, +): string { + for (const rel of skillCandidatePaths(skillName)) { + try { + return readFileSync(join(skillsPath, rel), 'utf8'); + } catch { + // try next candidate + } + } + return ''; +} + +/** + * Extracts the anti-/common-rationalization section from a skill. + * + * Upstream agent-skills title this "## Common Rationalizations"; some + * forks use "## Anti-Rationalization Table". Both must be recognized — + * otherwise the autonomous executor silently drops the tables that were + * the whole point of importing the skills. + */ +export function extractAntiRationalization(skill: string): string { + if (!skill) return ''; + const lines = skill.split(/\r?\n/); + const startIdx = lines.findIndex((l) => + /^#{1,6}\s*(?:anti[- ]?rationalization|common\s+rationalizations)\b/i.test( + l, + ), + ); + if (startIdx === -1) return ''; + const afterStart = lines.slice(startIdx); + const nextHeaderIdx = afterStart + .slice(1) + .findIndex((l) => /^#{1,6}\s+/.test(l)); + const slice = + nextHeaderIdx === -1 + ? afterStart + : afterStart.slice(0, nextHeaderIdx + 1); + return slice.join('\n').trim(); +} diff --git a/packages/asil-improvement-loop/src/types.ts b/packages/asil-improvement-loop/src/types.ts index 74131a6..85dafd8 100644 --- a/packages/asil-improvement-loop/src/types.ts +++ b/packages/asil-improvement-loop/src/types.ts @@ -26,6 +26,12 @@ export const CATEGORY_PRIORITY: Record = { documentation: 8, }; +/** + * Category → skill basename. Resolved by `loadSkillMarkdown`, which also + * tries upstream agent-skills folder names via `SKILL_NAME_ALIASES` and + * both flat (`skills/.md`) and directory (`skills//SKILL.md`) + * layouts. + */ export const CATEGORY_SKILL_MAP: Record = { 'test-failure': 'test-coverage-improvement', 'type-error': 'code-simplification', diff --git a/packages/asil-thought-multiplier/src/__tests__/skill-loader.test.ts b/packages/asil-thought-multiplier/src/__tests__/skill-loader.test.ts new file mode 100644 index 0000000..fb49261 --- /dev/null +++ b/packages/asil-thought-multiplier/src/__tests__/skill-loader.test.ts @@ -0,0 +1,40 @@ +import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { + loadSkillMarkdown, + skillCandidatePaths, +} from '../skill-loader.js'; +import { loadMarkdownSkill } from '../thinkers/shared.js'; + +describe('skillCandidatePaths', () => { + it('accepts names with or without .md and includes aliases', () => { + expect(skillCandidatePaths('testing-strategy.md')).toEqual([ + join('skills', 'testing-strategy.md'), + join('skills', 'testing-strategy', 'SKILL.md'), + join('skills', 'test-driven-development.md'), + join('skills', 'test-driven-development', 'SKILL.md'), + ]); + }); +}); + +describe('loadSkillMarkdown / loadMarkdownSkill', () => { + it('resolves agent-skills SKILL.md layout for thinker names', () => { + const root = mkdtempSync(join(tmpdir(), 'asil-tm-skills-')); + const dir = join(root, 'skills', 'test-driven-development'); + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, 'SKILL.md'), '# TDD\n\nRED GREEN REFACTOR\n'); + + expect(loadSkillMarkdown(root, 'testing-strategy.md')).toContain('RED GREEN'); + expect( + loadMarkdownSkill(root, 'testing-strategy.md', 'FALLBACK'), + ).toContain('RED GREEN'); + }); + + it('falls back when the skill is missing', () => { + expect(loadMarkdownSkill('/nonexistent', 'missing.md', 'FALLBACK')).toBe( + 'FALLBACK', + ); + }); +}); diff --git a/packages/asil-thought-multiplier/src/index.ts b/packages/asil-thought-multiplier/src/index.ts index 6d850f1..5e9568f 100644 --- a/packages/asil-thought-multiplier/src/index.ts +++ b/packages/asil-thought-multiplier/src/index.ts @@ -22,8 +22,14 @@ export { generateDoNotChange, } from './brief-builder.js'; export { runPapa, DEFAULT_CONFIG, type PapaResult } from './papa.js'; +export { + loadSkillMarkdown, + skillCandidatePaths, + SKILL_NAME_ALIASES, +} from './skill-loader.js'; export { runSpecWriter } from './thinkers/spec-writer.js'; export { runSecurity } from './thinkers/security.js'; export { runTestStrategist } from './thinkers/test-strategist.js'; export { runApiDesigner } from './thinkers/api-designer.js'; export { runPlanner } from './thinkers/planner.js'; +export { loadMarkdownSkill } from './thinkers/shared.js'; diff --git a/packages/asil-thought-multiplier/src/skill-loader.ts b/packages/asil-thought-multiplier/src/skill-loader.ts new file mode 100644 index 0000000..f99827f --- /dev/null +++ b/packages/asil-thought-multiplier/src/skill-loader.ts @@ -0,0 +1,55 @@ +/** + * Resolve Markdown skill content from either a flat file layout or the + * agent-skills directory layout (`skills//SKILL.md`), with aliases + * for historical ASIL thinker names that map onto upstream skill names. + */ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * When ASIL thinker skill filenames don't match upstream skill folder + * names 1:1, try these fallbacks in order after the primary name. + */ +export const SKILL_NAME_ALIASES: Readonly> = { + 'security-review': ['security-and-hardening'], + 'security-and-hardening': [], + 'testing-strategy': ['test-driven-development'], + 'test-driven-development': [], + 'planning-and-task-breakdown': [], + 'spec-driven-development': [], +}; + +/** Strip a trailing `.md` so callers can pass either `foo` or `foo.md`. */ +function normalizeSkillName(skillFile: string): string { + return skillFile.replace(/\.md$/i, ''); +} + +/** Candidate relative paths under `skillsPath` for a single skill name. */ +export function skillCandidatePaths(skillFile: string): string[] { + const primary = normalizeSkillName(skillFile); + const names = [primary, ...(SKILL_NAME_ALIASES[primary] ?? [])]; + const paths: string[] = []; + for (const name of names) { + paths.push(join('skills', `${name}.md`)); + paths.push(join('skills', name, 'SKILL.md')); + } + return paths; +} + +/** + * Load skill Markdown from disk. Returns `null` when nothing matches so + * callers can fall back to inline defaults. + */ +export function loadSkillMarkdown( + skillsPath: string, + skillFile: string, +): string | null { + for (const rel of skillCandidatePaths(skillFile)) { + try { + return readFileSync(join(skillsPath, rel), 'utf8'); + } catch { + // try next candidate + } + } + return null; +} diff --git a/packages/asil-thought-multiplier/src/thinkers/security.ts b/packages/asil-thought-multiplier/src/thinkers/security.ts index ff86bbc..636b37d 100644 --- a/packages/asil-thought-multiplier/src/thinkers/security.ts +++ b/packages/asil-thought-multiplier/src/thinkers/security.ts @@ -19,7 +19,9 @@ export async function runSecurity( 'security', { label: 'Security Review', - skillFile: 'security-review.md', + // Prefer upstream agent-skills name; aliases still resolve + // `security-review.md` for older flat installs. + skillFile: 'security-and-hardening.md', fallbackInstructions: FALLBACK, }, request, diff --git a/packages/asil-thought-multiplier/src/thinkers/shared.ts b/packages/asil-thought-multiplier/src/thinkers/shared.ts index e5706fc..da6116a 100644 --- a/packages/asil-thought-multiplier/src/thinkers/shared.ts +++ b/packages/asil-thought-multiplier/src/thinkers/shared.ts @@ -5,8 +5,6 @@ * project rules, and asks the LLM to respond in a structured JSON * envelope we can parse back into ThinkerOutput. */ -import { readFileSync } from 'node:fs'; -import { join } from 'node:path'; import type { Concern, LLMCaller, @@ -19,6 +17,7 @@ import type { Priority, Severity, } from '../types.js'; +import { loadSkillMarkdown as resolveSkillMarkdown } from '../skill-loader.js'; /** Default engineering rules injected into every thinker's system prompt. */ const PROJECT_RULES = ` @@ -81,11 +80,9 @@ export function loadMarkdownSkill( fallback: string, ): string { if (!skillFile) return fallback; - try { - return readFileSync(join(markdownSkillsPath, 'skills', skillFile), 'utf8'); - } catch { - return fallback; - } + return ( + resolveSkillMarkdown(markdownSkillsPath, skillFile) ?? fallback + ); } export function buildSystemPrompt( diff --git a/packages/asil-thought-multiplier/src/thinkers/test-strategist.ts b/packages/asil-thought-multiplier/src/thinkers/test-strategist.ts index 4a20510..705933b 100644 --- a/packages/asil-thought-multiplier/src/thinkers/test-strategist.ts +++ b/packages/asil-thought-multiplier/src/thinkers/test-strategist.ts @@ -19,7 +19,9 @@ export function runTestStrategist( 'test-strategist', { label: 'Test Strategist', - skillFile: 'testing-strategy.md', + // Prefer upstream agent-skills name; aliases still resolve + // `testing-strategy.md` for older flat installs. + skillFile: 'test-driven-development.md', fallbackInstructions: FALLBACK, }, request,