Merge pull request #3696 from Tibsfox/fix/prompt-step-ordering-and-tools
fix(gsd): prompt ordering, correct tools, crash guard, compaction fix
This commit is contained in:
commit
0339317d10
5 changed files with 97 additions and 6 deletions
|
|
@ -36,7 +36,10 @@ function installEpipeGuard(): void {
|
|||
if (handleRecoverableExtensionProcessError(err)) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
// Log unhandled errors instead of re-throwing — throwing inside an
|
||||
// uncaughtException handler is a fatal double-fault in Node.js (#3163).
|
||||
process.stderr.write(`[gsd] uncaught extension error (non-fatal): ${err.message}\n`);
|
||||
if (err.stack) process.stderr.write(`${err.stack}\n`);
|
||||
};
|
||||
process.on("uncaughtException", _gsdEpipeGuard);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,10 @@ export function registerHooks(pi: ExtensionAPI): void {
|
|||
});
|
||||
|
||||
pi.on("session_before_compact", async () => {
|
||||
if (isAutoActive() || isAutoPaused()) {
|
||||
// Only cancel compaction while auto-mode is actively running.
|
||||
// Paused auto-mode should allow compaction — the user may be doing
|
||||
// interactive work (#3165).
|
||||
if (isAutoActive()) {
|
||||
return { cancel: true };
|
||||
}
|
||||
const basePath = process.cwd();
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ Then:
|
|||
|
||||
### Verification Gate — STOP if verification failed
|
||||
|
||||
**If ANY verification failure was recorded in steps 3, 4, or 5, you MUST follow the failure path below. Do NOT proceed to step 9.**
|
||||
**If ANY verification failure was recorded in steps 3, 4, or 5, you MUST follow the failure path below. Do NOT proceed to step 10.**
|
||||
|
||||
**Failure path** (verification failed):
|
||||
- Do NOT call `gsd_complete_milestone` — the milestone must not be marked as complete.
|
||||
|
|
@ -39,7 +39,8 @@ Then:
|
|||
|
||||
**Success path** (all verifications passed — continue with steps 9–13):
|
||||
|
||||
9. **Persist completion through `gsd_complete_milestone`.** Call it with the parameters below. The tool updates the milestone status in the DB, renders `{{milestoneSummaryPath}}`, and validates all slices are complete before proceeding.
|
||||
9. For each requirement whose status changed in step 8, call `gsd_requirement_update` with the requirement ID and updated `status` and `validation` fields — the tool regenerates `.gsd/REQUIREMENTS.md` automatically. Do this BEFORE completing the milestone so requirement updates are persisted.
|
||||
10. **Persist completion through `gsd_complete_milestone`.** Call it with the parameters below. The tool updates the milestone status in the DB, renders `{{milestoneSummaryPath}}`, and validates all slices are complete before proceeding.
|
||||
|
||||
**Required parameters:**
|
||||
- `milestoneId` (string) — Milestone ID (e.g. M001)
|
||||
|
|
@ -57,7 +58,6 @@ Then:
|
|||
**Optional parameters:**
|
||||
- `followUps` (string) — Follow-up items for future milestones
|
||||
- `deviations` (string) — Deviations from the original plan
|
||||
10. For each requirement whose status changed in step 8, call `gsd_requirement_update` with the requirement ID and updated `status` and `validation` fields — the tool regenerates `.gsd/REQUIREMENTS.md` automatically.
|
||||
11. Update `.gsd/PROJECT.md`: use the `write` tool with `path: ".gsd/PROJECT.md"` and `content` containing the full updated document reflecting milestone completion and current project state. Do NOT use the `edit` tool for this — PROJECT.md is a full-document refresh.
|
||||
12. Review all slice summaries for cross-cutting lessons, patterns, or gotchas that emerged during this milestone. Append any non-obvious, reusable insights to `.gsd/KNOWLEDGE.md`.
|
||||
13. Do not commit manually — the system auto-commits your changes after this unit completes.
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ Then:
|
|||
3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first.
|
||||
4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
|
||||
5. If the slice involved runtime behavior, fill the **Operational Readiness** section (Q8) in the slice summary: health signal, failure signal, recovery procedure, and monitoring gaps. Omit entirely for simple slices with no runtime concerns.
|
||||
6. If this slice produced evidence that a requirement changed status (Active → Validated, Active → Deferred, etc.), call `gsd_save_decision` with scope="requirement", decision="{requirement-id}", choice="{new-status}", rationale="{evidence}". Do NOT write `.gsd/REQUIREMENTS.md` directly — the engine renders it from the database.
|
||||
6. If this slice produced evidence that a requirement changed status (Active → Validated, Active → Deferred, etc.), call `gsd_requirement_update` with the requirement ID, updated `status`, and `validation` evidence. Do NOT write `.gsd/REQUIREMENTS.md` directly — the engine renders it from the database.
|
||||
7. Write `{{sliceSummaryPath}}` (compress all task summaries).
|
||||
8. Write `{{sliceUatPath}}` — a concrete UAT script with real test cases derived from the slice plan and task summaries. Include preconditions, numbered steps with expected outcomes, and edge cases. This must NOT be a placeholder or generic template — tailor every test case to what this slice actually built.
|
||||
9. Review task summaries for `key_decisions`. Append any significant decisions to `.gsd/DECISIONS.md` if missing.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* Regression test for #3696 — prompt step ordering and runtime fixes
|
||||
*
|
||||
* 1. complete-milestone.md: gsd_requirement_update (step 9) before
|
||||
* gsd_complete_milestone (step 10)
|
||||
* 2. complete-slice.md: uses gsd_requirement_update
|
||||
* 3. register-extension.ts: _gsdEpipeGuard logs instead of re-throwing
|
||||
* 4. register-hooks.ts: session_before_compact only checks isAutoActive
|
||||
*/
|
||||
|
||||
import { describe, test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const completeMilestoneMd = readFileSync(
|
||||
join(__dirname, '..', 'prompts', 'complete-milestone.md'),
|
||||
'utf-8',
|
||||
);
|
||||
const completeSliceMd = readFileSync(
|
||||
join(__dirname, '..', 'prompts', 'complete-slice.md'),
|
||||
'utf-8',
|
||||
);
|
||||
const registerExtSrc = readFileSync(
|
||||
join(__dirname, '..', 'bootstrap', 'register-extension.ts'),
|
||||
'utf-8',
|
||||
);
|
||||
const registerHooksSrc = readFileSync(
|
||||
join(__dirname, '..', 'bootstrap', 'register-hooks.ts'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
describe('prompt step ordering (#3696)', () => {
|
||||
test('gsd_requirement_update step appears before gsd_complete_milestone step', () => {
|
||||
// Search for the numbered step definitions, not early "Do NOT call" warnings
|
||||
const reqUpdateMatch = completeMilestoneMd.match(/^\d+\.\s.*gsd_requirement_update/m);
|
||||
const completeMilestoneMatch = completeMilestoneMd.match(/^\d+\.\s.*gsd_complete_milestone/m);
|
||||
assert.ok(reqUpdateMatch, 'gsd_requirement_update should appear in a numbered step');
|
||||
assert.ok(completeMilestoneMatch, 'gsd_complete_milestone should appear in a numbered step');
|
||||
const reqUpdateIdx = completeMilestoneMd.indexOf(reqUpdateMatch![0]);
|
||||
const completeMilestoneIdx = completeMilestoneMd.indexOf(completeMilestoneMatch![0]);
|
||||
assert.ok(
|
||||
reqUpdateIdx < completeMilestoneIdx,
|
||||
'gsd_requirement_update step must come before gsd_complete_milestone step',
|
||||
);
|
||||
});
|
||||
|
||||
test('complete-slice.md uses gsd_requirement_update', () => {
|
||||
assert.match(completeSliceMd, /gsd_requirement_update/,
|
||||
'complete-slice.md should reference gsd_requirement_update');
|
||||
});
|
||||
});
|
||||
|
||||
describe('register-extension _gsdEpipeGuard (#3696)', () => {
|
||||
test('_gsdEpipeGuard exists and does not re-throw', () => {
|
||||
assert.match(registerExtSrc, /_gsdEpipeGuard/,
|
||||
'_gsdEpipeGuard should be defined in register-extension.ts');
|
||||
// After the fix, the handler logs instead of throwing
|
||||
assert.ok(
|
||||
!registerExtSrc.includes('throw err'),
|
||||
'_gsdEpipeGuard should NOT contain "throw err"',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('register-hooks session_before_compact (#3696)', () => {
|
||||
test('session_before_compact only checks isAutoActive', () => {
|
||||
// Extract the session_before_compact handler
|
||||
const compactIdx = registerHooksSrc.indexOf('session_before_compact');
|
||||
assert.ok(compactIdx > -1, 'session_before_compact hook should exist');
|
||||
// The first check in the handler should be isAutoActive(), not isAutoPaused()
|
||||
const afterCompact = registerHooksSrc.slice(compactIdx, compactIdx + 300);
|
||||
assert.match(afterCompact, /isAutoActive\(\)/,
|
||||
'session_before_compact should check isAutoActive()');
|
||||
// Should NOT block compaction when paused
|
||||
assert.ok(
|
||||
!afterCompact.includes('isAutoPaused()'),
|
||||
'session_before_compact should not check isAutoPaused',
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue