Skip to content

Instantly share code, notes, and snippets.

@gtrak
Created March 13, 2026 15:23
Show Gist options
  • Select an option

  • Save gtrak/cf9f58d89fcbda6d06a09585b7705db9 to your computer and use it in GitHub Desktop.

Select an option

Save gtrak/cf9f58d89fcbda6d06a09585b7705db9 to your computer and use it in GitHub Desktop.

Revised Architecture: Extensible GSDState + Middleware Interception

1. Extensible GSDState

// types.ts - GSDState becomes extensible
export interface GSDState {
  activeMilestone: ActiveRef | null;
  activeSlice: ActiveRef | null;
  activeTask: ActiveRef | null;
  phase: Phase;
  // ... existing fields ...

  // Extension point - hooks store typed data here
  extensions: {
    [hookName: string]: unknown;
  };
}

Code Review Extension Type

export interface ReviewLoopExtension {
  activeReview?: {
    taskId: string;
    cycle: number;           // 1–5
    status: 'pending_review' | 'fixing';
    lastReviewPath: string;  // Path to CODE-REVIEW.md
  };

  completedReviews: Array<{
    taskId: string;
    cycles: number;
    passed: boolean;
  }>;
}

Persistence

Hook state is serialized into the markdown frontmatter of the associated task, slice, or milestone.

---
id: T01
extensions:
  review-loop:
    activeReview:
      taskId: T01
      cycle: 2
      status: fixing
      lastReviewPath: tasks/T01-CODE-REVIEW.md
---

2. Middleware Intercepts /gsd next Flow

dispatchNextUnit runs a middleware chain that allows hooks to override the default dispatch behavior.

// auto.ts
async function dispatchNextUnit(ctx, pi) {
  const state = await deriveState(basePath);

  // Run middleware chain - hooks can override decision
  const middlewareCtx = await runMiddleware(state, { basePath, pi, ctx });

  if (middlewareCtx.decision) {
    // Hook took over - dispatch hook's unit
    await dispatchUnit(middlewareCtx.decision);
  } else {
    // Default GSD logic
    await defaultDispatchLogic(state);
  }
}

3. Code Review Hook Example

// review-loop-hook.ts
export const reviewLoopHook: GSDMiddleware = async (ctx, next) => {
  const { state, basePath } = ctx;

  // Only intercept when task just completed
  if (state.phase !== 'executing' || !state.activeTask) {
    await next(); // Continue to default execution
    return;
  }

  // Check if we have active review state
  const reviewState = state.extensions['review-loop'] as ReviewLoopExtension;

  if (reviewState?.activeReview?.taskId === state.activeTask.id) {
    // Resume review cycle
    const { cycle, status } = reviewState.activeReview;

    if (status === 'pending_review') {
      ctx.decision = {
        unitType: 'review-task',
        prompt: buildReviewPrompt(state, cycle),
      };
      return;
    }

    if (status === 'fixing') {
      ctx.decision = {
        unitType: 'fix-task',
        prompt: buildFixPrompt(state, cycle),
      };
      return;
    }
  }

  // Check if task needs first review
  if (shouldReviewTask(state)) {
    ctx.state = {
      ...state,
      extensions: {
        ...state.extensions,
        'review-loop': {
          activeReview: {
            taskId: state.activeTask.id,
            cycle: 1,
            status: 'pending_review',
            lastReviewPath: '',
          },
          completedReviews: reviewState?.completedReviews ?? [],
        } satisfies ReviewLoopExtension,
      },
    };

    ctx.decision = {
      unitType: 'review-task',
      prompt: buildReviewPrompt(state, 1),
    };

    return;
  }

  // No review needed - proceed with normal execution
  await next();
};

4. Review Completion Updates State

After a review-task completes, handleAgentEnd in auto.ts triggers middleware again.

Hook afterDispatch Handler

async function afterDispatch(result, ctx) {
  if (result.unitType === 'review-task') {
    const reviewFile = parseReviewOutput(result);
    const issues = extractIssues(reviewFile);

    if (issues.length === 0) {
      // Review passed - clear active review
      ctx.state.extensions['review-loop'].activeReview = undefined;

      // Allow task execution to proceed
      await next();
    } else {
      // Issues found - increment cycle and schedule fix
      const currentCycle =
        ctx.state.extensions['review-loop'].activeReview!.cycle;

      ctx.state.extensions['review-loop'].activeReview = {
        ...ctx.state.extensions['review-loop'].activeReview!,
        cycle: currentCycle + 1,
        status: 'fixing',
        lastReviewPath: reviewFile,
      };

      // Do not call next() - hook schedules fix-task next iteration
    }
  }
}

5. Distribution & Registration

Pi Extension Structure

~/.gsd/agent/extensions/my-review-loop/
├── index.ts
└── gsd-hooks.ts
  • index.ts: standard Pi extension entry point
  • gsd-hooks.ts: optional file exporting hook definitions

Registration Options

Option A — Self-Registration (Preferred)

// index.ts
import { registerHook } from '../gsd/hooks.js';

export default function (pi: ExtensionAPI) {
  registerHook('review-loop', reviewLoopHook);
}

Option B — Explicit Configuration

preferences.md

gsd_hooks:
  enabled:
    - name: review-loop
      source: my-review-loop

6. Key Open Questions

1. Hook Ordering

If multiple hooks attempt to override dispatch:

  • Option A: First registered hook wins
  • Option B: Explicit priority ordering

2. State Merging

If multiple hooks modify ctx.state.extensions:

  • Option A: Shallow merge
  • Option B: Hooks must manage their own namespace carefully

3. Type Safety

Hooks could augment the GSDState extension types.

declare module '../gsd/types.js' {
  interface GSDStateExtensions {
    'review-loop': ReviewLoopExtension;
  }
}

4. Iteration Tracking

Should the cycle counter:

  • auto-increment at the framework level, or
  • be fully managed by hooks?

Architectural Summary

  • Hooks operate as middleware interceptors in the /gsd next dispatch pipeline.
  • Hook-specific state lives in GSDState.extensions.
  • State persists through the existing markdown frontmatter system.
  • Hooks can override the next execution unit by setting ctx.decision.
  • Review loops become a pure extension, without modifying the core GSD execution engine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment