fix: reconcile story and sprint status on closeout

This commit is contained in:
Dicky Moore 2026-04-27 17:20:17 +01:00
parent 6ff74ba662
commit 999fbea7a3
4 changed files with 89 additions and 4 deletions

View File

@ -44,7 +44,7 @@
"test": "npm run test:refs && npm run test:install && npm run test:channels && npm run lint && npm run lint:md && npm run format:check", "test": "npm run test:refs && npm run test:install && npm run test:channels && npm run lint && npm run lint:md && npm run format:check",
"test:channels": "node test/test-installer-channels.js", "test:channels": "node test/test-installer-channels.js",
"test:install": "node test/test-installation-components.js", "test:install": "node test/test-installation-components.js",
"test:refs": "node test/test-file-refs-csv.js", "test:refs": "node test/test-file-refs-csv.js && node test/test-closeout-reconciliation.js",
"validate:refs": "node tools/validate-file-refs.js --strict", "validate:refs": "node tools/validate-file-refs.js --strict",
"validate:skills": "node tools/validate-skills.js --strict" "validate:skills": "node tools/validate-skills.js --strict"
}, },

View File

@ -104,6 +104,14 @@ If `{sprint_status}` file exists:
If `{sprint_status}` file does not exist, note that story status was updated in the story file only. If `{sprint_status}` file does not exist, note that story status was updated in the story file only.
#### Final reconciliation
Re-open the story file after saving and verify the top-level `Status:` field equals `{new_status}`.
If `{sprint_status}` exists and `{story_key}` was found, re-open `{sprint_status}` after saving and verify `development_status[{story_key}]` also equals `{new_status}`.
If either artifact does not match, HALT with a closeout reconciliation failure instead of reporting completion.
#### Completion summary #### Completion summary
> **Review Complete!** > **Review Complete!**
@ -113,12 +121,14 @@ If `{sprint_status}` file does not exist, note that story status was updated in
> **Action Items Created:** <action_count> > **Action Items Created:** <action_count>
> **Deferred:** <W> > **Deferred:** <W>
> **Dismissed:** <R> > **Dismissed:** <R>
> **Reconciled:** story markdown and sprint tracker agree on `{new_status}`
### 7. Next steps ### 7. Next steps
Present the user with follow-up options: Present the user with follow-up options:
> **What would you like to do next?** > **What would you like to do next?**
>
> 1. **Start the next story** — run `dev-story` to pick up the next `ready-for-dev` story > 1. **Start the next story** — run `dev-story` to pick up the next `ready-for-dev` story
> 2. **Re-run code review** — address findings and review again > 2. **Re-run code review** — address findings and review again
> 3. **Done** — end the workflow > 3. **Done** — end the workflow

View File

