Skip to content

feat(gsd-extension): carry forward pending actions into replanning#3413

Open
gunish wants to merge 1 commit intogsd-build:mainfrom
gunish:feat/pending-actions-replan-chaining
Open

feat(gsd-extension): carry forward pending actions into replanning#3413
gunish wants to merge 1 commit intogsd-build:mainfrom
gunish:feat/pending-actions-replan-chaining

Conversation

@gunish
Copy link
Copy Markdown

@gunish gunish commented Apr 1, 2026

TL;DR

What: Carry forward Pending actions: from completed task summaries into task prompts and automatic slice replanning.
Why: Required unfinished work can currently be documented in prose but still fail to become continuation state, which risks silent closeout or continuation without explicit replanning.
How: Extend summary parsing, surface pending actions in carry-forward context, and route both DB-backed and filesystem-backed state derivation into replanning-slice when the latest completed task leaves required pending follow-up.

What

This PR adds a lightweight durable contract for required unfinished work in completed task summaries:

## Known Issues

Pending actions:
- action 1
- action 2

With that in place, GSD now:

  • parses Pending actions: from completed task summaries
  • includes them in later task carry-forward context as pending_actions: ...
  • triggers replanning-slice before silent continuation or summarizing when the latest completed task still has required follow-up
  • allows a later completed task to trigger a new replan even if the slice has already been replanned for an earlier task
  • generalizes the replan prompt so the trigger can be either:
    • blocker_discovered: true, or
    • carry-forward pending actions from a completed task

Files changed:

  • src/resources/extensions/gsd/types.ts
  • src/resources/extensions/gsd/files.ts
  • src/resources/extensions/gsd/auto-prompts.ts
  • src/resources/extensions/gsd/state.ts
  • src/resources/extensions/gsd/prompts/execute-task.md
  • src/resources/extensions/gsd/prompts/guided-execute-task.md
  • src/resources/extensions/gsd/prompts/replan-slice.md
  • src/resources/extensions/gsd/templates/task-summary.md
  • src/resources/extensions/gsd/tests/flag-file-db.test.ts
  • src/resources/extensions/gsd/tests/pending-actions.test.ts

Why

GSD already has two strong seams here:

  • blocker-based replanning via blocker_discovered: true
  • carry-forward context from prior task summaries

The missing piece is a durable middle ground for tasks that are complete enough to close honestly, but still leave behind required follow-up work that must not disappear into prose.

Without that, the system can end up with one of two bad outcomes:

  • continuing without making the leftover work explicit in the task graph
  • or falling into slice summarization even though required follow-up is already known

This PR makes that unfinished required work machine-usable without changing the completion tool contract.

Refs #3406.
Related to #2479: this PR uses task-scoped loop protection for pending-action-triggered replans so an earlier replan on the same slice does not suppress a later legitimate replan from a different completed task.

How

Implementation details:

  • extend Summary with knownIssues and pendingActions
  • parse ## Known Issues and extract a Pending actions: bullet block in files.ts
  • surface pending actions into buildCarryForwardSection() so later execution prompts can see unresolved required follow-up
  • update execution prompts to preserve pending actions back into ## Known Issues if they still remain after the current task
  • update buildReplanSlicePrompt() and prompts/replan-slice.md so replanning can be triggered by pending actions as well as blockers
  • update both deriveStateFromDb() and _deriveStateImpl() so pending actions win before summarizing / normal executing
  • use replan_history.task_id matching for pending-action loop protection in the DB-backed path

I intentionally kept the contract markdown-based instead of adding a new gsd_task_complete.pendingActions parameter. That keeps the change smaller and backward-compatible with the existing artifact-driven workflow.

Verification

  • npm run typecheck:extensions
  • focused compiled tests:
    • pending-actions.test.ts
    • flag-file-db.test.ts
    • replan-slice.test.ts
  • broader compiled tests:
    • derive-state.test.ts
    • derive-state-db.test.ts
    • prompt-contracts.test.ts
  • npm run build

Notes

  • This PR does not change the existing blocker_discovered contract.
  • It does not add a new auto-mode phase.
  • It does not alter the completion-tool schema.
  • It extends the existing summary → carry-forward → replan chain with a durable convention for required unfinished work.
  • AI-assisted contribution.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

🔴 PR Risk Report — CRITICAL

Files changed 10
Systems affected 4
Overall risk 🔴 CRITICAL

Affected Systems

