From b2e1ee9e7b5981dc64809c89487cebc6575b9f4c Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 26 Mar 2026 10:45:57 -0600 Subject: [PATCH] fix: gen-skill-docs resolver merge + preamble tier gate + plan file discovery The local RESOLVERS record in gen-skill-docs.ts was shadowing the imported canonical resolvers, causing stale test coverage and preamble generators to be used instead of the authoritative versions in resolvers/. Changes: - Merge imported RESOLVERS with local overrides (spread + override pattern) - Fix preamble tier gate: tier 1 skills no longer get AskUserQuestion format - Make plan file discovery host-agnostic (search multiple plan dirs) - Add missing E2E tier entries for ship/review plan completion tests Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/gen-skill-docs.ts | 44 +++++++++++-------------------------- scripts/resolvers/review.ts | 16 ++++++++------ test/helpers/touchfiles.ts | 3 +++ 3 files changed, 25 insertions(+), 38 deletions(-) diff --git a/scripts/gen-skill-docs.ts b/scripts/gen-skill-docs.ts index 8d483dad..f1a72fd1 100644 --- a/scripts/gen-skill-docs.ts +++ b/scripts/gen-skill-docs.ts @@ -16,7 +16,7 @@ import * as fs from 'fs'; import * as path from 'path'; import type { Host, TemplateContext } from './resolvers/types'; import { HOST_PATHS } from './resolvers/types'; -import { RESOLVERS } from './resolvers/index'; +import { RESOLVERS as IMPORTED_RESOLVERS } from './resolvers/index'; import { codexSkillName, transformFrontmatter, extractHookSafetyProse, extractNameAndDescription, condenseOpenAIShortDescription, generateOpenAIYaml } from './resolvers/codex-helpers'; import { generatePlanCompletionAuditShip, generatePlanCompletionAuditReview, generatePlanVerificationExec } from './resolvers/review'; @@ -473,7 +473,7 @@ Hey gstack team — ran into this while using /{skill-name}: **What I was trying to do:** {what the user/agent was attempting} **What happened instead:** {what actually happened} -**My rating:** {0-10} — {one sentence on why it wasn't a 10} +**My Rating:** {0-10} — {one sentence on why it wasn't a 10} ## Steps to reproduce 1. {step} @@ -584,15 +584,14 @@ plan's living status.`; } function generatePreamble(ctx: TemplateContext): string { + const tier = ctx.preambleTier ?? 4; return [ generatePreambleBash(ctx), generateUpgradeCheck(ctx), generateLakeIntro(), generateTelemetryPrompt(ctx), - generateAskUserFormat(ctx), - generateCompletenessSection(), - generateRepoModeSection(), - generateSearchBeforeBuildingSection(ctx), + ...(tier >= 2 ? [generateAskUserFormat(ctx), generateCompletenessSection()] : []), + ...(tier >= 3 ? [generateRepoModeSection(), generateSearchBeforeBuildingSection(ctx)] : []), generateContributorMode(), generateCompletionStatus(), ].join('\n\n'); @@ -2801,37 +2800,20 @@ function generateSlugSetup(ctx: TemplateContext): string { return `eval "$(${ctx.paths.binDir}/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG`; } +// Use the canonical RESOLVERS from resolvers/index.ts, extended with local overrides. +// Local overrides are kept for functions that have hardcoded ~/.claude/ paths in the +// imported versions — the local versions are host-agnostic and safe for Codex output. const RESOLVERS: Record string> = { - SLUG_EVAL: generateSlugEval, - SLUG_SETUP: generateSlugSetup, - COMMAND_REFERENCE: generateCommandReference, - SNAPSHOT_FLAGS: generateSnapshotFlags, + ...IMPORTED_RESOLVERS, + // Local override — preamble generator with tier-gated sections PREAMBLE: generatePreamble, - BROWSE_SETUP: generateBrowseSetup, - BASE_BRANCH_DETECT: generateBaseBranchDetect, - QA_METHODOLOGY: generateQAMethodology, - DESIGN_METHODOLOGY: generateDesignMethodology, - DESIGN_HARD_RULES: generateDesignHardRules, - DESIGN_OUTSIDE_VOICES: generateDesignOutsideVoices, - DESIGN_REVIEW_LITE: generateDesignReviewLite, - REVIEW_DASHBOARD: generateReviewDashboard, + // Local overrides — these use host-agnostic paths (no ~/.claude/ hardcoding) PLAN_FILE_REVIEW_REPORT: generatePlanFileReviewReport, - TEST_BOOTSTRAP: generateTestBootstrap, - TEST_COVERAGE_AUDIT_PLAN: generateTestCoverageAuditPlan, - TEST_COVERAGE_AUDIT_SHIP: generateTestCoverageAuditShip, - TEST_COVERAGE_AUDIT_REVIEW: generateTestCoverageAuditReview, - TEST_FAILURE_TRIAGE: generateTestFailureTriage, - SPEC_REVIEW_LOOP: generateSpecReviewLoop, - DESIGN_SKETCH: generateDesignSketch, - BENEFITS_FROM: generateBenefitsFrom, - CODEX_SECOND_OPINION: generateCodexSecondOpinion, - CODEX_REVIEW_STEP: generateAdversarialStep, - ADVERSARIAL_STEP: generateAdversarialStep, - DEPLOY_BOOTSTRAP: generateDeployBootstrap, - CODEX_PLAN_REVIEW: generateCodexPlanReview, PLAN_COMPLETION_AUDIT_SHIP: generatePlanCompletionAuditShip, PLAN_COMPLETION_AUDIT_REVIEW: generatePlanCompletionAuditReview, PLAN_VERIFICATION_EXEC: generatePlanVerificationExec, + // Local-only entry not in resolvers/index.ts + CODEX_REVIEW_STEP: generateAdversarialStep, }; // ─── Codex Helpers ─────────────────────────────────────────── diff --git a/scripts/resolvers/review.ts b/scripts/resolvers/review.ts index 2b83f36d..960f171e 100644 --- a/scripts/resolvers/review.ts +++ b/scripts/resolvers/review.ts @@ -598,19 +598,21 @@ SOURCE = "codex" if Codex ran, "claude" if subagent ran. function generatePlanFileDiscovery(): string { return `### Plan File Discovery -1. **Conversation context (primary):** Check if there is an active plan file in this conversation — Claude Code system messages include plan file paths when in plan mode. Look for references like \`~/.claude/plans/*.md\` in system messages. If found, use it directly — this is the most reliable signal. +1. **Conversation context (primary):** Check if there is an active plan file in this conversation. The host agent's system messages include plan file paths when in plan mode. If found, use it directly — this is the most reliable signal. 2. **Content-based search (fallback):** If no plan file is referenced in conversation context, search by content: \`\`\`bash BRANCH=$(git branch --show-current 2>/dev/null | tr '/' '-') REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)") -# Try branch name match first (most specific) -PLAN=$(ls -t ~/.claude/plans/*.md 2>/dev/null | xargs grep -l "$BRANCH" 2>/dev/null | head -1) -# Fall back to repo name match -[ -z "$PLAN" ] && PLAN=$(ls -t ~/.claude/plans/*.md 2>/dev/null | xargs grep -l "$REPO" 2>/dev/null | head -1) -# Last resort: most recent plan modified in the last 24 hours -[ -z "$PLAN" ] && PLAN=$(find ~/.claude/plans -name '*.md' -mmin -1440 -maxdepth 1 2>/dev/null | xargs ls -t 2>/dev/null | head -1) +# Search common plan file locations +for PLAN_DIR in "$HOME/.claude/plans" "$HOME/.codex/plans" ".gstack/plans"; do + [ -d "$PLAN_DIR" ] || continue + PLAN=$(ls -t "$PLAN_DIR"/*.md 2>/dev/null | xargs grep -l "$BRANCH" 2>/dev/null | head -1) + [ -z "$PLAN" ] && PLAN=$(ls -t "$PLAN_DIR"/*.md 2>/dev/null | xargs grep -l "$REPO" 2>/dev/null | head -1) + [ -z "$PLAN" ] && PLAN=$(find "$PLAN_DIR" -name '*.md' -mmin -1440 -maxdepth 1 2>/dev/null | xargs ls -t 2>/dev/null | head -1) + [ -n "$PLAN" ] && break +done [ -n "$PLAN" ] && echo "PLAN_FILE: $PLAN" || echo "NO_PLAN_FILE" \`\`\` diff --git a/test/helpers/touchfiles.ts b/test/helpers/touchfiles.ts index d61ae164..c2994c3d 100644 --- a/test/helpers/touchfiles.ts +++ b/test/helpers/touchfiles.ts @@ -184,6 +184,7 @@ export const E2E_TIERS: Record = { 'review-base-branch': 'gate', 'review-design-lite': 'periodic', // 4/7 threshold is subjective 'review-coverage-audit': 'gate', + 'review-plan-completion': 'gate', // Office Hours 'office-hours-spec-review': 'gate', @@ -208,6 +209,8 @@ export const E2E_TIERS: Record = { 'ship-local-workflow': 'gate', 'ship-coverage-audit': 'gate', 'ship-triage': 'gate', + 'ship-plan-completion': 'gate', + 'ship-plan-verification': 'gate', // Retro — gate for cheap branch detection, periodic for full Opus retro 'retro': 'periodic',