@ -64,9 +64,9 @@ Activation is complete. Begin the workflow below.
## Input Files ## Input Files
| Input | Path | Load Strategy | | Input | Path | Load Strategy |
|-------|------|---------------| | ------------- | ---------------------- | ------------- |
| Sprint status | `{sprint_status_file}` | FULL_LOAD | | Sprint status | `{sprint_status_file}` | FULL_LOAD |
## Execution ## Execution
@ -148,6 +148,7 @@ Enter corrections (e.g., "1=in-progress, 2=backlog") or "skip" to continue witho
- IF any story has status "review": suggest `/bmad:bmm:workflows:code-review` - IF any story has status "review": suggest `/bmad:bmm:workflows:code-review`
- IF any story has status "in-progress" AND no stories have status "ready-for-dev": recommend staying focused on active story - IF any story has status "in-progress" AND no stories have status "ready-for-dev": recommend staying focused on active story
- IF any story markdown file top-level `Status:` does not match its `development_status` entry: warn "story/tracker status drift detected"
- IF all epics have status "backlog" AND no stories have status "ready-for-dev": prompt `/bmad:bmm:workflows:create-story` - IF all epics have status "backlog" AND no stories have status "ready-for-dev": prompt `/bmad:bmm:workflows:create-story`
- IF `last_updated` timestamp is more than 7 days old (or `last_updated` is missing, fall back to `generated`): warn "sprint-status.yaml may be stale" - IF `last_updated` timestamp is more than 7 days old (or `last_updated` is missing, fall back to `generated`): warn "sprint-status.yaml may be stale"
- IF any story key doesn't match an epic pattern (e.g., story "5-1-..." but no "epic-5"): warn "orphaned story detected" - IF any story key doesn't match an epic pattern (e.g., story "5-1-..." but no "epic-5"): warn "orphaned story detected"
@ -289,6 +290,14 @@ If the command targets a story, set `story_key={{next_story_id}}` when prompted.
<action>Return</action> <action>Return</action>
</check> </check>
<action>For each story entry in development_status, use `story_location` to open the matching story markdown file and compare its top-level `Status:` value with the tracker status when the tracker status is `review` or `done`</action>
<check if="any story markdown status does not match its tracker status for review/done stories">
<template-output>is_valid = false</template-output>
<template-output>error = "Story/tracker status drift detected: {{drift_entries}}"</template-output>
<template-output>suggestion = "Reconcile story markdown Status fields with sprint-status.yaml before closeout"</template-output>
<action>Return</action>
</check>
<template-output>is_valid = true</template-output> <template-output>is_valid = true</template-output>
<template-output>message = "sprint-status.yaml valid: metadata complete, all statuses recognized"</template-output> <template-output>message = "sprint-status.yaml valid: metadata complete, all statuses recognized"</template-output>
<action>Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action> <action>Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>

View File

@ -0,0 +1,66 @@
/**
* Closeout reconciliation workflow tests
*
* Ensures the implementation workflows explicitly enforce reconciliation
* between story markdown Status fields and sprint-status.yaml before closeout.
*
* Usage: node test/test-closeout-reconciliation.js
*/
const fs = require('node:fs');
const colors = {
reset: '\u001B[0m',
green: '\u001B[32m',
red: '\u001B[31m',
cyan: '\u001B[36m',
dim: '\u001B[2m',
};
let passed = 0;
let failed = 0;
function assert(condition, testName, errorMessage = '') {
if (condition) {
console.log(`${colors.green}${colors.reset} ${testName}`);
passed++;
} else {
console.log(`${colors.red}${colors.reset} ${testName}`);
if (errorMessage) {
console.log(` ${colors.dim}${errorMessage}${colors.reset}`);
}
failed++;
}
}
const codeReview = fs.readFileSync('src/bmm-skills/4-implementation/bmad-code-review/steps/step-04-present.md', 'utf8');
const sprintStatus = fs.readFileSync('src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md', 'utf8');
console.log(`\n${colors.cyan}Closeout Reconciliation Tests${colors.reset}\n`);
assert(
codeReview.includes('Re-open the story file after saving and verify the top-level `Status:` field equals `{new_status}`.'),
'code-review reopens the story file to verify final status reconciliation',
);
assert(
codeReview.includes('If either artifact does not match, HALT with a closeout reconciliation failure instead of reporting completion.'),
'code-review halts when closeout reconciliation fails',
);
assert(codeReview.includes('development_status[{story_key}]'), 'code-review verifies the sprint tracker entry during reconciliation');
assert(
codeReview.includes('story markdown and sprint tracker agree on `{new_status}`'),
'code-review reports successful reconciliation in the completion summary',
);
assert(sprintStatus.includes('story/tracker status drift detected'), 'sprint-status warns about story/tracker drift');
assert(
sprintStatus.includes('use `story_location` to open the matching story markdown file'),
'sprint-status validate mode uses story_location when checking story files against tracker state',
);
console.log(`\n${passed} passed, ${failed} failed\n`);
process.exit(failed > 0 ? 1 : 0);