* feat(sdk): add queued_phases to init.manager (closes #2497) Surfaces the milestone immediately AFTER the active one so the /gsd-manager dashboard can preview upcoming phases without mixing them into the active phases grid. Changes: - roadmap.ts: exports two new helpers - extractPhasesFromSection(section): parses phase number / name / goal / depends_on using the same pattern initManager uses for the active milestone, so queued phases have identical shape. - extractNextMilestoneSection(content, projectDir): resolves the current milestone via the STATE-first path (matching upstream PR #2508) then scans for the next ## milestone heading. Shipped milestones are stripped first so they can't shadow the real next. Returns null when the active milestone is the last one. - init-complex.ts: initManager now exposes - queued_phases: Array<{ number, name, display_name, goal, depends_on, dep_phases, deps_display }> - queued_milestone_version: string | null - queued_milestone_name: string | null Existing phases array is unchanged — callers that only care about the active milestone see no behavior difference. Scope note: PR #2508 (merged upstream 2026-04-21) superseded the #2495 + #2496 portions of this branch's original submission. This commit is the rebased remainder contributing only #2497 on top of upstream's new helpers. Test coverage (7 new tests, all passing): - roadmap.test.ts: +5 tests - extractPhasesFromSection parses multiple phases with goal + deps - extractPhasesFromSection returns [] when no phase headings - extractNextMilestoneSection returns the milestone after the STATE-resolved active one - extractNextMilestoneSection returns null when active is last - extractNextMilestoneSection returns null when no version found - init-complex.test.ts: +4 tests under `queued_phases (#2497)` - surfaces next milestone with version + name metadata - queued entries carry name / deps_display / display_name - queued phases are NOT mixed into active phases list - returns [] + nulls when active is the last milestone All 51 tests in roadmap.test.ts + init-complex.test.ts pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(workflows): render queued_phases section in /gsd-manager dashboard Surfaces the new `queued_phases` / `queued_milestone_version` / `queued_milestone_name` fields from init.manager (SDK #2497) in a compact preview section directly below the main active-milestone table. Changes to workflows/manager.md: - Initialize step: parse the optional trio (queued_milestone_version, queued_milestone_name, queued_phases) alongside the existing init.manager fields. Treat missing as empty for backward compatibility with older SDK versions. - Dashboard step: new "Queued section (next milestone preview)" rendered between the main active-milestone grid and the Recommendations section. Renders only when queued_phases is non-empty; skipped entirely when absent or empty (e.g. active milestone is the last one). - Queued rows render without D/P/E columns since the phases haven't been discussed yet — just number, display_name, deps_display, and a fixed "· Queued" status. - Success criterion added: queued section renders when non-empty and is skipped when absent. Queued phases are deliberately NOT eligible for the Continue action menu; they live in a future milestone. The preview exists for situational awareness only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,12 @@ import { homedir } from 'node:os';
|
||||
import { loadConfig } from '../config.js';
|
||||
import { resolveModel } from './config-query.js';
|
||||
import { planningPaths, normalizePhaseName, phaseTokenMatches, toPosixPath } from './helpers.js';
|
||||
import { getMilestoneInfo, extractCurrentMilestone } from './roadmap.js';
|
||||
import {
|
||||
getMilestoneInfo,
|
||||
extractCurrentMilestone,
|
||||
extractNextMilestoneSection,
|
||||
extractPhasesFromSection,
|
||||
} from './roadmap.js';
|
||||
import { withProjectRoot } from './init.js';
|
||||
import type { QueryHandler } from './utils.js';
|
||||
|
||||
@@ -543,6 +548,40 @@ export const initManager: QueryHandler = async (_args, projectDir, _workstream)
|
||||
|
||||
const completedCount = phases.filter(p => p.disk_status === 'complete').length;
|
||||
|
||||
// ── Next-milestone surface (issue #2497) ───────────────────────────────
|
||||
// Populate queued_phases + metadata with the milestone immediately after
|
||||
// the active one, so the /gsd-manager dashboard can preview what's coming
|
||||
// next without mixing it into the active phases grid. Empty/null when the
|
||||
// active milestone is the last one in ROADMAP.
|
||||
let queuedPhases: Record<string, unknown>[] = [];
|
||||
let queuedMilestoneVersion: string | null = null;
|
||||
let queuedMilestoneName: string | null = null;
|
||||
try {
|
||||
const next = await extractNextMilestoneSection(rawContent, projectDir);
|
||||
if (next) {
|
||||
queuedMilestoneVersion = next.version;
|
||||
queuedMilestoneName = next.name;
|
||||
queuedPhases = extractPhasesFromSection(next.section).map(p => {
|
||||
const MAX_NAME_WIDTH = 20;
|
||||
const display_name = p.name.length > MAX_NAME_WIDTH
|
||||
? p.name.slice(0, MAX_NAME_WIDTH - 1) + '…'
|
||||
: p.name;
|
||||
const depNums = p.depends_on && !/^none$/i.test(p.depends_on.trim())
|
||||
? (p.depends_on.match(/\d+(?:\.\d+)*/g) || [])
|
||||
: [];
|
||||
return {
|
||||
number: p.number,
|
||||
name: p.name,
|
||||
display_name,
|
||||
goal: p.goal,
|
||||
depends_on: p.depends_on,
|
||||
dep_phases: depNums,
|
||||
deps_display: depNums.length > 0 ? depNums.join(',') : '—',
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch { /* queued_phases is a non-critical enhancement */ }
|
||||
|
||||
// Read manager flags from config
|
||||
const managerConfig = (config as Record<string, unknown>).manager as Record<string, Record<string, string>> | undefined;
|
||||
const sanitizeFlags = (raw: unknown): string => {
|
||||
@@ -568,6 +607,9 @@ export const initManager: QueryHandler = async (_args, projectDir, _workstream)
|
||||
recommended_actions: recommendedActions,
|
||||
waiting_signal: waitingSignal,
|
||||
all_complete: completedCount === phases.length && phases.length > 0,
|
||||
queued_phases: queuedPhases,
|
||||
queued_milestone_version: queuedMilestoneVersion,
|
||||
queued_milestone_name: queuedMilestoneName,
|
||||
project_exists: pathExists(projectDir, '.planning/PROJECT.md'),
|
||||
roadmap_exists: true,
|
||||
state_exists: true,
|
||||
|
||||
Reference in New Issue
Block a user