fix: honor workstream in verify-work init (#3386)
* fix: honor workstream in verify-work init * fix: define verify-work phase arg
This commit is contained in:
6
.changeset/fix-3381-init-verify-work-ws.md
Normal file
6
.changeset/fix-3381-init-verify-work-ws.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3386
|
||||
---
|
||||
|
||||
**`/gsd-verify-work --ws <name>` now resolves workstream phases through the SDK** — `init.verify-work`, MVP-mode lookup, and phase-goal lookup all receive the selected workstream instead of falling back to root `.planning/`.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: gsd:verify-work
|
||||
description: Validate built features through conversational UAT
|
||||
argument-hint: "[phase number, e.g., '4']"
|
||||
argument-hint: "[phase number, e.g., '4'] [--ws <name>]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Bash
|
||||
|
||||
@@ -30,7 +30,11 @@ No Pass/Fail buttons. No severity questions. Just: "Here's what should happen. D
|
||||
If $ARGUMENTS contains a phase number, load context:
|
||||
|
||||
```bash
|
||||
INIT=$(gsd-sdk query init.verify-work "${PHASE_ARG}")
|
||||
GSD_WS=""
|
||||
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[^[:space:]]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[^[:space:]]+')
|
||||
PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[^[:space:]]+//g' | xargs)
|
||||
|
||||
INIT=$(gsd-sdk query init.verify-work "${PHASE_ARG}" ${GSD_WS})
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_PLANNER=$(gsd-sdk query agent-skills gsd-planner)
|
||||
AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-plan-checker)
|
||||
@@ -42,7 +46,7 @@ Parse JSON for: `planner_model`, `checker_model`, `commit_docs`, `phase_found`,
|
||||
# MVP mode detection via the centralized phase.mvp-mode resolver.
|
||||
# verify-work has no --mvp CLI flag (mode is inherited from the planned phase),
|
||||
# so we omit --cli-flag — the verb falls through roadmap → config → false.
|
||||
MVP_MODE=$(gsd-sdk query phase.mvp-mode "${phase_number}" --pick active)
|
||||
MVP_MODE=$(gsd-sdk query phase.mvp-mode "${phase_number}" ${GSD_WS} --pick active)
|
||||
```
|
||||
</step>
|
||||
|
||||
@@ -154,7 +158,7 @@ When `MVP_MODE=false` (mode is null, absent, or the phase has no `**Mode:**` lin
|
||||
**User-story format guard.** When `MVP_MODE=true`, also verify the phase's goal is in User Story format via the centralized validator:
|
||||
|
||||
```bash
|
||||
PHASE_GOAL=$(gsd-sdk query roadmap.get-phase "${phase_number}" --pick goal)
|
||||
PHASE_GOAL=$(gsd-sdk query roadmap.get-phase "${phase_number}" ${GSD_WS} --pick goal)
|
||||
USER_STORY_VALID=$(gsd-sdk query user-story.validate --story "$PHASE_GOAL" --pick valid)
|
||||
if [ "$USER_STORY_VALID" != "true" ]; then
|
||||
echo "Phase ${phase_number} has '**Mode:** mvp' in ROADMAP.md but the **Goal:** is not in user-story format."
|
||||
|
||||
@@ -448,6 +448,30 @@ describe('initVerifyWork', () => {
|
||||
expect(data.project_root).toBe(tmpDir);
|
||||
});
|
||||
|
||||
it('resolves workstream-scoped phases when workstream is provided', async () => {
|
||||
const wsDir = join(tmpDir, '.planning', 'workstreams', 'delivery');
|
||||
await mkdir(join(wsDir, 'phases', '32-shipment-creation-tracking-numbers-print-forms'), { recursive: true });
|
||||
await writeFile(join(wsDir, 'ROADMAP.md'), [
|
||||
'# Roadmap',
|
||||
'',
|
||||
'## v1.0: Delivery',
|
||||
'',
|
||||
'### Phase 32: Shipment Creation Tracking Numbers Print Forms',
|
||||
'',
|
||||
'**Goal:** Ship orders.',
|
||||
'',
|
||||
].join('\n'));
|
||||
|
||||
const result = await initVerifyWork(['32'], tmpDir, 'delivery');
|
||||
const data = result.data as Record<string, unknown>;
|
||||
|
||||
expect(data.phase_found).toBe(true);
|
||||
expect(data.phase_number).toBe('32');
|
||||
expect(data.phase_dir).toBe(
|
||||
'.planning/workstreams/delivery/phases/32-shipment-creation-tracking-numbers-print-forms',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns error when phase arg missing', async () => {
|
||||
const result = await initVerifyWork([], tmpDir);
|
||||
const data = result.data as Record<string, unknown>;
|
||||
|
||||
@@ -184,14 +184,15 @@ async function getPhaseInfoWithFallback(
|
||||
async function getPhaseInfoForVerifyWork(
|
||||
phase: string,
|
||||
projectDir: string,
|
||||
workstream?: string,
|
||||
): Promise<{ phaseInfo: Record<string, unknown> | null }> {
|
||||
const phaseResult = await findPhase([phase], projectDir);
|
||||
const phaseResult = await findPhase([phase], projectDir, workstream);
|
||||
let phaseInfo = phaseResult.data as Record<string, unknown> | null;
|
||||
if (phaseInfo && phaseInfo.found === false) {
|
||||
phaseInfo = null;
|
||||
}
|
||||
|
||||
const roadmapResult = await roadmapGetPhase([phase], projectDir);
|
||||
const roadmapResult = await roadmapGetPhase([phase], projectDir, workstream);
|
||||
const roadmapPhase = roadmapResult.data as Record<string, unknown> | null;
|
||||
|
||||
if (phaseInfo?.archived && roadmapPhase?.found) {
|
||||
@@ -205,9 +206,7 @@ async function getPhaseInfoForVerifyWork(
|
||||
directory: null,
|
||||
phase_number: roadmapPhase.phase_number,
|
||||
phase_name: phaseName,
|
||||
phase_slug: phaseName
|
||||
? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
|
||||
: null,
|
||||
phase_slug: phaseName ? generateSlugInternal(phaseName) : null,
|
||||
plans: [],
|
||||
summaries: [],
|
||||
incomplete_plans: [],
|
||||
@@ -623,14 +622,14 @@ export const initResume: QueryHandler = async (_args, projectDir) => {
|
||||
* Init handler for verify-work workflow.
|
||||
* Port of cmdInitVerifyWork from init.cjs lines 538-586.
|
||||
*/
|
||||
export const initVerifyWork: QueryHandler = async (args, projectDir) => {
|
||||
export const initVerifyWork: QueryHandler = async (args, projectDir, workstream) => {
|
||||
const phase = args[0];
|
||||
if (!phase) {
|
||||
return { data: { error: 'phase required for init verify-work' } };
|
||||
}
|
||||
|
||||
const config = await loadConfig(projectDir);
|
||||
const { phaseInfo } = await getPhaseInfoForVerifyWork(phase, projectDir);
|
||||
const config = await loadConfig(projectDir, workstream);
|
||||
const { phaseInfo } = await getPhaseInfoForVerifyWork(phase, projectDir, workstream);
|
||||
|
||||
const configExists = existsSync(join(projectDir, '.planning', 'config.json'));
|
||||
const [plannerModel, checkerModel] = configExists
|
||||
|
||||
49
tests/bug-3381-verify-work-workstream.test.cjs
Normal file
49
tests/bug-3381-verify-work-workstream.test.cjs
Normal file
@@ -0,0 +1,49 @@
|
||||
// allow-test-rule: source-text-is-the-product — verify-work.md is a runtime workflow contract.
|
||||
|
||||
'use strict';
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
describe('bug #3381: verify-work forwards workstream context', () => {
|
||||
test('workflow forwards ${GSD_WS} to workstream-sensitive SDK queries', () => {
|
||||
const workflow = fs.readFileSync(
|
||||
path.join(__dirname, '..', 'get-shit-done', 'workflows', 'verify-work.md'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
assert.match(workflow, /GSD_WS=""/, 'verify-work must initialize GSD_WS');
|
||||
assert.match(
|
||||
workflow,
|
||||
/grep -qE -- '--ws\[\[:space:\]\]\+\[\^\[:space:\]\]\+'/,
|
||||
'verify-work must detect --ws in $ARGUMENTS',
|
||||
);
|
||||
assert.match(
|
||||
workflow,
|
||||
/grep -oE -- '--ws\[\[:space:\]\]\+\[\^\[:space:\]\]\+'/,
|
||||
'verify-work must extract the --ws flag pair from $ARGUMENTS',
|
||||
);
|
||||
assert.match(
|
||||
workflow,
|
||||
/PHASE_ARG=\$\(echo "\$ARGUMENTS" \| sed -E 's\/--ws\[\[:space:\]\]\+\[\^\[:space:\]\]\+\/\/g' \| xargs\)/,
|
||||
'verify-work must derive PHASE_ARG after removing --ws',
|
||||
);
|
||||
assert.match(
|
||||
workflow,
|
||||
/gsd-sdk query init\.verify-work "\$\{PHASE_ARG\}" \$\{GSD_WS\}/,
|
||||
'init.verify-work must receive GSD_WS so phase_dir resolves in workstreams',
|
||||
);
|
||||
assert.match(
|
||||
workflow,
|
||||
/gsd-sdk query phase\.mvp-mode "\$\{phase_number\}" \$\{GSD_WS\} --pick active/,
|
||||
'phase.mvp-mode must receive GSD_WS so roadmap mode is workstream-scoped',
|
||||
);
|
||||
assert.match(
|
||||
workflow,
|
||||
/gsd-sdk query roadmap\.get-phase "\$\{phase_number\}" \$\{GSD_WS\} --pick goal/,
|
||||
'roadmap.get-phase must receive GSD_WS so goals are workstream-scoped',
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user