From f70ae7adf8dcc09591fce6198d4f5de410cb809b Mon Sep 17 00:00:00 2001 From: "Derek Palmer (Creative)" Date: Tue, 2 Jun 2026 21:35:04 -0400 Subject: [PATCH 1/2] feat(gaps): add forerunner-gaps skill for surfacing stale docs and scan gaps Adds a new agent-only skill that reads cached .forerunner state and surfaces two gap categories: STALE/MISSING docs from check-report.md (routing to forerunner-refresh or specific task skills), and unresolved scan.yaml gaps (offering quick Q&A fill or grill-with-docs session with graceful fallback when grill-with-docs is not installed). Registers the task in tasks.json (scan_exempt: true), adds the prompt file, places the skill at both the installed and plugin-source paths, updates skills-lock.json, and adds five new tests covering registration, refresh exclusion, installable slugs, file existence, and copy parity. Co-Authored-By: Claude Sonnet 4.6 --- .../skills/forerunner-gaps/SKILL.md | 81 +++++++++++++++++++ skills-lock.json | 6 ++ skills/forerunner-gaps/SKILL.md | 81 +++++++++++++++++++ src/codeforerunner/prompts/tasks/gaps.md | 52 ++++++++++++ src/codeforerunner/tasks.json | 3 +- tests/test_tasks.py | 32 +++++++- 6 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 plugins/codeforerunner/skills/forerunner-gaps/SKILL.md create mode 100644 skills/forerunner-gaps/SKILL.md create mode 100644 src/codeforerunner/prompts/tasks/gaps.md diff --git a/plugins/codeforerunner/skills/forerunner-gaps/SKILL.md b/plugins/codeforerunner/skills/forerunner-gaps/SKILL.md new file mode 100644 index 0000000..c2a9480 --- /dev/null +++ b/plugins/codeforerunner/skills/forerunner-gaps/SKILL.md @@ -0,0 +1,81 @@ +--- +name: forerunner-gaps +description: Surface missing or stale forerunner documentation findings and scan gaps. Use when the user wants to know what docs are outdated, what tasks have never been run, or what the scan couldn't determine. +--- + +# forerunner-gaps + +Reads cached forerunner state and surfaces two categories of gaps: +1. **Doc gaps** — STALE or MISSING findings from the check report +2. **Scan gaps** — fields the scan couldn't determine from available evidence + +Does not generate docs itself. Routes the user to the right next step. + +## Activate when + +User asks to: find gaps, check forerunner gaps, what's missing, surface stale findings, what docs are outdated, gaps report, what hasn't been run. + +## Collect this context + +- `.forerunner/scan.yaml` — scan result (check file modification time) +- `.forerunner/check-report.md` — staleness classifications (check file modification time) + +## Execute + +### Step 1 — Check cache freshness + +Read `.forerunner/scan.yaml` and `.forerunner/check-report.md`. + +**If either file is missing:** Tell the user which file is absent. Ask them to run `/forerunner-scan` and/or `/forerunner-check` first. Stop. + +**If scan.yaml is older than 24 hours:** Warn the user: "scan.yaml is X hours old — findings may not reflect current codebase. Run `/forerunner-scan` to refresh, or continue with cached results?" Wait for their choice before proceeding. + +**If check-report.md is older than scan.yaml:** Warn the user: "check-report.md predates the last scan — it may not reflect the current scan result. Run `/forerunner-check` to refresh, or continue?" Wait for their choice. + +### Step 2 — Surface doc gaps + +Extract all rows from check-report.md where status is `STALE` or `MISSING`. + +If none: report "All documented tasks are CURRENT." Skip to Step 3. + +Otherwise, present a summary table: + +``` +## Doc Gaps + +| Document | Status | Issue | +|-----------------------|---------|-----------------------------------------| +| README.md | STALE | DATABASE_URL missing from config section | +| docs/api.md | MISSING | No API docs found | +``` + +Then offer: "Run `/forerunner-refresh` to regenerate all stale/missing docs, or invoke a specific task skill (e.g. `/forerunner-readme`) for individual items." + +### Step 3 — Surface scan gaps + +Read the `gaps:` field from scan.yaml. If absent or empty, report "No scan gaps detected." Stop. + +Otherwise, list each gap and ask the user: + +> "The scan couldn't determine the following from available evidence: +> - [gap 1] +> - [gap 2] +> +> How would you like to fill these? +> A) Quick fill — I'll ask one question per gap and patch scan.yaml +> B) Full grill session — deeper conversation that also updates CONTEXT.md and domain docs (requires grill-with-docs skill) +> C) Skip" + +**If user selects B and `grill-with-docs` skill is not available** (check by looking for the skill in the skills directory or `skills-lock.json`): inform the user that `grill-with-docs` is not installed. Fall back to option A automatically, or suggest they install the skill first. + +**If user selects A:** Ask one question per gap in sequence. After each answer, update the corresponding field in `.forerunner/scan.yaml` and mark it as user-provided (add `# user-provided` comment inline). Continue until all gaps are filled or user skips a gap. + +**If user selects B:** Invoke the `grill-with-docs` skill, passing the gap list as the grilling focus. + +## Output + +No files written by default. Side effects: +- If quick-fill (A): patches `.forerunner/scan.yaml` with user-provided values +- If grill session (B): `grill-with-docs` owns its own output (CONTEXT.md, ADRs) + +Always end with a summary of what was surfaced, what was filled, and what remains open. diff --git a/skills-lock.json b/skills-lock.json index 54c9dd5..da6bab1 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -84,6 +84,12 @@ "sourceType": "local", "skillPath": "skills/forerunner-version-audit/SKILL.md", "computedHash": "17d9367e2e3608b9a9fde05730a4199cb1674a1e4529aaf53364c02b76e5149a" + }, + "forerunner-gaps": { + "source": "plugins/codeforerunner/skills/forerunner-gaps/SKILL.md", + "sourceType": "local", + "skillPath": "skills/forerunner-gaps/SKILL.md", + "computedHash": "0c314bfa709227b23dc5405418da561d83b3382be5914cab6d502814a08dd860" } } } diff --git a/skills/forerunner-gaps/SKILL.md b/skills/forerunner-gaps/SKILL.md new file mode 100644 index 0000000..c2a9480 --- /dev/null +++ b/skills/forerunner-gaps/SKILL.md @@ -0,0 +1,81 @@ +--- +name: forerunner-gaps +description: Surface missing or stale forerunner documentation findings and scan gaps. Use when the user wants to know what docs are outdated, what tasks have never been run, or what the scan couldn't determine. +--- + +# forerunner-gaps + +Reads cached forerunner state and surfaces two categories of gaps: +1. **Doc gaps** — STALE or MISSING findings from the check report +2. **Scan gaps** — fields the scan couldn't determine from available evidence + +Does not generate docs itself. Routes the user to the right next step. + +## Activate when + +User asks to: find gaps, check forerunner gaps, what's missing, surface stale findings, what docs are outdated, gaps report, what hasn't been run. + +## Collect this context + +- `.forerunner/scan.yaml` — scan result (check file modification time) +- `.forerunner/check-report.md` — staleness classifications (check file modification time) + +## Execute + +### Step 1 — Check cache freshness + +Read `.forerunner/scan.yaml` and `.forerunner/check-report.md`. + +**If either file is missing:** Tell the user which file is absent. Ask them to run `/forerunner-scan` and/or `/forerunner-check` first. Stop. + +**If scan.yaml is older than 24 hours:** Warn the user: "scan.yaml is X hours old — findings may not reflect current codebase. Run `/forerunner-scan` to refresh, or continue with cached results?" Wait for their choice before proceeding. + +**If check-report.md is older than scan.yaml:** Warn the user: "check-report.md predates the last scan — it may not reflect the current scan result. Run `/forerunner-check` to refresh, or continue?" Wait for their choice. + +### Step 2 — Surface doc gaps + +Extract all rows from check-report.md where status is `STALE` or `MISSING`. + +If none: report "All documented tasks are CURRENT." Skip to Step 3. + +Otherwise, present a summary table: + +``` +## Doc Gaps + +| Document | Status | Issue | +|-----------------------|---------|-----------------------------------------| +| README.md | STALE | DATABASE_URL missing from config section | +| docs/api.md | MISSING | No API docs found | +``` + +Then offer: "Run `/forerunner-refresh` to regenerate all stale/missing docs, or invoke a specific task skill (e.g. `/forerunner-readme`) for individual items." + +### Step 3 — Surface scan gaps + +Read the `gaps:` field from scan.yaml. If absent or empty, report "No scan gaps detected." Stop. + +Otherwise, list each gap and ask the user: + +> "The scan couldn't determine the following from available evidence: +> - [gap 1] +> - [gap 2] +> +> How would you like to fill these? +> A) Quick fill — I'll ask one question per gap and patch scan.yaml +> B) Full grill session — deeper conversation that also updates CONTEXT.md and domain docs (requires grill-with-docs skill) +> C) Skip" + +**If user selects B and `grill-with-docs` skill is not available** (check by looking for the skill in the skills directory or `skills-lock.json`): inform the user that `grill-with-docs` is not installed. Fall back to option A automatically, or suggest they install the skill first. + +**If user selects A:** Ask one question per gap in sequence. After each answer, update the corresponding field in `.forerunner/scan.yaml` and mark it as user-provided (add `# user-provided` comment inline). Continue until all gaps are filled or user skips a gap. + +**If user selects B:** Invoke the `grill-with-docs` skill, passing the gap list as the grilling focus. + +## Output + +No files written by default. Side effects: +- If quick-fill (A): patches `.forerunner/scan.yaml` with user-provided values +- If grill session (B): `grill-with-docs` owns its own output (CONTEXT.md, ADRs) + +Always end with a summary of what was surfaced, what was filled, and what remains open. diff --git a/src/codeforerunner/prompts/tasks/gaps.md b/src/codeforerunner/prompts/tasks/gaps.md new file mode 100644 index 0000000..f728850 --- /dev/null +++ b/src/codeforerunner/prompts/tasks/gaps.md @@ -0,0 +1,52 @@ +# Task: Surface Documentation and Scan Gaps + +Reads cached forerunner state and surfaces two categories of gaps for the user to act on: +1. **Doc gaps** — STALE or MISSING findings from the check report +2. **Scan gaps** — fields the scan could not determine from available evidence + +Does not generate docs. Routes the user to the right next step. + +## Input + +- `.forerunner/scan.yaml` — cached scan result (check file modification time) +- `.forerunner/check-report.md` — cached staleness classifications (check file modification time) + +## Instructions + +### Step 1 — Check cache freshness + +Read `.forerunner/scan.yaml` and `.forerunner/check-report.md`. + +If either file is missing: report which file is absent and stop. Ask the user to run `forerunner doc scan` and/or `forerunner doc check` first. + +If `scan.yaml` is older than 24 hours: warn the user and offer to continue with cached results or re-run scan first. + +If `check-report.md` is older than `scan.yaml`: warn the user and offer to continue or re-run check first. + +### Step 2 — Surface doc gaps + +Extract all rows from `check-report.md` where status is `STALE` or `MISSING`. + +If none: report "All documented tasks are CURRENT." Proceed to step 3. + +Present a summary table and offer: run `/forerunner-refresh` to regenerate all stale/missing docs, or invoke a specific task skill for individual items. + +### Step 3 — Surface scan gaps + +Read the `gaps:` field from `scan.yaml`. If absent or empty: report "No scan gaps detected." Stop. + +Otherwise, list each gap and ask the user how to fill them: + +**A) Quick fill** — ask one question per gap and patch `scan.yaml` with user-provided values +**B) Full grill session** — deeper conversation that also updates `CONTEXT.md` and domain docs (requires `grill-with-docs` skill; fall back to A if not available) +**C) Skip** + +For quick fill (A): ask one question per gap in sequence, update the corresponding field in `.forerunner/scan.yaml`, mark as `# user-provided` inline. + +For grill session (B): invoke `grill-with-docs` with the gap list as the grilling focus. + +## Rules + +- Never run a fresh scan or check automatically. Only read cached files. +- If `grill-with-docs` is not available when user selects B, fall back to A and inform the user. +- End with a summary: what was surfaced, what was filled, what remains open. diff --git a/src/codeforerunner/tasks.json b/src/codeforerunner/tasks.json index 87a423c..10b47da 100644 --- a/src/codeforerunner/tasks.json +++ b/src/codeforerunner/tasks.json @@ -25,6 +25,7 @@ {"name": "refresh", "scan_exempt": false, "skill_slug": "forerunner-refresh"}, {"name": "review", "scan_exempt": false, "skill_slug": "forerunner-review"}, {"name": "stack-docs", "scan_exempt": false, "skill_slug": "forerunner-stack-docs"}, - {"name": "version-audit", "scan_exempt": false, "skill_slug": "forerunner-version-audit"} + {"name": "version-audit", "scan_exempt": false, "skill_slug": "forerunner-version-audit"}, + {"name": "gaps", "scan_exempt": true, "skill_slug": "forerunner-gaps"} ] } diff --git a/tests/test_tasks.py b/tests/test_tasks.py index f3c73ca..6cf2cf2 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -33,7 +33,7 @@ def test_get_unknown_raises(): def test_scan_exempt_names(): - assert tasks.scan_exempt_names() == {"scan", "init-agent-onboarding"} + assert tasks.scan_exempt_names() == {"scan", "init-agent-onboarding", "gaps"} def test_refresh_tasks_ordered(): @@ -75,6 +75,36 @@ def test_cli_rejects_unregistered_task(tmp_path, capsys): assert "unknown task" in capsys.readouterr().err +def test_gaps_task_registered(): + t = tasks.get("gaps") + assert t.scan_exempt is True + assert t.skill_slug == "forerunner-gaps" + + +def test_gaps_excluded_from_refresh_sequence(): + names = [t.name for t in tasks.refresh_tasks()] + assert "gaps" not in names + + +def test_gaps_installable_slug_present(): + assert "forerunner-gaps" in tasks.installable_slugs() + + +def test_gaps_skill_files_exist(): + from pathlib import Path + root = Path(__file__).resolve().parent.parent + assert (root / "skills" / "forerunner-gaps" / "SKILL.md").is_file() + assert (root / "plugins" / "codeforerunner" / "skills" / "forerunner-gaps" / "SKILL.md").is_file() + + +def test_gaps_skill_copies_in_sync(): + from pathlib import Path + root = Path(__file__).resolve().parent.parent + installed = (root / "skills" / "forerunner-gaps" / "SKILL.md").read_text(encoding="utf-8") + plugin = (root / "plugins" / "codeforerunner" / "skills" / "forerunner-gaps" / "SKILL.md").read_text(encoding="utf-8") + assert installed == plugin + + def test_mcp_tools_list_matches_registry(): from codeforerunner.mcp_server import _tools import codeforerunner From 5a4ad92fffc89f2dc25a756b00545c8c10d3b311 Mon Sep 17 00:00:00 2001 From: "Derek Palmer (Creative)" Date: Tue, 2 Jun 2026 21:45:46 -0400 Subject: [PATCH 2/2] chore: update uv.lock --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 043d581..24adbae 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.11" [[package]] name = "codeforerunner" -version = "0.4.4" +version = "0.4.7" source = { editable = "." } dependencies = [ { name = "pyyaml" },