Risk System
🔴 critical State Machine
🔴 critical Agent Core
🟠 high GSD Workflow
🟠 high AI Providers
File Breakdown
Risk File Systems
🟠 src/resources/extensions/gsd/prompts/execute-task.md GSD Workflow
🟠 src/resources/extensions/gsd/prompts/guided-execute-task.md GSD Workflow
🟠 src/resources/extensions/gsd/prompts/replan-slice.md GSD Workflow
🔴 src/resources/extensions/gsd/state.ts State Machine
🟠 src/resources/extensions/gsd/templates/task-summary.md GSD Workflow
🔴 src/resources/extensions/gsd/types.ts Agent Core, AI Providers
src/resources/extensions/gsd/auto-prompts.ts (unclassified)
src/resources/extensions/gsd/files.ts (unclassified)
src/resources/extensions/gsd/tests/flag-file-db.test.ts (unclassified)
src/resources/extensions/gsd/tests/pending-actions.test.ts (unclassified)

⚠️ Critical risk — please verify: state persistence, auth token lifecycle, agent loop race conditions, RPC protocol compatibility.

@gunish gunish force-pushed the feat/pending-actions-replan-chaining branch from a341f2e to dcc206d Compare April 8, 2026 11:58
Copy link
Copy Markdown
Collaborator

@trek-e trek-e left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Copy Markdown
Collaborator

@trek-e trek-e left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #3413 — feat: carry forward pending actions into replanning

Verdict: REQUEST_CHANGES
Severity: MAJOR + MINOR


MAJOR: Filesystem-path loop protection is asymmetric with DB-path loop protection

DB-backed path (deriveStateFromDb):

const alreadyHandled = replanHistory.some(
  (entry) => String(entry["task_id"] ?? "") === latestPendingActionTask.taskId,
);
if (!alreadyHandled) { /* trigger replanning */ }

Filesystem-backed path (_deriveStateImpl):

if (!replanFile && !alreadyHandled) { /* trigger replanning */ }

The filesystem path has an extra !replanFile guard. If any REPLAN.md file exists on disk from a previous replan (for a different task), replanFile will be truthy and the pending-action check is silently skipped — even when the new pending action is from a different, later task with no matching replan_history entry. This is exactly the scenario the PR description says it handles: "a later completed task can trigger a new replan even if the slice has already been replanned for an earlier task." That claim is true for the DB path but false for the filesystem path. The !replanFile condition must be removed or the logic must match the DB path.


MAJOR: nativeParseSummaryFile fast-path now returns pendingActions parsed from pre-computed knownIssues, but knownIssues is derived before the native parser is called

const [fmLines, body] = splitFrontmatter(content);
const knownIssues = extractSection(body, 'Known Issues') || '';
const pendingActions = extractPendingActions(knownIssues);

const nativeResult = nativeParseSummaryFile(content);
if (nativeResult) {
  return { ...nativeResult, knownIssues, pendingActions };  // ← pendingActions injected
}

This is correct. The native parser returns the base fields; knownIssues and pendingActions are layered on top from the JS-side extraction. This is fine architecturally, but it means splitFrontmatter + extractSection are now always called even when the native parser succeeds — negating part of the native parser's performance benefit. This is a MINOR concern, not a correctness bug, but it should be documented.


MINOR: extractPendingActions regex requires the section to start with Pending actions: on its own line but the template shows Pending actions: without a preceding blank line guarantee

The regex:

/(?:^|\n)Pending actions:\s*\n((?:\s*[-*]\s+.+(?:\n|$))+)/i

This works when ## Known Issues section content starts directly with Pending actions:. However the template instructs authors to write prose above the Pending actions: block. If a user writes:

## Known Issues

Some known issue.

Pending actions:
- action 1

The regex still matches because (?:^|\n) anchors on the newline before Pending actions:. This is fine. Confirmed correct.


MINOR: getLatestPendingActionTask iterates completed tasks in reverse, but completedTaskIds ordering depends on DB/array ordering which may not be chronological

In deriveStateFromDb, completedTaskIds is derived as:

tasks.filter(t => isStatusDone(t.status)).map(t => t.id)

The ordering of tasks from the DB query is not shown in this diff. If tasks are not returned in completion order, reverse() in getLatestPendingActionTask may examine the wrong task first and surface stale pending actions from an earlier task instead of the truly latest one. The PR should document or enforce that completedTaskIds is in insertion/completion order.


MINOR: No test for the filesystem-path (_deriveStateImpl) pending-action trigger

flag-file-db.test.ts tests the DB-backed path. The verification list mentions derive-state.test.ts but the diff only shows changes to flag-file-db.test.ts and pending-actions.test.ts. The filesystem-path asymmetric loop protection bug (above) is untested.


Summary: Fix the !replanFile asymmetry in _deriveStateImpl — that's the core correctness issue. The feature is well-structured otherwise.

@trek-e trek-e added the needs-tests Change adds/changes behavior without test coverage label Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request needs-tests Change adds/changes behavior without test coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants