Compare commits
1 Commits
3d0e089a94
...
faae3b0609
| Author | SHA1 | Date |
|---|---|---|
|
|
faae3b0609 |
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
5. If `{failed_layers}` is non-empty, report which layers failed before announcing results. If zero findings remain after dropping dismissed AND `{failed_layers}` is non-empty, warn the user that the review may be incomplete rather than announcing a clean review.
|
||||
|
||||
6. If zero findings remain after triage (all rejected or none raised): state "✅ Clean review — all layers passed." (Step 3 already warned if any review layers failed via `{failed_layers}`.)
|
||||
6. If zero findings remain after dropping dismissed and no layers failed, note clean review.
|
||||
|
||||
|
||||
## NEXT
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ deferred_work_file: '{implementation_artifacts}/deferred-work.md'
|
|||
## RULES
|
||||
|
||||
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
|
||||
- When `{spec_file}` is set, always write findings to the story file before offering action choices.
|
||||
- `decision-needed` findings must be resolved before handling `patch` findings.
|
||||
- Always write findings to the story file before offering action choices.
|
||||
- Decision-needed findings must be resolved before handling patches.
|
||||
|
||||
## INSTRUCTIONS
|
||||
|
||||
|
|
@ -20,14 +20,14 @@ If zero findings remain after triage (all dismissed or none raised): state that
|
|||
|
||||
If `{spec_file}` exists and contains a Tasks/Subtasks section, append a `### Review Findings` subsection. Write all findings in this order:
|
||||
|
||||
1. **`decision-needed`** findings (unchecked):
|
||||
`- [ ] [Review][Decision] <Title> — <Detail>`
|
||||
1. **Decision needed** findings (unchecked):
|
||||
`- [ ] [Review][Decision] {Title} — {Detail}`
|
||||
|
||||
2. **`patch`** findings (unchecked):
|
||||
`- [ ] [Review][Patch] <Title> [<file>:<line>]`
|
||||
2. **Patch** findings (unchecked):
|
||||
`- [ ] [Review][Patch] {Title} [{file}:{line}]`
|
||||
|
||||
3. **`defer`** findings (checked off, marked deferred):
|
||||
`- [x] [Review][Defer] <Title> [<file>:<line>] — deferred, pre-existing`
|
||||
3. **Defer** findings (checked off, marked deferred):
|
||||
`- [x] [Review][Defer] {Title} [{file}:{line}] — deferred, pre-existing`
|
||||
|
||||
Also append each `defer` finding to `{deferred_work_file}` under a heading `## Deferred from: code review ({date})`. If `{spec_file}` is set, include its basename in the heading (e.g., `code review of story-3.3 (2026-03-18)`). One bullet per finding with description.
|
||||
|
||||
|
|
@ -35,50 +35,24 @@ Also append each `defer` finding to `{deferred_work_file}` under a heading `## D
|
|||
|
||||
Announce what was written:
|
||||
|
||||
> **Code review complete.** <D> `decision-needed`, <P> `patch`, <W> `defer`, <R> dismissed as noise.
|
||||
|
||||
If `{spec_file}` is set, add: `Findings written to the review findings section in {spec_file}.`
|
||||
Otherwise add: `Findings are listed above. No story file was provided, so nothing was persisted.`
|
||||
> **Code review complete.** {D} decision-needed, {P} patch, {W} deferred, {R} dismissed as noise.
|
||||
> Findings written to the review findings section in `{spec_file}`.
|
||||
|
||||
### 4. Resolve decision-needed findings
|
||||
|
||||
If `decision_needed` findings exist, present each one with its detail and the options available. The user must decide — the correct fix is ambiguous without their input. Walk through each finding (or batch related ones) and get the user's call. Once resolved, each becomes a `patch`, `defer`, or is dismissed.
|
||||
|
||||
If the user chooses to defer, ask: Quick one-line reason for deferring this item? (helps future reviews): — then append that reason to both the story file bullet and the `{deferred_work_file}` entry.
|
||||
### 5. Handle patch findings
|
||||
|
||||
**HALT** — I am waiting for your numbered choice. Reply with only the number (or "0" for batch). Do not proceed until you select an option.
|
||||
If `patch` findings exist (including any resolved from step 4), ask the user:
|
||||
|
||||
### 5. Handle `patch` findings
|
||||
|
||||
If `patch` findings exist (including any resolved from step 4), HALT. Ask the user:
|
||||
|
||||
If `{spec_file}` is set, present all three options (if >3 `patch` findings exist, also show option 0):
|
||||
|
||||
> **How would you like to handle the <Z> `patch` findings?**
|
||||
> 0. **Batch-apply all** — automatically fix every non-controversial patch (recommended when there are many)
|
||||
> **How would you like to handle the {Z} patch findings?**
|
||||
> 1. **Fix them automatically** — I will apply fixes now
|
||||
> 2. **Leave as action items** — they are already in the story file
|
||||
> 3. **Walk through each** — let me show details before deciding
|
||||
|
||||
If `{spec_file}` is **not** set, present only options 1 and 3 (omit option 2 — findings were not written to a file). If >3 `patch` findings exist, also show option 0:
|
||||
- **Option 1**: Apply each fix. After all patches are applied, present a summary of changes made and check off the items in the story file.
|
||||
- **Option 2**: Done — findings are already written to the story.
|
||||
- **Option 3**: Present each finding with full detail, diff context, and suggested fix. After walkthrough, re-offer options 1 and 2.
|
||||
|
||||
> **How would you like to handle the <Z> `patch` findings?**
|
||||
> 0. **Batch-apply all** — automatically fix every non-controversial patch (recommended when there are many)
|
||||
> 1. **Fix them automatically** — I will apply fixes now
|
||||
> 2. **Walk through each** — let me show details before deciding
|
||||
|
||||
**HALT** — I am waiting for your numbered choice. Reply with only the number (or "0" for batch). Do not proceed until you select an option.
|
||||
|
||||
- **Option 0** (only when >3 findings): Apply all non-controversial patches without per-finding confirmation. Skip any finding that requires judgment. Present a summary of changes made and any skipped findings.
|
||||
- **Option 1**: Apply each fix. After all patches are applied, present a summary of changes made. If `{spec_file}` is set, check off the items in the story file.
|
||||
- **Option 2** (only when `{spec_file}` is set): Done — findings are already written to the story.
|
||||
- **Walk through each**: Present each finding with full detail, diff context, and suggested fix. After walkthrough, re-offer the applicable options above.
|
||||
|
||||
**HALT** — I am waiting for your numbered choice. Reply with only the number (or "0" for batch). Do not proceed until you select an option.
|
||||
|
||||
**✅ Code review actions complete**
|
||||
|
||||
- Decision-needed resolved: <D>
|
||||
- Patches handled: <P>
|
||||
- Deferred: <W>
|
||||
- Dismissed: <R>
|
||||
Workflow complete.
|
||||
|
|
|
|||
|
|
@ -1635,15 +1635,6 @@ async function runTests() {
|
|||
);
|
||||
await fs.writeFile(path.join(taskSkillDir29, 'workflow.md'), '# Task Skill\n\nSkill in tasks\n');
|
||||
|
||||
// --- Native agent entrypoint inside agents/: core/agents/bmad-tea/ ---
|
||||
const nativeAgentDir29 = path.join(tempFixture29, 'core', 'agents', 'bmad-tea');
|
||||
await fs.ensureDir(nativeAgentDir29);
|
||||
await fs.writeFile(path.join(nativeAgentDir29, 'bmad-skill-manifest.yaml'), 'type: agent\ncanonicalId: bmad-tea\n');
|
||||
await fs.writeFile(
|
||||
path.join(nativeAgentDir29, 'SKILL.md'),
|
||||
'---\nname: bmad-tea\ndescription: Native agent entrypoint\n---\n\nPresent a capability menu.\n',
|
||||
);
|
||||
|
||||
// Minimal agent so core module is detected
|
||||
await fs.ensureDir(path.join(tempFixture29, 'core', 'agents'));
|
||||
const minimalAgent29 = '<agent name="Test" title="T"><persona>p</persona></agent>';
|
||||
|
|
@ -1673,17 +1664,6 @@ async function runTests() {
|
|||
const inTasks29 = generator29.tasks.find((t) => t.name === 'task-skill');
|
||||
assert(inTasks29 === undefined, 'Skill in tasks/ dir does NOT appear in tasks[]');
|
||||
|
||||
// Native agent entrypoint should be installed as a verbatim skill and also
|
||||
// remain visible to the agent manifest pipeline.
|
||||
const nativeAgentEntry29 = generator29.skills.find((s) => s.canonicalId === 'bmad-tea');
|
||||
assert(nativeAgentEntry29 !== undefined, 'Native type:agent SKILL.md dir appears in skills[]');
|
||||
assert(
|
||||
nativeAgentEntry29 && nativeAgentEntry29.path.includes('agents/bmad-tea/SKILL.md'),
|
||||
'Native type:agent SKILL.md path points to the agent directory entrypoint',
|
||||
);
|
||||
const nativeAgentManifest29 = generator29.agents.find((a) => a.name === 'bmad-tea');
|
||||
assert(nativeAgentManifest29 !== undefined, 'Native type:agent SKILL.md dir appears in agents[] for agent metadata');
|
||||
|
||||
// Regular workflow should be in workflows, NOT in skills
|
||||
const regularWf29 = generator29.workflows.find((w) => w.name === 'Regular Workflow');
|
||||
assert(regularWf29 !== undefined, 'Regular type:workflow appears in workflows[]');
|
||||
|
|
@ -1709,37 +1689,6 @@ async function runTests() {
|
|||
|
||||
const scannedModules29 = await generator29.scanInstalledModules(tempFixture29);
|
||||
assert(scannedModules29.includes('skill-only-mod'), 'scanInstalledModules recognizes skill-only module');
|
||||
|
||||
// Test scanInstalledModules recognizes native-agent-only modules too
|
||||
const agentOnlyModDir29 = path.join(tempFixture29, 'agent-only-mod');
|
||||
await fs.ensureDir(path.join(agentOnlyModDir29, 'deep', 'nested', 'bmad-tea'));
|
||||
await fs.writeFile(path.join(agentOnlyModDir29, 'deep', 'nested', 'bmad-tea', 'bmad-skill-manifest.yaml'), 'type: agent\n');
|
||||
await fs.writeFile(
|
||||
path.join(agentOnlyModDir29, 'deep', 'nested', 'bmad-tea', 'SKILL.md'),
|
||||
'---\nname: bmad-tea\ndescription: desc\n---\n\nAgent menu.\n',
|
||||
);
|
||||
|
||||
const rescannedModules29 = await generator29.scanInstalledModules(tempFixture29);
|
||||
assert(rescannedModules29.includes('agent-only-mod'), 'scanInstalledModules recognizes native-agent-only module');
|
||||
|
||||
// Test scanInstalledModules recognizes multi-entry manifests keyed under SKILL.md
|
||||
const multiEntryModDir29 = path.join(tempFixture29, 'multi-entry-mod');
|
||||
await fs.ensureDir(path.join(multiEntryModDir29, 'deep', 'nested', 'bmad-tea'));
|
||||
await fs.writeFile(
|
||||
path.join(multiEntryModDir29, 'deep', 'nested', 'bmad-tea', 'bmad-skill-manifest.yaml'),
|
||||
'SKILL.md:\n type: agent\n canonicalId: bmad-tea\n',
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(multiEntryModDir29, 'deep', 'nested', 'bmad-tea', 'SKILL.md'),
|
||||
'---\nname: bmad-tea\ndescription: desc\n---\n\nAgent menu.\n',
|
||||
);
|
||||
|
||||
const rescannedModules29b = await generator29.scanInstalledModules(tempFixture29);
|
||||
assert(rescannedModules29b.includes('multi-entry-mod'), 'scanInstalledModules recognizes multi-entry native-agent module');
|
||||
|
||||
// skill-manifest.csv should include the native agent entrypoint
|
||||
const skillManifestCsv29 = await fs.readFile(path.join(tempFixture29, '_config', 'skill-manifest.csv'), 'utf8');
|
||||
assert(skillManifestCsv29.includes('bmad-tea'), 'skill-manifest.csv includes native type:agent SKILL.md entrypoint');
|
||||
} catch (error) {
|
||||
assert(false, 'Unified skill scanner test succeeds', error.message);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -50,29 +50,6 @@ class ManifestGenerator {
|
|||
return getInstallToBmadShared(manifest, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Native SKILL.md entrypoints can be packaged as either skills or agents.
|
||||
* Both need verbatim installation for skill-format IDEs.
|
||||
* @param {string|null} artifactType - Manifest type resolved for SKILL.md
|
||||
* @returns {boolean} True when the directory should be installed verbatim
|
||||
*/
|
||||
isNativeSkillDirType(artifactType) {
|
||||
return artifactType === 'skill' || artifactType === 'agent';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a loaded bmad-skill-manifest.yaml declares a native
|
||||
* SKILL.md entrypoint, either as a single-entry manifest or a multi-entry map.
|
||||
* @param {Object|null} manifest - Loaded manifest
|
||||
* @returns {boolean} True when the manifest contains a native skill/agent entrypoint
|
||||
*/
|
||||
hasNativeSkillManifest(manifest) {
|
||||
if (!manifest) return false;
|
||||
if (manifest.__single) return this.isNativeSkillDirType(manifest.__single.type);
|
||||
|
||||
return Object.values(manifest).some((entry) => this.isNativeSkillDirType(entry?.type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean text for CSV output by normalizing whitespace.
|
||||
* Note: Quote escaping is handled by escapeCsv() at write time.
|
||||
|
|
@ -169,10 +146,9 @@ class ManifestGenerator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Recursively walk a module directory tree, collecting native SKILL.md entrypoints.
|
||||
* A native entrypoint directory is one that contains both a
|
||||
* bmad-skill-manifest.yaml with type: skill or type: agent AND a SKILL.md file
|
||||
* with name/description frontmatter.
|
||||
* Recursively walk a module directory tree, collecting skill directories.
|
||||
* A skill directory is one that contains both a bmad-skill-manifest.yaml with
|
||||
* type: skill AND a SKILL.md file with name/description frontmatter.
|
||||
* Populates this.skills[] and this.skillClaimedDirs (Set of absolute paths).
|
||||
*/
|
||||
async collectSkills() {
|
||||
|
|
@ -196,11 +172,11 @@ class ManifestGenerator {
|
|||
// Check this directory for skill manifest
|
||||
const manifest = await this.loadSkillManifest(dir);
|
||||
|
||||
// Determine if this directory is a native SKILL.md entrypoint
|
||||
// Determine if this directory is a skill (type: skill in manifest)
|
||||
const skillFile = 'SKILL.md';
|
||||
const artifactType = this.getArtifactType(manifest, skillFile);
|
||||
|
||||
if (this.isNativeSkillDirType(artifactType)) {
|
||||
if (artifactType === 'skill' || artifactType === 'agent') {
|
||||
const skillMdPath = path.join(dir, 'SKILL.md');
|
||||
const dirName = path.basename(dir);
|
||||
|
||||
|
|
@ -214,12 +190,11 @@ class ManifestGenerator {
|
|||
? `${this.bmadFolderName}/${moduleName}/${relativePath}/${skillFile}`
|
||||
: `${this.bmadFolderName}/${moduleName}/${skillFile}`;
|
||||
|
||||
// Native SKILL.md entrypoints derive canonicalId from directory name.
|
||||
// Agent entrypoints may keep canonicalId metadata for compatibility, so
|
||||
// only warn for non-agent SKILL.md directories.
|
||||
// Skills derive canonicalId from directory name — never from manifest
|
||||
// (agent-type skills legitimately use canonicalId for agent-manifest mapping, so skip warning)
|
||||
if (manifest && manifest.__single && manifest.__single.canonicalId && artifactType !== 'agent') {
|
||||
console.warn(
|
||||
`Warning: Native entrypoint manifest at ${dir}/bmad-skill-manifest.yaml contains canonicalId — this field is ignored for SKILL.md directories (directory name is the canonical ID)`,
|
||||
`Warning: Skill manifest at ${dir}/bmad-skill-manifest.yaml contains canonicalId — this field is ignored for skills (directory name is the canonical ID)`,
|
||||
);
|
||||
}
|
||||
const canonicalId = dirName;
|
||||
|
|
@ -249,21 +224,21 @@ class ManifestGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
// Warn if manifest says this is a native entrypoint but the directory was not claimed
|
||||
// Warn if manifest says type:skill but directory was not claimed
|
||||
if (manifest && !this.skillClaimedDirs.has(dir)) {
|
||||
let hasNativeSkillType = false;
|
||||
let hasSkillType = false;
|
||||
if (manifest.__single) {
|
||||
hasNativeSkillType = this.isNativeSkillDirType(manifest.__single.type);
|
||||
hasSkillType = manifest.__single.type === 'skill' || manifest.__single.type === 'agent';
|
||||
} else {
|
||||
for (const key of Object.keys(manifest)) {
|
||||
if (this.isNativeSkillDirType(manifest[key]?.type)) {
|
||||
hasNativeSkillType = true;
|
||||
if (manifest[key]?.type === 'skill' || manifest[key]?.type === 'agent') {
|
||||
hasSkillType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasNativeSkillType && debug) {
|
||||
console.log(`[DEBUG] collectSkills: dir has native SKILL.md manifest but failed validation: ${dir}`);
|
||||
if (hasSkillType && debug) {
|
||||
console.log(`[DEBUG] collectSkills: dir has type:skill manifest but failed validation: ${dir}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1384,8 +1359,7 @@ class ManifestGenerator {
|
|||
const hasTasks = await fs.pathExists(path.join(modulePath, 'tasks'));
|
||||
const hasTools = await fs.pathExists(path.join(modulePath, 'tools'));
|
||||
|
||||
// Check for native-entrypoint-only modules: recursive scan for
|
||||
// bmad-skill-manifest.yaml with type: skill or type: agent
|
||||
// Check for skill-only modules: recursive scan for bmad-skill-manifest.yaml with type: skill
|
||||
let hasSkills = false;
|
||||
if (!hasAgents && !hasWorkflows && !hasTasks && !hasTools) {
|
||||
hasSkills = await this._hasSkillManifestRecursive(modulePath);
|
||||
|
|
@ -1404,8 +1378,7 @@ class ManifestGenerator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Recursively check if a directory tree contains a bmad-skill-manifest.yaml that
|
||||
* declares a native SKILL.md entrypoint (type: skill or type: agent).
|
||||
* Recursively check if a directory tree contains a bmad-skill-manifest.yaml with type: skill.
|
||||
* Skips directories starting with . or _.
|
||||
* @param {string} dir - Directory to search
|
||||
* @returns {boolean} True if a skill manifest is found
|
||||
|
|
@ -1420,7 +1393,10 @@ class ManifestGenerator {
|
|||
|
||||
// Check for manifest in this directory
|
||||
const manifest = await this.loadSkillManifest(dir);
|
||||
if (this.hasNativeSkillManifest(manifest)) return true;
|
||||
if (manifest) {
|
||||
const type = this.getArtifactType(manifest, 'workflow.md');
|
||||
if (type === 'skill') return true;
|
||||
}
|
||||
|
||||
// Recurse into subdirectories
|
||||
for (const entry of entries) {
|
||||
|
|
|
|||
|
|
@ -630,7 +630,7 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|||
}
|
||||
|
||||
/**
|
||||
* Install verbatim native SKILL.md directories from skill-manifest.csv.
|
||||
* Install verbatim skill directories (type: skill entries from skill-manifest.csv).
|
||||
* Copies the entire source directory as-is into the IDE skill directory.
|
||||
* The source SKILL.md is used directly — no frontmatter transformation or file generation.
|
||||
* @param {string} projectDir - Project directory
|
||||
|
|
|
|||
Loading…
Reference in New Issue