From a1f3f42172623a18f539ebff2f66255690dab021 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 15:34:37 +0200 Subject: [PATCH 01/44] docs: design executing plan status files --- ...7-04-executing-plans-status-file-design.md | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 tmp/superpowers/specs/2026-07-04-executing-plans-status-file-design.md diff --git a/tmp/superpowers/specs/2026-07-04-executing-plans-status-file-design.md b/tmp/superpowers/specs/2026-07-04-executing-plans-status-file-design.md new file mode 100644 index 0000000..500c03d --- /dev/null +++ b/tmp/superpowers/specs/2026-07-04-executing-plans-status-file-design.md @@ -0,0 +1,139 @@ +# Executing Plans Status File Design + +## Purpose + +Improve `.github/skills/superpowers-executing-plans/SKILL.md` so every plan +execution leaves a compact, deterministic state file next to the plan. The file +must make the plan status obvious and give the next agent enough context to +resume without rereading the whole conversation. + +This spec is a design handoff only. It must not implement the skill change. + +## Target Skill + +- Modify: `.github/skills/superpowers-executing-plans/SKILL.md` +- Do not modify adjacent Superpowers skills unless implementation finds a direct + contract conflict that blocks this change. +- Keep the skill portable. Do not add repository-local paths, validators, or + examples beyond generic plan-relative behavior. + +## Decisions + +- The status file is written next to the original plan file. +- The filename format is `..md`. +- `` is the plan filename without the final `.md` suffix. +- Supported statuses are `DONE`, `BLOCKED`, `PARTIAL`, and `NEEDS_REVIEW`. +- Status values are uppercase. +- The skill writes a status file whenever execution reaches a terminal stop, + including success, blocker, partial completion, or initial review failure. +- Only one current status file should exist for a plan. Before writing a new + status file, the agent removes or replaces stale status files for the same + plan basename. +- The status file should stay short, normally under 40 lines. +- The main session owns the final status file, even when subagents contribute. + +## Status Semantics + +Use `DONE` only when all planned work is complete, required verification has +passed or any validation gaps are explicitly accepted, and the finishing step is +complete. + +Use `PARTIAL` when some plan work was completed but the execution cannot be +declared done. Typical cases include failing final validation, unfinished +finishing work, or a stop after meaningful progress. + +Use `BLOCKED` when execution cannot continue without user input, missing access, +missing dependency, unclear instructions, or a repeated non-repairable failure. + +Use `NEEDS_REVIEW` when the initial plan review finds critical gaps before +implementation starts, or when the plan itself needs user correction before it +can be safely executed. + +## Required File Shape + +The status file must use concise Markdown with these sections: + +```markdown +# - + +## Status + + +## Reason + + +## Completed +- + +## Remaining +- + +## Validation +- + +## Next + + +## Resume Notes + +``` + +The file must include only commands that were actually run. If validation was +not run, the file must say why. + +## Workflow Change + +Add a new terminal step to the skill after task execution and after any stop +condition: + +1. Determine the final status from the execution outcome. +2. Build the target status filename from the plan path and status. +3. Remove or replace stale sibling files matching the same plan basename and a + supported status suffix. +4. Write the compact status file. +5. Report the status file path in the final chat response. + +This step must also run when the agent stops before implementation because the +plan has critical gaps. + +## Relationship To Existing Finishing Step + +The existing `superpowers-finishing-a-development-branch` handoff remains part +of the workflow. + +`DONE` is valid only after that finishing workflow is complete. If task work is +complete but the finishing workflow is not complete, the correct status is +`PARTIAL` unless the user explicitly accepts the remaining gap. + +## Anti-Scope + +- Do not create timestamped status history files. +- Do not embed the only state summary inside the original plan. +- Do not require a repository-specific `tmp/superpowers/` path. +- Do not introduce a helper script unless the implementation clearly needs one. +- Do not create status files when there is no reliable plan path. + +## Validation + +The implementation should run the closest available skill validator for the +changed skill. In this repository, the expected validator is: + +```bash +./.github/scripts/.venv/bin/python ./.github/scripts/validate_internal_skills.py --skill superpowers-executing-plans --strict +``` + +If that validator is unavailable, run the closest Markdown or repository check +and report the validation gap explicitly. + +## Acceptance Criteria + +- `.github/skills/superpowers-executing-plans/SKILL.md` requires a status file + for every terminal execution outcome. +- The filename contract is clearly documented as + `..md`. +- The allowed statuses and their meanings are unambiguous. +- The status file content is short and resume-oriented. +- The skill preserves its existing critical-review, task execution, blocker, + and finishing workflow behavior. +- The implementation validates the changed skill or reports why validation could + not be run. From 0ae9ef9c5b50424692c70c4519583e2b6889ba92 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 15:34:53 +0200 Subject: [PATCH 02/44] docs: split global baseline from local rules --- AGENTS.md | 116 +++++++++++++----------------------------------------- 1 file changed, 27 insertions(+), 89 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8797adb..375ca95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,16 +25,10 @@ This block is the portable source baseline used to generate aliases, or past automation unless it exists on disk and is deliberately reintroduced. -## Progressive Disclosure - -- Load only the owner, skill, contract, or documentation surface required for - the current lane. -- Expand context only after bounded evidence leaves a material gap. - ## User Alignment -- For small, deterministic, low-risk tasks, proceed after confirming target, - owner, and validation path. +- For small, deterministic, low-risk tasks, proceed after identifying the + target, nearest owner, and validation path. - For non-trivial, ambiguous, architectural, policy, contract, or multi-step work, align with the user before implementation. @@ -44,8 +38,7 @@ This block is the portable source baseline used to generate validation path before broad commands. - Make surgical changes. Preserve user work, avoid unrelated refactors, and tie each edit to the requested outcome. -- Fix the controlling root cause where practical instead of layering - workaround-only changes. +- Fix the controlling issue where practical instead of layering workarounds. - Work toward verified outcomes. Run the closest available validation and report explicit gaps. @@ -58,78 +51,40 @@ This block is the portable source baseline used to generate ## Authoring Defaults -- Use Plain Technical English for repository-owned prose. +- Use Plain Technical English for repository-owned prose unless a narrower owner + explicitly overrides it. - Prefer short sentences, stable terms, active voice, and explicit `must`, `should`, and `may` wording. -- Keep required technical names unchanged, including paths, commands, schema - fields, product names, asset names, and established repository terms. -- The default authoring language for repository artifacts is English unless a - narrower owned file, skill, or local exception explicitly overrides it. -- User chat may use the user's language; repository-owned artifacts follow the - language rules of their closest owner. -- Keep repository-owned AI configuration files as Markdown. Use XML only as - runtime prompt-assembly delimiters, never as source format. -- XML-style code-span markers may delimit Markdown scope blocks when they clarify - runtime, sync, or locality boundaries. -- Do not rewrite imported upstream text that must remain verbatim. -- For vendor-owned or schema-driven surfaces, read primary documentation when - correctness depends on platform semantics. +- Keep required technical names unchanged. ## Tactical Defaults - Preserve compact working state across turns; avoid rebuilding full context unless new evidence invalidates the current state. +- Keep one active primary owner per execution lane; load narrower owners only + when path, runtime, symptom, or validation evidence proves they are needed. - Use bounded evidence: inspect changed sections and failing-validator context first, then expand only when gaps remain. +- Name the validation path early; if evidence changes it, update the working + assumption before editing. ## Delivery And Validation -- Use least privilege and never hardcode secrets, credentials, keys, tokens, or - sensitive tenant values. - Reason from repository evidence. Do not invent runtimes, validators, sync flows, or tests. -- Prefer the simplest correct change with the smallest credible blast radius. -- For non-trivial repository-owned work, make target state, anti-scope, - assumptions, tradeoffs, and validation path visible before delivery or - handoff. -- Run the applicable validation that exists for changed files. When no dedicated - validator exists, report the gap and use the closest check. - -## Contract Discipline - -- When a contract, policy, or ownership rule changes, align the owning tests, - validators, and technical docs to the new behavior. -- Do not let stale checks or historical wording force a reverted model. - -## Token And Drift Control - -- `AGENTS.md` is the canonical always-on policy surface; keep it near a 4,000 - estimated-token soft target measured as `ceil(UTF-8 bytes / 4)` by the - validator. +- For non-trivial work, make target state, anti-scope, assumptions, tradeoffs, + and validation path visible before implementation or handoff. +- When a contract or policy changes, align the owning tests, validators, or docs + instead of letting stale checks restore the old behavior. ## graphify -Use graphify first for codebase, project, architecture, file-relationship, or -content-orientation questions when `graphify-out/graph.json` exists or graphify -is available. - -When the user types `/graphify`, invoke the `skill` tool with `skill: "graphify"` -before doing anything else when the tool is available. - -- Rules: -- Start with `graphify query ""` when graphify state exists. Use - `graphify path "" ""` for relationships and - `graphify explain ""` for focused concepts. -- Dirty `graphify-out/` files after incremental updates are not a reason to skip - graphify by default. -- If `graphify-out/wiki/index.md` exists, use it for broad navigation before raw - source scanning. -- Read `graphify-out/GRAPH_REPORT.md` only for broad architecture review or when - `query`, `path`, and `explain` do not provide enough context. +- Use graphify first for codebase, project, architecture, file-relationship, or + content-orientation questions when `graphify-out/graph.json` exists or graphify + is available. +- Start with `graphify query ""` when graphify state exists. - If graphify is unavailable or has no useful state, state the gap briefly and use the best local fallback. -- After modifying code, run `graphify update .` when graphify is available and - repository state exists. `` @@ -137,18 +92,13 @@ before doing anything else when the tool is available. - Do not duplicate skill-owned paths, templates, workflow states, or command examples in this file. -- Keep volatile inventory out of this file; link to `.github/INVENTORY.md` - instead of copying catalog entries. -- Target repositories should express local exceptions in their own nearest valid - owner files. +- Keep volatile inventory out of this file; `.github/INVENTORY.md` owns the live + catalog for this repository. ## Purpose -- Orient coding agents before they read narrower files. -- Keep stable policy separate from volatile inventory; `.github/INVENTORY.md` - owns the exact live catalog of managed AI assets. -- Keep architecture and local context in `docs/architecture.md` and - `docs/repository-context.md`. +- Keep standards-repository-only policy separate from the shared baseline. +- Keep architecture and local context in `docs/` knowledge documents. This block applies only to this standards repository. Do not treat these rules as consumer-repository defaults without an explicit sync contract change. @@ -157,32 +107,20 @@ as consumer-repository defaults without an explicit sync contract change. - This repository owns the shared Copilot customization baseline, governance contracts, catalog automation, source-side sync tooling, and the source - baseline content used to generate the global home AGENTS policy. -- Source-managed AI assets live mainly under `.github/`; local knowledge - documents live in `docs/README.md`, `docs/repository-context.md`, - `docs/architecture.md`, `docs/tech.md`, and `docs/structure.md`. -- Source-side sync command centers and sync support skills own propagation behavior. Keep their procedures in the owning sync assets, not in root policy. -- Keep consumer-facing defaults target-agnostic. Do not encode this repository's - local paths, validators, or workflow checkpoints into shared guidance unless - the sync contract deliberately makes them shared. + content used to generate the global home agent baseline. +- Source-managed AI assets live mainly under `.github/`. ## Standards Repository Validation - Run `make token-risks` or `python3 ./.github/scripts/detect_token_risks.py --root .` after changes that - affect always-on guidance or major AI assets in this repository. -- Run the closest source-side validator for changed governance contracts, catalog - families, sync behavior, or AI runtime assets. -- Address actionable source-side findings before declaring this repository's AI - configuration complete. + affect root policy or major AI assets in this repository. ## Standards Repository Locality - Repo-local planning, brainstorming, temporary analysis, and working artifacts stay outside `docs/` unless a narrower owner explicitly says otherwise. -- Consumer repositories may receive local scaffolds or override layers, but those - target-local files remain owned by the consumer after materialization. -- Treat this block as source-local policy until sync automation or the sync - contract gains explicit support for excluding or transforming it. +- Consumer or target repositories own their local override layers after + materialization. `` From 3bbfa20730318d02b0a5b948694ef926b76dab3c Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 15:36:39 +0200 Subject: [PATCH 03/44] feat: add .superpowers to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ce63aaa..0f44666 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ env/ .github/scripts/lib/graphify-out/* .github/scripts/graphify-out/* tmp/ +.superpowers From 9e3218a2411211532daab3bddaae790dcf65c66b Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 15:34:53 +0200 Subject: [PATCH 04/44] docs: split global baseline from local rules --- AGENTS.md | 116 +++++++++++++----------------------------------------- 1 file changed, 27 insertions(+), 89 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8797adb..375ca95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,16 +25,10 @@ This block is the portable source baseline used to generate aliases, or past automation unless it exists on disk and is deliberately reintroduced. -## Progressive Disclosure - -- Load only the owner, skill, contract, or documentation surface required for - the current lane. -- Expand context only after bounded evidence leaves a material gap. - ## User Alignment -- For small, deterministic, low-risk tasks, proceed after confirming target, - owner, and validation path. +- For small, deterministic, low-risk tasks, proceed after identifying the + target, nearest owner, and validation path. - For non-trivial, ambiguous, architectural, policy, contract, or multi-step work, align with the user before implementation. @@ -44,8 +38,7 @@ This block is the portable source baseline used to generate validation path before broad commands. - Make surgical changes. Preserve user work, avoid unrelated refactors, and tie each edit to the requested outcome. -- Fix the controlling root cause where practical instead of layering - workaround-only changes. +- Fix the controlling issue where practical instead of layering workarounds. - Work toward verified outcomes. Run the closest available validation and report explicit gaps. @@ -58,78 +51,40 @@ This block is the portable source baseline used to generate ## Authoring Defaults -- Use Plain Technical English for repository-owned prose. +- Use Plain Technical English for repository-owned prose unless a narrower owner + explicitly overrides it. - Prefer short sentences, stable terms, active voice, and explicit `must`, `should`, and `may` wording. -- Keep required technical names unchanged, including paths, commands, schema - fields, product names, asset names, and established repository terms. -- The default authoring language for repository artifacts is English unless a - narrower owned file, skill, or local exception explicitly overrides it. -- User chat may use the user's language; repository-owned artifacts follow the - language rules of their closest owner. -- Keep repository-owned AI configuration files as Markdown. Use XML only as - runtime prompt-assembly delimiters, never as source format. -- XML-style code-span markers may delimit Markdown scope blocks when they clarify - runtime, sync, or locality boundaries. -- Do not rewrite imported upstream text that must remain verbatim. -- For vendor-owned or schema-driven surfaces, read primary documentation when - correctness depends on platform semantics. +- Keep required technical names unchanged. ## Tactical Defaults - Preserve compact working state across turns; avoid rebuilding full context unless new evidence invalidates the current state. +- Keep one active primary owner per execution lane; load narrower owners only + when path, runtime, symptom, or validation evidence proves they are needed. - Use bounded evidence: inspect changed sections and failing-validator context first, then expand only when gaps remain. +- Name the validation path early; if evidence changes it, update the working + assumption before editing. ## Delivery And Validation -- Use least privilege and never hardcode secrets, credentials, keys, tokens, or - sensitive tenant values. - Reason from repository evidence. Do not invent runtimes, validators, sync flows, or tests. -- Prefer the simplest correct change with the smallest credible blast radius. -- For non-trivial repository-owned work, make target state, anti-scope, - assumptions, tradeoffs, and validation path visible before delivery or - handoff. -- Run the applicable validation that exists for changed files. When no dedicated - validator exists, report the gap and use the closest check. - -## Contract Discipline - -- When a contract, policy, or ownership rule changes, align the owning tests, - validators, and technical docs to the new behavior. -- Do not let stale checks or historical wording force a reverted model. - -## Token And Drift Control - -- `AGENTS.md` is the canonical always-on policy surface; keep it near a 4,000 - estimated-token soft target measured as `ceil(UTF-8 bytes / 4)` by the - validator. +- For non-trivial work, make target state, anti-scope, assumptions, tradeoffs, + and validation path visible before implementation or handoff. +- When a contract or policy changes, align the owning tests, validators, or docs + instead of letting stale checks restore the old behavior. ## graphify -Use graphify first for codebase, project, architecture, file-relationship, or -content-orientation questions when `graphify-out/graph.json` exists or graphify -is available. - -When the user types `/graphify`, invoke the `skill` tool with `skill: "graphify"` -before doing anything else when the tool is available. - -- Rules: -- Start with `graphify query ""` when graphify state exists. Use - `graphify path "" ""` for relationships and - `graphify explain ""` for focused concepts. -- Dirty `graphify-out/` files after incremental updates are not a reason to skip - graphify by default. -- If `graphify-out/wiki/index.md` exists, use it for broad navigation before raw - source scanning. -- Read `graphify-out/GRAPH_REPORT.md` only for broad architecture review or when - `query`, `path`, and `explain` do not provide enough context. +- Use graphify first for codebase, project, architecture, file-relationship, or + content-orientation questions when `graphify-out/graph.json` exists or graphify + is available. +- Start with `graphify query ""` when graphify state exists. - If graphify is unavailable or has no useful state, state the gap briefly and use the best local fallback. -- After modifying code, run `graphify update .` when graphify is available and - repository state exists. `` @@ -137,18 +92,13 @@ before doing anything else when the tool is available. - Do not duplicate skill-owned paths, templates, workflow states, or command examples in this file. -- Keep volatile inventory out of this file; link to `.github/INVENTORY.md` - instead of copying catalog entries. -- Target repositories should express local exceptions in their own nearest valid - owner files. +- Keep volatile inventory out of this file; `.github/INVENTORY.md` owns the live + catalog for this repository. ## Purpose -- Orient coding agents before they read narrower files. -- Keep stable policy separate from volatile inventory; `.github/INVENTORY.md` - owns the exact live catalog of managed AI assets. -- Keep architecture and local context in `docs/architecture.md` and - `docs/repository-context.md`. +- Keep standards-repository-only policy separate from the shared baseline. +- Keep architecture and local context in `docs/` knowledge documents. This block applies only to this standards repository. Do not treat these rules as consumer-repository defaults without an explicit sync contract change. @@ -157,32 +107,20 @@ as consumer-repository defaults without an explicit sync contract change. - This repository owns the shared Copilot customization baseline, governance contracts, catalog automation, source-side sync tooling, and the source - baseline content used to generate the global home AGENTS policy. -- Source-managed AI assets live mainly under `.github/`; local knowledge - documents live in `docs/README.md`, `docs/repository-context.md`, - `docs/architecture.md`, `docs/tech.md`, and `docs/structure.md`. -- Source-side sync command centers and sync support skills own propagation behavior. Keep their procedures in the owning sync assets, not in root policy. -- Keep consumer-facing defaults target-agnostic. Do not encode this repository's - local paths, validators, or workflow checkpoints into shared guidance unless - the sync contract deliberately makes them shared. + content used to generate the global home agent baseline. +- Source-managed AI assets live mainly under `.github/`. ## Standards Repository Validation - Run `make token-risks` or `python3 ./.github/scripts/detect_token_risks.py --root .` after changes that - affect always-on guidance or major AI assets in this repository. -- Run the closest source-side validator for changed governance contracts, catalog - families, sync behavior, or AI runtime assets. -- Address actionable source-side findings before declaring this repository's AI - configuration complete. + affect root policy or major AI assets in this repository. ## Standards Repository Locality - Repo-local planning, brainstorming, temporary analysis, and working artifacts stay outside `docs/` unless a narrower owner explicitly says otherwise. -- Consumer repositories may receive local scaffolds or override layers, but those - target-local files remain owned by the consumer after materialization. -- Treat this block as source-local policy until sync automation or the sync - contract gains explicit support for excluding or transforming it. +- Consumer or target repositories own their local override layers after + materialization. `` From af298c349293d704d7089753089a623910a7109b Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 16:10:11 +0200 Subject: [PATCH 05/44] docs: align root policy contract and bridge checks --- .github/scripts/lib/catalog_checks.py | 2 ++ INTERNAL_CONTRACT.md | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/scripts/lib/catalog_checks.py b/.github/scripts/lib/catalog_checks.py index b392010..933deef 100644 --- a/.github/scripts/lib/catalog_checks.py +++ b/.github/scripts/lib/catalog_checks.py @@ -201,6 +201,8 @@ def check_bridge_references(root: Path) -> list[Finding]: agents_path = root / "AGENTS.md" copilot_path = root / ".github/copilot-instructions.md" if agents_path.exists(): + # Inventory anchoring is a repository-root AGENTS requirement, not a + # shared-baseline extraction requirement. agents_text = read_text(agents_path) if ".github/INVENTORY.md" not in agents_text: findings.append( diff --git a/INTERNAL_CONTRACT.md b/INTERNAL_CONTRACT.md index e23119a..b88800e 100644 --- a/INTERNAL_CONTRACT.md +++ b/INTERNAL_CONTRACT.md @@ -37,9 +37,9 @@ Treat the current skill-first architecture as the source of truth. Do not infer - `INTERNAL_CONTRACT.md` - source-side contract tests - Expected behavior: - - root `AGENTS.md` may carry a portable `` block that is the source content for the global `~/.agents/AGENTS.md` baseline - - root `AGENTS.md` may carry a source-local `` block that remains explicitly non-portable by default - - root `AGENTS.md` may keep compact graph orientation rules when those rules are deliberately part of the declared shared baseline + - root `AGENTS.md` may carry a portable `` block that serves as source content for the generated global `~/.agents/AGENTS.md` baseline + - root `AGENTS.md` may carry a source-local `` block that remains non-portable by default + - root `AGENTS.md` may keep compact graph orientation rules in the shared baseline when those rules are globally safe and conditionally worded - tests and validators must align to the current on-disk root-policy shape instead of assuming a separate `## Context Routing` section or the absence of root-level graph guidance #### `skill-first-inventory-is-externalized` From 7170c9ea2ff1f11d38f31c982ca9bc935b953556 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 16:11:10 +0200 Subject: [PATCH 06/44] feat: add executing plan state files --- .../superpowers-executing-plans/SKILL.md | 72 +++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/.github/skills/superpowers-executing-plans/SKILL.md b/.github/skills/superpowers-executing-plans/SKILL.md index c8c569e..3f90b53 100644 --- a/.github/skills/superpowers-executing-plans/SKILL.md +++ b/.github/skills/superpowers-executing-plans/SKILL.md @@ -7,19 +7,66 @@ description: Use when you have a written implementation plan to execute in a sep ## Overview -Load plan, review critically, execute all tasks, report when complete. +Load plan, review critically, execute tasks, write a plan state file for every terminal outcome, and report when complete. **Announce at start:** "I'm using the executing-plans skill to implement this plan." **Note:** Tell your human partner that Superpowers works much better with access to subagents. The quality of its work will be significantly higher if run on a platform with subagent support (Claude Code, Codex CLI, Codex App, and Copilot CLI all qualify; see the per-platform tool refs in `../using-superpowers/references/`). If subagents are available, use superpowers-subagent-driven-development instead of this skill. +## Plan State File Contract + +Whenever execution reaches a terminal stop, write a compact Markdown state file next to the original plan file. + +**Filename:** `..md` + +- `` is the plan filename without the final `.md` suffix. +- `` must be one of `DONE`, `BLOCKED`, `PARTIAL`, or `NEEDS_REVIEW`. +- Before writing a new state file, remove or replace stale sibling state files for the same plan basename with one of the supported status suffixes. +- Keep the file short, normally under 40 lines. +- The main session owns the final state file, even when subagents contribute. + +**Status meanings:** +- `DONE`: all planned work is complete, required verification has passed or gaps are explicitly accepted, and finishing is complete. +- `PARTIAL`: meaningful work is complete, but execution cannot be declared done because validation, finishing, or remaining plan work is unresolved. +- `BLOCKED`: execution cannot continue without user input, access, dependency, clearer instructions, or repair of a repeated non-repairable failure. +- `NEEDS_REVIEW`: initial plan review found critical gaps before implementation starts, or the plan needs user correction before safe execution. + +**Required file shape:** + +````markdown +# - + +## Status + + +## Reason + + +## Completed +- + +## Remaining +- + +## Validation +- + +## Next + + +## Resume Notes + +```` + +Include only commands actually run. If validation was not run, say why. + ## The Process ### Step 1: Load and Review Plan 1. Read plan file 2. Review critically - identify any questions or concerns about the plan -3. If concerns: Raise them with your human partner before starting -4. If no concerns: Create todos for the plan items and proceed +3. If the plan has critical gaps before implementation: write a `NEEDS_REVIEW` state file, report the path, and raise the concern with your human partner before starting +4. If no blocking concerns remain: Create todos for the plan items and proceed ### Step 2: Execute Tasks @@ -36,6 +83,17 @@ After all tasks complete and verified: - **REQUIRED SUB-SKILL:** Use superpowers-finishing-a-development-branch - Follow that skill to verify tests, present options, execute choice +### Step 4: Write Plan State File + +At every terminal stop: +1. Determine the final status from the execution outcome. +2. Build the target state filename from the plan path and status. +3. Remove or replace stale sibling state files matching the same plan basename and a supported status suffix. +4. Write the compact state file using the required shape above. +5. Report the state file path in the final chat response. + +Use `DONE` only after the finishing workflow is complete. If task work is complete but finishing is not complete, use `PARTIAL` unless the human partner explicitly accepts the remaining gap. + ## When to Stop and Ask for Help **STOP executing immediately when:** @@ -44,7 +102,10 @@ After all tasks complete and verified: - You don't understand an instruction - Verification fails repeatedly -**Ask for clarification rather than guessing.** +Before asking for clarification at a terminal stop, write the appropriate plan state file: +- Use `NEEDS_REVIEW` when the blocker is a plan-quality issue discovered before implementation. +- Use `BLOCKED` when execution cannot proceed without input, access, dependencies, or clearer instructions. +- Use `PARTIAL` when some execution work is complete but the plan cannot be declared done. ## When to Revisit Earlier Steps @@ -60,6 +121,9 @@ After all tasks complete and verified: - Don't skip verifications - Reference skills when plan says to - Stop when blocked, don't guess +- Always write a plan state file before ending at a terminal stop +- Report the plan state file path in the final response +- `DONE` requires completed finishing, not only completed task checkboxes - Never start implementation on main/master branch without explicit user consent ## Integration From 5f5a66f1aac2c3e9b7afdedc9a799ff7c40adc98 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 16:13:56 +0200 Subject: [PATCH 07/44] test: align root policy boundary assertions --- ...est_repository_workflow_policy_contract.py | 16 +++++++++----- tests/test_sync_and_token_risks.py | 21 ++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/tests/test_repository_workflow_policy_contract.py b/tests/test_repository_workflow_policy_contract.py index deec9cb..a159792 100644 --- a/tests/test_repository_workflow_policy_contract.py +++ b/tests/test_repository_workflow_policy_contract.py @@ -58,6 +58,7 @@ def test_github_pr_skill_owns_pr_merge_and_terminal_state_guardrails() -> None: def test_root_files_define_scoped_instruction_loading_for_manual_runtimes() -> None: agents_text = read_text("AGENTS.md") copilot_text = read_text(".github/copilot-instructions.md") + internal_contract_text = read_text("INTERNAL_CONTRACT.md") assert "## First Move" in agents_text assert "## Precedence" in agents_text @@ -74,8 +75,11 @@ def test_root_files_define_scoped_instruction_loading_for_manual_runtimes() -> N assert "``" in agents_text assert "portable source baseline" in agents_text assert "`~/.agents/AGENTS.md`" in agents_text + assert ( + "global `~/.agents/AGENTS.md` baseline" in internal_contract_text + or "global home agent baseline" in agents_text + ) assert "``" in agents_text - assert "This block applies only to this standards repository." in agents_text assert "It is not a general task-execution guide" in copilot_text assert "Do not ask the author to follow local runtime workflows" in copilot_text @@ -92,11 +96,13 @@ def test_agents_keeps_only_intentional_root_level_workflows() -> None: assert "tool-specific workflows here" in agents_text assert "Do not duplicate skill-owned paths" in agents_text - assert "## graphify" in agents_text - assert "Use graphify first" in agents_text - assert "`graphify-out/graph.json` exists or graphify" in agents_text - assert 'graphify query ""' in agents_text + assert ( + "Use graphify first" in agents_text + or "use graphify as the first orientation tool" in agents_text + ) + assert "graphify-out/graph.json" in agents_text assert "If graphify is unavailable or has no useful state" in agents_text + assert "best local fallback" in agents_text def test_internal_contract_tracks_current_root_policy_shape() -> None: diff --git a/tests/test_sync_and_token_risks.py b/tests/test_sync_and_token_risks.py index b486ddb..e9fe2f9 100644 --- a/tests/test_sync_and_token_risks.py +++ b/tests/test_sync_and_token_risks.py @@ -1480,22 +1480,27 @@ def test_sync_contract_restricts_allow_dirty_target_to_overlap_checked_work() -> def test_root_always_on_token_budget_contract_uses_validator_constants() -> None: agents_text = Path("AGENTS.md").read_text(encoding="utf-8") - shared_baseline = agents_text.split("``", 1)[1].split( - "``", 1 - )[0] + validator_text = Path(".github/scripts/lib/token_risks.py").read_text( + encoding="utf-8" + ) local_rules = agents_text.split("``", 1)[1].split( "``", 1 )[0] - target_text = f"{ROOT_ALWAYS_ON_TOKEN_TARGET:,}" calculated_estimates = { file_path: estimate_tokens(Path(file_path)) for file_path in ROOT_ALWAYS_ON_PATHS } assert "## Estimated Fixed-Load Token Budget" not in agents_text - assert "`AGENTS.md` is the canonical always-on policy surface" in agents_text - assert target_text in agents_text - assert "soft target" in agents_text - assert "`make token-risks`" not in shared_baseline + assert "primary always-on repository policy entrypoint" in agents_text + assert "ROOT_ALWAYS_ON_PATHS = (\"AGENTS.md\",)" in validator_text + assert ( + f"ROOT_ALWAYS_ON_TOKEN_TARGET = {ROOT_ALWAYS_ON_TOKEN_TARGET}" + in validator_text + ) + assert "AGENTS.md exceeds the canonical always-on " in validator_text + assert "soft target" in validator_text + # `make token-risks` is repository-local validation guidance in AGENTS.md. assert "`make token-risks`" in local_rules + assert "`make token-risks`" in agents_text assert set(calculated_estimates) == set(ROOT_ALWAYS_ON_PATHS) From 33a14a9c8bb0b5719a631cb2c97cfb2eb42a0368 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 16:17:06 +0200 Subject: [PATCH 08/44] test: enforce AGENTS block boundary assertions --- ...est_repository_workflow_policy_contract.py | 21 ++++++++++++++----- tests/test_sync_and_token_risks.py | 8 ++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/tests/test_repository_workflow_policy_contract.py b/tests/test_repository_workflow_policy_contract.py index a159792..27802ff 100644 --- a/tests/test_repository_workflow_policy_contract.py +++ b/tests/test_repository_workflow_policy_contract.py @@ -15,6 +15,12 @@ def section_between(body: str, heading: str) -> str: return section.split("\n## ", 1)[0] +def tagged_block(body: str, tag: str) -> str: + start = f"`<{tag}>`" + end = f"``" + return body.split(start, 1)[1].split(end, 1)[0] + + def python_fenced_blocks(body: str) -> list[str]: return re.findall(r"```python\n(.*?)\n```", body, flags=re.DOTALL) @@ -93,16 +99,21 @@ def test_root_files_define_scoped_instruction_loading_for_manual_runtimes() -> N def test_agents_keeps_only_intentional_root_level_workflows() -> None: agents_text = read_text("AGENTS.md") + shared_baseline = tagged_block(agents_text, "shared-baseline") + local_rules = tagged_block(agents_text, "standards-repository-local-rules") assert "tool-specific workflows here" in agents_text assert "Do not duplicate skill-owned paths" in agents_text assert ( - "Use graphify first" in agents_text - or "use graphify as the first orientation tool" in agents_text + "Use graphify first" in shared_baseline + or "use graphify as the first orientation tool" in shared_baseline ) - assert "graphify-out/graph.json" in agents_text - assert "If graphify is unavailable or has no useful state" in agents_text - assert "best local fallback" in agents_text + assert "## graphify" in shared_baseline + assert "graphify-out/graph.json" in shared_baseline + assert "If graphify is unavailable or has no useful state" in shared_baseline + assert "best local fallback" in shared_baseline + assert "## graphify" not in local_rules + assert "graphify-out/graph.json" not in local_rules def test_internal_contract_tracks_current_root_policy_shape() -> None: diff --git a/tests/test_sync_and_token_risks.py b/tests/test_sync_and_token_risks.py index e9fe2f9..7585485 100644 --- a/tests/test_sync_and_token_risks.py +++ b/tests/test_sync_and_token_risks.py @@ -1483,6 +1483,9 @@ def test_root_always_on_token_budget_contract_uses_validator_constants() -> None validator_text = Path(".github/scripts/lib/token_risks.py").read_text( encoding="utf-8" ) + shared_baseline = agents_text.split("``", 1)[1].split( + "``", 1 + )[0] local_rules = agents_text.split("``", 1)[1].split( "``", 1 )[0] @@ -1500,7 +1503,10 @@ def test_root_always_on_token_budget_contract_uses_validator_constants() -> None ) assert "AGENTS.md exceeds the canonical always-on " in validator_text assert "soft target" in validator_text - # `make token-risks` is repository-local validation guidance in AGENTS.md. + # Local validator commands stay in standards-repository-local-rules. + assert "`make token-risks`" not in shared_baseline + assert "`python3 ./.github/scripts/detect_token_risks.py --root .`" not in shared_baseline assert "`make token-risks`" in local_rules + assert "`python3 ./.github/scripts/detect_token_risks.py --root .`" in local_rules assert "`make token-risks`" in agents_text assert set(calculated_estimates) == set(ROOT_ALWAYS_ON_PATHS) From 87977c0d2346b38dbeb0911d8a3e907c56d5d449 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 16:26:36 +0200 Subject: [PATCH 09/44] docs: enhance AGENTS.md with detailed graphify usage instructions --- AGENTS.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 375ca95..c6a2989 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,12 +79,21 @@ This block is the portable source baseline used to generate ## graphify -- Use graphify first for codebase, project, architecture, file-relationship, or - content-orientation questions when `graphify-out/graph.json` exists or graphify - is available. -- Start with `graphify query ""` when graphify state exists. -- If graphify is unavailable or has no useful state, state the gap briefly and - use the best local fallback. +For any question about this repo's architecture, structure, components, or how to add/modify/find +code, your first action should be `graphify query ""` when `graphify-out/graph.json` +exists. Use `graphify path "" ""` for relationship questions and `graphify explain ""` +for focused-concept questions. These return a scoped subgraph, usually much smaller than the full +report or raw grep output. + +Triggers: "how do I…", "where is…", "what does … do", "add/modify a ", +"explain the architecture", or anything that depends on how files or classes relate. + +If `graphify-out/wiki/index.md` exists, use it for broad navigation. Read `graphify-out/GRAPH_REPORT.md` +only for broad architecture review or when query/path/explain do not surface enough context. Only read +source files when (a) modifying/debugging specific code, (b) the graph lacks the needed detail, or +(c) the graph is missing or stale. + +Type `/graphify` in Copilot Chat to build or update the graph. `` From fedaee94fffcdef3effe9e148a23af60a4856c07 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 22:43:10 +0200 Subject: [PATCH 10/44] fix: update LICENSE and YAML files for consistency; enhance skill descriptions and prompts --- .github/skills/openai-docx/LICENSE.txt | 2 +- .github/skills/openai-docx/agents/openai.yaml | 2 +- .../skills/openai-docx/scripts/render_docx.py | 7 ++ .github/skills/openai-gh-fix-ci/SKILL.md | 2 +- .github/skills/openai-pdf/SKILL.md | 2 +- .../skills/openai-slides/agents/openai.yaml | 2 +- .../openai-spreadsheet/agents/openai.yaml | 2 +- .../skills/superpowers-brainstorming/SKILL.md | 16 ++--- .../spec-document-reviewer-prompt.md | 2 +- .../superpowers-executing-plans/SKILL.md | 72 ++----------------- .../SKILL.md | 12 ++-- .../SKILL.md | 2 +- .../SKILL.md | 7 +- .../SKILL.md | 7 -- .../skills/superpowers-writing-plans/SKILL.md | 10 +-- 15 files changed, 35 insertions(+), 112 deletions(-) mode change 100644 => 100755 .github/skills/openai-docx/scripts/render_docx.py diff --git a/.github/skills/openai-docx/LICENSE.txt b/.github/skills/openai-docx/LICENSE.txt index b20752a..13e25df 100644 --- a/.github/skills/openai-docx/LICENSE.txt +++ b/.github/skills/openai-docx/LICENSE.txt @@ -179,7 +179,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include + replaced with your own identifying information. (Don\'t include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the diff --git a/.github/skills/openai-docx/agents/openai.yaml b/.github/skills/openai-docx/agents/openai.yaml index 51b5bd7..27ce451 100644 --- a/.github/skills/openai-docx/agents/openai.yaml +++ b/.github/skills/openai-docx/agents/openai.yaml @@ -3,4 +3,4 @@ interface: short_description: "Edit and review docx files" icon_small: "./assets/doc-small.svg" icon_large: "./assets/doc.png" - default_prompt: "Use $openai-docx to edit or review this .docx file and return the updated file plus a concise change summary." + default_prompt: "Edit or review this .docx file and return the updated file plus a concise change summary." diff --git a/.github/skills/openai-docx/scripts/render_docx.py b/.github/skills/openai-docx/scripts/render_docx.py old mode 100644 new mode 100755 index b552a1c..907ec89 --- a/.github/skills/openai-docx/scripts/render_docx.py +++ b/.github/skills/openai-docx/scripts/render_docx.py @@ -40,6 +40,7 @@ def calc_dpi_via_ooxml_docx(input_path: str, max_w_px: int, max_h_px: int) -> in root = ET.fromstring(xml) ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} + # Common placements: w:body/w:sectPr or w:body/w:p/w:pPr/w:sectPr sect_pr = root.find(".//w:sectPr", ns) if sect_pr is None: raise RuntimeError("Section properties not found in document.xml") @@ -47,6 +48,7 @@ def calc_dpi_via_ooxml_docx(input_path: str, max_w_px: int, max_h_px: int) -> in if pg_sz is None: raise RuntimeError("Page size not found in section properties") + # Values are in twips w_twips_str = pg_sz.get( "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}w" ) or pg_sz.get("w") @@ -111,6 +113,7 @@ def convert_to_pdf( convert_tmp_dir: str, stem: str, ) -> str: + # Try direct DOC(X) -> PDF cmd_pdf = [ "soffice", "-env:UserInstallation=file://" + user_profile, @@ -129,6 +132,7 @@ def convert_to_pdf( if exists(pdf_path): return pdf_path + # Fallback: DOCX -> ODT, then ODT -> PDF cmd_odt = [ "soffice", "-env:UserInstallation=file://" + user_profile, @@ -178,7 +182,9 @@ def rasterize( doc_path = abspath(doc_path) stem = splitext(basename(doc_path))[0] + # Use a unique user profile to avoid LibreOffice profile lock when running concurrently with tempfile.TemporaryDirectory(prefix="soffice_profile_") as user_profile: + # Write conversion outputs into a temp directory to avoid any IO oddities with tempfile.TemporaryDirectory(prefix="soffice_convert_") as convert_tmp_dir: pdf_path = convert_to_pdf( doc_path, @@ -204,6 +210,7 @@ def rasterize( ), ) + # Rename convert_from_path's output format f'page{thread_id:04d}-{page_num:02d}.' to 'page-.' pages: list[tuple[int, str]] = [] for src_path in paths_raw: base = splitext(basename(src_path))[0] diff --git a/.github/skills/openai-gh-fix-ci/SKILL.md b/.github/skills/openai-gh-fix-ci/SKILL.md index 4cc438a..42e55f5 100644 --- a/.github/skills/openai-gh-fix-ci/SKILL.md +++ b/.github/skills/openai-gh-fix-ci/SKILL.md @@ -1,5 +1,5 @@ --- -name: openai-gh-fix-ci +name: "openai-gh-fix-ci" description: "Use when a user asks to debug or fix failing GitHub PR checks that run in GitHub Actions; use `gh` to inspect checks and logs, summarize failure context, draft a fix plan, and implement only after explicit approval. Treat external providers (for example Buildkite) as out of scope and report only the details URL." --- diff --git a/.github/skills/openai-pdf/SKILL.md b/.github/skills/openai-pdf/SKILL.md index f1ca081..3e18b07 100644 --- a/.github/skills/openai-pdf/SKILL.md +++ b/.github/skills/openai-pdf/SKILL.md @@ -1,5 +1,5 @@ --- -name: openai-pdf +name: "openai-pdf" description: "Use when tasks involve reading, creating, or reviewing PDF files where rendering and layout matter; prefer visual checks by rendering pages (Poppler) and use Python tools such as `reportlab`, `pdfplumber`, and `pypdf` for generation and extraction." --- diff --git a/.github/skills/openai-slides/agents/openai.yaml b/.github/skills/openai-slides/agents/openai.yaml index 453b976..6f77e49 100644 --- a/.github/skills/openai-slides/agents/openai.yaml +++ b/.github/skills/openai-slides/agents/openai.yaml @@ -3,4 +3,4 @@ interface: short_description: "Create and edit PPTX slide decks" icon_small: "./assets/slides-small.svg" icon_large: "./assets/slides.png" - default_prompt: "Use $openai-slides to create or update this PPTX slide deck with PptxGenJS and validate the layout." + default_prompt: "Use $slides to create or update this PPTX slide deck with PptxGenJS and validate the layout." diff --git a/.github/skills/openai-spreadsheet/agents/openai.yaml b/.github/skills/openai-spreadsheet/agents/openai.yaml index 5e6fa50..c4a670e 100644 --- a/.github/skills/openai-spreadsheet/agents/openai.yaml +++ b/.github/skills/openai-spreadsheet/agents/openai.yaml @@ -3,4 +3,4 @@ interface: short_description: "Create, edit, and analyze spreadsheets" icon_small: "./assets/spreadsheet-small.svg" icon_large: "./assets/spreadsheet.png" - default_prompt: "Use $openai-spreadsheet to create or update a spreadsheet for this task with the right formulas, structure, and formatting." + default_prompt: "Use $spreadsheet to create or update a spreadsheet for this task with the right formulas, structure, and formatting." diff --git a/.github/skills/superpowers-brainstorming/SKILL.md b/.github/skills/superpowers-brainstorming/SKILL.md index fff9d7d..97df886 100644 --- a/.github/skills/superpowers-brainstorming/SKILL.md +++ b/.github/skills/superpowers-brainstorming/SKILL.md @@ -1,6 +1,6 @@ --- name: superpowers-brainstorming -description: "Use before creative or design-ambiguous work that needs option exploration and design approval before implementation." +description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation." --- # Brainstorming Ideas Into Designs @@ -9,17 +9,13 @@ Help turn ideas into fully formed designs and specs through natural collaborativ Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval. -Use this workflow only when the work still needs option exploration, design decisions, or explicit user approval before implementation. - -Do not use this workflow for deterministic repository-owned maintenance of prompt, skill, agent, instruction, or Markdown assets when the target state is already clear and concrete verification exists. - -Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies when the task is creative or design-ambiguous enough that the implementation path is not already settled. +Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity. -## Anti-Pattern: "Every Change Needs Brainstorming" +## Anti-Pattern: "This Is Too Simple To Need A Design" -Not every change needs this process. Use it for creative work, unclear requirements, or design ambiguity. Deterministic maintenance, governed text realignment, and other already-designed updates should skip brainstorming and move straight to the right execution or review workflow. +Every project goes through this process. A todo list, a single-function utility, a config change — all of them. "Simple" projects are where unexamined assumptions cause the most wasted work. The design can be short (a few sentences for truly simple projects), but you MUST present it and get approval. ## Checklist @@ -30,7 +26,7 @@ You MUST create a task for each of these items and complete them in order: 3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria 4. **Propose 2-3 approaches** — with trade-offs and your recommendation 5. **Present design** — in sections scaled to their complexity, get user approval after each section -6. **Write design doc** — save to `tmp/superpowers/specs/YYYY-MM-DD--design.md` and commit +6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD--design.md` and commit 7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) 8. **User reviews written spec** — ask user to review the spec file before proceeding 9. **Transition to implementation** — invoke writing-plans skill to create implementation plan @@ -107,7 +103,7 @@ digraph brainstorming { **Documentation:** -- Write the validated design (spec) to `tmp/superpowers/specs/YYYY-MM-DD--design.md` +- Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD--design.md` - (User preferences for spec location override this default) - Use elements-of-style:writing-clearly-and-concisely skill if available - Commit the design document to git diff --git a/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md b/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md index 08e54f8..6099312 100644 --- a/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md +++ b/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md @@ -4,7 +4,7 @@ Use this template when dispatching a spec document reviewer subagent. **Purpose:** Verify the spec is complete, consistent, and ready for implementation planning. -**Dispatch after:** Spec document is written to tmp/superpowers/specs/ +**Dispatch after:** Spec document is written to docs/superpowers/specs/ ``` Subagent (general-purpose): diff --git a/.github/skills/superpowers-executing-plans/SKILL.md b/.github/skills/superpowers-executing-plans/SKILL.md index 3f90b53..c8c569e 100644 --- a/.github/skills/superpowers-executing-plans/SKILL.md +++ b/.github/skills/superpowers-executing-plans/SKILL.md @@ -7,66 +7,19 @@ description: Use when you have a written implementation plan to execute in a sep ## Overview -Load plan, review critically, execute tasks, write a plan state file for every terminal outcome, and report when complete. +Load plan, review critically, execute all tasks, report when complete. **Announce at start:** "I'm using the executing-plans skill to implement this plan." **Note:** Tell your human partner that Superpowers works much better with access to subagents. The quality of its work will be significantly higher if run on a platform with subagent support (Claude Code, Codex CLI, Codex App, and Copilot CLI all qualify; see the per-platform tool refs in `../using-superpowers/references/`). If subagents are available, use superpowers-subagent-driven-development instead of this skill. -## Plan State File Contract - -Whenever execution reaches a terminal stop, write a compact Markdown state file next to the original plan file. - -**Filename:** `..md` - -- `` is the plan filename without the final `.md` suffix. -- `` must be one of `DONE`, `BLOCKED`, `PARTIAL`, or `NEEDS_REVIEW`. -- Before writing a new state file, remove or replace stale sibling state files for the same plan basename with one of the supported status suffixes. -- Keep the file short, normally under 40 lines. -- The main session owns the final state file, even when subagents contribute. - -**Status meanings:** -- `DONE`: all planned work is complete, required verification has passed or gaps are explicitly accepted, and finishing is complete. -- `PARTIAL`: meaningful work is complete, but execution cannot be declared done because validation, finishing, or remaining plan work is unresolved. -- `BLOCKED`: execution cannot continue without user input, access, dependency, clearer instructions, or repair of a repeated non-repairable failure. -- `NEEDS_REVIEW`: initial plan review found critical gaps before implementation starts, or the plan needs user correction before safe execution. - -**Required file shape:** - -````markdown -# - - -## Status - - -## Reason - - -## Completed -- - -## Remaining -- - -## Validation -- - -## Next - - -## Resume Notes - -```` - -Include only commands actually run. If validation was not run, say why. - ## The Process ### Step 1: Load and Review Plan 1. Read plan file 2. Review critically - identify any questions or concerns about the plan -3. If the plan has critical gaps before implementation: write a `NEEDS_REVIEW` state file, report the path, and raise the concern with your human partner before starting -4. If no blocking concerns remain: Create todos for the plan items and proceed +3. If concerns: Raise them with your human partner before starting +4. If no concerns: Create todos for the plan items and proceed ### Step 2: Execute Tasks @@ -83,17 +36,6 @@ After all tasks complete and verified: - **REQUIRED SUB-SKILL:** Use superpowers-finishing-a-development-branch - Follow that skill to verify tests, present options, execute choice -### Step 4: Write Plan State File - -At every terminal stop: -1. Determine the final status from the execution outcome. -2. Build the target state filename from the plan path and status. -3. Remove or replace stale sibling state files matching the same plan basename and a supported status suffix. -4. Write the compact state file using the required shape above. -5. Report the state file path in the final chat response. - -Use `DONE` only after the finishing workflow is complete. If task work is complete but finishing is not complete, use `PARTIAL` unless the human partner explicitly accepts the remaining gap. - ## When to Stop and Ask for Help **STOP executing immediately when:** @@ -102,10 +44,7 @@ Use `DONE` only after the finishing workflow is complete. If task work is comple - You don't understand an instruction - Verification fails repeatedly -Before asking for clarification at a terminal stop, write the appropriate plan state file: -- Use `NEEDS_REVIEW` when the blocker is a plan-quality issue discovered before implementation. -- Use `BLOCKED` when execution cannot proceed without input, access, dependencies, or clearer instructions. -- Use `PARTIAL` when some execution work is complete but the plan cannot be declared done. +**Ask for clarification rather than guessing.** ## When to Revisit Earlier Steps @@ -121,9 +60,6 @@ Before asking for clarification at a terminal stop, write the appropriate plan s - Don't skip verifications - Reference skills when plan says to - Stop when blocked, don't guess -- Always write a plan state file before ending at a terminal stop -- Report the plan state file path in the final response -- `DONE` requires completed finishing, not only completed task checkboxes - Never start implementation on main/master branch without explicit user consent ## Integration diff --git a/.github/skills/superpowers-requesting-code-review/SKILL.md b/.github/skills/superpowers-requesting-code-review/SKILL.md index 7805276..dd650ed 100644 --- a/.github/skills/superpowers-requesting-code-review/SKILL.md +++ b/.github/skills/superpowers-requesting-code-review/SKILL.md @@ -5,7 +5,7 @@ description: Use when completing tasks, implementing major features, or before m # Requesting Code Review -Request `internal-gateway-review` to catch issues before they cascade. The reviewer gets precisely crafted context for evaluation and preserves your own context for continued work. +Dispatch a code reviewer subagent to catch issues before they cascade. The reviewer gets precisely crafted context for evaluation — never your session's history. This keeps the reviewer focused on the work product, not your thought process, and preserves your own context for continued work. **Core principle:** Review early, review often. @@ -29,9 +29,9 @@ BASE_SHA=$(git rev-parse HEAD~1) # or origin/main HEAD_SHA=$(git rev-parse HEAD) ``` -**2. Prepare review-mode request:** +**2. Dispatch code reviewer subagent:** -Use a user-visible `internal-gateway-review` request, filling the template at [code-reviewer.md](code-reviewer.md) +Dispatch a `general-purpose` subagent, filling the template at [code-reviewer.md](code-reviewer.md) **Placeholders:** - `{DESCRIPTION}` - Brief summary of what you built @@ -55,13 +55,13 @@ You: Let me request code review before proceeding. BASE_SHA=$(git log --oneline | grep "Task 1" | head -1 | awk '{print $1}') HEAD_SHA=$(git rev-parse HEAD) -[Request internal-gateway-review] +[Dispatch code reviewer subagent] DESCRIPTION: Added verifyIndex() and repairIndex() with 4 issue types - PLAN_OR_REQUIREMENTS: Task 2 from tmp/superpowers/plans/deployment-plan.md + PLAN_OR_REQUIREMENTS: Task 2 from docs/superpowers/plans/deployment-plan.md BASE_SHA: a7981ec HEAD_SHA: 3df7661 -[Review returns]: +[Subagent returns]: Strengths: Clean architecture, real tests Issues: Important: Missing progress indicators diff --git a/.github/skills/superpowers-subagent-driven-development/SKILL.md b/.github/skills/superpowers-subagent-driven-development/SKILL.md index e400a5a..e77b833 100644 --- a/.github/skills/superpowers-subagent-driven-development/SKILL.md +++ b/.github/skills/superpowers-subagent-driven-development/SKILL.md @@ -274,7 +274,7 @@ a ledger file, not only in todos. ``` You: I'm using Subagent-Driven Development to execute this plan. -[Read plan file once: tmp/superpowers/plans/feature-plan.md] +[Read plan file once: docs/superpowers/plans/feature-plan.md] [Create todos for all tasks] Task 1: Hook installation script diff --git a/.github/skills/superpowers-test-driven-development/SKILL.md b/.github/skills/superpowers-test-driven-development/SKILL.md index 2e047ba..d3207f6 100644 --- a/.github/skills/superpowers-test-driven-development/SKILL.md +++ b/.github/skills/superpowers-test-driven-development/SKILL.md @@ -9,17 +9,13 @@ description: Use when implementing any feature or bugfix, before writing impleme Write the test first. Watch it fail. Write minimal code to pass. -Use this workflow when the outcome is executable and a failing test can prove the contract. - -Do not treat this workflow as the default for prompt, skill, agent, instruction, or Markdown authoring when the change is textual or governance-only and no meaningful test-first loop exists. - **Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing. **Violating the letter of the rules is violating the spirit of the rules.** ## When to Use -**Always for executable behavior:** +**Always:** - New features - Bug fixes - Refactoring @@ -29,7 +25,6 @@ Do not treat this workflow as the default for prompt, skill, agent, instruction, - Throwaway prototypes - Generated code - Configuration files -- Textual or governance-only authoring with no meaningful executable contract Thinking "skip TDD just this once"? Stop. That's rationalization. diff --git a/.github/skills/superpowers-verification-before-completion/SKILL.md b/.github/skills/superpowers-verification-before-completion/SKILL.md index 035e583..b60f91d 100644 --- a/.github/skills/superpowers-verification-before-completion/SKILL.md +++ b/.github/skills/superpowers-verification-before-completion/SKILL.md @@ -37,13 +37,6 @@ BEFORE claiming any status or expressing satisfaction: Skip any step = lying, not verifying ``` -## Compact Evidence Reporting - -Read enough full output to judge the claim honestly. When the output is long, -report the command, exit code, material counts, failing checks, header or -schema checks, changed files, and the exact gap instead of pasting raw logs. -Keep raw output only when the raw text itself is the disputed evidence. - ## Common Failures | Claim | Requires | Not Sufficient | diff --git a/.github/skills/superpowers-writing-plans/SKILL.md b/.github/skills/superpowers-writing-plans/SKILL.md index 5fc8a4e..dc9e7a4 100644 --- a/.github/skills/superpowers-writing-plans/SKILL.md +++ b/.github/skills/superpowers-writing-plans/SKILL.md @@ -1,6 +1,6 @@ --- name: superpowers-writing-plans -description: Use when a multi-step implementation effort still needs an explicit plan before execution +description: Use when you have a spec or requirements for a multi-step task, before touching code --- # Writing Plans @@ -9,17 +9,13 @@ description: Use when a multi-step implementation effort still needs an explicit Write comprehensive implementation plans assuming the engineer has zero context for our codebase and questionable taste. Document everything they need to know: which files to touch for each task, code, testing, docs they might need to check, how to test it. Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits. -Use this workflow when the steps, sequencing, or validation still need an explicit written plan before execution. - -Do not use this workflow as the default gate for deterministic repository-owned maintenance or realignment when the desired change is already clear and concretely verifiable. - Assume they are a skilled developer, but know almost nothing about our toolset or problem domain. Assume they don't know good test design very well. **Announce at start:** "I'm using the writing-plans skill to create the implementation plan." **Context:** If working in an isolated worktree, it should have been created via the `superpowers-using-git-worktrees` skill at execution time. -**Save plans to:** `tmp/superpowers/plans/YYYY-MM-DD-.md` +**Save plans to:** `docs/superpowers/plans/YYYY-MM-DD-.md` - (User preferences for plan location override this default) ## Scope Check @@ -161,7 +157,7 @@ If you find issues, fix them inline. No need to re-review — just fix and move After saving the plan, offer execution choice: -**"Plan complete and saved to `tmp/superpowers/plans/.md`. Two execution options:** +**"Plan complete and saved to `docs/superpowers/plans/.md`. Two execution options:** **1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration From f7c01652846e2b80fd19c5351f2500e3bcb12bd0 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 22:52:52 +0200 Subject: [PATCH 11/44] feat: update paths to use temporary workspace for superpowers plans and specs --- .../SKILL.md | 11 ++++--- .../references/superpowers-normalization.yaml | 4 +++ .../scripts/normalize_superpowers_imports.py | 29 +++++++++++++++++++ .../skills/superpowers-brainstorming/SKILL.md | 4 +-- .../spec-document-reviewer-prompt.md | 2 +- .../SKILL.md | 2 +- .../SKILL.md | 2 +- .../skills/superpowers-writing-plans/SKILL.md | 4 +-- 8 files changed, 47 insertions(+), 11 deletions(-) diff --git a/.github/skills/local-agent-sync-external-resources/SKILL.md b/.github/skills/local-agent-sync-external-resources/SKILL.md index c61344f..ddf84ec 100644 --- a/.github/skills/local-agent-sync-external-resources/SKILL.md +++ b/.github/skills/local-agent-sync-external-resources/SKILL.md @@ -80,6 +80,7 @@ For `local-sync-external-resources`, keep the split strict: - `scripts/apply_imported_asset_overrides.py` owns patch replay after an upstream refresh; prefer a clean replay first, allow a registered `git apply --3way` fallback when upstream text drift is compatible, and stop for review instead of forcing a hidden fork. - `references/superpowers-normalization.yaml` owns the upstream-to-local map, blocked legacy ids, and live scan scope for the `obra/superpowers` family. - `scripts/normalize_superpowers_imports.py` owns local id normalization after upstream materialization and before imported override replay. +- Repo-local path rewrites that must survive every `obra/superpowers` refresh belong in `references/superpowers-normalization.yaml` and are applied by `scripts/normalize_superpowers_imports.py`, not by ad hoc manual edits after import. Do not collapse these roles back into one file just because the current task touches all of them. @@ -159,12 +160,14 @@ For the `obra/superpowers` family: 1. Read `references/superpowers-normalization.yaml` before materializing files. 2. Install or refresh only the managed upstream skills listed in that reference. 3. Materialize local directories and frontmatter as `superpowers-*`, not `obra-*`. -4. Run `scripts/normalize_superpowers_imports.py --check` before the rename to expose drift. -5. Run `scripts/normalize_superpowers_imports.py --apply` after materialization or rename. -6. Run `scripts/apply_imported_asset_overrides.py --dry-run` after normalization and before applying overrides. -7. Rebuild `.github/INVENTORY.md` and run catalog validation. +4. Keep repo-local workspace paths in that same reference. For retained-plan and retained-spec paths, rewrite the upstream `docs/` Superpowers workspace references to `tmp/superpowers` there so every refresh reapplies the local storage contract automatically. +5. Run `scripts/normalize_superpowers_imports.py --check` before the rename to expose drift. +6. Run `scripts/normalize_superpowers_imports.py --apply` after materialization or rename. +7. Run `scripts/apply_imported_asset_overrides.py --dry-run` after normalization and before applying overrides. +8. Rebuild `.github/INVENTORY.md` and run catalog validation. Treat residual managed `obra-*` ids or `superpowers:` references in live catalog assets as blocking drift. Do not add a newly discovered upstream Superpowers skill unless the user explicitly expands the managed map. +Treat residual upstream Superpowers `docs/` workspace references in the refreshed managed assets as blocking drift when the repo-local contract still requires `tmp/superpowers`. ### 5. Keep Sync Evidence Deterministic diff --git a/.github/skills/local-agent-sync-external-resources/references/superpowers-normalization.yaml b/.github/skills/local-agent-sync-external-resources/references/superpowers-normalization.yaml index 41d9844..b0b2219 100644 --- a/.github/skills/local-agent-sync-external-resources/references/superpowers-normalization.yaml +++ b/.github/skills/local-agent-sync-external-resources/references/superpowers-normalization.yaml @@ -45,6 +45,10 @@ managed_skills: - upstream: writing-plans legacy_local: obra-writing-plans local: superpowers-writing-plans +managed_text_replacements: + - from: docs/superpowers + to: tmp/superpowers + reason: Keep retained plans and specs under the repository-local temporary workspace after every superpowers refresh. live_scan: include: - .github/agents diff --git a/.github/skills/local-agent-sync-external-resources/scripts/normalize_superpowers_imports.py b/.github/skills/local-agent-sync-external-resources/scripts/normalize_superpowers_imports.py index 056e1f4..58ae737 100644 --- a/.github/skills/local-agent-sync-external-resources/scripts/normalize_superpowers_imports.py +++ b/.github/skills/local-agent-sync-external-resources/scripts/normalize_superpowers_imports.py @@ -37,11 +37,18 @@ class ManagedPatch: path: str +@dataclass(frozen=True) +class ManagedTextReplacement: + old_text: str + new_text: str + + @dataclass(frozen=True) class NormalizationConfig: reference_path: Path managed_skills: tuple[ManagedSkill, ...] managed_patches: tuple[ManagedPatch, ...] + managed_text_replacements: tuple[ManagedTextReplacement, ...] scan_includes: tuple[str, ...] ignored_files: frozenset[str] @@ -128,6 +135,7 @@ def load_config(reference_path: Path) -> NormalizationConfig: managed_skills = tuple(load_managed_skills(payload)) managed_patches = tuple(load_managed_patches(payload)) + managed_text_replacements = tuple(load_managed_text_replacements(payload)) live_scan = payload.get("live_scan") if isinstance(payload.get("live_scan"), dict) else {} raw_includes = live_scan.get("include") if isinstance(live_scan, dict) else None scan_includes = tuple(item for item in raw_includes or () if isinstance(item, str) and item.strip()) @@ -140,6 +148,7 @@ def load_config(reference_path: Path) -> NormalizationConfig: reference_path=reference_path, managed_skills=managed_skills, managed_patches=managed_patches, + managed_text_replacements=managed_text_replacements, scan_includes=scan_includes, ignored_files=frozenset(ignored_files), ) @@ -176,6 +185,24 @@ def load_managed_patches(payload: dict[str, object]) -> list[ManagedPatch]: return managed_patches +def load_managed_text_replacements(payload: dict[str, object]) -> list[ManagedTextReplacement]: + raw_entries = payload.get("managed_text_replacements", []) + if not isinstance(raw_entries, list): + raise ValueError("managed_text_replacements must be a list when present.") + + replacements: list[ManagedTextReplacement] = [] + for raw_entry in raw_entries: + if not isinstance(raw_entry, dict): + raise ValueError("Each managed text replacement entry must be a mapping.") + replacements.append( + ManagedTextReplacement( + old_text=require_string(raw_entry, "from"), + new_text=require_string(raw_entry, "to"), + ) + ) + return replacements + + def require_string(raw_entry: dict[str, object], key: str) -> str: value = raw_entry.get(key) if not isinstance(value, str) or not value.strip(): @@ -255,6 +282,8 @@ def text_replacements(config: NormalizationConfig) -> list[tuple[str, str]]: replacements.append((f"superpowers:{entry.upstream}", entry.local)) for entry in config.managed_patches: replacements.append((entry.legacy_path, entry.path)) + for entry in config.managed_text_replacements: + replacements.append((entry.old_text, entry.new_text)) return replacements diff --git a/.github/skills/superpowers-brainstorming/SKILL.md b/.github/skills/superpowers-brainstorming/SKILL.md index 97df886..ddfb2c4 100644 --- a/.github/skills/superpowers-brainstorming/SKILL.md +++ b/.github/skills/superpowers-brainstorming/SKILL.md @@ -26,7 +26,7 @@ You MUST create a task for each of these items and complete them in order: 3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria 4. **Propose 2-3 approaches** — with trade-offs and your recommendation 5. **Present design** — in sections scaled to their complexity, get user approval after each section -6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD--design.md` and commit +6. **Write design doc** — save to `tmp/superpowers/specs/YYYY-MM-DD--design.md` and commit 7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) 8. **User reviews written spec** — ask user to review the spec file before proceeding 9. **Transition to implementation** — invoke writing-plans skill to create implementation plan @@ -103,7 +103,7 @@ digraph brainstorming { **Documentation:** -- Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD--design.md` +- Write the validated design (spec) to `tmp/superpowers/specs/YYYY-MM-DD--design.md` - (User preferences for spec location override this default) - Use elements-of-style:writing-clearly-and-concisely skill if available - Commit the design document to git diff --git a/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md b/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md index 6099312..08e54f8 100644 --- a/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md +++ b/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md @@ -4,7 +4,7 @@ Use this template when dispatching a spec document reviewer subagent. **Purpose:** Verify the spec is complete, consistent, and ready for implementation planning. -**Dispatch after:** Spec document is written to docs/superpowers/specs/ +**Dispatch after:** Spec document is written to tmp/superpowers/specs/ ``` Subagent (general-purpose): diff --git a/.github/skills/superpowers-requesting-code-review/SKILL.md b/.github/skills/superpowers-requesting-code-review/SKILL.md index dd650ed..01cacd4 100644 --- a/.github/skills/superpowers-requesting-code-review/SKILL.md +++ b/.github/skills/superpowers-requesting-code-review/SKILL.md @@ -57,7 +57,7 @@ HEAD_SHA=$(git rev-parse HEAD) [Dispatch code reviewer subagent] DESCRIPTION: Added verifyIndex() and repairIndex() with 4 issue types - PLAN_OR_REQUIREMENTS: Task 2 from docs/superpowers/plans/deployment-plan.md + PLAN_OR_REQUIREMENTS: Task 2 from tmp/superpowers/plans/deployment-plan.md BASE_SHA: a7981ec HEAD_SHA: 3df7661 diff --git a/.github/skills/superpowers-subagent-driven-development/SKILL.md b/.github/skills/superpowers-subagent-driven-development/SKILL.md index e77b833..e400a5a 100644 --- a/.github/skills/superpowers-subagent-driven-development/SKILL.md +++ b/.github/skills/superpowers-subagent-driven-development/SKILL.md @@ -274,7 +274,7 @@ a ledger file, not only in todos. ``` You: I'm using Subagent-Driven Development to execute this plan. -[Read plan file once: docs/superpowers/plans/feature-plan.md] +[Read plan file once: tmp/superpowers/plans/feature-plan.md] [Create todos for all tasks] Task 1: Hook installation script diff --git a/.github/skills/superpowers-writing-plans/SKILL.md b/.github/skills/superpowers-writing-plans/SKILL.md index dc9e7a4..8ffc116 100644 --- a/.github/skills/superpowers-writing-plans/SKILL.md +++ b/.github/skills/superpowers-writing-plans/SKILL.md @@ -15,7 +15,7 @@ Assume they are a skilled developer, but know almost nothing about our toolset o **Context:** If working in an isolated worktree, it should have been created via the `superpowers-using-git-worktrees` skill at execution time. -**Save plans to:** `docs/superpowers/plans/YYYY-MM-DD-.md` +**Save plans to:** `tmp/superpowers/plans/YYYY-MM-DD-.md` - (User preferences for plan location override this default) ## Scope Check @@ -157,7 +157,7 @@ If you find issues, fix them inline. No need to re-review — just fix and move After saving the plan, offer execution choice: -**"Plan complete and saved to `docs/superpowers/plans/.md`. Two execution options:** +**"Plan complete and saved to `tmp/superpowers/plans/.md`. Two execution options:** **1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration From cb3e0509af9747df5026a5e11bb292e267ee06bf Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 23:33:11 +0200 Subject: [PATCH 12/44] Remove test_plan_authoring.py file and its associated test cases for plan authoring CLI functionality --- .../internal-gateway-writing-plans/SKILL.md | 226 +--- .../agents/openai.yaml | 33 +- .../references/compact-plan-contract.md | 137 --- .../references/plan-review-gate.md | 71 -- .../references/scope-challenge.md | 92 -- .../scripts/plan_authoring.py | 969 ------------------ .../scripts/test_plan_authoring.py | 333 ------ 7 files changed, 55 insertions(+), 1806 deletions(-) delete mode 100644 .github/skills/internal-gateway-writing-plans/references/compact-plan-contract.md delete mode 100644 .github/skills/internal-gateway-writing-plans/references/plan-review-gate.md delete mode 100644 .github/skills/internal-gateway-writing-plans/references/scope-challenge.md delete mode 100644 .github/skills/internal-gateway-writing-plans/scripts/plan_authoring.py delete mode 100644 tests/github/skills/internal_gateway_writing_plans/scripts/test_plan_authoring.py diff --git a/.github/skills/internal-gateway-writing-plans/SKILL.md b/.github/skills/internal-gateway-writing-plans/SKILL.md index 1e46926..6934d13 100644 --- a/.github/skills/internal-gateway-writing-plans/SKILL.md +++ b/.github/skills/internal-gateway-writing-plans/SKILL.md @@ -1,204 +1,60 @@ --- name: internal-gateway-writing-plans -description: Use when repository-owned work needs a retained numbered plan under tmp/superpowers// and the plan must follow the local English-content execution-plan contract. +description: Use when repository-owned work needs a short repository preflight wrapper that delegates writing decisions to superpowers-writing-plans. --- # Internal Gateway Writing Plans ## Referenced skills -- `internal-gateway-simple-task`: concrete single-lane execution owner when no retained plan is needed. -- `internal-gateway-execute-plans`: retained-plan execution owner that infers execution strategy from profile, folder shape, and validation path. +- `superpowers-writing-plans`: required writing owner after preflight. -Repository-owned wrapper for retained plan authoring. This skill owns retained -plan creation, profile selection, where the plan lives, file names, content -language, and the authoring-to-execution handoff contract. It does not select a -separate execution consumer field inside the retained plan. +Repository-owned wrapper for retained planning requests. This skill does not +own a local retained-plan protocol or decide the output artifact. ## When to use -- Retaining a plan because work crosses turns, needs explicit tracking, or must - stop before execution. -- Converting a monolithic or overgrown plan into the local numbered-plan - structure. -- Preparing a retained plan for execution handoff. +- Preparing a request for `superpowers-writing-plans` after a short repository + preflight. +- Capturing the smallest target state, anti-scope, nearest owner, validation + path, stop conditions, and observable acceptance before delegation. ## When not to use - Clear, local, quick tasks whose next steps fit in chat and do not need a retained artifact. -- Substantive ideation before a plan; use `internal-gateway-idea-brainstorming`. +- Substantive ideation before planning; use `internal-gateway-idea-brainstorming`. - Editing imported `superpowers-*` skills. -## Profile Selection - -Choose the smallest profile that safely fits the work. Declare the profile in -`02-execution.md` for `compact` and `02-control.md` for `extended` under -`Plan profile`. - -`init` creates a scaffold only. A retained plan is execution-ready only after -`handoff-check` returns ready. - -New `compact` plans should use `tmp/superpowers/mini-plan-*`. - -| Profile | When | Required files | -| --- | --- | --- | -| `compact` | Single owner, concrete target, one validation path, low-to-medium risk, and one execution lane. Best fit for small/fast executors after positive handoff validation. | `01-change-summary.md`, `02-execution.md` | -| `extended` | Cross-family changes, higher risk, lower-context execution, multiple validators, or multi-slice execution state. Soft-limit profile: use judgment-based size review with completeness over compression, explicit control files, and deterministic read order. | `01-change-summary.md`, `02-control.md`, `03-execution.md`, additional numbered files by category (`04-...`). | - -### Plan Profile Selection Guard - -Escalate to `extended` when completeness or context-discipline risk is material: -cross-skill token-discipline changes; exports, generated reports, or datasets -with non-trivial reconciliation; validator-impacting contract changes; -external API contracts (credentials, pagination, retries, schema pinning); -executive-facing output; multiple validators; or synced always-on guidance -edits. - -Do not use `compact` when the executor needs exact sources, target files, -validators, blockers, or external pins that only `02-control.md` -can provide. - -If `compact` is still chosen near one of those edges, the plan must record the -contrary evidence that keeps one owner, one execution lane, and one validation -path sufficient despite lower-context execution. - -## Explicit Constraints - -- Create retained plans under `tmp/superpowers//`. -- New `compact` folders must use `tmp/superpowers/mini-plan-*`. -- Use English file names. Write `01-change-summary.md` in Italian. Write all - other plan files in English. -- Keep `01-change-summary.md` as a compressed, non-executable decision capsule. - Target at most 300 estimated tokens. Use only these required sections: - `Problema da risolvere`, `Risultato atteso`, `Risorse coinvolte`, - `Decisione richiesta`, and `Decisioni aperte`. -- `Risorse coinvolte` keeps the `Risorsa | Azione | Scopo` table. Use one row - per materially changed resource group, not one row per file when that would - duplicate `02-execution.md` or `02-control.md`. -- Do not put chosen logic, validation detail, execution route notes, source-item - coverage, blockers, or implementation-contract detail in `01-change-summary.md`. - Put those facts in `02-execution.md` for `compact` or `02-control.md` for - `extended`. -- `01-change-summary.md` must still preserve `counter-validation-critical facts`: - observable result criteria without which the user cannot verify whether the - plan really covers the request. These are result facts, not execution detail. - Keep them in `Risultato atteso` or `Risorse coinvolte`. -- Do not compress away concrete contract facts that materially define the - requested result, such as column order, new columns, required fields, - must-never-be-empty conditions, blocking diagnostics, repaired data gaps, - row-count or no-data-loss invariants, or user-visible output-contract - changes. -- Compact plans have a 2,000 estimated-token total budget measured as - `ceil(UTF-8 bytes / 4)` across plan Markdown files. Keep `02-execution.md` - under 1,500 estimated tokens. Treat warnings as required review inputs. -- For `extended`, treat token warnings as review inputs for completeness and - slicing. Prefer splitting into numbered files over compression. -- `compact` uses exactly `01-change-summary.md` and `02-execution.md` during - authoring. `extended` uses `01-change-summary.md`, `02-control.md`, - `03-execution.md`, and optional higher numbered files. - -## Core Contract - -- For debugging, drift, or data-mismatch work, produce a compact diagnosis - capsule before retained-plan authoring starts: symptom, target artifact, - compared sources or layers, cheapest falsifier, and stop rule. Keep it only - in chat or request context; do not create another retained file for it. -- `01-change-summary.md`: compressed Italian decision capsule following - `Explicit Constraints`. -- Distinguish execution detail from `counter-validation-critical facts`. - Execution detail covers route, commands, source-item ids, file-by-file steps, - fallback logic, and validator order; those stay out of `01-change-summary.md`. - `Counter-validation-critical facts` are the user-observable end-state facts - needed to verify coverage at a glance, and they stay in `Risultato atteso` - or `Risorse coinvolte`. -- When the plan changes output, schema, or data behavior, do not collapse the - result into generic phrases such as `rispetta il nuovo schema`, `aggiorna gli - output`, or `corregge i dati` if the request depends on concrete facts like - first columns, field order, newly added columns, never-empty fields, - blocking diagnostics, repaired row ranges, or row-count invariants. -- `02-execution.md` for `compact` must include these exact headings: - `Plan profile`, `Target and anti-scope` (with `### Target` and - `### Anti-scope`), `Owner and validator`, `Stop conditions`, `Objective`, - `Chosen logic`, `Key assumptions`, `Executable steps`, `Validation`, and - `Source item coverage`. -- `02-execution.md` executable steps use numbered items with explicit labels: - `Target:`, `Acceptance:`, `Validation:`, and `Fallback:`. -- `02-control.md` for `extended`: control file with `Recommended use`, - `Plan profile`, `File map and role`, clarification gate status, - `Initial evidence pass`, `Reading budget`, target, anti-scope, owner, - validator, stop conditions, and `Source item ledger`. -- Preserve known-context handoff quality in `Initial evidence pass` and `Reading budget` so executors can avoid repeated broad rereads. -- `03-execution.md`: first executable file for `extended`. -- For `extended`, implementation-contract sections are merged into `02-control.md` - with these exact headings: `Sources`, `Candidate targets`, - `Validation commands`, `Blockers and fallback rules`, and `External pins`. -- For `extended`, recommend adding deep companion files only when justified by - triggers, and keep them as recommendations (not ERROR-level required files): - `data-contract.md` for reconciled datasets and schema mappings, - `validation-runbook.md` for multi-validator troubleshooting or rollback paths, - and API/schema pin notes when external dependencies or credentials drive risk. -- Apply a say-once rule: each control fact (target, owner, validator, blockers, - pins, and source-item coverage) is written once in the owning file, and step - files do not restate target/owner/validator. -- Keep profile token weight explicit: `compact` stays within the 2,000-token - total budget with a compressed `01`; `extended` keeps control weight in - `02-control.md` and execution weight in numbered step files. -- `done-*`, `evidence-envelope.md`, and `completion-report.md` are packaging - artifacts only. Do not create them during authoring. - -## Workflow - -1. Decide: retained plan or chat. -2. For debugging, drift, or data-mismatch work, write the diagnosis capsule - before `01-change-summary.md`. -3. For new plans, run bundle-local `init` first to create the scaffold. -4. Choose folder name and write the compressed `01-change-summary.md` in Italian, - preserving the user-visible `counter-validation-critical facts` needed to - verify coverage without reading the control file. -5. For `compact`, write `02-execution.md` with profile, control header, steps, - and source-item coverage. -6. For `extended`, write `02-control.md` with profile, control facts, merged - implementation-contract sections, and source-item coverage. -7. Run the clarification gate when user decisions remain. -8. For `extended`, write executable numbered files in order starting with - `03-execution.md`. -9. Keep each executable file scoped to its slice and validation path so executors can run targeted rereads. -10. Fold open-user decisions into one summary line instead of a separate - `questions.md` file for `compact`. -11. Run scope challenge and plan review gate for non-trivial plans. -12. Run `audit` first, then run `handoff-check`; execute only when ready. -13. Treat token warnings as review inputs, not as proof of measured savings. For - `extended`, prefer splitting into numbered files over compression, and never - compress away source pins, schema contracts, validation rules, stop - conditions, or failure-investigation steps. - -## Validation - -- Plan lives under `tmp/superpowers//`. -- `01-change-summary.md` is Italian, compressed, and contains only the required - decision-capsule sections. -- `01-change-summary.md` preserves the most important - `counter-validation-critical facts` in `Risultato atteso` or - `Risorse coinvolte` when the plan changes output, schema, or data behavior. -- `compact` uses `01-change-summary.md` + `02-execution.md` only. -- `compact` stays under the 2,000 estimated-token total budget, or the warning - was resolved by compression or escalation. -- `compact` `02-execution.md` uses the exact validator headings and step labels. -- `extended` uses `01-change-summary.md`, `02-control.md`, and - `03-execution.md` plus optional higher-numbered slices. -- The diagnosis capsule exists before authoring when debugging, drift, or - data-mismatch work is being retained, and it stays in chat/request context. -- `extended` includes merged contract sections in `02-control.md`. -- Step files do not restate target, owner, or validator. - -## Common mistakes - -- Retaining a plan for work that should stay in chat. -- Skipping the diagnosis capsule and turning an unproven mismatch into a - retained plan. -- Using `compact` when execution actually needs an implementation contract. -- Compressing away user-visible contract facts from `01-change-summary.md` and - leaving only generic schema or output wording that prevents - counter-validation. -- Creating `done-*` markers during authoring. +## Contract + +- Run a short preflight before loading `superpowers-writing-plans`. +- Delegate the next action to `superpowers-writing-plans`. +- `superpowers-writing-plans` will decide whether to create a retained plan, + ask a blocking clarification, redirect to a better owner, or stop with a + reason. +- Save created retained plans through the Superpowers default path: + `tmp/superpowers/plans/YYYY-MM-DD-.md`. +- If a retained plan is created, run an `Execution-readiness check` before + claiming completion. +- Stop after the delegated writing outcome and wait for the user's next choice. + +## Preflight + +- `Target`: the smallest target state that satisfies the request. +- `Anti-scope`: tempting work that must stay outside the plan. +- `Nearest owner`: repository owner or skill family that owns the change. +- `Validation path`: concrete validator, review path, or explicit validation gap. +- `Stop conditions`: missing input, unsafe scope, ownership conflict, or + validation failure that must stop execution. +- `Observable acceptance`: diff, file state, validator assertion, manual check, + or explicit non-action that proves the result. + +## Execution-readiness check + +For any created retained plan, verify task order, concrete file targets, clear +edit intent, validation commands or explicit gaps, stop conditions, and +handoff readiness for `superpowers-executing-plans` or +`superpowers-subagent-driven-development`. + +Preserve known-context handoff quality with a short preflight and targeted rereads when the delegation leaves a gap. diff --git a/.github/skills/internal-gateway-writing-plans/agents/openai.yaml b/.github/skills/internal-gateway-writing-plans/agents/openai.yaml index 470fd51..f150a7b 100644 --- a/.github/skills/internal-gateway-writing-plans/agents/openai.yaml +++ b/.github/skills/internal-gateway-writing-plans/agents/openai.yaml @@ -1,22 +1,17 @@ interface: display_name: "Internal Gateway Writing Plans" - short_description: "Repo wrapper for current-only numbered plan authoring" + short_description: "Repository preflight wrapper for superpowers-writing-plans" default_prompt: >- - Use $internal-gateway-writing-plans to author retained repository-owned - numbered plans under tmp/superpowers. For new plans, run the bundle-local - init scaffold first, then author content. Choose compact (summary + merged - execution/control) or extended (summary + control + numbered execution - files). - New compact plans should use mini-plan-* folders. Write - 01-change-summary.md in Italian as a compressed decision capsule with - Risorsa|Azione|Scopo table and Decisioni aperte; keep logic, validation, - route notes, and source-item coverage in the control/execution file. All - other files are English. Reject unsupported profiles. Keep compact plans - under 2,000 estimated tokens total, with 01 under 300 and 02 under 1,500. - Run bundle-local CLI audit first and handoff-check second for deterministic - readiness. Treat token warnings as review inputs for compression or split - decisions, not as measured-savings proof. Do not write a Recommended consumer field into the retained plan. Leave execution strategy selection to - $internal-gateway-execute-plans, which will infer the best execution path from - profile, folder shape, and validation path. Keep implementation-contract - content merged in 02-control.md for extended and avoid cross-file fact - duplication. Emit a Decision Brief before handoff. + Use $internal-gateway-writing-plans as a repository preflight wrapper. + Capture a short preflight with Target, Anti-scope, Nearest owner, + Validation path, Stop conditions, and Observable acceptance, then load + $superpowers-writing-plans and delegate the next action. Let + $superpowers-writing-plans decide whether to create a retained plan, ask a + blocking clarification, redirect to a better owner, or stop with a reason. + Save created retained plans to tmp/superpowers/plans/YYYY-MM-DD-.md. + If a retained plan is created, run an Execution-readiness check for ordered + tasks, concrete file targets, clear edit intent, validation commands or + explicit gaps, stop conditions, and handoff readiness for + $superpowers-executing-plans or $superpowers-subagent-driven-development. + Stop after the delegated writing outcome and wait for the user's next + choice. diff --git a/.github/skills/internal-gateway-writing-plans/references/compact-plan-contract.md b/.github/skills/internal-gateway-writing-plans/references/compact-plan-contract.md deleted file mode 100644 index eeb7f48..0000000 --- a/.github/skills/internal-gateway-writing-plans/references/compact-plan-contract.md +++ /dev/null @@ -1,137 +0,0 @@ -# Compact Plan Contract - -Schema, templates, and escalation rules for the `compact` retained-plan profile. -Load this reference only when writing or validating a `compact` plan. - -New `compact` plans should use `tmp/superpowers/mini-plan-*`. - -`01-change-summary.md` is written in Italian per the retained-plan authoring -contract. All other plan content uses English. - -## Profile Declaration - -Declare the profile in `02-execution.md`: - -```text -Plan profile: compact -``` - -Missing profiles are rejected. Do not infer `compact` or `extended` from folder -content. Folders without a declared `Plan profile` return -`unsupported-plan-contract`. - -## Compact File Shape - -| File | Role | Mandatory | -| --- | --- | --- | -| `01-change-summary.md` | Compressed Italian decision capsule. Non-executable. | Yes | -| `02-execution.md` | Merged control and execution file. Must contain profile and control header, executable numbered steps, and inline source-item coverage. | Yes | -| `done-*`, `evidence-envelope.md`, `completion-report.md` | Final packaging artifacts created after execution, not during authoring. | No (authoring) | - -## Compact Read Order - -Mandatory first reads: - -1. `01-change-summary.md` -2. `02-execution.md` - -Excluded reads: - -- `done-*`, `evidence-envelope.md`, and `completion-report.md` during authoring. - -`compact` is ready for retained-plan execution only after `handoff-check` is ready. -`internal-gateway-execute-plans` infers the concrete execution strategy from the -profile, folder shape, and validation path. - -## Escalation To Extended - -Escalate from `compact` to `extended` when any of these is true: - -- The plan spans multiple owners or skill families. -- The executor is low-context and needs exact sources, target files, validators, - blockers, or external pins that only `02-control.md` can provide. -- The validation path includes cross-family checks, external pins, or fallback rules. -- Hidden assumptions about file shape, naming, or repository conventions would - force the executor to rediscover them. - -When escalating, add `02-control.md` and additional numbered files -starting at `03-execution.md`. Update `Plan profile` to `extended`. - -## Lifecycle States - -- `scaffold`: created by `init`; not ready for execution. -- `ready`: `handoff-check` ready. -- `closed`: execution and packaging handled by the approved consumer. - -## Compact Token Expectations - -- Keep total plan Markdown under 2,000 estimated tokens, measured as - `ceil(UTF-8 bytes / 4)`. -- Keep `01-change-summary.md` under 300 estimated tokens. -- Keep `02-execution.md` under 1,500 estimated tokens. -- Warnings from the bundle-local CLI are review inputs that require compression - or escalation before handoff. -- Long procedural detail in `01-change-summary.md` is a defect. Long procedural - detail in `02-execution.md` is an escalation signal to `extended`. - -## Template: 01-change-summary.md (Italian) - -Required sections in Italian: - -- `Problema da risolvere` — concrete problem statement. -- `Risultato atteso` — expected outcome, short bullet list. -- `Risorse coinvolte` — table with columns `Risorsa | Azione | Scopo`. Required - for non-trivial plans. Use one row per materially changed resource group, not - one row per file when that would duplicate the control file. -- `Decisione richiesta` — decision request. -- `Decisioni aperte` — one line (`none` when there are no blockers). - -Do not put chosen logic, validation detail, execution route notes, source-item -coverage, blockers, or implementation-contract detail in `01-change-summary.md`. -Those facts belong in `02-execution.md`. - -`01-change-summary.md` must still preserve `counter-validation-critical facts`: -observable result criteria without which the user cannot counter-validate -whether the plan really covers the request. These facts are not execution -detail. Keep them in `Risultato atteso` or `Risorse coinvolte`. - -Keep concrete examples when they materially define the requested result, such -as column order, newly added columns, required fields, must-never-be-empty -conditions, blocking diagnostics, repaired data gaps, row-count or no-data-loss -invariants, or changed user-visible output contracts. - -Do not move execution detail into the summary: commands, file-by-file steps, -validator order, source-item ids, fallback logic, and coverage mechanics stay -in `02-execution.md`. - -A generic line like `rispetta il nuovo schema`, `aggiorna gli output`, or -`corregge i dati` is insufficient when the source-item coverage tracks concrete -schema, output, or data requirements that the user needs to verify at a glance. - -## Template: 02-execution.md - -Required sections: - -- `Plan profile` — `compact`. -- `Target and anti-scope` — smallest target and explicit exclusions. -- `Owner and validator` — primary owner, lane-change owner, and validation path. -- `Stop conditions` — blockers that must stop execution. -- `Objective` — concrete goal. -- `Chosen logic` — why this approach. -- `Key assumptions` — key assumptions. -- `Executable steps` — numbered steps with observable acceptance per step. -- `Validation` — validation commands or checks. -- `Source item coverage` — compact table with item id, acceptance, evidence, and status. - -## Ledger Row Template - -| ID | Source item | Observable acceptance | Evidence class | Acceptance evidence | Status | Route | - -- `ID`: stable item identifier (e.g., `RP-01`). -- `Source item`: what was requested or what must change. -- `Observable acceptance`: diff, file state, validator assertion, manual check, or - explicit non-action. -- `Evidence class`: diff, file, validator, manual, gap. -- `Acceptance evidence`: concrete command, path, or condition. -- `Status`: `PENDING` initially. -- `Route`: which plan file or explicit non-action owns this item. diff --git a/.github/skills/internal-gateway-writing-plans/references/plan-review-gate.md b/.github/skills/internal-gateway-writing-plans/references/plan-review-gate.md deleted file mode 100644 index 0c09589..0000000 --- a/.github/skills/internal-gateway-writing-plans/references/plan-review-gate.md +++ /dev/null @@ -1,71 +0,0 @@ -# Plan Review Gate - -Use this gate for a lightweight review before a retained plan moves to execution -or handoff. It checks clarity and validity without creating reviewer personas. - -## Source Patterns - -- Comparative source: `tmp/external-comparison/compound-engineering-plugin/plugins/compound-engineering/skills/ce-plan/SKILL.md`. -- Comparative source: `tmp/external-comparison/compound-engineering-plugin/plugins/compound-engineering/skills/ce-doc-review/SKILL.md`. -- Adopt the document/plan review gate only. Do not import Compound runtime or - persona agents. - -## Checklist - -| Check | Question | -| --- | --- | -| Clarity | Can an executor identify the target files, owner, and next action without guessing? | -| Coherence | Do the objective, rationale, executable steps, and validation path agree? | -| Spec sufficiency | Are the target, success criteria, boundaries, validation path, and open questions concrete enough to decide whether execution is safe? | -| Validability | Is there a concrete validator, review path, or explicit validation gap? | -| Observable acceptance | Does each executable verb name the expected diff, file state, assertion, or explicit non-action that will prove it was done? | -| Evidence | Are external claims, provenance paths, or comparative patterns cited where needed? | -| Scope | Are anti-scope and stop conditions explicit enough to prevent drift? | -| Summary focus | Does `01-change-summary.md` contain only the compressed Italian decision capsule with required sections and a `Risorsa \| Azione \| Scopo` table for non-trivial plans? | -| Summary clarity | Are changed resources and intended actions obvious in the summary? | -| Summary counter-validation | Does `01-change-summary.md` preserve enough observable result criteria for the user to counter-validate coverage without reading `02-execution.md` or `02-control.md`? | -| Semantic coverage | For `compact`, does `02-execution.md` preserve each requested or source item with stable item id, observable acceptance, evidence class, status, and route? For `extended`, does `02-control.md` preserve the same coverage? | -| Implementation contract | For `extended` profiles, does `02-control.md` include the exact sources, target files, validation order, blockers, and any external pin or fallback? For `compact` profiles, a separate implementation contract is not required. | -| Profile | Is `Plan profile` declared as `compact` or `extended`? Missing or unsupported profiles return `unsupported-plan-contract`. | -| Executor context | Can a smaller or lower-context executor see the key files, validators, owner, assumptions, and stop conditions without rediscovering the plan? | -| File naming | Are plan file names English while `01-change-summary.md` is Italian and all executable/control files are English? | -| Folder semantics | For `extended`, does `02-control.md` state `Recommended use` and `File map and role` so a generic reader can tell whether the folder is for review, apply, resume, rewrite, or status only? | -| Route map | Do ledger `Route` values map to existing executable numbered files, or to explicit non-action routes such as `closed`, `manual`, `gap`, or `not applicable`? | -| Open questions | Is `questions.md` present and set to `- none` for execution handoff, or explicitly blocking handoff? | -| Lifecycle status | Is plan state explicit (`scaffold`, `ready`, or `closed`) so an executor does not infer readiness? | -| Token discipline | Does the ledger define `Initial evidence pass` and `Reading budget` so the executor can classify the folder with the fewest safe reads? | -| Profile token budget | Is `compact` within the 2,000 estimated-token total budget, with `01-change-summary.md` under 300 and `02-execution.md` under 1,500, or escalated to `extended`? For `extended`, are soft limits reviewed with completeness over compression and split-by-slice decisions when files grow large? | - -## Outcomes - -- `READY`: the plan can move to execution or handoff. -- `REVISE`: the plan needs local edits before execution. -- `ASK`: a user decision is required before execution. - -## Rules - -- Keep the gate plain text and short. -- Prefer editing the retained plan over explaining around a bad plan. -- Reframe vague requirements into observable success criteria when evidence supports it; otherwise return `ASK` instead of accepting an unverifiable plan. -- Treat clarification-only completion for executable verbs as a plan defect. Rewrite the item until an executor can prove it from diff, file, validator, manual, or explicit-gap evidence. -- Do not add persona agents, runtime-specific frontmatter, or external workflow - dependencies. -- For strategic-to-operational conversions, coverage review comes before shape-only validation. -- For plans intended for a smaller or lower-context executor, keep technical - identifiers, file names, commands, and validation steps explicit. Short - English glosses near critical decisions are allowed when they reduce handoff - ambiguity. -- Treat missing merged-contract sections in `02-control.md` as a plan-quality defect for `extended` profiles. For `compact` profiles, a separate implementation contract is not required unless the plan changes always-on guidance, validators, or cross-family contracts. -- For `compact`, treat missing `Plan profile` and control header in `02-execution.md` as a plan-quality defect. -- When external evidence is needed, require an exact pin or explicit fallback in `02-control.md` before approving execution. -- Treat a long or overloaded `01-change-summary.md` as a plan-quality defect, not as a documentation nit. It should be a compressed decision capsule, not a control file. -- Treat an English `01-change-summary.md` as a plan-quality defect per the current retained-plan contract (Italian only). -- Treat an unclear resource table or missing resource-action-purpose columns in `01-change-summary.md` as a plan-quality defect. -- Treat a summary as a plan-quality defect when output, schema, or data-contract - changes are compressed into generic formulas without concrete observable - examples. Keep those user-visible facts in `Risultato atteso` or - `Risorse coinvolte`, not in execution detail. -- Treat a missing or weak source-item ledger as a plan-quality defect, not as a documentation nit. -- Treat a missing evidence pass or reading budget as a token-waste defect for non-trivial retained plans. -- Treat missing source-item coverage for requested work as a plan-quality defect, not as an editorial preference. -- Treat an unsupported or missing `Plan profile` as a plan-quality defect. diff --git a/.github/skills/internal-gateway-writing-plans/references/scope-challenge.md b/.github/skills/internal-gateway-writing-plans/references/scope-challenge.md deleted file mode 100644 index d8be9a0..0000000 --- a/.github/skills/internal-gateway-writing-plans/references/scope-challenge.md +++ /dev/null @@ -1,92 +0,0 @@ -# Scope Challenge - -Use this gate before retaining or approving a non-trivial execution plan. It -keeps the plan executable by forcing target, anti-scope, owner, validator, and -stop conditions into the open. - -## Source Pattern - -- Comparative source: `tmp/external-comparison/gstack/plan-eng-review/SKILL.md` - Step 0. -- Adopt the scope challenge idea only. Do not import the external runtime. - -## Required Questions - -Every retained plan must be able to answer these questions: - -1. `target`: What is the smallest target state that satisfies the request? -2. `anti-scope`: What tempting work is explicitly outside this plan? -3. `owner`: Which repository owner owns the change and which neighboring owner - should win if the lane changes? -4. `validator`: Which validator, test, review path, or explicit gap will prove - the result? -5. `stop conditions`: What missing input, unsafe scope, ownership conflict, or - validation failure must stop execution? -6. `reading budget`: What is the smallest first read and evidence pass that can classify the folder without broad context loading? -7. `observable acceptance`: Which diff, file state, validator assertion, manual check, or explicit non-action will prove each executable item? -8. `implementation contract`: For `extended` profiles, does `02-control.md` list the exact sources, target files, validation order, blockers, and any external pin or fallback? For `compact` profiles, a separate implementation contract is not required. -9. `profile`: Is `Plan profile` declared as `compact` or `extended`? Currently, no other profiles are supported. Missing or unrecognized profiles return `unsupported-plan-contract`. -10. `summary language`: Is `01-change-summary.md` written in Italian as a compressed decision capsule with required sections and a `Risorsa | Azione | Scopo` table for non-trivial plans? -11. `summary clarity`: Are changed resources and intended actions obvious in the summary without reading deeper files? -12. `summary counter-validation`: Does the summary contain enough observable criteria to let the user counter-validate the plan without reading the control file? -13. `route map`: Do `Source item ledger` routes point to existing numbered files or explicit non-action routes? -14. `questions state`: Is `questions.md` either `- none` (ready) or explicitly blocking execution handoff? -15. `token budget`: For `compact`, is total plan Markdown within 2,000 estimated tokens, with `01-change-summary.md` under 300 and `02-execution.md` under 1,500? - -For non-trivial retained plans and strategic-to-operational or -monolithic-to-executable conversions, also answer this: - -1. `coverage`: How does `02-execution.md` (`compact`) or `02-control.md` - (`extended`) preserve every requested or source item before execution or - before the source artifact is retired? - -For `compact`, answer how `02-execution.md` preserves coverage with inline item -rows. - -For retained plans with numbered files, confirm that `01-change-summary.md` is a -brief compressed decision summary only (Italian, non-executable), and that -`02-execution.md` (`compact`) or `02-control.md` (`extended`) exposes the same -target, anti-scope, owner, validator, and stop conditions. For `extended`, -confirm `02-control.md` also carries `Recommended use`, `Plan profile`, -`File map and role`, `Initial evidence pass`, `Reading budget`, source-item -coverage, and merged implementation-contract sections. - -For retained plans that rewrite an existing strategic or review-only artifact, -confirm that the folder contains `02-execution.md` (`compact`) or -`02-control.md` (`extended`) as a clear traceability owner before the source -artifact is deleted, replaced, or compressed. - -## Gate Result - -Use one of these outcomes: - -- `READY`: all required answers are concrete enough for execution. -- `NEEDS_REVISION`: one or more answers are missing or too vague. -- `BLOCKED`: a required decision, permission, or evidence source is missing. - -For non-trivial retained plans, `READY` also requires explicit source-item -coverage and, for `extended` profiles, complete merged implementation-contract -sections in `02-control.md`. - -## Output Template - -```text -Scope Challenge: READY | NEEDS_REVISION | BLOCKED -Target: -Anti-scope: -Owner: -Validator: -Stop conditions: -Profile: -Summary language: -Summary clarity: -Summary counter-validation: -Source-item ledger: -Reading budget: -Observable acceptance: -Implementation contract: -Coverage: -``` - -Do not proceed to `apply-plan` when the result is `NEEDS_REVISION` or `BLOCKED` -unless the user explicitly accepts the risk. diff --git a/.github/skills/internal-gateway-writing-plans/scripts/plan_authoring.py b/.github/skills/internal-gateway-writing-plans/scripts/plan_authoring.py deleted file mode 100644 index 90b1713..0000000 --- a/.github/skills/internal-gateway-writing-plans/scripts/plan_authoring.py +++ /dev/null @@ -1,969 +0,0 @@ -"""Bundle-local CLI for retained-plan authoring. - -Commands: init audit handoff-check tokens -""" - -from __future__ import annotations - -import argparse -import json -import math -import re -import sys -import textwrap -from dataclasses import dataclass -from pathlib import Path - -COMPACT_REQUIRED_FILES = frozenset({"01-change-summary.md", "02-execution.md"}) -EXTENDED_REQUIRED_FILES = frozenset( - {"01-change-summary.md", "02-control.md", "03-execution.md"} -) -SUPPORTED_PROFILES = frozenset({"compact", "extended"}) -COMPACT_FOLDER_PREFIX = "mini-plan-" -COMPACT_TOTAL_TOKEN_TARGET = 2000 -COMPACT_SUMMARY_TOKEN_TARGET = 300 -COMPACT_EXECUTION_TOKEN_TARGET = 1500 -CONTROL_REQUIRED_FIELDS = ( - "Recommended use", - "File map and role", - "Clarification gate", - "Initial evidence pass", - "Reading budget", - "Target and anti-scope", - "Owner and validator", - "Stop conditions", - "Source item ledger", -) -ITALIAN_SUMMARY_SECTIONS = ( - "Problema da risolvere", - "Risultato atteso", - "Risorse coinvolte", - "Decisione richiesta", - "Decisioni aperte", -) -RESOURCE_TABLE_HEADER_RE = re.compile(r"\|?\s*Risorsa\s*\|\s*Azione\s*\|\s*Scopo\s*\|?") -PLACEHOLDER_RE = re.compile(r"\b(?:x|tbd|todo|placeholder)\b", re.IGNORECASE) -EXECUTABLE_STEP_RE = re.compile(r"^\d+\.\s+(.+)$", re.MULTILINE) -GENERIC_TMP_NAMES = frozenset({"plan", "tmp", "test", "misc", "notes", "draft", "new-plan"}) -MERGED_CONTRACT_SECTIONS = ( - "Sources", - "Candidate targets", - "Validation commands", - "Blockers and fallback rules", - "External pins", -) -NON_ACTION_ROUTES = frozenset({"closed", "manual", "gap", "not applicable", "n/a", "none"}) -GENERIC_SUMMARY_PATTERNS = ( - re.compile(r"\brispett\w+\s+(?:il|lo|la)?\s*nuov\w+\s+schema\b", re.IGNORECASE), - re.compile(r"\baggiorn\w+\s+gli?\s+output\b", re.IGNORECASE), - re.compile(r"\bcorregg\w+\s+i\s+dati\b", re.IGNORECASE), - re.compile(r"\balline\w+\s+(?:gli?\s+output|lo\s+schema|i\s+dati)\b", re.IGNORECASE), - re.compile(r"\badatt\w+\s+(?:gli?\s+output|lo\s+schema|i\s+dati)\b", re.IGNORECASE), -) -OBSERVABLE_CONTRACT_PATTERNS = { - "column-order": re.compile( - r"\b(column|columns|colonn\w*|order|ordine|first|before|prime?\s+colonne|prima\s+di)\b", - re.IGNORECASE, - ), - "required-field": re.compile( - r"\b(required|mandatory|obbligator\w*|must\s+never\s+be\s+empty|never\s+be\s+empty|sempre\s+valorizzat\w*|mai\s+vuot\w*|non\s+vuot\w*|always\s+populated)\b", - re.IGNORECASE, - ), - "diagnostic": re.compile( - r"\b(diagnostic\w*|bloccant\w*|blocking|must\s+fail|fails?\b|errore|errori|error)\b", - re.IGNORECASE, - ), - "data-integrity": re.compile( - r"\b(gap|missing|rows?\b|righe\b|row[- ]count|data[- ]loss|perdita\s+dati|nessuna\s+perdita|buco\s+dati)\b", - re.IGNORECASE, - ), - "contract-output": re.compile(r"\b(schema|output|contract|generated|field|fields|colum\w*)\b", re.IGNORECASE), -} -IDENTIFIER_SIGNAL_RE = re.compile(r"\b[a-z][a-z0-9]*_[a-z0-9_]+\b") -NUMERIC_RANGE_RE = re.compile(r"\b\d+\s*-\s*\d+\b") -NUMERIC_SIGNAL_RE = re.compile(r"\b\d+\b") - - -@dataclass -class Finding: - code: str - message: str - severity: str = "ERROR" - - -def classify_profile(plan_folder: Path) -> str: - compact_path = plan_folder / "02-execution.md" - control_path = plan_folder / "02-control.md" - - if compact_path.is_file(): - text = compact_path.read_text(encoding="utf-8") - if re.search(r"Plan profile[:\s]+compact", text): - return "compact" - - if control_path.is_file(): - text = control_path.read_text(encoding="utf-8") - if re.search(r"Plan profile[:\s]+extended", text): - return "extended" - - return "unsupported" - - -def _check_unsupported(plan_folder: Path) -> tuple[str | None, list[Finding]]: - profile = classify_profile(plan_folder) - if profile == "unsupported": - return None, [ - Finding( - "unsupported-plan-contract", - f"Plan folder {plan_folder} has no supported Plan profile (compact or extended)", - ) - ] - return profile, [] - - -def _normalize_text(text: str) -> str: - normalized = text.replace("`", " ") - normalized = re.sub(r"\|[- :]+\|", " ", normalized) - normalized = re.sub(r"[^\w\s/-]", " ", normalized) - return re.sub(r"\s+", " ", normalized).strip() - - -def _split_ledger_row(row: str) -> list[str]: - stripped = row.strip().strip("|").strip() - return [cell.strip() for cell in re.split(r"\s+\|\s+", stripped)] - - -def _is_placeholder(text: str) -> bool: - normalized = _normalize_text(text) - if not normalized: - return True - if PLACEHOLDER_RE.search(normalized): - return True - lowered = normalized.lower() - if lowered in {"-", "none", "n/a"}: - return True - return len([token for token in lowered.split() if token not in {"-", "todo", "tbd", "x"}]) == 0 - - -def _section_body(text: str, heading: str) -> str: - pattern = re.compile( - rf"^##\s+{re.escape(heading)}\s*$([\s\S]*?)(?=^##\s+|\Z)", - re.MULTILINE, - ) - match = pattern.search(text) - return match.group(1).strip() if match else "" - - -def _check_required_section_content( - findings: list[Finding], text: str, heading: str, *, code_prefix: str -) -> None: - body = _section_body(text, heading) - if not body: - findings.append(Finding(f"{code_prefix}-missing-section", f"Missing section content for: {heading}")) - return - if _is_placeholder(body): - findings.append(Finding(f"{code_prefix}-placeholder", f"Section contains placeholder-only content: {heading}")) - - -def _validate_folder_name(plan_folder: Path, profile: str) -> list[Finding]: - parts = plan_folder.parts - if "tmp" not in parts: - return [] - folder_name = plan_folder.name.strip().lower() - if profile == "compact" and not folder_name.startswith(COMPACT_FOLDER_PREFIX): - return [ - Finding( - "compact-folder-prefix-required", - f"Compact retained-plan folders must use mini-plan-*; got {plan_folder.name}", - ) - ] - if folder_name in GENERIC_TMP_NAMES: - return [ - Finding( - "unclear-temp-directory-name", - f"Temporary plan folder name must communicate action or context: {plan_folder.name}", - ) - ] - tokens = [token for token in re.split(r"[-_]+", folder_name) if token] - if len(tokens) < 2: - return [ - Finding( - "unclear-temp-directory-name", - f"Temporary plan folder name must communicate action or context: {plan_folder.name}", - ) - ] - return [] - - -def _validate_summary(summary_text: str) -> list[Finding]: - findings: list[Finding] = [] - for section in ITALIAN_SUMMARY_SECTIONS: - body = _section_body(summary_text, section) - if not body: - findings.append(Finding("missing-summary-section", f"Summary missing Italian section: {section}", "WARNING")) - continue - if section == "Decisioni aperte" and _normalize_text(body).lower() in { - "none", - "- none", - "nessuna", - "nessuno", - }: - continue - if _is_placeholder(body): - findings.append(Finding("placeholder-summary-section", f"Summary section contains placeholder-only content: {section}")) - if not RESOURCE_TABLE_HEADER_RE.search(summary_text): - findings.append(Finding("missing-resource-table", "Summary missing Risorsa | Azione | Scopo table header", "WARNING")) - return findings - - -def _summary_focus_text(summary_text: str) -> str: - parts = [ - _section_body(summary_text, "Risultato atteso"), - _section_body(summary_text, "Risorse coinvolte"), - ] - return "\n".join(part for part in parts if part).strip() - - -def _identifier_signals(text: str) -> set[str]: - return {match.lower() for match in IDENTIFIER_SIGNAL_RE.findall(text)} - - -def _observable_contract_categories(text: str) -> set[str]: - return { - name - for name, pattern in OBSERVABLE_CONTRACT_PATTERNS.items() - if pattern.search(text) - } - - -def _coverage_text(plan_folder: Path, profile: str) -> str: - if profile == "compact": - path = plan_folder / "02-execution.md" - heading = "Source item coverage" - else: - path = plan_folder / "02-control.md" - heading = "Source item ledger" - if not path.is_file(): - return "" - return _section_body(path.read_text(encoding="utf-8"), heading) - - -def _has_observable_contract_requirements(coverage_text: str) -> bool: - if not coverage_text: - return False - categories = _observable_contract_categories(coverage_text) - specific_categories = categories - {"contract-output"} - if _identifier_signals(coverage_text): - return True - if len(specific_categories) >= 2: - return True - return bool(specific_categories and "contract-output" in categories) - - -def _has_concrete_counter_validation_examples(summary_focus: str, coverage_text: str) -> bool: - coverage_identifiers = _identifier_signals(coverage_text) - summary_identifiers = _identifier_signals(summary_focus) - if coverage_identifiers & summary_identifiers: - return True - - summary_categories = _observable_contract_categories(summary_focus) - {"contract-output"} - if len(summary_categories) >= 2: - return True - if summary_categories and NUMERIC_RANGE_RE.search(summary_focus): - return True - if summary_categories and NUMERIC_SIGNAL_RE.search(summary_focus): - return True - return False - - -def _validate_counter_validation_summary(plan_folder: Path, profile: str) -> list[Finding]: - summary_path = plan_folder / "01-change-summary.md" - if not summary_path.is_file(): - return [] - - summary_text = summary_path.read_text(encoding="utf-8") - summary_focus = _summary_focus_text(summary_text) - if not summary_focus: - return [] - - coverage_text = _coverage_text(plan_folder, profile) - if not _has_observable_contract_requirements(coverage_text): - return [] - - if not any(pattern.search(summary_focus) for pattern in GENERIC_SUMMARY_PATTERNS): - return [] - - if _has_concrete_counter_validation_examples(summary_focus, coverage_text): - return [] - - examples = sorted(_identifier_signals(coverage_text))[:3] - example_suffix = f" Example signals: {', '.join(examples)}." if examples else "" - return [ - Finding( - "summary-missing-counter-validation-facts", - "Summary uses generic schema/output/data wording while the source-item coverage contains observable contract requirements. Keep the critical user-visible result criteria in Risultato atteso or Risorse coinvolte instead of only generic summary formulas." - + example_suffix, - "WARNING", - ) - ] - - -def _normalize_route_cell(route_cell: str) -> str: - normalized = route_cell.replace("`", " ") - normalized = re.sub(r"\s+", " ", normalized) - return normalized.strip() - - -def _extract_route_files(route_cell: str) -> list[str]: - return re.findall(r"\b\d{2}-[A-Za-z0-9._-]+\.md\b", route_cell) - - -def _validate_coverage_rows( - findings: list[Finding], - rows_section: str, - *, - plan_folder: Path, - code_prefix: str, -) -> None: - rows = [ - line - for line in rows_section.splitlines() - if line.strip().startswith("|") and "Source item" not in line and "---" not in line - ] - if not rows: - findings.append(Finding("missing-source-item-coverage", "Source item coverage has no executable rows")) - return - - for row in rows: - cells = _split_ledger_row(row) - if len(cells) < 7 or any(_is_placeholder(cell) for cell in cells[:6]): - findings.append(Finding(f"{code_prefix}-placeholder-row", f"Coverage row is incomplete or placeholder-only: {row}")) - break - route_cell = _normalize_route_cell(cells[6]) - if not route_cell: - findings.append(Finding("missing-ledger-route", f"Coverage row route is empty: {row}")) - continue - if route_cell.lower() in NON_ACTION_ROUTES: - continue - referenced_files = _extract_route_files(route_cell) - if not referenced_files: - findings.append( - Finding( - "invalid-ledger-route", - f"Coverage row route must reference numbered files or explicit non-action routes: {cells[6]}", - ) - ) - continue - missing = [file_name for file_name in referenced_files if not (plan_folder / file_name).is_file()] - if missing: - findings.append( - Finding( - "missing-route-target", - f"Coverage route references missing numbered files: {', '.join(missing)}", - ) - ) - - -def _validate_control_file(plan_folder: Path) -> list[Finding]: - findings: list[Finding] = [] - control_path = plan_folder / "02-control.md" - if not control_path.is_file(): - return [Finding("missing-control", "02-control.md is missing")] - - control_text = control_path.read_text(encoding="utf-8") - for field in CONTROL_REQUIRED_FIELDS: - if field not in control_text: - findings.append(Finding("missing-control-fields", f"Missing control field: {field}")) - - for heading in ( - "Initial evidence pass", - "Reading budget", - "Owner and validator", - "Stop conditions", - "Source item ledger", - ): - _check_required_section_content(findings, control_text, heading, code_prefix="control") - - target_body = _section_body(control_text, "Target and anti-scope") - if not target_body: - findings.append(Finding("control-missing-section", "Missing section content for: Target and anti-scope")) - else: - for subheading in ("Target", "Anti-scope"): - pattern = re.compile( - rf"^###\s+{re.escape(subheading)}\s*$([\s\S]*?)(?=^###\s+|^##\s+|\Z)", - re.MULTILINE, - ) - match = pattern.search(target_body) - body = match.group(1).strip() if match else "" - if not body: - findings.append(Finding("control-missing-subsection", f"Missing subsection content for: {subheading}")) - elif _is_placeholder(body): - findings.append(Finding("control-placeholder", f"Subsection contains placeholder-only content: {subheading}")) - - source_item_ledger_body = _section_body(control_text, "Source item ledger") - _validate_coverage_rows(findings, source_item_ledger_body, plan_folder=plan_folder, code_prefix="control") - - for heading in MERGED_CONTRACT_SECTIONS: - _check_required_section_content(findings, control_text, heading, code_prefix="control-contract") - - validation_commands = _section_body(control_text, "Validation commands") - has_order_signal = bool(re.search(r"run in this order", validation_commands, re.IGNORECASE)) or bool( - re.search(r"^\s*\d+\.\s+", validation_commands, re.MULTILINE) - ) - if validation_commands and not has_order_signal: - findings.append( - Finding( - "control-validation-order", - "Extended validation commands must include execution order (numbered list or 'Run in this order').", - ) - ) - - external_pins = _section_body(control_text, "External pins") - if external_pins and _is_placeholder(external_pins): - findings.append( - Finding( - "missing-external-evidence-pin", - "Extended control contract must name an external pin, explicit no-external-evidence statement, or fallback", - ) - ) - - return findings - - -def _extract_step_blocks(execution_text: str) -> list[tuple[str, str]]: - body = _section_body(execution_text, "Executable steps") - if not body: - return [] - matches = list(EXECUTABLE_STEP_RE.finditer(body)) - blocks: list[tuple[str, str]] = [] - for index, match in enumerate(matches): - start = match.start() - end = matches[index + 1].start() if index + 1 < len(matches) else len(body) - blocks.append((match.group(1).strip(), body[start:end].strip())) - return blocks - - -def _validate_execution_file( - path: Path, - *, - code_prefix: str, - require_coverage: bool, - plan_folder: Path, -) -> list[Finding]: - findings: list[Finding] = [] - if not path.is_file(): - return [Finding(f"missing-{code_prefix}", f"{path.name} is missing")] - - text = path.read_text(encoding="utf-8") - - for heading in ("Objective", "Chosen logic", "Key assumptions", "Executable steps", "Validation"): - _check_required_section_content(findings, text, heading, code_prefix=code_prefix) - - step_blocks = _extract_step_blocks(text) - if not step_blocks: - findings.append(Finding("missing-executable-steps", f"{path.name} has no numbered executable steps")) - return findings - - created_artifacts: dict[str, int] = {} - consumed_before_create: list[str] = [] - for index, (title, block) in enumerate(step_blocks, start=1): - if _is_placeholder(title): - findings.append(Finding("placeholder-step-title", f"Executable step {index} title is placeholder-only")) - for label in ("Target:", "Acceptance:", "Validation:", "Fallback:"): - label_match = re.search(rf"{re.escape(label)}\s*(.+)", block) - if label_match is None: - findings.append(Finding("incomplete-executable-step", f"Executable step {index} is missing {label.rstrip(':')}")) - continue - if _is_placeholder(label_match.group(1)): - findings.append(Finding("placeholder-executable-step", f"Executable step {index} has placeholder-only {label.rstrip(':')}")) - create_match = re.search(r"Creates:\s*(.+)", block) - if create_match: - created_artifacts[create_match.group(1).strip()] = index - consume_match = re.search(r"Consumes:\s*(.+)", block) - if consume_match: - artifact = consume_match.group(1).strip() - created_at = created_artifacts.get(artifact) - if created_at is None: - consumed_before_create.append(artifact) - - for artifact in consumed_before_create: - findings.append(Finding("impossible-execution-order", f"Execution consumes artifact before any step creates it: {artifact}")) - - if require_coverage: - _check_required_section_content(findings, text, "Source item coverage", code_prefix=code_prefix) - source_item_coverage = _section_body(text, "Source item coverage") - _validate_coverage_rows(findings, source_item_coverage, plan_folder=plan_folder, code_prefix=code_prefix) - - return findings - - -def _validate_compact_execution(plan_folder: Path) -> list[Finding]: - path = plan_folder / "02-execution.md" - findings = _validate_execution_file( - path, - code_prefix="execution", - require_coverage=True, - plan_folder=plan_folder, - ) - - if not path.is_file(): - return findings - - text = path.read_text(encoding="utf-8") - for heading in ("Plan profile", "Target and anti-scope", "Owner and validator", "Stop conditions"): - _check_required_section_content(findings, text, heading, code_prefix="execution") - - if "Plan profile" in text and not re.search(r"Plan profile[:\s]+compact", text): - findings.append(Finding("profile-mismatch", "02-execution.md must declare Plan profile: compact")) - - return findings - - -def _validate_extended_execution(plan_folder: Path) -> list[Finding]: - return _validate_execution_file( - plan_folder / "03-execution.md", - code_prefix="execution", - require_coverage=False, - plan_folder=plan_folder, - ) - - -def _validate_lower_context_compatibility(plan_folder: Path, profile: str) -> list[Finding]: - findings: list[Finding] = [] - summary_text = (plan_folder / "01-change-summary.md").read_text(encoding="utf-8") - - if _is_placeholder(_section_body(summary_text, "Risultato atteso")): - findings.append(Finding("lower-context-compatible", "Expected outcome is not concrete enough for a lower-context executor")) - - if profile == "compact": - execution_text = (plan_folder / "02-execution.md").read_text(encoding="utf-8") - if _is_placeholder(_section_body(execution_text, "Stop conditions")): - findings.append(Finding("lower-context-compatible", "Stop conditions are not concrete enough for a lower-context executor")) - if not _extract_step_blocks(execution_text): - findings.append(Finding("lower-context-compatible", "Execution file does not show where to start")) - else: - control_text = (plan_folder / "02-control.md").read_text(encoding="utf-8") - execution_text = (plan_folder / "03-execution.md").read_text(encoding="utf-8") - if _is_placeholder(_section_body(control_text, "Stop conditions")): - findings.append(Finding("lower-context-compatible", "Stop conditions are not concrete enough for a lower-context executor")) - if not _extract_step_blocks(execution_text): - findings.append(Finding("lower-context-compatible", "Execution file does not show where to start")) - if _is_placeholder(_section_body(control_text, "Candidate targets")): - findings.append(Finding("lower-context-compatible", "Extended plan does not name concrete candidate targets")) - if _is_placeholder(_section_body(control_text, "Validation commands")): - findings.append(Finding("lower-context-compatible", "Extended plan does not name completion evidence or validation commands")) - - return findings - - -def _validate(plan_folder: Path, profile: str) -> list[Finding]: - findings: list[Finding] = [] - required = EXTENDED_REQUIRED_FILES if profile == "extended" else COMPACT_REQUIRED_FILES - for name in sorted(required): - if not (plan_folder / name).is_file(): - findings.append(Finding("missing-required-files", f"Missing required file: {name}")) - - findings.extend(_validate_folder_name(plan_folder, profile)) - - summary_path = plan_folder / "01-change-summary.md" - if summary_path.is_file(): - findings.extend(_validate_summary(summary_path.read_text(encoding="utf-8"))) - else: - findings.append(Finding("missing-summary", "01-change-summary.md is missing")) - - if profile == "compact": - findings.extend(_validate_compact_execution(plan_folder)) - else: - findings.extend(_validate_control_file(plan_folder)) - findings.extend(_validate_extended_execution(plan_folder)) - - findings.extend(_validate_counter_validation_summary(plan_folder, profile)) - - return findings - - -def cmd_init(plan_folder: Path, profile: str = "compact") -> int: - if plan_folder.exists(): - print(f"ERROR: {plan_folder} already exists", file=sys.stderr) - return 1 - - folder_findings = _validate_folder_name(plan_folder, profile) - if folder_findings: - print(f"ERROR: {folder_findings[0].message}", file=sys.stderr) - return 1 - - plan_folder.mkdir(parents=True) - - (plan_folder / "01-change-summary.md").write_text( - textwrap.dedent( - """\ - # Sintesi delle modifiche - - ## Problema da risolvere - - TODO - - ## Risultato atteso - - TODO - - ## Risorse coinvolte - - | Risorsa | Azione | Scopo | - | --- | --- | --- | - | TBD | TBD | TBD | - - ## Decisione richiesta - - TODO - - ## Decisioni aperte - - none - """ - ), - encoding="utf-8", - ) - - if profile == "compact": - (plan_folder / "02-execution.md").write_text( - textwrap.dedent( - """\ - # Execution - - ## Plan profile - - compact - - ## Target and anti-scope - - ### Target - - TODO - - ### Anti-scope - - TODO - - ## Owner and validator - - TODO - - ## Stop conditions - - TODO - - ## Objective - - TODO - - ## Chosen logic - - TODO - - ## Key assumptions - - TODO - - ## Executable steps - - 1. Define the first executable step. - Target: TODO - Acceptance: TODO - Validation: TODO - Fallback: TODO - - ## Validation - - TODO - - ## Source item coverage - - | ID | Source item | Observable acceptance | Evidence class | Acceptance evidence | Status | Route | - | --- | --- | --- | --- | --- | --- | --- | - | TBD-01 | TODO | TODO | repository | TODO | PENDING | `02-execution.md` | - """ - ), - encoding="utf-8", - ) - else: - (plan_folder / "02-control.md").write_text( - textwrap.dedent( - """\ - # Control - - ## Recommended use - - execute after explicit approval - - ## Plan profile - - extended - - ## File map and role - - | File | Role | - | --- | --- | - | `01-change-summary.md` | Italian decision summary; non-executable | - | `02-control.md` | Authoritative control, merged contract, and source-item coverage | - | `03-execution.md` | Executable steps | - - ## Clarification gate - - clarification required - - ## Initial evidence pass - - TODO - - ## Reading budget - - TODO - - ## Target and anti-scope - - ### Target - - TODO - - ### Anti-scope - - TODO - - ## Owner and validator - - TODO - - ## Stop conditions - - TODO - - ## Sources - - TODO - - ## Candidate targets - - TODO - - ## Validation commands - - Run in this order: - 1. TODO - - ## Blockers and fallback rules - - TODO - - ## External pins - - no external evidence - - ## Source item ledger - - | ID | Source item | Observable acceptance | Evidence class | Acceptance evidence | Status | Route | - | --- | --- | --- | --- | --- | --- | --- | - | TBD-01 | TODO | TODO | repository | TODO | PENDING | `03-execution.md` | - """ - ), - encoding="utf-8", - ) - (plan_folder / "03-execution.md").write_text( - textwrap.dedent( - """\ - # Execution - - ## Objective - - TODO - - ## Chosen logic - - TODO - - ## Key assumptions - - TODO - - ## Executable steps - - 1. Define the first executable step. - Target: TODO - Acceptance: TODO - Validation: TODO - Fallback: TODO - - ## Validation - - TODO - """ - ), - encoding="utf-8", - ) - - print(f"Created plan folder: {plan_folder}") - return 0 - - -def _token_warnings(plan_folder: Path, profile: str | None = None) -> list[str]: - file_tokens: list[tuple[str, int]] = [] - total_tokens = 0 - for md_path in sorted(plan_folder.glob("*.md")): - tokens = math.ceil(md_path.stat().st_size / 4) - file_tokens.append((md_path.name, tokens)) - total_tokens += tokens - - warnings: list[str] = [] - if profile is None: - detected = classify_profile(plan_folder) - profile = detected if detected in SUPPORTED_PROFILES else None - - token_map = {name: tokens for name, tokens in file_tokens} - - if profile == "compact": - summary_tokens = token_map.get("01-change-summary.md", 0) - execution_tokens = token_map.get("02-execution.md", 0) - if total_tokens > COMPACT_TOTAL_TOKEN_TARGET: - warnings.append("Compact plan exceeds the 2,000 estimated-token budget; compress or escalate to extended.") - if summary_tokens > COMPACT_SUMMARY_TOKEN_TARGET: - warnings.append("Compact 01-change-summary.md is oversized; keep it to the Italian decision capsule only.") - if execution_tokens > COMPACT_EXECUTION_TOKEN_TARGET: - warnings.append("Compact execution file is oversized; keep 02-execution.md under the compact slice cap or escalate to extended.") - else: - control_names = {"01-change-summary.md", "02-control.md"} - control_tokens = sum(tokens for name, tokens in file_tokens if name in control_names) - if total_tokens and control_tokens / total_tokens > 0.7: - warnings.append("Initial control read is disproportionately large; prefer splitting control facts into numbered files by delivery slice.") - - for name, tokens in file_tokens: - if tokens > 1200: - if profile == "extended": - warnings.append( - f"Informational: estimated token weight is high for {name}; prefer splitting into numbered files by delivery slice." - ) - else: - warnings.append(f"Estimated token weight is high for {name}; split or compress by delivery slice.") - - return warnings - - -def _questions_blocking(summary_text: str) -> bool: - section = _section_body(summary_text, "Decisioni aperte") - if not section: - return False - normalized = _normalize_text(section).lower() - return normalized not in {"none", "- none", "nessuna", "nessuno"} - - -def cmd_audit(plan_folder: Path, format: str = "text") -> int: - profile, findings = _check_unsupported(plan_folder) - if profile is None: - _emit_findings(findings, format, warnings=[]) - return 1 - - findings.extend(_validate(plan_folder, profile)) - _emit_findings(findings, format, warnings=_token_warnings(plan_folder, profile)) - return 0 if not any(f.severity == "ERROR" for f in findings) else 1 - - -def cmd_handoff_check(plan_folder: Path, format: str = "text") -> int: - profile, findings = _check_unsupported(plan_folder) - if profile is None: - _emit_findings(findings, format, warnings=[]) - return 1 - - findings.extend(_validate(plan_folder, profile)) - - if profile == "extended": - control_path = plan_folder / "02-control.md" - if control_path.is_file(): - control_text = control_path.read_text(encoding="utf-8") - if "clarification required" in control_text: - findings.append(Finding("clarification-required", "Clarification gate is still required")) - - summary_path = plan_folder / "01-change-summary.md" - if summary_path.is_file() and _questions_blocking(summary_path.read_text(encoding="utf-8")): - findings.append(Finding("open-questions-blocking", "Decisioni aperte must be 'none' before execution handoff")) - - findings.extend(_validate_lower_context_compatibility(plan_folder, profile)) - _emit_findings(findings, format, warnings=_token_warnings(plan_folder, profile)) - return 0 if not any(f.severity == "ERROR" for f in findings) else 1 - - -def cmd_tokens(plan_folder: Path, format: str = "text") -> int: - total_bytes = 0 - file_tokens: list[tuple[str, int]] = [] - for md_path in sorted(plan_folder.glob("*.md")): - size = md_path.stat().st_size - total_bytes += size - tokens = math.ceil(size / 4) - file_tokens.append((md_path.name, tokens)) - total_tokens = math.ceil(total_bytes / 4) - detected = classify_profile(plan_folder) - warnings = _token_warnings(plan_folder, detected if detected in SUPPORTED_PROFILES else None) - if format == "json": - json.dump( - { - "plan_folder": str(plan_folder), - "total_tokens_estimate": total_tokens, - "files": [{"name": name, "tokens": tok} for name, tok in file_tokens], - "warnings": warnings, - }, - sys.stdout, - indent=2, - ) - else: - print(f"Plan folder: {plan_folder}") - print(f"Total estimated tokens: {total_tokens}") - for warning in warnings: - print(f"WARNING: {warning}") - return 0 - - -def _emit_findings(findings: list[Finding], format: str, *, warnings: list[str]) -> None: - if format == "json": - json.dump( - { - "findings": [f.__dict__ for f in findings], - "warnings": warnings, - "ready": not any(f.severity == "ERROR" for f in findings), - }, - sys.stdout, - indent=2, - ) - else: - if findings: - print("\n".join(f"[{f.severity}] {f.code}: {f.message}" for f in findings)) - else: - print("No findings.") - for warning in warnings: - print(f"[WARNING] token-guidance: {warning}") - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Portable retained-plan authoring CLI.") - sub = parser.add_subparsers(dest="command", required=True) - for command in ("audit", "handoff-check", "tokens"): - p = sub.add_parser(command) - p.add_argument("plan_folder", type=Path) - p.add_argument("--format", choices=("text", "json"), default="text") - init_p = sub.add_parser("init") - init_p.add_argument("plan_folder", type=Path) - init_p.add_argument("--profile", choices=("compact", "extended"), default="compact") - return parser.parse_args() - - -def main() -> int: - args = parse_args() - if args.command == "init": - return cmd_init(args.plan_folder, args.profile) - if args.command == "audit": - return cmd_audit(args.plan_folder, args.format) - if args.command == "handoff-check": - return cmd_handoff_check(args.plan_folder, args.format) - if args.command == "tokens": - return cmd_tokens(args.plan_folder, args.format) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tests/github/skills/internal_gateway_writing_plans/scripts/test_plan_authoring.py b/tests/github/skills/internal_gateway_writing_plans/scripts/test_plan_authoring.py deleted file mode 100644 index 2caaef5..0000000 --- a/tests/github/skills/internal_gateway_writing_plans/scripts/test_plan_authoring.py +++ /dev/null @@ -1,333 +0,0 @@ -from __future__ import annotations - -import json -import subprocess -import sys -from pathlib import Path - -AUTHORING_CLI = Path( - ".github/skills/internal-gateway-writing-plans/scripts/plan_authoring.py" -).resolve() - - -def run_cli(*args: str | Path) -> subprocess.CompletedProcess[str]: - return subprocess.run( - [sys.executable, str(AUTHORING_CLI), *[str(a) for a in args]], - capture_output=True, - text=True, - ) - - -def _write_compact_plan(plan_folder: Path) -> None: - plan_folder.mkdir(parents=True, exist_ok=True) - (plan_folder / "01-change-summary.md").write_text( - "## Problema da risolvere\nRidurre l'ambiguita di handoff.\n" - "## Risultato atteso\nUn executor puo partire senza riletture larghe.\n" - "## Risorse coinvolte\n| Risorsa | Azione | Scopo |\n| --- | --- | --- |\n| Skill | update | Tighten handoff |\n" - "## Decisione richiesta\nApprovare l'esecuzione del piano.\n" - "## Decisioni aperte\nnone\n", - encoding="utf-8", - ) - (plan_folder / "02-execution.md").write_text( - "# Execution\n\n" - "## Plan profile\ncompact\n\n" - "## Target and anti-scope\n" - "### Target\n- Tighten plan handoff validation.\n" - "### Anti-scope\n- Do not add model policy.\n\n" - "## Owner and validator\n- Owner: internal-gateway-writing-plans.\n- Validator: pytest.\n\n" - "## Stop conditions\n- Stop if checks require subjective prose scoring.\n\n" - "## Objective\nDeliver a compact executable handoff contract.\n\n" - "## Chosen logic\nValidate semantic minima instead of heading presence only.\n\n" - "## Key assumptions\nThe bundle-local CLI remains stdlib-only.\n\n" - "## Executable steps\n" - "1. Tighten the semantic audit in `plan_authoring.py`.\n" - " Target: `.github/skills/internal-gateway-writing-plans/scripts/plan_authoring.py`\n" - " Acceptance: empty sections fail.\n" - " Validation: pytest -q tests/github/skills/internal_gateway_writing_plans/scripts/test_plan_authoring.py\n" - " Fallback: stop if the rule requires subjective prose scoring.\n\n" - "## Validation\n- Run the focused pytest target.\n\n" - "## Source item coverage\n" - "| ID | Source item | Observable acceptance | Evidence class | Acceptance evidence | Status | Route |\n" - "| --- | --- | --- | --- | --- | --- | --- |\n" - "| PLAN-01 | Empty-section detection | `handoff-check` rejects empty sections | repository | failing then passing test | PENDING | `02-execution.md` |\n", - encoding="utf-8", - ) - - -def _write_extended_plan(plan_folder: Path) -> None: - _write_compact_plan(plan_folder) - (plan_folder / "02-execution.md").unlink() - (plan_folder / "02-control.md").write_text( - "# Source Item Control\n\n" - "## Recommended use\nexecute after explicit approval\n\n" - "## Plan profile\nextended\n\n" - "## File map and role\n| File | Role |\n| --- | --- |\n| `03-execution.md` | executable |\n\n" - "## Clarification gate\nclarification satisfied\n\n" - "## Initial evidence pass\n1. Verified the bundle-local CLI exists.\n\n" - "## Reading budget\n- Start from summary, control, and execution only.\n\n" - "## Target and anti-scope\n### Target\n- Tighten plan handoff validation.\n### Anti-scope\n- Do not add model policy.\n\n" - "## Owner and validator\n- Owner: internal-gateway-writing-plans.\n- Validator: pytest.\n\n" - "## Stop conditions\n- Stop if checks require subjective prose scoring.\n\n" - "## Sources\n- `.github/skills/internal-gateway-writing-plans/scripts/plan_authoring.py`\n\n" - "## Candidate targets\n- The bundle-local authoring CLI and focused fixtures.\n\n" - "## Validation commands\nRun in this order:\n1. pytest -q tests/github/skills/internal_gateway_writing_plans/scripts/test_plan_authoring.py\n\n" - "## Blockers and fallback rules\n- Stop if validation would require subjective scoring.\n\n" - "## External pins\nno external evidence\n\n" - "## Source item ledger\n" - "| ID | Source item | Observable acceptance | Evidence class | Acceptance evidence | Status | Route |\n" - "| --- | --- | --- | --- | --- | --- | --- |\n" - "| PLAN-01 | Empty-section detection | `handoff-check` rejects empty sections | repository | failing then passing test | PENDING | `03-execution.md` |\n", - encoding="utf-8", - ) - (plan_folder / "03-execution.md").write_text( - "# Execution\n\n" - "## Objective\nDeliver an extended executable handoff contract.\n\n" - "## Chosen logic\nKeep control facts in 02-control and execution in 03-execution.\n\n" - "## Key assumptions\nThe bundle-local CLI remains stdlib-only.\n\n" - "## Executable steps\n" - "1. Tighten the semantic audit in `plan_authoring.py`.\n" - " Target: `.github/skills/internal-gateway-writing-plans/scripts/plan_authoring.py`\n" - " Acceptance: empty sections fail.\n" - " Validation: pytest -q tests/github/skills/internal_gateway_writing_plans/scripts/test_plan_authoring.py\n" - " Fallback: stop if the rule requires subjective prose scoring.\n\n" - "## Validation\n- Run the focused pytest target.\n", - encoding="utf-8", - ) - - -def test_init_creates_compact_scaffold(tmp_path: Path) -> None: - plan_folder = tmp_path / "tmp" / "superpowers" / "mini-plan-my-plan" - result = run_cli("init", plan_folder) - assert result.returncode == 0 - assert (plan_folder / "02-execution.md").is_file() - assert not (plan_folder / "questions.md").exists() - summary_text = (plan_folder / "01-change-summary.md").read_text(encoding="utf-8") - assert "## Decisioni aperte" in summary_text - assert "## Comportamento scelto" not in summary_text - assert "## Validazione prevista" not in summary_text - assert "## Esecuzione prevista" not in summary_text - - -def test_init_rejects_non_prefixed_compact_folder(tmp_path: Path) -> None: - plan_folder = tmp_path / "tmp" / "superpowers" / "handoff-plan" - result = run_cli("init", plan_folder) - assert result.returncode != 0 - assert "mini-plan-*" in result.stderr - - -def test_init_creates_extended_scaffold(tmp_path: Path) -> None: - plan_folder = tmp_path / "my-extended-plan" - result = run_cli("init", plan_folder, "--profile", "extended") - assert result.returncode == 0 - assert (plan_folder / "02-control.md").is_file() - assert (plan_folder / "03-execution.md").is_file() - - -def test_audit_compact_ready(tmp_path: Path) -> None: - plan_folder = ( - tmp_path / "tmp" / "superpowers" / "mini-plan-improve-handoff-contract" - ) - _write_compact_plan(plan_folder) - result = run_cli("audit", plan_folder, "--format", "json") - payload = json.loads(result.stdout) - assert result.returncode == 0 - assert payload["ready"] is True - - -def test_handoff_check_extended_ready(tmp_path: Path) -> None: - plan_folder = tmp_path / "lower-context-compatible-plan" - _write_extended_plan(plan_folder) - result = run_cli("handoff-check", plan_folder, "--format", "json") - payload = json.loads(result.stdout) - assert result.returncode == 0 - assert payload["ready"] is True - - -def test_handoff_check_rejects_placeholder_sections(tmp_path: Path) -> None: - plan_folder = tmp_path / "tmp" / "superpowers" / "mini-plan-placeholder-plan" - _write_compact_plan(plan_folder) - (plan_folder / "02-execution.md").write_text( - "# Execution\n\n## Plan profile\ncompact\n\n## Objective\nTODO\n", - encoding="utf-8", - ) - result = run_cli("handoff-check", plan_folder, "--format", "json") - payload = json.loads(result.stdout) - assert result.returncode == 1 - assert any( - f["code"] == "execution-placeholder" or f["code"] == "missing-executable-steps" - for f in payload["findings"] - ) - - -def test_handoff_check_rejects_missing_route_targets(tmp_path: Path) -> None: - plan_folder = tmp_path / "tmp" / "superpowers" / "mini-plan-missing-route-target" - _write_compact_plan(plan_folder) - execution_path = plan_folder / "02-execution.md" - execution_path.write_text( - execution_path.read_text(encoding="utf-8").replace( - "`02-execution.md`", "`07-non-existent.md`" - ), - encoding="utf-8", - ) - result = run_cli("handoff-check", plan_folder, "--format", "json") - payload = json.loads(result.stdout) - assert result.returncode == 1 - assert any(f["code"] == "missing-route-target" for f in payload["findings"]) - - -def test_handoff_check_warns_on_oversized_compact_execution(tmp_path: Path) -> None: - plan_folder = tmp_path / "tmp" / "superpowers" / "mini-plan-heavy-execution" - _write_compact_plan(plan_folder) - heavy_block = "A" * 6200 - (plan_folder / "02-execution.md").write_text( - "# Execution\n\n" - "## Plan profile\ncompact\n\n" - "## Target and anti-scope\n### Target\n- Keep compact validation deterministic.\n### Anti-scope\n- none\n\n" - "## Owner and validator\n- owner\n\n" - "## Stop conditions\n- none\n\n" - "## Objective\nKeep compact validation deterministic.\n\n" - "## Chosen logic\nLarge payload to trigger warning.\n\n" - "## Key assumptions\nThe test controls markdown size.\n\n" - "## Executable steps\n" - f"1. Trim oversized execution content. {heavy_block}\n" - " Target: `02-execution.md`\n" - " Acceptance: warning appears for compact profile.\n" - " Validation: handoff-check json output.\n" - " Fallback: escalate to extended profile.\n\n" - "## Validation\n- handoff-check\n\n" - "## Source item coverage\n" - "| ID | Source item | Observable acceptance | Evidence class | Acceptance evidence | Status | Route |\n" - "| --- | --- | --- | --- | --- | --- | --- |\n" - "| PLAN-01 | oversized warning | warning appears | validator | handoff-check | PENDING | `02-execution.md` |\n", - encoding="utf-8", - ) - result = run_cli("handoff-check", plan_folder, "--format", "json") - payload = json.loads(result.stdout) - assert result.returncode == 0 - assert any( - "Compact execution file is oversized" in warning - for warning in payload["warnings"] - ) - - -def test_handoff_check_warns_on_oversized_compact_summary(tmp_path: Path) -> None: - plan_folder = tmp_path / "tmp" / "superpowers" / "mini-plan-heavy-summary" - _write_compact_plan(plan_folder) - heavy_summary = "A" * 1400 - summary_path = plan_folder / "01-change-summary.md" - summary_path.write_text( - summary_path.read_text(encoding="utf-8").replace( - "Ridurre l'ambiguita di handoff.", - f"Ridurre l'ambiguita di handoff. {heavy_summary}", - ), - encoding="utf-8", - ) - result = run_cli("handoff-check", plan_folder, "--format", "json") - payload = json.loads(result.stdout) - assert result.returncode == 0 - assert any( - "Compact 01-change-summary.md is oversized" in warning - for warning in payload["warnings"] - ) - - -def test_audit_warns_when_summary_hides_counter_validation_facts( - tmp_path: Path, -) -> None: - plan_folder = ( - tmp_path / "tmp" / "superpowers" / "mini-plan-counter-validation-warning" - ) - _write_compact_plan(plan_folder) - (plan_folder / "01-change-summary.md").write_text( - "## Problema da risolvere\n" - "Allineare il piano a un nuovo contratto dati per output generati.\n" - "## Risultato atteso\n" - "- Gli output rigenerati rispettano il nuovo schema e correggono i dati.\n" - "## Risorse coinvolte\n" - "| Risorsa | Azione | Scopo |\n" - "| --- | --- | --- |\n" - "| Aggregate + slide outputs | update | Allineare gli output |\n" - "## Decisione richiesta\n" - "Approvare il piano.\n" - "## Decisioni aperte\n" - "none\n", - encoding="utf-8", - ) - execution_path = plan_folder / "02-execution.md" - execution_path.write_text( - execution_path.read_text(encoding="utf-8").replace( - "| PLAN-01 | Empty-section detection | `handoff-check` rejects empty sections | repository | failing then passing test | PENDING | `02-execution.md` |", - "| PLAN-01 | Aggregate and slide data contract | `impacted_platforms` and `impacted_subscriptions` become the first aggregate columns; `technology_or_service` stays before `retiring_feature`; `source_links` never stays empty and missing values trigger a blocking diagnostic; rows 27-33 are restored with no row-count loss | repository | audit plus focused file diff | PENDING | `02-execution.md` |", - ), - encoding="utf-8", - ) - - result = run_cli("audit", plan_folder, "--format", "json") - payload = json.loads(result.stdout) - - assert result.returncode == 0 - assert payload["ready"] is True - assert any( - f["code"] == "summary-missing-counter-validation-facts" - and f["severity"] == "WARNING" - for f in payload["findings"] - ) - - -def test_audit_accepts_summary_with_counter_validation_facts(tmp_path: Path) -> None: - plan_folder = tmp_path / "tmp" / "superpowers" / "mini-plan-counter-validation-ok" - _write_compact_plan(plan_folder) - (plan_folder / "01-change-summary.md").write_text( - "## Problema da risolvere\n" - "Allineare il piano a un nuovo contratto dati per output generati.\n" - "## Risultato atteso\n" - "- Aggregate TSV con `impacted_platforms` e `impacted_subscriptions` come prime colonne.\n" - "- `technology_or_service` resta prima di `retiring_feature`; `source_links` e sempre valorizzato oppure scatta una diagnostica bloccante.\n" - "- Le righe 27-33 vengono ripristinate senza perdita di righe.\n" - "## Risorse coinvolte\n" - "| Risorsa | Azione | Scopo |\n" - "| --- | --- | --- |\n" - "| Aggregate + slide outputs | update | Preservare ordine colonne, campi obbligatori e integrita dati |\n" - "## Decisione richiesta\n" - "Approvare il piano.\n" - "## Decisioni aperte\n" - "none\n", - encoding="utf-8", - ) - execution_path = plan_folder / "02-execution.md" - execution_path.write_text( - execution_path.read_text(encoding="utf-8").replace( - "| PLAN-01 | Empty-section detection | `handoff-check` rejects empty sections | repository | failing then passing test | PENDING | `02-execution.md` |", - "| PLAN-01 | Aggregate and slide data contract | `impacted_platforms` and `impacted_subscriptions` become the first aggregate columns; `technology_or_service` stays before `retiring_feature`; `source_links` never stays empty and missing values trigger a blocking diagnostic; rows 27-33 are restored with no row-count loss | repository | audit plus focused file diff | PENDING | `02-execution.md` |", - ), - encoding="utf-8", - ) - - result = run_cli("audit", plan_folder, "--format", "json") - payload = json.loads(result.stdout) - - assert result.returncode == 0 - assert payload["ready"] is True - assert not any( - f["code"] == "summary-missing-counter-validation-facts" - for f in payload["findings"] - ) - - -def test_handoff_check_rejects_extended_control_without_ordered_validation( - tmp_path: Path, -) -> None: - plan_folder = tmp_path / "extended-without-ordered-validation" - _write_extended_plan(plan_folder) - control_path = plan_folder / "02-control.md" - control_path.write_text( - control_path.read_text(encoding="utf-8").replace( - "Run in this order:\n1. pytest -q tests/github/skills/internal_gateway_writing_plans/scripts/test_plan_authoring.py", - "- pytest -q tests/github/skills/internal_gateway_writing_plans/scripts/test_plan_authoring.py", - ), - encoding="utf-8", - ) - result = run_cli("handoff-check", plan_folder, "--format", "json") - payload = json.loads(result.stdout) - assert result.returncode == 1 - assert any(f["code"] == "control-validation-order" for f in payload["findings"]) From c79b3cdaa0336dc1e03ddfea8526078f198d1284 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 23:33:27 +0200 Subject: [PATCH 13/44] Refactor tests to align with new gateway status file structure and remove legacy markers - Updated test_completion_report_contract.py to check for new status file content. - Refactored test_plan_policy_contract.py to validate new superpowers wrapper and reject legacy terms. - Modified test_retained_plan_artifact_contract.py to implement new gateway status file validation. - Cleaned up test_token_budget_skill_contract.py by removing unnecessary verification text checks. - Adjusted test_workflow_review_contract.py to reflect changes in execution owner and compliance audit checks. --- .../internal-gateway-execute-plans/SKILL.md | 253 ++----- .../agents/openai.yaml | 4 +- .../references/completion-report.md | 145 ---- .../references/plan-handoff.md | 103 +-- .../references/resume-protocol.md | 103 +-- .../references/status-file.md | 76 ++ .../scripts/plan_execution.py | 678 ++++-------------- .../scripts/test_plan_execution.py | 497 +++---------- tests/test_completion_report_contract.py | 7 +- tests/test_plan_policy_contract.py | 162 ++--- tests/test_retained_plan_artifact_contract.py | 218 +++--- tests/test_token_budget_skill_contract.py | 4 - tests/test_workflow_review_contract.py | 49 +- 13 files changed, 656 insertions(+), 1643 deletions(-) delete mode 100644 .github/skills/internal-gateway-execute-plans/references/completion-report.md create mode 100644 .github/skills/internal-gateway-execute-plans/references/status-file.md diff --git a/.github/skills/internal-gateway-execute-plans/SKILL.md b/.github/skills/internal-gateway-execute-plans/SKILL.md index 1f27227..03273a4 100644 --- a/.github/skills/internal-gateway-execute-plans/SKILL.md +++ b/.github/skills/internal-gateway-execute-plans/SKILL.md @@ -1,209 +1,76 @@ --- name: internal-gateway-execute-plans -description: Use when executing an approved compact or extended repository-owned plan from tmp/superpowers// and the numbered-file order, done-* loop, and blocker handling must stay explicit. +description: Use when executing an approved repository-owned retained plan and repo-local policy must wrap superpowers-executing-plans with a resumable status file. --- # Internal Gateway Execute Plans -## Referenced skills +gateway-only wrapper for approved retained plans. This skill does not replace `superpowers-executing-plans`; it activates before that skill and adds repository-local execution policy. -- `internal-gateway-writing-plans`: retained-plan authoring owner for plans that feed this execution loop. -- `superpowers-executing-plans`: imported step-by-step execution engine when local policy is settled. -- `superpowers-subagent-driven-development`: imported worker-isolation engine when same-session subagents are available. -- `superpowers-verification-before-completion`: evidence gate before item completion and final retained-plan completion claims. +## Referenced Skills -Repository-owned wrapper for applying retained numbered plans. This owner -consumes approved `compact` and `extended` retained plans. +- `superpowers-executing-plans`: required execution engine for the plan steps. +- `superpowers-subagent-driven-development`: preferred when same-session subagents are available and the execution path supports them. +- `superpowers-verification-before-completion`: evidence gate before completion claims when fresh validation is required. +- `internal-gateway-writing-plans`: owner of retained-plan authoring contracts. +- `internal-gateway-review`: owner when the next risk is correctness evidence. -## When to use +## When To Use -- Executing approved `compact` retained plans from `tmp/superpowers/mini-plan-*`. -- Executing approved `extended` retained plans from `tmp/superpowers//`. -- Applying a `compact` or `extended` plan authored with `internal-gateway-writing-plans`. +- Executing approved retained plans under `tmp/superpowers/`. +- Resuming approved retained plans that were previously stopped through this gateway. +- Applying repo-local status-file policy before using `superpowers-executing-plans`. -## When not to use +## When Not To Use +- Writing or reformulating a plan; use `internal-gateway-writing-plans`. - Reviewing or challenging a plan; use `internal-gateway-review` or `internal-gateway-critical-master`. -- Treating `questions.md` as an executable file. - -## Core Algorithm - -1. Read `01-change-summary.md` first. -2. For `compact`, read `02-execution.md`; for `extended`, read `02-control.md`. -3. Verify `Plan profile: compact` or `Plan profile: extended`, then infer the execution strategy from profile, folder shape, and validation path. -4. Establish execution state from summary, control file, and active validator outcomes; keep it compact and update it as items close. -5. Avoid repeated full rereads: inspect changed sections, active executable item context, and failing-validator evidence first. -6. Run the ledger evidence pass. -7. Identify mandatory applicable requirements from selected skills using target, runtime, ownership, and validation path. -8. For `compact`, confirm the folder uses `mini-plan-*` and `02-execution.md` is the only executable file. -9. For `extended`, read numbered executable files after `02-control.md` in order starting at `03-execution.md`. -10. Process numbered executable files in order with the Agentic Execution Loop. -11. Run an item-level compliance audit before closing each executable item. -12. Track progress through the live ledger. -13. Aggregate unresolved mandatory applicable requirements before closeout. -14. Determine the completion state using the Closing Procedure state tree. -15. Package closeout only for `DONE`; for all other states, write a `-plan-state.md` marker and keep the live ledger. - -## Execution Contract - -- Reject unsupported profiles immediately. -- Reject `compact` folders outside the `mini-plan-*` convention. -- Ignore `questions.md` during execution. -- Maintain a compact execution state and prefer targeted rereads over full file re-ingestion unless new evidence invalidates current state. -- Use `Compact Evidence Reporting` for large validator output: read enough output to decide the state honestly, then retain command, exit code, material counts, header or schema checks, changed files, and exact gaps instead of pasting raw logs. -- Infer the execution strategy from `Plan profile`, folder shape, merged control-contract sections in `02-control.md` when applicable, and the validation path. Do not require a separate retained-plan consumer field. -- Audit only mandatory requirements that are applicable; do not convert specialist rules into universal policy. -- Use `superpowers-verification-before-completion` as the fresh-evidence owner; do not duplicate its mechanics. -- Block item closure and block `SHIPPED` whenever mandatory applicable requirements remain unverified. -- Escalate architecture ownership conflicts, cross-owner skill conflicts, and undefined validation strategy. -- Pressure-test boundary: a Lambda-owned hashed requirements file does not grant a separate stdlib-only CLI launcher permission to install that dependency set. -- Only `DONE` may create `done-*` markers or remove numbered plan files, or publish a lightweight `DONE-plan-state.md` marker (`-plan-state.md` convention) with `State: DONE` and `Continuation: none`. -- Non-`DONE` exits keep the live ledger and numbered files in place. - -## Agentic Execution Loop - -When execution is already authorized, stay inside the active owner, target -scope, anti-scope, and validation path, then iterate: - -1. Confirm the current goal and nearest evidence. -2. Apply the smallest in-scope action. -3. Run the focused validation or evidence check. -4. If validation fails for an in-scope, repairable reason, fix once and re-run. -5. Continue only while evidence improves and no stop condition fires. -6. Stop with `DONE`, a blocker, or an explicit evidence gap. - -Apply `Compact Evidence Reporting` after each focused validation: preserve the -exact gap and proof path, but keep large outputs summarized unless the raw -output is itself the missing evidence. - -Stop on scope drift, destructive action, owner conflict, missing validation -path, human approval need, secret exposure risk, or repeated non-improving -failures. - -## Closing Procedure - -A retained plan folder must end every execution session in exactly one explicit -state. The agent determines the state from evidence, not from intent. Use the -state tree below, then apply the matching per-state actions. - -### State Determination - -Evaluate the following checks in order and stop at the first match: - -1. **Cancelled?** The user explicitly decided to abandon the plan, and the - decision is recorded as `INTENTIONAL_NON_ACTION` with a documented reason in - the ledger. State: `CANCELLED`. -2. **Blocked?** A real, evidenced blocker prevents safe continuation. State: - `BLOCKED`. -3. **Rolled back?** Applied work was reverted or superseded by a different safe - state, and the original ledger rows are closed with evidence. State: - `ROLLED_BACK`. -4. **Done?** All in-scope ledger rows are closed, validators pass, mandatory - applicable evidence is verified, and no open blocker remains. State: `DONE`. -5. **Applied but unverified?** Edits were applied, but a required validator, - review, or evidence coverage is missing. State: `APPLIED_UNVERIFIED`. -6. **Partial?** Some in-scope items remain incomplete or intentionally deferred. - State: `PARTIAL`. - -If the ledger is absent, stale, or cannot be reconstructed, the state cannot be -`DONE`; use `APPLIED_UNVERIFIED` or `PARTIAL` and report the exact gap. - -### Per-State Actions - -| State | Numbered files | Live ledger | `done-*` markers | `-plan-state.md` | `completion-report.md` | -| --- | --- | --- | --- | --- | --- | -| `DONE` | May be removed after evidence is stable | Finalized and retained | Required for full packaging | Optional lightweight marker (`DONE-plan-state.md`) | Required for full packaging, optional for lightweight | -| `APPLIED_UNVERIFIED` | Keep in place | Keep in place | Do not create | Required | Optional but recommended | -| `PARTIAL` | Keep in place | Keep in place | Do not create | Required | Optional but recommended | -| `BLOCKED` | Keep in place | Keep in place | Do not create | Required | Optional but recommended | -| `ROLLED_BACK` | Keep in place | Keep in place | Do not create | Required | Optional but recommended | -| `CANCELLED` | Keep in place | Keep in place | Do not create | Required | Optional but recommended | - -`done-*` markers and numbered-file removal are exclusive to `DONE`. Every other -state is a live-folder state and must preserve the retained plan for resume or -manual continuation. - -### Lightweight Marker Contract - -A lightweight marker is a file named `-plan-state.md` in the plan folder. -It is a terminal marker only when `State: DONE` and `Continuation: none`. For -non-`DONE` states it documents the current live-folder state. - -Required fields: - -- `State:` exactly the state encoded in the filename. -- `Continuation:` `none` for `DONE`; `continuing` or `waiting` for all other states. -- `User action required:` mandatory when `Continuation` is `waiting`. -- `Next-step package:` `Owner`, `Scope`, `Action`, `Validation`, and `Risk` - mandatory for non-`DONE` states. -- `Evidence gaps:` the exact gap preventing `DONE`. - -Only one `-plan-state.md` marker may exist in a plan folder at a time. -When the state changes, replace the marker; do not accumulate multiple state -markers. - -### State Transitions - -Allowed transitions: - -- Any live-folder state (`APPLIED_UNVERIFIED`, `PARTIAL`, `BLOCKED`, - `ROLLED_BACK`, `CANCELLED`) to `DONE` when evidence gaps close. -- `APPLIED_UNVERIFIED` to `PARTIAL` or `BLOCKED` when new evidence shows the - work is incomplete or blocked. -- `PARTIAL` to `APPLIED_UNVERIFIED` when all planned edits are applied but - verification is still missing. -- `BLOCKED` to `PARTIAL` or `APPLIED_UNVERIFIED` when the blocker is resolved. -- `ROLLED_BACK` to any state if new work restarts or supersedes the rolled-back - state. -- `CANCELLED` is terminal. A new plan must be authored to restart cancelled work. - -A transition must be evidenced by a fresh ledger pass and, when validators exist, -by fresh validator output. Do not move a folder to `DONE` from a non-`DONE` state -without re-running the applicable validation. - -### Closeout Artifact Checklist - -Before declaring any closeout step complete: - -1. Compare promised work with observed delivery using the source-item ledger, - current diff, touched files, validators, and explicit non-actions. -2. Record the completion state in `completion-report.md` for full packaging, or - in `-plan-state.md` for lightweight live-folder or `DONE` markers. -3. For `DONE`, ensure the evidence envelope maps every ledger row or `done-*` - item to a status, evidence path or command, and route. -4. For non-`DONE` states, ensure the marker records the exact evidence gap, - continuation intent, and next-step package. -5. Run the bundle-local `state-check` and `completion-check` commands and - resolve any ERROR or WARNING they report. +- Executing `questions.md` or unapproved planning notes. +- Changing `superpowers-executing-plans`; this wrapper must not patch imported Superpowers behavior. + +## Wrapper Contract + +1. Confirm the retained plan path and that execution is approved. +2. Read the smallest plan context needed to identify target, anti-scope, validation path, and stop conditions. +3. If the plan is a compact plan, read `02-execution.md`; if it is an extended plan, read `02-control.md` before numbered execution files. +4. Announce that this gateway is wrapping `superpowers-executing-plans`. +5. Invoke `superpowers-executing-plans` for the task-by-task execution loop. +6. Preserve compact execution state. Prefer targeted rereads over full re-ingestion unless new evidence invalidates the current state. +7. Use Compact Evidence Reporting for large validator output: retain command, exit code, material counts, changed files, and exact gaps without pasting raw logs when a summary preserves the evidence. +8. Stop on scope drift, destructive action, owner conflict, missing validation path, human approval need, secret exposure risk, or repeated non-improving failures. +9. Before final response or pause, write exactly one status file in the retained plan folder named `..md`. +10. No `DONE` claim is allowed unless the status file exists and fresh evidence supports `DONE`, or the status file records the exact validation gap under another status. + +## Status File Contract + +`` is the retained plan folder name. Supported statuses: + +- `DONE`: all in-scope work is complete and required validation passed. +- `BLOCKED`: a real blocker or required external/user action prevents safe continuation. +- `PARTIAL`: some in-scope work remains incomplete or intentionally deferred. +- `NEEDS_REVIEW`: work was applied but review, validation, or evidence coverage is still required before `DONE`. + +Required headings: + +- `## Status` +- `## Reason` +- `## Completed` +- `## Remaining` +- `## Validation` +- `## Next` +- `## Resume Notes` + +The status file must be short, factual, and useful to a later agent. It must state why the current status was chosen and what evidence or action is needed to resume or finish. + +## Legacy Contract Boundary + +This gateway no longer owns `done-*` packaging, `completion-report.md`, `evidence-envelope.md`, or `-plan-state.md` markers. Existing historical artifacts may be read as evidence, but new gateway output uses `..md`. ## Validation -- Summary and profile-control files are read first. -- `Plan profile` is `compact` or `extended`. -- Execution strategy is inferred from profile, folder shape, and validation path. -- `compact` retained plans use `tmp/superpowers/mini-plan-*` and `02-execution.md` as the only executable file. -- `extended` retained plans include `02-control.md` before executable work. -- Mandatory applicable requirements are checked at item close and before `DONE`. -- The Agentic Execution Loop stayed inside the authorized owner, scope, and - validation path. -- Missing mandatory applicable evidence maps to a non-`DONE` state. -- `done-*` markers appear only during full close packaging; lightweight closeout uses `-plan-state.md` (for closed plans: `DONE-plan-state.md`) and may retain numbered files. -- Non-`DONE` states keep numbered plan files and the live ledger in place. -- `-plan-state.md` filename matches the declared `State:` and encodes a single marker. -- `Continuation` matches the state: `none` for `DONE`, `continuing` or `waiting` for all other states. -- Non-`DONE` markers include a `User action required`, `Next-step package`, and `Evidence gaps` field when applicable. - -## Common failure modes - -- Closing an item because edits exist while mandatory applicable evidence is still open. -- Treating every loaded specialist rule as mandatory without applicability proof. -- Hiding ownership conflicts instead of escalating a next owner and validation path. -- Continuing the Agentic Execution Loop after evidence stops improving or a - stop condition fires. -- Pasting raw large validator output when compact evidence would preserve the same proof. -- Packaging `DONE` while evidence gaps still require `APPLIED_UNVERIFIED`, `PARTIAL`, or `BLOCKED`. -- Declaring a non-`DONE` state without writing or updating the `-plan-state.md` marker. -- Leaving stale `-plan-state.md` markers behind after a state transition. -- Creating `done-*` markers or removing numbered plan files for `APPLIED_UNVERIFIED`, `PARTIAL`, `BLOCKED`, `ROLLED_BACK`, or `CANCELLED`. -- Moving a folder to `DONE` from a non-`DONE` state without fresh validator evidence. -- Treating `CANCELLED` as `DONE` or as a substitute for missing evidence. +- The wrapper references `superpowers-executing-plans` and does not duplicate its execution algorithm. +- The retained plan path and plan basename are known before execution starts. +- The final or paused state maps to exactly one supported status. +- The status file exists in the retained plan folder. +- The status filename, status value, and required headings match the contract. +- Fresh validation evidence or an explicit validation gap is recorded before any completion claim. diff --git a/.github/skills/internal-gateway-execute-plans/agents/openai.yaml b/.github/skills/internal-gateway-execute-plans/agents/openai.yaml index e99a341..0b47651 100644 --- a/.github/skills/internal-gateway-execute-plans/agents/openai.yaml +++ b/.github/skills/internal-gateway-execute-plans/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Internal Gateway Execute Plans" - short_description: "Repo wrapper for current-only retained plan execution" - default_prompt: "Use $internal-gateway-execute-plans to classify retained plan folders from 01-change-summary.md and the profile control file (02-execution.md for compact, 02-control.md for extended), then infer the best execution strategy from plan profile, folder shape, merged control-contract sections when applicable, and validation path. Reject unsupported profiles. This wrapper executes approved compact mini-plan-* plans and approved extended plans, and enforces shared closeout validation contracts. Do not require a Recommended consumer field in the retained plan. Create done-* markers only for DONE final packaging. For non-DONE exits, keep the plan live and report State, Continuation, and a visible next-step package. Use the bundle-local CLI for deterministic inspect, resume, checkpoint, state-check, and completion-check." + short_description: "Repo wrapper around superpowers-executing-plans with required status artifact" + default_prompt: "Use $internal-gateway-execute-plans for approved retained-plan execution under tmp/superpowers/. This gateway activates before superpowers-executing-plans, keeps execution inside repo-local target and anti-scope, and requires exactly one sibling status file named ..md before final response or pause. Supported statuses are DONE, BLOCKED, PARTIAL, and NEEDS_REVIEW. Do not modify superpowers-executing-plans. Do not create new -plan-state.md, done-* packaging, completion-report.md, or evidence-envelope.md artifacts as this gateway's closeout." diff --git a/.github/skills/internal-gateway-execute-plans/references/completion-report.md b/.github/skills/internal-gateway-execute-plans/references/completion-report.md deleted file mode 100644 index 5e691d1..0000000 --- a/.github/skills/internal-gateway-execute-plans/references/completion-report.md +++ /dev/null @@ -1,145 +0,0 @@ -# Completion Report - -Use this reference to close retained plan execution with an explicit state. A -completion report is evidence packaging, not a free-form summary. - -Treat `completion-report.md` and `evidence-envelope.md` as late-stage packaging artifacts. Update them when the current validator, diff, and item-level evidence set is stable enough to report, not after every intermediate patch. - -## Source Patterns - -- Comparative source: `tmp/external-comparison/compound-engineering-plugin/plugins/compound-engineering/skills/ce-work/references/shipping-workflow.md`. -- Comparative source: `tmp/external-comparison/hotl-plugin/docs/contracts/execution-report-output.md`. -- Adopt completion semantics only. Do not import external runtime reporting. - -## Profile Gate - -Before any completion-phase validation, verify the `Plan profile` is `compact` -or `extended`. Unsupported or missing profiles return `unsupported-plan-contract` -and the completion attempt stops. - -`compact` and `extended` execution both use `internal-gateway-execute-plans`. -`compact` closeout still stays lightweight and must not require an -standalone implementation-contract file. - -When packaged folders no longer include `02-control.md`, the -completion report should include `Plan profile: compact` or -`Plan profile: extended` so profile validation remains deterministic. - -## Completion States - -| State | Criteria | Folder behavior | Continuation | -| --- | --- | --- | --- | -| `DONE` | All in-scope ledger items are implemented or intentionally closed, validators passed, and mandatory applicable evidence is verified. Closeout may use either full packaging (`done-*` + `evidence-envelope.md` + `completion-report.md`) or a lightweight marker (`DONE-plan-state.md`, using `-plan-state.md`) with `State: DONE` and `Continuation: none`. | Full packaging removes numbered files only after evidence is stable. Lightweight marker keeps numbered plan files in place while recording an explicit closed state. | `none` | -| `APPLIED_UNVERIFIED` | Edits were applied, but required validator, review, or evidence coverage is missing. | Keep numbered plan files and the live ledger in place. Do not create new `done-*` markers. | `continuing` or `waiting` | -| `PARTIAL` | Some in-scope items remain incomplete or intentionally deferred. | Keep numbered plan files and the live ledger in place. Do not create new `done-*` markers. | `continuing` or `waiting` | -| `BLOCKED` | A real blocker prevents correct continuation. | Keep numbered plan files and the live ledger in place. Do not create new `done-*` markers. Record the blocker and next-step package. | `waiting` | -| `ROLLED_BACK` | Applied work was reverted or superseded by a different safe state. | Keep numbered plan files and the live ledger in place unless a later `DONE` package supersedes the folder. Do not create new `done-*` markers for the rolled-back state. | `waiting` or `none` | -| `CANCELLED` | Explicitly evidenced as `INTENTIONAL_NON_ACTION` with documented reason. Not a substitute for missing evidence. | Keep numbered plan files and the live ledger in place. Do not create new `done-*` markers. Record the cancellation rationale in the completion report. | `none` | - -`DONE` requires passed validators and no open source-item ledger rows. -Full-package `DONE` requires a completed report, no numbered plan files, and -an evidence envelope or equivalent item-level evidence for every requested or source item. -Lightweight `DONE` requires `DONE-plan-state.md` with explicit `State: DONE` -and `Continuation: none` and may keep numbered plan files in place. -`DONE` also requires that mandatory applicable skill requirements are verified; -if mandatory applicable evidence is missing, use `APPLIED_UNVERIFIED`, `PARTIAL`, -or `BLOCKED` and report the exact gap. -If validators or evidence coverage cannot run, or any row remains `PENDING`, -`PARTIAL`, `NOT_DONE`, `UNVERIFIABLE`, or `BLOCKED`, use -`APPLIED_UNVERIFIED`, `PARTIAL`, or `BLOCKED` with the explicit gap. -Only `DONE` is a close-package state. Every other state is a live-folder -state and must preserve the retained plan for resume or manual continuation. - -`CANCELLED` is accepted only when the plan was explicitly abandoned by user -decision and the cancellation is recorded as `INTENTIONAL_NON_ACTION` with a -documented reason in the ledger. Otherwise the ledger row remains open and -the state is `PARTIAL` or `BLOCKED`. - -When the state is not `DONE`, the report must also declare: - -- `Continuation`: `continuing` or `waiting`. -- `User action required`: mandatory when `Continuation` is `waiting`. -- `Next-step package`: `Owner`, `Scope`, `Action`, `Validation`, and `Risk`. - -## Required Fields - -- Active phase and owner. -- Completion state. -- Plan profile. -- Continuation. -- User action required. -- Files changed. -- Items completed. -- Intentional non-actions. -- Validators or tests run. -- Evidence envelope. -- Source-item ledger status. -- Evidence gaps. -- Residual risks. -- Lessons status, including `added`, `codified`, or `none`. -- Next-step package. -- Follow-up suggestions separated from required work. - -## Evidence Envelope - -For non-trivial retained plans, include or link an evidence envelope. The -envelope must map each `02-control.md` row (or `02-execution.md` row for -`compact`), retained-plan item, or -reconstructed `done-*` item to a status, evidence path or command, and route. - -Before assigning `DONE`, compare promised work with observed delivery. Use the source-item ledger, current diff, touched files, validators, and explicit non-actions. When merged implementation-contract sections in `02-control.md` are required, compare them with the touched files, validators, blocker handling, and any required external pin or fallback. Report any gap under `Evidence gaps` or `Residual risks`. A summary that says an item was done is not evidence. - -Do not churn the evidence envelope after every local slice when the validator output is still moving. Refresh it when the retained-plan state is coherent enough to support the reported completion state. - -Minimum fields: - -- Source item or source `done-*` file. -- Ledger item id when available. -- Reconstructed item when the original numbered plan file was already removed. -- Intended observable acceptance, such as diff, file state, validator assertion, - manual check, or explicit non-action. -- Status using `DONE`, `PARTIAL`, `NOT_DONE`, `CHANGED`, or `UNVERIFIABLE`. -- Evidence path, artifact, or command. -- Route for unresolved or intentionally deferred work. - -If a `done-*` marker lacks enough item-level evidence and no independent file, -diff, or validator evidence exists, mark the item `UNVERIFIABLE` instead of -claiming `DONE`. - -If the ledger is absent or stale and cannot be reconstructed from source plan -files, `done-*` files, and reachable artifacts, mark the completion state -`PARTIAL` or `APPLIED_UNVERIFIED`; do not claim `DONE`. - -If the completion state is `APPLIED_UNVERIFIED`, `PARTIAL`, `BLOCKED`, -`ROLLED_BACK`, or `CANCELLED`, do not use the report as permission to remove numbered plan files -or the live ledger. Those states document a live retained-plan folder, not a -physical close package. - -## Review Tiers - -| Tier | Trigger | Minimum lenses | -| --- | --- | --- | -| `light` | Up to 2 files or text-only changes with low blast radius. | `internal-code-review` where code exists, plus concise contract check. | -| `standard` | Multi-file changes with workflow, tests, or adjacent contracts. | Always-on review lenses from `review-lenses.md`. | -| `deep` | Always-on guidance, wrapper agents, validators, or cross-family contracts. | Always-on, cross-cutting, stack-specific lenses, plus scope drift check. | - -## Template - -```text -Completion Report -Active phase and owner: -State: DONE | APPLIED_UNVERIFIED | PARTIAL | BLOCKED | ROLLED_BACK | CANCELLED -Continuation: none | continuing | waiting -User action required: -Files changed: -Completed items: -Intentional non-actions: -Validators: -Evidence envelope: -Source-item ledger: -Evidence gaps: -Residual risks: -Lessons: added | codified in | none - -Next-step package: Owner=<...>; Scope=<...>; Action=<...>; Validation=<...>; Risk=<...> -Follow-up suggestions: -``` diff --git a/.github/skills/internal-gateway-execute-plans/references/plan-handoff.md b/.github/skills/internal-gateway-execute-plans/references/plan-handoff.md index 071a63a..912738f 100644 --- a/.github/skills/internal-gateway-execute-plans/references/plan-handoff.md +++ b/.github/skills/internal-gateway-execute-plans/references/plan-handoff.md @@ -1,81 +1,32 @@ # Plan Handoff -Use this reference when `internal-gateway-execute-plans` receives a retained -plan or Decision Brief from planning. It defines the minimum input contract -before the `done-*` loop starts. - -## Source Pattern - -- Comparative source: `tmp/external-comparison/compound-engineering-plugin/plugins/compound-engineering/skills/ce-plan/references/plan-handoff.md`. -- Adopt the handoff checks only. Do not import Compound runtime behavior. +Use this reference when `internal-gateway-execute-plans` receives an approved +retained plan. ## Required Inputs -- Plan folder under `tmp/superpowers//`. -- `Plan profile` declared as `compact` or `extended` in `02-execution.md` - (`compact`) or `02-control.md` (`extended`). - Unsupported or missing profiles return `unsupported-plan-contract`. -- `01-change-summary.md`: compressed Italian decision capsule with required - sections and a `Risorsa | Azione | Scopo` table for non-trivial plans. - Non-executable. -- `02-execution.md` (`compact`) with merged control header and executable steps. -- `02-control.md` (`extended`) with `Recommended use`, `File map and role`, - `Initial evidence pass`, `Reading budget`, source-item coverage, and merged - implementation-contract sections. -- Execution strategy is inferred by `internal-gateway-execute-plans` from the - declared profile, folder shape, and validation path; do not require a - separate consumer field in the retained plan. -- Numbered executable plan files after the summary and control files. -- A plan for closing the summary and ledger files through matching `done-*` - markers when the folder completes as `DONE`. -- Confirmed target state and anti-scope. -- Selected owner and known lane-change owner. -- Validation path or explicit validation gap. -- Observable acceptance for each executable action, especially broad verbs such - as compress, rewrite, refactor, harden, align, or simplify. -- A stable ledger row for every requested or source item, with status and route. -- Stop conditions. - -## Before Starting - -1. Read `01-change-summary.md` first for human decision context, then read - `02-execution.md` (`compact`) or `02-control.md` (`extended`) to classify - folder purpose, next expected treatment, file roles, reading budget, and - source-item coverage. -2. Run the declared `Initial evidence pass` before broad reading. Use `rg --no-ignore` for retained artifacts under `tmp/` when checking plan claims. -3. For `compact`, `02-execution.md` is the only executable file after the - summary. For `extended`, read numbered executable plan files in order after - `02-control.md`. -4. Check whether a Decision Brief exists in chat, a prompt, or a retained plan - artifact. -5. If a required input is missing, inspect repository evidence before asking. -6. If the missing input cannot be recovered, stop with a visible gap instead of - guessing. - -## Standard Stop Conditions - -- `Plan profile` is missing, unsupported, or cannot be classified as `compact` - or `extended`. Return `unsupported-plan-contract`. -- `compact` plans do not use the `tmp/superpowers/mini-plan-*` folder convention. -- `compact` plans include additional executable numbered files beyond - `02-execution.md` without escalating to `extended`. -- Scope moves outside the declared files, owners, or asset family. -- `01-change-summary.md` is missing, overloaded with control details, written in - English, or missing the required decision-capsule sections or - `Risorsa | Azione | Scopo` table for non-trivial plans. -- `02-control.md`, `Recommended use`, or `File map and role` is missing from an - `extended` retained plan. -- `Initial evidence pass`, `Reading budget`, or source-item coverage is missing - from a non-trivial retained plan (`02-execution.md` for `compact`, - `02-control.md` for `extended`). -- Merged implementation-contract sections in `02-control.md` are missing, stale, - or too weak to recover exact sources, target files, validators, blockers, or - required external pins for a non-trivial or lower-context retained plan. -- Owner is missing or the active owner no longer fits. -- Validation path is absent and no closest check exists. -- A promotion-gated contract would be violated. -- A cited evidence path cannot be read. - -When a stop condition fires, package the next step through -`internal-agent-support-next-step` or lane-change through -`internal-agent-support-lane-change-engine`. +- Retained plan folder under `tmp/superpowers/`. +- Approval to execute the retained plan. +- `01-change-summary.md` when present. +- `02-execution.md` for compact plans or `02-control.md` for extended plans. +- Target, anti-scope, validation path, and stop conditions recoverable from the + retained plan. + +## Before Delegating To Superpowers + +1. Confirm the plan folder path and basename. +2. Read only the smallest context needed to recover target, anti-scope, + validation path, and stop conditions. +3. Treat `questions.md` as non-executable. +4. If the plan cannot be classified or lacks an executable path, stop with a + `BLOCKED` status file instead of guessing. +5. Invoke `superpowers-executing-plans` for task-by-task execution once the + handoff is safe. + +## Stop Conditions + +- The plan is not approved for execution. +- The retained plan path is missing or unreadable. +- Target, anti-scope, or validation path cannot be recovered. +- The requested action would modify `superpowers-executing-plans`. +- A required approval, credential, dependency, or owner decision is missing. diff --git a/.github/skills/internal-gateway-execute-plans/references/resume-protocol.md b/.github/skills/internal-gateway-execute-plans/references/resume-protocol.md index bccfa56..6570e7b 100644 --- a/.github/skills/internal-gateway-execute-plans/references/resume-protocol.md +++ b/.github/skills/internal-gateway-execute-plans/references/resume-protocol.md @@ -1,89 +1,28 @@ # Resume Protocol -Use this verify-first protocol when resuming an interrupted or compacted retained -plan execution. The goal is to recover from evidence, not memory. +Use this verify-first protocol when resuming retained-plan execution through +`internal-gateway-execute-plans`. -## Source Pattern +## Verify-First Sequence -- Comparative source: `tmp/external-comparison/hotl-plugin/skills/resuming/SKILL.md`. -- Adopt verify-first recovery only. Do not import HOTL state files or runtime - commands. - -## Profile Gate - -Before any inspection, verify the `Plan profile` is `compact` or `extended`. -Unsupported or missing profiles return `unsupported-plan-contract` and the -resume attempt stops. - -## Verify-first Sequence - -1. Read `01-change-summary.md` first when present, then - `02-execution.md` (`compact`) or `02-control.md` (`extended`), and recover - the intended folder purpose, `Recommended use`, `File map and role`, - `Initial evidence pass`, `Reading budget`, source-item coverage, and whether - merged implementation-contract sections should exist. -2. Run the shortest safe evidence pass before reading broadly: target existence, riskiest claim, and nearest validator or explicit gap. -3. Use `rg --no-ignore` or equivalent ignored-file-aware search for retained artifacts under `tmp/`. -4. List every `done-*` file in the retained plan folder. -5. List every remaining executable numbered plan file, ignore `questions.md` as an - executable file, and treat `02-control.md` as a support/control - file for `extended` plans. -6. Check whether each `done-*` file preserves the completed item and evidence, - or points to an evidence envelope. -7. Check whether the source-item ledger exists and whether every row has a - credible status, evidence, and route. -8. Read merged implementation-contract sections in `02-control.md` when present - or required and compare them with touched files, validators, blockers, and - any external pins or fallback. -9. Check `git status` and `git diff` for uncommitted evidence of completed work. -10. Re-run the validators declared by the plan, or name the closest available - validator and the gap. -11. If `completion-report.md` exists, read its `State`, `Continuation`, and - `User action required` fields before assuming the folder is closed or ready - to continue. -12. Produce a status report before editing canonical files. - -## Status Report Template - -```text -Resume status -Summary control file: <02-execution.md or 02-control.md present and usable, missing, or stale> -Source-item ledger: -Evidence pass: -Completion state: -Continuation: -Completed steps: -In-progress steps: -Pending steps: -Evidence gaps: -Next action: -``` +1. Resolve the retained plan folder and plan basename. +2. Look for a sibling status file named `..md`. +3. Read `## Status`, `## Reason`, `## Remaining`, `## Validation`, `## Next`, + and `## Resume Notes` before editing. +4. Re-check the target files, current diff, and nearest validation command named + in the status file or retained plan. +5. Resume through `superpowers-executing-plans` only when the status and fresh + evidence support continuation. ## Reconciliation Rules -- If a `done-*` file exists but the target diff, file, or validator evidence is - absent, mark the item `UNVERIFIABLE` and reopen the evidence question. -- If `01-change-summary.md` or required control files (`02-execution.md` for - `compact`, `02-control.md` for `extended`) are missing or stale enough that - file roles and source coverage cannot be inferred safely, stop and reopen the - plan-handoff gap before execution resumes. -- If merged implementation-contract sections are required but missing or too - weak to recover exact sources, target files, validators, blockers, or - required external pins, stop and reopen the plan-handoff gap before execution - resumes. -- If `Initial evidence pass`, `Reading budget`, or source-item coverage is - missing, reconstruct the minimum pass from reachable evidence before broad - reading, or mark the resume state `UNVERIFIABLE`. -- If a `done-*` file lacks an item/evidence table or evidence-envelope pointer, - reconstruct the item from reachable artifacts or mark it `UNVERIFIABLE`. -- If a source-item ledger row is `PENDING`, `PARTIAL`, `NOT_DONE`, - `UNVERIFIABLE`, or `BLOCKED`, do not claim completion until the row is - delivered, intentionally closed with evidence, or reported as a blocker. -- If `completion-report.md` declares `APPLIED_UNVERIFIED`, `PARTIAL`, - `BLOCKED`, or `ROLLED_BACK`, treat the retained plan folder as live even when - the report and evidence envelope exist. -- If a `done-*` file is missing but the diff shows compatible completed work, - ask for confirmation before creating or updating the marker. -- If validators fail after resume, fix the root cause or route the blocker before - continuing to later plan files. -- Do not create new `done-*` markers from chat memory alone. +- If the status file says `DONE`, do not resume unless new evidence invalidates + completion. +- If the status file says `BLOCKED`, resolve or route the blocker before + editing. +- If the status file says `PARTIAL`, continue from the first evidenced + remaining item. +- If the status file says `NEEDS_REVIEW`, review or validate before adding new + work. +- If no status file exists, reconstruct the current state from retained-plan + files, diff, and validators, then write the status file before stopping. diff --git a/.github/skills/internal-gateway-execute-plans/references/status-file.md b/.github/skills/internal-gateway-execute-plans/references/status-file.md new file mode 100644 index 0000000..d8bb970 --- /dev/null +++ b/.github/skills/internal-gateway-execute-plans/references/status-file.md @@ -0,0 +1,76 @@ +# Status File + +Use this reference when `internal-gateway-execute-plans` finishes, pauses, or +blocks retained-plan execution. + +The gateway writes exactly one sibling status file in the retained plan folder: + +```text +..md +``` + +`` is the retained plan folder name. `STATUS` must be one of: + +- `DONE` +- `BLOCKED` +- `PARTIAL` +- `NEEDS_REVIEW` + +## Required Headings + +- `## Status` +- `## Reason` +- `## Completed` +- `## Remaining` +- `## Validation` +- `## Next` +- `## Resume Notes` + +## Template + +```markdown +# Status + +## Status + +DONE + +## Reason + +All in-scope work is complete and required validation passed. + +## Completed + +- + +## Remaining + +- None. + +## Validation + +- `` passed. + +## Next + +- No action required. + +## Resume Notes + +- Re-run `` if new changes appear before merge. +``` + +## Status Selection + +- Use `DONE` only when all in-scope work is complete and required validation + passed. +- Use `BLOCKED` when safe continuation requires external input, approval, + missing credentials, dependency access, or a non-repairable blocker. +- Use `PARTIAL` when some planned work remains incomplete or intentionally + deferred. +- Use `NEEDS_REVIEW` when edits were applied but review, validation, or evidence + coverage is still required. + +Historical `done-*`, `completion-report.md`, `evidence-envelope.md`, and +`-plan-state.md` files may be read as evidence. Do not create them as +new gateway closeout artifacts. \ No newline at end of file diff --git a/.github/skills/internal-gateway-execute-plans/scripts/plan_execution.py b/.github/skills/internal-gateway-execute-plans/scripts/plan_execution.py index ed078e1..a509a84 100644 --- a/.github/skills/internal-gateway-execute-plans/scripts/plan_execution.py +++ b/.github/skills/internal-gateway-execute-plans/scripts/plan_execution.py @@ -1,6 +1,6 @@ -"""Bundle-local CLI for retained-plan execution. +"""Bundle-local CLI for gateway retained-plan status files. -Commands: inspect resume checkpoint state-check completion-check +Commands: inspect resume checkpoint status-check completion-check Stdlib-only. Read-only. Does not import sibling bundles or .github/scripts/lib. """ @@ -11,50 +11,18 @@ import json import re import sys -from dataclasses import dataclass, field +from dataclasses import dataclass from pathlib import Path -NUMBERED_FILE_PATTERN = re.compile(r"\d{2}-.+\.md") -DONE_FILE_PATTERN = re.compile(r"done-\d{2}.+\.md") - -COMPLETION_REPORT_FIELDS = ( - "Completion Report", - "Active phase and owner:", - "State:", - "Continuation:", - "User action required:", - "Files changed:", - "Completed items:", - "Intentional non-actions:", - "Validators:", - "Evidence envelope:", - "Source-item ledger:", - "Evidence gaps:", - "Residual risks:", - "Next-step package:", - "Follow-up suggestions:", -) - -PLAN_STATE_REQUIRED_FIELDS = ( - "State:", - "Continuation:", -) - -PLAN_STATE_FILE_RE = re.compile(r"^([A-Z0-9_-]+)-plan-state\.md$") - -VALID_PLAN_STATES = frozenset( - {"DONE", "APPLIED_UNVERIFIED", "PARTIAL", "BLOCKED", "ROLLED_BACK", "CANCELLED"} -) -NON_DONE_STATES = frozenset({"APPLIED_UNVERIFIED", "PARTIAL", "BLOCKED", "ROLLED_BACK", "CANCELLED"}) -OPEN_STATUSES = frozenset({"PENDING", "PARTIAL", "NOT_DONE", "UNVERIFIABLE", "BLOCKED"}) - -LEDGER_REQUIRED_FIELDS = ( - "Recommended use", - "File map and role", - "Clarification gate", - "Target and anti-scope", - "Owner and validator", - "Stop conditions", +VALID_STATUSES = frozenset({"DONE", "BLOCKED", "PARTIAL", "NEEDS_REVIEW"}) +REQUIRED_HEADINGS = ( + "## Status", + "## Reason", + "## Completed", + "## Remaining", + "## Validation", + "## Next", + "## Resume Notes", ) @@ -65,549 +33,188 @@ class Finding: severity: str = "ERROR" -def classify_profile(plan_folder: Path) -> str: - """Classify plan profile. Returns profile name or 'unsupported'.""" - compact_path = plan_folder / "02-execution.md" - if compact_path.is_file(): - text = compact_path.read_text(encoding="utf-8") - if re.search(r"Plan profile[:\s]+compact", text): - return "compact" - - control_path = plan_folder / "02-control.md" - if control_path.is_file(): - text = control_path.read_text(encoding="utf-8") - if re.search(r"Plan profile[:\s]+extended", text): - return "extended" - - # Packaged folders may no longer include numbered source files. - report_path = plan_folder / "completion-report.md" - if report_path.is_file(): - report_text = report_path.read_text(encoding="utf-8") - if re.search(r"Plan profile[:\s]+extended", report_text): - return "extended" - if re.search(r"Plan profile[:\s]+compact", report_text): - return "compact" - - return "unsupported" - - -def numbered_files(plan_folder: Path) -> list[str]: - """Return sorted numbered plan files.""" - return sorted( - p.name - for p in plan_folder.glob("*.md") - if NUMBERED_FILE_PATTERN.match(p.name) - ) - +def status_pattern(plan_folder: Path) -> re.Pattern[str]: + escaped_name = re.escape(plan_folder.name) + statuses = "|".join(sorted(VALID_STATUSES)) + return re.compile(rf"^{escaped_name}\.({statuses})\.md$") -def done_files(plan_folder: Path) -> list[str]: - """Return sorted done-* files.""" - return sorted( - p.name for p in plan_folder.glob("done-*.md") - ) - -def executable_numbered_files(plan_folder: Path, profile: str) -> list[str]: - """Return sorted executable numbered plan files for the given profile. - - For compact plans, only 02-execution.md is executable. For extended plans, - executable files start at 03-execution.md; 02-control.md is control and - 01-change-summary.md is the decision record. - """ - all_numbered = numbered_files(plan_folder) - if profile == "compact": - return [name for name in all_numbered if name == "02-execution.md"] - if profile == "extended": - executable: list[str] = [] - for name in all_numbered: - match = re.match(r"^(\d{2})-.+\.md$", name) - if match and int(match.group(1)) >= 3: - executable.append(name) - return executable - return all_numbered - - -def resolve_plan_state_marker(plan_folder: Path) -> tuple[Path | None, str | None, list[Finding]]: - """Resolve lightweight plan-state marker path and state-from-filename if present.""" +def find_status_files(plan_folder: Path) -> tuple[list[Path], list[Finding]]: findings: list[Finding] = [] - named_paths = sorted(path for path in plan_folder.glob("*-plan-state.md") if path.is_file()) - - if len(named_paths) > 1: - findings.append( - Finding( - code="multiple-plan-state-markers", - message="Multiple -plan-state.md markers found; keep exactly one", - ) - ) - return None, None, findings + pattern = status_pattern(plan_folder) + status_files: list[Path] = [] - if named_paths: - marker_path = named_paths[0] - match = PLAN_STATE_FILE_RE.match(marker_path.name) - if match is None: + for path in sorted(plan_folder.glob("*.md")): + if pattern.match(path.name): + status_files.append(path) + elif re.match(rf"^{re.escape(plan_folder.name)}\.[A-Z0-9_-]+\.md$", path.name): findings.append( Finding( - code="invalid-plan-state-marker", - message="Plan state marker must match -plan-state.md using uppercase state", + code="invalid-status-file-name", + message=( + f"{path.name} uses an unsupported status; valid statuses are " + f"{', '.join(sorted(VALID_STATUSES))}" + ), ) ) - return None, None, findings - marker_state = match.group(1).upper() - return marker_path, marker_state, findings - - return None, None, findings - -def _check_unsupported(plan_folder: Path) -> tuple[str | None, list[Finding]]: - profile = classify_profile(plan_folder) - if profile == "unsupported": - return None, [ + legacy_markers = sorted(plan_folder.glob("*-plan-state.md")) + if legacy_markers: + findings.append( Finding( - code="unsupported-plan-contract", - message=f"Plan folder {plan_folder} has no supported Plan profile (compact or extended)", + code="legacy-plan-state-marker", + message="Legacy -plan-state.md marker found; gateway output must use ..md", ) - ] - return profile, [] - - -def _emit_findings(findings: list[Finding], format: str, report: dict | None = None) -> None: - if format == "json": - payload: dict = { - "findings": [{"code": f.code, "message": f.message, "severity": f.severity} for f in findings], - "ready": not any(f.severity == "ERROR" for f in findings), - } - if report: - payload = {**report, **payload} - json.dump(payload, sys.stdout, indent=2) - else: - if report: - for key, value in report.items(): - if isinstance(value, list): - print(f"{key}: {', '.join(str(v) for v in value)}") - else: - print(f"{key}: {value}") - if findings: - for f in findings: - print(f"[{f.severity}] {f.code}: {f.message}") - if not findings and not report: - print("No findings.") - - - -def cmd_inspect(plan_folder: Path, format: str = "text") -> int: - """Inspect a plan folder and report status.""" - profile, findings = _check_unsupported(plan_folder) - if profile is None: - _emit_findings(findings, format) - return 1 - - active = numbered_files(plan_folder) - dones = done_files(plan_folder) - envelope = (plan_folder / "evidence-envelope.md").is_file() - report_exists = (plan_folder / "completion-report.md").is_file() - questions = (plan_folder / "questions.md").is_file() - summary = (plan_folder / "01-change-summary.md").is_file() - - report = { - "plan_folder": str(plan_folder), - "profile": profile, - "summary_present": summary, - "questions_present": questions, - "active_numbered_files": active, - "done_files": dones, - "evidence_envelope_present": envelope, - "completion_report_present": report_exists, - } - - _emit_findings(findings, format, report) - return 0 + ) + return status_files, findings -def cmd_resume(plan_folder: Path, format: str = "text") -> int: - """Check resume safety for an interrupted plan.""" - profile, findings = _check_unsupported(plan_folder) - if profile is None: - _emit_findings(findings, format) - return 1 - active = numbered_files(plan_folder) - dones = done_files(plan_folder) +def validate_status_file(plan_folder: Path) -> tuple[Path | None, str | None, list[Finding]]: + status_files, findings = find_status_files(plan_folder) - if not active and dones: + if len(status_files) > 1: findings.append( Finding( - code="resume-no-active-files", - message="No active numbered files remain but done files exist; may be ready for completion", - severity="WARNING", + code="multiple-status-files", + message="Multiple gateway status files found; keep exactly one current status file", ) ) + return None, None, findings - # Check for completion report state - cr_path = plan_folder / "completion-report.md" - if cr_path.is_file(): - cr_text = cr_path.read_text(encoding="utf-8") - for state in ("APPLIED_UNVERIFIED", "PARTIAL", "BLOCKED", "ROLLED_BACK"): - if f"State: {state}" in cr_text: - findings.append( - Finding( - code="resume-live-folder", - message=f"Completion report state is {state}; folder is live, resume is valid", - severity="WARNING", - ) - ) - break - - report = { - "plan_folder": str(plan_folder), - "profile": profile, - "active_numbered_files": active, - "done_files": dones, - "resumable": True, - } - - _emit_findings(findings, format, report) - return 0 - - -def cmd_checkpoint(plan_folder: Path, format: str = "text") -> int: - """Check that a plan is safe to checkpoint (pause).""" - profile, findings = _check_unsupported(plan_folder) - if profile is None: - _emit_findings(findings, format) - return 1 - - active = numbered_files(plan_folder) - dones = done_files(plan_folder) - - report = { - "plan_folder": str(plan_folder), - "profile": profile, - "active_numbered_files": active, - "done_files": dones, - "can_checkpoint": True, - } - - _emit_findings(findings, format, report) - return 0 - - -def _validate_plan_state_content( - marker_path: Path, - state_from_filename: str, - plan_state_text: str, - require_non_done_fields: bool = False, -) -> list[Finding]: - """Validate the content of a plan-state marker.""" - findings: list[Finding] = [] - - if state_from_filename not in VALID_PLAN_STATES: + if not status_files: findings.append( Finding( - code="invalid-plan-state", - message=( - f"{marker_path.name} encodes unknown state {state_from_filename}; " - f"valid states are {', '.join(sorted(VALID_PLAN_STATES))}" - ), + code="missing-status-file", + message="No ..md status file found", ) ) + return None, None, findings - for field in PLAN_STATE_REQUIRED_FIELDS: - if field not in plan_state_text: - findings.append( - Finding( - code="missing-plan-state-field", - message=f"{marker_path.name} is missing {field}", - ) - ) - - state_match = re.search(r"^State:\s*(.+)$", plan_state_text, re.MULTILINE) - continuation_match = re.search( - r"^Continuation:\s*(.+)$", plan_state_text, re.MULTILINE - ) - - if state_match: - declared_state = state_match.group(1).strip().upper() - if declared_state != state_from_filename: - findings.append( - Finding( - code="plan-state-name-mismatch", - message=( - f"{marker_path.name} encodes state {state_from_filename} " - f"but content declares State: {declared_state}" - ), - ) - ) - if declared_state not in VALID_PLAN_STATES: - findings.append( - Finding( - code="invalid-declared-state", - message=( - f"{marker_path.name} declares unknown state {declared_state}" - ), - ) + status_path = status_files[0] + match = status_pattern(plan_folder).match(status_path.name) + if match is None: + findings.append( + Finding( + code="invalid-status-file-name", + message=f"{status_path.name} does not match ..md", ) + ) + return status_path, None, findings - if continuation_match: - declared_continuation = continuation_match.group(1).strip().lower() - is_done_state = state_from_filename == "DONE" - if is_done_state and declared_continuation != "none": - findings.append( - Finding( - code="nonterminal-continuation", - message=f"{marker_path.name} must declare Continuation: none for DONE", - ) - ) - elif not is_done_state and declared_continuation not in ("continuing", "waiting"): - findings.append( - Finding( - code="invalid-continuation", - message=( - f"{marker_path.name} Continuation must be 'continuing' or 'waiting' " - f"for state {state_from_filename}" - ), - ) - ) + status = match.group(1) + text = status_path.read_text(encoding="utf-8") - if require_non_done_fields and state_from_filename in NON_DONE_STATES: - if continuation_match: - declared_continuation = continuation_match.group(1).strip().lower() - if ( - declared_continuation == "waiting" - and "User action required:" not in plan_state_text - ): - findings.append( - Finding( - code="missing-user-action", - message=( - f"{marker_path.name} must declare User action required " - f"when Continuation is waiting" - ), - ) - ) - if "Next-step package:" not in plan_state_text: - findings.append( - Finding( - code="missing-next-step-package", - message=f"{marker_path.name} must declare Next-step package for non-DONE state", - ) - ) - if "Evidence gaps:" not in plan_state_text: + for heading in REQUIRED_HEADINGS: + if heading not in text: findings.append( Finding( - code="missing-evidence-gaps", - message=f"{marker_path.name} should declare Evidence gaps for non-DONE state", - severity="WARNING", + code="missing-required-heading", + message=f"{status_path.name} is missing {heading}", ) ) - return findings - - -def cmd_state_check(plan_folder: Path, format: str = "text") -> int: - """Validate any -plan-state.md marker in the plan folder.""" - findings: list[Finding] = [] - - _profile, profile_findings = _check_unsupported(plan_folder) - if _profile is None: - findings.extend(profile_findings) - _emit_findings(findings, format) - return 1 - - marker_path, state_from_filename, marker_findings = resolve_plan_state_marker(plan_folder) - findings.extend(marker_findings) - - report: dict = { - "plan_folder": str(plan_folder), - "profile": _profile, - "marker_present": marker_path is not None, - "marker_state": state_from_filename, - } - - if marker_path is None: + status_match = re.search(r"^## Status\s*\n+\s*([A-Z0-9_-]+)\s*$", text, re.MULTILINE) + if status_match is None: findings.append( Finding( - code="missing-plan-state-marker", - message="No -plan-state.md marker found", + code="missing-declared-status", + message=f"{status_path.name} must declare the status under ## Status", ) ) - _emit_findings(findings, format, report) - return 1 - - plan_state_text = marker_path.read_text(encoding="utf-8") - content_findings = _validate_plan_state_content( - marker_path, - state_from_filename, - plan_state_text, - require_non_done_fields=True, - ) - findings.extend(content_findings) - - _emit_findings(findings, format, report) - return 0 if not any(f.severity == "ERROR" for f in findings) else 1 - - -def cmd_completion_check(plan_folder: Path, format: str = "text") -> int: - """Validate completion packaging for DONE readiness.""" - findings: list[Finding] = [] - - # Profile gate is always required during completion checks, including - # packaged folders where numbered plan files were already removed. - _profile, profile_findings = _check_unsupported(plan_folder) - if _profile is None: - _emit_findings(profile_findings, format) - return 1 - - active = executable_numbered_files(plan_folder, _profile) - dones = done_files(plan_folder) - - plan_state_path, state_from_filename, marker_findings = resolve_plan_state_marker(plan_folder) - findings.extend(marker_findings) - envelope_path = plan_folder / "evidence-envelope.md" - report_path = plan_folder / "completion-report.md" - - if marker_findings: - _emit_findings(findings, format) - return 1 - - # Lightweight or live-folder state marker path. - if plan_state_path: - plan_state_text = plan_state_path.read_text(encoding="utf-8") - content_findings = _validate_plan_state_content( - plan_state_path, - state_from_filename, - plan_state_text, - require_non_done_fields=True, - ) - findings.extend(content_findings) - - if state_from_filename == "DONE": - if dones or envelope_path.is_file() or report_path.is_file(): - findings.append( - Finding( - code="mixed-closeout-style", - message="DONE state marker exists alongside full-packaging artifacts; prefer one closeout style", - severity="WARNING", - ) - ) - else: - if dones: - findings.append( - Finding( - code="invalid-done-for-non-done-state", - message=f"State {state_from_filename} must not have done-* markers", - ) - ) - - _emit_findings(findings, format) - return 0 if not any(f.severity == "ERROR" for f in findings) else 1 - - # Active numbered files must be gone for DONE - if active: + elif status_match.group(1) != status: findings.append( Finding( - code="active-numbered-files", - message=f"Active numbered plan files remain: {', '.join(active)}", + code="status-mismatch", + message=f"{status_path.name} encodes {status} but declares {status_match.group(1)}", ) ) - # Must have done files - if not dones: - findings.append( - Finding( - code="missing-done-files", - message="No done-* files found; full DONE packaging requires done markers", - ) - ) + return status_path, status, findings - # Evidence envelope - if not envelope_path.is_file(): - findings.append( - Finding(code="missing-evidence-envelope", message="evidence-envelope.md is missing") - ) - else: - envelope_text = envelope_path.read_text(encoding="utf-8") - if "| Status |" not in envelope_text: - findings.append( - Finding(code="missing-status-column", message="Evidence envelope missing Status column") - ) - if "| Evidence path or command |" not in envelope_text: - findings.append( - Finding(code="missing-evidence-column", message="Evidence envelope missing Evidence path or command column") - ) - for status in OPEN_STATUSES: - if f"| {status} |" in envelope_text: - findings.append( - Finding(code="open-status", message=f"Evidence envelope contains open status: {status}") - ) - for done_file in dones: - if f"`{done_file}`" not in envelope_text: - findings.append( - Finding(code="missing-done-reference", message=f"Evidence envelope does not reference {done_file}") - ) +def emit(findings: list[Finding], output_format: str, report: dict | None = None) -> None: + ready = not any(f.severity == "ERROR" for f in findings) + if output_format == "json": + payload = { + "ready": ready, + "findings": [ + {"code": f.code, "message": f.message, "severity": f.severity} + for f in findings + ], + } + if report: + payload = {**report, **payload} + json.dump(payload, sys.stdout, indent=2) + print() + return + + if report: + for key, value in report.items(): + print(f"{key}: {value}") + if findings: + for finding in findings: + print(f"[{finding.severity}] {finding.code}: {finding.message}") + elif not report: + print("No findings.") + + +def basic_report(plan_folder: Path) -> dict: + status_files, findings = find_status_files(plan_folder) + return { + "plan_folder": str(plan_folder), + "plan_basename": plan_folder.name, + "status_file_present": bool(status_files) and not findings, + "status_files": [path.name for path in status_files], + } - # Completion report - if not report_path.is_file(): - findings.append( - Finding(code="missing-completion-report", message="completion-report.md is missing") - ) - else: - report_text = report_path.read_text(encoding="utf-8") - for field in COMPLETION_REPORT_FIELDS: - if field not in report_text: - findings.append( - Finding(code="missing-report-field", message=f"Completion report missing: {field}") - ) - # DONE requires explicit State: DONE - if "State: DONE" not in report_text: - findings.append( - Finding( - code="not-done-state", - message="Completion report does not declare State: DONE", - ) - ) - # Item-by-item coverage matrix - if envelope_path.is_file(): - envelope_text = envelope_path.read_text(encoding="utf-8") - rows = re.findall(r"^\|.*\|$", envelope_text, re.MULTILINE) - data_rows = [r for r in rows if re.search(r"\| (DONE|CHANGED|INTENTIONAL_NON_ACTION) \|", r)] - if not data_rows: - findings.append( - Finding( - code="weak-envelope-coverage", - message="No DONE, CHANGED, or INTENTIONAL_NON_ACTION rows in evidence envelope", - severity="WARNING", - ) - ) +def cmd_inspect(plan_folder: Path, output_format: str) -> int: + emit([], output_format, basic_report(plan_folder)) + return 0 + - _emit_findings(findings, format) +def cmd_resume(plan_folder: Path, output_format: str) -> int: + status_path, status, findings = validate_status_file(plan_folder) + report = basic_report(plan_folder) + report.update( + { + "status_file": status_path.name if status_path else None, + "status": status, + "resumable": status in {"BLOCKED", "PARTIAL", "NEEDS_REVIEW"}, + } + ) + emit(findings, output_format, report) return 0 if not any(f.severity == "ERROR" for f in findings) else 1 -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Portable retained-plan execution CLI.") - sub = parser.add_subparsers(dest="command", required=True) +def cmd_checkpoint(plan_folder: Path, output_format: str) -> int: + report = basic_report(plan_folder) + report["status_file_required"] = True + emit([], output_format, report) + return 0 - inspect_p = sub.add_parser("inspect", help="Inspect plan folder status") - inspect_p.add_argument("plan_folder", type=Path, help="Path to plan folder") - inspect_p.add_argument("--format", choices=("text", "json"), default="text") - resume_p = sub.add_parser("resume", help="Check resume safety") - resume_p.add_argument("plan_folder", type=Path, help="Path to plan folder") - resume_p.add_argument("--format", choices=("text", "json"), default="text") +def cmd_status_check(plan_folder: Path, output_format: str) -> int: + status_path, status, findings = validate_status_file(plan_folder) + report = { + "plan_folder": str(plan_folder), + "plan_basename": plan_folder.name, + "status_file": status_path.name if status_path else None, + "status": status, + } + emit(findings, output_format, report) + return 0 if not any(f.severity == "ERROR" for f in findings) else 1 - checkpoint_p = sub.add_parser("checkpoint", help="Check checkpoint safety") - checkpoint_p.add_argument("plan_folder", type=Path, help="Path to plan folder") - checkpoint_p.add_argument("--format", choices=("text", "json"), default="text") - state_p = sub.add_parser("state-check", help="Validate -plan-state.md marker") - state_p.add_argument("plan_folder", type=Path, help="Path to plan folder") - state_p.add_argument("--format", choices=("text", "json"), default="text") +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Validate gateway retained-plan status files.") + sub = parser.add_subparsers(dest="command", required=True) - cc_p = sub.add_parser("completion-check", help="Validate DONE readiness") - cc_p.add_argument("plan_folder", type=Path, help="Path to plan folder") - cc_p.add_argument("--format", choices=("text", "json"), default="text") + for command in ("inspect", "resume", "checkpoint", "status-check", "completion-check"): + command_parser = sub.add_parser(command) + command_parser.add_argument("plan_folder", type=Path) + command_parser.add_argument("--format", choices=("text", "json"), default="text") return parser.parse_args() @@ -626,10 +233,9 @@ def main() -> int: return cmd_resume(plan_folder, args.format) if args.command == "checkpoint": return cmd_checkpoint(plan_folder, args.format) - if args.command == "state-check": - return cmd_state_check(plan_folder, args.format) - if args.command == "completion-check": - return cmd_completion_check(plan_folder, args.format) + if args.command in {"status-check", "completion-check"}: + return cmd_status_check(plan_folder, args.format) + print(f"Unknown command: {args.command}", file=sys.stderr) return 1 diff --git a/tests/github/skills/internal_gateway_execute_plans/scripts/test_plan_execution.py b/tests/github/skills/internal_gateway_execute_plans/scripts/test_plan_execution.py index 4c8d826..22870d5 100644 --- a/tests/github/skills/internal_gateway_execute_plans/scripts/test_plan_execution.py +++ b/tests/github/skills/internal_gateway_execute_plans/scripts/test_plan_execution.py @@ -1,4 +1,4 @@ -"""Tests for plan_execution.py — bundle-local CLI for retained-plan execution.""" +"""Tests for plan_execution.py status-file validation.""" from __future__ import annotations @@ -23,461 +23,200 @@ def run_cli(*args: str | Path) -> subprocess.CompletedProcess[str]: ) -def _write_profile_ledger(plan_folder: Path, profile: str = "compact") -> None: - if profile == "compact": - (plan_folder / "02-execution.md").write_text( - "# Execution\n\n" - "## Plan profile\ncompact\n\n" - "## Target and anti-scope\n### Target\nTarget.\n### Anti-scope\nNone.\n\n" - "## Owner and validator\nOwner.\n\n" - "## Stop conditions\nNone.\n\n" - "## Objective\nTest.\n\n" - "## Chosen logic\nTest.\n\n" - "## Key assumptions\nNone.\n\n" - "## Executable steps\n" - "1. Do it.\n" - " Target: file\n" - " Acceptance: done\n" - " Validation: pytest\n" - " Fallback: stop\n\n" - "## Validation\nTest.\n\n" - "## Source item coverage\n" - "| ID | Source item | Observable acceptance | Evidence class | Acceptance evidence | Status | Route |\n" - "| --- | --- | --- | --- | --- | --- | --- |\n" - "| X-01 | Test | diff | diff | pytest | DONE | `none` |\n", - encoding="utf-8", - ) - return - - (plan_folder / "02-control.md").write_text( - "# Control\n\n" - "## Recommended use\napply-plan\n\n" - "## Plan profile\nextended\n\n" - "## File map and role\n| File | Role |\n| --- | --- |\n| `03-execution.md` | execution |\n\n" - "## Clarification gate\nclarification satisfied\n\n" - "## Initial evidence pass\n1. quick pass\n\n" - "## Reading budget\n- compact\n\n" - "## Target and anti-scope\n### Target\nTarget.\n### Anti-scope\nNone.\n\n" - "## Owner and validator\nOwner.\n\n" - "## Stop conditions\nNone.\n\n" - "## Sources\n- source\n\n" - "## Candidate targets\n- target\n\n" - "## Validation commands\nRun in this order:\n1. pytest\n\n" - "## Blockers and fallback rules\n- none\n\n" - "## External pins\nno external evidence\n\n" - "## Source item ledger\n" - "| ID | Source item | Observable acceptance | Evidence class | Acceptance evidence | Status | Route |\n" - "| --- | --- | --- | --- | --- | --- | --- |\n" - "| X-01 | Test | diff | diff | pytest | DONE | `03-execution.md` |\n", - encoding="utf-8", - ) - - -def _write_compact_plan(plan_folder: Path) -> None: +def write_plan(plan_folder: Path) -> None: plan_folder.mkdir(parents=True, exist_ok=True) (plan_folder / "01-change-summary.md").write_text( "## Problema da risolvere\nTest.\n" - "## Risultato atteso\nTest.\n" - "## Risorse coinvolte\n| Risorsa | Azione | Scopo |\n| X | Y | Z |\n" - "## Comportamento scelto\nTest.\n" - "## Validazione prevista\nTest.\n" - "## Decisione richiesta\nApprove.\n", + "## Risultato atteso\nTest.\n", encoding="utf-8", ) - _write_profile_ledger(plan_folder, profile="compact") - - -def _write_completed_plan(plan_folder: Path) -> None: - plan_folder.mkdir(parents=True, exist_ok=True) - (plan_folder / "done-01-sample.md").write_text("# Done\n", encoding="utf-8") - (plan_folder / "evidence-envelope.md").write_text( - "# Evidence Envelope\n\n" - "| Source item | Status | Evidence path or command |\n" - "| --- | --- | --- |\n" - "| `done-01-sample.md` | DONE | `pytest` |\n" - "| RPV-01 | DONE | `pytest` |\n", - encoding="utf-8", - ) - (plan_folder / "completion-report.md").write_text( - "Completion Report\n" - "Plan profile: compact\n" - "Active phase and owner: execute\n" - "State: DONE\n" - "Continuation: none\n" - "User action required: none\n" - "Files changed: test\n" - "Completed items: all\n" - "Intentional non-actions: none\n" - "Validators: pytest\n" - "Evidence envelope: evidence-envelope.md\n" - "Source-item ledger: all closed\n" - "Evidence gaps: none\n" - "Residual risks: none\n" - "Next-step package: none\n" - "Follow-up suggestions: none\n", + (plan_folder / "02-execution.md").write_text( + "# Execution\n\n" + "Plan profile: compact\n\n" + "Target: test\n" + "Anti-scope: none\n" + "Validation: pytest\n", encoding="utf-8", ) -def _write_lightweight_plan_state( - plan_folder: Path, state: str = "DONE", continuation: str = "none" -) -> None: - marker_name = f"{state}-plan-state.md" - (plan_folder / marker_name).write_text( - f"Plan State\nState: {state}\nContinuation: {continuation}\n", +def write_status(plan_folder: Path, status: str = "DONE") -> Path: + status_path = plan_folder / f"{plan_folder.name}.{status}.md" + status_path.write_text( + f"# {plan_folder.name} Status\n\n" + "## Status\n\n" + f"{status}\n\n" + "## Reason\n\n" + "Evidence supports this state.\n\n" + "## Completed\n\n" + "- Contract checked.\n\n" + "## Remaining\n\n" + "- None.\n\n" + "## Validation\n\n" + "- `pytest` passed.\n\n" + "## Next\n\n" + "- No action required.\n\n" + "## Resume Notes\n\n" + "- Re-run validation after new edits.\n", encoding="utf-8", ) + return status_path -# inspect +def test_inspect_reports_plan_without_status(tmp_path: Path) -> None: + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) - -def test_inspect_compact_ready(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) result = run_cli("inspect", plan_folder) assert result.returncode == 0 - assert "compact" in result.stdout - assert "02-execution.md" in result.stdout or "02" in result.stdout - - -def test_inspect_compact_json(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) - result = run_cli("inspect", plan_folder, "--format", "json") - - assert result.returncode == 0 - payload = json.loads(result.stdout) - assert payload["profile"] == "compact" - - -def test_inspect_unsupported_rejected(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - plan_folder.mkdir() - result = run_cli("inspect", plan_folder) - assert result.returncode != 0 - assert "unsupported-plan-contract" in result.stdout - - -def test_inspect_missing_folder(tmp_path: Path) -> None: - result = run_cli("inspect", tmp_path / "nonexistent") - assert result.returncode != 0 - assert "Not a directory" in result.stderr - - -# resume - - -def test_resume_active_plan(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) - result = run_cli("resume", plan_folder) - - assert result.returncode == 0 - assert "resumable" in result.stdout.lower() + assert "sample-plan" in result.stdout + assert "status_file_present: False" in result.stdout -def test_resume_live_folder_state(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) - (plan_folder / "completion-report.md").write_text( - "Completion Report\nState: PARTIAL\nContinuation: continuing\n", - encoding="utf-8", - ) - result = run_cli("resume", plan_folder) - assert result.returncode == 0 - assert "PARTIAL" in result.stdout +def test_status_check_accepts_done_status_file(tmp_path: Path) -> None: + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) + write_status(plan_folder, "DONE") - -def test_resume_unsupported_rejected(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - plan_folder.mkdir() - result = run_cli("resume", plan_folder) - assert result.returncode != 0 - assert "unsupported-plan-contract" in result.stdout - - -# checkpoint - - -def test_checkpoint_active_plan(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) - result = run_cli("checkpoint", plan_folder) + result = run_cli("status-check", plan_folder) assert result.returncode == 0 - assert "can_checkpoint" in result.stdout.lower() - - -def test_checkpoint_unsupported_rejected(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - plan_folder.mkdir() - result = run_cli("checkpoint", plan_folder) - assert result.returncode != 0 - assert "unsupported-plan-contract" in result.stdout + assert "DONE" in result.stdout -# completion-check +def test_completion_check_is_status_check_compatibility_alias(tmp_path: Path) -> None: + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) + write_status(plan_folder, "DONE") - -def test_completion_check_shipped_ready(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_completed_plan(plan_folder) - result = run_cli("completion-check", plan_folder) - - assert result.returncode == 0 - - -def test_completion_check_lightweight_shipped_ready(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) - _write_lightweight_plan_state(plan_folder) - - result = run_cli("completion-check", plan_folder) - - assert result.returncode == 0 - - -def test_completion_check_shipped_json(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_completed_plan(plan_folder) result = run_cli("completion-check", plan_folder, "--format", "json") assert result.returncode == 0 payload = json.loads(result.stdout) assert payload["ready"] is True + assert payload["status"] == "DONE" -def test_completion_check_lightweight_json(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) - _write_lightweight_plan_state(plan_folder) +def test_status_check_rejects_missing_status_file(tmp_path: Path) -> None: + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) - result = run_cli("completion-check", plan_folder, "--format", "json") + result = run_cli("status-check", plan_folder) - assert result.returncode == 0 - payload = json.loads(result.stdout) - assert payload["ready"] is True + assert result.returncode != 0 + assert "missing-status-file" in result.stdout -def test_completion_rejects_active_numbered_files(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_completed_plan(plan_folder) - # Add a profile file so the profile gate passes - (plan_folder / "02-execution.md").write_text( - "## Plan profile\ncompact\n", +def test_status_check_rejects_legacy_plan_state_marker(tmp_path: Path) -> None: + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) + (plan_folder / "DONE-plan-state.md").write_text( + "Plan State\nState: DONE\nContinuation: none\n", encoding="utf-8", ) - (plan_folder / "03-still-active.md").write_text("# Active\n", encoding="utf-8") - result = run_cli("completion-check", plan_folder) + + result = run_cli("status-check", plan_folder) assert result.returncode != 0 - assert "active-numbered-files" in result.stdout - - -def test_completion_rejects_open_statuses(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - plan_folder.mkdir() - (plan_folder / "done-01.md").write_text("# Done\n", encoding="utf-8") - (plan_folder / "evidence-envelope.md").write_text( - "# Evidence Envelope\n\n" - "| Source item | Status | Evidence path or command |\n" - "| --- | --- | --- |\n" - "| `done-01.md` | PENDING | `pytest` |\n", - encoding="utf-8", - ) - (plan_folder / "completion-report.md").write_text( - "Completion Report\nPlan profile: compact\nState: DONE\n" - "Continuation: none\nUser action required: none\nFiles changed: test\n" - "Completed items: all\nIntentional non-actions: none\nValidators: pytest\n" - "Evidence envelope: evidence-envelope.md\nSource-item ledger: all closed\n" - "Evidence gaps: none\nResidual risks: none\nNext-step package: none\n" - "Follow-up suggestions: none\n", + assert "legacy-plan-state-marker" in result.stdout + + +def test_status_check_rejects_invalid_status(tmp_path: Path) -> None: + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) + (plan_folder / "sample-plan.CANCELLED.md").write_text( + "# sample-plan Status\n\n" + "## Status\n\n" + "CANCELLED\n\n" + "## Reason\n\n" + "Cancelled.\n\n" + "## Completed\n\n" + "- None.\n\n" + "## Remaining\n\n" + "- All.\n\n" + "## Validation\n\n" + "- Not run.\n\n" + "## Next\n\n" + "- Author a new plan.\n\n" + "## Resume Notes\n\n" + "- Closed.\n", encoding="utf-8", ) - result = run_cli("completion-check", plan_folder) + + result = run_cli("status-check", plan_folder) + assert result.returncode != 0 - assert "open-status" in result.stdout + assert "invalid-status-file-name" in result.stdout -def test_completion_lightweight_rejects_not_shipped_state(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) - _write_lightweight_plan_state( - plan_folder, state="PARTIAL", continuation="continuing" +def test_status_check_rejects_missing_heading(tmp_path: Path) -> None: + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) + status_path = write_status(plan_folder, "NEEDS_REVIEW") + status_path.write_text( + status_path.read_text(encoding="utf-8").replace("## Resume Notes\n\n", ""), + encoding="utf-8", ) - result = run_cli("completion-check", plan_folder) + result = run_cli("status-check", plan_folder) assert result.returncode != 0 - assert "missing-next-step-package" in result.stdout + assert "missing-required-heading" in result.stdout -def test_completion_lightweight_rejects_nonterminal_continuation( - tmp_path: Path, -) -> None: - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) - _write_lightweight_plan_state(plan_folder, continuation="waiting") +def test_status_check_rejects_multiple_status_files(tmp_path: Path) -> None: + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) + write_status(plan_folder, "PARTIAL") + write_status(plan_folder, "BLOCKED") - result = run_cli("completion-check", plan_folder) + result = run_cli("status-check", plan_folder) assert result.returncode != 0 - assert "nonterminal-continuation" in result.stdout + assert "multiple-status-files" in result.stdout -def test_completion_lightweight_rejects_filename_state_mismatch(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) - (plan_folder / "DONE-plan-state.md").write_text( - "Plan State\nState: PARTIAL\nContinuation: none\n", - encoding="utf-8", - ) +def test_resume_reports_existing_status(tmp_path: Path) -> None: + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) + write_status(plan_folder, "PARTIAL") - result = run_cli("completion-check", plan_folder) + result = run_cli("resume", plan_folder) - assert result.returncode != 0 - assert "plan-state-name-mismatch" in result.stdout - - -def test_completion_rejects_not_shipped_state(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - _write_completed_plan(plan_folder) - (plan_folder / "completion-report.md").write_text( - "Completion Report\nPlan profile: compact\nState: PARTIAL\n" - "Continuation: waiting\nUser action required: approve\nFiles changed: test\n" - "Completed items: some\nIntentional non-actions: none\nValidators: pytest\n" - "Evidence envelope: evidence-envelope.md\nSource-item ledger: open\n" - "Evidence gaps: missing\nResidual risks: high\nNext-step package: none\n" - "Follow-up suggestions: none\n", - encoding="utf-8", - ) - result = run_cli("completion-check", plan_folder) - assert result.returncode != 0 - assert "not-done-state" in result.stdout - - -def test_completion_missing_envelope(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - plan_folder.mkdir() - (plan_folder / "completion-report.md").write_text( - "Completion Report\nPlan profile: compact\nState: DONE\n" - "Continuation: none\nUser action required: none\nFiles changed: test\n" - "Completed items: all\nIntentional non-actions: none\nValidators: pytest\n" - "Evidence envelope: evidence-envelope.md\nSource-item ledger: all closed\n" - "Evidence gaps: none\nResidual risks: none\nNext-step package: none\n" - "Follow-up suggestions: none\n", - encoding="utf-8", - ) - result = run_cli("completion-check", plan_folder) - assert result.returncode != 0 - assert "missing-evidence-envelope" in result.stdout - - -def test_completion_missing_done_reference(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - plan_folder.mkdir() - (plan_folder / "done-01.md").write_text("# Done\n", encoding="utf-8") - (plan_folder / "done-02.md").write_text("# Done\n", encoding="utf-8") - (plan_folder / "evidence-envelope.md").write_text( - "# Evidence Envelope\n\n" - "| Source item | Status | Evidence path or command |\n" - "| --- | --- | --- |\n" - "| `done-01.md` | DONE | `pytest` |\n", - encoding="utf-8", - ) - (plan_folder / "completion-report.md").write_text( - "Completion Report\nPlan profile: compact\nState: DONE\n" - "Continuation: none\nUser action required: none\nFiles changed: test\n" - "Completed items: all\nIntentional non-actions: none\nValidators: pytest\n" - "Evidence envelope: evidence-envelope.md\nSource-item ledger: all closed\n" - "Evidence gaps: none\nResidual risks: none\nNext-step package: none\n" - "Follow-up suggestions: none\n", - encoding="utf-8", - ) - result = run_cli("completion-check", plan_folder) - assert result.returncode != 0 - assert "missing-done-reference" in result.stdout - - -def test_completion_cancelled_not_equivalent_to_done(tmp_path: Path) -> None: - """CANCELLED is not a DONE substitute.""" - plan_folder = tmp_path / "plan" - _write_completed_plan(plan_folder) - (plan_folder / "completion-report.md").write_text( - "Completion Report\nPlan profile: compact\nState: CANCELLED\n" - "Continuation: none\nUser action required: none\nFiles changed: test\n" - "Completed items: none\nIntentional non-actions: cancelled\nValidators: none\n" - "Evidence envelope: evidence-envelope.md\nSource-item ledger: open\n" - "Evidence gaps: all\nResidual risks: high\nNext-step package: none\n" - "Follow-up suggestions: none\n", - encoding="utf-8", - ) - result = run_cli("completion-check", plan_folder) - assert result.returncode != 0 - assert "not-done-state" in result.stdout + assert result.returncode == 0 + assert "PARTIAL" in result.stdout -def test_completion_unsupported_rejected(tmp_path: Path) -> None: - plan_folder = tmp_path / "plan" - plan_folder.mkdir() - (plan_folder / "03-still-active.md").write_text("# Active\n", encoding="utf-8") - result = run_cli("completion-check", plan_folder) - assert result.returncode != 0 - assert "unsupported-plan-contract" in result.stdout - - -def test_completion_rejects_missing_profile_when_packaged(tmp_path: Path) -> None: - """completion-check enforces the profile gate even with no active numbered files.""" - plan_folder = tmp_path / "plan" - _write_completed_plan(plan_folder) - (plan_folder / "completion-report.md").write_text( - "Completion Report\n" - "Active phase and owner: execute\n" - "State: DONE\n" - "Continuation: none\n" - "User action required: none\n" - "Files changed: test\n" - "Completed items: all\n" - "Intentional non-actions: none\n" - "Validators: pytest\n" - "Evidence envelope: evidence-envelope.md\n" - "Source-item ledger: all closed\n" - "Evidence gaps: none\n" - "Residual risks: none\n" - "Next-step package: none\n" - "Follow-up suggestions: none\n", - encoding="utf-8", - ) - result = run_cli("completion-check", plan_folder) - assert result.returncode != 0 - assert "unsupported-plan-contract" in result.stdout +def test_checkpoint_reports_status_requirement(tmp_path: Path) -> None: + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) + result = run_cli("checkpoint", plan_folder) -# Isolated copy test + assert result.returncode == 0 + assert "status_file_required: True" in result.stdout def test_copied_bundle_runs_independently(tmp_path: Path) -> None: - """A copied skill bundle runs its CLI with ambient Python stdlib only.""" bundle_copy = tmp_path / "internal-gateway-execute-plans" shutil.copytree(Path(".github/skills/internal-gateway-execute-plans"), bundle_copy) cli = bundle_copy / "scripts" / "plan_execution.py" - plan_folder = tmp_path / "plan" - _write_compact_plan(plan_folder) + plan_folder = tmp_path / "sample-plan" + write_plan(plan_folder) + write_status(plan_folder, "DONE") result = subprocess.run( - [sys.executable, str(cli), "inspect", str(plan_folder)], + [sys.executable, str(cli), "status-check", str(plan_folder)], capture_output=True, text=True, ) assert result.returncode == 0 - assert "compact" in result.stdout + assert "DONE" in result.stdout -def test_copied_bundle_no_cross_bundle_import(tmp_path: Path) -> None: - """Verify no import of sibling bundle or .github/scripts/lib.""" +def test_copied_bundle_no_cross_bundle_import() -> None: import ast executing_code = EXECUTING_CLI.read_text(encoding="utf-8") diff --git a/tests/test_completion_report_contract.py b/tests/test_completion_report_contract.py index 03214d2..a1c7ca2 100644 --- a/tests/test_completion_report_contract.py +++ b/tests/test_completion_report_contract.py @@ -45,10 +45,11 @@ def test_completion_report_docs_match_optional_detail_contract() -> None: def test_completion_report_requires_mandatory_applicable_evidence_for_shipped() -> None: completion_reference = read_text( - ".github/skills/internal-gateway-execute-plans/references/completion-report.md" + ".github/skills/internal-gateway-execute-plans/references/status-file.md" ) assert ( - "mandatory applicable skill requirements are verified" in completion_reference + "Use this reference when `internal-gateway-execute-plans` finishes, pauses, or" + in completion_reference ) - assert "if mandatory applicable evidence is missing" in completion_reference + assert "## Resume Notes" in completion_reference diff --git a/tests/test_plan_policy_contract.py b/tests/test_plan_policy_contract.py index 09c97fd..8771429 100644 --- a/tests/test_plan_policy_contract.py +++ b/tests/test_plan_policy_contract.py @@ -15,36 +15,73 @@ def test_root_policy_files_keep_retained_plan_paths_outside_always_on_detail() - assert "tmp/superpowers/" not in copilot_text -def test_writing_plans_declares_profile_only_handoff_contract() -> None: +def test_writing_plans_resets_to_superpowers_wrapper_contract() -> None: writing_text = read_text(".github/skills/internal-gateway-writing-plans/SKILL.md") - compact_reference = read_text( - ".github/skills/internal-gateway-writing-plans/references/compact-plan-contract.md" + wrapper_text = read_text( + ".github/skills/internal-gateway-writing-plans/agents/openai.yaml" ) - assert "Recommended consumer" not in writing_text - assert "internal-gateway-simple-task" in writing_text - assert "internal-gateway-execute-plans" in writing_text - assert "mini-plan-*" in compact_reference - assert "Decisioni aperte" in compact_reference - assert "2,000 estimated tokens" in compact_reference - assert "completeness over compression" in writing_text - assert "Escalate to `extended`" in writing_text - assert "prefer splitting into numbered files over compression" in writing_text - assert "data-contract.md" in writing_text - - -def test_executing_plans_accepts_compact_and_extended_consumers() -> None: + + assert "superpowers-writing-plans" in writing_text + assert "delegate" in writing_text.lower() + assert "decide whether to create a retained plan" in writing_text + assert "tmp/superpowers/plans/YYYY-MM-DD-.md" in writing_text + assert "Target" in writing_text + assert "Anti-scope" in writing_text + assert "Nearest owner" in writing_text + assert "Validation path" in writing_text + assert "Stop conditions" in writing_text + assert "Observable acceptance" in writing_text + assert "Execution-readiness check" in writing_text + assert "task order" in writing_text + assert "file targets" in writing_text + assert "handoff readiness" in writing_text + assert "superpowers-writing-plans" in wrapper_text + assert "delegate" in wrapper_text.lower() + + +def test_writing_plans_wrapper_rejects_legacy_protocol_terms() -> None: + tracked_paths = [ + ".github/skills/internal-gateway-writing-plans/SKILL.md", + ".github/skills/internal-gateway-writing-plans/agents/openai.yaml", + ] + banned_terms = [ + "`compact`", + "`extended`", + "mini-plan-*", + "01-change-summary.md", + "02-execution.md", + "02-control.md", + "handoff-check", + "plan_authoring.py", + "Recommended consumer", + "Plan-only mode", + "force plan-only mode", + "stop after plan creation", + ] + + for relative_path in tracked_paths: + text = read_text(relative_path) + for banned in banned_terms: + assert banned not in text, f"{relative_path} still contains {banned}" + + +def test_writing_plans_preserves_short_preflight_context_discipline() -> None: + writing_text = read_text(".github/skills/internal-gateway-writing-plans/SKILL.md") + assert "short preflight" in writing_text + assert "targeted rereads" in writing_text + assert "known-context handoff quality" in writing_text + + +def test_executing_plans_is_lightweight_superpowers_wrapper() -> None: executing_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") - assert "approved `compact`" in executing_text - assert "approved `extended`" in executing_text - assert "infer the execution strategy" in executing_text - assert ( - "Reject `compact` folders outside the `mini-plan-*` convention" - in executing_text - ) - assert "mandatory requirements that are applicable" in executing_text - assert "Block item closure and block `SHIPPED`" in executing_text - assert "Establish execution state" in executing_text - assert "Avoid repeated full rereads" in executing_text + assert "gateway-only wrapper" in executing_text + assert "superpowers-executing-plans" in executing_text + assert "..md" in executing_text + assert "DONE" in executing_text + assert "BLOCKED" in executing_text + assert "PARTIAL" in executing_text + assert "NEEDS_REVIEW" in executing_text + assert "Legacy Contract Boundary" in executing_text def test_gateway_handoff_references_use_canonical_execution_owner() -> None: @@ -58,74 +95,3 @@ def test_gateway_handoff_references_use_canonical_execution_owner() -> None: assert "internal-executing-plans" not in compatibility_text assert "internal-gateway-execute-plans" in handoff_text assert "internal-executing-plans" not in handoff_text - - -def test_wrapper_prompts_respect_compact_and_extended_consumers() -> None: - writing_wrapper = read_text( - ".github/skills/internal-gateway-writing-plans/agents/openai.yaml" - ) - simple_wrapper = read_text( - ".github/skills/internal-gateway-simple-task/agents/openai.yaml" - ) - execute_wrapper = read_text( - ".github/skills/internal-gateway-execute-plans/agents/openai.yaml" - ) - assert "Recommended consumer field" in writing_wrapper - assert "Do not" in writing_wrapper - assert "best execution path" in writing_wrapper - assert "validation path" in writing_wrapper - assert ( - "Route approved retained-plan execution to internal-gateway-execute-plans" - in simple_wrapper - ) - assert ( - "approved compact mini-plan-* plans and approved extended plans" - in execute_wrapper - ) - - -def test_new_retained_plan_file_model_is_declared() -> None: - writing_text = read_text(".github/skills/internal-gateway-writing-plans/SKILL.md") - compact_reference = read_text( - ".github/skills/internal-gateway-writing-plans/references/compact-plan-contract.md" - ) - execute_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") - - assert "02-execution.md" in writing_text - assert "02-control.md" in writing_text - assert "merged into `02-control.md`" in writing_text - assert "02-execution.md" in compact_reference - assert "02-control.md" in execute_text - - -def test_gateway_skills_preserve_compact_context_discipline() -> None: - simple_text = read_text(".github/skills/internal-gateway-simple-task/SKILL.md") - writing_text = read_text(".github/skills/internal-gateway-writing-plans/SKILL.md") - - assert "Preserve compact working state" in simple_text - assert "Escalation trigger" in simple_text - assert "Preserve known-context handoff quality" in writing_text - assert "run targeted rereads" in writing_text - - -def test_writing_plans_counter_validation_contract_is_explicit() -> None: - writing_text = read_text(".github/skills/internal-gateway-writing-plans/SKILL.md") - compact_reference = read_text( - ".github/skills/internal-gateway-writing-plans/references/compact-plan-contract.md" - ) - review_gate = read_text( - ".github/skills/internal-gateway-writing-plans/references/plan-review-gate.md" - ) - scope_challenge = read_text( - ".github/skills/internal-gateway-writing-plans/references/scope-challenge.md" - ) - - assert "counter-validation-critical facts" in writing_text - assert "counter-validation-critical facts" in compact_reference - assert ( - "counter-validate coverage without reading `02-execution.md` or `02-control.md`" - in review_gate - ) - assert ( - "counter-validate the plan without reading the control file" in scope_challenge - ) diff --git a/tests/test_retained_plan_artifact_contract.py b/tests/test_retained_plan_artifact_contract.py index a7d6e89..2c6b721 100644 --- a/tests/test_retained_plan_artifact_contract.py +++ b/tests/test_retained_plan_artifact_contract.py @@ -39,7 +39,16 @@ "BLOCKED", ) -PLAN_STATE_MARKER_RE = re.compile(r"^([A-Z0-9_-]+)-plan-state\.md$") +GATEWAY_STATUS_VALUES = ("DONE", "BLOCKED", "PARTIAL", "NEEDS_REVIEW") +GATEWAY_STATUS_HEADINGS = ( + "## Status", + "## Reason", + "## Completed", + "## Remaining", + "## Validation", + "## Next", + "## Resume Notes", +) def retained_plan_folders() -> list[Path]: @@ -61,30 +70,42 @@ def done_files(plan_folder: Path) -> list[Path]: return sorted(plan_folder.glob("done-*.md")) -def resolve_plan_state_marker( +def resolve_gateway_status_file( plan_folder: Path, ) -> tuple[Path | None, str | None, list[str]]: violations: list[str] = [] - named_markers = sorted( - path for path in plan_folder.glob("*-plan-state.md") if path.is_file() - ) + statuses = "|".join(GATEWAY_STATUS_VALUES) + status_re = re.compile(rf"^{re.escape(plan_folder.name)}\.({statuses})\.md$") + status_files: list[Path] = [] + + for path in sorted(plan_folder.glob("*.md")): + if status_re.match(path.name): + status_files.append(path) + elif re.match(rf"^{re.escape(plan_folder.name)}\.[A-Z0-9_-]+\.md$", path.name): + violations.append( + f"{path} must use one of these statuses: {', '.join(GATEWAY_STATUS_VALUES)}" + ) - if len(named_markers) > 1: - violations.append(f"{plan_folder} has multiple -plan-state.md markers") + legacy_markers = sorted(plan_folder.glob("*-plan-state.md")) + for marker in legacy_markers: + violations.append( + f"{marker} is a legacy gateway marker; use ..md" + ) + + if len(status_files) > 1: + violations.append(f"{plan_folder} has multiple gateway status files") return None, None, violations - if named_markers: - marker = named_markers[0] - match = PLAN_STATE_MARKER_RE.match(marker.name) - if match is None: - violations.append( - f"{marker} must match -plan-state.md with uppercase state" - ) - return None, None, violations - marker_state = match.group(1).upper() - return marker, marker_state, violations + if not status_files: + return None, None, violations - return None, None, violations + marker = status_files[0] + match = status_re.match(marker.name) + if match is None: + violations.append(f"{marker} must match ..md") + return None, None, violations + + return marker, match.group(1), violations def completed_retained_plan_violations(root: Path) -> list[str]: @@ -92,73 +113,29 @@ def completed_retained_plan_violations(root: Path) -> list[str]: for folder in sorted(path for path in root.iterdir() if path.is_dir()): folder_done_files = done_files(folder) - plan_state_path, marker_state, marker_violations = resolve_plan_state_marker( - folder - ) + status_path, marker_state, marker_violations = resolve_gateway_status_file(folder) violations.extend(marker_violations) - if not folder_done_files and plan_state_path is None: + if not folder_done_files and status_path is None: continue - if plan_state_path is not None and not folder_done_files: - plan_state_text = plan_state_path.read_text(encoding="utf-8") - state_match = re.search(r"^State:\s*(.+)$", plan_state_text, re.MULTILINE) - continuation_match = re.search( - r"^Continuation:\s*(.+)$", plan_state_text, re.MULTILINE - ) - user_action_match = re.search( - r"^User action required:\s*(.+)$", plan_state_text, re.MULTILINE - ) - next_step_match = re.search( - r"^Next-step package:\s*(.+)$", plan_state_text, re.MULTILINE - ) - evidence_gaps_match = re.search( - r"^Evidence gaps:\s*(.+)$", plan_state_text, re.MULTILINE + if status_path is not None and not folder_done_files: + status_text = status_path.read_text(encoding="utf-8") + for heading in GATEWAY_STATUS_HEADINGS: + if heading not in status_text: + violations.append(f"{status_path} is missing {heading}") + + status_match = re.search( + r"^## Status\s*\n+\s*([A-Z0-9_-]+)\s*$", + status_text, + re.MULTILINE, ) - declared_state: str | None = None - - if state_match is None: - violations.append(f"{plan_state_path} is missing State") - else: - declared_state = state_match.group(1).strip().upper() - - if ( - marker_state - and state_match - and state_match.group(1).strip().upper() != marker_state - ): + if status_match is None: + violations.append(f"{status_path} must declare status under ## Status") + elif status_match.group(1) != marker_state: violations.append( - f"{plan_state_path} filename state does not match declared State" + f"{status_path} filename status does not match declared status" ) - - if continuation_match is None: - violations.append(f"{plan_state_path} is missing Continuation") - elif declared_state is not None: - continuation = continuation_match.group(1).strip() - - if declared_state == "DONE": - if continuation != "none": - violations.append( - f"{plan_state_path} must declare Continuation: none" - ) - else: - if continuation not in {"continuing", "waiting"}: - violations.append( - f"{plan_state_path} must declare Continuation: continuing or waiting" - ) - - if continuation == "waiting" and user_action_match is None: - violations.append( - f"{plan_state_path} is missing User action required" - ) - - if next_step_match is None: - violations.append( - f"{plan_state_path} is missing Next-step package" - ) - - if evidence_gaps_match is None: - violations.append(f"{plan_state_path} is missing Evidence gaps") continue active_files = numbered_plan_files(folder) @@ -244,11 +221,25 @@ def write_lightweight_completed_plan_folder(root: Path) -> Path: "## Source item coverage\n" "| ID | Source item | Observable acceptance | Evidence class | Acceptance evidence | Status | Route |\n" "| --- | --- | --- | --- | --- | --- | --- |\n" - "| PLAN-01 | Lightweight completion marker | DONE marker present | file | DONE-plan-state.md | DONE | `none` |\n", + "| PLAN-01 | Lightweight completion marker | DONE status file present | file | sample-plan.DONE.md | DONE | `none` |\n", encoding="utf-8", ) - (plan_folder / "DONE-plan-state.md").write_text( - "Plan State\nState: DONE\nContinuation: none\n", + (plan_folder / "sample-plan.DONE.md").write_text( + "# sample-plan Status\n\n" + "## Status\n\n" + "DONE\n\n" + "## Reason\n\n" + "Complete.\n\n" + "## Completed\n\n" + "- All items.\n\n" + "## Remaining\n\n" + "- None.\n\n" + "## Validation\n\n" + "- `pytest` passed.\n\n" + "## Next\n\n" + "- No action required.\n\n" + "## Resume Notes\n\n" + "- Re-run validation after new edits.\n", encoding="utf-8", ) return plan_folder @@ -257,13 +248,22 @@ def write_lightweight_completed_plan_folder(root: Path) -> Path: def write_blocked_plan_folder(root: Path) -> Path: plan_folder = root / "sample-plan" plan_folder.mkdir() - (plan_folder / "BLOCKED-plan-state.md").write_text( - "Plan State\n" - "State: BLOCKED\n" - "Continuation: waiting\n" - "User action required: approve next owner\n" - "Next-step package: Owner=internal-markdown; Scope=state marker refresh; Action=resume plan; Validation=pytest; Risk=stale evidence\n" - "Evidence gaps: validator remains red\n", + (plan_folder / "sample-plan.BLOCKED.md").write_text( + "# sample-plan Status\n\n" + "## Status\n\n" + "BLOCKED\n\n" + "## Reason\n\n" + "Blocked.\n\n" + "## Completed\n\n" + "- Partial work.\n\n" + "## Remaining\n\n" + "- Await approval.\n\n" + "## Validation\n\n" + "- Not run.\n\n" + "## Next\n\n" + "- Approve next owner.\n\n" + "## Resume Notes\n\n" + "- Resume after approval.\n", encoding="utf-8", ) return plan_folder @@ -325,15 +325,27 @@ def test_completed_retained_plan_validation_rejects_lightweight_non_shipped( tmp_path: Path, ) -> None: plan_folder = write_lightweight_completed_plan_folder(tmp_path) - (plan_folder / "DONE-plan-state.md").write_text( - "Plan State\nState: PARTIAL\nContinuation: continuing\n", + (plan_folder / "sample-plan.DONE.md").write_text( + "# sample-plan Status\n\n" + "## Status\n\n" + "PARTIAL\n\n" + "## Reason\n\n" + "Incomplete.\n\n" + "## Completed\n\n" + "- Some items.\n\n" + "## Remaining\n\n" + "- More items.\n\n" + "## Validation\n\n" + "- Pending.\n\n" + "## Next\n\n" + "- Continue.\n\n" + "## Resume Notes\n\n" + "- Resume later.\n", encoding="utf-8", ) assert completed_retained_plan_violations(tmp_path) == [ - f"{plan_folder / 'DONE-plan-state.md'} filename state does not match declared State", - f"{plan_folder / 'DONE-plan-state.md'} is missing Next-step package", - f"{plan_folder / 'DONE-plan-state.md'} is missing Evidence gaps", + f"{plan_folder / 'sample-plan.DONE.md'} filename status does not match declared status", ] @@ -342,15 +354,25 @@ def test_completed_retained_plan_validation_rejects_waiting_marker_missing_field ) -> None: plan_folder = tmp_path / "sample-plan" plan_folder.mkdir() - (plan_folder / "BLOCKED-plan-state.md").write_text( - "Plan State\nState: BLOCKED\nContinuation: waiting\n", + (plan_folder / "sample-plan.BLOCKED.md").write_text( + "# sample-plan Status\n\n" + "## Status\n\n" + "BLOCKED\n\n" + "## Reason\n\n" + "Blocked.\n\n" + "## Completed\n\n" + "- Partial work.\n\n" + "## Remaining\n\n" + "- Await approval.\n\n" + "## Validation\n\n" + "- Not run.\n\n" + "## Next\n\n" + "- Approve next owner.\n\n" encoding="utf-8", ) assert completed_retained_plan_violations(tmp_path) == [ - f"{plan_folder / 'BLOCKED-plan-state.md'} is missing User action required", - f"{plan_folder / 'BLOCKED-plan-state.md'} is missing Next-step package", - f"{plan_folder / 'BLOCKED-plan-state.md'} is missing Evidence gaps", + f"{plan_folder / 'sample-plan.BLOCKED.md'} is missing ## Resume Notes", ] diff --git a/tests/test_token_budget_skill_contract.py b/tests/test_token_budget_skill_contract.py index 83f5756..fb799b3 100644 --- a/tests/test_token_budget_skill_contract.py +++ b/tests/test_token_budget_skill_contract.py @@ -15,9 +15,6 @@ def test_token_budget_guardrails_are_distributed_by_owner() -> None: idea_text = read_text(".github/skills/internal-gateway-idea-brainstorming/SKILL.md") execute_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") review_text = read_text(".github/skills/internal-gateway-review/SKILL.md") - verification_text = read_text( - ".github/skills/superpowers-verification-before-completion/SKILL.md" - ) spreadsheet_text = read_text(".github/skills/openai-spreadsheet/SKILL.md") assert "Token Budget Gate" in simple_text @@ -26,7 +23,6 @@ def test_token_budget_guardrails_are_distributed_by_owner() -> None: assert "aggregate-first" in idea_text assert "Compact Evidence Reporting" in execute_text assert "Compact Evidence Reporting" in review_text - assert "Compact Evidence Reporting" in verification_text assert "Structured Data Evidence Budget" in spreadsheet_text diff --git a/tests/test_workflow_review_contract.py b/tests/test_workflow_review_contract.py index 61ec4e8..1a689ee 100644 --- a/tests/test_workflow_review_contract.py +++ b/tests/test_workflow_review_contract.py @@ -117,22 +117,18 @@ def test_compact_and_extended_execution_owner_is_unified() -> None: ".github/skills/internal-gateway-critical-master/SKILL.md" ) executing_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") - assert "Recommended consumer" not in writing_text - assert "internal-gateway-execute-plans" in writing_text - assert "internal-gateway-simple-task" in writing_text - assert "mini-plan-*" in executing_text - assert "`compact`" in executing_text - assert "retained plans" in executing_text + assert "superpowers-writing-plans" in writing_text + assert "delegate" in writing_text.lower() assert "internal-gateway-execute-plans" in simple_text assert "internal-executing-plans" not in simple_text assert "internal-gateway-execute-plans" in critical_text assert "internal-executing-plans" not in critical_text - assert "approved `compact`" in executing_text - assert "approved `extended`" in executing_text + assert "gateway-only wrapper" in executing_text + assert "approved retained plans" in executing_text assert "internal-gateway-execute-plans" in executing_text -def test_gateway_compliance_audit_contract_is_explicit() -> None: +def test_gateway_status_file_contract_is_explicit() -> None: simple_text = read_text(".github/skills/internal-gateway-simple-task/SKILL.md") executing_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") @@ -141,10 +137,10 @@ def test_gateway_compliance_audit_contract_is_explicit() -> None: assert "Block completion claims" in simple_text assert "single-lane and single-phase" in simple_text - assert "item-level compliance audit" in executing_text - assert "block `SHIPPED`" in executing_text - assert "undefined validation strategy" in executing_text - assert "stdlib-only CLI launcher" in executing_text + assert "gateway-only wrapper" in executing_text + assert "Compact Evidence Reporting" in executing_text + assert "..md" in executing_text + assert "No `DONE` claim" in executing_text def test_simple_gateway_direct_execution_control_contract_is_explicit() -> None: @@ -187,18 +183,16 @@ def test_gateway_wrappers_route_compact_and_extended_to_execute_plans() -> None: execute_wrapper = read_text( ".github/skills/internal-gateway-execute-plans/agents/openai.yaml" ) - completion_reference = read_text( - ".github/skills/internal-gateway-execute-plans/references/completion-report.md" + status_reference = read_text( + ".github/skills/internal-gateway-execute-plans/references/status-file.md" ) assert ( "Route approved retained-plan execution to internal-gateway-execute-plans" in simple_wrapper ) - assert "infer the best execution strategy from plan profile" in execute_wrapper - assert ( - "`compact` and `extended` execution both use `internal-gateway-execute-plans`" - in completion_reference.lower() - ) + assert "superpowers-executing-plans" in execute_wrapper + assert "..md" in execute_wrapper + assert "## Resume Notes" in status_reference def test_simple_gateway_readiness_brief_and_approval_gate_contract() -> None: @@ -229,10 +223,11 @@ def test_writing_plans_scaffold_first_and_audit_early_contract() -> None: ".github/skills/internal-gateway-writing-plans/agents/openai.yaml" ) - assert "run bundle-local `init` first" in writing_text - assert "Run `audit` first, then run `handoff-check`" in writing_text - assert "token warnings as review inputs" in writing_text - assert "init scaffold first" in runtime_text - assert "audit first" in runtime_text - assert "handoff-check" in runtime_text - assert "second" in runtime_text + assert "short preflight" in writing_text + assert "Execution-readiness check" in writing_text + assert "tmp/superpowers/plans/YYYY-MM-DD-.md" in writing_text + assert "repository preflight wrapper" in runtime_text + assert "superpowers-writing-plans" in runtime_text + assert "delegate" in runtime_text.lower() + assert "Execution-readiness check" in runtime_text + assert "handoff readiness" in runtime_text From 0829515918d1ab57d6462918b8fa25bb5fa3277b Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sat, 4 Jul 2026 23:58:26 +0200 Subject: [PATCH 14/44] refactor: update SKILL.md to streamline referenced skills and add execution discipline guidelines --- .../internal-gateway-execute-plans/SKILL.md | 17 ++++++++---- .gitignore | 1 + tests/test_plan_policy_contract.py | 27 +++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/.github/skills/internal-gateway-execute-plans/SKILL.md b/.github/skills/internal-gateway-execute-plans/SKILL.md index 03273a4..e87606a 100644 --- a/.github/skills/internal-gateway-execute-plans/SKILL.md +++ b/.github/skills/internal-gateway-execute-plans/SKILL.md @@ -10,10 +10,7 @@ gateway-only wrapper for approved retained plans. This skill does not replace `s ## Referenced Skills - `superpowers-executing-plans`: required execution engine for the plan steps. -- `superpowers-subagent-driven-development`: preferred when same-session subagents are available and the execution path supports them. - `superpowers-verification-before-completion`: evidence gate before completion claims when fresh validation is required. -- `internal-gateway-writing-plans`: owner of retained-plan authoring contracts. -- `internal-gateway-review`: owner when the next risk is correctness evidence. ## When To Use @@ -23,11 +20,21 @@ gateway-only wrapper for approved retained plans. This skill does not replace `s ## When Not To Use -- Writing or reformulating a plan; use `internal-gateway-writing-plans`. -- Reviewing or challenging a plan; use `internal-gateway-review` or `internal-gateway-critical-master`. +- Writing or reformulating a plan. +- Reviewing or challenging a plan. - Executing `questions.md` or unapproved planning notes. - Changing `superpowers-executing-plans`; this wrapper must not patch imported Superpowers behavior. +## Execution Discipline + +- Plan-bound execution: follow the approved retained plan and stop when the plan is no longer executable as written. +- DRY: do not duplicate gateway workflow logic, status-file handling, validation reporting, or source logic when an existing owner already provides it. +- YAGNI: do not add speculative helpers, abstractions, configuration, or future-proofing beyond the approved plan. +- KISS: prefer the simplest coherent change that satisfies the plan and remains readable. +- Separation of Concerns: keep execution, status files, validation evidence, planning, review, critique, and domain implementation responsibilities separate. +- single responsibility: each new helper, script, section, or code path must have one clear reason tied to the active task. +- Fail-fast on drift: stop and record the defect when the plan forces confusing code, duplicated logic, missing validation, owner conflict, or scope drift. + ## Wrapper Contract 1. Confirm the retained plan path and that execution is approved. diff --git a/.gitignore b/.gitignore index 0f44666..1719451 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ env/ .github/scripts/lib/graphify-out/* .github/scripts/graphify-out/* tmp/ +tmp/superpowers/specs/ .superpowers diff --git a/tests/test_plan_policy_contract.py b/tests/test_plan_policy_contract.py index 8771429..ef1d88c 100644 --- a/tests/test_plan_policy_contract.py +++ b/tests/test_plan_policy_contract.py @@ -84,6 +84,33 @@ def test_executing_plans_is_lightweight_superpowers_wrapper() -> None: assert "Legacy Contract Boundary" in executing_text +def test_executing_plans_gateway_referenced_skills_are_narrow() -> None: + executing_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") + + assert executing_text.index("# Internal Gateway Execute Plans") < executing_text.index( + "## Referenced Skills" + ) + assert executing_text.index("## Referenced Skills") < executing_text.index( + "## When To Use" + ) + assert "superpowers-executing-plans" in executing_text + assert "superpowers-verification-before-completion" in executing_text + assert "superpowers-subagent-driven-development" not in executing_text + assert "internal-gateway-writing-plans" not in executing_text + assert "internal-gateway-review" not in executing_text + assert "internal-gateway-critical-master" not in executing_text + assert "internal-tdd" not in executing_text + assert "superpowers-test-driven-development" not in executing_text + assert "## Execution Discipline" in executing_text + assert "Plan-bound execution" in executing_text + assert "DRY" in executing_text + assert "YAGNI" in executing_text + assert "KISS" in executing_text + assert "Separation of Concerns" in executing_text + assert "single responsibility" in executing_text + assert "Fail-fast on drift" in executing_text + + def test_gateway_handoff_references_use_canonical_execution_owner() -> None: compatibility_text = read_text( ".github/skills/internal-gateway-idea-brainstorming/references/compatibility-matrix.md" From b278126492d7fdcb7d47c3509b76783cce1cf7f1 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 00:01:17 +0200 Subject: [PATCH 15/44] Remove obsolete test files for sync resource fingerprint, token budget skill contract, validation entrypoints contract, workflow review contract, and debug log analysis tool. These tests are no longer relevant to the current project structure and functionality. --- tests/conftest.py | 10 - tests/fixtures/critical_output_bad.md | 44 - tests/fixtures/critical_output_good.md | 36 - .../github/scripts/lib/test_catalog_checks.py | 123 -- .../github/scripts/lib/test_fingerprinting.py | 106 -- .../scripts/lib/test_internal_skills.py | 166 -- tests/github/scripts/lib/test_jsonc.py | 66 - tests/github/scripts/lib/test_shared.py | 117 -- .../scripts/test_benchmark_skill_tokens.py | 113 -- tests/github/scripts/test_cli_entrypoints.py | 472 ----- .../test_repo_owned_script_coverage.py | 66 - .../scripts/test_runner_dispatch_contract.py | 42 - .../scripts/test_plan_execution.py | 236 --- .../scripts/test_audit_contract.py | 51 - .../scripts/test_resolve_simple_task.py | 193 --- .../scripts/test_suggest_support_skills.py | 318 ---- .../scripts/test_agent_translation.py | 310 ---- .../scripts/test_bisync_skills.py | 618 ------- .../scripts/test_home_syncing.py | 917 ---------- .../test_agent_os_portable_skills_contract.py | 41 - tests/test_apply_imported_asset_overrides.py | 63 - tests/test_canonical_agents_contract.py | 41 - tests/test_completion_report_contract.py | 55 - tests/test_critical_master_scripts.py | 125 -- .../test_external_refresh_workspace_guard.py | 68 - tests/test_home_ai_resources_sync.py | 1089 ------------ .../test_imported_asset_overrides_contract.py | 112 -- ...t_internal_agent_creator_skill_contract.py | 89 - tests/test_inventory_and_consistency.py | 457 ----- tests/test_markdownlint_config.py | 33 - tests/test_normalize_superpowers_imports.py | 95 -- tests/test_plan_policy_contract.py | 124 -- tests/test_repo_only_agents_contract.py | 159 -- ...est_repository_workflow_policy_contract.py | 355 ---- tests/test_retained_learning_contract.py | 77 - tests/test_retained_plan_artifact_contract.py | 392 ----- tests/test_sync_and_token_risks.py | 1512 ----------------- tests/test_sync_resource_fingerprint.py | 106 -- tests/test_token_budget_skill_contract.py | 54 - tests/test_validation_entrypoints_contract.py | 151 -- tests/test_workflow_review_contract.py | 233 --- .../test_entrypoint.py | 513 ------ 42 files changed, 9948 deletions(-) delete mode 100644 tests/conftest.py delete mode 100644 tests/fixtures/critical_output_bad.md delete mode 100644 tests/fixtures/critical_output_good.md delete mode 100644 tests/github/scripts/lib/test_catalog_checks.py delete mode 100644 tests/github/scripts/lib/test_fingerprinting.py delete mode 100644 tests/github/scripts/lib/test_internal_skills.py delete mode 100644 tests/github/scripts/lib/test_jsonc.py delete mode 100644 tests/github/scripts/lib/test_shared.py delete mode 100644 tests/github/scripts/test_benchmark_skill_tokens.py delete mode 100644 tests/github/scripts/test_cli_entrypoints.py delete mode 100644 tests/github/scripts/test_repo_owned_script_coverage.py delete mode 100644 tests/github/scripts/test_runner_dispatch_contract.py delete mode 100644 tests/github/skills/internal_gateway_execute_plans/scripts/test_plan_execution.py delete mode 100644 tests/github/skills/internal_gateway_idea_brainstorming/scripts/test_audit_contract.py delete mode 100644 tests/github/skills/internal_gateway_simple_task/scripts/test_resolve_simple_task.py delete mode 100644 tests/github/skills/internal_gateway_simple_task/scripts/test_suggest_support_skills.py delete mode 100644 tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_agent_translation.py delete mode 100644 tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_bisync_skills.py delete mode 100644 tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_home_syncing.py delete mode 100644 tests/test_agent_os_portable_skills_contract.py delete mode 100644 tests/test_apply_imported_asset_overrides.py delete mode 100644 tests/test_canonical_agents_contract.py delete mode 100644 tests/test_completion_report_contract.py delete mode 100644 tests/test_critical_master_scripts.py delete mode 100644 tests/test_external_refresh_workspace_guard.py delete mode 100644 tests/test_home_ai_resources_sync.py delete mode 100644 tests/test_imported_asset_overrides_contract.py delete mode 100644 tests/test_internal_agent_creator_skill_contract.py delete mode 100644 tests/test_inventory_and_consistency.py delete mode 100644 tests/test_markdownlint_config.py delete mode 100644 tests/test_normalize_superpowers_imports.py delete mode 100644 tests/test_plan_policy_contract.py delete mode 100644 tests/test_repo_only_agents_contract.py delete mode 100644 tests/test_repository_workflow_policy_contract.py delete mode 100644 tests/test_retained_learning_contract.py delete mode 100644 tests/test_retained_plan_artifact_contract.py delete mode 100644 tests/test_sync_and_token_risks.py delete mode 100644 tests/test_sync_resource_fingerprint.py delete mode 100644 tests/test_token_budget_skill_contract.py delete mode 100644 tests/test_validation_entrypoints_contract.py delete mode 100644 tests/test_workflow_review_contract.py delete mode 100644 tests/tools/analyze_copilot_debug_log/test_entrypoint.py diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 7a1324e..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -import sys -from pathlib import Path - -REPO_ROOT = Path(__file__).resolve().parent.parent -SCRIPTS_ROOT = REPO_ROOT / ".github/scripts" - -if str(SCRIPTS_ROOT) not in sys.path: - sys.path.insert(0, str(SCRIPTS_ROOT)) diff --git a/tests/fixtures/critical_output_bad.md b/tests/fixtures/critical_output_bad.md deleted file mode 100644 index 96c2e36..0000000 --- a/tests/fixtures/critical_output_bad.md +++ /dev/null @@ -1,44 +0,0 @@ -## Summary - -We are challenging a proposal to add a sync manifest layer between the -governance catalog and consumer repositories. The change matters now because -it rewires the contract that every consumer repo reads and could break -materialization for repositories that already adopted the older layout. - -## Findings - -### 1. The consumer rollout risk is understated - -- **Impact:** Consumer repos mid-migration will lose catalog updates. -- **Evidence:** rollout looks fine — no class is declared. -- **Mitigation:** Ship a v1/v2 manifest reader behind a feature flag. - -### 2. The validation cost is hidden - -- **Impact:** Each new schema field requires a sync-wide revalidation. -- **Evidence:** `guess` — the contract test must cover every manifest field. -- **Mitigation:** Reuse the existing inventory validator. - -### 3. The compatibility window is undefined - -- **Impact:** Teams without a documented deprecation timeline stall. -- **Evidence:** `inference` — the proposal does not name a deprecation owner. -- **Mitigation:** Add a deprecation owner and a published window. - -### 4. The observability surface drifts - -- **Impact:** Drift detection loses signal as the manifest evolves. -- **Evidence:** `inference` — no schema-versioned metrics are described. -- **Mitigation:** Version the manifest metrics in the same change. - -## Outcome - -`defer-forever` - -## Next owner - -Route to `internal-gateway-simple-task` to draft the compatibility shim and -the validator reuse notes for the next rollout cycle once the proposal is -finalized end to end and we have a single named owner for the deprecation -window, the rollout flag, the observability plan, and the validation -strategy. diff --git a/tests/fixtures/critical_output_good.md b/tests/fixtures/critical_output_good.md deleted file mode 100644 index d624f78..0000000 --- a/tests/fixtures/critical_output_good.md +++ /dev/null @@ -1,36 +0,0 @@ -## Summary - -We are challenging a proposal to add a sync manifest layer between the -governance catalog and consumer repositories. The change matters now because -it rewires the contract that every consumer repo reads and could break -materialization for repositories that already adopted the older layout. - -## Findings - -### 1. The consumer rollout risk is understated - -- **Impact:** Consumer repos mid-migration will lose catalog updates. -- **Evidence:** `inference` — no compatibility shim is described. -- **Mitigation:** Ship a v1/v2 manifest reader behind a feature flag. -- **Reframe:** Treat the manifest as a contract API, not a config file. - -### 2. Validation cost is hidden - -- **Impact:** Each new schema field requires a sync-wide revalidation. -- **Evidence:** `confirmed` — the contract test must cover every manifest field. -- **Mitigation:** Reuse the existing inventory validator instead of adding one. - -## Synthesis - -The strongest risk is the consumer rollout gap; the validation cost is real -but recoverable by reusing existing tooling. The plan can move forward once -the compatibility shim and the validator reuse are agreed. - -## Outcome - -`execute-clear-next-step` - -## Next owner - -Route to `internal-gateway-simple-task` to draft the compatibility shim and -the validator reuse notes. diff --git a/tests/github/scripts/lib/test_catalog_checks.py b/tests/github/scripts/lib/test_catalog_checks.py deleted file mode 100644 index d4595d0..0000000 --- a/tests/github/scripts/lib/test_catalog_checks.py +++ /dev/null @@ -1,123 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from lib.catalog_checks import run_consistency_checks -from lib.inventory import build_inventory_markdown - -LEGACY_INSTRUCTION_DIR = ".github/" + "instructions" -LEGACY_APPLY_TO_KEY = "apply" + "To" - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def write_bridge_files(root: Path) -> None: - write_file( - root / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - root / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md` and `.github/INVENTORY.md`.\n", - ) - - -def test_run_consistency_checks_flags_active_residual_instruction_reference( - tmp_path: Path, -) -> None: - write_bridge_files(tmp_path) - write_file( - tmp_path / ".github/skills/internal-demo/SKILL.md", - "---\n" - "name: internal-demo\n" - "description: Use when demo validation needs a fixture.\n" - "---\n\n" - "# Internal Demo\n\n" - "## When to use\n\n" - "Use this skill for tests.\n\n" - "See `.github/copilot-code-review-instructions.md`.\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - findings_by_path = {(finding.path, finding.code) for finding in findings} - - assert ( - ".github/skills/internal-demo/SKILL.md", - "residual-instruction-reference", - ) in findings_by_path - - -def test_run_consistency_checks_allows_active_instruction_family_references( - tmp_path: Path, -) -> None: - write_bridge_files(tmp_path) - write_file( - tmp_path / LEGACY_INSTRUCTION_DIR / "internal-python.instructions.md", - "---\n" - "description: Python\n" - f"{LEGACY_APPLY_TO_KEY}: '**/*.py'\n" - "---\n\n" - "# Python Instructions\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - write_file( - tmp_path / ".github/skills/internal-demo/SKILL.md", - "---\n" - "name: internal-demo\n" - "description: Use when demo validation needs a fixture.\n" - "---\n\n" - "# Internal Demo\n\n" - "See `.github/instructions/internal-python.instructions.md`.\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "residual-instruction-reference" not in finding_codes - - -def test_run_consistency_checks_ignores_historical_deprecation_entries( - tmp_path: Path, -) -> None: - write_bridge_files(tmp_path) - write_file( - tmp_path / ".github/DEPRECATION.md", - "# Deprecation\n\n- `.github/copilot-code-review-instructions.md`: retired under migration exception.\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - findings_by_path = {(finding.path, finding.code) for finding in findings} - - assert ( - ".github/DEPRECATION.md", - "residual-instruction-reference", - ) not in findings_by_path - - -def test_run_consistency_checks_enforces_source_instruction_review_contract( - tmp_path: Path, -) -> None: - write_bridge_files(tmp_path) - write_file( - tmp_path / LEGACY_INSTRUCTION_DIR / "internal-python.instructions.md", - "---\n" - "description: Python checks\n" - f"{LEGACY_APPLY_TO_KEY}: '**/*.py'\n" - "---\n\n" - "# Python checks\n\n" - "- Verify input validation.\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "source-instruction-missing-exclude-agent" in finding_codes - assert "source-instruction-missing-review-statement" in finding_codes diff --git a/tests/github/scripts/lib/test_fingerprinting.py b/tests/github/scripts/lib/test_fingerprinting.py deleted file mode 100644 index 1628d39..0000000 --- a/tests/github/scripts/lib/test_fingerprinting.py +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import annotations - -import json -from pathlib import Path - -from lib.fingerprinting import ( - build_fingerprint, - build_manifest, - collect_files, - diff_manifests, - load_manifest, - normalize_content, - render_diff_text, -) - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def test_collect_files_expands_directories_and_builds_manifest(tmp_path: Path) -> None: - root = tmp_path - write_file(root / ".github/agents/internal-fast.agent.md", "# fast\n") - write_file(root / "README.md", "# readme\n") - - files = collect_files(root, [Path(".github/agents"), Path("README.md")]) - manifest = build_manifest( - root, files, source_ref_base="https://example.test/source" - ) - resources = { - resource["resource_id"]: resource for resource in manifest["resources"] - } - - assert [path.relative_to(root).as_posix() for path in files] == [ - ".github/agents/internal-fast.agent.md", - "README.md", - ] - assert resources[".github/agents/internal-fast.agent.md"]["kind"] == "agent" - assert resources[".github/agents/internal-fast.agent.md"]["source_ref"] == ( - "https://example.test/source/.github/agents/internal-fast.agent.md" - ) - - -def test_build_fingerprint_normalizes_text_and_leaves_binary_content_unchanged( - tmp_path: Path, -) -> None: - root = tmp_path - text_path = root / ".github/skills/internal-python/SKILL.md" - text_path.parent.mkdir(parents=True, exist_ok=True) - text_path.write_bytes(b"line one\r\nline two\r\n\r\n") - - fingerprint = build_fingerprint(root, text_path, source_ref_base="source-root") - - assert fingerprint.kind == "skill" - assert ( - fingerprint.source_ref == "source-root/.github/skills/internal-python/SKILL.md" - ) - assert fingerprint.metadata["bytes"] == len(b"line one\r\nline two\r\n\r\n") - assert fingerprint.metadata["normalized_bytes"] == len(b"line one\nline two\n") - assert normalize_content("binary.bin", b"\xff\x00") == b"\xff\x00" - - -def test_diff_manifests_tracks_changed_noise_only_created_and_removed( - tmp_path: Path, -) -> None: - old_manifest = { - "normalization_version": "v1", - "hash_algo": "sha256", - "resources": [ - {"resource_id": "same.md", "source_hash": "a", "content_hash": "same"}, - {"resource_id": "noise.md", "source_hash": "old", "content_hash": "same"}, - {"resource_id": "changed.md", "source_hash": "old", "content_hash": "old"}, - { - "resource_id": "removed.md", - "source_hash": "gone", - "content_hash": "gone", - }, - ], - } - new_manifest = { - "normalization_version": "v1", - "hash_algo": "sha256", - "resources": [ - {"resource_id": "same.md", "source_hash": "a", "content_hash": "same"}, - {"resource_id": "noise.md", "source_hash": "new", "content_hash": "same"}, - {"resource_id": "changed.md", "source_hash": "new", "content_hash": "new"}, - {"resource_id": "created.md", "source_hash": "new", "content_hash": "new"}, - ], - } - - result = diff_manifests(old_manifest, new_manifest) - diff_text = render_diff_text(result) - manifest_path = tmp_path / "manifest.json" - manifest_path.write_text(json.dumps(new_manifest), encoding="utf-8") - - assert result["summary"] == { - "created": 1, - "removed": 1, - "changed": 1, - "noise_only": 1, - "unchanged": 1, - } - assert "created created.md" in diff_text - assert "changed changed.md" in diff_text - assert load_manifest(manifest_path) == new_manifest diff --git a/tests/github/scripts/lib/test_internal_skills.py b/tests/github/scripts/lib/test_internal_skills.py deleted file mode 100644 index 4dd5551..0000000 --- a/tests/github/scripts/lib/test_internal_skills.py +++ /dev/null @@ -1,166 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from lib.internal_skills import ( - detect_internal_skill_findings, - markdown_targets, - resolve_reference, - validate_internal_skill, -) - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def write_valid_skill(skill_dir: Path, skill_name: str) -> None: - write_file( - skill_dir / "SKILL.md", - "---\n" - f"name: {skill_name}\n" - "description: Use when repository-owned Python skills need structural validation.\n" - "---\n\n" - f"# {skill_name}\n\n" - "## When to use\n\n" - "Use this skill when repository-owned Python skills need structural validation.\n\n" - "Use `.github/copilot-sync.manifest.json` only as a generated artifact.\n" - "Reference `references/guide.md` for examples.\n" - "```md\n" - "[Ignored](references/missing.md)\n" - "```\n", - ) - write_file(skill_dir / "references/guide.md", "# guide\n") - write_file( - skill_dir / "agents/openai.yaml", - "interface:\n" - f" display_name: {skill_name}\n" - " short_description: Keep repository-owned Python skills aligned\n" - f" default_prompt: Use ${skill_name} for validation.\n", - ) - - -def test_detect_internal_skill_findings_filters_selected_skills( - tmp_path: Path, -) -> None: - root = tmp_path - (root / ".github").mkdir(parents=True, exist_ok=True) - write_valid_skill(root / ".github/skills/internal-good", "internal-good") - (root / ".github/skills/internal-bad").mkdir(parents=True, exist_ok=True) - - selected_findings = detect_internal_skill_findings( - root, selected_skills={"internal-good"} - ) - all_findings = detect_internal_skill_findings(root) - all_codes = {finding.code for finding in all_findings} - - assert selected_findings == [] - assert "missing-skill-md" in all_codes - - -def test_validate_internal_skill_reports_metadata_and_reference_issues( - tmp_path: Path, -) -> None: - root = tmp_path - (root / ".github").mkdir(parents=True, exist_ok=True) - skill_dir = root / ".github/skills/internal-demo" - write_file( - skill_dir / "SKILL.md", - "---\n" - "name: internal-other\n" - "description: Validate metadata for internal skills carefully.\n" - "---\n\n" - "# internal-demo\n\n" - "[Missing](references/missing.md)\n\n" - "```md\n" - "[Ignored](references/ignored.md)\n" - "```\n", - ) - write_file( - skill_dir / "agents/openai.yaml", - "interface:\n" - " display_name: Demo Skill\n" - " short_description: Too short\n" - " default_prompt: Use the validator.\n", - ) - - findings = validate_internal_skill(root, skill_dir) - finding_codes = {finding.code for finding in findings} - missing_reference_findings = [ - finding for finding in findings if finding.code == "missing-local-reference" - ] - - assert "skill-name-mismatch" in finding_codes - assert "description-not-trigger-first" in finding_codes - assert "missing-when-to-use-heading" in finding_codes - assert "short-description-length" in finding_codes - assert "default-prompt-skill-mention" in finding_codes - assert len(missing_reference_findings) == 1 - - -def test_validate_internal_skill_rejects_cross_skill_file_references( - tmp_path: Path, -) -> None: - root = tmp_path - (root / ".github").mkdir(parents=True, exist_ok=True) - skill_dir = root / ".github/skills/internal-demo" - other_skill_dir = root / ".github/skills/internal-other" - - write_valid_skill(skill_dir, "internal-demo") - write_valid_skill(other_skill_dir, "internal-other") - write_file(other_skill_dir / "references/guide.md", "# Guide\n") - write_file( - skill_dir / "SKILL.md", - "---\n" - "name: internal-demo\n" - "description: Use when repository-owned Python skills need structural validation.\n" - "---\n\n" - "# internal-demo\n\n" - "## When to use\n\n" - "Use this skill when repository-owned Python skills need structural validation.\n\n" - "Do not read `../internal-other/SKILL.md`.\n" - "Do not read `.github/skills/internal-other/references/guide.md`.\n", - ) - - findings = validate_internal_skill(root, skill_dir) - cross_skill_findings = [ - finding for finding in findings if finding.code == "cross-skill-file-reference" - ] - - assert len(cross_skill_findings) == 2 - - -def test_markdown_targets_and_resolve_reference_support_repo_and_skill_paths( - tmp_path: Path, -) -> None: - root = tmp_path - skill_dir = root / ".github/skills/internal-demo" - source_file = skill_dir / "SKILL.md" - write_file(root / ".github/copilot-instructions.md", "# Copilot\n") - write_file(skill_dir / "references/example.md", "# Example\n") - text = ( - "[Repo](.github/copilot-instructions.md)\n" - "[Local](references/example.md)\n" - "`scripts/run.py`\n" - "`tmp/generated.md`\n" - ) - - targets = markdown_targets(text) - - assert ".github/copilot-instructions.md" in targets - assert "references/example.md" in targets - assert "scripts/run.py" in targets - assert ( - resolve_reference( - root, skill_dir, source_file, ".github/copilot-instructions.md" - ) - == root / ".github/copilot-instructions.md" - ) - assert resolve_reference(root, skill_dir, source_file, "references/example.md") == ( - skill_dir / "references/example.md" - ) - assert resolve_reference(root, skill_dir, source_file, "tmp/generated.md") is None - assert ( - resolve_reference(root, skill_dir, source_file, "https://example.com") is None - ) diff --git a/tests/github/scripts/lib/test_jsonc.py b/tests/github/scripts/lib/test_jsonc.py deleted file mode 100644 index aa8d8d8..0000000 --- a/tests/github/scripts/lib/test_jsonc.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import annotations - -import pytest -from lib.jsonc import ( - JsoncParseError, - apply_managed_vscode_copilot_settings, - consume_jsonc_string, - parse_jsonc_root_object, -) - - -def test_parse_jsonc_root_object_handles_comments_and_nested_members() -> None: - text = ( - "{\n" - " // Root comment\n" - ' "github.copilot.chat.codeGeneration.useInstructionFiles": true,\n' - ' "chat.instructionsFilesLocations": {\n' - " /* nested comment */\n" - ' ".github/instructions": true\n' - " }\n" - "}\n" - ) - - root = parse_jsonc_root_object(text) - - assert [member.key for member in root.members] == [ - "github.copilot.chat.codeGeneration.useInstructionFiles", - "chat.instructionsFilesLocations", - ] - assert root.members[1].object_value is not None - assert [member.key for member in root.members[1].object_value.members] == [ - ".github/instructions" - ] - - -def test_consume_jsonc_string_decodes_escape_sequences() -> None: - value, end = consume_jsonc_string('"line\\n\\u0041\\/end"', 0) - - assert value == "line\nA/end" - assert end == len('"line\\n\\u0041\\/end"') - - -def test_apply_managed_vscode_copilot_settings_merges_boolean_values() -> None: - original = ( - "{\n" - ' "github.copilot.chat.codeGeneration.useInstructionFiles": true,\n' - ' "chat.instructionsFilesLocations": {}\n' - "}\n" - ) - - updated = apply_managed_vscode_copilot_settings(original) - - assert '"github.copilot.chat.codeGeneration.useInstructionFiles": false' in updated - assert '".github/instructions": false' in updated - - -def test_apply_managed_vscode_copilot_settings_rejects_duplicate_keys() -> None: - duplicate = ( - "{\n" - ' "github.copilot.chat.codeGeneration.useInstructionFiles": true,\n' - ' "github.copilot.chat.codeGeneration.useInstructionFiles": false\n' - "}\n" - ) - - with pytest.raises(JsoncParseError, match="duplicate key"): - apply_managed_vscode_copilot_settings(duplicate) diff --git a/tests/github/scripts/lib/test_shared.py b/tests/github/scripts/lib/test_shared.py deleted file mode 100644 index 2726169..0000000 --- a/tests/github/scripts/lib/test_shared.py +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import pytest -from lib.shared import ( - Finding, - all_files_under, - dedupe_preserve_order, - find_repo_root, - finding_sort_key, - is_consumer_sync_excluded_path, - is_local_asset, - path_list, - resolve_markdown_target, - split_frontmatter, - strip_frontmatter, -) - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def test_split_frontmatter_and_strip_frontmatter_return_expected_parts() -> None: - text = "---\nname: demo\nitems:\n - one\n---\nBody\n" - - frontmatter, body = split_frontmatter(text) - - assert frontmatter == {"name": "demo", "items": ["one"]} - assert body == "Body\n" - assert strip_frontmatter(text) == "Body\n" - - -def test_split_frontmatter_returns_empty_mapping_for_invalid_yaml() -> None: - text = "---\nname: [broken\n---\nBody\n" - - frontmatter, body = split_frontmatter(text) - - assert frontmatter == {} - assert body == "Body\n" - - -@pytest.mark.parametrize( - ("relative_path", "is_local", "is_excluded"), - [ - (".github/agents/local-helper.agent.md", True, False), - (".github/skills/local-demo/SKILL.md", True, False), - (".github/agents/internal-sync-helper.agent.md", False, True), - (".github/skills/internal-demo/agents/internal-sync-helper.yaml", False, True), - (".github/skills/graphify/SKILL.md", False, False), - ("docs/readme.md", False, False), - ], -) -def test_local_and_consumer_excluded_path_helpers( - relative_path: str, is_local: bool, is_excluded: bool -) -> None: - assert is_local_asset(relative_path) is is_local - assert is_consumer_sync_excluded_path(relative_path) is is_excluded - - -def test_find_repo_root_and_resolve_markdown_target(tmp_path: Path) -> None: - root = tmp_path - current_file = root / ".github/agents/internal-gateway-review.agent.md" - (root / ".github/agents").mkdir(parents=True, exist_ok=True) - (root / "docs").mkdir(parents=True, exist_ok=True) - current_file.write_text("# delivery operator\n", encoding="utf-8") - - assert find_repo_root(root / "nested" / "deeper") == root - assert ( - resolve_markdown_target(root, current_file, "AGENTS.md") == root / "AGENTS.md" - ) - assert ( - resolve_markdown_target(root, current_file, ".github/copilot-instructions.md") - == root / ".github/copilot-instructions.md" - ) - assert ( - resolve_markdown_target(root, current_file, "../README.md#usage") - == (root / ".github/README.md").resolve() - ) - assert resolve_markdown_target(root, current_file, "https://example.com") is None - assert resolve_markdown_target(root, current_file, "/absolute/path.md") is None - - -def test_all_files_under_path_list_and_dedupe_preserve_order(tmp_path: Path) -> None: - root = tmp_path - write_file(root / ".github/agents/internal-fast.agent.md", "# fast\n") - write_file(root / ".github/agents/README.md", "# ignored\n") - write_file(root / ".github/agents/__pycache__/cache.pyc", "") - write_file(root / ".github/agents/local-helper.agent.md", "# local\n") - - assert all_files_under(root, ".github/agents") == [ - ".github/agents/internal-fast.agent.md", - ".github/agents/local-helper.agent.md", - ] - assert path_list(root, ".github/agents/*.agent.md") == [ - ".github/agents/internal-fast.agent.md", - ".github/agents/local-helper.agent.md", - ] - assert dedupe_preserve_order(["one", "two", "one", "three", "two"]) == [ - "one", - "two", - "three", - ] - - -def test_finding_sort_key_orders_blocking_findings_before_non_blocking() -> None: - findings = [ - Finding("non-blocking", "z-code", "b.md", "message", "suggestion"), - Finding("blocking", "a-code", "a.md", "message", "suggestion"), - ] - - ordered = sorted(findings, key=finding_sort_key) - - assert ordered[0].severity == "blocking" - assert ordered[0].path == "a.md" diff --git a/tests/github/scripts/test_benchmark_skill_tokens.py b/tests/github/scripts/test_benchmark_skill_tokens.py deleted file mode 100644 index 80b6a2b..0000000 --- a/tests/github/scripts/test_benchmark_skill_tokens.py +++ /dev/null @@ -1,113 +0,0 @@ -from __future__ import annotations - -import json -import subprocess -import sys -from pathlib import Path - -BENCHMARK_SCRIPT = Path(".github/scripts/benchmark_skill_tokens.py") - - -def run_benchmark() -> dict: - result = subprocess.run( - [sys.executable, str(BENCHMARK_SCRIPT)], - capture_output=True, - text=True, - check=True, - ) - return json.loads(result.stdout) - - -def test_benchmark_skill_tokens_runs_without_error() -> None: - output = run_benchmark() - assert "scenarios" in output - assert "descriptions" in output - assert "summary" in output - assert "idea_gateway" in output - assert len(output["scenarios"]) > 0 - assert len(output["descriptions"]) > 0 - - -def test_benchmark_detects_no_chain_risk_for_self_contained_skills() -> None: - output = run_benchmark() - - self_contained_skills = { - "internal-python-script", - "internal-python-project", - "internal-bash-script", - "internal-nodejs-project", - "internal-java-project", - } - - for scenario in output["scenarios"]: - if scenario["expected_owner"] in self_contained_skills: - assert scenario["chain_risks"] == [], ( - f"Expected no chain risks for {scenario['expected_owner']} " - f"but found {scenario['chain_risks']}" - ) - - -def test_gateway_scenarios_have_unique_required_skill_lists() -> None: - output = run_benchmark() - gateway_scenarios = output["gateway"]["required_context_scenarios"] - for scenario in gateway_scenarios: - skills = scenario["required_skills"] - assert len(skills) == len(set(skills)), ( - f"Duplicate skills in scenario '{scenario['scenario']}': {skills}" - ) - - by_name = { - scenario["scenario"]: scenario["required_skills"] - for scenario in gateway_scenarios - } - assert by_name["Plan handoff"] == [ - "internal-gateway-idea-brainstorming", - "internal-gateway-writing-plans", - "internal-agent-support-next-step", - ] - assert by_name["Approved apply-plan"] == [ - "internal-gateway-idea-brainstorming", - "internal-gateway-execute-plans", - ] - assert "internal-writing-plans" not in by_name["Plan handoff"] - assert "internal-executing-plans" not in by_name["Approved apply-plan"] - - -def test_idea_gateway_scenarios_exist_and_are_unique() -> None: - output = run_benchmark() - idea_scenarios = output["idea_gateway"]["context_scenarios"] - assert len(idea_scenarios) >= 4, ( - f"Expected >=4 idea-gateway scenarios, got {len(idea_scenarios)}" - ) - - scenario_names = {s["scenario"] for s in idea_scenarios} - expected = { - "Idea core entry", - "Interview support", - "Mandatory critical pass", - "Visible handoff", - } - missing = expected - scenario_names - assert not missing, f"Missing idea-gateway scenarios: {missing}" - - for scenario in idea_scenarios: - skills = scenario["required_skills"] - assert len(skills) == len(set(skills)), ( - f"Duplicate skills in scenario '{scenario['scenario']}': {skills}" - ) - - -def test_idea_gateway_progressive_totals_are_consistent() -> None: - output = run_benchmark() - idea_scenarios = output["idea_gateway"]["context_scenarios"] - by_name = {s["scenario"]: s["estimated_tokens"] for s in idea_scenarios} - - core = by_name["Idea core entry"] - interview = by_name["Interview support"] - critical = by_name["Mandatory critical pass"] - handoff = by_name["Visible handoff"] - - assert core > 0 - assert interview > core - assert critical > 0 - assert handoff > core diff --git a/tests/github/scripts/test_cli_entrypoints.py b/tests/github/scripts/test_cli_entrypoints.py deleted file mode 100644 index 4bc207e..0000000 --- a/tests/github/scripts/test_cli_entrypoints.py +++ /dev/null @@ -1,472 +0,0 @@ -from __future__ import annotations - -import argparse -import json -import textwrap -from pathlib import Path -from types import SimpleNamespace - -import audit_copilot_catalog -import build_inventory -import check_catalog_consistency -import detect_token_risks -import github_catalog_validation -import sync_copilot_catalog -import validate_internal_skills - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def initialize_governance_repo(root: Path, *, with_inventory: bool = True) -> None: - write_file( - root / "AGENTS.md", - "# AGENTS\n\n" - "- Use `.github/copilot-instructions.md`.\n" - "- Use `.github/INVENTORY.md`.\n", - ) - write_file( - root / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md` and `.github/INVENTORY.md`.\n", - ) - write_file( - root - / ".github/skills/local-agent-sync-external-resources/references/superpowers-normalization.yaml", - textwrap.dedent( - """ - version: 1 - source_family: obra/superpowers - local_prefix: superpowers- - managed_skills: - - upstream: demo - legacy_local: obra-demo - local: superpowers-demo - live_scan: - include: - - .github/skills - ignored_files: - - superpowers-normalization.yaml - """ - ).lstrip(), - ) - if with_inventory: - sync_inventory(root) - - -def sync_inventory(root: Path) -> None: - write_file( - root / ".github/INVENTORY.md", build_inventory.build_inventory_markdown(root) - ) - - -def write_valid_internal_skill(skill_dir: Path, skill_name: str) -> None: - write_file( - skill_dir / "SKILL.md", - "---\n" - f"name: {skill_name}\n" - "description: Use when validating repository-owned skill metadata safely.\n" - "---\n\n" - f"# {skill_name}\n\n" - "## When to use\n" - "- Use when validating repository-owned skill metadata safely.\n\n" - "Use `AGENTS.md` for root policy context.\n", - ) - write_file( - skill_dir / "agents/openai.yaml", - "interface:\n" - f" display_name: {skill_name}\n" - " short_description: Validate internal skill metadata safely\n" - f" default_prompt: Use ${skill_name} for validation.\n", - ) - - -def test_build_inventory_main_rebuilds_inventory_file( - monkeypatch, tmp_path: Path, capsys -) -> None: - initialize_governance_repo(tmp_path, with_inventory=False) - monkeypatch.setattr( - build_inventory, - "parse_args", - lambda: argparse.Namespace(root=str(tmp_path), check=False), - ) - - exit_code = build_inventory.main() - - assert exit_code == 0 - assert (tmp_path / ".github/INVENTORY.md").read_text( - encoding="utf-8" - ) == build_inventory.build_inventory_markdown(tmp_path) - assert "Inventory rebuilt successfully." in capsys.readouterr().out - - -def test_github_catalog_validation_main_runs_required_targets_then_token_risks( - monkeypatch, tmp_path: Path, capsys -) -> None: - initialize_governance_repo(tmp_path, with_inventory=False) - called_targets: list[str] = [] - - def fake_run(command: list[str], cwd: Path, check: bool) -> SimpleNamespace: - assert check is False - assert Path(cwd) == tmp_path - assert command[0] == "make" - called_targets.append(command[1]) - if command[1] == "token-risks": - return SimpleNamespace(returncode=1) - return SimpleNamespace(returncode=0) - - monkeypatch.setattr( - github_catalog_validation, - "parse_args", - lambda: argparse.Namespace( - root=str(tmp_path), - skip_token_risks=False, - token_risks_only=False, - ), - ) - monkeypatch.setattr(github_catalog_validation.subprocess, "run", fake_run) - - exit_code = github_catalog_validation.main() - output = capsys.readouterr().out - - assert exit_code == 0 - assert called_targets == [ - "catalog-lint", - "test", - "skill-lint", - "catalog-check", - "docs-lint", - "token-risks", - ] - assert ( - "continuing to match .github/workflows/_github-catalog-validation.yml" in output - ) - - -def test_github_catalog_validation_main_honors_token_risks_only( - monkeypatch, tmp_path: Path -) -> None: - initialize_governance_repo(tmp_path, with_inventory=False) - called_targets: list[str] = [] - - def fake_run(command: list[str], cwd: Path, check: bool) -> SimpleNamespace: - assert check is False - assert Path(cwd) == tmp_path - called_targets.append(command[1]) - return SimpleNamespace(returncode=0) - - monkeypatch.setattr( - github_catalog_validation, - "parse_args", - lambda: argparse.Namespace( - root=str(tmp_path), - skip_token_risks=False, - token_risks_only=True, - ), - ) - monkeypatch.setattr(github_catalog_validation.subprocess, "run", fake_run) - - exit_code = github_catalog_validation.main() - - assert exit_code == 0 - assert called_targets == ["token-risks"] - - -def test_build_inventory_main_check_detects_inventory_drift( - monkeypatch, tmp_path: Path, capsys -) -> None: - initialize_governance_repo(tmp_path) - write_file(tmp_path / ".github/INVENTORY.md", "# stale\n") - monkeypatch.setattr( - build_inventory, - "parse_args", - lambda: argparse.Namespace(root=str(tmp_path), check=True), - ) - - exit_code = build_inventory.main() - - assert exit_code == 1 - assert "Inventory drift detected." in capsys.readouterr().out - - -def test_check_catalog_consistency_main_emits_json_and_fails_on_blocking_findings( - monkeypatch, tmp_path: Path, capsys -) -> None: - initialize_governance_repo(tmp_path, with_inventory=False) - write_file( - tmp_path / ".github/agents/internal-empty.agent.md", - "---\nname: internal-empty\ntools: []\n---\n\n# Empty\n", - ) - sync_inventory(tmp_path) - monkeypatch.setattr( - check_catalog_consistency, - "parse_args", - lambda: argparse.Namespace( - root=str(tmp_path), - include_token_risks=False, - strict=False, - format="json", - ), - ) - - exit_code = check_catalog_consistency.main() - payload = json.loads(capsys.readouterr().out) - finding_codes = {item["code"] for item in payload} - - assert exit_code == 1 - assert "internal-agent-missing-tools" in finding_codes - - -def test_check_catalog_consistency_main_emits_compact_summary( - monkeypatch, tmp_path: Path, capsys -) -> None: - initialize_governance_repo(tmp_path, with_inventory=False) - monkeypatch.setattr( - check_catalog_consistency, - "parse_args", - lambda: argparse.Namespace( - root=str(tmp_path), - include_token_risks=False, - strict=False, - format="compact", - ), - ) - - exit_code = check_catalog_consistency.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 1 - assert payload["status"] == "failed" - assert payload["finding_counts"]["blocking"] >= 1 - assert "next_action" in payload - - -def test_detect_token_risks_main_respects_strict_mode( - monkeypatch, tmp_path: Path, capsys -) -> None: - repeated_lines = "\n".join( - [ - "- Keep policy separate from inventory.", - "- Keep AGENTS.md strategic and stable.", - "- Keep .github/copilot-instructions.md review-only.", - "- Keep .github/INVENTORY.md as the exact catalog.", - "- Preserve explicit precedence rules.", - "- Remove overlap instead of keeping compatibility copies.", - ] - ) - - write_file(tmp_path / "AGENTS.md", f"# AGENTS\n\n{repeated_lines}\n") - write_file( - tmp_path / ".github/copilot-instructions.md", - f"# Copilot\n\n{repeated_lines}\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - monkeypatch.setattr( - detect_token_risks, - "parse_args", - lambda: argparse.Namespace(root=str(tmp_path), strict=True, format="json"), - ) - - exit_code = detect_token_risks.main() - payload = json.loads(capsys.readouterr().out) - finding_codes = {item["code"] for item in payload} - - assert exit_code == 1 - assert "root-policy-overlap" in finding_codes - - -def test_audit_copilot_catalog_main_groups_blocking_findings( - monkeypatch, tmp_path: Path, capsys -) -> None: - initialize_governance_repo(tmp_path, with_inventory=False) - monkeypatch.setattr( - audit_copilot_catalog, - "parse_args", - lambda: argparse.Namespace(root=str(tmp_path), format="text"), - ) - - exit_code = audit_copilot_catalog.main() - output = capsys.readouterr().out - - assert exit_code == 1 - assert "BLOCKING" in output - assert ".github/INVENTORY.md" in output - - -def test_validate_internal_skills_main_honors_skill_selection( - monkeypatch, tmp_path: Path, capsys -) -> None: - initialize_governance_repo(tmp_path, with_inventory=False) - write_valid_internal_skill( - tmp_path / ".github/skills/internal-good", "internal-good" - ) - (tmp_path / ".github/skills/internal-bad").mkdir(parents=True, exist_ok=True) - monkeypatch.setattr( - validate_internal_skills, - "parse_args", - lambda: argparse.Namespace( - root=str(tmp_path), - skill=["internal-good"], - strict=True, - format="text", - ), - ) - - exit_code = validate_internal_skills.main() - - assert exit_code == 0 - assert "validation passed with no findings" in capsys.readouterr().out - - -def test_validate_internal_skills_main_emits_compact_summary( - monkeypatch, tmp_path: Path, capsys -) -> None: - initialize_governance_repo(tmp_path, with_inventory=False) - monkeypatch.setattr( - validate_internal_skills, - "parse_args", - lambda: argparse.Namespace( - root=str(tmp_path), - skill=[], - strict=False, - format="compact", - ), - ) - - exit_code = validate_internal_skills.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 0 - assert payload["status"] == "ok" - assert payload["finding_counts"]["blocking"] == 0 - assert "next_action" in payload - - -def test_sync_copilot_catalog_plan_mode_outputs_json_and_creates_plan( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - initialize_governance_repo(source_root) - initialize_governance_repo(target_root, with_inventory=False) - write_file( - source_root / ".github/agents/internal-fast.agent.md", - "---\nname: internal-fast\ntools: [read]\n---\n\n# Fast\n", - ) - sync_inventory(source_root) - monkeypatch.setattr( - sync_copilot_catalog, - "parse_args", - lambda: argparse.Namespace( - command="plan", - source_root=str(source_root), - target_repo=str(target_root), - allow_dirty_target=False, - format="json", - ), - ) - - exit_code = sync_copilot_catalog.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 0 - assert payload["mode"] == "plan" - assert payload["plan_path"].endswith("tmp/copilot-sync.plan.md") - assert (target_root / "tmp/copilot-sync.plan.md").exists() - - -def test_sync_copilot_catalog_plan_mode_outputs_compact( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - initialize_governance_repo(source_root) - initialize_governance_repo(target_root, with_inventory=False) - sync_inventory(source_root) - monkeypatch.setattr( - sync_copilot_catalog, - "parse_args", - lambda: argparse.Namespace( - command="plan", - source_root=str(source_root), - target_repo=str(target_root), - allow_dirty_target=False, - format="compact", - ), - ) - - exit_code = sync_copilot_catalog.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 0 - assert payload["mode"] == "plan" - assert payload["status"] == "ok" - assert "operation_counts" in payload - assert "next_action" in payload - - -def test_github_catalog_validation_compact_outputs_bounded_summary( - monkeypatch, tmp_path: Path, capsys -) -> None: - initialize_governance_repo(tmp_path, with_inventory=False) - - def fake_run( - command: list[str], cwd: Path, check: bool, **kwargs - ) -> SimpleNamespace: - assert check is False - assert Path(cwd) == tmp_path - assert command[0] == "make" - return SimpleNamespace(returncode=0, stdout="ok", stderr="") - - log_file = tmp_path / "tmp/validation.log" - monkeypatch.setattr( - github_catalog_validation, - "parse_args", - lambda: argparse.Namespace( - root=str(tmp_path), - skip_token_risks=False, - token_risks_only=True, - format="compact", - log_file=str(log_file), - ), - ) - monkeypatch.setattr(github_catalog_validation.subprocess, "run", fake_run) - - exit_code = github_catalog_validation.main() - output = capsys.readouterr().out - payload = json.loads(output[output.index("{") :]) - - assert exit_code == 0 - assert payload["status"] == "ok" - assert payload["targets_run"] == 1 - assert payload["failed_required"] == [] - assert payload["full_output_log"].endswith("validation.log") - - -def test_sync_copilot_catalog_apply_aborts_when_source_has_blocking_findings( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - initialize_governance_repo(source_root, with_inventory=False) - initialize_governance_repo(target_root, with_inventory=False) - monkeypatch.setattr( - sync_copilot_catalog, - "parse_args", - lambda: argparse.Namespace( - command="apply", - source_root=str(source_root), - target_repo=str(target_root), - allow_dirty_target=False, - format="text", - ), - ) - - exit_code = sync_copilot_catalog.main() - - assert exit_code == 1 - assert not (target_root / ".github/copilot-sync.manifest.json").exists() - assert ( - "Source repository has blocking governance findings" in capsys.readouterr().out - ) diff --git a/tests/github/scripts/test_repo_owned_script_coverage.py b/tests/github/scripts/test_repo_owned_script_coverage.py deleted file mode 100644 index 6c0522e..0000000 --- a/tests/github/scripts/test_repo_owned_script_coverage.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -TEST_OWNERS = { - "tests/github/scripts/test_cli_entrypoints.py": { - ".github/scripts/github_catalog_validation.py", - ".github/scripts/audit_copilot_catalog.py", - ".github/scripts/build_inventory.py", - ".github/scripts/check_catalog_consistency.py", - ".github/scripts/detect_token_risks.py", - ".github/scripts/lib/cli_runner.py", - ".github/scripts/sync_copilot_catalog.py", - ".github/scripts/validate_critical_output.py", - ".github/scripts/validate_internal_skills.py", - }, - "tests/github/scripts/lib/test_fingerprinting.py": { - ".github/scripts/lib/fingerprinting.py", - }, - "tests/github/scripts/lib/test_internal_skills.py": { - ".github/scripts/lib/internal_skills.py", - }, - "tests/github/scripts/lib/test_shared.py": { - ".github/scripts/lib/shared.py", - }, - "tests/test_critical_master_scripts.py": { - ".github/scripts/lib/critical_master.py", - ".github/scripts/validate_critical_output.py", - }, - "tests/test_inventory_and_consistency.py": { - ".github/scripts/lib/catalog_checks.py", - ".github/scripts/lib/inventory.py", - }, - "tests/test_home_ai_resources_sync.py": { - ".github/scripts/sync_home_ai_resources.py", - }, - "tests/test_sync_and_token_risks.py": { - ".github/scripts/lib/syncing.py", - ".github/scripts/lib/token_risks.py", - }, - "tests/github/scripts/lib/test_jsonc.py": { - ".github/scripts/lib/jsonc.py", - }, - "tests/github/scripts/test_benchmark_skill_tokens.py": { - ".github/scripts/benchmark_skill_tokens.py", - }, -} - - -def test_all_repo_owned_python_scripts_have_declared_test_owners() -> None: - source_scripts = { - path.as_posix() - for path in Path(".github/scripts").rglob("*.py") - if ".venv" not in path.parts - and "__pycache__" not in path.parts - and path.name != "__init__.py" - } - declared_scripts = { - script_path - for covered_scripts in TEST_OWNERS.values() - for script_path in covered_scripts - } - - assert declared_scripts == source_scripts - assert all(Path(script_path).is_file() for script_path in declared_scripts) - assert all(Path(test_path).is_file() for test_path in TEST_OWNERS) diff --git a/tests/github/scripts/test_runner_dispatch_contract.py b/tests/github/scripts/test_runner_dispatch_contract.py deleted file mode 100644 index 5c6437f..0000000 --- a/tests/github/scripts/test_runner_dispatch_contract.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import annotations - -import subprocess -from pathlib import Path - -RUNNER = Path(".github/scripts/run.sh").resolve() - - -def run_runner(*args: str) -> subprocess.CompletedProcess[str]: - return subprocess.run( - ["bash", str(RUNNER), *args], - capture_output=True, - text=True, - check=False, - ) - - -def test_runner_rejects_unknown_tool_with_usage() -> None: - result = run_runner("unknown_tool") - - assert result.returncode == 1 - assert "Unknown tool: unknown_tool" in result.stderr - assert "Usage:" in result.stderr or "Usage:" in result.stdout - - -def test_runner_resolves_diagnostic_cli_aliases_through_venv() -> None: - commands = ( - ("analyze_copilot_debug_log", "--help"), - ("benchmark_skill_tokens",), - ) - - for command in commands: - result = run_runner(*command) - assert result.returncode == 0, result.stderr - assert result.stdout.strip() or result.stderr.strip() - - -def test_runner_supports_shell_suffix_alias_for_diagnostic_cli() -> None: - result = run_runner("analyze_copilot_debug_log.sh", "--help") - - assert result.returncode == 0, result.stderr - assert "prompt-exports|debug-logs" in result.stdout diff --git a/tests/github/skills/internal_gateway_execute_plans/scripts/test_plan_execution.py b/tests/github/skills/internal_gateway_execute_plans/scripts/test_plan_execution.py deleted file mode 100644 index 22870d5..0000000 --- a/tests/github/skills/internal_gateway_execute_plans/scripts/test_plan_execution.py +++ /dev/null @@ -1,236 +0,0 @@ -"""Tests for plan_execution.py status-file validation.""" - -from __future__ import annotations - -import json -import shutil -import subprocess -import sys -from pathlib import Path - -import pytest - -EXECUTING_CLI = Path( - ".github/skills/internal-gateway-execute-plans/scripts/plan_execution.py" -).resolve() - - -def run_cli(*args: str | Path) -> subprocess.CompletedProcess[str]: - return subprocess.run( - [sys.executable, str(EXECUTING_CLI), *[str(a) for a in args]], - capture_output=True, - text=True, - ) - - -def write_plan(plan_folder: Path) -> None: - plan_folder.mkdir(parents=True, exist_ok=True) - (plan_folder / "01-change-summary.md").write_text( - "## Problema da risolvere\nTest.\n" - "## Risultato atteso\nTest.\n", - encoding="utf-8", - ) - (plan_folder / "02-execution.md").write_text( - "# Execution\n\n" - "Plan profile: compact\n\n" - "Target: test\n" - "Anti-scope: none\n" - "Validation: pytest\n", - encoding="utf-8", - ) - - -def write_status(plan_folder: Path, status: str = "DONE") -> Path: - status_path = plan_folder / f"{plan_folder.name}.{status}.md" - status_path.write_text( - f"# {plan_folder.name} Status\n\n" - "## Status\n\n" - f"{status}\n\n" - "## Reason\n\n" - "Evidence supports this state.\n\n" - "## Completed\n\n" - "- Contract checked.\n\n" - "## Remaining\n\n" - "- None.\n\n" - "## Validation\n\n" - "- `pytest` passed.\n\n" - "## Next\n\n" - "- No action required.\n\n" - "## Resume Notes\n\n" - "- Re-run validation after new edits.\n", - encoding="utf-8", - ) - return status_path - - -def test_inspect_reports_plan_without_status(tmp_path: Path) -> None: - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - - result = run_cli("inspect", plan_folder) - - assert result.returncode == 0 - assert "sample-plan" in result.stdout - assert "status_file_present: False" in result.stdout - - -def test_status_check_accepts_done_status_file(tmp_path: Path) -> None: - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - write_status(plan_folder, "DONE") - - result = run_cli("status-check", plan_folder) - - assert result.returncode == 0 - assert "DONE" in result.stdout - - -def test_completion_check_is_status_check_compatibility_alias(tmp_path: Path) -> None: - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - write_status(plan_folder, "DONE") - - result = run_cli("completion-check", plan_folder, "--format", "json") - - assert result.returncode == 0 - payload = json.loads(result.stdout) - assert payload["ready"] is True - assert payload["status"] == "DONE" - - -def test_status_check_rejects_missing_status_file(tmp_path: Path) -> None: - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - - result = run_cli("status-check", plan_folder) - - assert result.returncode != 0 - assert "missing-status-file" in result.stdout - - -def test_status_check_rejects_legacy_plan_state_marker(tmp_path: Path) -> None: - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - (plan_folder / "DONE-plan-state.md").write_text( - "Plan State\nState: DONE\nContinuation: none\n", - encoding="utf-8", - ) - - result = run_cli("status-check", plan_folder) - - assert result.returncode != 0 - assert "legacy-plan-state-marker" in result.stdout - - -def test_status_check_rejects_invalid_status(tmp_path: Path) -> None: - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - (plan_folder / "sample-plan.CANCELLED.md").write_text( - "# sample-plan Status\n\n" - "## Status\n\n" - "CANCELLED\n\n" - "## Reason\n\n" - "Cancelled.\n\n" - "## Completed\n\n" - "- None.\n\n" - "## Remaining\n\n" - "- All.\n\n" - "## Validation\n\n" - "- Not run.\n\n" - "## Next\n\n" - "- Author a new plan.\n\n" - "## Resume Notes\n\n" - "- Closed.\n", - encoding="utf-8", - ) - - result = run_cli("status-check", plan_folder) - - assert result.returncode != 0 - assert "invalid-status-file-name" in result.stdout - - -def test_status_check_rejects_missing_heading(tmp_path: Path) -> None: - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - status_path = write_status(plan_folder, "NEEDS_REVIEW") - status_path.write_text( - status_path.read_text(encoding="utf-8").replace("## Resume Notes\n\n", ""), - encoding="utf-8", - ) - - result = run_cli("status-check", plan_folder) - - assert result.returncode != 0 - assert "missing-required-heading" in result.stdout - - -def test_status_check_rejects_multiple_status_files(tmp_path: Path) -> None: - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - write_status(plan_folder, "PARTIAL") - write_status(plan_folder, "BLOCKED") - - result = run_cli("status-check", plan_folder) - - assert result.returncode != 0 - assert "multiple-status-files" in result.stdout - - -def test_resume_reports_existing_status(tmp_path: Path) -> None: - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - write_status(plan_folder, "PARTIAL") - - result = run_cli("resume", plan_folder) - - assert result.returncode == 0 - assert "PARTIAL" in result.stdout - - -def test_checkpoint_reports_status_requirement(tmp_path: Path) -> None: - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - - result = run_cli("checkpoint", plan_folder) - - assert result.returncode == 0 - assert "status_file_required: True" in result.stdout - - -def test_copied_bundle_runs_independently(tmp_path: Path) -> None: - bundle_copy = tmp_path / "internal-gateway-execute-plans" - shutil.copytree(Path(".github/skills/internal-gateway-execute-plans"), bundle_copy) - cli = bundle_copy / "scripts" / "plan_execution.py" - - plan_folder = tmp_path / "sample-plan" - write_plan(plan_folder) - write_status(plan_folder, "DONE") - - result = subprocess.run( - [sys.executable, str(cli), "status-check", str(plan_folder)], - capture_output=True, - text=True, - ) - assert result.returncode == 0 - assert "DONE" in result.stdout - - -def test_copied_bundle_no_cross_bundle_import() -> None: - import ast - - executing_code = EXECUTING_CLI.read_text(encoding="utf-8") - tree = ast.parse(executing_code) - - for node in ast.walk(tree): - if isinstance(node, ast.Import): - for alias in node.names: - if ( - "internal-gateway-writing-plans" in alias.name - or "retained_plans" in alias.name - ): - pytest.fail(f"Cross-bundle import found: {ast.dump(node)}") - elif isinstance(node, ast.ImportFrom): - module = node.module or "" - if "internal-gateway-writing-plans" in module or "retained_plans" in module: - pytest.fail(f"Cross-bundle import found: {ast.dump(node)}") diff --git a/tests/github/skills/internal_gateway_idea_brainstorming/scripts/test_audit_contract.py b/tests/github/skills/internal_gateway_idea_brainstorming/scripts/test_audit_contract.py deleted file mode 100644 index 440fe7f..0000000 --- a/tests/github/skills/internal_gateway_idea_brainstorming/scripts/test_audit_contract.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import annotations - -import json -import subprocess -import sys -from pathlib import Path - -SCRIPT_PATH = Path( - ".github/skills/internal-gateway-idea-brainstorming/scripts/audit_contract.py" -) - - -def run_json(*args: str) -> dict: - result = subprocess.run( - [sys.executable, str(SCRIPT_PATH), *args], - capture_output=True, - text=True, - check=True, - ) - return json.loads(result.stdout) - - -def test_audit_reports_minimal_marker_shape() -> None: - report = run_json() - assert set(report["markers"]) == { - "critical", - "planning", - "stop_before_execution", - "specialization_gated", - "bounded_evidence_pass", - "facts_options_summary", - "intent_traceability", - "plan_approval_gate", - "handoff_gate_4", - "ask_before_critical", - "direct_vs_plan_recommendation", - "explicit_plan_approval", - "alias_mapping", - } - assert report["strict_ok"] is True - - -def test_audit_script_emits_json_for_invocation() -> None: - report = run_json() - assert isinstance(report, dict) - - -def test_audit_script_checks_the_canonical_planning_owner() -> None: - script_text = SCRIPT_PATH.read_text(encoding="utf-8") - assert "internal-gateway-writing-plans" in script_text - assert "internal-writing-plans" not in script_text diff --git a/tests/github/skills/internal_gateway_simple_task/scripts/test_resolve_simple_task.py b/tests/github/skills/internal_gateway_simple_task/scripts/test_resolve_simple_task.py deleted file mode 100644 index 2e29faf..0000000 --- a/tests/github/skills/internal_gateway_simple_task/scripts/test_resolve_simple_task.py +++ /dev/null @@ -1,193 +0,0 @@ -from __future__ import annotations - -import importlib.util -import re -import sys -from pathlib import Path -from types import ModuleType - -SCRIPT_PATH = Path( - ".github/skills/internal-gateway-simple-task/scripts/resolve_simple_task.py" -) -SKILL_PATH = Path(".github/skills/internal-gateway-simple-task/SKILL.md") -LANES_REFERENCE_PATH = Path( - ".github/skills/internal-gateway-simple-task/references/simple-lanes.md" -) -INTERNAL_ONLY_LANES = ("unspecified",) -OUTCOME_ONLY_LANES = ("escalate",) - - -def load_script_module() -> ModuleType: - spec = importlib.util.spec_from_file_location("resolve_simple_task", SCRIPT_PATH) - - assert spec is not None - assert spec.loader is not None - - module = importlib.util.module_from_spec(spec) - sys.modules[spec.name] = module - spec.loader.exec_module(module) - return module - - -def test_detect_depth_keywords_from_prompt() -> None: - module = load_script_module() - - assert module.detect_depth_keywords("vai in modalita full", []) == ["full"] - - -def test_gate_returns_trivial_skip_for_bounded_local_edit() -> None: - module = load_script_module() - decision = module.build_gate_decision( - task="tighten one line in a markdown file", - lane="edit", - trivial_kind="tiny-edit", - validation_path="markdownlint .github/skills/internal-gateway-simple-task/SKILL.md", - ) - - assert decision["gate_outcome"] == "trivial-skip" - assert decision["next_owner"] == "internal-gateway-simple-task" - assert decision["lane"] == "edit" - assert "trivial-kind:tiny-edit" in decision["reason_codes"] - - -def test_gate_depth_keyword_forbids_trivial_skip() -> None: - module = load_script_module() - decision = module.build_gate_decision( - task="tighten one line in a markdown file", - lane="edit", - trivial_kind="tiny-edit", - prompt="vai in modalita full", - validation_path="markdownlint .github/skills/internal-gateway-simple-task/SKILL.md", - ) - - assert decision["gate_outcome"] == "full-gate" - assert "depth-keyword:full" in decision["reason_codes"] - - -def test_gate_escalates_to_brainstorming_when_plan_or_ownership_is_unsettled() -> None: - module = load_script_module() - decision = module.build_gate_decision( - task="redesign the simple-task bundle", - needs_plan=True, - owner_ambiguous=True, - validation_gap="validation path depends on which bundle surfaces change", - ) - - assert decision["gate_outcome"] == "escalate" - assert decision["next_owner"] == "internal-gateway-idea-brainstorming" - assert "needs-plan" in decision["reason_codes"] - assert "owner-ambiguous" in decision["reason_codes"] - - -def test_gate_escalates_to_review_when_review_is_the_real_job() -> None: - module = load_script_module() - decision = module.build_gate_decision( - task="decide whether this diff is merge-ready", - needs_review=True, - validation_gap="review evidence is not collected yet", - ) - - assert decision["gate_outcome"] == "escalate" - assert decision["next_owner"] == "internal-gateway-review" - - -def test_claim_resolution_for_fixed_requires_debugging_and_verification() -> None: - module = load_script_module() - requirements = module.resolve_claim_requirements(["fixed"]) - - assert requirements == [ - { - "owner": "internal-debugging", - "evidence_gate": "Re-run the original loop, or state the blocker.", - }, - { - "owner": "internal-gateway-simple-task", - "evidence_gate": "Run Direct Completion Control over every in-scope source item and mandatory applicable requirement before the claim.", - }, - { - "owner": "superpowers-verification-before-completion", - "evidence_gate": "Fresh validation evidence, not intent or stale output.", - }, - ] - - -def test_claim_resolution_deduplicates_verification_owner() -> None: - module = load_script_module() - requirements = module.resolve_claim_requirements(["pr-ready", "completion"]) - - assert requirements == [ - { - "owner": "internal-github-pr", - "evidence_gate": "Check PR lifecycle evidence before the claim.", - }, - { - "owner": "internal-gateway-simple-task", - "evidence_gate": "Run Direct Completion Control over every in-scope source item and mandatory applicable requirement before the claim.", - }, - { - "owner": "superpowers-verification-before-completion", - "evidence_gate": "Fresh validation evidence, not intent or stale output.", - }, - ] - - -def test_direct_execution_claims_require_completion_control() -> None: - module = load_script_module() - requirements = module.resolve_claim_requirements( - ["completion", "readiness", "validator-passes"] - ) - - assert requirements[0] == { - "owner": "internal-gateway-simple-task", - "evidence_gate": "Run Direct Completion Control over every in-scope source item and mandatory applicable requirement before the claim.", - } - assert requirements[1]["owner"] == "superpowers-verification-before-completion" - - -def lanes_from_simple_lanes_reference(reference_text: str) -> set[str]: - table_start = reference_text.index("## Lane Selection") - table_end = reference_text.index("\n## ", table_start + 1) - table_body = reference_text[table_start:table_end] - return { - match.group(1) - for match in re.finditer(r"^\| `([a-z-]+)` \|", table_body, flags=re.MULTILINE) - } - - -def lanes_from_skill_when_to_use(skill_text: str) -> set[str]: - section_start = skill_text.index("## When to use") - section_end = skill_text.index("\n## ", section_start + 1) - section_body = skill_text[section_start:section_end] - for line in section_body.splitlines(): - if "quick lane can finish" not in line: - continue - return set(re.findall(r"`([a-z-]+)`", line)) - return set() - - -def test_helper_lanes_match_canonical_simple_lanes_reference() -> None: - module = load_script_module() - documented_lanes = lanes_from_simple_lanes_reference( - LANES_REFERENCE_PATH.read_text(encoding="utf-8") - ) - user_choosable_lanes = set(module.LANES) - set(INTERNAL_ONLY_LANES) - documented_choosable_lanes = documented_lanes - set(OUTCOME_ONLY_LANES) - - assert user_choosable_lanes == documented_choosable_lanes - - -def test_skill_when_to_use_lane_list_matches_helper_lanes() -> None: - module = load_script_module() - skill_text = SKILL_PATH.read_text(encoding="utf-8") - listed_lanes = lanes_from_skill_when_to_use(skill_text) - user_choosable_lanes = set(module.LANES) - set(INTERNAL_ONLY_LANES) - - assert listed_lanes == user_choosable_lanes - - -def test_execute_lane_is_not_exposed_by_helper_or_skill_summary() -> None: - module = load_script_module() - skill_text = SKILL_PATH.read_text(encoding="utf-8") - - assert "execute" not in module.LANES - assert "execute" not in lanes_from_skill_when_to_use(skill_text) diff --git a/tests/github/skills/internal_gateway_simple_task/scripts/test_suggest_support_skills.py b/tests/github/skills/internal_gateway_simple_task/scripts/test_suggest_support_skills.py deleted file mode 100644 index ff0a8d2..0000000 --- a/tests/github/skills/internal_gateway_simple_task/scripts/test_suggest_support_skills.py +++ /dev/null @@ -1,318 +0,0 @@ -from __future__ import annotations - -import importlib.util -import re -import sys -from pathlib import Path -from types import ModuleType - -SKILL_PATH = Path(".github/skills/internal-gateway-simple-task/SKILL.md") -SUPPORT_ROUTING_PATH = Path( - ".github/skills/internal-gateway-simple-task/references/support-routing.md" -) -SCRIPT_PATH = Path( - ".github/skills/internal-gateway-simple-task/scripts/suggest_support_skills.py" -) - -REPRESENTATIVE_PATHS = ( - ".github/skills/internal-demo/SKILL.md", - ".github/agents/internal-demo.agent.md", - ".github/skills/internal-demo/references/usage.md", - ".github/skills/internal-yaml/SKILL.md", - ".github/skills/internal-yaml/references/example.md", - ".github/skills/internal-yaml/scripts/helper.py", - ".github/scripts/tool.py", - "src/app.py", - "tools/check.py", - "scripts/demo.sh", - "bin/check.sh", - "infra/main.tf", - "services/api/main.go", - "src/index.ts", - "package.json", - "package-lock.json", - "pnpm-lock.yaml", - "yarn.lock", - "bun.lockb", - "pom.xml", - "src/main/java/App.java", - "Makefile", - "docs/guide.md", - "data/registry.json", - "azure-pipelines.yml", - ".github/dependabot.yml", - ".pre-commit-config.yaml", - ".github/CODEOWNERS", - ".github/workflows/ci.yml", - ".github/actions/test/action.yml", - "Chart.yaml", - "Dockerfile", - "compose.yaml", - "k8s/app/deployment.yaml", - "config/app.yaml", - "config/app.json", - "src/payment_lambda.py", -) - -EXPECTED_SKILLS_BY_PATH = { - ".github/skills/internal-demo/SKILL.md": {"internal-skill-creator"}, - ".github/agents/internal-demo.agent.md": {"internal-agent-creator"}, - ".github/skills/internal-demo/references/usage.md": {"internal-skill-creator"}, - ".github/skills/internal-yaml/SKILL.md": {"internal-skill-creator"}, - ".github/skills/internal-yaml/references/example.md": {"internal-skill-creator"}, - ".github/skills/internal-yaml/scripts/helper.py": {"internal-skill-creator"}, - ".github/scripts/tool.py": {"internal-python-script"}, - "src/app.py": {"internal-python-project"}, - "tools/check.py": {"internal-python"}, - "scripts/demo.sh": {"internal-bash-script"}, - "bin/check.sh": {"internal-bash"}, - "infra/main.tf": {"internal-terraform"}, - "services/api/main.go": {"internal-go"}, - "src/index.ts": {"internal-nodejs-project"}, - "package.json": {"internal-nodejs"}, - "package-lock.json": {"internal-nodejs"}, - "pnpm-lock.yaml": {"internal-nodejs"}, - "yarn.lock": {"internal-nodejs"}, - "bun.lockb": {"internal-nodejs"}, - "pom.xml": {"internal-java"}, - "src/main/java/App.java": {"internal-java-project"}, - "Makefile": {"internal-makefile"}, - "docs/guide.md": {"internal-markdown"}, - "data/registry.json": {"internal-json"}, - "azure-pipelines.yml": {"internal-azure-devops"}, - ".github/dependabot.yml": {"awesome-copilot-dependabot"}, - ".pre-commit-config.yaml": {"internal-yaml"}, - ".github/CODEOWNERS": {"internal-github-governance"}, - ".github/workflows/ci.yml": {"internal-github-actions"}, - ".github/actions/test/action.yml": {"internal-github-action-composite"}, - "Chart.yaml": {"internal-kubernetes"}, - "Dockerfile": {"internal-docker"}, - "compose.yaml": {"internal-docker"}, - "k8s/app/deployment.yaml": {"internal-kubernetes"}, - "config/app.yaml": {"internal-yaml"}, - "config/app.json": {"internal-json"}, - "src/payment_lambda.py": {"internal-aws-lambda"}, -} - -EXPECTED_PATH_SKILLS = { - skill for skills in EXPECTED_SKILLS_BY_PATH.values() for skill in skills -} - -ALLOWLISTED_EXTERNAL_SKILLS: set[str] = set() - -CLAIM_GATE_SYMPTOMS = { - "bug", - "tdd", - "performance", - "pr-readiness", - "code-review", - "no-findings", - "systems-review", - "completion-claim", -} - - -def load_script_module() -> ModuleType: - spec = importlib.util.spec_from_file_location("suggest_support_skills", SCRIPT_PATH) - - assert spec is not None - assert spec.loader is not None - - module = importlib.util.module_from_spec(spec) - sys.modules[spec.name] = module - spec.loader.exec_module(module) - return module - - -def section_between(text: str, heading: str) -> str: - section = text.split(heading, 1)[1] - return section.split("\n## ", 1)[0] - - -def claim_gate_owners_from_skill(skill_text: str) -> set[str]: - section = section_between(skill_text, "## Claim Gates") - return set(re.findall(r"^- Load `([^`]+)` before", section, flags=re.MULTILINE)) - - -def claim_gate_owners_from_reference(reference_text: str) -> set[str]: - section = section_between(reference_text, "## Claim Gates") - owners: set[str] = set() - - for line in section.splitlines(): - if not line.startswith("| "): - continue - - cells = [cell.strip() for cell in line.split("|")[1:-1]] - if len(cells) < 2: - continue - - owner = cells[1] - if owner.startswith("`") and owner.endswith("`"): - owners.add(owner.strip("`")) - - return owners - - -def collect_emitted_skills(module: ModuleType) -> set[str]: - suggestions: dict[str, set[str]] = {} - - for path_text in REPRESENTATIVE_PATHS: - module.suggest_for_path(path_text, suggestions) - - emitted_skills = {skill for skill, _reason in module.SYMPTOM_SKILLS.values()} - emitted_skills.update(suggestions) - return emitted_skills - - -def test_suggest_support_skills_only_emits_live_skill_ids() -> None: - module = load_script_module() - emitted_skills = collect_emitted_skills(module) - - assert EXPECTED_PATH_SKILLS.issubset(emitted_skills) - - missing = sorted( - skill_id - for skill_id in emitted_skills - if skill_id not in ALLOWLISTED_EXTERNAL_SKILLS - and not Path(f".github/skills/{skill_id}/SKILL.md").is_file() - ) - - assert missing == [] - - -def test_suggest_support_skills_prefers_single_narrowest_owner_per_path() -> None: - module = load_script_module() - - for path_text, expected_skills in EXPECTED_SKILLS_BY_PATH.items(): - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path(path_text, suggestions) - - assert set(suggestions) == expected_skills - - -def test_suggest_support_skills_keeps_simple_yaml_edit_on_yaml_owner_only() -> None: - module = load_script_module() - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path("config/app.yaml", suggestions) - - assert set(suggestions) == {"internal-yaml"} - - -def test_suggest_support_skills_keeps_generic_json_edit_on_json_owner_only() -> None: - module = load_script_module() - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path("config/app.json", suggestions) - - assert set(suggestions) == {"internal-json"} - - -def test_suggest_support_skills_routes_node_lockfiles_to_nodejs_owner() -> None: - module = load_script_module() - - for path_text in ("package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"): - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path(path_text, suggestions) - - assert set(suggestions) == {"internal-nodejs"} - - -def test_suggest_support_skills_keeps_generic_python_and_bash_on_base_owner_only() -> ( - None -): - module = load_script_module() - - python_suggestions: dict[str, set[str]] = {} - bash_suggestions: dict[str, set[str]] = {} - - module.suggest_for_path("tools/check.py", python_suggestions) - module.suggest_for_path("bin/check.sh", bash_suggestions) - - assert set(python_suggestions) == {"internal-python"} - assert set(bash_suggestions) == {"internal-bash"} - - -def test_suggest_support_skills_routes_bundle_siblings_to_skill_creator() -> None: - module = load_script_module() - - for path_text in ( - ".github/skills/internal-yaml/SKILL.md", - ".github/skills/internal-yaml/references/example.md", - ".github/skills/internal-yaml/scripts/helper.py", - ): - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path(path_text, suggestions) - - assert set(suggestions) == {"internal-skill-creator"} - - -def test_suggest_support_skills_keeps_generic_markdown_on_markdown_owner_only() -> None: - module = load_script_module() - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path("docs/guide.md", suggestions) - - assert set(suggestions) == {"internal-markdown"} - - -def test_suggest_support_skills_routes_dependabot_yaml_to_dependabot_owner() -> None: - module = load_script_module() - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path(".github/dependabot.yml", suggestions) - - assert set(suggestions) == {"awesome-copilot-dependabot"} - - -def test_suggest_support_skills_keeps_precommit_yaml_on_generic_yaml_owner() -> None: - module = load_script_module() - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path(".pre-commit-config.yaml", suggestions) - - assert set(suggestions) == {"internal-yaml"} - - -def test_suggest_support_skills_routes_chart_yaml_to_kubernetes_owner() -> None: - module = load_script_module() - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path("Chart.yaml", suggestions) - - assert set(suggestions) == {"internal-kubernetes"} - - -def test_suggest_support_skills_routes_internal_agent_paths_to_agent_creator() -> None: - module = load_script_module() - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path(".github/agents/internal-demo.agent.md", suggestions) - - assert set(suggestions) == {"internal-agent-creator"} - - -def test_suggest_support_skills_claim_gate_owners_match_core_contract() -> None: - module = load_script_module() - skill_text = SKILL_PATH.read_text(encoding="utf-8") - reference_claim_gate_owners = claim_gate_owners_from_reference( - SUPPORT_ROUTING_PATH.read_text(encoding="utf-8") - ) - symptom_claim_gate_owners = { - module.SYMPTOM_SKILLS[symptom][0] for symptom in CLAIM_GATE_SYMPTOMS - } - - assert "single source of truth for claim-gate" in skill_text - assert symptom_claim_gate_owners == reference_claim_gate_owners - - -def test_suggest_support_skills_normalizes_absolute_repo_owned_paths() -> None: - module = load_script_module() - suggestions: dict[str, set[str]] = {} - - module.suggest_for_path(str(SKILL_PATH.resolve()), suggestions) - - assert "internal-skill-creator" in suggestions diff --git a/tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_agent_translation.py b/tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_agent_translation.py deleted file mode 100644 index fc205bb..0000000 --- a/tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_agent_translation.py +++ /dev/null @@ -1,310 +0,0 @@ -"""Tests for agent_translation module.""" - -from __future__ import annotations - -import importlib.util -import sys -from pathlib import Path - -import pytest -import yaml - - -def find_repo_root(start: Path) -> Path: - for candidate in start.resolve().parents: - if (candidate / "AGENTS.md").is_file(): - return candidate - raise FileNotFoundError(f"Unable to find repository root from {start}") - - -REPO_ROOT = find_repo_root(Path(__file__)) -SKILL_SCRIPTS_ROOT = ( - REPO_ROOT / ".github/skills/local-agent-sync-install-ai-resources/scripts" -) -sys.path.insert(0, SKILL_SCRIPTS_ROOT.as_posix()) - -from agent_translation import ( # noqa: E402 - _build_claude_tools, - _build_opencode_permission, - _render_handoffs_body, - _translate_for_claude, - _translate_for_copilot, - _translate_for_opencode, - parse_frontmatter_and_body, - render_frontmatter_md, - target_extension, - translate_agent_for_target, -) - - -class TestParseFrontmatter: - def test_basic_parse(self): - content = "---\nname: test-agent\ntools: [read]\n---\nBody content here\n" - fm, body = parse_frontmatter_and_body(content) - assert fm["name"] == "test-agent" - assert fm["tools"] == ["read"] - assert body == "Body content here\n" - - def test_no_frontmatter_raises(self): - with pytest.raises(ValueError, match="no valid YAML frontmatter"): - parse_frontmatter_and_body("Just markdown, no frontmatter") - - def test_empty_frontmatter(self): - content = "---\n\n---\nBody\n" - fm, body = parse_frontmatter_and_body(content) - assert fm == {} - assert body == "Body\n" - - -class TestRenderFrontmatter: - def test_render_simple(self): - result = render_frontmatter_md({"name": "test", "tools": "Read"}) - assert result.startswith("---\n") - assert result.endswith("---\n") - assert "name: test" in result - assert "tools: Read" in result - - -class TestClaudeTranslation: - def test_claude_tools_basic(self): - fm = {"tools": ["read", "edit"]} - tools = _build_claude_tools(fm) - assert "Read" in tools - assert "Edit" in tools - - def test_claude_tools_search_maps_to_glob_grep(self): - fm = {"tools": ["search"]} - tools = _build_claude_tools(fm) - assert "Glob" in tools - assert "Grep" in tools - - def test_claude_tools_execute_maps_to_bash(self): - fm = {"tools": ["execute"]} - tools = _build_claude_tools(fm) - assert "Bash" in tools - - def test_claude_tools_web_maps(self): - fm = {"tools": ["web"]} - tools = _build_claude_tools(fm) - assert "WebSearch" in tools - assert "WebFetch" in tools - - def test_claude_agents_added(self): - fm = {"tools": ["read"], "agents": ["agent1", "agent2"]} - tools = _build_claude_tools(fm) - assert tools.startswith("Agent(agent1, agent2)") - - def test_claude_full_translation(self): - fm = { - "name": "my-agent", - "description": "Test agent", - "tools": ["read", "execute"], - "handoffs": [ - {"label": "Next", "agent": "other-agent", "prompt": "Do something"} - ], - } - body = "# My Agent\n\nInstructions here.\n" - result = _translate_for_claude(fm, body) - # Verify YAML is valid - parsed = yaml.safe_load(result.split("---\n", 2)[1]) - assert parsed["name"] == "my-agent" - assert parsed["tools"] == "Read, Bash" - assert "Handoffs" in result - assert "other-agent" in result - - def test_claude_no_tools_field(self): - fm = {"name": "minimal"} - body = "Just body.\n" - result = _translate_for_claude(fm, body) - parsed = yaml.safe_load(result.split("---\n", 2)[1]) - assert parsed["name"] == "minimal" - assert "tools" not in parsed - - -class TestOpenCodeTranslation: - def test_opencode_permission_basic(self): - fm = {"tools": ["read", "edit"]} - perm = _build_opencode_permission(fm) - assert perm["read"] == "allow" - assert perm["edit"] == "allow" - - def test_opencode_disable_model_invocation(self): - fm = {"tools": ["read"], "disable-model-invocation": True} - body = "Body.\n" - result = _translate_for_opencode(fm, body) - assert "hidden: true" in result - - def test_opencode_agents_permission(self): - fm = {"agents": ["agent1"]} - perm = _build_opencode_permission(fm) - assert "task" in perm - assert perm["task"]["*"] == "deny" - assert perm["task"]["agent1"] == "allow" - - def test_opencode_full_translation(self): - fm = { - "description": "Test agent", - "tools": ["read", "web"], - "handoffs": [ - {"label": "Switch", "agent": "other", "prompt": "Handle this"} - ], - } - body = "# Test\n\nContent.\n" - result = _translate_for_opencode(fm, body) - parsed = yaml.safe_load(result.split("---\n", 2)[1]) - assert parsed["description"] == "Test agent" - assert parsed["mode"] == "subagent" - assert parsed["permission"]["read"] == "allow" - assert "webfetch" in parsed["permission"] - assert "websearch" in parsed["permission"] - assert "Handoffs" in result - - def test_opencode_no_name_in_frontmatter(self): - fm = {"description": "Just desc"} - body = "Body.\n" - result = _translate_for_opencode(fm, body) - parsed = yaml.safe_load(result.split("---\n", 2)[1]) - assert "name" not in parsed - - -class TestHandoffsBody: - def test_empty_handoffs(self): - assert _render_handoffs_body({}) == "" - - def test_no_handoffs_field(self): - fm = {"name": "test"} - assert _render_handoffs_body(fm) == "" - - def test_handoffs_rendered(self): - fm = { - "handoffs": [ - {"label": "Go to X", "agent": "agent-x", "prompt": "Do X task"} - ] - } - result = _render_handoffs_body(fm) - assert "## Handoffs" in result - assert "**Go to X**" in result - assert "`agent-x`" in result - assert "Do X task" in result - - -class TestCopilotTranslation: - def test_copilot_passthrough(self): - fm = {"name": "test", "tools": ["read"]} - body = "Body here.\n" - result = _translate_for_copilot(fm, body) - assert "name: test" in result - assert "Body here." in result - assert "tools:" in result - - -class TestTargetExtension: - def test_extensions(self): - assert target_extension("copilot") == ".agent.md" - assert target_extension("claude") == ".md" - assert target_extension("opencode") == ".md" - assert target_extension("codex") == ".toml" - - -class TestCodexTranslation: - def test_codex_basic(self): - if importlib.util.find_spec("tomli_w") is None: - pytest.skip("tomli_w not installed") - fm = { - "name": "test-agent", - "description": "A test agent", - } - body = "Instructions here.\n" - from agent_translation import _translate_for_codex - - result = _translate_for_codex(fm, body) - assert "name" in result - assert "test-agent" in result - assert "developer_instructions" in result - - def test_codex_handoffs_in_instructions(self): - if importlib.util.find_spec("tomli_w") is None: - pytest.skip("tomli_w not installed") - fm = { - "name": "test", - "handoffs": [{"label": "Go", "agent": "other", "prompt": "Do task"}], - } - body = "Main body.\n" - from agent_translation import _translate_for_codex - - result = _translate_for_codex(fm, body) - assert "Handoffs" in result - assert "other" in result - - def test_codex_omit_tools_from_frontmatter(self): - if importlib.util.find_spec("tomli_w") is None: - pytest.skip("tomli_w not installed") - fm = {"name": "minimal"} - body = "Body.\n" - from agent_translation import _translate_for_codex - - result = _translate_for_codex(fm, body) - assert "name = " in result - - def test_codex_real_file(self): - if importlib.util.find_spec("tomli_w") is None: - pytest.skip("tomli_w not installed") - source = Path(".github/agents/internal-gateway-review.agent.md") - if not source.exists(): - pytest.skip("Source file not available") - result = translate_agent_for_target(source, "codex") - assert "name = " in result - assert "developer_instructions" in result - assert "internal-gateway-review" in result - - -class TestIntegrationWithRealFiles: - def test_review_gateway_to_claude(self): - source = Path(".github/agents/internal-gateway-review.agent.md") - if not source.exists(): - pytest.skip("Source file not available") - result = translate_agent_for_target(source, "claude") - fm, body = parse_frontmatter_and_body(result) - assert fm["name"] == "internal-gateway-review" - assert "Handoffs" in body - - def test_review_gateway_to_opencode(self): - source = Path(".github/agents/internal-gateway-review.agent.md") - if not source.exists(): - pytest.skip("Source file not available") - result = translate_agent_for_target(source, "opencode") - fm, body = parse_frontmatter_and_body(result) - assert fm["mode"] == "subagent" - assert "hidden" in fm - assert "Handoffs" in body - - def test_critical_master_to_claude(self): - source = Path(".github/agents/internal-gateway-critical-master.agent.md") - if not source.exists(): - pytest.skip("Source file not available") - result = translate_agent_for_target(source, "claude") - fm, body = parse_frontmatter_and_body(result) - assert fm["name"] == "internal-gateway-critical-master" - assert "Glob" in fm["tools"] - assert "Grep" in fm["tools"] - - def test_critical_master_to_opencode(self): - source = Path(".github/agents/internal-gateway-critical-master.agent.md") - if not source.exists(): - pytest.skip("Source file not available") - result = translate_agent_for_target(source, "opencode") - fm, body = parse_frontmatter_and_body(result) - assert fm["permission"]["read"] == "allow" - # critical-master has no edit/execute/web tools - assert "edit" not in fm["permission"] - - def test_all_copilot_passthrough(self): - source = Path(".github/agents/internal-gateway-simple-task.agent.md") - if not source.exists(): - pytest.skip("Source file not available") - original = source.read_text() - result = translate_agent_for_target(source, "copilot") - fm_orig, body_orig = parse_frontmatter_and_body(original) - fm_result, body_result = parse_frontmatter_and_body(result) - assert fm_result["name"] == fm_orig["name"] - assert body_result.strip() == body_orig.strip() diff --git a/tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_bisync_skills.py b/tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_bisync_skills.py deleted file mode 100644 index 4f63db4..0000000 --- a/tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_bisync_skills.py +++ /dev/null @@ -1,618 +0,0 @@ -from __future__ import annotations - -import argparse -import importlib.util -import json -import os -import subprocess -import sys -from pathlib import Path - -import pytest - - -def find_repo_root(start: Path) -> Path: - for candidate in start.resolve().parents: - if (candidate / "AGENTS.md").is_file(): - return candidate - raise FileNotFoundError(f"Unable to find repository root from {start}") - - -REPO_ROOT = find_repo_root(Path(__file__)) -SKILL_SCRIPTS_ROOT = ( - REPO_ROOT / ".github/skills/local-agent-sync-install-ai-resources/scripts" -) - - -def load_bisync_module(): - inserted_path = False - if SKILL_SCRIPTS_ROOT.as_posix() not in sys.path: - sys.path.insert(0, SKILL_SCRIPTS_ROOT.as_posix()) - inserted_path = True - try: - spec = importlib.util.spec_from_file_location( - "_test_local_bisync_skills", - SKILL_SCRIPTS_ROOT / "bisync_skills.py", - ) - assert spec is not None - assert spec.loader is not None - module = importlib.util.module_from_spec(spec) - sys.modules[spec.name] = module - spec.loader.exec_module(module) - return module - finally: - if inserted_path: - sys.path.remove(SKILL_SCRIPTS_ROOT.as_posix()) - - -bisync_skills = load_bisync_module() -apply_bisync_plan = bisync_skills.apply_bisync_plan -build_bisync_plan = bisync_skills.build_bisync_plan -BisyncDriftEntry = bisync_skills.BisyncDriftEntry -BisyncPlan = bisync_skills.BisyncPlan - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def init_git_repo(root: Path) -> None: - root.mkdir(parents=True, exist_ok=True) - write_file(root / "AGENTS.md", "# AGENTS\n") - subprocess.run( - ["git", "init", "-b", "main"], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "config", "user.email", "test@example.com"], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "config", "user.name", "Test"], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "add", "-A"], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "commit", "-m", "initial"], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - - -def commit_all(root: Path, message: str) -> None: - subprocess.run( - ["git", "add", "-A"], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "commit", "-m", message], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - - -def make_skill(root: Path, skill_name: str, content: str) -> Path: - skill_dir = root / skill_name - write_file(skill_dir / "SKILL.md", content) - write_file( - skill_dir / "agents" / "openai.yaml", - "interface:\n" - f' display_name: "{skill_name}"\n' - ' short_description: "Test skill"\n' - f' default_prompt: "Use ${skill_name}."\n', - ) - return skill_dir - - -def set_tree_mtime(root: Path, timestamp: float) -> None: - for path in sorted(root.rglob("*")): - os.utime(path, (timestamp, timestamp)) - os.utime(root, (timestamp, timestamp)) - - -def test_build_plan_in_sync_with_ignored_runtime_artifacts(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - source_skill = make_skill(source / ".github" / "skills", "alpha-skill", "# Alpha\n") - home_skill = make_skill(home / ".agents" / "skills", "alpha-skill", "# Alpha\n") - write_file(source_skill / "__pycache__" / "ignored.pyc", "source\n") - write_file(home_skill / "__pycache__" / "ignored.pyc", "home\n") - write_file(source_skill / ".venv" / "marker.txt", "source\n") - write_file(home_skill / ".venv" / "marker.txt", "home\n") - - plan = build_bisync_plan(source, home, mode="plan") - - assert plan.drifts == [] - assert plan.blocked_codes == [] - assert plan.verification["status"] == "ok" - - -def test_build_plan_detects_repo_to_home_direction(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - source_skill = make_skill( - source / ".github" / "skills", "beta-skill", "# Beta source\n" - ) - home_skill = make_skill(home / ".agents" / "skills", "beta-skill", "# Beta home\n") - set_tree_mtime(home_skill, 100.0) - set_tree_mtime(source_skill, 200.0) - - plan = build_bisync_plan(source, home, mode="plan") - - assert len(plan.drifts) == 1 - drift = plan.drifts[0] - assert drift.skill_name == "beta-skill" - assert drift.drift_type == "drift" - assert drift.direction == "repo-to-home" - - -def test_build_plan_detects_home_to_repo_direction(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - source_skill = make_skill( - source / ".github" / "skills", "gamma-skill", "# Gamma source\n" - ) - home_skill = make_skill( - home / ".agents" / "skills", "gamma-skill", "# Gamma home\n" - ) - set_tree_mtime(source_skill, 100.0) - set_tree_mtime(home_skill, 200.0) - - plan = build_bisync_plan(source, home, mode="plan") - - assert len(plan.drifts) == 1 - drift = plan.drifts[0] - assert drift.skill_name == "gamma-skill" - assert drift.drift_type == "drift" - assert drift.direction == "home-to-repo" - - -def test_build_plan_treats_hash_equal_different_mtime_as_in_sync( - tmp_path: Path, -) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - source_skill = make_skill( - source / ".github" / "skills", "same-hash-skill", "# Same content\n" - ) - home_skill = make_skill( - home / ".agents" / "skills", "same-hash-skill", "# Same content\n" - ) - set_tree_mtime(source_skill, 200.0) - set_tree_mtime(home_skill, 100.0) - - plan = build_bisync_plan(source, home, mode="plan") - - assert plan.drifts == [] - assert plan.blocked_codes == [] - assert plan.verification["status"] == "ok" - - -def test_build_plan_blocks_equal_mtime(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - source_skill = make_skill( - source / ".github" / "skills", "equal-skill", "# Equal source\n" - ) - home_skill = make_skill( - home / ".agents" / "skills", "equal-skill", "# Equal home\n" - ) - set_tree_mtime(source_skill, 100.0) - set_tree_mtime(home_skill, 100.0) - - plan = build_bisync_plan(source, home, mode="plan") - - assert len(plan.drifts) == 1 - assert plan.drifts[0].drift_type == "equal-mtime" - assert plan.blocked_codes == ["bisync-equal-mtime"] - assert plan.next_action["allowed"] is False - - -def test_build_plan_blocks_only_repo_and_only_home(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - (home / ".agents" / "skills").mkdir(parents=True, exist_ok=True) - make_skill(source / ".github" / "skills", "repo-only", "# Repo only\n") - make_skill(home / ".agents" / "skills", "home-only", "# Home only\n") - - plan = build_bisync_plan(source, home, mode="plan") - - drift_types = {drift.skill_name: drift.drift_type for drift in plan.drifts} - assert drift_types == {"repo-only": "only-repo", "home-only": "only-home"} - assert plan.blocked_codes == ["bisync-only-home", "bisync-only-repo"] - - -def test_build_plan_ignores_catalog_excluded_skills(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - write_file( - source - / ".github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml", - "version: 1\n" - "defaults:\n" - " include_internal_skills: true\n" - " include_local_skills: false\n" - " include_unlisted_skills: true\n" - " excluded_skills:\n" - " - graphify\n" - "resources: []\n", - ) - make_skill(source / ".github" / "skills", "graphify", "# Repo graphify\n") - make_skill(home / ".agents" / "skills", "graphify", "# Home graphify\n") - - plan = build_bisync_plan(source, home, mode="plan") - - assert plan.drifts == [] - assert plan.blocked_codes == [] - - -def test_excludes_local_prefixed_bundles_from_scan(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - make_skill( - source / ".github" / "skills", - "local-agent-sync-install-ai-resources", - "# Sync bundle\n", - ) - make_skill( - home / ".agents" / "skills", - "local-agent-sync-install-ai-resources", - "# Diverged sync bundle\n", - ) - make_skill(source / ".github" / "skills", "local-custom", "# Local source\n") - make_skill(home / ".agents" / "skills", "local-custom", "# Local home\n") - make_skill(source / ".github" / "skills", "normal-skill", "# Normal\n") - make_skill(home / ".agents" / "skills", "normal-skill", "# Normal\n") - - plan = build_bisync_plan(source, home, mode="plan") - - assert {drift.skill_name for drift in plan.drifts} == set() - - -def test_apply_blocks_dirty_repository_without_writes(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - source_skill = make_skill( - source / ".github" / "skills", "dirty-skill", "# Source\n" - ) - home_skill = make_skill(home / ".agents" / "skills", "dirty-skill", "# Home\n") - set_tree_mtime(home_skill, 100.0) - set_tree_mtime(source_skill, 200.0) - write_file(source / "dirty.txt", "dirty\n") - - plan = build_bisync_plan(source, home, mode="plan") - result = apply_bisync_plan(source, home, plan) - - assert result.blocked_codes == ["bisync-repo-dirty"] - assert result.verification["code"] == "bisync-repo-dirty" - assert (home_skill / "SKILL.md").read_text(encoding="utf-8") == "# Home\n" - - -def test_run_bisync_apply_allows_only_repo_blocker(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - make_skill(source / ".github" / "skills", "repo-only", "# Repo only\n") - (home / ".agents" / "skills").mkdir(parents=True, exist_ok=True) - commit_all(source, "add repo-only") - - args = argparse.Namespace( - source_root=source.as_posix(), - home_root=home.as_posix(), - format="json", - ) - - exit_code = bisync_skills.run_bisync_apply(args) - - assert exit_code == 0 - assert (home / ".agents" / "skills" / "repo-only" / "SKILL.md").read_text( - encoding="utf-8" - ) == "# Repo only\n" - verify_plan = build_bisync_plan(source, home, mode="plan") - assert verify_plan.drifts == [] - - -def test_run_bisync_apply_blocks_non_resolvable_codes(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - make_skill(source / ".github" / "skills", "repo-only", "# Repo only\n") - make_skill(home / ".agents" / "skills", "home-only", "# Home only\n") - commit_all(source, "add repo-only") - - args = argparse.Namespace( - source_root=source.as_posix(), - home_root=home.as_posix(), - format="json", - ) - - exit_code = bisync_skills.run_bisync_apply(args) - - assert exit_code == 1 - assert not (home / ".agents" / "skills" / "repo-only").exists() - - -def test_apply_repo_to_home_converges_without_manifest_entry( - tmp_path: Path, -) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - source_skill = make_skill( - source / ".github" / "skills", "apply-skill", "# Source\n" - ) - home_skill = make_skill(home / ".agents" / "skills", "apply-skill", "# Home\n") - set_tree_mtime(home_skill, 100.0) - set_tree_mtime(source_skill, 200.0) - commit_all(source, "add apply-skill") - - plan = build_bisync_plan(source, home, mode="plan") - result = apply_bisync_plan(source, home, plan) - - assert result.blocked_codes == [] - assert result.verification["status"] == "converged" - assert (home_skill / "SKILL.md").read_text(encoding="utf-8") == "# Source\n" - - -def test_apply_repo_to_home_blocks_on_manifest_source_mismatch( - tmp_path: Path, -) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - state_root = home / ".sync" / "cloud-strategy-governance" / "home-ai-resources" - manifest_path = state_root / "manifest.json" - init_git_repo(source) - source_skill = make_skill( - source / ".github" / "skills", "managed-skill", "# Source\n" - ) - home_skill = make_skill(home / ".agents" / "skills", "managed-skill", "# Home\n") - set_tree_mtime(home_skill, 100.0) - set_tree_mtime(source_skill, 200.0) - commit_all(source, "add managed-skill") - - state_root.mkdir(parents=True, exist_ok=True) - manifest_payload = { - "managed_resources": [ - { - "target": "skills", - "resource_family": "skills", - "resource_id": "managed-skill", - "source_path": ".github/skills/not-managed-skill", - "target_path": home_skill.as_posix(), - "source_hash": "", - "content_hash": "", - "last_action": "copy", - } - ] - } - manifest_path.write_text(json.dumps(manifest_payload), encoding="utf-8") - - plan = build_bisync_plan(source, home, mode="plan") - result = apply_bisync_plan(source, home, plan) - - assert result.blocked_codes == ["bisync-manifest-reconcile-failed"] - assert result.verification["code"] == "bisync-manifest-reconcile-failed" - assert (home_skill / "SKILL.md").read_text(encoding="utf-8") == "# Source\n" - - -def test_apply_home_to_repo_converges(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - source_skill = make_skill(source / ".github" / "skills", "return-skill", "# Repo\n") - home_skill = make_skill(home / ".agents" / "skills", "return-skill", "# Home\n") - set_tree_mtime(source_skill, 100.0) - set_tree_mtime(home_skill, 200.0) - commit_all(source, "add return-skill") - - plan = build_bisync_plan(source, home, mode="plan") - result = apply_bisync_plan(source, home, plan) - - assert result.blocked_codes == [] - assert result.verification["status"] == "converged" - assert (source_skill / "SKILL.md").read_text(encoding="utf-8") == "# Home\n" - verify_plan = build_bisync_plan(source, home, mode="plan") - assert verify_plan.drifts == [] - - -def test_apply_reports_verify_failure_with_stable_code( - monkeypatch: pytest.MonkeyPatch, tmp_path: Path -) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - source_skill = make_skill( - source / ".github" / "skills", "verify-skill", "# Source\n" - ) - home_skill = make_skill(home / ".agents" / "skills", "verify-skill", "# Home\n") - set_tree_mtime(home_skill, 100.0) - set_tree_mtime(source_skill, 200.0) - commit_all(source, "add verify-skill") - - plan = build_bisync_plan(source, home, mode="plan") - original_hash_bundle = bisync_skills.hash_bundle - - def fake_hash_bundle(path: Path) -> str: - if path == home_skill: - return "bad-hash" - return original_hash_bundle(path) - - monkeypatch.setattr(bisync_skills, "hash_bundle", fake_hash_bundle) - result = apply_bisync_plan(source, home, plan) - - assert result.blocked_codes == ["bisync-verify-failed"] - assert result.verification["code"] == "bisync-verify-failed" - assert result.verification["skill"] == "verify-skill" - - -def test_apply_reports_residual_drift_with_stable_code( - monkeypatch: pytest.MonkeyPatch, tmp_path: Path -) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - source_skill = make_skill( - source / ".github" / "skills", "residual-skill", "# Source\n" - ) - home_skill = make_skill(home / ".agents" / "skills", "residual-skill", "# Home\n") - set_tree_mtime(home_skill, 100.0) - set_tree_mtime(source_skill, 200.0) - commit_all(source, "add residual-skill") - - state_root = home / ".sync/cloud-strategy-governance/home-ai-resources" - state_root.mkdir(parents=True, exist_ok=True) - manifest_path = state_root / "manifest.json" - manifest_path.write_text( - json.dumps( - { - "schema_version": 1, - "generated_at": "2026-06-09T00:00:00Z", - "source_root": source.as_posix(), - "source_revision": "initial", - "state_root": state_root.as_posix(), - "targets": ["skills"], - "managed_resources": [ - { - "target": "skills", - "resource_family": "skills", - "resource_id": "residual-skill", - "source_path": ".github/skills/residual-skill", - "target_path": home_skill.as_posix(), - "source_hash": bisync_skills.hash_bundle(source_skill), - "content_hash": bisync_skills.hash_bundle(home_skill), - "last_action": "copy", - } - ], - }, - indent=2, - sort_keys=True, - ), - encoding="utf-8", - ) - - plan = build_bisync_plan(source, home, mode="plan") - original_build_bisync_plan = bisync_skills.build_bisync_plan - - def fake_build_bisync_plan( - source_root: Path, - home_root: Path, - *, - mode: str = "plan", - ) -> BisyncPlan: - if mode == "verify": - return BisyncPlan( - source_root=source_root, - home_root=home_root, - source_skills_root=source_root / ".github" / "skills", - home_skills_root=home_root / ".agents" / "skills", - mode=mode, - drifts=[ - BisyncDriftEntry( - skill_name="residual-skill", - drift_type="drift", - direction="repo-to-home", - repo_path=( - source_root / ".github" / "skills" / "residual-skill" - ).as_posix(), - home_path=( - home_root / ".agents" / "skills" / "residual-skill" - ).as_posix(), - ) - ], - blocked_codes=[], - ) - return original_build_bisync_plan(source_root, home_root, mode=mode) - - monkeypatch.setattr(bisync_skills, "build_bisync_plan", fake_build_bisync_plan) - result = apply_bisync_plan(source, home, plan) - - assert "bisync-residual-drift" in result.blocked_codes - assert result.verification["code"] == "bisync-residual-drift" - assert result.next_action["allowed"] is False - - -def test_plan_json_output_contains_structured_next_action(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - (home / ".agents" / "skills").mkdir(parents=True, exist_ok=True) - make_skill(source / ".github" / "skills", "json-skill", "# JSON test\n") - - plan = build_bisync_plan(source, home, mode="plan") - payload = plan.to_dict() - json_str = json.dumps(payload, sort_keys=True) - - assert "json-skill" in json_str - assert payload["next_action"]["action"] == "resolve_blockers" - assert payload["next_action"]["allowed"] is False - assert payload["next_action"]["requires_explicit_approval"] is True - - -def test_emit_report_output_groups_repo_home_buckets( - tmp_path: Path, capsys: pytest.CaptureFixture[str] -) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - init_git_repo(source) - make_skill(source / ".github" / "skills", "repo-only", "# Repo only\n") - make_skill(home / ".agents" / "skills", "home-only", "# Home only\n") - source_skill = make_skill( - source / ".github" / "skills", "direction-skill", "# Repo\n" - ) - home_skill = make_skill(home / ".agents" / "skills", "direction-skill", "# Home\n") - set_tree_mtime(home_skill, 100.0) - set_tree_mtime(source_skill, 200.0) - - plan = build_bisync_plan(source, home, mode="plan") - bisync_skills._emit_bisync_output(plan, "report") - output = capsys.readouterr().out - - assert "repo-only" in output - assert "home-only" in output - assert "repo-to-home" in output - assert "Repository bundle timestamp is newer than home bundle." in output - assert "bisync-only-repo" in output - - -def test_source_root_missing_skills_dir_returns_blocker(tmp_path: Path) -> None: - source = tmp_path / "source" - home = tmp_path / "home" - source.mkdir() - home.mkdir() - - plan = build_bisync_plan(source, home, mode="plan") - - assert "bisync-source-missing" in plan.blocked_codes - assert plan.next_action["allowed"] is False diff --git a/tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_home_syncing.py b/tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_home_syncing.py deleted file mode 100644 index 93bdab3..0000000 --- a/tests/github/skills/local_agent_sync_install_ai_resources/scripts/test_home_syncing.py +++ /dev/null @@ -1,917 +0,0 @@ -from __future__ import annotations - -import importlib.util -import json -import os -import subprocess -import sys -from pathlib import Path - -import pytest -import yaml - - -def find_repo_root(start: Path) -> Path: - for candidate in start.resolve().parents: - if (candidate / "AGENTS.md").is_file(): - return candidate - raise FileNotFoundError(f"Unable to find repository root from {start}") - - -REPO_ROOT = find_repo_root(Path(__file__)) -SKILL_SCRIPTS_ROOT = ( - REPO_ROOT / ".github/skills/local-agent-sync-install-ai-resources/scripts" -) - - -def load_skill_module(): - inserted_path = False - if SKILL_SCRIPTS_ROOT.as_posix() not in sys.path: - sys.path.insert(0, SKILL_SCRIPTS_ROOT.as_posix()) - inserted_path = True - try: - spec = importlib.util.spec_from_file_location( - "_test_local_home_syncing", - SKILL_SCRIPTS_ROOT / "home_syncing.py", - ) - assert spec is not None - assert spec.loader is not None - module = importlib.util.module_from_spec(spec) - sys.modules[spec.name] = module - spec.loader.exec_module(module) - return module - finally: - if inserted_path: - sys.path.remove(SKILL_SCRIPTS_ROOT.as_posix()) - - -home_syncing = load_skill_module() -apply_home_sync_plan = home_syncing.apply_home_sync_plan -build_home_sync_plan = home_syncing.build_home_sync_plan -parse_targets = home_syncing.parse_targets - - -def load_bisync_module(): - inserted_path = False - if SKILL_SCRIPTS_ROOT.as_posix() not in sys.path: - sys.path.insert(0, SKILL_SCRIPTS_ROOT.as_posix()) - inserted_path = True - try: - spec = importlib.util.spec_from_file_location( - "_test_local_bisync_skills", - SKILL_SCRIPTS_ROOT / "bisync_skills.py", - ) - assert spec is not None - assert spec.loader is not None - module = importlib.util.module_from_spec(spec) - sys.modules[spec.name] = module - spec.loader.exec_module(module) - return module - finally: - if inserted_path: - sys.path.remove(SKILL_SCRIPTS_ROOT.as_posix()) - - -bisync_skills = load_bisync_module() -apply_bisync_plan = bisync_skills.apply_bisync_plan -build_bisync_plan = bisync_skills.build_bisync_plan - - -def init_git_repo(root: Path) -> None: - root.mkdir(parents=True, exist_ok=True) - subprocess.run( - ["git", "init", "-b", "main"], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "config", "user.email", "test@example.com"], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "config", "user.name", "Test"], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - - -def commit_all(root: Path, message: str) -> None: - subprocess.run( - ["git", "add", "-A"], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "commit", "-m", message], - cwd=root, - text=True, - capture_output=True, - check=True, - ) - - -def set_tree_mtime(root: Path, timestamp: float) -> None: - for path in sorted(root.rglob("*")): - os.utime(path, (timestamp, timestamp)) - os.utime(root, (timestamp, timestamp)) - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def initialize_source_repo(root: Path) -> None: - write_file(root / "AGENTS.md", "# AGENTS\n") - write_file( - root / ".github/skills/demo-skill/SKILL.md", - "---\n" - "name: demo-skill\n" - "description: Use when a demo home-sync skill is needed.\n" - "---\n\n" - "# Demo Skill\n\n" - "## When to use\n\n" - "- Use when a demo home-sync skill is needed.\n", - ) - write_file( - root / ".github/skills/demo-skill/agents/openai.yaml", - "interface:\n" - ' display_name: "Demo Skill"\n' - ' short_description: "Portable demo skill bundle"\n' - ' default_prompt: "Use $demo-skill for demo work."\n', - ) - write_file( - root - / ".github/skills/local-agent-sync-install-ai-resources/references/runtime-support-matrix.yaml", - "version: 1\n" - "rows:\n" - " - target: skills\n" - " resource_family: skills\n" - " support_level: Documented\n" - " home_path: ~/.agents/skills//\n" - " direct_copy_possible: true\n" - " translation_required: false\n" - " include_in_v1: true\n" - " evidence: []\n" - " notes: Shared skill support.\n" - " - target: codex\n" - " resource_family: skills\n" - " support_level: Documented\n" - " home_path: ~/.codex/skills//\n" - " direct_copy_possible: true\n" - " translation_required: false\n" - " include_in_v1: true\n" - " evidence: []\n" - " notes: Codex direct-copy skill support.\n" - " - target: opencode\n" - " resource_family: skills\n" - " support_level: User-provided / To verify\n" - " home_path: ~/.config/opencode/skills//\n" - " direct_copy_possible: true\n" - " translation_required: false\n" - " include_in_v1: false\n" - " evidence: []\n" - " notes: Undocumented support.\n", - ) - write_file( - root - / ".github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml", - "version: 1\n" - "defaults:\n" - " include_internal_skills: false\n" - " include_local_skills: false\n" - " include_unlisted_skills: false\n" - "resources:\n" - " - resource_id: demo-skill\n" - " source_family: skills\n" - " source_path: .github/skills/demo-skill\n" - " include_targets:\n" - " - codex\n" - " - opencode\n" - " target_support: Documented\n" - " notes: Demo bundle.\n", - ) - - -def test_parse_targets_normalizes_known_values_and_rejects_unknowns() -> None: - assert parse_targets(" opencode, codex , codex ") == ("codex", "opencode") - assert parse_targets("skills") == ("skills",) - assert parse_targets("all") == ("skills", "codex", "copilot", "opencode") - - with pytest.raises(ValueError, match="unknown-target"): - parse_targets("codex,unknown") - - -def test_build_home_sync_plan_blocks_unmanaged_targets_and_docs_unverified_apply( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - - unmanaged_target = home_root / ".agents/skills/demo-skill" - write_file(unmanaged_target / "SKILL.md", "# unmanaged\n") - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - operation_codes = { - operation.code for operation in plan.operations if operation.code - } - - assert "target-exists-unmanaged" in operation_codes - assert "docs-unverified" not in operation_codes - - -def test_build_home_sync_plan_adopts_unmanaged_skills_when_repo_wins_policy_is_enabled( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - catalog_path = ( - source_root - / ".github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml" - ) - catalog_path.write_text( - "version: 1\n" - "defaults:\n" - " include_internal_skills: false\n" - " include_local_skills: false\n" - " include_unlisted_skills: false\n" - " unmanaged_existing_skills_policy: repo-wins\n" - "resources:\n" - " - resource_id: demo-skill\n" - " source_family: skills\n" - " source_path: .github/skills/demo-skill\n" - " include_targets:\n" - " - codex\n" - " - opencode\n" - " target_support: Documented\n" - " notes: Demo bundle.\n", - encoding="utf-8", - ) - - unmanaged_target = home_root / ".agents/skills/demo-skill" - write_file(unmanaged_target / "SKILL.md", "# unmanaged\n") - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - actions_by_resource = { - operation.resource_id: operation.action - for operation in plan.operations - if operation.resource_id - } - operation_codes = { - operation.code for operation in plan.operations if operation.code - } - - assert actions_by_resource["demo-skill"] == "copy" - assert "target-exists-unmanaged" not in operation_codes - - -def test_apply_home_sync_plan_creates_missing_dirs_with_flag_and_writes_manifest( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - write_file( - source_root / ".github/skills/demo-skill/scripts/.venv/marker.txt", "runtime\n" - ) - write_file( - source_root / ".github/skills/demo-skill/__pycache__/demo.pyc", "runtime\n" - ) - write_file( - source_root / ".github/skills/demo-skill/.pytest_cache/CACHEDIR.TAG", - "runtime\n", - ) - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - - with pytest.raises(RuntimeError, match="needs-directory-create"): - apply_home_sync_plan(plan) - - manifest_path = apply_home_sync_plan(plan, create_missing_dirs=True) - copied_skill = home_root / ".agents/skills/demo-skill/SKILL.md" - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - - assert copied_skill.is_file() - assert not (home_root / ".agents/skills/demo-skill/scripts/.venv").exists() - assert not (home_root / ".agents/skills/demo-skill/__pycache__").exists() - assert not (home_root / ".agents/skills/demo-skill/.pytest_cache").exists() - assert manifest["targets"] == ["skills"] - assert manifest["managed_resources"][0]["resource_id"] == "demo-skill" - - -def test_skill_bundle_sync_scripts_can_load_bundled_references( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - write_file(source_root / "AGENTS.md", "# AGENTS\n") - write_file( - source_root / ".github/skills/demo-skill/SKILL.md", - "---\n" - "name: demo-skill\n" - "description: Use when a demo home-sync skill is needed.\n" - "---\n\n" - "# Demo Skill\n\n" - "## When to use\n\n" - "- Use when a demo home-sync skill is needed.\n", - ) - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="plan", - ) - - assert plan.source_resources_considered >= 1 - - -def test_stale_manifest_path_escapes_home_is_blocked(tmp_path: Path) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - manifest_path = apply_home_sync_plan(plan, create_missing_dirs=True) - - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - manifest["managed_resources"][0]["target_path"] = "/tmp/escaped/path" - manifest_path.write_text(json.dumps(manifest), encoding="utf-8") - - catalog_path = ( - source_root - / ".github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml" - ) - catalog_path.write_text( - "version: 1\n" - "defaults:\n" - " include_internal_skills: false\n" - " include_local_skills: false\n" - " include_unlisted_skills: false\n" - "resources: []\n", - encoding="utf-8", - ) - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - prune_managed=True, - ) - blocked_ops = [op for op in plan.operations if op.action == "blocked"] - assert any( - op.code in {"unsafe-home-path", "symlink-not-allowed"} for op in blocked_ops - ) - - -def test_stale_managed_content_drift_blocks_delete(tmp_path: Path) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - apply_home_sync_plan(plan, create_missing_dirs=True) - - copied_skill = home_root / ".agents/skills/demo-skill/SKILL.md" - copied_skill.write_text("# drifted content\n", encoding="utf-8") - - catalog_path = ( - source_root - / ".github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml" - ) - catalog_path.write_text( - "version: 1\n" - "defaults:\n" - " include_internal_skills: false\n" - " include_local_skills: false\n" - " include_unlisted_skills: false\n" - "resources: []\n", - encoding="utf-8", - ) - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - prune_managed=True, - ) - blocked_ops = [op for op in plan.operations if op.action == "blocked"] - assert any(op.code == "stale-content-drifted" for op in blocked_ops) - - -def test_doctor_checks_include_agent_roots(tmp_path: Path) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - - checks, blocked_codes = home_syncing.run_doctor( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - ) - - skill_checks = [c for c in checks if c.get("name") == "target-root:skills"] - assert len(skill_checks) == 1 - assert skill_checks[0]["path"].endswith(".agents/skills") - assert all(".codex/agents" not in c["path"] for c in checks) - - -def test_doctor_checks_keep_agent_roots_for_runtime_targets(tmp_path: Path) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - - checks, blocked_codes = home_syncing.run_doctor( - source_root=source_root, - home_root=home_root, - targets=parse_targets("codex"), - ) - - codex_checks = [c for c in checks if c.get("name") == "target-root:codex"] - assert len(codex_checks) == 2 - paths = {c["path"] for c in codex_checks} - assert any(".agents/skills" in p for p in paths) - assert any(".codex/agents" in p for p in paths) - - -def test_triple_apply_skip_plan_sequence_is_convergent(tmp_path: Path) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - - plan1 = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("codex"), - mode="apply", - ) - manifest_path = apply_home_sync_plan(plan1, create_missing_dirs=True) - manifest1 = json.loads(manifest_path.read_text(encoding="utf-8")) - - plan2 = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("codex"), - mode="apply", - ) - manifest_path2 = apply_home_sync_plan(plan2) - manifest2 = json.loads(manifest_path2.read_text(encoding="utf-8")) - - plan3 = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("codex"), - mode="plan", - ) - skip_ops = [op for op in plan3.operations if op.action == "skip"] - assert len(skip_ops) == 1 - - assert ( - manifest1["managed_resources"][0]["content_hash"] - == manifest2["managed_resources"][0]["content_hash"] - ) - - -def test_bisync_apply_creates_home_bundle_for_only_repo_skill(tmp_path: Path) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - (home_root / ".agents/skills").mkdir(parents=True, exist_ok=True) - init_git_repo(source_root) - commit_all(source_root, "initial source bundle") - - plan = build_bisync_plan(source_root, home_root, mode="plan") - repo_only = next((d for d in plan.drifts if d.skill_name == "demo-skill"), None) - - assert repo_only is not None - assert repo_only.drift_type == "only-repo" - assert repo_only.direction == "repo-to-home" - assert repo_only.blocked_codes == [] - - result = apply_bisync_plan(source_root, home_root, plan) - assert result.blocked_codes == [] - assert result.verification["status"] == "converged" - assert (home_root / ".agents/skills/demo-skill/SKILL.md").is_file() - - post_plan = build_bisync_plan(source_root, home_root, mode="plan") - assert post_plan.drifts == [] - - -def test_repo_to_home_bisync_refreshes_install_manifest(tmp_path: Path) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - init_git_repo(source_root) - commit_all(source_root, "initial source bundle") - - install_plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - manifest_path = apply_home_sync_plan(install_plan, create_missing_dirs=True) - - source_skill = source_root / ".github/skills/demo-skill" - home_skill = home_root / ".agents/skills/demo-skill" - write_file(source_skill / "SKILL.md", "# Source v2\n") - commit_all(source_root, "repo-to-home drift") - set_tree_mtime(home_skill, 100.0) - set_tree_mtime(source_skill, 200.0) - - bisync_plan = build_bisync_plan(source_root, home_root, mode="plan") - assert any(drift.direction == "repo-to-home" for drift in bisync_plan.drifts) - - bisync_result = apply_bisync_plan(source_root, home_root, bisync_plan) - assert bisync_result.blocked_codes == [] - assert bisync_result.verification["status"] == "converged" - - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - managed = manifest["managed_resources"][0] - assert managed["source_hash"] == home_syncing.hash_resource(source_skill) - assert managed["content_hash"] == home_syncing.hash_resource(home_skill) - - post_bisync_plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - blocked_codes = { - operation.code for operation in post_bisync_plan.operations if operation.code - } - assert "target-modified-managed" not in blocked_codes - - -def test_home_newer_managed_drift_becomes_warning_until_bisync_review( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - init_git_repo(source_root) - commit_all(source_root, "initial source bundle") - - install_plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - apply_home_sync_plan(install_plan, create_missing_dirs=True) - - source_skill = source_root / ".github/skills/demo-skill" - copied_skill = home_root / ".agents/skills/demo-skill" - write_file(copied_skill / "SKILL.md", "# locally edited\n") - set_tree_mtime(source_skill, 100.0) - set_tree_mtime(copied_skill, 200.0) - - review_plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - blocked_codes = { - operation.code - for operation in review_plan.operations - if operation.action == "blocked" and operation.code - } - warning_codes = { - operation.code - for operation in review_plan.operations - if operation.action == "warning" and operation.code - } - - assert "target-modified-managed" not in blocked_codes - assert "target-modified-managed" in warning_codes - - -def test_ambiguous_managed_drift_still_blocks_target_modified_managed( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - init_git_repo(source_root) - commit_all(source_root, "initial source bundle") - - install_plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - apply_home_sync_plan(install_plan, create_missing_dirs=True) - - source_skill = source_root / ".github/skills/demo-skill" - copied_skill = home_root / ".agents/skills/demo-skill" - write_file(copied_skill / "SKILL.md", "# locally edited\n") - set_tree_mtime(source_skill, 100.0) - set_tree_mtime(copied_skill, 100.0) - - blocked_plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - blocked_codes = { - operation.code for operation in blocked_plan.operations if operation.code - } - - assert "target-modified-managed" in blocked_codes - - -def test_home_sync_plan_includes_graphify_when_present_in_catalog( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - write_file( - source_root / ".github/skills/graphify/SKILL.md", - "---\nname: graphify\n---\n\n# Graphify\n", - ) - - catalog_path = ( - source_root - / ".github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml" - ) - catalog_path.write_text( - "version: 1\n" - "defaults:\n" - " include_internal_skills: false\n" - " include_local_skills: false\n" - " include_unlisted_skills: false\n" - "resources:\n" - " - resource_id: demo-skill\n" - " source_family: skills\n" - " source_path: .github/skills/demo-skill\n" - " include_targets:\n" - " - codex\n" - " - resource_id: graphify\n" - " source_family: skills\n" - " source_path: .github/skills/graphify\n" - " include_targets:\n" - " - codex\n", - encoding="utf-8", - ) - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("codex"), - mode="plan", - ) - - resource_ids = { - op.resource_id for op in plan.operations if hasattr(op, "resource_id") - } - assert "graphify" in resource_ids or any( - ".agents/skills/graphify" in str(op.path) for op in plan.operations - ) - - -def test_removed_source_bundle_becomes_stale_managed_instead_of_source_missing( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - ) - apply_home_sync_plan(plan, create_missing_dirs=True) - - skill_dir = source_root / ".github/skills/demo-skill" - for path in sorted(skill_dir.rglob("*"), reverse=True): - if path.is_file(): - path.unlink() - elif path.is_dir(): - path.rmdir() - skill_dir.rmdir() - - plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("skills"), - mode="apply", - prune_managed=True, - ) - codes = {op.code for op in plan.operations if op.code} - delete_paths = {op.path for op in plan.operations if op.action == "delete"} - - assert "source-missing" not in codes - assert str(home_root / ".agents/skills/demo-skill") in delete_paths - - -def test_apply_can_retire_selected_targets_without_touching_remaining_targets( - monkeypatch: pytest.MonkeyPatch, - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - home_syncing, - "translate_agent_for_target", - lambda source_path, target, config_path=None: f"# translated for {target}\n", - ) - - support_matrix_path = ( - source_root - / ".github/skills/local-agent-sync-install-ai-resources/references/runtime-support-matrix.yaml" - ) - support_matrix_path.write_text( - "version: 1\n" - "rows:\n" - " - target: codex\n" - " resource_family: skills\n" - " support_level: Documented\n" - " home_path: ~/.agents/skills//\n" - " direct_copy_possible: true\n" - " translation_required: false\n" - " include_in_v1: true\n" - " evidence: []\n" - " notes: Codex direct-copy skill support.\n" - " - target: copilot\n" - " resource_family: skills\n" - " support_level: Documented\n" - " home_path: ~/.agents/skills//\n" - " direct_copy_possible: true\n" - " translation_required: false\n" - " include_in_v1: true\n" - " evidence: []\n" - " notes: Copilot direct-copy skill support.\n" - " - target: opencode\n" - " resource_family: skills\n" - " support_level: Documented\n" - " home_path: ~/.agents/skills//\n" - " direct_copy_possible: true\n" - " translation_required: false\n" - " include_in_v1: true\n" - " evidence: []\n" - " notes: OpenCode direct-copy skill support.\n" - " - target: codex\n" - " resource_family: agents\n" - " support_level: Documented\n" - " home_path: ~/.codex/agents/.md\n" - " direct_copy_possible: false\n" - " translation_required: true\n" - " include_in_v1: true\n" - " evidence: []\n" - " notes: Codex agent translation support.\n" - " - target: copilot\n" - " resource_family: agents\n" - " support_level: Documented\n" - " home_path: ~/.copilot/agents/.md\n" - " direct_copy_possible: true\n" - " translation_required: false\n" - " include_in_v1: true\n" - " evidence: []\n" - " notes: Copilot agent direct-copy support.\n" - " - target: opencode\n" - " resource_family: agents\n" - " support_level: Documented\n" - " home_path: ~/.config/opencode/agents/.md\n" - " direct_copy_possible: false\n" - " translation_required: true\n" - " include_in_v1: true\n" - " evidence: []\n" - " notes: OpenCode agent translation support.\n", - encoding="utf-8", - ) - catalog_path = ( - source_root - / ".github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml" - ) - catalog_path.write_text( - "version: 1\n" - "defaults:\n" - " include_internal_skills: false\n" - " include_local_skills: false\n" - " include_unlisted_skills: false\n" - "resources:\n" - " - resource_id: demo-skill\n" - " source_family: skills\n" - " source_path: .github/skills/demo-skill\n" - " include_targets:\n" - " - codex\n" - " - copilot\n" - " target_support: Documented\n" - " notes: Demo bundle.\n", - encoding="utf-8", - ) - agent_path = source_root / ".github/agents/demo.agent.md" - write_file( - agent_path, - "---\nname: demo\ndescription: Demo agent.\n---\n\n# Demo agent\n", - ) - catalog_payload = yaml.safe_load(catalog_path.read_text(encoding="utf-8")) - catalog_payload["resources"].append( - { - "resource_id": "demo", - "source_family": "agents", - "source_path": ".github/agents/demo.agent.md", - "include_targets": ["codex", "copilot", "opencode"], - "target_support": "Documented", - "notes": "Demo agent.", - } - ) - catalog_path.write_text( - yaml.safe_dump(catalog_payload, sort_keys=False), encoding="utf-8" - ) - - initial_plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("codex,copilot,opencode"), - mode="apply", - ) - manifest_path = apply_home_sync_plan(initial_plan, create_missing_dirs=True) - - retire_plan = build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=parse_targets("codex,copilot"), - retired_targets=parse_targets("opencode"), - mode="apply", - prune_managed=True, - ) - apply_home_sync_plan(retire_plan, prune_managed=True) - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - - assert manifest["targets"] == ["codex", "copilot"] - assert not (home_root / ".config/opencode/agents/demo.md").exists() - assert (home_root / ".copilot/agents/demo.agent.md").is_file() - assert (home_root / ".agents/skills/demo-skill").is_dir() - assert all(entry["target"] != "opencode" for entry in manifest["managed_resources"]) - - -def test_home_sync_catalog_does_not_contain_internal_graphify_in_real_repo() -> None: - catalog = home_syncing.load_home_sync_catalog(REPO_ROOT) - resource_ids = {resource.resource_id for resource in catalog} - assert "graphify" not in resource_ids - assert "internal-graphify" not in resource_ids - - -def test_home_sync_catalog_contains_internal_ai_resource_review_in_real_repo() -> None: - catalog = home_syncing.load_home_sync_catalog(REPO_ROOT) - resource_ids = {resource.resource_id for resource in catalog} - assert "internal-ai-resource-review" in resource_ids - - -def test_home_sync_catalog_keeps_skill_and_agent_variants_for_gateway_ids_in_real_repo() -> ( - None -): - catalog = home_syncing.load_home_sync_catalog(REPO_ROOT) - resources = { - (resource.resource_id, resource.source_family): resource.source_path - for resource in catalog - } - - assert ("internal-gateway-simple-task", "skills") in resources - assert ("internal-gateway-simple-task", "agents") in resources diff --git a/tests/test_agent_os_portable_skills_contract.py b/tests/test_agent_os_portable_skills_contract.py deleted file mode 100644 index 5cca966..0000000 --- a/tests/test_agent_os_portable_skills_contract.py +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - - -def read_text(relative_path: str) -> str: - return Path(relative_path).read_text(encoding="utf-8") - - -SKILL_SOURCE_MAP = { - ".github/skills/agent-os-discover-standards/SKILL.md": ".claude/commands/agent-os/discover-standards.md", - ".github/skills/agent-os-index-standards/SKILL.md": ".claude/commands/agent-os/index-standards.md", - ".github/skills/agent-os-inject-standards/SKILL.md": ".claude/commands/agent-os/inject-standards.md", - ".github/skills/agent-os-plan-product/SKILL.md": ".claude/commands/agent-os/plan-product.md", - ".github/skills/agent-os-shape-spec/SKILL.md": ".claude/commands/agent-os/shape-spec.md", -} - - -def test_agent_os_portable_skills_are_present_and_mapped() -> None: - for skill_path, source_path in SKILL_SOURCE_MAP.items(): - text = read_text(skill_path) - assert "## Referenced skills" in text - assert "## Source command parity" in text - assert source_path in text - - -def test_agent_os_portable_skills_keep_runtime_portability_boundary() -> None: - for skill_path in SKILL_SOURCE_MAP: - text = read_text(skill_path) - assert "portable for Copilot and Codex" in text - assert "does not rely on Claude slash commands" in text - - -def test_agent_os_portable_skills_preserve_declarative_storage_targets() -> None: - discover_text = read_text(".github/skills/agent-os-discover-standards/SKILL.md") - plan_product_text = read_text(".github/skills/agent-os-plan-product/SKILL.md") - shape_spec_text = read_text(".github/skills/agent-os-shape-spec/SKILL.md") - - assert "agent-os/standards/" in discover_text - assert "agent-os/product/" in plan_product_text - assert "agent-os/specs/" in shape_spec_text diff --git a/tests/test_apply_imported_asset_overrides.py b/tests/test_apply_imported_asset_overrides.py deleted file mode 100644 index fac1763..0000000 --- a/tests/test_apply_imported_asset_overrides.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import annotations - -import importlib.util -from pathlib import Path - - -def load_module(): - script_path = Path( - ".github/skills/local-agent-sync-external-resources/scripts/apply_imported_asset_overrides.py" - ) - spec = importlib.util.spec_from_file_location( - "apply_imported_asset_overrides", script_path - ) - module = importlib.util.module_from_spec(spec) - assert spec is not None - assert spec.loader is not None - spec.loader.exec_module(module) - return module - - -def test_detect_patch_status_uses_registered_3way_fallback(monkeypatch) -> None: - module = load_module() - - commands: list[tuple[str, ...]] = [] - - def fake_run_git(command, repo_root, quiet=False): # noqa: ARG001 - commands.append(tuple(command)) - if command[:3] == ["git", "apply", "--check"]: - return 1 - if command[:4] == ["git", "apply", "--reverse", "--check"]: - return 1 - if command[:4] == ["git", "apply", "--3way", "--check"]: - return 0 - return 1 - - monkeypatch.setattr(module, "run_git", fake_run_git) - - status = module.detect_patch_status( - Path("."), - Path("override.patch"), - apply_strategy="git-apply-3way", - ) - - assert status == "applicable-with-3way" - assert ("git", "apply", "--3way", "--check", "override.patch") in commands - - -def test_build_apply_command_uses_3way_only_when_needed() -> None: - module = load_module() - - patch_path = Path("override.patch") - - assert module.build_apply_command(patch_path, "applicable") == [ - "git", - "apply", - "override.patch", - ] - assert module.build_apply_command(patch_path, "applicable-with-3way") == [ - "git", - "apply", - "--3way", - "override.patch", - ] diff --git a/tests/test_canonical_agents_contract.py b/tests/test_canonical_agents_contract.py deleted file mode 100644 index 09418ce..0000000 --- a/tests/test_canonical_agents_contract.py +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import yaml - -CANONICAL_AGENTS = { - "internal-gateway-idea-brainstorming": ".github/agents/internal-gateway-idea-brainstorming.agent.md", - "internal-gateway-review": ".github/agents/internal-gateway-review.agent.md", - "internal-gateway-critical-master": ".github/agents/internal-gateway-critical-master.agent.md", - "internal-gateway-simple-task": ".github/agents/internal-gateway-simple-task.agent.md", -} - - -def load_frontmatter(relative_path: str) -> dict[str, object]: - text = Path(relative_path).read_text(encoding="utf-8") - return yaml.safe_load(text.split("---\n", 2)[1]) - - -def test_canonical_agents_exist_and_keep_frontmatter_contracts() -> None: - for name, relative_path in CANONICAL_AGENTS.items(): - frontmatter = load_frontmatter(relative_path) - assert frontmatter["name"] == name - assert frontmatter["disable-model-invocation"] is True - assert isinstance(frontmatter.get("handoffs"), list) - - -def test_review_gateway_is_canonical_and_operational_flow_wrapper_is_retired() -> None: - assert Path(".github/agents/internal-gateway-review.agent.md").is_file() - assert not Path( - ".github/agents/internal-gateway-operational-flow.agent.md" - ).exists() - - -def test_agents_readme_mentions_review_gateway_and_extended_execution_boundary() -> ( - None -): - readme = Path(".github/agents/README.md").read_text(encoding="utf-8") - assert "internal-gateway-review" in readme - assert "internal-gateway-execute-plans" in readme - assert "internal-gateway-operational-flow" not in readme diff --git a/tests/test_completion_report_contract.py b/tests/test_completion_report_contract.py deleted file mode 100644 index a1c7ca2..0000000 --- a/tests/test_completion_report_contract.py +++ /dev/null @@ -1,55 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - - -def read_text(relative_path: str) -> str: - return Path(relative_path).read_text(encoding="utf-8") - - -def test_repo_projection_keeps_completion_report_compact() -> None: - copilot_text = read_text(".github/copilot-instructions.md") - - assert "This file is only for GitHub.com Copilot code review." in copilot_text - assert "It is not a general task-execution guide" in copilot_text - assert "Review changed files for defects that matter before merge." in copilot_text - assert "Report findings first, ordered by severity." in copilot_text - assert ( - "Do not treat this file as instructions for coding agents, local CLIs, or" - in copilot_text - ) - assert "Report completed work with outcome, changed files" not in copilot_text - assert "`✅ Outcome`" not in copilot_text - assert "`1 = resources used`" not in copilot_text - - -def test_completion_report_docs_match_optional_detail_contract() -> None: - readme_text = read_text(".github/README.md") - internal_contract_text = read_text("INTERNAL_CONTRACT.md") - sync_contract_text = read_text( - ".github/skills/local-agent-sync-global-copilot-configs-into-repo/references/sync-contract.md" - ) - - assert "offer it as an optional follow-up" in readme_text - assert "number-only replies" in readme_text - - assert ( - "supporting sections such as `🤖 Agents`, `📘 Instructions`, `🧩 Skills`, and `📦 Other Resources` are optional detail by default" - in internal_contract_text - ) - assert "accepts number-only replies" in internal_contract_text - - assert "offer it as optional follow-up detail" in sync_contract_text - assert "accepts number-only replies" in sync_contract_text - - -def test_completion_report_requires_mandatory_applicable_evidence_for_shipped() -> None: - completion_reference = read_text( - ".github/skills/internal-gateway-execute-plans/references/status-file.md" - ) - - assert ( - "Use this reference when `internal-gateway-execute-plans` finishes, pauses, or" - in completion_reference - ) - assert "## Resume Notes" in completion_reference diff --git a/tests/test_critical_master_scripts.py b/tests/test_critical_master_scripts.py deleted file mode 100644 index c897a8c..0000000 --- a/tests/test_critical_master_scripts.py +++ /dev/null @@ -1,125 +0,0 @@ -from __future__ import annotations - -import argparse -import json -from pathlib import Path - -import validate_critical_output -from lib.critical_master import ( - ALLOWED_CLAIM_CLASSES, - ALLOWED_OUTCOMES, - classify_claim_class, - count_words, - extract_outcome_value, - parse_findings, - parse_markdown_sections, - validate_outcome_value, -) - -FIXTURES_DIR = Path(__file__).parent / "fixtures" -GOOD_FIXTURE = FIXTURES_DIR / "critical_output_good.md" -BAD_FIXTURE = FIXTURES_DIR / "critical_output_bad.md" - - -def test_count_words_excludes_code_fences() -> None: - body = "alpha beta\n```\nignore this code block\n```\ngamma delta" - assert count_words(body) == 4 - - -def test_count_words_handles_empty_string() -> None: - assert count_words("") == 0 - - -def test_parse_markdown_sections_returns_only_h2_headings() -> None: - text = "## Summary\nbody 1\n## Findings\n### 1. x\nbody 2\n## Outcome\nbody 3" - sections = parse_markdown_sections(text) - assert list(sections.keys()) == ["Summary", "Findings", "Outcome"] - assert "### 1. x" in sections["Findings"] - - -def test_validate_outcome_value_accepts_only_allowed_set() -> None: - for value in ALLOWED_OUTCOMES: - assert validate_outcome_value(value) is True - assert validate_outcome_value("defer-forever") is False - assert validate_outcome_value("ACCEPT-WITH-RISK") is False - - -def test_extract_outcome_value_picks_backtick_value() -> None: - assert extract_outcome_value("`accept-with-risk`") == "accept-with-risk" - assert ( - extract_outcome_value("`continue-critical` is the result.") - == "continue-critical" - ) - assert extract_outcome_value("no backticks here") is None - - -def test_classify_claim_class_reads_backticked_class() -> None: - body = "- **Impact:** x\n- **Evidence:** `inference` — note\n- **Mitigation:** y" - assert classify_claim_class(body) == "inference" - assert ALLOWED_CLAIM_CLASSES == {"confirmed", "inference", "estimate"} - - -def test_parse_findings_extracts_claim_class_per_finding() -> None: - body = ( - "### 1. first\n" - "- **Impact:** x\n" - "- **Evidence:** `confirmed` — repo evidence\n" - "- **Mitigation:** y\n" - "\n" - "### 2. second\n" - "- **Impact:** a\n" - "- **Evidence:** `estimate` — guess\n" - "- **Mitigation:** b\n" - ) - parsed = parse_findings(body) - assert len(parsed) == 2 - assert parsed[0].evidence_class == "confirmed" - assert parsed[1].evidence_class == "estimate" - - -def test_validate_critical_output_main_passes_good_fixture(monkeypatch, capsys) -> None: - monkeypatch.setattr( - validate_critical_output, - "parse_args", - lambda: argparse.Namespace( - file=str(GOOD_FIXTURE), format="text", max_words=600, strict=False - ), - ) - exit_code = validate_critical_output.main() - assert exit_code == 0 - assert "passes the contract" in capsys.readouterr().out - - -def test_validate_critical_output_main_reports_blocking_for_bad_fixture( - monkeypatch, capsys -) -> None: - monkeypatch.setattr( - validate_critical_output, - "parse_args", - lambda: argparse.Namespace( - file=str(BAD_FIXTURE), format="text", max_words=600, strict=False - ), - ) - exit_code = validate_critical_output.main() - assert exit_code == 1 - out = capsys.readouterr().out - assert "missing-section-synthesis" in out - assert "finding-count-out-of-range" in out - assert "invalid-outcome-value" in out - assert out.count("BLOCKING") >= 3 - - -def test_validate_critical_output_main_strict_passes_on_good( - monkeypatch, capsys -) -> None: - monkeypatch.setattr( - validate_critical_output, - "parse_args", - lambda: argparse.Namespace( - file=str(GOOD_FIXTURE), format="compact", max_words=600, strict=True - ), - ) - exit_code = validate_critical_output.main() - payload = json.loads(capsys.readouterr().out) - assert exit_code == 0 - assert payload["status"] == "ok" diff --git a/tests/test_external_refresh_workspace_guard.py b/tests/test_external_refresh_workspace_guard.py deleted file mode 100644 index 8930817..0000000 --- a/tests/test_external_refresh_workspace_guard.py +++ /dev/null @@ -1,68 +0,0 @@ -from __future__ import annotations - -import importlib.util -import sys -from pathlib import Path - - -def load_module(): - script_path = Path( - ".github/skills/local-agent-sync-external-resources/scripts/check_external_refresh_workspace.py" - ) - spec = importlib.util.spec_from_file_location( - "check_external_refresh_workspace", script_path - ) - module = importlib.util.module_from_spec(spec) - assert spec is not None - assert spec.loader is not None - sys.modules[spec.name] = module - spec.loader.exec_module(module) - return module - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def test_missing_graphifyignore_patterns_are_reported(tmp_path: Path) -> None: - module = load_module() - write_file(tmp_path / ".graphifyignore", "/graphify-out/\n") - - missing = module.missing_graphifyignore_patterns(tmp_path) - - assert "tmp/" in missing - assert "/tmp/" in missing - assert "tmp/external-refresh/" in missing - assert "**/.git/" in missing - - -def test_repo_local_workspace_is_rejected(tmp_path: Path) -> None: - module = load_module() - workspace = tmp_path / "tmp/external-refresh" - workspace.mkdir(parents=True) - - findings = module.validate_workspace(tmp_path, workspace) - - assert findings == [ - "External refresh workspace must be outside the repository: tmp/external-refresh" - ] - - -def test_external_workspace_is_accepted(tmp_path: Path) -> None: - module = load_module() - workspace = tmp_path.parent / "cloud-strategy-github-external-refresh" - workspace.mkdir(exist_ok=True) - - findings = module.validate_workspace(tmp_path, workspace) - - assert findings == [] - - -def test_repo_local_refresh_leftovers_are_reported(tmp_path: Path) -> None: - module = load_module() - (tmp_path / "tmp/external-refresh/awesome-copilot").mkdir(parents=True) - - findings = module.find_repo_local_refresh_dirs(tmp_path) - - assert findings == ["tmp/external-refresh"] diff --git a/tests/test_home_ai_resources_sync.py b/tests/test_home_ai_resources_sync.py deleted file mode 100644 index c38d06a..0000000 --- a/tests/test_home_ai_resources_sync.py +++ /dev/null @@ -1,1089 +0,0 @@ -from __future__ import annotations - -import argparse -import json -import os -import subprocess -from pathlib import Path - -import sync_home_ai_resources - - -def sync_cli_module(): - return getattr(sync_home_ai_resources, "SKILL_CLI", sync_home_ai_resources) - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def set_tree_mtime(root: Path, timestamp: float) -> None: - for path in sorted(root.rglob("*")): - os.utime(path, (timestamp, timestamp)) - os.utime(root, (timestamp, timestamp)) - - -def initialize_source_repo(root: Path) -> None: - write_file(root / "AGENTS.md", "# AGENTS\n") - write_file( - root / ".github/skills/demo-skill/SKILL.md", - "---\n" - "name: demo-skill\n" - "description: Use when a demo home-sync skill is needed.\n" - "---\n\n" - "# Demo Skill\n\n" - "## When to use\n\n" - "- Use when a demo home-sync skill is needed.\n", - ) - write_file( - root / ".github/skills/demo-skill/agents/openai.yaml", - "interface:\n" - ' display_name: "Demo Skill"\n' - ' short_description: "Portable demo skill bundle"\n' - ' default_prompt: "Use $demo-skill for demo work."\n', - ) - write_file( - root - / ".github/skills/local-agent-sync-install-ai-resources/references/runtime-support-matrix.yaml", - "version: 1\n" - "rows:\n" - " - target: skills\n" - " resource_family: skills\n" - " support_level: Documented\n" - " home_path: ~/.agents/skills//\n" - " direct_copy_possible: true\n" - " translation_required: false\n" - " include_in_v1: true\n" - " evidence: []\n" - " notes: Shared skill support.\n" - " - target: codex\n" - " resource_family: skills\n" - " support_level: Documented\n" - " home_path: ~/.codex/skills//\n" - " direct_copy_possible: true\n" - " translation_required: false\n" - " include_in_v1: true\n" - " evidence: []\n" - " notes: Codex direct-copy skill support.\n" - " - target: opencode\n" - " resource_family: skills\n" - " support_level: User-provided / To verify\n" - " home_path: ~/.config/opencode/skills//\n" - " direct_copy_possible: true\n" - " translation_required: false\n" - " include_in_v1: false\n" - " evidence: []\n" - " notes: Undocumented support.\n", - ) - write_file( - root - / ".github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml", - "version: 1\n" - "defaults:\n" - " include_internal_skills: false\n" - " include_local_skills: false\n" - " include_unlisted_skills: false\n" - "resources:\n" - " - resource_id: demo-skill\n" - " source_family: skills\n" - " source_path: .github/skills/demo-skill\n" - " include_targets:\n" - " - codex\n" - " - opencode\n" - " target_support: Documented\n" - " notes: Demo bundle.\n", - ) - - -def test_main_plan_emits_json_for_selected_targets( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="plan", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=False, - prune_managed=False, - experimental_targets=False, - format="json", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 0 - assert payload["mode"] == "plan" - assert payload["selected_targets"] == ["skills"] - assert str(home_root / ".agents/skills") in payload["missing_dirs"] - assert str(home_root / ".codex/agents") not in payload["missing_dirs"] - - -def test_main_plan_emits_compact_projection_for_selected_targets( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="plan", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=False, - prune_managed=False, - experimental_targets=False, - format="compact", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 0 - assert payload["mode"] == "plan" - assert "status" in payload - assert payload["selected_targets_count"] == 1 - assert payload["retired_targets_count"] == 0 - assert payload["source_resources_considered"] >= 1 - assert "operations" not in payload - assert "copied" not in payload - assert "skipped" not in payload - assert "blocked" not in payload - assert "conflicts" not in payload - assert "missing_dirs" not in payload - assert "state_path" in payload - assert "manifest_path" not in payload - - -def test_main_apply_blocks_docs_unverified_targets( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="apply", - source_root=str(source_root), - home_root=str(home_root), - targets="opencode", - retire_targets="", - create_missing_dirs=False, - prune_managed=False, - experimental_targets=False, - format="json", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 1 - assert payload["mode"] == "apply" - assert payload["blocked_codes"] == ["docs-unverified"] - - -def test_bisync_plan_json_output(monkeypatch, tmp_path: Path, capsys) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - source_root.mkdir() - home_root.mkdir() - write_file(source_root / ".gitkeep", "") - (source_root / ".github" / "skills").mkdir(parents=True, exist_ok=True) - (home_root / ".agents" / "skills").mkdir(parents=True, exist_ok=True) - subprocess.run( - ["git", "init", "-b", "main"], - cwd=source_root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "config", "user.email", "test@example.com"], - cwd=source_root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "config", "user.name", "Test"], - cwd=source_root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "add", "-A"], - cwd=source_root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "commit", "-m", "initial"], - cwd=source_root, - text=True, - capture_output=True, - check=True, - ) - - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="bisync", - bisync_command="plan", - source_root=str(source_root), - home_root=str(home_root), - format="json", - ), - ) - - sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert "drifts" in payload - assert "next_action" in payload - assert "blocked_codes" in payload - assert "action" in payload["next_action"] - - -def test_bisync_plan_emits_compact_projection( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - source_root.mkdir() - home_root.mkdir() - write_file(source_root / ".github/skills/repo-only/SKILL.md", "# Repo only\n") - write_file(home_root / ".agents/skills/home-only/SKILL.md", "# Home only\n") - (source_root / ".github" / "skills").mkdir(parents=True, exist_ok=True) - (home_root / ".agents" / "skills").mkdir(parents=True, exist_ok=True) - subprocess.run( - ["git", "init", "-b", "main"], - cwd=source_root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "config", "user.email", "test@example.com"], - cwd=source_root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "config", "user.name", "Test"], - cwd=source_root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "add", "-A"], - cwd=source_root, - text=True, - capture_output=True, - check=True, - ) - subprocess.run( - ["git", "commit", "-m", "initial"], - cwd=source_root, - text=True, - capture_output=True, - check=True, - ) - - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="bisync", - bisync_command="plan", - source_root=str(source_root), - home_root=str(home_root), - format="compact", - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 1 - assert payload["mode"] == "plan" - assert "status" in payload - assert payload["drift_total"] == 2 - assert payload["direction_counts"] == {"repo_to_home": 1, "home_to_repo": 0} - assert payload["bucket_counts"] == { - "only_repo": 1, - "only_home": 1, - "equal_mtime": 0, - } - assert payload["changed_resources"] == [ - { - "blocked_codes": ["bisync-only-home"], - "skill": "home-only", - "type": "only-home", - }, - { - "direction": "repo-to-home", - "skill": "repo-only", - "type": "only-repo", - }, - ] - assert "drifts" not in payload - assert "state_path" not in payload - - -def test_bisync_apply_returns_nonzero_on_blockers( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - source_root.mkdir() - home_root.mkdir() - (source_root / ".github" / "skills").mkdir(parents=True, exist_ok=True) - (home_root / ".agents" / "skills").mkdir(parents=True, exist_ok=True) - - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="bisync", - bisync_command="apply", - source_root=str(source_root), - home_root=str(home_root), - format="json", - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 1 - assert payload["next_action"]["allowed"] is False - - -def test_plan_next_action_present(monkeypatch, tmp_path: Path, capsys) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="plan", - source_root=str(source_root), - home_root=str(home_root), - targets="codex", - retire_targets="", - create_missing_dirs=False, - prune_managed=False, - experimental_targets=False, - format="json", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 0 - assert "next_action" in payload - assert "next_step" in payload - assert "action" in payload["next_action"] - assert "allowed" in payload["next_action"] - assert "requires_explicit_approval" in payload["next_action"] - - -def test_main_plan_blocks_overlap_between_active_and_retired_targets( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="plan", - source_root=str(source_root), - home_root=str(home_root), - targets="codex,opencode", - retire_targets="opencode", - create_missing_dirs=False, - prune_managed=False, - experimental_targets=False, - format="json", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 1 - assert payload["blocked_codes"] == ["retire-target-overlap"] - assert payload["retired_targets"] == ["opencode"] - - -def test_sync_auto_applies_clean_repo_to_home_install( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="sync", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=True, - prune_managed=False, - experimental_targets=False, - format="json", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 0 - assert payload["mode"] == "sync" - assert payload["status"] == "done" - assert payload["install"]["manifest_path"] - assert payload["bisync"]["drifts"] == [] - assert (home_root / ".agents/skills/demo-skill/SKILL.md").is_file() - - -def test_sync_stops_for_bisync_drift_after_clean_install( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - write_file(home_root / ".agents/skills/home-only/SKILL.md", "# Home only\n") - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="sync", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=True, - prune_managed=False, - experimental_targets=False, - format="json", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 1 - assert payload["mode"] == "sync" - assert payload["status"] == "needs_review" - assert payload["bisync"]["blocked_codes"] == ["bisync-only-home"] - assert payload["bisync"]["drifts"][0]["skill"] == "home-only" - - -def test_sync_allows_safe_repo_only_bisync_after_clean_install( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - write_file(source_root / ".github/skills/repo-only-extra/SKILL.md", "# Repo only\n") - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="sync", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=True, - prune_managed=False, - experimental_targets=False, - format="json", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 0 - assert payload["mode"] == "sync" - assert payload["status"] == "done" - assert payload["bisync"]["blocked_codes"] == ["bisync-only-repo"] - assert payload["bisync"]["drifts"] == [ - { - "direction": "repo-to-home", - "home": str(home_root / ".agents/skills/repo-only-extra"), - "repo": str(source_root / ".github/skills/repo-only-extra"), - "skill": "repo-only-extra", - "type": "only-repo", - } - ] - - -def test_sync_stops_on_home_newer_managed_drift_via_bisync_review( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - cli_module = sync_cli_module() - initial_plan = cli_module.build_home_sync_plan( - source_root=source_root, - home_root=home_root, - targets=cli_module.parse_targets("skills"), - mode="apply", - ) - cli_module.apply_home_sync_plan( - initial_plan, - create_missing_dirs=True, - ) - - source_skill = source_root / ".github/skills/demo-skill" - home_skill = home_root / ".agents/skills/demo-skill" - write_file(home_skill / "SKILL.md", "# Home edited\n") - set_tree_mtime(source_skill, 100.0) - set_tree_mtime(home_skill, 200.0) - - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="sync", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=False, - prune_managed=False, - experimental_targets=False, - format="json", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 1 - assert payload["mode"] == "sync" - assert payload["status"] == "needs_review" - assert payload["install"]["blocked_codes"] == [] - assert any( - operation["action"] == "warning" - and operation["code"] == "target-modified-managed" - for operation in payload["install"]["operations"] - ) - assert any( - drift["direction"] == "home-to-repo" and drift["skill"] == "demo-skill" - for drift in payload["bisync"]["drifts"] - ) - assert (home_skill / "SKILL.md").read_text(encoding="utf-8") == "# Home edited\n" - - -def test_skill_bundle_default_targets_focus_on_shared_skills( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="plan", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=False, - prune_managed=False, - experimental_targets=False, - format="report", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - output = capsys.readouterr().out - - assert exit_code == 0 - assert "Targets: skills" in output - assert ".agents/skills" in output - assert ".codex/agents" not in output - - -def test_skill_runbook_distinguishes_install_and_bisync_lanes() -> None: - skill_path = ( - Path(__file__).resolve().parent - / "../.github/skills/local-agent-sync-install-ai-resources/SKILL.md" - ).resolve() - content = skill_path.read_text(encoding="utf-8") - - assert "## Deterministic Operator Protocol" in content - assert "Install sync is unidirectional: repo -> home only." in content - assert "default `skills` target" in content - assert "python3 ./.github/scripts/sync_home_ai_resources.py" in content - assert "--format report" in content - assert ( - "The `bisync` lane provides explicit bidirectional synchronization" in content - ) - assert "Do not infer the mode, do not skip blockers" in content - assert "next_action` as user approval for `apply`" in content - assert "## Reporting Contract" in content - assert "summary-first" in content - assert "Auto-run safe repo-to-home install" in content - assert "repo wins" in content - assert "local-*" in content - assert "Do not run `bisync apply` automatically" in content - assert "remove it manually so sync can restore the source-of-truth version" not in ( - content.lower() - ) - assert "routine recovery step" in content - assert "non-thinking" not in content.lower() - - -def test_sync_contract_documents_verified_bisync_reconciliation() -> None: - contract_path = ( - Path(__file__).resolve().parent - / "../.github/skills/local-agent-sync-install-ai-resources/references/sync-contract.md" - ).resolve() - error_codes_path = ( - Path(__file__).resolve().parent - / "../.github/skills/local-agent-sync-install-ai-resources/references/error-codes.md" - ).resolve() - contract_content = contract_path.read_text(encoding="utf-8") - error_codes_content = error_codes_path.read_text(encoding="utf-8") - - assert "stale `target-modified-managed` blocker" in contract_content - assert "bisync-manifest-reconcile-failed" in contract_content - assert "Text reports must use a summary-first layout" in contract_content - assert "### Sync, Plan, Audit, And Bisync Plan Report" in contract_content - assert "Auto-applied" in contract_content - assert "Planned repo-to-home copies" in contract_content - assert "The `sync` command is the only auto-execute mode." in contract_content - assert ( - "| Resource or path | Lane | Planned action | Why this will change | Evidence or winner |" - in contract_content - ) - assert ( - "| Resource or path | Action performed | Why it was done | Result | Verification |" - in contract_content - ) - assert "bisync-manifest-reconcile-failed" in error_codes_content - assert "combining the `Meaning` and `Rationale` columns" in error_codes_content - assert "remove it manually so sync can restore the source-of-truth version" not in ( - error_codes_content.lower() - ) - - -def test_sync_report_output_has_compact_chat_sections( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - write_file(home_root / ".agents/skills/home-only/SKILL.md", "# Home only\n") - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="sync", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=True, - prune_managed=False, - experimental_targets=False, - format="report", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - output = capsys.readouterr().out - - assert exit_code == 1 - assert "Status: sync | status=needs_review" in output - assert "targets=skills" in output - assert "next_action=review_bisync" in output - assert "## Summary" in output - assert "## Auto-applied" in output - assert "## Stopped on" in output - assert "## Validation" in output - assert "## Next" in output - assert "# Install Lane" not in output - assert "# Bisync Lane" not in output - - -def test_sync_report_uses_planned_heading_when_install_review_stops_before_writes( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - write_file(source_root / ".github/skills/extra-skill/SKILL.md", "# Extra\n") - write_file( - source_root - / ".github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml", - "version: 1\n" - "defaults:\n" - " include_internal_skills: false\n" - " include_local_skills: false\n" - " include_unlisted_skills: true\n" - "resources: []\n", - ) - write_file(home_root / ".agents/skills/demo-skill/SKILL.md", "# Local unmanaged\n") - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="sync", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=True, - prune_managed=False, - experimental_targets=False, - format="report", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - output = capsys.readouterr().out - - assert exit_code == 1 - assert "Status: sync | status=needs_review" in output - assert "next_action=review_install" in output - assert "## Planned repo-to-home copies" in output - assert "## Auto-applied" not in output - assert "Why this is planned" in output - assert "extra-skill" in output - assert "demo-skill" in output - - -def test_agent_and_skill_align_on_summary_first_reporting() -> None: - agent_path = ( - Path(__file__).resolve().parent - / "../.github/agents/local-sync-install-ai-resources.agent.md" - ).resolve() - skill_path = ( - Path(__file__).resolve().parent - / "../.github/skills/local-agent-sync-install-ai-resources/SKILL.md" - ).resolve() - agent_content = agent_path.read_text(encoding="utf-8") - skill_content = skill_path.read_text(encoding="utf-8") - - assert "table-first report layout" not in agent_content - assert "why each blocker matters" not in agent_content - assert "summary-first" in skill_content - - -def test_agent_and_skill_align_on_default_sync_sequence() -> None: - agent_path = ( - Path(__file__).resolve().parent - / "../.github/agents/local-sync-install-ai-resources.agent.md" - ).resolve() - skill_path = ( - Path(__file__).resolve().parent - / "../.github/skills/local-agent-sync-install-ai-resources/SKILL.md" - ).resolve() - agent_content = agent_path.read_text(encoding="utf-8") - skill_content = skill_path.read_text(encoding="utf-8") - - expected = "run install `plan` first for the default `skills` target" - assert expected not in agent_content - assert "Run `sync` for the default `skills` target." in skill_content - - -def test_main_plan_compact_output_preserves_changed_resource_evidence( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="plan", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=False, - prune_managed=False, - experimental_targets=False, - format="compact", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - payload = json.loads(capsys.readouterr().out) - - assert exit_code == 0 - assert payload["changed_resources"] - assert any(item["action"] == "mkdir" for item in payload["changed_resources"]) - - -def test_main_plan_report_output_has_stable_summary_sections( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="plan", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - retire_targets="", - create_missing_dirs=False, - prune_managed=False, - experimental_targets=False, - format="report", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - output = capsys.readouterr().out - - assert exit_code == 0 - assert "## Summary" in output - assert "repo-to-home install" in output - assert "targets=skills" in output - assert "next_action=apply" in output - assert "## Changes" in output - assert "## Attention" in output - assert "## Validation" in output - assert "## Next" in output - assert "## Current State" not in output - - -def test_bisync_plan_report_output_has_stable_summary_sections( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - source_root.mkdir() - home_root.mkdir() - write_file(source_root / ".github/skills/repo-only/SKILL.md", "# Repo only\n") - write_file(home_root / ".agents/skills/home-only/SKILL.md", "# Home only\n") - - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="bisync", - bisync_command="plan", - source_root=str(source_root), - home_root=str(home_root), - format="report", - ), - ) - - exit_code = sync_home_ai_resources.main() - output = capsys.readouterr().out - - assert exit_code == 1 - assert "## Summary" in output - assert "repo-home drift" in output - assert "next_action=resolve_blockers" in output - assert "## Changes" in output - assert "## Attention" in output - assert "## Validation" in output - assert "## Remaining Work" in output - assert "## Next" in output - assert "## Current State" not in output - - -def test_install_report_bounds_large_change_tables() -> None: - cli = sync_cli_module() - operations = [ - { - "action": "copy", - "resource_id": f"skill-{index:02d}", - "path": f"/tmp/home/.agents/skills/skill-{index:02d}", - "reason": "Repository copy is newer than home copy.", - "target": "skills", - } - for index in range(25) - ] - payload = { - "mode": "plan", - "selected_targets": ["skills"], - "retired_targets": [], - "source_resources_considered": 25, - "operations": operations, - "blocked_codes": [], - "validation": "ready", - "next_action": { - "action": "apply", - "allowed": True, - "requires_explicit_approval": True, - "command": "apply --targets skills", - "reason": "Plan is ready.", - }, - "next_step": "Run apply when ready.", - } - - output = cli.render_install_report(payload) - - assert "skill-00" in output - assert "skill-19" in output - assert "skill-20" not in output - assert ( - "5 additional change rows omitted; use --format json for full detail." in output - ) - - -def test_doctor_report_output_has_readiness_sections( - monkeypatch, tmp_path: Path, capsys -) -> None: - source_root = tmp_path / "source" - home_root = tmp_path / "home" - initialize_source_repo(source_root) - monkeypatch.setattr( - sync_home_ai_resources, - "parse_args", - lambda _=None: argparse.Namespace( - command="doctor", - source_root=str(source_root), - home_root=str(home_root), - targets="skills", - create_missing_dirs=False, - prune_managed=False, - experimental_targets=False, - format="report", - fast=False, - changed_only=False, - ), - ) - - exit_code = sync_home_ai_resources.main() - output = capsys.readouterr().out - - assert exit_code == 0 - assert "Status: repo-to-home install | mode=doctor | targets=skills" in output - assert "next_action=resolve_blockers" in output - assert "## Readiness" in output - assert "target-root:skills" in output - assert "needs-directory-create" in output - assert "## Next" in output - - -def test_report_tables_escape_pipe_and_newline_cells() -> None: - cli = sync_cli_module() - payload = { - "mode": "plan", - "selected_targets": ["skills"], - "retired_targets": [], - "source_resources_considered": 1, - "operations": [ - { - "action": "blocked", - "resource_id": "broken|skill", - "path": "/tmp/home/.agents/skills/broken|skill", - "reason": "First line\nSecond | part", - "code": "target-exists-unmanaged", - "target": "skills", - } - ], - "blocked_codes": ["target-exists-unmanaged"], - "validation": "blocked", - "next_action": { - "action": "resolve_blockers", - "allowed": False, - "requires_explicit_approval": True, - "command": "none", - "reason": "Resolve blockers.", - }, - "next_step": "Resolve blockers.", - } - - output = cli.render_install_report(payload) - - assert "broken\\|skill" in output - assert "First line Second \\| part" in output - - -def test_cli_defaults_to_report_output_for_user_facing_modes() -> None: - cli = sync_cli_module() - - assert cli.parse_args(["plan"]).format == "report" - assert cli.parse_args(["doctor"]).format == "report" - assert cli.parse_args(["bisync", "plan"]).format == "report" - - -def test_invalid_target_report_has_clear_blocked_next_action(capsys) -> None: - cli = sync_cli_module() - args = cli.parse_args(["plan", "--targets", "nope"]) - - exit_code = cli.run(args) - output = capsys.readouterr().out - - assert exit_code == 1 - assert ( - "Status: repo-to-home install | mode=plan | targets=none | status=blocked" - in output - ) - assert "next_action=resolve_blockers" in output - assert "unknown-target" in output diff --git a/tests/test_imported_asset_overrides_contract.py b/tests/test_imported_asset_overrides_contract.py deleted file mode 100644 index 6c40576..0000000 --- a/tests/test_imported_asset_overrides_contract.py +++ /dev/null @@ -1,112 +0,0 @@ -from __future__ import annotations - -import sys -from pathlib import Path - -import yaml - -sys.path.insert(0, str((Path(".") / ".github/scripts").resolve())) - -from lib.fingerprinting import build_fingerprint # noqa: E402 - -REGISTRY_PATH = Path( - ".github/skills/local-agent-sync-external-resources/references/imported-asset-overrides.yaml" -) -SKILL_ROOT = REGISTRY_PATH.parent.parent - - -def load_registry() -> dict[str, object]: - return yaml.safe_load(REGISTRY_PATH.read_text(encoding="utf-8")) - - -def test_imported_asset_override_registry_tracks_expected_imported_targets() -> None: - payload = load_registry() - overrides = payload["overrides"] - targets = {entry["target_path"] for entry in overrides} - - assert payload["policy"]["default_rule"] == "keep imported upstream assets verbatim" - assert targets == { - ".github/skills/grill-me/SKILL.md", - ".github/skills/openai-spreadsheet/SKILL.md", - } - assert all( - entry["approval"] == "explicit-user-counter-validated" for entry in overrides - ) - assert all(entry["lifecycle_mode"] == "post-refresh-patch" for entry in overrides) - assert all(entry["apply_strategy"] == "git-apply-3way" for entry in overrides) - assert all((SKILL_ROOT / entry["patch_path"]).is_file() for entry in overrides) - - -def test_imported_asset_override_registry_hashes_match_live_targets() -> None: - payload = load_registry() - repo_root = Path(".").resolve() - - for entry in payload["overrides"]: - target_path = repo_root / entry["target_path"] - assert ( - build_fingerprint(repo_root, target_path).content_hash - == entry["expected_content_hash"] - ) - - -def test_imported_asset_override_policy_is_visible_in_canonical_and_sync_assets() -> ( - None -): - agents_text = Path("AGENTS.md").read_text(encoding="utf-8") - copilot_text = Path(".github/copilot-instructions.md").read_text(encoding="utf-8") - sync_agent_text = Path( - ".github/agents/local-sync-external-resources.agent.md" - ).read_text(encoding="utf-8") - sync_skill_text = Path( - ".github/skills/local-agent-sync-external-resources/SKILL.md" - ).read_text(encoding="utf-8") - target_sync_agent_text = Path( - ".github/agents/local-sync-global-copilot-configs-into-repo.agent.md" - ).read_text(encoding="utf-8") - target_sync_skill_text = Path( - ".github/skills/local-agent-sync-global-copilot-configs-into-repo/SKILL.md" - ).read_text(encoding="utf-8") - - assert ( - "Source-side sync command centers and sync support skills own propagation behavior." - in agents_text - ) - assert "Keep consumer-facing defaults target-agnostic." in agents_text - assert "Do not treat this file as instructions for coding agents" in copilot_text - assert ( - "Do not edit imported upstream assets in place unless the need is strong" - not in copilot_text - ) - assert "Every approved imported in-place override must be mapped" in sync_agent_text - assert ( - "Allow a direct in-place override only for a strong repo-specific need" - in sync_agent_text - ) - assert "scripts/apply_imported_asset_overrides.py" in sync_skill_text - assert ( - "approved imported-asset override registries or replay patches" - in target_sync_agent_text - ) - assert ( - "approved imported-asset override registry plus replay patches" - in target_sync_skill_text - ) - - -def test_external_refresh_skill_points_to_execution_guardrails() -> None: - skill = Path( - ".github/skills/local-agent-sync-external-resources/SKILL.md" - ).read_text(encoding="utf-8") - - assert "references/refresh-execution-guardrails.md" in skill - assert "check_external_refresh_workspace.py" in skill - - -def test_external_refresh_agent_reports_workspace_and_graphify_guard() -> None: - agent = Path(".github/agents/local-sync-external-resources.agent.md").read_text( - encoding="utf-8" - ) - - assert "Workspace guard" in agent - assert "Graphify guard" in agent - assert "Scoped validation" in agent diff --git a/tests/test_internal_agent_creator_skill_contract.py b/tests/test_internal_agent_creator_skill_contract.py deleted file mode 100644 index 19acafc..0000000 --- a/tests/test_internal_agent_creator_skill_contract.py +++ /dev/null @@ -1,89 +0,0 @@ -from __future__ import annotations - -import json -import subprocess -import sys -from pathlib import Path - -SKILL_DIR = Path(".github/skills/internal-agent-creator") - - -def read_text(relative_path: str) -> str: - return Path(relative_path).read_text(encoding="utf-8") - - -def run_json_script(*args: str) -> dict[str, object]: - result = subprocess.run( - [sys.executable, *args], - check=True, - capture_output=True, - text=True, - ) - return json.loads(result.stdout) - - -def test_internal_agent_creator_uses_single_core_skill_contract() -> None: - skill_text = read_text(".github/skills/internal-agent-creator/SKILL.md") - contract_text = read_text( - ".github/skills/internal-agent-creator/references/agent-contract.md" - ) - template_text = read_text( - ".github/skills/internal-agent-creator/references/agent-template.md" - ) - - assert "## Referenced skills" in skill_text - assert "## Core Skill" in skill_text - assert "zero skill references or one explicit core skill" in skill_text - assert ( - "one existing repo-owned skill for its required operating logic" - in contract_text - ) - assert "must list exactly one canonical skill identifier" in contract_text - assert "## Core Skill" in template_text - assert "## Optional Support Skills\n\n-" not in template_text - assert "## Mandatory Engine Skills\n\n-" not in template_text - - -def test_internal_agent_creator_has_official_source_map_with_openai_guidance() -> None: - source_map = read_text( - ".github/skills/internal-agent-creator/references/official-source-map.md" - ) - - assert ( - "https://docs.github.com/en/copilot/reference/custom-agents-configuration" - in source_map - ) - assert "https://code.visualstudio.com/docs/copilot/agents/subagents" in source_map - assert "https://agentskills.io/specification" not in source_map - assert "skill-creation" not in source_map - assert ( - "https://developers.openai.com/api/docs/guides/prompt-engineering#coding" - in source_map - ) - assert ( - "https://developers.openai.com/codex/learn/best-practices#improve-reliability-with-testing-and-review" - in source_map - ) - - -def test_internal_agent_creator_scripts_report_agent_and_token_shape() -> None: - audit = run_json_script( - ".github/skills/internal-agent-creator/scripts/audit_agent_contract.py", - "--root", - ".", - "--format", - "json", - ) - token_audit = run_json_script( - ".github/skills/internal-agent-creator/scripts/measure_skill_bundle_tokens.py", - "--skill-dir", - ".github/skills/internal-agent-creator", - "--format", - "json", - ) - - assert audit["summary"]["agent_count"] >= 5 - assert audit["summary"]["legacy_agent_count"] == 0 - assert token_audit["totals"]["loaded_docs"] < 4500 - assert token_audit["totals"]["script_code"] > 0 - assert token_audit["summary"]["finding_count"] == 0 diff --git a/tests/test_inventory_and_consistency.py b/tests/test_inventory_and_consistency.py deleted file mode 100644 index ce987f4..0000000 --- a/tests/test_inventory_and_consistency.py +++ /dev/null @@ -1,457 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from lib.catalog_checks import check_superpowers_import_naming, run_consistency_checks -from lib.inventory import build_inventory_markdown - -LEGACY_INSTRUCTION_DIR = ".github/" + "instructions" - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def test_build_inventory_markdown_lists_catalog_sections(tmp_path: Path) -> None: - write_file(tmp_path / "AGENTS.md", "# AGENTS\n") - write_file( - tmp_path / ".github/agents/internal-gateway-simple-task.agent.md", - "---\nname: internal-gateway-simple-task\ntools: [read]\n---\n", - ) - write_file( - tmp_path / ".github/skills/internal-catalog/SKILL.md", - "---\nname: internal-catalog\ndescription: Catalog helper\n---\n", - ) - write_file( - tmp_path / ".github/prompts/internal-agent-plan-next-step.prompt.md", - "---\ndescription: Planning next step\n---\n", - ) - - inventory = build_inventory_markdown(tmp_path) - - assert "## Instructions" in inventory - assert "## Prompts" in inventory - assert "No instruction files currently ship in the live catalog." in inventory - assert "- `.github/skills/internal-catalog/SKILL.md`" in inventory - assert "- `.github/agents/internal-gateway-simple-task.agent.md`" in inventory - assert "- `.github/prompts/internal-agent-plan-next-step.prompt.md`" in inventory - - -def test_build_inventory_markdown_lists_source_instruction_entries( - tmp_path: Path, -) -> None: - write_file( - tmp_path / LEGACY_INSTRUCTION_DIR / "internal-python.instructions.md", - "---\ndescription: Python\napplyTo: '**/*.py'\nexcludeAgent: cloud-agent\n---\n\n" - "This file is optimized for Copilot code review and should produce only evidenced findings on matching changed files.\n", - ) - - inventory = build_inventory_markdown(tmp_path) - - assert "No instruction files currently ship in the live catalog." not in inventory - assert "- `.github/instructions/internal-python.instructions.md`" in inventory - - -def test_build_inventory_markdown_lists_source_managed_instructions( - tmp_path: Path, -) -> None: - write_file(tmp_path / "AGENTS.md", "# AGENTS\n") - write_file( - tmp_path / LEGACY_INSTRUCTION_DIR / "internal-python.instructions.md", - "---\n" - "description: Python review checks\n" - "applyTo: '**/*.py'\n" - "excludeAgent: cloud-agent\n" - "---\n\n" - "This file is optimized for Copilot code review and should produce only evidenced findings on matching changed files.\n", - ) - - inventory = build_inventory_markdown(tmp_path) - - assert "No instruction files currently ship in the live catalog." not in inventory - assert "- `.github/instructions/internal-python.instructions.md`" in inventory - - -def test_run_consistency_checks_flags_prompt_inventory_drift(tmp_path: Path) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md` and `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/prompts/internal-agent-review-next-actions.prompt.md", - "---\ndescription: Review next actions\n---\n", - ) - write_file( - tmp_path / ".github/INVENTORY.md", - "# Copilot Inventory\n\n" - "## Instructions\n\nNo instruction files currently ship in the live catalog.\n\n" - "## Skills\n\nNo skill files currently ship in the live catalog.\n\n" - "## Agents\n\nNo agent files currently ship in the live catalog.\n", - ) - findings = run_consistency_checks(tmp_path) - findings_by_path = {(finding.path, finding.code) for finding in findings} - - assert ( - ".github/prompts/internal-agent-review-next-actions.prompt.md", - "inventory-missing-entry", - ) in findings_by_path - - -def test_run_consistency_checks_flags_prompt_contract_gaps(tmp_path: Path) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md` and `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/prompts/internal-invalid.prompt.md", - "---\n" - "name: internal-mismatch\n" - "description: Broken prompt contract\n" - "---\n\n" - "No reusable inputs here.\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - findings_by_path = {(finding.path, finding.code) for finding in findings} - - assert ( - ".github/prompts/internal-invalid.prompt.md", - "prompt-name-mismatch", - ) in findings_by_path - assert ( - ".github/prompts/internal-invalid.prompt.md", - "prompt-missing-agent", - ) in findings_by_path - assert ( - ".github/prompts/internal-invalid.prompt.md", - "prompt-missing-input-placeholder", - ) in findings_by_path - - -def test_run_consistency_checks_flags_broken_prompt_local_links(tmp_path: Path) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md` and `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/prompts/internal-valid.prompt.md", - "---\n" - "name: internal-valid\n" - "agent: internal-gateway-review\n" - "description: Valid prompt metadata\n" - "---\n\n" - "Prompt target:\n" - "${input:subject:Describe the target.}\n\n" - "See [missing guide](../docs/missing.md).\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - findings_by_path = {(finding.path, finding.code) for finding in findings} - - assert ( - ".github/prompts/internal-valid.prompt.md", - "broken-local-link", - ) in findings_by_path - - -def test_run_consistency_checks_flags_generic_prompt_agent_owner( - tmp_path: Path, -) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md` and `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/prompts/internal-generic.prompt.md", - "---\n" - "name: internal-generic\n" - "agent: agent\n" - "description: Generic agent owner\n" - "---\n\n" - "${input:subject:Describe the target.}\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - findings_by_path = {(finding.path, finding.code) for finding in findings} - - assert ( - ".github/prompts/internal-generic.prompt.md", - "prompt-generic-agent", - ) in findings_by_path - - -def test_run_consistency_checks_flags_inventory_drift_and_missing_tools( - tmp_path: Path, -) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md` and `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/INVENTORY.md", - "# Copilot Inventory\n\n## Instructions\n\nNo instruction files currently ship in the live catalog.\n\n## Skills\n\nNo skill files currently ship in the live catalog.\n\n## Agents\n\nNo agent files currently ship in the live catalog.\n", - ) - write_file( - tmp_path / ".github/agents/internal-sync.agent.md", - "---\nname: internal-sync\n---\n\n# Internal Sync\n", - ) - - findings = run_consistency_checks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "inventory-missing-entry" in finding_codes - assert "internal-agent-missing-tools" in finding_codes - - -def test_run_consistency_checks_flags_empty_and_invalid_tools_contracts( - tmp_path: Path, -) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md`.\n", - ) - write_file( - tmp_path / ".github/agents/internal-empty.agent.md", - "---\nname: internal-empty\ntools: []\n---\n\n# Empty\n", - ) - write_file( - tmp_path / ".github/agents/internal-invalid.agent.md", - "---\nname: internal-invalid\ntools:\n - read\n - 1\n---\n\n# Invalid\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - findings_by_path = {(finding.path, finding.code) for finding in findings} - - assert ( - ".github/agents/internal-empty.agent.md", - "internal-agent-missing-tools", - ) in findings_by_path - assert ( - ".github/agents/internal-invalid.agent.md", - "internal-agent-invalid-tools", - ) in findings_by_path - - -def test_run_consistency_checks_flags_invalid_imported_asset_override_registry( - tmp_path: Path, -) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md`.\n", - ) - write_file( - tmp_path / ".github/skills/obra-demo/SKILL.md", - "---\nname: obra-demo\ndescription: Imported workflow.\n---\n", - ) - write_file( - tmp_path - / ".github/skills/local-agent-sync-external-resources/references/imported-asset-overrides.yaml", - "overrides:\n" - " - id: bad-entry\n" - " target_path: .github/skills/obra-demo/SKILL.md\n" - " lifecycle_mode: support-only\n" - " apply_strategy: ad-hoc\n" - " approval: pending\n" - " patch_path: patches/missing.patch\n" - " expected_content_hash: short\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "imported-asset-override-approval-missing" in finding_codes - assert "imported-asset-override-invalid-lifecycle" in finding_codes - assert "imported-asset-override-invalid-apply-strategy" in finding_codes - assert "imported-asset-override-patch-missing" in finding_codes - assert "imported-asset-override-invalid-hash" in finding_codes - - -def write_superpowers_normalization_reference(root: Path) -> None: - write_file( - root - / ".github/skills/local-agent-sync-external-resources/references/superpowers-normalization.yaml", - "version: 1\n" - "source_family: obra/superpowers\n" - "local_prefix: superpowers-\n" - "blocked_local_prefixes:\n" - " - obra-\n" - "blocked_managed_reference_prefix: 'superpowers:'\n" - "managed_skills:\n" - " - upstream: demo\n" - " legacy_local: obra-demo\n" - " local: superpowers-demo\n" - "managed_patches:\n" - " - legacy_path: patches/obra-demo.patch\n" - " path: patches/superpowers-demo.patch\n" - "live_scan:\n" - " include:\n" - " - .github/agents\n" - " - .github/skills\n" - " ignored_files:\n" - " - README.md\n" - " - CHANGELOG.md\n" - " - superpowers-normalization.yaml\n", - ) - - -def test_check_superpowers_import_naming_flags_legacy_drift(tmp_path: Path) -> None: - write_superpowers_normalization_reference(tmp_path) - write_file( - tmp_path / ".github/skills/obra-demo/SKILL.md", - "---\nname: obra-demo\ndescription: Demo.\n---\n\nUse superpowers:demo.\n", - ) - write_file( - tmp_path / ".github/agents/local-demo.agent.md", - "---\nname: local-demo\ntools: [read]\n---\n\n- `obra-demo`\n- `superpowers:demo`\n", - ) - write_file( - tmp_path - / ".github/skills/local-agent-sync-external-resources/patches/obra-demo.patch", - "diff --git a/.github/skills/obra-demo/SKILL.md b/.github/skills/obra-demo/SKILL.md\n", - ) - - findings = check_superpowers_import_naming(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "superpowers-import-legacy-skill-directory" in finding_codes - assert "superpowers-import-skill-name-mismatch" in finding_codes - assert "superpowers-import-legacy-reference" in finding_codes - assert "superpowers-import-upstream-reference" in finding_codes - - -def test_check_superpowers_import_naming_ignores_reference_legacy_map( - tmp_path: Path, -) -> None: - write_superpowers_normalization_reference(tmp_path) - write_file( - tmp_path / ".github/skills/superpowers-demo/SKILL.md", - "---\nname: superpowers-demo\ndescription: Demo.\n---\n", - ) - - findings = check_superpowers_import_naming(tmp_path) - - assert findings == [] - - -def test_run_consistency_checks_flags_legacy_repo_owned_agent_skill_headings( - tmp_path: Path, -) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md`.\n", - ) - write_file( - tmp_path / ".github/agents/local-demo.agent.md", - "---\nname: local-demo\ntools: [read]\n---\n\n" - "# Local Demo\n\n" - "## Preferred/Optional Skills\n\n" - "- `local-demo`\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - findings_by_path = {(finding.path, finding.code) for finding in findings} - - assert ( - ".github/agents/local-demo.agent.md", - "repo-owned-agent-legacy-skill-heading", - ) in findings_by_path - - -def test_run_consistency_checks_flags_skill_usage_contract_without_optional_support( - tmp_path: Path, -) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md`.\n", - ) - write_file( - tmp_path / ".github/agents/internal-demo.agent.md", - "---\nname: internal-demo\ntools: [read]\n---\n\n" - "# Internal Demo\n\n" - "## Mandatory Engine Skills\n\n" - "- `internal-demo`\n\n" - "## Skill Usage Contract\n\n" - "- `internal-demo`: Use when the skill is mandatory.\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - findings_by_path = {(finding.path, finding.code) for finding in findings} - - assert ( - ".github/agents/internal-demo.agent.md", - "repo-owned-agent-skill-usage-without-optional-support", - ) in findings_by_path - - -def test_run_consistency_checks_flags_active_residual_instruction_reference( - tmp_path: Path, -) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n- Use `.github/copilot-instructions.md`.\n- Use `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot Instructions\n\nSee `AGENTS.md` and `.github/INVENTORY.md`.\n", - ) - write_file( - tmp_path / ".github/skills/internal-demo/SKILL.md", - "---\n" - "name: internal-demo\n" - "description: Use when demo validation needs a fixture.\n" - "---\n\n" - "# Internal Demo\n\n" - "## When to use\n\n" - "Use this skill for tests.\n\n" - "See `.github/copilot-code-review-instructions.md`.\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", build_inventory_markdown(tmp_path)) - - findings = run_consistency_checks(tmp_path) - findings_by_code = {finding.code for finding in findings} - - assert "residual-instruction-reference" in findings_by_code diff --git a/tests/test_markdownlint_config.py b/tests/test_markdownlint_config.py deleted file mode 100644 index 5cb0c66..0000000 --- a/tests/test_markdownlint_config.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import annotations - -import json -from pathlib import Path - - -def read_markdownlint_config() -> dict[str, object]: - raw_text = Path(".markdownlint-cli2.jsonc").read_text(encoding="utf-8") - json_text = "\n".join( - line for line in raw_text.splitlines() if not line.lstrip().startswith("//") - ) - return json.loads(json_text) - - -def test_markdownlint_ignores_local_venv_and_preserved_upstream_assets() -> None: - config = read_markdownlint_config() - ignores = config["ignores"] - - assert "tmp/**" in ignores - assert ".github/scripts/.venv/**" in ignores - assert ".github/skills/antigravity-*/**" in ignores - assert ".github/skills/awesome-copilot-*/**" in ignores - assert ".github/skills/mattpocock-*/**" in ignores - assert ".github/skills/openai-*/**" in ignores - assert ".github/skills/superpowers-*/**" in ignores - - -def test_markdownlint_config_documents_why_special_paths_are_ignored() -> None: - config_text = Path(".markdownlint-cli2.jsonc").read_text(encoding="utf-8") - - assert "dedicated contract tests cover tmp/superpowers artifacts" in config_text - assert "generated tooling state" in config_text - assert "Preserve imported support skill families verbatim" in config_text diff --git a/tests/test_normalize_superpowers_imports.py b/tests/test_normalize_superpowers_imports.py deleted file mode 100644 index fcf7c02..0000000 --- a/tests/test_normalize_superpowers_imports.py +++ /dev/null @@ -1,95 +0,0 @@ -from __future__ import annotations - -import importlib.util -import sys -from pathlib import Path - - -def load_module(): - script_path = Path( - ".github/skills/local-agent-sync-external-resources/scripts/normalize_superpowers_imports.py" - ) - spec = importlib.util.spec_from_file_location( - "normalize_superpowers_imports", script_path - ) - module = importlib.util.module_from_spec(spec) - assert spec is not None - assert spec.loader is not None - sys.modules[spec.name] = module - spec.loader.exec_module(module) - return module - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def write_reference(root: Path) -> Path: - reference_path = ( - root - / ".github/skills/local-agent-sync-external-resources/references/superpowers-normalization.yaml" - ) - write_file( - reference_path, - "version: 1\n" - "source_family: obra/superpowers\n" - "managed_skills:\n" - " - upstream: demo\n" - " legacy_local: obra-demo\n" - " local: superpowers-demo\n" - "managed_patches:\n" - " - legacy_path: patches/obra-demo.patch\n" - " path: patches/superpowers-demo.patch\n" - "live_scan:\n" - " include:\n" - " - .github/agents\n" - " - .github/skills\n" - " ignored_files:\n" - " - superpowers-normalization.yaml\n", - ) - return reference_path - - -def test_normalizer_detects_and_applies_legacy_skill_drift(tmp_path: Path) -> None: - module = load_module() - reference_path = write_reference(tmp_path) - write_file( - tmp_path / ".github/skills/obra-demo/SKILL.md", - "---\nname: obra-demo\ndescription: Demo.\n---\n\nUse superpowers:demo.\n", - ) - write_file( - tmp_path / ".github/agents/local-demo.agent.md", - "---\nname: local-demo\ntools: [read]\n---\n\n- `obra-demo`\n", - ) - write_file( - tmp_path - / ".github/skills/local-agent-sync-external-resources/patches/obra-demo.patch", - "diff --git a/.github/skills/obra-demo/SKILL.md b/.github/skills/obra-demo/SKILL.md\n", - ) - - config = module.load_config(reference_path) - drift = module.detect_drift(tmp_path, config) - - assert {change.kind for change in drift} == {"legacy-path", "legacy-reference"} - - changes = module.apply_normalization(tmp_path, config) - - assert changes - assert not (tmp_path / ".github/skills/obra-demo").exists() - assert (tmp_path / ".github/skills/superpowers-demo/SKILL.md").is_file() - assert "name: superpowers-demo" in ( - tmp_path / ".github/skills/superpowers-demo/SKILL.md" - ).read_text(encoding="utf-8") - assert "superpowers-demo" in ( - tmp_path / ".github/agents/local-demo.agent.md" - ).read_text(encoding="utf-8") - assert not ( - tmp_path - / ".github/skills/local-agent-sync-external-resources/patches/obra-demo.patch" - ).exists() - assert ( - tmp_path - / ".github/skills/local-agent-sync-external-resources/patches/superpowers-demo.patch" - ).is_file() - assert module.detect_drift(tmp_path, config) == [] diff --git a/tests/test_plan_policy_contract.py b/tests/test_plan_policy_contract.py deleted file mode 100644 index ef1d88c..0000000 --- a/tests/test_plan_policy_contract.py +++ /dev/null @@ -1,124 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - - -def read_text(relative_path: str) -> str: - return Path(relative_path).read_text(encoding="utf-8") - - -def test_root_policy_files_keep_retained_plan_paths_outside_always_on_detail() -> None: - agents_text = read_text("AGENTS.md") - copilot_text = read_text(".github/copilot-instructions.md") - assert "tmp/superpowers/" not in agents_text - assert "retained-plan" not in agents_text - assert "tmp/superpowers/" not in copilot_text - - -def test_writing_plans_resets_to_superpowers_wrapper_contract() -> None: - writing_text = read_text(".github/skills/internal-gateway-writing-plans/SKILL.md") - wrapper_text = read_text( - ".github/skills/internal-gateway-writing-plans/agents/openai.yaml" - ) - - assert "superpowers-writing-plans" in writing_text - assert "delegate" in writing_text.lower() - assert "decide whether to create a retained plan" in writing_text - assert "tmp/superpowers/plans/YYYY-MM-DD-.md" in writing_text - assert "Target" in writing_text - assert "Anti-scope" in writing_text - assert "Nearest owner" in writing_text - assert "Validation path" in writing_text - assert "Stop conditions" in writing_text - assert "Observable acceptance" in writing_text - assert "Execution-readiness check" in writing_text - assert "task order" in writing_text - assert "file targets" in writing_text - assert "handoff readiness" in writing_text - assert "superpowers-writing-plans" in wrapper_text - assert "delegate" in wrapper_text.lower() - - -def test_writing_plans_wrapper_rejects_legacy_protocol_terms() -> None: - tracked_paths = [ - ".github/skills/internal-gateway-writing-plans/SKILL.md", - ".github/skills/internal-gateway-writing-plans/agents/openai.yaml", - ] - banned_terms = [ - "`compact`", - "`extended`", - "mini-plan-*", - "01-change-summary.md", - "02-execution.md", - "02-control.md", - "handoff-check", - "plan_authoring.py", - "Recommended consumer", - "Plan-only mode", - "force plan-only mode", - "stop after plan creation", - ] - - for relative_path in tracked_paths: - text = read_text(relative_path) - for banned in banned_terms: - assert banned not in text, f"{relative_path} still contains {banned}" - - -def test_writing_plans_preserves_short_preflight_context_discipline() -> None: - writing_text = read_text(".github/skills/internal-gateway-writing-plans/SKILL.md") - assert "short preflight" in writing_text - assert "targeted rereads" in writing_text - assert "known-context handoff quality" in writing_text - - -def test_executing_plans_is_lightweight_superpowers_wrapper() -> None: - executing_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") - assert "gateway-only wrapper" in executing_text - assert "superpowers-executing-plans" in executing_text - assert "..md" in executing_text - assert "DONE" in executing_text - assert "BLOCKED" in executing_text - assert "PARTIAL" in executing_text - assert "NEEDS_REVIEW" in executing_text - assert "Legacy Contract Boundary" in executing_text - - -def test_executing_plans_gateway_referenced_skills_are_narrow() -> None: - executing_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") - - assert executing_text.index("# Internal Gateway Execute Plans") < executing_text.index( - "## Referenced Skills" - ) - assert executing_text.index("## Referenced Skills") < executing_text.index( - "## When To Use" - ) - assert "superpowers-executing-plans" in executing_text - assert "superpowers-verification-before-completion" in executing_text - assert "superpowers-subagent-driven-development" not in executing_text - assert "internal-gateway-writing-plans" not in executing_text - assert "internal-gateway-review" not in executing_text - assert "internal-gateway-critical-master" not in executing_text - assert "internal-tdd" not in executing_text - assert "superpowers-test-driven-development" not in executing_text - assert "## Execution Discipline" in executing_text - assert "Plan-bound execution" in executing_text - assert "DRY" in executing_text - assert "YAGNI" in executing_text - assert "KISS" in executing_text - assert "Separation of Concerns" in executing_text - assert "single responsibility" in executing_text - assert "Fail-fast on drift" in executing_text - - -def test_gateway_handoff_references_use_canonical_execution_owner() -> None: - compatibility_text = read_text( - ".github/skills/internal-gateway-idea-brainstorming/references/compatibility-matrix.md" - ) - handoff_text = read_text( - ".github/skills/internal-gateway-execute-plans/references/plan-handoff.md" - ) - assert "internal-gateway-execute-plans" in compatibility_text - assert "internal-executing-plans" not in compatibility_text - assert "internal-gateway-execute-plans" in handoff_text - assert "internal-executing-plans" not in handoff_text diff --git a/tests/test_repo_only_agents_contract.py b/tests/test_repo_only_agents_contract.py deleted file mode 100644 index 1f25f1e..0000000 --- a/tests/test_repo_only_agents_contract.py +++ /dev/null @@ -1,159 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import yaml - -SYNC_AGENTS = { - "local-sync-external-resources": ".github/agents/local-sync-external-resources.agent.md", - "local-sync-global-copilot-configs-into-repo": ".github/agents/local-sync-global-copilot-configs-into-repo.agent.md", - "local-sync-install-ai-resources": ".github/agents/local-sync-install-ai-resources.agent.md", -} - -WATCHLIST_PATH = Path( - ".github/skills/local-agent-sync-external-resources/references/external-watchlist.yaml" -) - -LEGACY_AGENT_HEADINGS = ( - "## Mandatory Engine Skills", - "## Optional Support Skills", - "## Preferred/Optional Skills", - "## Skill Usage Contract", -) - -EXPECTED_CORE_SKILLS = { - "local-sync-external-resources": "local-agent-sync-external-resources", - "local-sync-global-copilot-configs-into-repo": ( - "local-agent-sync-global-copilot-configs-into-repo" - ), - "local-sync-install-ai-resources": "local-agent-sync-install-ai-resources", -} - - -def retired_mattpocock_ids() -> tuple[str, ...]: - return tuple( - f"mattpocock-{suffix}" - for suffix in ( - "diagnose", - "tdd", - "improve-codebase-architecture", - "zoom-out", - "grill-with-docs", - "setup-matt-pocock-skills", - ) - ) - - -def load_frontmatter(relative_path: str) -> dict[str, object]: - text = Path(relative_path).read_text(encoding="utf-8") - frontmatter_text = text.split("---\n", 2)[1] - return yaml.safe_load(frontmatter_text) - - -def read_body(relative_path: str) -> str: - text = Path(relative_path).read_text(encoding="utf-8") - return text.split("---\n", 2)[2] - - -def core_skill(body: str) -> str: - section = body.split("## Core Skill", 1)[1].split("\n## ", 1)[0] - matches = [ - line.strip().strip("- ").strip("`") - for line in section.splitlines() - if line.strip().startswith("- ") - ] - assert len(matches) == 1 - return matches[0] - - -def test_repo_only_sync_agents_keep_boundary_and_tool_contracts() -> None: - for agent_name, relative_path in SYNC_AGENTS.items(): - frontmatter = load_frontmatter(relative_path) - body = read_body(relative_path) - - assert frontmatter["name"] == agent_name - assert isinstance(frontmatter.get("description"), str) - assert frontmatter.get("tools") - assert "agent" not in frontmatter.get("tools", []) - if agent_name == "local-sync-install-ai-resources": - assert "web" not in frontmatter.get("tools", []) - assert frontmatter.get("disable-model-invocation") is True - assert frontmatter.get("agents") in (None, []) - assert "## Core Skill" in body - if agent_name == "local-sync-install-ai-resources": - assert "## Routing Rules" not in body - assert "## Boundary Definition" not in body - assert "## Mode Selection" not in body - assert "## Output Expectations" not in body - else: - assert "## Boundary Definition" in body - assert "## Output Expectations" in body - for heading in LEGACY_AGENT_HEADINGS: - assert heading not in body - assert core_skill(body) == EXPECTED_CORE_SKILLS[agent_name] - - -def test_repo_only_sync_agents_keep_their_named_operating_engines() -> None: - sync_control_center = read_body( - ".github/agents/local-sync-external-resources.agent.md" - ) - sync_global = read_body( - ".github/agents/local-sync-global-copilot-configs-into-repo.agent.md" - ) - - assert "- `local-agent-sync-external-resources`" in sync_control_center - assert "- `local-agent-sync-global-copilot-configs-into-repo`" in sync_global - - -def test_mattpocock_sync_scope_keeps_only_active_managed_imports() -> None: - sync_control_center = read_body( - ".github/agents/local-sync-external-resources.agent.md" - ) - sync_global = read_body( - ".github/agents/local-sync-global-copilot-configs-into-repo.agent.md" - ) - combined_text = f"{sync_control_center}\n{sync_global}" - - assert "`caveman` -> `mattpocock-caveman`" in combined_text - assert "`grill-me` -> `grill-me`" in combined_text - for retired_id in retired_mattpocock_ids(): - assert retired_id not in combined_text - - -def test_external_watchlist_is_alert_only_and_internal_owner_mapped() -> None: - payload = yaml.safe_load(WATCHLIST_PATH.read_text(encoding="utf-8")) - items = payload["items"] - owners = {item["local_owner"] for item in items} - upstream_ids = {item["upstream_id"] for item in items} - - assert payload["version"] == 1 - assert len(items) == 11 - assert {item["source_family"] for item in items} == { - "addyosmani/agent-skills", - "github/awesome-copilot", - "mattpocock/skills", - } - assert all(item["action"] == "alert-only" for item in items) - assert { - "azure-devops-pipelines.instructions.md", - "go.instructions.md", - "idea-refine", - "zoom-out", - } <= upstream_ids - assert { - "internal-azure-devops", - "internal-go", - "internal-kubernetes", - "internal-bash", - "internal-debugging", - "internal-tdd", - "internal-high-level-review", - "internal-gateway-idea-brainstorming", - "internal-gateway-writing-plans", - "local-agent-sync-external-resources", - } <= owners - - -def test_direct_entry_model_keeps_removed_router_assets_out_of_live_catalog() -> None: - assert not Path(".github/agents/internal-router.agent.md").exists() - assert not Path(".github/skills/internal-agent-routing-engine").exists() diff --git a/tests/test_repository_workflow_policy_contract.py b/tests/test_repository_workflow_policy_contract.py deleted file mode 100644 index 27802ff..0000000 --- a/tests/test_repository_workflow_policy_contract.py +++ /dev/null @@ -1,355 +0,0 @@ -from __future__ import annotations - -import re -from pathlib import Path - -import yaml - - -def read_text(relative_path: str) -> str: - return Path(relative_path).read_text(encoding="utf-8") - - -def section_between(body: str, heading: str) -> str: - section = body.split(heading, 1)[1] - return section.split("\n## ", 1)[0] - - -def tagged_block(body: str, tag: str) -> str: - start = f"`<{tag}>`" - end = f"``" - return body.split(start, 1)[1].split(end, 1)[0] - - -def python_fenced_blocks(body: str) -> list[str]: - return re.findall(r"```python\n(.*?)\n```", body, flags=re.DOTALL) - - -def test_github_pr_skill_owns_pr_merge_and_terminal_state_guardrails() -> None: - agents_text = read_text("AGENTS.md") - copilot_text = read_text(".github/copilot-instructions.md") - github_pr_skill_text = read_text(".github/skills/internal-github-pr/SKILL.md") - github_governance_skill_text = read_text( - ".github/skills/internal-github-governance/SKILL.md" - ) - inventory_text = read_text(".github/INVENTORY.md") - internal_contract_text = read_text("INTERNAL_CONTRACT.md") - - assert "## Repository Workflow Reminders" not in agents_text - assert "For self-authored PRs under required-review policy" in github_pr_skill_text - assert "qualifying non-author approval" in github_pr_skill_text - assert "Prefer `gh pr merge --squash`" in github_pr_skill_text - assert ( - "Treat organization-wide `gh search prs` results as eventually consistent" - in github_pr_skill_text - ) - assert "`gh pr view --json state,mergedAt`" in github_pr_skill_text - assert "Follow `AGENTS.md` for repository workflow reminders" not in copilot_text - assert "@your-org/platform-governance-team" in github_governance_skill_text - assert ".github/skills/internal-github-pr/SKILL.md" in inventory_text - assert ".github/skills/internal-pr-editor/SKILL.md" not in inventory_text - assert ".github/skills/internal-github-governance/SKILL.md" in inventory_text - - assert ( - "repository-workflow-github-pr-merge-and-terminal-state-reminders-stay-owned" - in internal_contract_text - ) - assert "qualifying non-author approval" in internal_contract_text - assert ( - "the GitHub PR skill prefers `gh pr merge --squash`" in internal_contract_text - ) - assert "`gh pr view --json state,mergedAt`" in internal_contract_text - - -def test_root_files_define_scoped_instruction_loading_for_manual_runtimes() -> None: - agents_text = read_text("AGENTS.md") - copilot_text = read_text(".github/copilot-instructions.md") - internal_contract_text = read_text("INTERNAL_CONTRACT.md") - - assert "## First Move" in agents_text - assert "## Precedence" in agents_text - assert "Resolve conflicts with the smallest valid owner." in agents_text - assert ( - "fallback policy, not permission to override narrower contracts." in agents_text - ) - - assert "## Scope And Placement" in agents_text - assert "operational procedures, checklists, file-shape recipes" in agents_text - assert ".github/instructions" not in agents_text - assert "bridge" not in agents_text.lower() - - assert "``" in agents_text - assert "portable source baseline" in agents_text - assert "`~/.agents/AGENTS.md`" in agents_text - assert ( - "global `~/.agents/AGENTS.md` baseline" in internal_contract_text - or "global home agent baseline" in agents_text - ) - assert "``" in agents_text - - assert "It is not a general task-execution guide" in copilot_text - assert "Do not ask the author to follow local runtime workflows" in copilot_text - assert "that GitHub.com cannot" in copilot_text - assert "execute." in copilot_text - assert ( - "Do not treat this file as instructions for coding agents, local CLIs, or" - in copilot_text - ) - - -def test_agents_keeps_only_intentional_root_level_workflows() -> None: - agents_text = read_text("AGENTS.md") - shared_baseline = tagged_block(agents_text, "shared-baseline") - local_rules = tagged_block(agents_text, "standards-repository-local-rules") - - assert "tool-specific workflows here" in agents_text - assert "Do not duplicate skill-owned paths" in agents_text - assert ( - "Use graphify first" in shared_baseline - or "use graphify as the first orientation tool" in shared_baseline - ) - assert "## graphify" in shared_baseline - assert "graphify-out/graph.json" in shared_baseline - assert "If graphify is unavailable or has no useful state" in shared_baseline - assert "best local fallback" in shared_baseline - assert "## graphify" not in local_rules - assert "graphify-out/graph.json" not in local_rules - - -def test_internal_contract_tracks_current_root_policy_shape() -> None: - internal_contract_text = read_text("INTERNAL_CONTRACT.md") - - assert "portable `` block" in internal_contract_text - assert "global `~/.agents/AGENTS.md` baseline" in internal_contract_text - assert "`` block" in internal_contract_text - assert "compact graph orientation rules" in internal_contract_text - assert ( - "instead of assuming a separate `## Context Routing` section" - in internal_contract_text - ) - - -def test_agents_tactical_defaults_include_compact_context_discipline() -> None: - agents_text = read_text("AGENTS.md") - - assert "## Tactical Defaults" in agents_text - assert "Preserve compact working state across turns" in agents_text - assert "Use bounded evidence" in agents_text - assert "For small, deterministic, low-risk tasks" in agents_text - assert ( - "For non-trivial, ambiguous, architectural, policy, contract, or multi-step" - in agents_text - ) - - -def test_lightweight_skill_references_stay_on_demand() -> None: - checklist_text = read_text( - ".github/skills/internal-skill-creator/references/writing-skills-checklist.md" - ) - support_routing_text = read_text( - ".github/skills/internal-gateway-simple-task/references/support-routing.md" - ) - - assert ( - "`## Referenced skills` as an audit index, not a preload bundle" - in checklist_text - ) - assert ( - "Add file extensions or path tokens in `description:` only when they materially disambiguate the owner" - in checklist_text - ) - assert ( - "Treat a support skill's `## Referenced skills` section as an owner index" - in support_routing_text - ) - assert "prefer the single narrowest owner proved by that" in support_routing_text - - lightweight_skill_paths = ( - ".github/skills/internal-bash/SKILL.md", - ".github/skills/internal-go/SKILL.md", - ".github/skills/internal-java/SKILL.md", - ".github/skills/internal-nodejs/SKILL.md", - ".github/skills/internal-python/SKILL.md", - ".github/skills/internal-yaml/SKILL.md", - ".github/skills/internal-kubernetes/SKILL.md", - ".github/skills/internal-terraform/SKILL.md", - ) - - for relative_path in lightweight_skill_paths: - referenced_section = section_between( - read_text(relative_path), "## Referenced skills" - ) - - assert "on-demand" in referenced_section - assert "Do not preload" in referenced_section - assert "only when" in referenced_section - - -def test_lightweight_workflow_and_review_skills_keep_references_on_demand() -> None: - skill_paths = ( - ".github/skills/internal-gateway-idea-brainstorming/SKILL.md", - ".github/skills/internal-debugging/SKILL.md", - ".github/skills/internal-high-level-review/SKILL.md", - ".github/skills/internal-github-pr/SKILL.md", - ".github/skills/internal-github-actions/SKILL.md", - ".github/skills/internal-github-action-composite/SKILL.md", - ".github/skills/internal-azure-devops/SKILL.md", - ) - - for relative_path in skill_paths: - referenced_section = section_between( - read_text(relative_path), "## Referenced skills" - ) - - assert "on-demand" in referenced_section - assert "Do not preload" in referenced_section - assert "only the owner proved" in referenced_section - - -def test_terraform_lock_matrix_policy_stays_visible() -> None: - terraform_skill_text = read_text(".github/skills/internal-terraform/SKILL.md") - precommit_text = read_text(".pre-commit-config.yaml") - - assert "canonical lock platform matrix" in terraform_skill_text - assert ( - "`terraform_providers_lock` block in the repo `.pre-commit-config.yaml`" - in terraform_skill_text - ) - assert ( - "If `.terraform.lock.hcl` changes or checksum mismatches appear" - in terraform_skill_text - ) - - assert ( - "Keep this hook commented to avoid slowing normal pre-commit runs." - in precommit_text - ) - assert ( - "Treat the platform list below as the canonical `.terraform.lock.hcl` matrix." - in precommit_text - ) - - -def test_dependabot_tracks_source_repository_dependency_manifests() -> None: - dependabot_config = yaml.safe_load(read_text(".github/dependabot.yml")) - updates = { - (entry["package-ecosystem"], entry["directory"]): entry - for entry in dependabot_config["updates"] - } - - assert ("github-actions", "/") in updates - assert ("pip", "/.github/scripts") in updates - assert ("pre-commit", "/") in updates - assert Path(".github/scripts/requirements.txt").exists() - assert Path(".pre-commit-config.yaml").exists() - assert ( - "python-tooling-minor-patch" in updates[("pip", "/.github/scripts")]["groups"] - ) - assert "pre-commit-minor-patch" in updates[("pre-commit", "/")]["groups"] - - -def test_recent_lessons_are_codified_in_skill_owners() -> None: - python_skill_text = read_text(".github/skills/internal-python/SKILL.md") - github_actions_skill_text = read_text( - ".github/skills/internal-github-actions/SKILL.md" - ) - - assert "modify `sys.path` before importing a standalone script" in python_skill_text - assert "# noqa: E402" in python_skill_text - assert "remove truly unused imports or variables" in python_skill_text - - assert ( - "Using `runner.temp` or other runner-scoped contexts in workflow-root `env`" - in github_actions_skill_text - ) - assert ( - "Smoke-testing a repository wrapper around an external CLI" - in github_actions_skill_text - ) - assert "even on `--dry-run` paths" in github_actions_skill_text - - -def test_technology_skill_modularity_contract_is_owned_and_searchable() -> None: - python_skill_text = read_text(".github/skills/internal-python/SKILL.md").lower() - python_script_skill_text = read_text( - ".github/skills/internal-python-script/SKILL.md" - ).lower() - python_project_skill_text = read_text( - ".github/skills/internal-python-project/SKILL.md" - ).lower() - bash_skill_text = read_text(".github/skills/internal-bash/SKILL.md").lower() - bash_script_skill_text = read_text( - ".github/skills/internal-bash-script/SKILL.md" - ).lower() - node_skill_text = read_text(".github/skills/internal-nodejs/SKILL.md").lower() - node_project_skill_text = read_text( - ".github/skills/internal-nodejs-project/SKILL.md" - ).lower() - java_skill_text = read_text(".github/skills/internal-java/SKILL.md").lower() - java_project_skill_text = read_text( - ".github/skills/internal-java-project/SKILL.md" - ).lower() - - base_skill_texts = ( - python_skill_text, - bash_skill_text, - node_skill_text, - java_skill_text, - ) - - for body in base_skill_texts: - assert "300" in body - assert "400" in body - assert "split-or-justify" in body - assert "dry" in body - - assert "entrypoint" in python_script_skill_text - assert "utils/" in python_script_skill_text - assert "single-file" in python_script_skill_text - assert "toolkit" in python_script_skill_text - assert "project" in python_script_skill_text - assert "generated" in python_script_skill_text - assert "fixture" in python_script_skill_text - assert "## referenced skills" in python_script_skill_text - assert "internal-tdd" in python_script_skill_text - assert "references/reporting.md" in python_script_skill_text - assert "executionreporter" in python_script_skill_text - assert "rich" in python_script_skill_text - assert "test-first" in python_script_skill_text - assert "pip install --require-hashes" in python_script_skill_text - - assert "entrypoint" in bash_script_skill_text - assert "sourced helper" in bash_script_skill_text - - assert "thin" in node_project_skill_text - assert "async boundaries" in node_project_skill_text - - assert "thin" in java_project_skill_text - assert "god class" in java_project_skill_text - assert "dto" in java_project_skill_text - - assert "domain/service/adapter" in python_project_skill_text - assert "## referenced skills" in python_project_skill_text - assert "internal-tdd" in python_project_skill_text - assert "structured" in python_project_skill_text - assert "rich" in python_project_skill_text - assert "cli adapter" in python_project_skill_text - assert "no emoji" in python_project_skill_text - assert "references/logging-and-reporting.md" in python_project_skill_text - assert "typed results" in python_project_skill_text - assert "machine-readable" in python_project_skill_text - - -def test_python_skill_template_snippets_are_copyable_python() -> None: - template_paths = ( - ".github/skills/internal-python-project/references/logging-and-reporting.md", - ".github/skills/internal-python-script/references/layout-and-templates.md", - ".github/skills/internal-python-script/references/reporting.md", - ) - - for relative_path in template_paths: - blocks = python_fenced_blocks(read_text(relative_path)) - assert blocks, f"{relative_path} should contain Python fenced snippets" - - for index, block in enumerate(blocks, start=1): - compile(block, f"{relative_path}:python-block-{index}", "exec") diff --git a/tests/test_retained_learning_contract.py b/tests/test_retained_learning_contract.py deleted file mode 100644 index 9b9fcfb..0000000 --- a/tests/test_retained_learning_contract.py +++ /dev/null @@ -1,77 +0,0 @@ -from __future__ import annotations - -import re -from pathlib import Path - -REPO_ROOT = Path(__file__).resolve().parent.parent - - -def read_text(relative_path: str) -> str: - return (REPO_ROOT / relative_path).read_text(encoding="utf-8") - - -def test_retained_learning_contract_preserves_on_disk_lessons_rows() -> None: - expected_contracts: dict[str, list[str]] = { - ".github/skills/internal-lesson-codification/SKILL.md": [ - "This skill owns the codification workflow.", - "ledger row format, which stays in `LESSONS_LEARNED.md`", - "First try to codify the lesson in the smallest valid canonical owner.", - "Use the ledger only for stable, reusable, repository-relevant lessons", - ], - "LESSONS_LEARNED.md": [ - "Before editing this file, read its current on-disk contents", - "including local uncommitted rows already present on disk", - ( - "Add a new lesson by appending one new row to the pending table; do not " - "regenerate, reorder, or rewrite unrelated rows." - ), - "Preserve unrelated existing lessons, including local uncommitted ones already on disk.", - ( - "Only update or remove a specific lesson row when that same lesson is being " - "codified, disproven, narrowed, or deduplicated." - ), - ], - } - - for relative_path, expected_snippets in expected_contracts.items(): - text = read_text(relative_path) - for snippet in expected_snippets: - assert snippet in text, ( - f"{relative_path} is missing retained-learning guardrail text: {snippet}" - ) - - copilot_text = read_text(".github/copilot-instructions.md") - assert "This file is only for GitHub.com Copilot code review." in copilot_text - assert ( - "Do not treat this file as instructions for coding agents, local CLIs" - in copilot_text - ) - - agents_text = read_text("AGENTS.md") - assert "LESSONS_LEARNED.md" not in agents_text - assert "ledger row" not in agents_text - - -def test_pending_lesson_rows_have_expected_shape_when_present() -> None: - lines = read_text("LESSONS_LEARNED.md").splitlines() - - section_start = lines.index("## Pending Rules") + 1 - header_index = next( - index - for index in range(section_start, len(lines)) - if lines[index].startswith("|") - ) - data_start = header_index + 2 - - data_rows: list[list[str]] = [] - for line in lines[data_start:]: - if not line.startswith("|"): - break - cells = [cell.strip() for cell in line.strip().strip("|").split("|")] - if any(cells): - data_rows.append(cells) - - for row in data_rows: - assert len(row) == 4 - assert re.fullmatch(r"\d{4}-\d{2}-\d{2}", row[0]) - assert all(cell for cell in row[1:]) diff --git a/tests/test_retained_plan_artifact_contract.py b/tests/test_retained_plan_artifact_contract.py deleted file mode 100644 index 2c6b721..0000000 --- a/tests/test_retained_plan_artifact_contract.py +++ /dev/null @@ -1,392 +0,0 @@ -from __future__ import annotations - -import re -from pathlib import Path - -RETAINED_PLAN_ROOT = Path("tmp/superpowers") - -INVALID_PATCH_MARKERS = ( - "*** Begin Patch", - "*** Update File:", - "*** Add File:", - "*** Delete File:", - "*** End Patch", -) - -COMPLETION_REPORT_FIELDS = ( - "Completion Report", - "Active phase and owner:", - "State:", - "Continuation:", - "User action required:", - "Files changed:", - "Completed items:", - "Intentional non-actions:", - "Validators:", - "Evidence envelope:", - "Source-item ledger:", - "Evidence gaps:", - "Residual risks:", - "Next-step package:", - "Follow-up suggestions:", -) - -OPEN_COMPLETION_STATUSES = ( - "PENDING", - "PARTIAL", - "NOT_DONE", - "UNVERIFIABLE", - "BLOCKED", -) - -GATEWAY_STATUS_VALUES = ("DONE", "BLOCKED", "PARTIAL", "NEEDS_REVIEW") -GATEWAY_STATUS_HEADINGS = ( - "## Status", - "## Reason", - "## Completed", - "## Remaining", - "## Validation", - "## Next", - "## Resume Notes", -) - - -def retained_plan_folders() -> list[Path]: - if not RETAINED_PLAN_ROOT.exists(): - return [] - - return sorted(path for path in RETAINED_PLAN_ROOT.iterdir() if path.is_dir()) - - -def numbered_plan_files(plan_folder: Path) -> list[Path]: - return sorted( - path - for path in plan_folder.glob("*.md") - if re.fullmatch(r"\d{2}-.+\.md", path.name) - ) - - -def done_files(plan_folder: Path) -> list[Path]: - return sorted(plan_folder.glob("done-*.md")) - - -def resolve_gateway_status_file( - plan_folder: Path, -) -> tuple[Path | None, str | None, list[str]]: - violations: list[str] = [] - statuses = "|".join(GATEWAY_STATUS_VALUES) - status_re = re.compile(rf"^{re.escape(plan_folder.name)}\.({statuses})\.md$") - status_files: list[Path] = [] - - for path in sorted(plan_folder.glob("*.md")): - if status_re.match(path.name): - status_files.append(path) - elif re.match(rf"^{re.escape(plan_folder.name)}\.[A-Z0-9_-]+\.md$", path.name): - violations.append( - f"{path} must use one of these statuses: {', '.join(GATEWAY_STATUS_VALUES)}" - ) - - legacy_markers = sorted(plan_folder.glob("*-plan-state.md")) - for marker in legacy_markers: - violations.append( - f"{marker} is a legacy gateway marker; use ..md" - ) - - if len(status_files) > 1: - violations.append(f"{plan_folder} has multiple gateway status files") - return None, None, violations - - if not status_files: - return None, None, violations - - marker = status_files[0] - match = status_re.match(marker.name) - if match is None: - violations.append(f"{marker} must match ..md") - return None, None, violations - - return marker, match.group(1), violations - - -def completed_retained_plan_violations(root: Path) -> list[str]: - violations: list[str] = [] - - for folder in sorted(path for path in root.iterdir() if path.is_dir()): - folder_done_files = done_files(folder) - status_path, marker_state, marker_violations = resolve_gateway_status_file(folder) - violations.extend(marker_violations) - - if not folder_done_files and status_path is None: - continue - - if status_path is not None and not folder_done_files: - status_text = status_path.read_text(encoding="utf-8") - for heading in GATEWAY_STATUS_HEADINGS: - if heading not in status_text: - violations.append(f"{status_path} is missing {heading}") - - status_match = re.search( - r"^## Status\s*\n+\s*([A-Z0-9_-]+)\s*$", - status_text, - re.MULTILINE, - ) - if status_match is None: - violations.append(f"{status_path} must declare status under ## Status") - elif status_match.group(1) != marker_state: - violations.append( - f"{status_path} filename status does not match declared status" - ) - continue - - active_files = numbered_plan_files(folder) - envelope_path = folder / "evidence-envelope.md" - report_path = folder / "completion-report.md" - - if active_files: - violations.append(f"{folder} still has active numbered plan files") - if not envelope_path.is_file(): - violations.append(f"{folder} is missing evidence-envelope.md") - continue - if not report_path.is_file(): - violations.append(f"{folder} is missing completion-report.md") - continue - - envelope_text = envelope_path.read_text(encoding="utf-8") - report_text = report_path.read_text(encoding="utf-8") - - if "| Status |" not in envelope_text: - violations.append(f"{envelope_path} is missing Status column") - if "| Evidence path or command |" not in envelope_text: - violations.append( - f"{envelope_path} is missing Evidence path or command column" - ) - for status in OPEN_COMPLETION_STATUSES: - if f"| {status} |" in envelope_text: - violations.append( - f"{envelope_path} contains open completion status {status}" - ) - - for done_file in folder_done_files: - if f"`{done_file.name}`" not in envelope_text: - violations.append( - f"{envelope_path} does not reference {done_file.name}" - ) - - for field in COMPLETION_REPORT_FIELDS: - if field not in report_text: - violations.append(f"{report_path} is missing {field}") - - return violations - - -def patch_marker_violations(root: Path) -> list[str]: - violations: list[str] = [] - - for markdown_path in sorted(root.glob("**/*.md")): - text = markdown_path.read_text(encoding="utf-8") - for marker in INVALID_PATCH_MARKERS: - if marker in text: - violations.append(f"{markdown_path.as_posix()} contains {marker}") - - return violations - - -def write_completed_plan_folder(root: Path) -> Path: - plan_folder = root / "sample-plan" - plan_folder.mkdir() - (plan_folder / "done-01-sample.md").write_text("# Done Sample\n", encoding="utf-8") - (plan_folder / "evidence-envelope.md").write_text( - "# Evidence Envelope\n\n" - "| Source item | Status | Evidence path or command |\n" - "| --- | --- | --- |\n" - "| `done-01-sample.md` | DONE | `pytest` |\n", - encoding="utf-8", - ) - (plan_folder / "completion-report.md").write_text( - "\n".join(COMPLETION_REPORT_FIELDS) + "\n", - encoding="utf-8", - ) - return plan_folder - - -def write_lightweight_completed_plan_folder(root: Path) -> Path: - plan_folder = root / "sample-plan" - plan_folder.mkdir() - (plan_folder / "01-change-summary.md").write_text( - "# Summary\n", - encoding="utf-8", - ) - (plan_folder / "02-execution.md").write_text( - "## Plan profile\ncompact\n\n" - "## Source item coverage\n" - "| ID | Source item | Observable acceptance | Evidence class | Acceptance evidence | Status | Route |\n" - "| --- | --- | --- | --- | --- | --- | --- |\n" - "| PLAN-01 | Lightweight completion marker | DONE status file present | file | sample-plan.DONE.md | DONE | `none` |\n", - encoding="utf-8", - ) - (plan_folder / "sample-plan.DONE.md").write_text( - "# sample-plan Status\n\n" - "## Status\n\n" - "DONE\n\n" - "## Reason\n\n" - "Complete.\n\n" - "## Completed\n\n" - "- All items.\n\n" - "## Remaining\n\n" - "- None.\n\n" - "## Validation\n\n" - "- `pytest` passed.\n\n" - "## Next\n\n" - "- No action required.\n\n" - "## Resume Notes\n\n" - "- Re-run validation after new edits.\n", - encoding="utf-8", - ) - return plan_folder - - -def write_blocked_plan_folder(root: Path) -> Path: - plan_folder = root / "sample-plan" - plan_folder.mkdir() - (plan_folder / "sample-plan.BLOCKED.md").write_text( - "# sample-plan Status\n\n" - "## Status\n\n" - "BLOCKED\n\n" - "## Reason\n\n" - "Blocked.\n\n" - "## Completed\n\n" - "- Partial work.\n\n" - "## Remaining\n\n" - "- Await approval.\n\n" - "## Validation\n\n" - "- Not run.\n\n" - "## Next\n\n" - "- Approve next owner.\n\n" - "## Resume Notes\n\n" - "- Resume after approval.\n", - encoding="utf-8", - ) - return plan_folder - - -def test_completed_retained_plan_validation_accepts_well_formed_folder( - tmp_path: Path, -) -> None: - write_completed_plan_folder(tmp_path) - - assert completed_retained_plan_violations(tmp_path) == [] - - -def test_completed_retained_plan_validation_accepts_lightweight_folder( - tmp_path: Path, -) -> None: - write_lightweight_completed_plan_folder(tmp_path) - - assert completed_retained_plan_violations(tmp_path) == [] - - -def test_completed_retained_plan_validation_accepts_active_blocked_marker( - tmp_path: Path, -) -> None: - write_blocked_plan_folder(tmp_path) - - assert completed_retained_plan_violations(tmp_path) == [] - - -def test_completed_retained_plan_validation_rejects_active_numbered_files( - tmp_path: Path, -) -> None: - plan_folder = write_completed_plan_folder(tmp_path) - (plan_folder / "01-still-active.md").write_text("# Active\n", encoding="utf-8") - - assert completed_retained_plan_violations(tmp_path) == [ - f"{plan_folder} still has active numbered plan files" - ] - - -def test_completed_retained_plan_validation_rejects_open_statuses( - tmp_path: Path, -) -> None: - plan_folder = write_completed_plan_folder(tmp_path) - (plan_folder / "evidence-envelope.md").write_text( - "# Evidence Envelope\n\n" - "| Source item | Status | Evidence path or command |\n" - "| --- | --- | --- |\n" - "| `done-01-sample.md` | PENDING | `pytest` |\n", - encoding="utf-8", - ) - - assert completed_retained_plan_violations(tmp_path) == [ - f"{plan_folder / 'evidence-envelope.md'} contains open completion status PENDING" - ] - - -def test_completed_retained_plan_validation_rejects_lightweight_non_shipped( - tmp_path: Path, -) -> None: - plan_folder = write_lightweight_completed_plan_folder(tmp_path) - (plan_folder / "sample-plan.DONE.md").write_text( - "# sample-plan Status\n\n" - "## Status\n\n" - "PARTIAL\n\n" - "## Reason\n\n" - "Incomplete.\n\n" - "## Completed\n\n" - "- Some items.\n\n" - "## Remaining\n\n" - "- More items.\n\n" - "## Validation\n\n" - "- Pending.\n\n" - "## Next\n\n" - "- Continue.\n\n" - "## Resume Notes\n\n" - "- Resume later.\n", - encoding="utf-8", - ) - - assert completed_retained_plan_violations(tmp_path) == [ - f"{plan_folder / 'sample-plan.DONE.md'} filename status does not match declared status", - ] - - -def test_completed_retained_plan_validation_rejects_waiting_marker_missing_fields( - tmp_path: Path, -) -> None: - plan_folder = tmp_path / "sample-plan" - plan_folder.mkdir() - (plan_folder / "sample-plan.BLOCKED.md").write_text( - "# sample-plan Status\n\n" - "## Status\n\n" - "BLOCKED\n\n" - "## Reason\n\n" - "Blocked.\n\n" - "## Completed\n\n" - "- Partial work.\n\n" - "## Remaining\n\n" - "- Await approval.\n\n" - "## Validation\n\n" - "- Not run.\n\n" - "## Next\n\n" - "- Approve next owner.\n\n" - encoding="utf-8", - ) - - assert completed_retained_plan_violations(tmp_path) == [ - f"{plan_folder / 'sample-plan.BLOCKED.md'} is missing ## Resume Notes", - ] - - -def test_repository_completed_retained_plan_folders_preserve_completion_evidence() -> ( - None -): - if not RETAINED_PLAN_ROOT.exists(): - return - - assert completed_retained_plan_violations(RETAINED_PLAN_ROOT) == [] - - -def test_retained_plan_markdown_does_not_contain_patch_markers() -> None: - if not RETAINED_PLAN_ROOT.exists(): - return - - assert patch_marker_violations(RETAINED_PLAN_ROOT) == [] diff --git a/tests/test_sync_and_token_risks.py b/tests/test_sync_and_token_risks.py deleted file mode 100644 index 7585485..0000000 --- a/tests/test_sync_and_token_risks.py +++ /dev/null @@ -1,1512 +0,0 @@ -from __future__ import annotations - -import json -import subprocess -from pathlib import Path - -import pytest -from lib.shared import find_repo_root -from lib.syncing import apply_sync_plan, build_sync_plan, write_sync_plan -from lib.token_risks import ( - ROOT_ALWAYS_ON_PATHS, - ROOT_ALWAYS_ON_TOKEN_TARGET, - detect_token_risks, - estimate_tokens, -) - -LEGACY_INSTRUCTION_DIR = ".github/" + "instructions" -LEGACY_APPLY_TO_KEY = "apply" + "To" - - -def write_file(path: Path, content: str) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content, encoding="utf-8") - - -def run_git(root: Path, *args: str) -> None: - subprocess.run( - ["git", *args], - cwd=root, - check=True, - capture_output=True, - text=True, - ) - - -def init_git_repo(root: Path) -> None: - run_git(root, "init") - run_git(root, "config", "user.name", "Test User") - run_git(root, "config", "user.email", "test@example.com") - - -def commit_all(root: Path, message: str) -> None: - run_git(root, "add", "-A") - run_git(root, "commit", "-m", message) - - -def test_find_repo_root_accepts_git_repo_without_github_directory( - tmp_path: Path, -) -> None: - target_root = tmp_path / "target" - nested_path = target_root / "nested" / "path" - - target_root.mkdir(parents=True) - init_git_repo(target_root) - nested_path.mkdir(parents=True) - - assert find_repo_root(nested_path) == target_root - - -def test_build_sync_plan_preserves_local_assets_and_deletes_non_local_assets( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\n") - write_file( - source_root / ".github/agents/internal-fast.agent.md", - "---\nname: internal-fast\ntools: [read]\n---\n", - ) - write_file( - source_root / ".github/agents/internal-sync-legacy.agent.md", - "---\nname: internal-sync-legacy\ntools: [read]\n---\n", - ) - - write_file(target_root / "AGENTS.md", "# AGENTS\nold\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\nold\n") - write_file( - target_root / ".github/agents/internal-fast.agent.md", - "---\nname: internal-fast\ntools: [read]\n---\nold\n", - ) - write_file( - target_root / ".github/agents/internal-sync-legacy.agent.md", - "---\nname: internal-sync-legacy\ntools: [read]\n---\nlegacy\n", - ) - write_file( - target_root / ".github/agents/local-special.agent.md", - "---\nname: local-special\ntools: [read]\n---\n", - ) - write_file( - target_root / ".github/agents/custom.agent.md", - "---\nname: custom\ntools: [read]\n---\n", - ) - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - - assert ("preserve", ".github/agents/local-special.agent.md") in actions - assert ("delete", ".github/agents/custom.agent.md") in actions - assert ("update", ".github/agents/internal-fast.agent.md") in actions - assert ("delete", ".github/agents/internal-sync-legacy.agent.md") in actions - - -def test_build_sync_plan_does_not_ship_source_instruction_family( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\n") - write_file( - source_root / LEGACY_INSTRUCTION_DIR / "internal-python.instructions.md", - f"---\ndescription: Python\n{LEGACY_APPLY_TO_KEY}: '**/*.py'\nexcludeAgent: cloud-agent\n---\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\n") - - plan = build_sync_plan(source_root, target_root) - planned_paths = {operation.path for operation in plan.operations} - - assert ( - f"{LEGACY_INSTRUCTION_DIR}/internal-python.instructions.md" not in planned_paths - ) - - -def test_build_sync_plan_deletes_target_non_local_instructions_and_preserves_local( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\n") - write_file( - source_root / LEGACY_INSTRUCTION_DIR / "internal-python.instructions.md", - f"---\ndescription: Python\n{LEGACY_APPLY_TO_KEY}: '**/*.py'\nexcludeAgent: cloud-agent\n---\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\n") - write_file( - target_root / LEGACY_INSTRUCTION_DIR / "internal-python.instructions.md", - f"---\ndescription: Old Python\n{LEGACY_APPLY_TO_KEY}: '**/*.py'\nexcludeAgent: cloud-agent\n---\n", - ) - write_file( - target_root / LEGACY_INSTRUCTION_DIR / "local-team.instructions.md", - f"---\ndescription: Local team\n{LEGACY_APPLY_TO_KEY}: '**/*.md'\n---\n", - ) - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - - assert ( - "delete", - f"{LEGACY_INSTRUCTION_DIR}/internal-python.instructions.md", - ) in actions - assert ( - "preserve", - f"{LEGACY_INSTRUCTION_DIR}/local-team.instructions.md", - ) in actions - - -def test_build_sync_plan_does_not_recreate_source_instructions_after_cleanup( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\n") - write_file( - source_root / LEGACY_INSTRUCTION_DIR / "internal-python.instructions.md", - f"---\ndescription: Python\n{LEGACY_APPLY_TO_KEY}: '**/*.py'\nexcludeAgent: cloud-agent\n---\n", - ) - - write_file(target_root / "AGENTS.md", "# AGENTS\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\n") - write_file( - target_root / LEGACY_INSTRUCTION_DIR / "internal-python.instructions.md", - f"---\ndescription: Old Python\n{LEGACY_APPLY_TO_KEY}: '**/*.py'\nexcludeAgent: cloud-agent\n---\n", - ) - write_file( - target_root / LEGACY_INSTRUCTION_DIR / "local-team.instructions.md", - f"---\ndescription: Local team\n{LEGACY_APPLY_TO_KEY}: '**/*.md'\nexcludeAgent: cloud-agent\n---\n", - ) - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - - assert ( - "delete", - f"{LEGACY_INSTRUCTION_DIR}/internal-python.instructions.md", - ) in actions - assert ( - "create", - f"{LEGACY_INSTRUCTION_DIR}/internal-python.instructions.md", - ) not in actions - assert ( - "preserve", - f"{LEGACY_INSTRUCTION_DIR}/local-team.instructions.md", - ) in actions - - -def test_build_sync_plan_includes_graphify_skill_in_consumer_sync( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\n") - write_file( - source_root / ".github/skills/graphify/SKILL.md", - "---\nname: graphify\n---\n\n# Graphify\n", - ) - - write_file(target_root / "AGENTS.md", "# AGENTS\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\n") - write_file( - target_root / ".github/skills/graphify/SKILL.md", - "---\nname: graphify\n---\n\n# Old\n", - ) - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - - assert ("update", ".github/skills/graphify/SKILL.md") in actions - - -def test_build_sync_plan_does_not_create_legacy_override_when_missing( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - - plan = build_sync_plan(source_root, target_root) - - assert ".github/copilot-instructions.override.md" not in { - operation.path for operation in plan.operations - } - - -def test_build_sync_plan_creates_consumer_local_knowledge_docs_from_templates( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file( - source_root / ".github/templates/docs-README.md.template", "# Docs scaffold\n" - ) - write_file( - source_root / ".github/templates/repository-context.md.template", - "# Context scaffold\n", - ) - write_file( - source_root / ".github/templates/architecture.md.template", - "# Architecture scaffold\n", - ) - write_file(source_root / ".github/templates/tech.md.template", "# Tech scaffold\n") - write_file( - source_root / ".github/templates/structure.md.template", - "# Structure scaffold\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - planned_paths = {operation.path for operation in plan.operations} - - assert ("create", "docs/README.md") in actions - assert ("create", "docs/repository-context.md") in actions - assert ("create", "docs/architecture.md") in actions - assert ("create", "docs/tech.md") in actions - assert ("create", "docs/structure.md") in actions - assert ".github/templates/docs-README.md.template" not in planned_paths - assert ".github/templates/repository-context.md.template" not in planned_paths - assert ".github/templates/architecture.md.template" not in planned_paths - assert ".github/templates/tech.md.template" not in planned_paths - assert ".github/templates/structure.md.template" not in planned_paths - - -def test_apply_sync_plan_creates_consumer_local_knowledge_docs_from_templates( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file( - source_root / ".github/templates/docs-README.md.template", "# Docs scaffold\n" - ) - write_file( - source_root / ".github/templates/repository-context.md.template", - "# Context scaffold\n", - ) - write_file( - source_root / ".github/templates/architecture.md.template", - "# Architecture scaffold\n", - ) - write_file(source_root / ".github/templates/tech.md.template", "# Tech scaffold\n") - write_file( - source_root / ".github/templates/structure.md.template", - "# Structure scaffold\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - - plan = build_sync_plan(source_root, target_root) - apply_sync_plan(plan) - - assert (target_root / "docs/README.md").read_text( - encoding="utf-8" - ) == "# Docs scaffold\n" - assert (target_root / "docs/repository-context.md").read_text( - encoding="utf-8" - ) == "# Context scaffold\n" - assert (target_root / "docs/architecture.md").read_text( - encoding="utf-8" - ) == "# Architecture scaffold\n" - assert (target_root / "docs/tech.md").read_text( - encoding="utf-8" - ) == "# Tech scaffold\n" - assert (target_root / "docs/structure.md").read_text( - encoding="utf-8" - ) == "# Structure scaffold\n" - - -def test_build_sync_plan_preserves_existing_consumer_local_knowledge_docs( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file( - source_root / ".github/templates/docs-README.md.template", "# Docs scaffold\n" - ) - write_file( - source_root / ".github/templates/repository-context.md.template", - "# Context scaffold\n", - ) - write_file( - source_root / ".github/templates/architecture.md.template", - "# Architecture scaffold\n", - ) - write_file(source_root / ".github/templates/tech.md.template", "# Tech scaffold\n") - write_file( - source_root / ".github/templates/structure.md.template", - "# Structure scaffold\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / "docs/README.md", "# Target docs\n") - write_file(target_root / "docs/repository-context.md", "# Target context\n") - write_file(target_root / "docs/architecture.md", "# Target architecture\n") - write_file(target_root / "docs/tech.md", "# Target tech\n") - write_file(target_root / "docs/structure.md", "# Target structure\n") - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - - assert ("preserve", "docs/README.md") in actions - assert ("preserve", "docs/repository-context.md") in actions - assert ("preserve", "docs/architecture.md") in actions - assert ("preserve", "docs/tech.md") in actions - assert ("preserve", "docs/structure.md") in actions - assert "docs/README.md" in plan.local_assets - assert "docs/repository-context.md" in plan.local_assets - assert "docs/architecture.md" in plan.local_assets - assert "docs/tech.md" in plan.local_assets - assert "docs/structure.md" in plan.local_assets - - -def test_build_sync_plan_renames_legacy_architecture_when_new_path_is_missing( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file( - source_root / ".github/templates/architecture.md.template", - "# Architecture scaffold\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / "docs/01-local-architecture.md", "# Legacy architecture\n") - - plan = build_sync_plan(source_root, target_root) - - assert ("rename", "docs/architecture.md") in { - (operation.action, operation.path) for operation in plan.operations - } - - apply_sync_plan(plan) - - assert not (target_root / "docs/01-local-architecture.md").exists() - assert (target_root / "docs/architecture.md").read_text( - encoding="utf-8" - ) == "# Legacy architecture\n" - - -def test_build_sync_plan_renames_legacy_repository_context_when_new_path_is_missing( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file( - source_root / ".github/templates/repository-context.md.template", - "# Context scaffold\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file( - target_root / "docs/02-local-repository-context.md", "# Legacy context\n" - ) - - plan = build_sync_plan(source_root, target_root) - - assert ("rename", "docs/repository-context.md") in { - (operation.action, operation.path) for operation in plan.operations - } - - apply_sync_plan(plan) - - assert not (target_root / "docs/02-local-repository-context.md").exists() - assert (target_root / "docs/repository-context.md").read_text( - encoding="utf-8" - ) == "# Legacy context\n" - - -def test_apply_sync_plan_blocks_when_legacy_and_new_architecture_coexist( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file( - source_root / ".github/templates/architecture.md.template", - "# Architecture scaffold\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / "docs/architecture.md", "# Canonical architecture\n") - write_file(target_root / "docs/01-local-architecture.md", "# Legacy architecture\n") - - plan = build_sync_plan(source_root, target_root) - - assert ("manual", "docs/architecture.md") in { - (operation.action, operation.path) for operation in plan.operations - } - with pytest.raises(RuntimeError, match="manual reconciliation"): - apply_sync_plan(plan) - - -def test_apply_sync_plan_blocks_when_legacy_and_new_repository_context_coexist( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file( - source_root / ".github/templates/repository-context.md.template", - "# Context scaffold\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / "docs/repository-context.md", "# Canonical context\n") - write_file( - target_root / "docs/02-local-repository-context.md", "# Legacy context\n" - ) - - plan = build_sync_plan(source_root, target_root) - - assert ("manual", "docs/repository-context.md") in { - (operation.action, operation.path) for operation in plan.operations - } - with pytest.raises(RuntimeError, match="manual reconciliation"): - apply_sync_plan(plan) - - -def test_build_sync_plan_deletes_legacy_runtime_fit_document(tmp_path: Path) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / "docs/runtime-fit.md", "# Runtime fit\n") - - plan = build_sync_plan(source_root, target_root) - - assert ("delete", "docs/runtime-fit.md") in { - (operation.action, operation.path) for operation in plan.operations - } - - -def test_build_sync_plan_deletes_retired_runtime_operating_model( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file( - target_root / "docs/03-local-ai-runtime-operating-model.md", "# Runtime\n" - ) - - plan = build_sync_plan(source_root, target_root) - - assert ("delete", "docs/03-local-ai-runtime-operating-model.md") in { - (operation.action, operation.path) for operation in plan.operations - } - - -def test_build_sync_plan_includes_prompt_assets_in_managed_inventory( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file( - source_root / ".github/prompts/internal-review-ai-resources.prompt.md", - "---\ndescription: Review AI resources\n---\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - - assert ( - "create", - ".github/prompts/internal-review-ai-resources.prompt.md", - ) in actions - assert "## Prompts" in plan.generated_inventory - assert ( - "- `.github/prompts/internal-review-ai-resources.prompt.md`" - in plan.generated_inventory - ) - - -def test_apply_sync_plan_clears_plan_file_and_writes_manifest(tmp_path: Path) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / "VERSION", "1.2.3\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file( - source_root / ".github/agents/internal-fast.agent.md", - "---\nname: internal-fast\ntools: [read]\n---\n\n# Source\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file( - target_root / ".github/agents/internal-fast.agent.md", - "---\nname: internal-fast\ntools: [read]\n---\n\n# Target\n", - ) - write_file( - target_root / "tmp/internal-sync-copilot-configs.plan.md", "legacy plan\n" - ) - write_file( - target_root / ".github/internal-sync-copilot-configs.manifest.json", "{}\n" - ) - plan = build_sync_plan(source_root, target_root) - plan_path = write_sync_plan(plan) - - manifest_path = apply_sync_plan(plan) - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - - assert not plan_path.exists() - assert manifest_path.exists() - assert manifest_path.name == "copilot-sync.manifest.json" - assert not (target_root / "tmp/internal-sync-copilot-configs.plan.md").exists() - assert not ( - target_root / ".github/internal-sync-copilot-configs.manifest.json" - ).exists() - assert "AGENTS.md" in manifest["managed_hashes"] - assert manifest["managed_hashes"][".github/agents/internal-fast.agent.md"] - assert manifest["source_version"] == "1.2.3" - assert (target_root / "AGENTS.md").read_text( - encoding="utf-8" - ) == "# AGENTS\nsource\n" - assert (target_root / ".gitignore").read_text( - encoding="utf-8" - ) == "/tmp/superpowers/\n" - - -def test_build_sync_plan_ensures_target_gitignore_entry_without_mirroring_source_gitignore( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(source_root / ".gitignore", "/tmp/\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / ".gitignore", "node_modules/\n") - - plan = build_sync_plan(source_root, target_root) - operations = {(operation.action, operation.path) for operation in plan.operations} - - assert ("ensure", ".gitignore") in operations - assert plan.generated_gitignore == "node_modules/\n/tmp/superpowers/\n" - - -def test_build_sync_plan_accepts_existing_tmp_superpowers_gitignore_entry( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / ".gitignore", "node_modules/\ntmp/superpowers/\n") - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - - assert ("unchanged", ".gitignore") in actions - assert plan.generated_gitignore == "node_modules/\ntmp/superpowers/\n" - - -def test_build_sync_plan_reads_source_and_target_manifest_versions( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / "VERSION", "2.4.0\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file( - target_root / ".github/copilot-sync.manifest.json", - json.dumps({"source_version": "2.3.1"}) + "\n", - ) - - plan = build_sync_plan(source_root, target_root) - - assert plan.source_version == "2.4.0" - assert plan.target_manifest_source_version == "2.3.1" - - -def test_sync_plan_json_reports_dirty_overlap_for_managed_mutations( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\nstale\n") - write_file(target_root / ".gitignore", "/tmp/superpowers/\n") - - init_git_repo(target_root) - commit_all(target_root, "Initial target state") - - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ndirty\n") - write_file(target_root / "notes.txt", "target-only notes\n") - - plan = build_sync_plan(source_root, target_root) - payload = plan.to_dict() - - assert payload["dirty_paths"] == [".github/copilot-instructions.md"] - assert ".github/copilot-instructions.md" in payload["managed_mutation_paths"] - assert payload["dirty_managed_overlap"] == [".github/copilot-instructions.md"] - assert "notes.txt" not in payload["dirty_paths"] - assert "notes.txt" not in payload["dirty_managed_overlap"] - - -def test_apply_sync_plan_creates_target_lessons_from_source_template( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - source_lessons = ( - "# Lessons\n\n" - "Source-managed retained learning ledger.\n\n" - "## Pending Rules\n\n" - "| Date | Lesson | Status | Intended canonical target |\n" - "| --- | --- | --- | --- |\n\n" - "No pending lessons currently.\n" - ) - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(source_root / "LESSONS_LEARNED.md", source_lessons) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - - assert ("create", "LESSONS_LEARNED.md") in actions - - apply_sync_plan(plan) - - assert (target_root / "LESSONS_LEARNED.md").read_text( - encoding="utf-8" - ) == source_lessons - - -def test_apply_sync_plan_realigns_lessons_structure_without_losing_target_rows( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - source_lessons = ( - "# Lessons\n\n" - "Source-managed retained learning ledger.\n\n" - "## Entry Rules\n\n" - "- Use the source structure.\n\n" - "## Pending Rules\n\n" - "| Date | Lesson | Status | Intended canonical target | Notes |\n" - "| --- | --- | --- | --- | --- |\n\n" - "No pending lessons currently.\n" - ) - target_lessons = ( - "# Lessons\n\n" - "Target-only intro that should be replaced.\n\n" - "## Pending Rules\n\n" - "| Date | Lesson | Status | Intended canonical target |\n" - "| --- | --- | --- | --- |\n" - "| 2026-04-12 | Preserve local lesson | pending | AGENTS.md |\n" - ) - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(source_root / "LESSONS_LEARNED.md", source_lessons) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / "LESSONS_LEARNED.md", target_lessons) - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - - assert ("update", "LESSONS_LEARNED.md") in actions - - apply_sync_plan(plan) - - assert (target_root / "LESSONS_LEARNED.md").read_text(encoding="utf-8") == ( - "# Lessons\n\n" - "Source-managed retained learning ledger.\n\n" - "## Entry Rules\n\n" - "- Use the source structure.\n\n" - "## Pending Rules\n\n" - "| Date | Lesson | Status | Intended canonical target | Notes |\n" - "| --- | --- | --- | --- | --- |\n" - "| 2026-04-12 | Preserve local lesson | pending | AGENTS.md | |\n" - ) - - -def test_apply_sync_plan_preserves_multiple_lessons_rows_separated_by_blank_lines( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - source_lessons = ( - "# Lessons\n\n" - "Source-managed retained learning ledger.\n\n" - "## Pending Rules\n\n" - "| Date | Lesson | Status | Intended canonical target |\n" - "| --- | --- | --- | --- |\n\n" - "No pending lessons currently.\n" - ) - target_lessons = ( - "# Lessons\n\n" - "Target-managed retained learning ledger.\n\n" - "## Pending Rules\n\n" - "| Date | Lesson | Status | Intended canonical target |\n" - "| --- | --- | --- | --- |\n" - "| 2026-04-23 | Keep first lesson | pending | first.md |\n\n" - "| 2026-04-21 | Keep second lesson | pending | second.md |\n" - ) - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(source_root / "LESSONS_LEARNED.md", source_lessons) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / "LESSONS_LEARNED.md", target_lessons) - - plan = build_sync_plan(source_root, target_root) - - assert ("update", "LESSONS_LEARNED.md") in { - (operation.action, operation.path) for operation in plan.operations - } - - apply_sync_plan(plan) - - assert (target_root / "LESSONS_LEARNED.md").read_text(encoding="utf-8") == ( - "# Lessons\n\n" - "Source-managed retained learning ledger.\n\n" - "## Pending Rules\n\n" - "| Date | Lesson | Status | Intended canonical target |\n" - "| --- | --- | --- | --- |\n" - "| 2026-04-23 | Keep first lesson | pending | first.md |\n" - "| 2026-04-21 | Keep second lesson | pending | second.md |\n" - ) - - -def test_apply_sync_plan_keeps_no_pending_marker_when_source_has_rows( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - source_lessons = ( - "# Lessons\n\n" - "Source-managed retained learning ledger.\n\n" - "## Pending Rules\n\n" - "| Date | Lesson | Status | Intended canonical target |\n" - "| --- | --- | --- | --- |\n" - "| 2026-04-16 | Centralize shared Terraform lesson | pending | .github/skills/internal-terraform/SKILL.md |\n" - ) - target_lessons = ( - "# Lessons\n\n" - "Target-managed retained learning ledger.\n\n" - "## Pending Rules\n\n" - "| Date | Lesson | Status | Intended canonical target |\n" - "| --- | --- | --- | --- |\n\n" - "No pending lessons currently require codification.\n" - ) - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(source_root / "LESSONS_LEARNED.md", source_lessons) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / "LESSONS_LEARNED.md", target_lessons) - - plan = build_sync_plan(source_root, target_root) - - assert ("update", "LESSONS_LEARNED.md") in { - (operation.action, operation.path) for operation in plan.operations - } - - apply_sync_plan(plan) - - assert (target_root / "LESSONS_LEARNED.md").read_text(encoding="utf-8") == ( - "# Lessons\n\n" - "Source-managed retained learning ledger.\n\n" - "## Pending Rules\n\n" - "| Date | Lesson | Status | Intended canonical target |\n" - "| --- | --- | --- | --- |\n\n" - "No pending lessons currently require codification.\n" - ) - - -def test_build_sync_plan_includes_shared_repo_hygiene_files_only( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(source_root / ".editorconfig", "root = true\n") - write_file(source_root / ".pre-commit-config.yaml", "repos: []\n") - write_file( - source_root / ".github/workflows/_pre-commit.yml", - "name: pre-commit\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / ".editorconfig", "root = false\n") - write_file(target_root / ".pre-commit-config.yaml", "repos:\n - repo: old\n") - write_file( - target_root / ".github/workflows/_pre-commit.yml", - "name: old-pre-commit\n", - ) - write_file(target_root / ".github/workflows/local-only.yml", "name: local-only\n") - - plan = build_sync_plan(source_root, target_root) - actions = {(operation.action, operation.path) for operation in plan.operations} - planned_paths = {operation.path for operation in plan.operations} - - assert ("update", ".editorconfig") in actions - assert ("update", ".pre-commit-config.yaml") in actions - assert ("update", ".github/workflows/_pre-commit.yml") in actions - assert ".github/workflows/local-only.yml" not in planned_paths - - -def test_apply_sync_plan_mirrors_shared_repo_hygiene_files( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(source_root / ".editorconfig", "root = true\n") - write_file(source_root / ".pre-commit-config.yaml", "repos: []\n") - write_file( - source_root / ".github/workflows/_pre-commit.yml", - "name: pre-commit\n", - ) - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file(target_root / ".editorconfig", "root = false\n") - write_file(target_root / ".pre-commit-config.yaml", "repos:\n - repo: old\n") - write_file( - target_root / ".github/workflows/_pre-commit.yml", - "name: old-pre-commit\n", - ) - - plan = build_sync_plan(source_root, target_root) - apply_sync_plan(plan) - - assert (target_root / ".editorconfig").read_text( - encoding="utf-8" - ) == "root = true\n" - assert (target_root / ".pre-commit-config.yaml").read_text( - encoding="utf-8" - ) == "repos: []\n" - assert (target_root / ".github/workflows/_pre-commit.yml").read_text( - encoding="utf-8" - ) == "name: pre-commit\n" - - -def test_build_sync_plan_ensures_vscode_settings_when_missing(tmp_path: Path) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - - plan = build_sync_plan(source_root, target_root) - - assert ("ensure", ".vscode/settings.json") in { - (operation.action, operation.path) for operation in plan.operations - } - - -def test_apply_sync_plan_merges_vscode_settings_in_jsonc_file(tmp_path: Path) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file( - target_root / ".vscode/settings.json", - "{\n" - " // keep local test settings\n" - ' "python.testing.pytestEnabled": true,\n' - ' "github.copilot.chat.codeGeneration.useInstructionFiles": true,\n' - ' "chat.instructionsFilesLocations": {\n' - " // local override\n" - ' ".github/instructions": true,\n' - ' "./team": true,\n' - " },\n" - "}\n", - ) - - plan = build_sync_plan(source_root, target_root) - - assert ("ensure", ".vscode/settings.json") in { - (operation.action, operation.path) for operation in plan.operations - } - - apply_sync_plan(plan) - merged = (target_root / ".vscode/settings.json").read_text(encoding="utf-8") - - assert '"python.testing.pytestEnabled": true' in merged - assert '"github.copilot.chat.codeGeneration.useInstructionFiles": false' in merged - assert '".github/instructions": false' in merged - assert '"./team": true' in merged - assert "// keep local test settings" in merged - assert "// local override" in merged - - -def test_build_sync_plan_marks_vscode_settings_unchanged_when_aligned( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file( - target_root / ".vscode/settings.json", - "{\n" - ' "github.copilot.chat.codeGeneration.useInstructionFiles": false,\n' - ' "chat.instructionsFilesLocations": {\n' - ' ".github/instructions": false\n' - " },\n" - "}\n", - ) - - plan = build_sync_plan(source_root, target_root) - - assert ("unchanged", ".vscode/settings.json") in { - (operation.action, operation.path) for operation in plan.operations - } - - -def test_build_sync_plan_blocks_vscode_settings_on_malformed_jsonc( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file( - target_root / ".vscode/settings.json", - "{\n" - ' "github.copilot.chat.codeGeneration.useInstructionFiles": true,\n' - ' "chat.instructionsFilesLocations": {\n' - ' ".github/instructions": true,\n' - "\n", - ) - - plan = build_sync_plan(source_root, target_root) - - assert ("manual", ".vscode/settings.json") in { - (operation.action, operation.path) for operation in plan.operations - } - - -def test_build_sync_plan_blocks_vscode_settings_on_duplicate_relevant_keys( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - write_file( - target_root / ".vscode/settings.json", - "{\n" - ' "chat.instructionsFilesLocations": {\n' - ' ".github/instructions": true,\n' - ' ".github/instructions": false\n' - " }\n" - "}\n", - ) - - plan = build_sync_plan(source_root, target_root) - - assert ("manual", ".vscode/settings.json") in { - (operation.action, operation.path) for operation in plan.operations - } - - -def test_apply_sync_manifest_tracks_vscode_settings_without_file_hash( - tmp_path: Path, -) -> None: - source_root = tmp_path / "source" - target_root = tmp_path / "target" - - write_file(source_root / "AGENTS.md", "# AGENTS\nsource\n") - write_file(source_root / "VERSION", "9.9.9\n") - write_file(source_root / ".github/copilot-instructions.md", "# Copilot\nsource\n") - write_file(target_root / "AGENTS.md", "# AGENTS\ntarget\n") - write_file(target_root / ".github/copilot-instructions.md", "# Copilot\ntarget\n") - - plan = build_sync_plan(source_root, target_root) - manifest_path = apply_sync_plan(plan) - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - - assert ".vscode/settings.json" not in manifest["managed_hashes"] - assert ( - manifest["managed_settings"][ - "github.copilot.chat.codeGeneration.useInstructionFiles" - ] - is False - ) - assert ( - manifest["managed_settings"][ - "chat.instructionsFilesLocations/.github/instructions" - ] - is False - ) - - -def test_detect_token_risks_reports_root_policy_overlap(tmp_path: Path) -> None: - repeated_lines = "\n".join( - [ - "- Keep policy separate from inventory.", - "- Keep AGENTS.md strategic and stable.", - "- Keep .github/copilot-instructions.md review-only.", - "- Keep .github/INVENTORY.md as the exact catalog.", - "- Preserve explicit precedence rules.", - "- Remove overlap instead of keeping compatibility copies.", - "- Use GitHub Copilot terminology only.", - ] - ) - - write_file(tmp_path / "AGENTS.md", f"# AGENTS\n\n{repeated_lines}\n") - write_file( - tmp_path / ".github/copilot-instructions.md", f"# Copilot\n\n{repeated_lines}\n" - ) - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "root-policy-overlap" in finding_codes - - -def test_detect_token_risks_reports_root_always_on_budget(tmp_path: Path) -> None: - write_file(tmp_path / "AGENTS.md", "# AGENTS\n\n" + ("Root policy line.\n" * 5000)) - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot\n\n" + ("Projection policy line.\n" * 5000), - ) - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "root-always-on-budget" in finding_codes - - -def test_detect_token_risks_reports_delegated_review_prompt_budget( - tmp_path: Path, -) -> None: - write_file(tmp_path / "AGENTS.md", "# AGENTS\n") - write_file(tmp_path / ".github/copilot-instructions.md", "# Copilot\n") - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - write_file( - tmp_path / ".github/prompts/internal-review-ai-resources.prompt.md", - "---\nname: internal-review-ai-resources\n---\n\n" - + ("Reusable review workflow detail stays inline.\n" * 500), - ) - - findings = detect_token_risks(tmp_path) - matching = [ - finding - for finding in findings - if finding.code == "delegated-review-prompt-budget" - ] - - assert len(matching) == 1 - assert matching[0].path == ".github/prompts/internal-review-ai-resources.prompt.md" - - -def test_detect_token_risks_ignores_other_large_prompts_for_delegated_review_budget( - tmp_path: Path, -) -> None: - write_file(tmp_path / "AGENTS.md", "# AGENTS\n") - write_file(tmp_path / ".github/copilot-instructions.md", "# Copilot\n") - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - write_file( - tmp_path / ".github/prompts/internal-mega-review.prompt.md", - "---\nname: internal-mega-review\n---\n\n" - + ("Intentionally broad prompt detail.\n" * 1000), - ) - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "delegated-review-prompt-budget" not in finding_codes - - -def test_detect_token_risks_reports_agents_operational_procedure_markers( - tmp_path: Path, -) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n" - "## Retained Plans\n\n" - "- Keep unresolved questions in `questions.md`.\n" - "- During execution, create matching `done-*` files.\n", - ) - write_file(tmp_path / ".github/copilot-instructions.md", "# Copilot\n") - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "agents-operational-procedure-marker" in finding_codes - - -def test_detect_token_risks_reports_review_baseline_window_missing_core_rules( - tmp_path: Path, -) -> None: - write_file(tmp_path / "AGENTS.md", "# AGENTS\n") - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot\n", - ) - write_file( - tmp_path / ".github/instructions/copilot-code-review.instructions.md", - "# Review\n\n" + ("Repository background before critical rules.\n" * 150), - ) - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "review-baseline-window-missing-core-rules" in finding_codes - - -def test_detect_token_risks_ignores_structural_root_policy_references( - tmp_path: Path, -) -> None: - write_file( - tmp_path / "AGENTS.md", - "# AGENTS\n\n" - "- Use `AGENTS.md` as the repository agent entrypoint.\n" - "- Use `.github/INVENTORY.md` as the live catalog.\n" - "- Use `.github/skills/` when a reusable workflow or technical baseline is relevant.\n" - "- Use `.github/agents/` when a stable owner is relevant.\n", - ) - write_file(tmp_path / ".github/copilot-instructions.md", "# Copilot\n") - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "inventory-dump-in-root-policy" not in finding_codes - - -def test_detect_token_risks_reports_internal_root_policy_overlap( - tmp_path: Path, -) -> None: - root_policy_lines = "\n".join( - [ - "- Keep policy separate from inventory.", - "- Keep AGENTS.md strategic and stable.", - "- Keep .github/copilot-instructions.md as the projection layer.", - "- Keep .github/INVENTORY.md as the exact catalog.", - "- Preserve explicit precedence rules.", - "- Remove overlap instead of keeping compatibility copies.", - "- Keep language exceptions explicit and local.", - "- Keep repository-wide defaults in one canonical place.", - ] - ) - - write_file(tmp_path / "AGENTS.md", f"# AGENTS\n\n{root_policy_lines}\n") - write_file( - tmp_path / ".github/copilot-instructions.md", - "# Copilot\n\n- Keep policy separate from inventory.\n", - ) - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - write_file( - tmp_path / ".github/agents/local-sync-external-resources.agent.md", - "---\nname: local-sync-external-resources\ntools: [read]\n---\n\n" - "# Internal Sync External Resources\n\n" - "Use `AGENTS.md`, `.github/copilot-instructions.md`, and `.github/INVENTORY.md`.\n\n" - f"{root_policy_lines}\n", - ) - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "internal-root-policy-overlap" in finding_codes - - -def test_detect_token_risks_reports_paired_agent_skill_overlap(tmp_path: Path) -> None: - shared_lines = "\n".join( - [ - "- Keep the paired agent short.", - "- The skill owns the reusable sync procedure.", - "- Preserve target local assets during apply.", - "- Exclude internal-sync resources from consumer mirroring.", - "- Keep root-guidance files layered.", - "- Write the tracking plan before mirrored changes.", - ] - ) - - write_file(tmp_path / "AGENTS.md", "# AGENTS\n") - write_file(tmp_path / ".github/copilot-instructions.md", "# Copilot\n") - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - write_file( - tmp_path / ".github/agents/internal-sync-example.agent.md", - "---\n" - "name: internal-sync-example\n" - "tools: [read]\n" - "---\n\n" - "# Internal Sync Example\n\n" - "## Mandatory Engine Skills\n\n" - "- `internal-sync-example`\n\n" - f"{shared_lines}\n", - ) - write_file( - tmp_path / ".github/skills/internal-sync-example/SKILL.md", - "---\n" - "name: internal-sync-example\n" - "description: Sync example\n" - "---\n\n" - "# Internal Sync Example\n\n" - f"{shared_lines}\n", - ) - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "paired-agent-skill-overlap" in finding_codes - - -def test_detect_token_risks_reports_paired_local_agent_skill_overlap( - tmp_path: Path, -) -> None: - shared_lines = "\n".join( - [ - "- Keep the paired agent short.", - "- The skill owns the reusable support workflow.", - "- Keep references as the home for starter templates.", - "- Re-check the paired bundle before finalizing.", - "- Avoid cloning the same subtopic inventory in three places.", - "- Leave routing and boundary language in the agent only.", - ] - ) - - write_file(tmp_path / "AGENTS.md", "# AGENTS\n") - write_file(tmp_path / ".github/copilot-instructions.md", "# Copilot\n") - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - write_file( - tmp_path / ".github/agents/local-sync-example.agent.md", - "---\n" - "name: local-sync-example\n" - "tools: [read]\n" - "---\n\n" - "# Local Sync Example\n\n" - "## Mandatory Engine Skills\n\n" - "- `local-sync-example`\n\n" - f"{shared_lines}\n", - ) - write_file( - tmp_path / ".github/skills/local-sync-example/SKILL.md", - "---\n" - "name: local-sync-example\n" - "description: Local sync example\n" - "---\n\n" - "# Local Sync Example\n\n" - f"{shared_lines}\n", - ) - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "paired-agent-skill-overlap" in finding_codes - - -def test_detect_token_risks_reports_unprofiled_imported_skill_description_budget( - tmp_path: Path, -) -> None: - long_description = ( - "Use when " + "optimizing cloud catalog trigger routing safely. " * 12 - ) - - write_file(tmp_path / "AGENTS.md", "# AGENTS\n") - write_file(tmp_path / ".github/copilot-instructions.md", "# Copilot\n") - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - write_file( - tmp_path / ".github/repo-profiles.yml", - "version: 1\nprofiles:\n minimal:\n recommended_skills: []\n", - ) - write_file( - tmp_path / ".github/skills/awesome-long/SKILL.md", - "---\n" - "name: awesome-long\n" - f"description: {long_description}\n" - "---\n\n" - "# Awesome Long\n", - ) - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "imported-skill-description-budget" in finding_codes - - -def test_detect_token_risks_reports_skill_description_trigger_collision( - tmp_path: Path, -) -> None: - description = "Use when reviewing repository-owned GitHub governance boundaries and validation evidence." - - write_file(tmp_path / "AGENTS.md", "# AGENTS\n") - write_file(tmp_path / ".github/copilot-instructions.md", "# Copilot\n") - write_file(tmp_path / ".github/INVENTORY.md", "# Inventory\n") - write_file( - tmp_path / ".github/skills/internal-one/SKILL.md", - f"---\nname: internal-one\ndescription: {description}\n---\n\n# Internal One\n", - ) - write_file( - tmp_path / ".github/skills/internal-two/SKILL.md", - f"---\nname: internal-two\ndescription: {description}\n---\n\n# Internal Two\n", - ) - - findings = detect_token_risks(tmp_path) - finding_codes = {finding.code for finding in findings} - - assert "skill-description-trigger-collision" in finding_codes - - -def test_sync_contract_requires_target_local_validation_after_apply() -> None: - sync_contract_text = Path( - ".github/skills/local-agent-sync-global-copilot-configs-into-repo/references/sync-contract.md" - ).read_text(encoding="utf-8") - - assert ( - "run the closest target-local catalog or contract validation" - in sync_contract_text - ) - assert ( - "Treat any resulting fixes as consumer-local follow-up work" - in sync_contract_text - ) - - -def test_sync_contract_requires_source_side_convergence_check_without_local_validator() -> ( - None -): - sync_contract_text = Path( - ".github/skills/local-agent-sync-global-copilot-configs-into-repo/references/sync-contract.md" - ).read_text(encoding="utf-8") - - assert ( - "When the target has no local catalog or contract validation script" - in sync_contract_text - ) - assert ( - "python3 ./.github/scripts/sync_copilot_catalog.py plan --target-repo --format json" - in sync_contract_text - ) - assert ( - "zero managed `create`, `update`, `ensure`, `rebuild`, or `delete` operations" - in sync_contract_text - ) - - -def test_sync_contract_restricts_allow_dirty_target_to_overlap_checked_work() -> None: - sync_contract_text = Path( - ".github/skills/local-agent-sync-global-copilot-configs-into-repo/references/sync-contract.md" - ).read_text(encoding="utf-8") - - assert ( - "compare dirty paths against the planned managed mutations" - in sync_contract_text - ) - assert "do not use `--allow-dirty-target` as a blanket bypass" in sync_contract_text - - -def test_root_always_on_token_budget_contract_uses_validator_constants() -> None: - agents_text = Path("AGENTS.md").read_text(encoding="utf-8") - validator_text = Path(".github/scripts/lib/token_risks.py").read_text( - encoding="utf-8" - ) - shared_baseline = agents_text.split("``", 1)[1].split( - "``", 1 - )[0] - local_rules = agents_text.split("``", 1)[1].split( - "``", 1 - )[0] - calculated_estimates = { - file_path: estimate_tokens(Path(file_path)) - for file_path in ROOT_ALWAYS_ON_PATHS - } - - assert "## Estimated Fixed-Load Token Budget" not in agents_text - assert "primary always-on repository policy entrypoint" in agents_text - assert "ROOT_ALWAYS_ON_PATHS = (\"AGENTS.md\",)" in validator_text - assert ( - f"ROOT_ALWAYS_ON_TOKEN_TARGET = {ROOT_ALWAYS_ON_TOKEN_TARGET}" - in validator_text - ) - assert "AGENTS.md exceeds the canonical always-on " in validator_text - assert "soft target" in validator_text - # Local validator commands stay in standards-repository-local-rules. - assert "`make token-risks`" not in shared_baseline - assert "`python3 ./.github/scripts/detect_token_risks.py --root .`" not in shared_baseline - assert "`make token-risks`" in local_rules - assert "`python3 ./.github/scripts/detect_token_risks.py --root .`" in local_rules - assert "`make token-risks`" in agents_text - assert set(calculated_estimates) == set(ROOT_ALWAYS_ON_PATHS) diff --git a/tests/test_sync_resource_fingerprint.py b/tests/test_sync_resource_fingerprint.py deleted file mode 100644 index 9655f0b..0000000 --- a/tests/test_sync_resource_fingerprint.py +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import annotations - -import json -import subprocess -import sys -from pathlib import Path - -SCRIPT = Path( - ".github/skills/local-agent-sync-external-resources/scripts/sync_resource_fingerprint.py" -).resolve() - - -def run_script(*args: str) -> subprocess.CompletedProcess[str]: - return subprocess.run( - [sys.executable, str(SCRIPT), *args], - capture_output=True, - text=True, - check=True, - ) - - -def test_snapshot_writes_manifest_and_uses_relative_resource_ids( - tmp_path: Path, -) -> None: - repo = tmp_path / "repo" - repo.mkdir() - input_file = repo / "sample.txt" - input_file.write_text("hello\n", encoding="utf-8") - output_file = repo / "manifest.json" - - result = run_script( - "snapshot", - "sample.txt", - "--root", - str(repo), - "--output", - str(output_file), - "--source-ref-base", - "upstream/resources", - ) - - assert output_file.as_posix() in result.stdout - payload = json.loads(output_file.read_text(encoding="utf-8")) - resource = payload["resources"][0] - assert payload["hash_algo"] == "sha256" - assert resource["resource_id"] == "sample.txt" - assert resource["target_path"] == "sample.txt" - assert resource["source_ref"] == "upstream/resources/sample.txt" - - -def test_diff_json_reports_created_removed_and_changed(tmp_path: Path) -> None: - old_manifest = tmp_path / "old.json" - new_manifest = tmp_path / "new.json" - old_manifest.write_text( - json.dumps( - { - "resources": [ - { - "resource_id": "a.txt", - "source_hash": "111", - "content_hash": "111", - }, - { - "resource_id": "b.txt", - "source_hash": "222", - "content_hash": "222", - }, - ] - } - ), - encoding="utf-8", - ) - new_manifest.write_text( - json.dumps( - { - "resources": [ - { - "resource_id": "b.txt", - "source_hash": "333", - "content_hash": "333", - }, - { - "resource_id": "c.txt", - "source_hash": "444", - "content_hash": "444", - }, - ] - } - ), - encoding="utf-8", - ) - - result = run_script( - "diff", - "--old", - str(old_manifest), - "--new", - str(new_manifest), - "--format", - "json", - ) - payload = json.loads(result.stdout) - - assert payload["summary"]["created"] == 1 - assert payload["summary"]["removed"] == 1 - assert payload["summary"]["changed"] == 1 diff --git a/tests/test_token_budget_skill_contract.py b/tests/test_token_budget_skill_contract.py deleted file mode 100644 index fb799b3..0000000 --- a/tests/test_token_budget_skill_contract.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - - -def read_text(relative_path: str) -> str: - return Path(relative_path).read_text(encoding="utf-8") - - -def test_token_budget_guardrails_are_distributed_by_owner() -> None: - simple_text = read_text(".github/skills/internal-gateway-simple-task/SKILL.md") - plan_mode_text = read_text( - ".github/skills/internal-gateway-simple-task/references/plan-mode.md" - ) - idea_text = read_text(".github/skills/internal-gateway-idea-brainstorming/SKILL.md") - execute_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") - review_text = read_text(".github/skills/internal-gateway-review/SKILL.md") - spreadsheet_text = read_text(".github/skills/openai-spreadsheet/SKILL.md") - - assert "Token Budget Gate" in simple_text - assert "Copilot or debug log analysis" in simple_text - assert "Token Budget Gate" in plan_mode_text - assert "aggregate-first" in idea_text - assert "Compact Evidence Reporting" in execute_text - assert "Compact Evidence Reporting" in review_text - assert "Structured Data Evidence Budget" in spreadsheet_text - - -def test_token_budget_gate_stays_runner_agnostic_and_non_disruptive() -> None: - simple_text = read_text(".github/skills/internal-gateway-simple-task/SKILL.md") - plan_mode_text = read_text( - ".github/skills/internal-gateway-simple-task/references/plan-mode.md" - ) - - assert "do not require `jq`, `awk`" in simple_text - assert "does not interrupt ordinary conversation" in simple_text - assert "token or context impact" in simple_text - assert "does not interrupt ordinary conversation" in plan_mode_text - assert "token or context impact" in plan_mode_text - - -def test_structured_data_guardrails_preserve_full_file_correctness() -> None: - spreadsheet_text = read_text(".github/skills/openai-spreadsheet/SKILL.md") - lowered = spreadsheet_text.lower() - - assert ".xlsx" in spreadsheet_text - assert ".csv" in spreadsheet_text - assert ".tsv" in spreadsheet_text - assert "row counts" in lowered - assert "column counts" in lowered - assert "full-file" in lowered - assert "sampling does not replace full-file validation" in lowered - assert "source links" in lowered - assert "duplicate ids" in lowered diff --git a/tests/test_validation_entrypoints_contract.py b/tests/test_validation_entrypoints_contract.py deleted file mode 100644 index 8816084..0000000 --- a/tests/test_validation_entrypoints_contract.py +++ /dev/null @@ -1,151 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - - -def read_text(relative_path: str) -> str: - return Path(relative_path).read_text(encoding="utf-8") - - -def test_makefile_lint_target_covers_docs_lint_without_double_running_from_all() -> ( - None -): - makefile_text = read_text("Makefile") - - assert "lint: python-version-check docs-lint" in makefile_text - assert "all: lint test catalog-check" in makefile_text - assert "all: lint test catalog-check docs-lint" not in makefile_text - - -def test_makefile_uses_quiet_compileall_for_local_catalog_linting() -> None: - makefile_text = read_text("Makefile") - - assert "$(PYTHON) -m compileall -q $(PYTHON_PATHS)" in makefile_text - assert "$(PYTHON) -m compileall $(PYTHON_PATHS)" not in makefile_text - - -def test_makefile_exposes_explicit_catalog_fast_check_entrypoint() -> None: - makefile_text = read_text("Makefile") - - assert "catalog-fast-check" in makefile_text - assert "build_inventory --root . --check" in makefile_text - assert "check_catalog_consistency --root ." in makefile_text - assert "validate_internal_skills --root . --strict" in makefile_text - assert "tests/test_inventory_and_consistency.py" in makefile_text - assert "tests/test_validation_entrypoints_contract.py" in makefile_text - assert "tests/test_retained_plan_artifact_contract.py" in makefile_text - assert "tests/github/scripts/test_cli_entrypoints.py" in makefile_text - assert "CATALOG_FAST_INCLUDE_TOKEN_RISKS=1" in makefile_text - - -def test_makefile_does_not_expose_legacy_graphify_wrapper_entrypoints() -> None: - makefile_text = read_text("Makefile") - - assert ( - ".PHONY: help python-version-check lint catalog-lint catalog-fast-check github-catalog-validation test" - in makefile_text - ) - assert "graphify-update:" not in makefile_text - assert "graphify-check:" not in makefile_text - assert "graphify-prepare:" not in makefile_text - assert "graphify_update" not in makefile_text - - -def test_internal_graphify_wrapper_files_are_removed() -> None: - assert not Path(".github/skills/internal-graphify/SKILL.md").exists() - assert not Path(".github/skills/internal-graphify/agents/openai.yaml").exists() - - -def test_docs_lint_target_does_not_require_npm_network_outside_ci() -> None: - makefile_text = read_text("Makefile") - - assert "MARKDOWNLINT_VERSION := 0.18.1" in makefile_text - assert "command -v markdownlint-cli2" in makefile_text - assert "npm exec --offline --yes markdownlint-cli2@" in makefile_text - assert 'if [ -n "$${CI:-}" ]; then' in makefile_text - assert "skipping markdown lint outside CI" in makefile_text - - -def test_github_catalog_validation_workflow_uses_canonical_wrapper_entrypoints() -> ( - None -): - workflow_text = read_text(".github/workflows/_github-catalog-validation.yml") - - assert ( - "bash ./.github/scripts/github_catalog_validation.sh --skip-token-risks" - in workflow_text - ) - assert ( - "bash ./.github/scripts/github_catalog_validation.sh --token-risks-only" - in workflow_text - ) - assert "python ./.github/scripts/github_catalog_validation.py" not in workflow_text - - -def test_graphify_wrapper_shortcut_is_removed_from_validation_wrappers() -> None: - root_wrapper = read_text("github_catalog_validation.sh") - scripts_wrapper = read_text(".github/scripts/github_catalog_validation.sh") - runner_text = read_text(".github/scripts/run.sh") - - assert "--graphify" not in root_wrapper - assert "--graphify" not in scripts_wrapper - assert "graphify_update" not in runner_text - - -def test_runner_dispatch_includes_diagnostic_cli_aliases() -> None: - runner_text = read_text(".github/scripts/run.sh") - - assert "analyze_copilot_debug_log|analyze_copilot_debug_log.sh" in runner_text - assert "benchmark_skill_tokens|benchmark_skill_tokens.py" in runner_text - - -def test_copilot_analyzer_has_single_canonical_wrapper() -> None: - assert not Path(".github/scripts/analyze_copilot_prompt_exports.py").exists() - assert not Path(".github/scripts/analyze_copilot_debug_logs.py").exists() - assert Path("tools/analyze_copilot_debug_log/prompt_exports.py").exists() - assert Path("tools/analyze_copilot_debug_log/debug_logs.py").exists() - assert Path("tools/analyze_copilot_debug_log/cli.py").exists() - assert Path("tools/analyze_copilot_debug_log/__main__.py").exists() - assert Path("tools/analyze_copilot_debug_log/run.sh").exists() - assert Path("tools/analyze_copilot_debug_log/requirements.txt").exists() - - -def test_code_analysis_workflow_smoke_tests_documented_sync_wrappers() -> None: - workflow_text = read_text(".github/workflows/_code-analysis.yml") - - assert "bash -n .github/scripts/sync_home_ai_resources.sh" in workflow_text - assert ( - "bash -n .github/skills/local-agent-sync-install-ai-resources/scripts/run.sh" - in workflow_text - ) - - -def test_code_analysis_workflow_smoke_tests_runner_diagnostic_clis() -> None: - workflow_text = read_text(".github/workflows/_code-analysis.yml") - - assert ( - "bash tools/analyze_copilot_debug_log/run.sh prompt-exports --help" - in workflow_text - ) - assert ( - "bash tools/analyze_copilot_debug_log/run.sh debug-logs --help" in workflow_text - ) - assert "./.github/scripts/run.sh analyze_copilot_debug_log --help" in workflow_text - assert "./.github/scripts/run.sh benchmark_skill_tokens --help" in workflow_text - - -def test_local_copilot_log_analyzer_skill_routes_canonical_wrapper() -> None: - skill_text = read_text(".github/skills/local-copilot-log-analyzer/SKILL.md") - agent_text = read_text( - ".github/skills/local-copilot-log-analyzer/agents/openai.yaml" - ) - inventory_text = read_text(".github/INVENTORY.md") - - assert "## Referenced skills" in skill_text - assert "- None." in skill_text - assert "tools/analyze_copilot_debug_log/run.sh" in skill_text - assert "./.github/scripts/run.sh analyze_copilot_debug_log --help" in skill_text - assert "aggregate-first" in skill_text - assert "avoid full json dumps" in skill_text.lower() - assert "$local-copilot-log-analyzer" in agent_text - assert ".github/skills/local-copilot-log-analyzer/SKILL.md" in inventory_text diff --git a/tests/test_workflow_review_contract.py b/tests/test_workflow_review_contract.py deleted file mode 100644 index 1a689ee..0000000 --- a/tests/test_workflow_review_contract.py +++ /dev/null @@ -1,233 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - - -def read_text(relative_path: str) -> str: - return Path(relative_path).read_text(encoding="utf-8") - - -def test_idea_gateway_owns_retained_planning() -> None: - skill_text = read_text( - ".github/skills/internal-gateway-idea-brainstorming/SKILL.md" - ) - reference_text = read_text( - ".github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md" - ) - runtime_text = read_text( - ".github/skills/internal-gateway-idea-brainstorming/agents/openai.yaml" - ) - assert "retained plan" in skill_text - assert "internal-gateway-writing-plans" in skill_text - assert "stop before execution" in skill_text - assert "Plan Approval Gate 3" in skill_text - assert "Handoff Gate 4" in skill_text - assert "ask whether to continue" in skill_text - assert "Specialization Checkpoint: gated" in skill_text - assert ( - "User insistence does not bypass Idea Gate 0 or Critical Gate 2" in skill_text - ) - assert "go`/`ok`/`procedi" in skill_text - assert "Plan Approval Gate 3: waiting" in reference_text - assert "Handoff Gate 4: plan-created" in reference_text - assert "Specialization Checkpoint: gated" in reference_text - assert "Direct Execution vs Retained Plan Recommendation" in skill_text - assert "direct execution via `internal-gateway-simple-task`" in skill_text - assert "Recommendation`, `Why`, `Tradeoff`, and `Decision`" in reference_text - assert "mini-plan" in reference_text - assert "go/ok/procedi" in runtime_text - assert "Specialization Checkpoint: gated" in runtime_text - assert "Direct Execution vs Retained Plan Recommendation" in runtime_text - assert "choose execute, plan, or an explicit override" in runtime_text - assert "ask whether the user wants this owner to keep the task" not in skill_text - assert ( - "ask whether the user wants this owner to keep the task" not in reference_text - ) - assert "ask whether the user wants this owner to keep the task" not in runtime_text - assert ( - "At Interview Gate 1: ready-for-critical, ask whether to continue" - in runtime_text - ) - - -def test_review_gateway_exists_and_stops_before_fixes() -> None: - skill_text = read_text(".github/skills/internal-gateway-review/SKILL.md") - agent_text = read_text(".github/agents/internal-gateway-review.agent.md") - review_gate_text = read_text( - ".github/skills/internal-gateway-review/references/review-gate.md" - ) - review_gate_lower = review_gate_text.lower() - assert "defect-first review" in skill_text - assert "does not apply fixes" in skill_text - assert "internal-gateway-writing-plans" in skill_text - assert "internal-ai-resource-review" in skill_text - assert "internal-copilot-audit" in skill_text - assert "Lens selection" in skill_text - assert ".github/skills/**" in skill_text - assert "internal-gateway-simple-task" in agent_text - assert "Review Gate" in review_gate_text - assert "severity" in review_gate_lower - assert "confidence" in review_gate_lower - assert "evidence gap" in review_gate_lower - assert "counter-validation" in skill_text - assert "counter-validation" in review_gate_lower - assert "route or next owner" in review_gate_lower - assert "decision-usefulness" in review_gate_lower - assert "accept, patch, investigate, plan" in review_gate_lower - - -def test_review_usefulness_contract_is_explicit() -> None: - gateway_text = read_text(".github/skills/internal-gateway-review/SKILL.md") - ai_review_text = read_text(".github/skills/internal-ai-resource-review/SKILL.md") - report_text = read_text( - ".github/skills/internal-ai-resource-review/references/report-contract.md" - ) - profile_text = read_text( - ".github/skills/internal-ai-resource-review/references/review-profiles.md" - ) - replay_text = read_text( - ".github/skills/internal-ai-resource-review/references/review-usefulness-replay-fixture.md" - ) - - assert "decision-usefulness" in gateway_text - assert "clear next decision" in gateway_text - assert "Bundle coverage rules" in ai_review_text - assert "review-usefulness-replay-fixture.md" in ai_review_text - assert ( - "live prompt pack, generated artifact, retained report, or fixture" - in ai_review_text - ) - assert "evidence digest or decision trace" in ai_review_text - assert "Adaptive layout patterns" in report_text - assert "Evidence compression" in report_text - assert "Missing proof handling" in report_text - assert "test-gap" in report_text - assert "runtime-artifact" in report_text - assert "No-finding and low-finding reviews" in report_text - assert "Report coverage separately from findings" in profile_text - assert "coach-personale" in replay_text - assert "Focused pytest execution was unavailable" in replay_text - assert "Does not invent additional findings" in replay_text - - -def test_compact_and_extended_execution_owner_is_unified() -> None: - writing_text = read_text(".github/skills/internal-gateway-writing-plans/SKILL.md") - simple_text = read_text(".github/skills/internal-gateway-simple-task/SKILL.md") - critical_text = read_text( - ".github/skills/internal-gateway-critical-master/SKILL.md" - ) - executing_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") - assert "superpowers-writing-plans" in writing_text - assert "delegate" in writing_text.lower() - assert "internal-gateway-execute-plans" in simple_text - assert "internal-executing-plans" not in simple_text - assert "internal-gateway-execute-plans" in critical_text - assert "internal-executing-plans" not in critical_text - assert "gateway-only wrapper" in executing_text - assert "approved retained plans" in executing_text - assert "internal-gateway-execute-plans" in executing_text - - -def test_gateway_status_file_contract_is_explicit() -> None: - simple_text = read_text(".github/skills/internal-gateway-simple-task/SKILL.md") - executing_text = read_text(".github/skills/internal-gateway-execute-plans/SKILL.md") - - assert "mandatory applicable requirements" in simple_text - assert "pre-close compliance audit" in simple_text - assert "Block completion claims" in simple_text - assert "single-lane and single-phase" in simple_text - - assert "gateway-only wrapper" in executing_text - assert "Compact Evidence Reporting" in executing_text - assert "..md" in executing_text - assert "No `DONE` claim" in executing_text - - -def test_simple_gateway_direct_execution_control_contract_is_explicit() -> None: - simple_text = read_text(".github/skills/internal-gateway-simple-task/SKILL.md") - lanes_text = read_text( - ".github/skills/internal-gateway-simple-task/references/simple-lanes.md" - ) - runtime_text = read_text( - ".github/skills/internal-gateway-simple-task/agents/openai.yaml" - ) - - assert "Direct Execution Control" in simple_text - assert "Direct Completion Control" in simple_text - assert "original intent, separated from emerged requirements" in simple_text - assert "all in-scope source items" in runtime_text - assert "mandatory applicable requirements are closed" in runtime_text - assert "direct-control status" in lanes_text - assert "One successful validator" in simple_text - assert "emerged in-scope requirements" in simple_text - - -def test_critical_master_claim_discipline_contract() -> None: - critical_text = read_text( - ".github/skills/internal-gateway-critical-master/SKILL.md" - ) - - assert "Claim Discipline" in critical_text - assert "confirmed" in critical_text - assert "inference" in critical_text - assert "estimate" in critical_text - assert "unsupported numeric precision" in critical_text - assert "original intent" in critical_text - assert "emerged requirements" in critical_text - - -def test_gateway_wrappers_route_compact_and_extended_to_execute_plans() -> None: - simple_wrapper = read_text( - ".github/skills/internal-gateway-simple-task/agents/openai.yaml" - ) - execute_wrapper = read_text( - ".github/skills/internal-gateway-execute-plans/agents/openai.yaml" - ) - status_reference = read_text( - ".github/skills/internal-gateway-execute-plans/references/status-file.md" - ) - assert ( - "Route approved retained-plan execution to internal-gateway-execute-plans" - in simple_wrapper - ) - assert "superpowers-executing-plans" in execute_wrapper - assert "..md" in execute_wrapper - assert "## Resume Notes" in status_reference - - -def test_simple_gateway_readiness_brief_and_approval_gate_contract() -> None: - simple_text = read_text(".github/skills/internal-gateway-simple-task/SKILL.md") - clarification_text = read_text( - ".github/skills/internal-gateway-simple-task/references/clarification-gate.md" - ) - - assert "Readiness Brief" in simple_text - assert "explicit user approval" in simple_text - assert "before operational" in simple_text - assert "Simple Gate Policy" in simple_text - assert "`full`, `idea`, and `complete`" in simple_text - assert "Trivial-skip proof" in simple_text - assert "Run `grill-me` first" in simple_text - assert "critical gate" in simple_text - - assert "compact focused `grill-me` block" in clarification_text - assert "internal-gateway-critical-master" in clarification_text - assert "material risk" in clarification_text - assert "Depth Keyword Override" in clarification_text - assert "Do not use `trivial-skip`" in clarification_text - - -def test_writing_plans_scaffold_first_and_audit_early_contract() -> None: - writing_text = read_text(".github/skills/internal-gateway-writing-plans/SKILL.md") - runtime_text = read_text( - ".github/skills/internal-gateway-writing-plans/agents/openai.yaml" - ) - - assert "short preflight" in writing_text - assert "Execution-readiness check" in writing_text - assert "tmp/superpowers/plans/YYYY-MM-DD-.md" in writing_text - assert "repository preflight wrapper" in runtime_text - assert "superpowers-writing-plans" in runtime_text - assert "delegate" in runtime_text.lower() - assert "Execution-readiness check" in runtime_text - assert "handoff readiness" in runtime_text diff --git a/tests/tools/analyze_copilot_debug_log/test_entrypoint.py b/tests/tools/analyze_copilot_debug_log/test_entrypoint.py deleted file mode 100644 index f1259f3..0000000 --- a/tests/tools/analyze_copilot_debug_log/test_entrypoint.py +++ /dev/null @@ -1,513 +0,0 @@ -from __future__ import annotations - -import json -import os -import subprocess -import sys -from pathlib import Path - -from tools.analyze_copilot_debug_log import cli, debug_logs, main, prompt_exports - -SCRIPT = Path("tools/analyze_copilot_debug_log/run.sh").resolve() -REPO_ROOT = Path(__file__).resolve().parents[3] - - -def run_tool(*args: str) -> subprocess.CompletedProcess[str]: - env = os.environ.copy() - env["PYTHON_BIN"] = sys.executable - return subprocess.run( - ["bash", str(SCRIPT), *args], - cwd=REPO_ROOT, - capture_output=True, - text=True, - check=True, - env=env, - ) - - -def test_prompt_exports_subcommand_summarizes_and_dedupes(tmp_path: Path) -> None: - system_text = """ - -internal-gateway-execute-plans -/Users/example/.agents/skills/internal-gateway-execute-plans/SKILL.md ---- -name: internal-gateway-execute-plans -description: Execute approved retained plans with validation evidence. ---- - - -superpowers-executing-plans -/Users/example/.agents/skills/superpowers-executing-plans/SKILL.md ---- -name: superpowers-executing-plans -description: Execute written implementation plans step by step. ---- - -PRIVATE SYSTEM BODY -""" - prompt_export = { - "exportedAt": "2026-06-10T08:33:51Z", - "prompts": [ - { - "promptId": "prompt-1", - "title": "Prompt one", - "logs": [ - { - "name": "model_request", - "kind": "request", - "metadata": { - "copilotUsageAic": 1.25, - "usage": { - "prompt_tokens": 100, - "prompt_tokens_details": {"cached_tokens": 70}, - "completion_tokens": 12, - "reasoning_tokens": 3, - }, - }, - "requestMessages": { - "messages": [ - { - "role": 0, - "content": [{"type": 1, "text": system_text}], - }, - { - "role": 1, - "content": [ - { - "type": 1, - "text": "PRIVATE USER BODY", - "path": "docs/repeated.md", - } - ], - }, - ] - }, - }, - { - "kind": "toolCall", - "tool": "tool_search", - "args": {"query": "graphify"}, - "result": {"message": "BBBBB"}, - "metadata": { - "usage": { - "prompt_tokens": 40, - "completion_tokens": 2, - "copilot_usage": {"total_nano_aiu": 12.5}, - } - }, - "requestMessages": { - "messages": [ - { - "role": 0, - "content": [{"type": 1, "text": system_text}], - }, - { - "role": 1, - "content": [ - { - "type": 1, - "text": "PRIVATE USER BODY", - "path": "docs/repeated.md", - } - ], - }, - ] - }, - }, - { - "kind": "toolCall", - "tool": {"name": "retry_tool_search"}, - "arguments": {"query": "graphify"}, - "output": {"message": "BBBBB"}, - "metadata": { - "usage": { - "prompt_tokens": 40, - "completion_tokens": 2, - "copilot_usage": {"total_nano_aiu": 12.5}, - } - }, - }, - ], - } - ], - } - prompt_a = tmp_path / "prompt-a.json" - prompt_b = tmp_path / "prompt-b.json" - prompt_a.write_text(json.dumps(prompt_export), encoding="utf-8") - prompt_b.write_text( - json.dumps({**prompt_export, "exportedAt": "2026-06-10T09:00:00Z"}), - encoding="utf-8", - ) - - result = run_tool("prompt-exports", str(prompt_a), str(prompt_b)) - payload = json.loads(result.stdout) - - assert payload["prompt_export_count"] == 2 - assert payload["deduped_prompt_export_count"] == 1 - assert payload["unsupported_input_count"] == 0 - assert payload["aggregate"]["prompt_count"] == 1 - assert payload["aggregate"]["request_count"] == 3 - assert payload["aggregate"]["prompt_tokens"] == 180 - assert payload["aggregate"]["cache_read_tokens"] == 70 - assert payload["aggregate"]["non_cached_input_tokens"] == 110 - assert payload["aggregate"]["completion_tokens"] == 16 - assert payload["aggregate"]["reasoning_tokens"] == 3 - assert payload["aggregate"]["aiu_total"] == 26.25 - assert payload["aggregate"]["max_prompt_tokens"] == 100 - assert payload["aggregate"]["cache_read_ratio"] == 0.3889 - assert payload["aggregate"]["tool_calls"] == 2 - assert payload["aggregate"]["tool_counts_by_name"] == { - "retry_tool_search": 1, - "tool_search": 1, - } - assert payload["aggregate"]["tool_payload_bytes"] > 0 - assert ( - payload["aggregate"]["top_tool_payloads"][0]["tool_name"] == "retry_tool_search" - ) - assert ( - payload["aggregate"]["top_tool_payloads"][0]["payload_bytes"] - >= payload["aggregate"]["top_tool_payloads"][1]["payload_bytes"] - ) - assert payload["aggregate"]["retry_like_duplicate_count"] == 1 - assert payload["aggregate"]["retry_like_duplicate_records"][0]["occurrences"] == 2 - assert payload["prompts"][0]["context_growth_tokens"] == 0 - composition = payload["aggregate"]["composition"] - assert composition["system_message_count"] == 2 - assert composition["repeated_system_message_hashes"][0]["occurrences"] == 2 - assert composition["skill_metadata_block_count"] == 4 - assert composition["gateway_superpowers_co_present_count"] == 2 - assert composition["duplicate_attachment_paths"] == [ - {"path": "docs/repeated.md", "occurrences": 2} - ] - assert composition["largest_skill_descriptions"][0]["skill_id"] in { - "internal-gateway-execute-plans", - "superpowers-executing-plans", - } - assert "PRIVATE SYSTEM BODY" not in result.stdout - assert "PRIVATE USER BODY" not in result.stdout - - -def test_prompt_exports_reports_sequence_cost_diagnostics(tmp_path: Path) -> None: - prompt_export = { - "prompts": [ - { - "promptId": "prompt-sequence", - "logs": [ - { - "id": "request-start", - "kind": "request", - "metadata": { - "model": "gpt-test", - "usage": { - "prompt_tokens": 100_000, - "prompt_tokens_details": {"cached_tokens": 95_000}, - "completion_tokens": 100, - }, - }, - }, - { - "id": "patch-big", - "kind": "toolCall", - "tool": "apply_patch", - "args": "A" * 12_000, - "response": "patched", - }, - { - "id": "request-after-patch", - "kind": "request", - "metadata": { - "model": "gpt-test", - "usage": { - "prompt_tokens": 114_000, - "prompt_tokens_details": {"cached_tokens": 90_000}, - "completion_tokens": 50, - }, - }, - }, - { - "id": "tiny-tool", - "kind": "toolCall", - "tool": "manage_todo_list", - "args": {"todoList": []}, - "response": "ok", - }, - { - "id": "request-cache-drop", - "kind": "request", - "metadata": { - "model": "gpt-test", - "usage": { - "prompt_tokens": 114_500, - "prompt_tokens_details": {"cached_tokens": 70_000}, - "completion_tokens": 25, - }, - }, - }, - ], - } - ] - } - prompt_path = tmp_path / "prompt-sequence.json" - prompt_path.write_text(json.dumps(prompt_export), encoding="utf-8") - - result = run_tool("prompt-exports", str(prompt_path)) - payload = json.loads(result.stdout) - - prompt_summary = payload["prompts"][0] - top_spike = prompt_summary["top_non_cached_spikes"][0] - assert top_spike == { - "prompt_id": "prompt-sequence", - "request_id": "request-cache-drop", - "model": "gpt-test", - "prompt_tokens": 114_500, - "cached_tokens": 70_000, - "non_cached_input_tokens": 44_500, - "previous_cached_tokens": 90_000, - "cache_delta_tokens": -20_000, - "prompt_delta_tokens": 500, - "previous_tool_payload_bytes": 17, - "previous_tool_names": ["manage_todo_list"], - } - assert prompt_summary["cache_drop_events"] == [top_spike] - - payload_candidate = prompt_summary["payload_to_noncache_candidates"][0] - assert payload_candidate["request_id"] == "request-after-patch" - assert payload_candidate["previous_tool_names"] == ["apply_patch"] - assert payload_candidate["previous_tool_payload_bytes"] >= 12_000 - - aggregate = payload["aggregate"] - assert aggregate["top_non_cached_spikes"][0] == top_spike - assert aggregate["cache_drop_events"][0] == top_spike - assert aggregate["payload_to_noncache_candidates"][0] == payload_candidate - - markdown_result = run_tool( - "prompt-exports", str(prompt_path), "--format", "markdown" - ) - assert "## Sequence Diagnostics" in markdown_result.stdout - assert "prompt-sequence/request-cache-drop" in markdown_result.stdout - assert "previous tools manage_todo_list" in markdown_result.stdout - - -def test_debug_logs_subcommand_summarizes_otlp_and_dedupes_prompt_exports( - tmp_path: Path, -) -> None: - otlp_path = tmp_path / "otlp.json" - otlp_path.write_text( - json.dumps( - { - "resourceSpans": [ - { - "resource": { - "attributes": [ - { - "key": "service.name", - "value": {"stringValue": "copilot-chat"}, - }, - { - "key": "session.id", - "value": {"stringValue": "sess-1"}, - }, - ] - }, - "scopeSpans": [ - { - "spans": [ - { - "name": "chat:gpt-5.4-mini", - "status": {"code": 0}, - "attributes": [ - { - "key": "gen_ai.usage.input_tokens", - "value": {"intValue": 40}, - }, - { - "key": "gen_ai.usage.output_tokens", - "value": {"intValue": 8}, - }, - ], - }, - { - "name": "tool_search", - "status": {"code": 0}, - "attributes": [ - { - "key": "gen_ai.usage.input_tokens", - "value": {"intValue": 10}, - }, - { - "key": "gen_ai.usage.output_tokens", - "value": {"intValue": 1}, - }, - { - "key": "gen_ai.tool.name", - "value": {"stringValue": "tool_search"}, - }, - { - "key": "gen_ai.tool.call.result", - "value": {"stringValue": "AAAA"}, - }, - ], - }, - { - "name": "graphify_query", - "status": {"code": 2}, - "attributes": [ - { - "key": "gen_ai.usage.input_tokens", - "value": {"intValue": 10}, - }, - { - "key": "gen_ai.usage.output_tokens", - "value": {"intValue": 3}, - }, - { - "key": "gen_ai.tool.name", - "value": { - "stringValue": "graphify_query" - }, - }, - { - "key": "gen_ai.tool.call.result", - "value": {"stringValue": "BBBBB"}, - }, - ], - }, - ] - } - ], - } - ] - } - ), - encoding="utf-8", - ) - prompt_a = tmp_path / "prompt-a.json" - prompt_b = tmp_path / "prompt-b.json" - prompt_payload = { - "exportedAt": "2026-06-10T08:33:51Z", - "copilotChat": { - "sessionId": "lane-session-1", - "sessionTitle": "Internal Gateway Execute Plans", - }, - "prompts": [ - { - "promptId": "prompt-1", - "logs": [ - { - "name": "tool_search", - "kind": "info", - "metadata": { - "maxPromptTokens": 200, - "usage": { - "prompt_tokens": 200, - "prompt_tokens_details": {"cached_tokens": 150}, - "completion_tokens": 25, - "copilot_usage": {"total_nano_aiu": 12.5}, - }, - }, - "response": {"type": "message", "message": "CCCCCC"}, - } - ], - } - ], - } - prompt_a.write_text(json.dumps(prompt_payload), encoding="utf-8") - prompt_b.write_text( - json.dumps({**prompt_payload, "exportedAt": "2026-06-10T08:40:00Z"}), - encoding="utf-8", - ) - - result = run_tool("debug-logs", str(otlp_path), str(prompt_a), str(prompt_b)) - payload = json.loads(result.stdout) - - assert payload["snapshot_export_count"] == 2 - assert payload["deduped_snapshot_count"] == 1 - assert payload["unsupported_input_count"] == 0 - assert payload["aggregate"]["request_count"] == 4 - assert payload["aggregate"]["tool_result_bytes"] == 15 - assert payload["aggregate"]["error_count"] == 1 - assert payload["aggregate"]["graphify_invocation_count"] == 1 - assert payload["aggregate"]["graphify_discovery_count"] == 2 - assert payload["aggregate"]["cache_read_tokens"] == 150 - assert payload["aggregate"]["non_cached_input_tokens"] == 50 - assert payload["aggregate"]["aiu_total"] == 12.5 - lane_sessions = [ - session - for session in payload["sessions"] - if session.get("session_id") == "lane-session-1" - ] - assert lane_sessions - assert lane_sessions[0]["title"] == "Internal Gateway Execute Plans" - assert all( - "cccccc" not in json.dumps(session).lower() for session in payload["sessions"] - ) - - -def test_debug_logs_subcommand_can_render_markdown(tmp_path: Path) -> None: - otlp_path = tmp_path / "otlp.json" - otlp_path.write_text( - json.dumps( - { - "sessions": [ - { - "id": "sess-2", - "title": "Sync reporting", - "requests": [ - {"input_tokens": 10, "output_tokens": 5, "tool_calls": []} - ], - } - ] - } - ), - encoding="utf-8", - ) - - result = run_tool("debug-logs", str(otlp_path), "--format", "markdown") - - assert "# Debug Log Summary" in result.stdout - assert "Sync reporting" in result.stdout - - -def test_prompt_exports_subcommand_markdown_includes_aiu_total(tmp_path: Path) -> None: - prompt_path = tmp_path / "prompt.json" - prompt_path.write_text( - json.dumps( - { - "prompts": [ - { - "promptId": "prompt-aiu", - "logs": [ - { - "metadata": { - "usage": { - "prompt_tokens": 50, - "completion_tokens": 5, - "copilot_usage": {"total_nano_aiu": 3.5}, - } - } - } - ], - } - ] - } - ), - encoding="utf-8", - ) - - result = run_tool("prompt-exports", str(prompt_path), "--format", "markdown") - - assert "# Prompt Export Summary" in result.stdout - assert "- AIU total: 3.5" in result.stdout - - -def test_tool_package_exports_main_and_parsers() -> None: - assert callable(main) - assert cli.parse_args(["prompt-exports", "input.json"]).command == "prompt-exports" - assert ( - debug_logs.parse_args(["input.json", "--format", "markdown"]).format - == "markdown" - ) - assert ( - prompt_exports.parse_args(["input.json", "--format", "markdown"]).format - == "markdown" - ) From 593d3545bc8596abf486f6e534b0c09dc90bd518 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 00:08:32 +0200 Subject: [PATCH 16/44] refactor: remove obsolete handoff definitions and update outcome routing references in internal gateway critical master files --- .../internal-gateway-critical-master.agent.md | 13 ------- .github/scripts/lib/critical_master.py | 4 +-- .github/scripts/validate_critical_output.py | 18 ---------- .../internal-gateway-critical-master/SKILL.md | 35 +++++++------------ .../agents/openai.yaml | 2 +- .../references/output-contract.md | 19 ++-------- 6 files changed, 16 insertions(+), 75 deletions(-) diff --git a/.github/agents/internal-gateway-critical-master.agent.md b/.github/agents/internal-gateway-critical-master.agent.md index f94f9cf..28e5d1d 100644 --- a/.github/agents/internal-gateway-critical-master.agent.md +++ b/.github/agents/internal-gateway-critical-master.agent.md @@ -4,19 +4,6 @@ description: "Use this agent when a repository-owned plan, proposal, decision, o tools: ["read", "search"] disable-model-invocation: true agents: [] -handoffs: - - label: "Next step: Reopen planning" - agent: "internal-gateway-idea-brainstorming" - prompt: "Continue from the critical outcome above by reopening planning." - send: false - - label: "Next action: Use simple fast path" - agent: "internal-gateway-simple-task" - prompt: "Handle only the concrete simple task left by the critical outcome." - send: false - - label: "Next step: Review evidence" - agent: "internal-gateway-review" - prompt: "Continue from the critical outcome above through defect-first evidence review." - send: false --- # Internal Gateway Critical Master diff --git a/.github/scripts/lib/critical_master.py b/.github/scripts/lib/critical_master.py index 19f41d7..1f631d3 100644 --- a/.github/scripts/lib/critical_master.py +++ b/.github/scripts/lib/critical_master.py @@ -15,7 +15,7 @@ import re from dataclasses import dataclass, field -# Allowed outcome values from SKILL.md Outcome Routing table. +# Allowed outcome values from SKILL.md Outcome meanings table. ALLOWED_OUTCOMES: frozenset[str] = frozenset( { "reformulate-plan", @@ -38,7 +38,6 @@ "Findings", "Synthesis", "Outcome", - "Next owner", ) # Required sub-fields inside each finding (Reframe is optional per the contract). @@ -56,7 +55,6 @@ FINDING_OBJECTION_MAX_WORDS = 30 FINDING_FIELD_MAX_WORDS = 30 FINDING_REFRAME_MAX_WORDS = 25 -NEXT_OWNER_MAX_WORDS = 50 TOTAL_MAX_WORDS = 600 MIN_FINDINGS = 1 MAX_FINDINGS = 3 diff --git a/.github/scripts/validate_critical_output.py b/.github/scripts/validate_critical_output.py index 2c2eebc..529f8c1 100644 --- a/.github/scripts/validate_critical_output.py +++ b/.github/scripts/validate_critical_output.py @@ -23,7 +23,6 @@ FINDING_REFRAME_MAX_WORDS, MAX_FINDINGS, MIN_FINDINGS, - NEXT_OWNER_MAX_WORDS, REQUIRED_FINDING_FIELDS, REQUIRED_SECTIONS, SUMMARY_MAX_WORDS, @@ -107,7 +106,6 @@ def validate_output(text: str, *, max_words: int = TOTAL_MAX_WORDS) -> list[Find synthesis_body = sections.get("Synthesis", "") findings_body = sections.get("Findings", "") outcome_body = sections.get("Outcome", "") - next_owner_body = sections.get("Next owner", "") summary_words = count_words(summary_body) if summary_body and summary_words > SUMMARY_MAX_WORDS: @@ -188,22 +186,6 @@ def validate_output(text: str, *, max_words: int = TOTAL_MAX_WORDS) -> list[Find ) ) - next_owner_words = count_words(next_owner_body) - if next_owner_body and next_owner_words > NEXT_OWNER_MAX_WORDS: - findings.append( - Finding( - severity="non-blocking", - code="next-owner-word-limit", - path="## Next owner", - message=( - f"Next owner has {next_owner_words} words; " - f"limit is {NEXT_OWNER_MAX_WORDS}." - ), - suggestion="Trim the next-owner package.", - extras={"words": next_owner_words, "limit": NEXT_OWNER_MAX_WORDS}, - ) - ) - total_words = count_words(text) if total_words > max_words: findings.append( diff --git a/.github/skills/internal-gateway-critical-master/SKILL.md b/.github/skills/internal-gateway-critical-master/SKILL.md index d58f95f..8a666b8 100644 --- a/.github/skills/internal-gateway-critical-master/SKILL.md +++ b/.github/skills/internal-gateway-critical-master/SKILL.md @@ -5,13 +5,6 @@ description: Use when a repository-owned plan, proposal, or decision needs a cri # Internal Gateway Critical Master -## Referenced skills - -- `internal-gateway-idea-brainstorming`: route when critique shows the plan must be reformulated. -- `internal-gateway-simple-task`: route when critique leaves a concrete local task. -- `internal-gateway-execute-plans`: route approved retained-plan execution. -- `internal-gateway-review`: route when the next risk is correctness evidence. - Use this skill as the portable core for critical challenge work. The calling gateway decides when to invoke it; this skill challenges only. @@ -21,14 +14,10 @@ gateway decides when to invoke it; this skill challenges only. ## When not to use -- The next step is retained planning, implementation, or evidence-first review; use `internal-gateway-idea-brainstorming`, `internal-gateway-simple-task`, `internal-gateway-execute-plans`, or `internal-gateway-review`. +- The next step is retained planning, implementation, or evidence-first review. ## Boundaries -- Use `internal-gateway-idea-brainstorming` when the main job is reformulating the plan. -- Use `internal-gateway-simple-task` when the critique leaves a concrete local task. -- Use `internal-gateway-execute-plans` for approved `compact` and `extended` retained-plan execution. -- Use `internal-gateway-review` when the next step is evidence-based validation of a concrete change. - This skill challenges; it does not edit files, run commands, or author retained plans. ## Critical Procedure @@ -75,7 +64,7 @@ For a pre-mortem, state one concrete failure, list the 2-3 most likely root caus - Run the Final Consistency Gate: name the strongest supported objection, downgrade weak claims to hypotheses, and surface unresolved uncertainty. - Format the result using the contract in `references/output-contract.md`. -- Recommend exactly one outcome from `## Outcome Routing`. +- Recommend exactly one outcome from `## Outcome meanings`. ## Token Budget @@ -83,7 +72,7 @@ For a pre-mortem, state one concrete failure, list the 2-3 most likely root caus - Maximum findings: **3**. - Maximum per finding: **150 words**. - Maximum synthesis: **300 words**. -- If the topic demands more depth, split the work and route to `continue-critical`. +- If the topic demands more depth, split the work into another critical cycle. ## Claim Discipline @@ -97,13 +86,13 @@ For a pre-mortem, state one concrete failure, list the 2-3 most likely root caus - Optional: `validate_critical_output.py` checks a rendered output against the contract in `references/output-contract.md`. - Bundle-level budget is owned by the repo-wide token checks; routing into the skill is owned by the calling gateway. -## Outcome Routing +## Outcome meanings -| Outcome | Use when | Recommended next owner | -| --- | --- | --- | -| `reformulate-plan` | Planning must be rewritten. | `internal-gateway-idea-brainstorming` | -| `de-escalate-to-simple` | A concrete local task remains. | `internal-gateway-simple-task` | -| `execute-clear-next-step` | Execution is approved and clear. | `internal-gateway-simple-task` or `internal-gateway-execute-plans` | -| `review-evidence` | The next risk is correctness evidence. | `internal-gateway-review` | -| `continue-critical` | Another pressure-test loop is needed. | `internal-gateway-critical-master` | -| `accept-with-risk` | The user may proceed while accepting a named residual risk. | Current workflow with explicit risk note | +| Outcome | Use when | +| --- | --- | +| `reformulate-plan` | Planning must be rewritten. | +| `de-escalate-to-simple` | A concrete local task remains. | +| `execute-clear-next-step` | Execution is approved and clear. | +| `review-evidence` | The next risk is correctness evidence. | +| `continue-critical` | Another pressure-test loop is needed. | +| `accept-with-risk` | The user may proceed while accepting a named residual risk. | diff --git a/.github/skills/internal-gateway-critical-master/agents/openai.yaml b/.github/skills/internal-gateway-critical-master/agents/openai.yaml index 2da4dc6..20c1c0e 100644 --- a/.github/skills/internal-gateway-critical-master/agents/openai.yaml +++ b/.github/skills/internal-gateway-critical-master/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Internal Gateway Critical Master" - short_description: "Critical challenge with outcome routing" + short_description: "Critical challenge only" default_prompt: "Use $internal-gateway-critical-master to pressure-test this plan or decision. Follow the three-phase procedure in SKILL.md, select 2-3 lenses (with the third being lateral: analogy or reverse assumption), and close with the structured output contract in references/output-contract.md. Keep the total response under 600 words." diff --git a/.github/skills/internal-gateway-critical-master/references/output-contract.md b/.github/skills/internal-gateway-critical-master/references/output-contract.md index 5e07600..3350a0d 100644 --- a/.github/skills/internal-gateway-critical-master/references/output-contract.md +++ b/.github/skills/internal-gateway-critical-master/references/output-contract.md @@ -14,13 +14,12 @@ Use this reference to produce a compact, consistent critical challenge deliverab | `finding.mitigation` | Condition or action required before execution resumes. | 30 words | | `finding.reframe` | Optional lateral reframe. | 25 words | | `synthesis` | Result of the Final Consistency Gate. | 100 words | -| `outcome` | Exactly one value from `## Outcome Routing` in `SKILL.md`. | 1 value | -| `next_owner` | Recommended next owner and one-sentence next-step package. | 50 words | +| `outcome` | Exactly one value from `## Outcome meanings` in `SKILL.md`. | 1 value | ## Budget - Total output target: **600 words or fewer**. -- If the material demands more, split the work and route to `continue-critical`. +- If the material demands more, split the work into another critical cycle. - Do not pad findings to reach 3; one strong finding is better than three weak ones. ## Output template @@ -46,10 +45,6 @@ Use this reference to produce a compact, consistent critical challenge deliverab ## Outcome `` - -## Next owner - - ``` ## Example @@ -75,14 +70,4 @@ The strongest risk is compliance visibility, not implementation effort. The prop ## Outcome `accept-with-risk` - -## Next owner - -Route to `internal-gateway-simple-task` to add attestation logging to the pre-commit wrapper. ``` - -## Consumer gateway notes - -- `internal-gateway-idea-brainstorming` uses the `outcome` and `next_owner` fields to decide whether to reformulate the plan. -- `internal-gateway-review` treats `findings` as inputs to a defect-first review. -- `internal-gateway-simple-task` executes the `mitigation` or `next_owner` package. From 51011004126e2aeba24f8700ef81908785fefabd Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 00:16:42 +0200 Subject: [PATCH 17/44] feat: add critical-master validation scripts and helper functions for output contract compliance --- .../internal-gateway-critical-master/SKILL.md | 3 +- .../scripts/critical_master.py | 219 ++++++++++ .../scripts/validate_critical_output.py | 397 ++++++++++++++++++ 3 files changed, 618 insertions(+), 1 deletion(-) create mode 100644 .github/skills/internal-gateway-critical-master/scripts/critical_master.py create mode 100644 .github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py diff --git a/.github/skills/internal-gateway-critical-master/SKILL.md b/.github/skills/internal-gateway-critical-master/SKILL.md index 8a666b8..6f14443 100644 --- a/.github/skills/internal-gateway-critical-master/SKILL.md +++ b/.github/skills/internal-gateway-critical-master/SKILL.md @@ -83,7 +83,8 @@ For a pre-mortem, state one concrete failure, list the 2-3 most likely root caus ## Tooling -- Optional: `validate_critical_output.py` checks a rendered output against the contract in `references/output-contract.md`. +- Optional: `scripts/validate_critical_output.py` checks a rendered output against the contract in `references/output-contract.md`. +- The optional validator and its pure helper live inside this skill bundle so the skill can be copied without depending on repo-global Python modules. - Bundle-level budget is owned by the repo-wide token checks; routing into the skill is owned by the calling gateway. ## Outcome meanings diff --git a/.github/skills/internal-gateway-critical-master/scripts/critical_master.py b/.github/skills/internal-gateway-critical-master/scripts/critical_master.py new file mode 100644 index 0000000..30bb1cb --- /dev/null +++ b/.github/skills/internal-gateway-critical-master/scripts/critical_master.py @@ -0,0 +1,219 @@ +"""Pure helpers for the internal-gateway-critical-master validator. + +This module provides reusable building blocks for the sibling +``validate_critical_output.py`` script inside the skill bundle. +All functions in this module are pure: no I/O, no global state mutation, +no network access. +""" + +from __future__ import annotations + +import re +from dataclasses import dataclass, field + +ALLOWED_OUTCOMES: frozenset[str] = frozenset( + { + "reformulate-plan", + "de-escalate-to-simple", + "execute-clear-next-step", + "review-evidence", + "continue-critical", + "accept-with-risk", + } +) + +ALLOWED_CLAIM_CLASSES: frozenset[str] = frozenset( + {"confirmed", "inference", "estimate"} +) + +REQUIRED_SECTIONS: tuple[str, ...] = ( + "Summary", + "Findings", + "Synthesis", + "Outcome", +) + +REQUIRED_FINDING_FIELDS: tuple[str, ...] = ( + "Impact", + "Evidence", + "Mitigation", +) + +OPTIONAL_FINDING_FIELDS: tuple[str, ...] = ("Reframe",) + +SUMMARY_MAX_WORDS = 75 +SYNTHESIS_MAX_WORDS = 100 +FINDING_OBJECTION_MAX_WORDS = 30 +FINDING_FIELD_MAX_WORDS = 30 +FINDING_REFRAME_MAX_WORDS = 25 +TOTAL_MAX_WORDS = 600 +MIN_FINDINGS = 1 +MAX_FINDINGS = 3 + + +@dataclass(frozen=True) +class Finding: + """A single contract finding.""" + + severity: str + code: str + path: str + message: str + suggestion: str = "" + extras: dict[str, object] = field(default_factory=dict) + + def to_dict(self) -> dict[str, object]: + payload: dict[str, object] = { + "severity": self.severity, + "code": self.code, + "path": self.path, + "message": self.message, + "suggestion": self.suggestion, + } + if self.extras: + payload["extras"] = dict(self.extras) + return payload + + +def count_words(text: str) -> int: + """Count whitespace-delimited words. Code fences contribute no words.""" + cleaned = _strip_code_fences(text) + tokens = re.findall(r"\S+", cleaned) + return len(tokens) + + +def _strip_code_fences(text: str) -> str: + """Remove Markdown fenced code blocks so they do not count toward words.""" + return re.sub(r"```.*?```", "", text, flags=re.DOTALL) + + +_HEADING_PATTERN = re.compile(r"^(#{1,6})\s+(.*?)\s*$", re.MULTILINE) + + +def parse_markdown_sections(text: str) -> dict[str, str]: + """Parse a Markdown document into ``{section_title: body}``.""" + matches = list(_HEADING_PATTERN.finditer(text)) + sections: dict[str, str] = {} + if not matches: + return sections + + top_level_indices = [ + index for index, match in enumerate(matches) if len(match.group(1)) == 2 + ] + for list_index, match_index in enumerate(top_level_indices): + match = matches[match_index] + title = match.group(2).strip() + if title in sections: + continue + start = match.end() + if list_index + 1 < len(top_level_indices): + end = matches[top_level_indices[list_index + 1]].start() + else: + end = len(text) + body = text[start:end] + sections[title] = body.rstrip() + return sections + + +@dataclass(frozen=True) +class ParsedFinding: + """One finding extracted from a rendered Markdown output.""" + + heading: str + body: str + has_impact: bool + has_evidence: bool + has_mitigation: bool + has_reframe: bool + evidence_class: str | None + raw_lines: tuple[str, ...] + + +_FINDING_HEADING_PATTERN = re.compile(r"^(#{3,6})\s+(\d+)\.\s+(.*?)\s*$", re.MULTILINE) + + +def parse_findings(findings_body: str) -> list[ParsedFinding]: + """Parse the ``## Findings`` section body into individual finding records.""" + matches = list(_FINDING_HEADING_PATTERN.finditer(findings_body)) + parsed: list[ParsedFinding] = [] + for index, match in enumerate(matches): + heading = f"{match.group(2)}. {match.group(3)}" + start = match.end() + end = matches[index + 1].start() if index + 1 < len(matches) else len(findings_body) + body = findings_body[start:end].rstrip() + lower_body = body.lower() + has_impact = "**impact:**" in lower_body + has_evidence = "**evidence:**" in lower_body + has_mitigation = "**mitigation:**" in lower_body + has_reframe = "**reframe:**" in lower_body + evidence_class = _extract_evidence_class(body) + parsed.append( + ParsedFinding( + heading=heading, + body=body, + has_impact=has_impact, + has_evidence=has_evidence, + has_mitigation=has_mitigation, + has_reframe=has_reframe, + evidence_class=evidence_class, + raw_lines=tuple(body.splitlines()), + ) + ) + return parsed + + +_CLAIM_CLASS_PATTERN = re.compile( + r"\*\*[Ee]vidence:\*\*\s*`?(confirmed|inference|estimate)`?", + re.IGNORECASE, +) + + +def _extract_evidence_class(body: str) -> str | None: + match = _CLAIM_CLASS_PATTERN.search(body) + if not match: + return None + return match.group(1).lower() + + +_OUTCOME_PATTERN = re.compile(r"`([^`]+)`") + + +def extract_outcome_value(outcome_text: str) -> str | None: + """Extract the single backtick-wrapped outcome from the Outcome section.""" + cleaned = outcome_text.strip() + if not cleaned: + return None + match = _OUTCOME_PATTERN.search(cleaned) + if not match: + return None + return match.group(1).strip() + + +def validate_outcome_value(value: str) -> bool: + """Return True if ``value`` is an allowed outcome from SKILL.md.""" + return value in ALLOWED_OUTCOMES + + +def classify_claim_class(body: str) -> str | None: + """Return the claim class extracted from an Evidence bullet, or None.""" + return _extract_evidence_class(body) + + +__all__ = [ + "ALLOWED_CLAIM_CLASSES", + "ALLOWED_OUTCOMES", + "Finding", + "OPTIONAL_FINDING_FIELDS", + "ParsedFinding", + "REQUIRED_FINDING_FIELDS", + "REQUIRED_SECTIONS", + "SUMMARY_MAX_WORDS", + "SYNTHESIS_MAX_WORDS", + "TOTAL_MAX_WORDS", + "classify_claim_class", + "count_words", + "extract_outcome_value", + "parse_findings", + "parse_markdown_sections", + "validate_outcome_value", +] diff --git a/.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py b/.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py new file mode 100644 index 0000000..61751cd --- /dev/null +++ b/.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +"""Validate a critical-master output Markdown against the output contract. + +Usage examples: + python3 ./.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py --file tests/fixtures/critical_output_good.md + python3 ./.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py --file output.md --format json + python3 ./.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py --file output.md --strict +""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Protocol, TypeVar + +from critical_master import ( + ALLOWED_CLAIM_CLASSES, + ALLOWED_OUTCOMES, + FINDING_FIELD_MAX_WORDS, + FINDING_OBJECTION_MAX_WORDS, + FINDING_REFRAME_MAX_WORDS, + MAX_FINDINGS, + MIN_FINDINGS, + REQUIRED_FINDING_FIELDS, + REQUIRED_SECTIONS, + SUMMARY_MAX_WORDS, + SYNTHESIS_MAX_WORDS, + TOTAL_MAX_WORDS, + Finding, + count_words, + extract_outcome_value, + parse_findings, + parse_markdown_sections, + validate_outcome_value, +) + + +class FindingLike(Protocol): + severity: str + + def to_dict(self) -> dict[str, object]: ... + + +FindingT = TypeVar("FindingT", bound=FindingLike) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Validate a critical-master output against references/output-contract.md." + ) + parser.add_argument( + "--file", + help="Path to the Markdown output file. Defaults to stdin when omitted.", + ) + parser.add_argument( + "--format", + choices=["text", "json", "compact"], + default="text", + help="Output format.", + ) + parser.add_argument( + "--max-words", + type=int, + default=TOTAL_MAX_WORDS, + help="Override the total output word limit.", + ) + parser.add_argument( + "--strict", + action="store_true", + help="Return a non-zero exit code when any non-blocking finding is reported.", + ) + return parser.parse_args() + + +def run_finding_cli( + *, + detect_fn, + format_name: str, + render_text, + compact_builder=None, +) -> list[FindingT]: + findings = detect_fn() + if format_name == "json": + print(render_json([finding.to_dict() for finding in findings])) + elif format_name == "compact": + if compact_builder is None: + raise ValueError("compact output requires a compact_builder") + print(render_json(compact_builder(findings))) + else: + render_text(findings) + return findings + + +def has_severity(findings: list[FindingLike], severity: str) -> bool: + return any(finding.severity == severity for finding in findings) + + +def should_fail( + findings: list[FindingLike], + *, + strict: bool = False, + blocking_severity: str | None = "blocking", +) -> bool: + if blocking_severity is not None and has_severity(findings, blocking_severity): + return True + return strict and bool(findings) + + +def render_json(data: object) -> str: + return json.dumps(data, indent=2, sort_keys=True) + + +def log_info(message: str) -> None: + print(f"INFO: {message}", flush=True) + + +def log_warn(message: str) -> None: + print(f"WARN: {message}", flush=True) + + +def main() -> int: + args = parse_args() + if args.file: + text = Path(args.file).read_text(encoding="utf-8") + else: + text = sys.stdin.read() + + findings = run_finding_cli( + detect_fn=lambda: validate_output(text, max_words=args.max_words), + format_name=args.format, + render_text=render_text, + compact_builder=_compact_payload, + ) + return 1 if should_fail(findings, strict=args.strict) else 0 + + +def validate_output(text: str, *, max_words: int = TOTAL_MAX_WORDS) -> list[Finding]: + """Run every output-contract check and return a list of Findings.""" + findings: list[Finding] = [] + sections = parse_markdown_sections(text) + + for required in REQUIRED_SECTIONS: + if required not in sections: + findings.append( + Finding( + severity="blocking", + code=f"missing-section-{_section_slug(required)}", + path="(output)", + message=f"Required section '## {required}' is missing.", + suggestion=( + f"Add a '## {required}' section. " + "See references/output-contract.md for the template." + ), + ) + ) + + summary_body = sections.get("Summary", "") + synthesis_body = sections.get("Synthesis", "") + findings_body = sections.get("Findings", "") + outcome_body = sections.get("Outcome", "") + + summary_words = count_words(summary_body) + if summary_body and summary_words > SUMMARY_MAX_WORDS: + findings.append( + Finding( + severity="non-blocking", + code="summary-word-limit", + path="## Summary", + message=( + f"Summary has {summary_words} words; limit is {SUMMARY_MAX_WORDS}." + ), + suggestion="Compress the summary paragraph.", + extras={"words": summary_words, "limit": SUMMARY_MAX_WORDS}, + ) + ) + + synthesis_words = count_words(synthesis_body) + if synthesis_body and synthesis_words > SYNTHESIS_MAX_WORDS: + findings.append( + Finding( + severity="non-blocking", + code="synthesis-word-limit", + path="## Synthesis", + message=( + f"Synthesis has {synthesis_words} words; limit is {SYNTHESIS_MAX_WORDS}." + ), + suggestion="Compress the synthesis paragraph.", + extras={"words": synthesis_words, "limit": SYNTHESIS_MAX_WORDS}, + ) + ) + + parsed_findings = parse_findings(findings_body) + finding_count = len(parsed_findings) + if finding_count < MIN_FINDINGS or finding_count > MAX_FINDINGS: + findings.append( + Finding( + severity="blocking", + code="finding-count-out-of-range", + path="## Findings", + message=( + f"Found {finding_count} findings; expected {MIN_FINDINGS}-{MAX_FINDINGS}." + ), + suggestion=( + "Adjust the count to fit the contract: one strong finding is " + "better than three weak ones." + ), + extras={"count": finding_count, "min": MIN_FINDINGS, "max": MAX_FINDINGS}, + ) + ) + + for parsed in parsed_findings: + _check_finding(parsed, findings) + + outcome_value = extract_outcome_value(outcome_body) + if outcome_value is None: + findings.append( + Finding( + severity="blocking", + code="missing-outcome-value", + path="## Outcome", + message="Outcome section is missing a backtick-wrapped value.", + suggestion="Wrap the outcome value in single backticks, e.g. `accept-with-risk`.", + ) + ) + elif not validate_outcome_value(outcome_value): + findings.append( + Finding( + severity="blocking", + code="invalid-outcome-value", + path="## Outcome", + message=( + f"Outcome value '{outcome_value}' is not in the allowed set." + ), + suggestion=( + f"Use one of: {', '.join(sorted(ALLOWED_OUTCOMES))}." + ), + extras={"allowed": sorted(ALLOWED_OUTCOMES)}, + ) + ) + + total_words = count_words(text) + if total_words > max_words: + findings.append( + Finding( + severity="non-blocking", + code="total-word-limit", + path="(output)", + message=( + f"Total output is {total_words} words; limit is {max_words}." + ), + suggestion="Compress the deliverable or split into multiple cycles.", + extras={"words": total_words, "limit": max_words}, + ) + ) + + return findings + + +def _check_finding(parsed, findings: list[Finding]) -> None: + path = f"## Findings :: {parsed.heading}" + for required in REQUIRED_FINDING_FIELDS: + if not getattr(parsed, f"has_{required.lower()}"): + findings.append( + Finding( + severity="blocking", + code=f"missing-finding-field-{required.lower()}", + path=path, + message=f"Finding is missing **'{required}:'** field.", + suggestion=( + f"Add a '**{required}:**' bullet under the finding heading." + ), + ) + ) + if parsed.evidence_class is None: + findings.append( + Finding( + severity="blocking", + code="missing-claim-class", + path=path, + message=( + "Evidence field must declare a claim class " + f"({', '.join(sorted(ALLOWED_CLAIM_CLASSES))})." + ), + suggestion=( + "Append the class to the Evidence bullet, " + "e.g. '**Evidence:** `inference` — ...'." + ), + ) + ) + elif parsed.evidence_class not in ALLOWED_CLAIM_CLASSES: + findings.append( + Finding( + severity="blocking", + code="invalid-claim-class", + path=path, + message=( + f"Claim class '{parsed.evidence_class}' is not in the allowed set." + ), + suggestion=( + f"Use one of: {', '.join(sorted(ALLOWED_CLAIM_CLASSES))}." + ), + ) + ) + objection_words = _field_word_count(parsed.body, "Objection") + if objection_words > FINDING_OBJECTION_MAX_WORDS: + findings.append( + Finding( + severity="non-blocking", + code="finding-objection-word-limit", + path=path, + message=( + f"Objection has {objection_words} words; " + f"limit is {FINDING_OBJECTION_MAX_WORDS}." + ), + suggestion="Shorten the objection heading.", + extras={"words": objection_words, "limit": FINDING_OBJECTION_MAX_WORDS}, + ) + ) + for field_name in ("Impact", "Evidence", "Mitigation"): + words = _field_word_count(parsed.body, field_name) + if words > FINDING_FIELD_MAX_WORDS: + findings.append( + Finding( + severity="non-blocking", + code=f"finding-field-word-limit-{field_name.lower()}", + path=path, + message=( + f"'{field_name}' has {words} words; " + f"limit is {FINDING_FIELD_MAX_WORDS}." + ), + suggestion=f"Shorten the '{field_name}' bullet.", + extras={ + "words": words, + "limit": FINDING_FIELD_MAX_WORDS, + }, + ) + ) + if parsed.has_reframe: + words = _field_word_count(parsed.body, "Reframe") + if words > FINDING_REFRAME_MAX_WORDS: + findings.append( + Finding( + severity="non-blocking", + code="finding-reframe-word-limit", + path=path, + message=( + f"Reframe has {words} words; limit is {FINDING_REFRAME_MAX_WORDS}." + ), + suggestion="Shorten the optional reframe.", + extras={"words": words, "limit": FINDING_REFRAME_MAX_WORDS}, + ) + ) + + +def _field_word_count(body: str, field_name: str) -> int: + pattern = re.compile(rf"\*\*{field_name}:\*\*\s*(.*?)(?=\n\s*-\s*\*\*|\Z)", re.DOTALL) + match = pattern.search(body) + if not match: + return 0 + return count_words(match.group(1)) + + +def _section_slug(title: str) -> str: + return title.lower().replace(" ", "-") + + +def _compact_payload(findings: list[Finding]) -> dict[str, object]: + blocking = sum(1 for finding in findings if finding.severity == "blocking") + return { + "status": "failed" if blocking else "ok", + "finding_counts": { + "total": len(findings), + "blocking": blocking, + }, + "next_action": ( + "Resolve blocking findings before publishing the output." + if blocking + else "Output passes the contract; non-blocking findings are advisory." + ), + } + + +def render_text(findings: list[Finding]) -> None: + if not findings: + log_info("Output passes the contract.") + return + for finding in findings: + marker = "BLOCKING" if finding.severity == "blocking" else "advisory" + log_warn(f"[{marker}] {finding.path} :: {finding.code} :: {finding.message}") + print(f" Suggestion: {finding.suggestion}") + + +if __name__ == "__main__": + raise SystemExit(main()) From 495e3094a4f13c8e5b853b4b33f605c4f0b1083c Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 00:16:58 +0200 Subject: [PATCH 18/44] refactor: remove obsolete shell scripts and update references to use run.sh for script execution --- .github/INVENTORY.md | 11 - .github/scripts/audit_copilot_catalog.sh | 11 - .github/scripts/build_inventory.sh | 11 - .github/scripts/check_catalog_consistency.sh | 11 - .github/scripts/detect_token_risks.sh | 11 - .github/scripts/github_catalog_validation.sh | 12 - .github/scripts/lib/critical_master.py | 239 ------------ .github/scripts/run.sh | 4 +- .github/scripts/sync_copilot_catalog.sh | 12 - .github/scripts/sync_home_ai_resources.sh | 12 - .github/scripts/validate_critical_output.py | 342 ------------------ .github/scripts/validate_critical_output.sh | 11 - .github/scripts/validate_internal_skills.sh | 11 - .../skills/internal-copilot-audit/SKILL.md | 2 +- .../SKILL.md | 8 +- .../references/sync-contract.md | 12 +- .../SKILL.md | 6 +- .github/workflows/_code-analysis.yml | 9 +- .../workflows/_github-catalog-validation.yml | 4 +- docs/tech.md | 6 +- 20 files changed, 23 insertions(+), 722 deletions(-) delete mode 100755 .github/scripts/audit_copilot_catalog.sh delete mode 100755 .github/scripts/build_inventory.sh delete mode 100755 .github/scripts/check_catalog_consistency.sh delete mode 100755 .github/scripts/detect_token_risks.sh delete mode 100644 .github/scripts/github_catalog_validation.sh delete mode 100644 .github/scripts/lib/critical_master.py delete mode 100755 .github/scripts/sync_copilot_catalog.sh delete mode 100644 .github/scripts/sync_home_ai_resources.sh delete mode 100644 .github/scripts/validate_critical_output.py delete mode 100755 .github/scripts/validate_critical_output.sh delete mode 100755 .github/scripts/validate_internal_skills.sh diff --git a/.github/INVENTORY.md b/.github/INVENTORY.md index 3a78e40..459679a 100644 --- a/.github/INVENTORY.md +++ b/.github/INVENTORY.md @@ -155,21 +155,15 @@ These imported `openai-*` office skills remain support-only depth for repositori ## Scripts - `.github/scripts/audit_copilot_catalog.py` -- `.github/scripts/audit_copilot_catalog.sh` - `.github/scripts/benchmark_skill_tokens.py` - `.github/scripts/build_inventory.py` -- `.github/scripts/build_inventory.sh` - `.github/scripts/check_catalog_consistency.py` -- `.github/scripts/check_catalog_consistency.sh` - `.github/scripts/detect_token_risks.py` -- `.github/scripts/detect_token_risks.sh` - `.github/scripts/github_catalog_validation.py` -- `.github/scripts/github_catalog_validation.sh` - `.github/scripts/graphify-file-change-hook.sh` - `.github/scripts/install-graphify-hooks.sh` - `.github/scripts/lib/catalog_checks.py` - `.github/scripts/lib/cli_runner.py` -- `.github/scripts/lib/critical_master.py` - `.github/scripts/lib/fingerprinting.py` - `.github/scripts/lib/internal_skills.py` - `.github/scripts/lib/inventory.py` @@ -179,13 +173,8 @@ These imported `openai-*` office skills remain support-only depth for repositori - `.github/scripts/lib/token_risks.py` - `.github/scripts/run.sh` - `.github/scripts/sync_copilot_catalog.py` -- `.github/scripts/sync_copilot_catalog.sh` - `.github/scripts/sync_home_ai_resources.py` -- `.github/scripts/sync_home_ai_resources.sh` -- `.github/scripts/validate_critical_output.py` -- `.github/scripts/validate_critical_output.sh` - `.github/scripts/validate_internal_skills.py` -- `.github/scripts/validate_internal_skills.sh` ## Agents diff --git a/.github/scripts/audit_copilot_catalog.sh b/.github/scripts/audit_copilot_catalog.sh deleted file mode 100755 index d388143..0000000 --- a/.github/scripts/audit_copilot_catalog.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# -# Purpose: Run the deep Copilot catalog audit. -# Usage examples: -# ./.github/scripts/audit_copilot_catalog.sh --root . - -set -Eeuo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -exec "$SCRIPT_DIR/run.sh" audit_copilot_catalog "$@" diff --git a/.github/scripts/build_inventory.sh b/.github/scripts/build_inventory.sh deleted file mode 100755 index 3182977..0000000 --- a/.github/scripts/build_inventory.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# -# Purpose: Run the Copilot inventory builder. -# Usage examples: -# ./.github/scripts/build_inventory.sh --root . - -set -Eeuo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -exec "$SCRIPT_DIR/run.sh" build_inventory "$@" diff --git a/.github/scripts/check_catalog_consistency.sh b/.github/scripts/check_catalog_consistency.sh deleted file mode 100755 index fde1381..0000000 --- a/.github/scripts/check_catalog_consistency.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# -# Purpose: Run the fast Copilot catalog consistency checks. -# Usage examples: -# ./.github/scripts/check_catalog_consistency.sh --root . --include-token-risks - -set -Eeuo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -exec "$SCRIPT_DIR/run.sh" check_catalog_consistency "$@" diff --git a/.github/scripts/detect_token_risks.sh b/.github/scripts/detect_token_risks.sh deleted file mode 100755 index b102db3..0000000 --- a/.github/scripts/detect_token_risks.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# -# Purpose: Run token-risk analysis for Copilot governance assets. -# Usage examples: -# ./.github/scripts/detect_token_risks.sh --root . - -set -Eeuo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -exec "$SCRIPT_DIR/run.sh" detect_token_risks "$@" diff --git a/.github/scripts/github_catalog_validation.sh b/.github/scripts/github_catalog_validation.sh deleted file mode 100644 index d693afc..0000000 --- a/.github/scripts/github_catalog_validation.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -# -# Purpose: Run the GitHub catalog validation workflow simulation. -# Usage examples: -# bash ./.github/scripts/github_catalog_validation.sh -# bash ./.github/scripts/github_catalog_validation.sh --skip-token-risks - -set -Eeuo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -exec "$SCRIPT_DIR/run.sh" github_catalog_validation "$@" diff --git a/.github/scripts/lib/critical_master.py b/.github/scripts/lib/critical_master.py deleted file mode 100644 index 1f631d3..0000000 --- a/.github/scripts/lib/critical_master.py +++ /dev/null @@ -1,239 +0,0 @@ -"""Pure helpers for the internal-gateway-critical-master validator. - -This module provides reusable building blocks for the optional -``validate_critical_output`` script. It carries only what the output -validator needs; bundle-level routing, preflight, and token budget are -owned by the calling gateway and the repo-wide token checks. - -All functions in this module are pure: no I/O, no global state mutation, -no network access. The CLI layer in ``validate_critical_output.py`` -converts results into ``Finding`` objects and handles exit codes. -""" - -from __future__ import annotations - -import re -from dataclasses import dataclass, field - -# Allowed outcome values from SKILL.md Outcome meanings table. -ALLOWED_OUTCOMES: frozenset[str] = frozenset( - { - "reformulate-plan", - "de-escalate-to-simple", - "execute-clear-next-step", - "review-evidence", - "continue-critical", - "accept-with-risk", - } -) - -# Allowed claim classes from SKILL.md Claim Discipline section. -ALLOWED_CLAIM_CLASSES: frozenset[str] = frozenset( - {"confirmed", "inference", "estimate"} -) - -# Required Markdown sections in the output contract. -REQUIRED_SECTIONS: tuple[str, ...] = ( - "Summary", - "Findings", - "Synthesis", - "Outcome", -) - -# Required sub-fields inside each finding (Reframe is optional per the contract). -REQUIRED_FINDING_FIELDS: tuple[str, ...] = ( - "Impact", - "Evidence", - "Mitigation", -) - -OPTIONAL_FINDING_FIELDS: tuple[str, ...] = ("Reframe",) - -# Word limits per field, sourced from the output contract. -SUMMARY_MAX_WORDS = 75 -SYNTHESIS_MAX_WORDS = 100 -FINDING_OBJECTION_MAX_WORDS = 30 -FINDING_FIELD_MAX_WORDS = 30 -FINDING_REFRAME_MAX_WORDS = 25 -TOTAL_MAX_WORDS = 600 -MIN_FINDINGS = 1 -MAX_FINDINGS = 3 - - -@dataclass(frozen=True) -class Finding: - """A single contract finding. - - Reuses the same dataclass shape as ``lib.shared.Finding`` so output - rendering stays consistent across scripts. - """ - - severity: str # "blocking" | "non-blocking" | "notice" - code: str - path: str - message: str - suggestion: str = "" - extras: dict[str, object] = field(default_factory=dict) - - def to_dict(self) -> dict[str, object]: - payload: dict[str, object] = { - "severity": self.severity, - "code": self.code, - "path": self.path, - "message": self.message, - "suggestion": self.suggestion, - } - if self.extras: - payload["extras"] = dict(self.extras) - return payload - - -def count_words(text: str) -> int: - """Count whitespace-delimited words. Code fences contribute no words.""" - cleaned = _strip_code_fences(text) - tokens = re.findall(r"\S+", cleaned) - return len(tokens) - - -def _strip_code_fences(text: str) -> str: - """Remove Markdown fenced code blocks so they do not count toward words.""" - return re.sub(r"```.*?```", "", text, flags=re.DOTALL) - - -_HEADING_PATTERN = re.compile(r"^(#{1,6})\s+(.*?)\s*$", re.MULTILINE) - - -def parse_markdown_sections(text: str) -> dict[str, str]: - """Parse a Markdown document into ``{section_title: body}``. - - Only ``##``-level headings are treated as top-level section starts because - the output contract uses level-2 headings throughout. Deeper headings - (``###`` and beyond) remain part of the current section body. The first - occurrence of each title wins; later duplicates are merged under the first - title. - """ - matches = list(_HEADING_PATTERN.finditer(text)) - sections: dict[str, str] = {} - if not matches: - return sections - - top_level_indices = [ - index for index, match in enumerate(matches) if len(match.group(1)) == 2 - ] - for list_index, match_index in enumerate(top_level_indices): - match = matches[match_index] - title = match.group(2).strip() - if title in sections: - continue - start = match.end() - if list_index + 1 < len(top_level_indices): - end = matches[top_level_indices[list_index + 1]].start() - else: - end = len(text) - body = text[start:end] - sections[title] = body.rstrip() - return sections - - -@dataclass(frozen=True) -class ParsedFinding: - """One finding extracted from a rendered Markdown output.""" - - heading: str - body: str - has_impact: bool - has_evidence: bool - has_mitigation: bool - has_reframe: bool - evidence_class: str | None - raw_lines: tuple[str, ...] - - -_FINDING_HEADING_PATTERN = re.compile(r"^(#{3,6})\s+(\d+)\.\s+(.*?)\s*$", re.MULTILINE) - - -def parse_findings(findings_body: str) -> list[ParsedFinding]: - """Parse the ``## Findings`` section body into individual finding records.""" - matches = list(_FINDING_HEADING_PATTERN.finditer(findings_body)) - parsed: list[ParsedFinding] = [] - for index, match in enumerate(matches): - heading = f"{match.group(2)}. {match.group(3)}" - start = match.end() - end = matches[index + 1].start() if index + 1 < len(matches) else len(findings_body) - body = findings_body[start:end].rstrip() - lower_body = body.lower() - has_impact = "**impact:**" in lower_body - has_evidence = "**evidence:**" in lower_body - has_mitigation = "**mitigation:**" in lower_body - has_reframe = "**reframe:**" in lower_body - evidence_class = _extract_evidence_class(body) - parsed.append( - ParsedFinding( - heading=heading, - body=body, - has_impact=has_impact, - has_evidence=has_evidence, - has_mitigation=has_mitigation, - has_reframe=has_reframe, - evidence_class=evidence_class, - raw_lines=tuple(body.splitlines()), - ) - ) - return parsed - - -_CLAIM_CLASS_PATTERN = re.compile( - r"\*\*[Ee]vidence:\*\*\s*`?(confirmed|inference|estimate)`?", - re.IGNORECASE, -) - - -def _extract_evidence_class(body: str) -> str | None: - match = _CLAIM_CLASS_PATTERN.search(body) - if not match: - return None - return match.group(1).lower() - - -_OUTCOME_PATTERN = re.compile(r"`([^`]+)`") - - -def extract_outcome_value(synthesis_text: str) -> str | None: - """Extract the single backtick-wrapped outcome from the Outcome section.""" - cleaned = synthesis_text.strip() - if not cleaned: - return None - match = _OUTCOME_PATTERN.search(cleaned) - if not match: - return None - return match.group(1).strip() - - -def validate_outcome_value(value: str) -> bool: - """Return True if ``value`` is an allowed outcome from SKILL.md.""" - return value in ALLOWED_OUTCOMES - - -def classify_claim_class(body: str) -> str | None: - """Return the claim class extracted from an Evidence bullet, or None.""" - return _extract_evidence_class(body) - - -__all__ = [ - "ALLOWED_CLAIM_CLASSES", - "ALLOWED_OUTCOMES", - "Finding", - "OPTIONAL_FINDING_FIELDS", - "ParsedFinding", - "REQUIRED_FINDING_FIELDS", - "REQUIRED_SECTIONS", - "SUMMARY_MAX_WORDS", - "SYNTHESIS_MAX_WORDS", - "TOTAL_MAX_WORDS", - "classify_claim_class", - "count_words", - "extract_outcome_value", - "parse_findings", - "parse_markdown_sections", - "validate_outcome_value", -] diff --git a/.github/scripts/run.sh b/.github/scripts/run.sh index 7174c3e..d0efe3c 100755 --- a/.github/scripts/run.sh +++ b/.github/scripts/run.sh @@ -143,8 +143,8 @@ resolve_script() { sync_copilot_catalog|sync_copilot_catalog.py) printf '%s\n' "$SCRIPT_DIR/sync_copilot_catalog.py" ;; - validate_critical_output|validate_critical_output.py) - printf '%s\n' "$SCRIPT_DIR/validate_critical_output.py" + validate_critical_output) + printf '%s\n' "$SCRIPT_DIR/../skills/internal-gateway-critical-master/scripts/validate_critical_output.py" ;; validate_internal_skills|validate_internal_skills.py) printf '%s\n' "$SCRIPT_DIR/validate_internal_skills.py" diff --git a/.github/scripts/sync_copilot_catalog.sh b/.github/scripts/sync_copilot_catalog.sh deleted file mode 100755 index 5792dfb..0000000 --- a/.github/scripts/sync_copilot_catalog.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -# -# Purpose: Run the Copilot catalog sync planner and apply tool. -# Usage examples: -# ./.github/scripts/sync_copilot_catalog.sh plan --target-repo ../consumer-repo -# ./.github/scripts/sync_copilot_catalog.sh apply --target-repo ../consumer-repo --allow-dirty-target - -set -Eeuo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -exec "$SCRIPT_DIR/run.sh" sync_copilot_catalog "$@" diff --git a/.github/scripts/sync_home_ai_resources.sh b/.github/scripts/sync_home_ai_resources.sh deleted file mode 100644 index 9845d66..0000000 --- a/.github/scripts/sync_home_ai_resources.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -# -# Purpose: Run the local home AI resource sync planner and apply tool. -# Usage examples: -# ./.github/scripts/sync_home_ai_resources.sh sync --targets skills --format report -# ./.github/scripts/sync_home_ai_resources.sh apply --targets codex --create-missing-dirs --format report - -set -Eeuo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -exec "$SCRIPT_DIR/run.sh" sync_home_ai_resources "$@" diff --git a/.github/scripts/validate_critical_output.py b/.github/scripts/validate_critical_output.py deleted file mode 100644 index 529f8c1..0000000 --- a/.github/scripts/validate_critical_output.py +++ /dev/null @@ -1,342 +0,0 @@ -#!/usr/bin/env python3 -"""Purpose: validate a critical-master output Markdown against the output contract. - -Usage examples: - python3 ./.github/scripts/validate_critical_output.py --file tests/fixtures/critical_output_good.md - python3 ./.github/scripts/validate_critical_output.py --file output.md --format json - python3 ./.github/scripts/validate_critical_output.py --file output.md --strict -""" - -from __future__ import annotations - -import argparse -import re -import sys -from pathlib import Path - -from lib.cli_runner import run_finding_cli, should_fail -from lib.critical_master import ( - ALLOWED_CLAIM_CLASSES, - ALLOWED_OUTCOMES, - FINDING_FIELD_MAX_WORDS, - FINDING_OBJECTION_MAX_WORDS, - FINDING_REFRAME_MAX_WORDS, - MAX_FINDINGS, - MIN_FINDINGS, - REQUIRED_FINDING_FIELDS, - REQUIRED_SECTIONS, - SUMMARY_MAX_WORDS, - SYNTHESIS_MAX_WORDS, - TOTAL_MAX_WORDS, - Finding, - count_words, - extract_outcome_value, - parse_findings, - parse_markdown_sections, - validate_outcome_value, -) -from lib.shared import log_info, log_warn - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Validate a critical-master output against references/output-contract.md." - ) - parser.add_argument( - "--file", - help="Path to the Markdown output file. Defaults to stdin when omitted.", - ) - parser.add_argument( - "--format", - choices=["text", "json", "compact"], - default="text", - help="Output format.", - ) - parser.add_argument( - "--max-words", - type=int, - default=TOTAL_MAX_WORDS, - help="Override the total output word limit.", - ) - parser.add_argument( - "--strict", - action="store_true", - help="Return a non-zero exit code when any non-blocking finding is reported.", - ) - return parser.parse_args() - - -def main() -> int: - args = parse_args() - if args.file: - text = Path(args.file).read_text(encoding="utf-8") - else: - text = sys.stdin.read() - - findings = run_finding_cli( - detect_fn=lambda: validate_output(text, max_words=args.max_words), - format_name=args.format, - render_text=render_text, - compact_builder=_compact_payload, - ) - return 1 if should_fail(findings, strict=args.strict) else 0 - - -def validate_output(text: str, *, max_words: int = TOTAL_MAX_WORDS) -> list[Finding]: - """Run every output-contract check and return a list of Findings.""" - findings: list[Finding] = [] - sections = parse_markdown_sections(text) - - for required in REQUIRED_SECTIONS: - if required not in sections: - findings.append( - Finding( - severity="blocking", - code=f"missing-section-{_section_slug(required)}", - path="(output)", - message=f"Required section '## {required}' is missing.", - suggestion=( - f"Add a '## {required}' section. " - "See references/output-contract.md for the template." - ), - ) - ) - - summary_body = sections.get("Summary", "") - synthesis_body = sections.get("Synthesis", "") - findings_body = sections.get("Findings", "") - outcome_body = sections.get("Outcome", "") - - summary_words = count_words(summary_body) - if summary_body and summary_words > SUMMARY_MAX_WORDS: - findings.append( - Finding( - severity="non-blocking", - code="summary-word-limit", - path="## Summary", - message=( - f"Summary has {summary_words} words; limit is {SUMMARY_MAX_WORDS}." - ), - suggestion="Compress the summary paragraph.", - extras={"words": summary_words, "limit": SUMMARY_MAX_WORDS}, - ) - ) - - synthesis_words = count_words(synthesis_body) - if synthesis_body and synthesis_words > SYNTHESIS_MAX_WORDS: - findings.append( - Finding( - severity="non-blocking", - code="synthesis-word-limit", - path="## Synthesis", - message=( - f"Synthesis has {synthesis_words} words; limit is {SYNTHESIS_MAX_WORDS}." - ), - suggestion="Compress the synthesis paragraph.", - extras={"words": synthesis_words, "limit": SYNTHESIS_MAX_WORDS}, - ) - ) - - parsed_findings = parse_findings(findings_body) - finding_count = len(parsed_findings) - if finding_count < MIN_FINDINGS or finding_count > MAX_FINDINGS: - findings.append( - Finding( - severity="blocking", - code="finding-count-out-of-range", - path="## Findings", - message=( - f"Found {finding_count} findings; expected {MIN_FINDINGS}-{MAX_FINDINGS}." - ), - suggestion=( - "Adjust the count to fit the contract: one strong finding is " - "better than three weak ones." - ), - extras={"count": finding_count, "min": MIN_FINDINGS, "max": MAX_FINDINGS}, - ) - ) - - for parsed in parsed_findings: - _check_finding(parsed, findings) - - outcome_value = extract_outcome_value(outcome_body) - if outcome_value is None: - findings.append( - Finding( - severity="blocking", - code="missing-outcome-value", - path="## Outcome", - message="Outcome section is missing a backtick-wrapped value.", - suggestion="Wrap the outcome value in single backticks, e.g. `accept-with-risk`.", - ) - ) - elif not validate_outcome_value(outcome_value): - findings.append( - Finding( - severity="blocking", - code="invalid-outcome-value", - path="## Outcome", - message=( - f"Outcome value '{outcome_value}' is not in the allowed set." - ), - suggestion=( - f"Use one of: {', '.join(sorted(ALLOWED_OUTCOMES))}." - ), - extras={"allowed": sorted(ALLOWED_OUTCOMES)}, - ) - ) - - total_words = count_words(text) - if total_words > max_words: - findings.append( - Finding( - severity="non-blocking", - code="total-word-limit", - path="(output)", - message=( - f"Total output is {total_words} words; limit is {max_words}." - ), - suggestion="Compress the deliverable or split into multiple cycles.", - extras={"words": total_words, "limit": max_words}, - ) - ) - - return findings - - -def _check_finding(parsed, findings: list[Finding]) -> None: - path = f"## Findings :: {parsed.heading}" - for required in REQUIRED_FINDING_FIELDS: - if not getattr(parsed, f"has_{required.lower()}"): - findings.append( - Finding( - severity="blocking", - code=f"missing-finding-field-{required.lower()}", - path=path, - message=f"Finding is missing **'{required}:'** field.", - suggestion=( - f"Add a '**{required}:**' bullet under the finding heading." - ), - ) - ) - if parsed.evidence_class is None: - findings.append( - Finding( - severity="blocking", - code="missing-claim-class", - path=path, - message=( - "Evidence field must declare a claim class " - f"({', '.join(sorted(ALLOWED_CLAIM_CLASSES))})." - ), - suggestion=( - "Append the class to the Evidence bullet, " - "e.g. '**Evidence:** `inference` — ...'." - ), - ) - ) - elif parsed.evidence_class not in ALLOWED_CLAIM_CLASSES: - findings.append( - Finding( - severity="blocking", - code="invalid-claim-class", - path=path, - message=( - f"Claim class '{parsed.evidence_class}' is not in the allowed set." - ), - suggestion=( - f"Use one of: {', '.join(sorted(ALLOWED_CLAIM_CLASSES))}." - ), - ) - ) - objection_words = _field_word_count(parsed.body, "Objection") - if objection_words > FINDING_OBJECTION_MAX_WORDS: - findings.append( - Finding( - severity="non-blocking", - code="finding-objection-word-limit", - path=path, - message=( - f"Objection has {objection_words} words; " - f"limit is {FINDING_OBJECTION_MAX_WORDS}." - ), - suggestion="Shorten the objection heading.", - extras={"words": objection_words, "limit": FINDING_OBJECTION_MAX_WORDS}, - ) - ) - for field_name in ("Impact", "Evidence", "Mitigation"): - words = _field_word_count(parsed.body, field_name) - if words > FINDING_FIELD_MAX_WORDS: - findings.append( - Finding( - severity="non-blocking", - code=f"finding-field-word-limit-{field_name.lower()}", - path=path, - message=( - f"'{field_name}' has {words} words; " - f"limit is {FINDING_FIELD_MAX_WORDS}." - ), - suggestion=f"Shorten the '{field_name}' bullet.", - extras={ - "words": words, - "limit": FINDING_FIELD_MAX_WORDS, - }, - ) - ) - if parsed.has_reframe: - words = _field_word_count(parsed.body, "Reframe") - if words > FINDING_REFRAME_MAX_WORDS: - findings.append( - Finding( - severity="non-blocking", - code="finding-reframe-word-limit", - path=path, - message=( - f"Reframe has {words} words; limit is {FINDING_REFRAME_MAX_WORDS}." - ), - suggestion="Shorten the optional reframe.", - extras={"words": words, "limit": FINDING_REFRAME_MAX_WORDS}, - ) - ) - - -def _field_word_count(body: str, field_name: str) -> int: - pattern = re.compile(rf"\*\*{field_name}:\*\*\s*(.*?)(?=\n\s*-\s*\*\*|\Z)", re.DOTALL) - match = pattern.search(body) - if not match: - return 0 - return count_words(match.group(1)) - - -def _section_slug(title: str) -> str: - return title.lower().replace(" ", "-") - - -def _compact_payload(findings: list[Finding]) -> dict[str, object]: - blocking = sum(1 for f in findings if f.severity == "blocking") - return { - "status": "failed" if blocking else "ok", - "finding_counts": { - "total": len(findings), - "blocking": blocking, - }, - "next_action": ( - "Resolve blocking findings before publishing the output." - if blocking - else "Output passes the contract; non-blocking findings are advisory." - ), - } - - -def render_text(findings: list[Finding]) -> None: - if not findings: - log_info("Output passes the contract.") - return - for finding in findings: - marker = "BLOCKING" if finding.severity == "blocking" else "advisory" - log_warn(f"[{marker}] {finding.path} :: {finding.code} :: {finding.message}") - print(f" Suggestion: {finding.suggestion}") - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.github/scripts/validate_critical_output.sh b/.github/scripts/validate_critical_output.sh deleted file mode 100755 index 23b9807..0000000 --- a/.github/scripts/validate_critical_output.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# -# Purpose: Validate a critical-master output against the output contract. -# Usage examples: -# ./.github/scripts/validate_critical_output.sh --file tests/fixtures/critical_output_good.md - -set -Eeuo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -exec "$SCRIPT_DIR/run.sh" validate_critical_output "$@" diff --git a/.github/scripts/validate_internal_skills.sh b/.github/scripts/validate_internal_skills.sh deleted file mode 100755 index 96cd990..0000000 --- a/.github/scripts/validate_internal_skills.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# -# Purpose: Validate repository-owned internal skill metadata and references. -# Usage examples: -# ./.github/scripts/validate_internal_skills.sh --root . --strict - -set -Eeuo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -exec "$SCRIPT_DIR/run.sh" validate_internal_skills "$@" diff --git a/.github/skills/internal-copilot-audit/SKILL.md b/.github/skills/internal-copilot-audit/SKILL.md index 04bf3a7..2913cb4 100644 --- a/.github/skills/internal-copilot-audit/SKILL.md +++ b/.github/skills/internal-copilot-audit/SKILL.md @@ -36,7 +36,7 @@ For skill bundles, treat `references/`, `scripts/`, `assets/`, and `agents/opena - Detect sync workflows that skip or fail to report governance review for `.github/copilot-instructions.md` and root `AGENTS.md`. - Detect naming violations and stale inventory references. - Detect governance files that still describe removed, renamed, or retired assets. -- Detect catalog retirements or remaps that were not propagated in the same change to the supported consistency and sync entrypoints, `.github/scripts/check_catalog_consistency.sh` and `.github/scripts/sync_copilot_catalog.sh`. +- Detect catalog retirements or remaps that were not propagated in the same change to the supported consistency and sync entrypoints, `./.github/scripts/run.sh check_catalog_consistency` and `./.github/scripts/run.sh sync_copilot_catalog`. - Detect token-risk claims that rely on assumed runtime loading behavior instead of observable repository signals such as description length, exact trigger collisions, or repo-profile coverage. ## Audit Order diff --git a/.github/skills/local-agent-sync-global-copilot-configs-into-repo/SKILL.md b/.github/skills/local-agent-sync-global-copilot-configs-into-repo/SKILL.md index 6de1def..131aa05 100644 --- a/.github/skills/local-agent-sync-global-copilot-configs-into-repo/SKILL.md +++ b/.github/skills/local-agent-sync-global-copilot-configs-into-repo/SKILL.md @@ -23,7 +23,7 @@ The paired agent should not restate default mode handling, preserved `local-*` b - Refresh target `AGENTS.md`, `.github/copilot-instructions.md`, and `.github/INVENTORY.md` to the current root-policy and review-only model after mirroring. - Refresh shared repository-hygiene files that are part of the managed sync baseline, currently `.editorconfig`, `.pre-commit-config.yaml`, and `.github/workflows/_pre-commit.yml`. - Refresh repository-root `LESSONS_LEARNED.md` from the source structure while preserving and, when needed, migrating target-authored pending lesson rows. -- Run or interpret `.github/scripts/sync_copilot_catalog.sh` or `.github/scripts/sync_copilot_catalog.py`. +- Run or interpret `./.github/scripts/run.sh sync_copilot_catalog` or `.github/scripts/sync_copilot_catalog.py`. - Audit source-target drift before or after a sync. ## Core Operating Contract @@ -56,7 +56,7 @@ The paired agent should not restate default mode handling, preserved `local-*` b - `plan`: default mode and safest starting point. - `apply`: explicit only, after reviewing a conflict-safe plan and current source findings. -- `audit`: use when source or target drift needs diagnosis before deciding whether to plan or apply; prefer `.github/scripts/audit_copilot_catalog.sh` plus the sync planner evidence instead of inventing a third sync mode. +- `audit`: use when source or target drift needs diagnosis before deciding whether to plan or apply; prefer `./.github/scripts/run.sh audit_copilot_catalog` plus the sync planner evidence instead of inventing a third sync mode. ## Agent-facing output modes @@ -90,7 +90,7 @@ If `dirty_managed_overlap` is empty, `--allow-dirty-target` can stay eligible wh ## Validation -- For source-side baseline changes, prefer `./.github/scripts/check_catalog_consistency.sh --root . --include-token-risks`. -- Rebuild `.github/INVENTORY.md` when touched catalog paths require it by using `./.github/scripts/build_inventory.sh --root .`. +- For source-side baseline changes, prefer `./.github/scripts/run.sh check_catalog_consistency --root . --include-token-risks`. +- Rebuild `.github/INVENTORY.md` when touched catalog paths require it by using `./.github/scripts/run.sh build_inventory --root .`. - For sync automation changes, run `pytest tests/test_sync_and_token_risks.py`. - If a dedicated sync-contract test does not exist for the touched behavior, say so explicitly and use the closest existing verification. diff --git a/.github/skills/local-agent-sync-global-copilot-configs-into-repo/references/sync-contract.md b/.github/skills/local-agent-sync-global-copilot-configs-into-repo/references/sync-contract.md index 8b4e9c7..17b68f4 100644 --- a/.github/skills/local-agent-sync-global-copilot-configs-into-repo/references/sync-contract.md +++ b/.github/skills/local-agent-sync-global-copilot-configs-into-repo/references/sync-contract.md @@ -74,8 +74,8 @@ Do not flatten these roles into one file. Do not let target `AGENTS.md` become a ## Automation Entry Points -- Preferred wrapper: `.github/scripts/sync_copilot_catalog.sh` -- Audit wrapper: `.github/scripts/audit_copilot_catalog.sh` +- Preferred entrypoint: `./.github/scripts/run.sh sync_copilot_catalog` +- Audit entrypoint: `./.github/scripts/run.sh audit_copilot_catalog` - Python entry point: `.github/scripts/sync_copilot_catalog.py` - Audit entry point: `.github/scripts/audit_copilot_catalog.py` - Core implementation: `.github/scripts/lib/syncing.py` @@ -87,10 +87,10 @@ Prefer the shipped scripts when the request matches `plan`, `apply`, or a script Use the closest existing checks for the touched behavior: -1. `./.github/scripts/check_catalog_consistency.sh --root . --include-token-risks` -2. `./.github/scripts/build_inventory.sh --root .` -3. `./.github/scripts/sync_copilot_catalog.sh plan --target-repo ` -4. `./.github/scripts/audit_copilot_catalog.sh --root .` when the decision depends on governance drift or local override behavior +1. `./.github/scripts/run.sh check_catalog_consistency --root . --include-token-risks` +2. `./.github/scripts/run.sh build_inventory --root .` +3. `./.github/scripts/run.sh sync_copilot_catalog plan --target-repo ` +4. `./.github/scripts/run.sh audit_copilot_catalog --root .` when the decision depends on governance drift or local override behavior 5. `pytest tests/test_sync_and_token_risks.py` when sync automation changes If a dedicated contract test is missing, call out the gap explicitly. diff --git a/.github/skills/local-agent-sync-install-ai-resources/SKILL.md b/.github/skills/local-agent-sync-install-ai-resources/SKILL.md index b0afe83..8d0852e 100644 --- a/.github/skills/local-agent-sync-install-ai-resources/SKILL.md +++ b/.github/skills/local-agent-sync-install-ai-resources/SKILL.md @@ -154,8 +154,8 @@ After any manual cleanup, re-run `plan` or `bisync plan` and require zero blocke ## Validation -- Rebuild `.github/INVENTORY.md` when the bundle or related scripts change by using `./.github/scripts/build_inventory.sh --root .`. -- Run `./.github/scripts/check_catalog_consistency.sh --root . --include-token-risks` after bundle or automation changes. -- Run `bash -n .github/skills/local-agent-sync-install-ai-resources/scripts/run.sh .github/scripts/sync_home_ai_resources.sh` after shell wrapper changes. +- Rebuild `.github/INVENTORY.md` when the bundle or related scripts change by using `./.github/scripts/run.sh build_inventory --root .`. +- Run `./.github/scripts/run.sh check_catalog_consistency --root . --include-token-risks` after bundle or automation changes. +- Run `bash -n .github/skills/local-agent-sync-install-ai-resources/scripts/run.sh .github/scripts/run.sh` after shell entrypoint changes. - Run focused agent or skill contract tests for this bundle. - Run focused sync tests for report layout, target parsing, support-matrix policy, manifest handling, overwrite gates, bisync protocol, and missing-directory behavior when automation changes. diff --git a/.github/workflows/_code-analysis.yml b/.github/workflows/_code-analysis.yml index 27deccc..bc79a2c 100644 --- a/.github/workflows/_code-analysis.yml +++ b/.github/workflows/_code-analysis.yml @@ -83,13 +83,8 @@ jobs: ./.github/scripts/run.sh --help || true bash -n .github/scripts/run.sh bash -n tools/analyze_copilot_debug_log/run.sh - bash -n .github/scripts/build_inventory.sh - bash -n .github/scripts/check_catalog_consistency.sh - bash -n .github/scripts/audit_copilot_catalog.sh - bash -n .github/scripts/detect_token_risks.sh - bash -n .github/scripts/sync_copilot_catalog.sh - bash -n .github/scripts/sync_home_ai_resources.sh - bash -n .github/scripts/github_catalog_validation.sh + bash -n .github/scripts/install-graphify-hooks.sh + bash -n .github/scripts/graphify-file-change-hook.sh bash -n .github/skills/local-agent-sync-install-ai-resources/scripts/run.sh - name: ✅ Run pytest diff --git a/.github/workflows/_github-catalog-validation.yml b/.github/workflows/_github-catalog-validation.yml index a67eb46..7b53de5 100644 --- a/.github/workflows/_github-catalog-validation.yml +++ b/.github/workflows/_github-catalog-validation.yml @@ -56,10 +56,10 @@ jobs: - name: Run repository validation targets run: | set -euo pipefail - bash ./.github/scripts/github_catalog_validation.sh --skip-token-risks + ./.github/scripts/run.sh github_catalog_validation --skip-token-risks - name: Run dedicated token-risk scan continue-on-error: true run: | set -euo pipefail - bash ./.github/scripts/github_catalog_validation.sh --token-risks-only + ./.github/scripts/run.sh github_catalog_validation --token-risks-only diff --git a/docs/tech.md b/docs/tech.md index cc52344..96ef28e 100644 --- a/docs/tech.md +++ b/docs/tech.md @@ -18,9 +18,9 @@ repository. | Category | Observed tooling | | --- | --- | -| Sync logic | `.github/scripts/sync_copilot_catalog.py`, `.github/scripts/lib/syncing.py` | -| Catalog generation | `.github/scripts/build_inventory.sh`, inventory helpers in `.github/scripts/lib/` | -| Contract checks | `pytest` tests under `tests/`, plus shell wrappers | +| Sync logic | `.github/scripts/sync_copilot_catalog.py`, `.github/scripts/lib/syncing.py`, `./.github/scripts/run.sh ` | +| Catalog generation | `.github/scripts/build_inventory.py`, inventory helpers in `.github/scripts/lib/`, and `./.github/scripts/run.sh build_inventory` | +| Contract checks | `pytest` tests under `tests/`, plus validator subcommands through `run.sh` | | Linting and policy checks | Make targets such as `docs-lint`, `token-risks`, and catalog checks | ## Validation Surface From 9213c9fce8c62d0bc4e1bdd9b406b79d8c9611b1 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 00:25:13 +0200 Subject: [PATCH 19/44] refactor: update AGENTS.md to clarify usage of tmp/ directory and remove obsolete status file design spec --- AGENTS.md | 2 + ...7-04-executing-plans-status-file-design.md | 139 ------------------ 2 files changed, 2 insertions(+), 139 deletions(-) delete mode 100644 tmp/superpowers/specs/2026-07-04-executing-plans-status-file-design.md diff --git a/AGENTS.md b/AGENTS.md index c6a2989..66c6db6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,6 +48,8 @@ This block is the portable source baseline used to generate ownership boundaries, and routing anchors. - Do not put operational procedures, checklists, file-shape recipes, command playbooks, or tool-specific workflows here. +- `tmp/` is temporary support only. Treat its contents as disposable working + artifacts and do not commit files from `tmp/`. ## Authoring Defaults diff --git a/tmp/superpowers/specs/2026-07-04-executing-plans-status-file-design.md b/tmp/superpowers/specs/2026-07-04-executing-plans-status-file-design.md deleted file mode 100644 index 500c03d..0000000 --- a/tmp/superpowers/specs/2026-07-04-executing-plans-status-file-design.md +++ /dev/null @@ -1,139 +0,0 @@ -# Executing Plans Status File Design - -## Purpose - -Improve `.github/skills/superpowers-executing-plans/SKILL.md` so every plan -execution leaves a compact, deterministic state file next to the plan. The file -must make the plan status obvious and give the next agent enough context to -resume without rereading the whole conversation. - -This spec is a design handoff only. It must not implement the skill change. - -## Target Skill - -- Modify: `.github/skills/superpowers-executing-plans/SKILL.md` -- Do not modify adjacent Superpowers skills unless implementation finds a direct - contract conflict that blocks this change. -- Keep the skill portable. Do not add repository-local paths, validators, or - examples beyond generic plan-relative behavior. - -## Decisions - -- The status file is written next to the original plan file. -- The filename format is `..md`. -- `` is the plan filename without the final `.md` suffix. -- Supported statuses are `DONE`, `BLOCKED`, `PARTIAL`, and `NEEDS_REVIEW`. -- Status values are uppercase. -- The skill writes a status file whenever execution reaches a terminal stop, - including success, blocker, partial completion, or initial review failure. -- Only one current status file should exist for a plan. Before writing a new - status file, the agent removes or replaces stale status files for the same - plan basename. -- The status file should stay short, normally under 40 lines. -- The main session owns the final status file, even when subagents contribute. - -## Status Semantics - -Use `DONE` only when all planned work is complete, required verification has -passed or any validation gaps are explicitly accepted, and the finishing step is -complete. - -Use `PARTIAL` when some plan work was completed but the execution cannot be -declared done. Typical cases include failing final validation, unfinished -finishing work, or a stop after meaningful progress. - -Use `BLOCKED` when execution cannot continue without user input, missing access, -missing dependency, unclear instructions, or a repeated non-repairable failure. - -Use `NEEDS_REVIEW` when the initial plan review finds critical gaps before -implementation starts, or when the plan itself needs user correction before it -can be safely executed. - -## Required File Shape - -The status file must use concise Markdown with these sections: - -```markdown -# - - -## Status - - -## Reason - - -## Completed -- - -## Remaining -- - -## Validation -- - -## Next - - -## Resume Notes - -``` - -The file must include only commands that were actually run. If validation was -not run, the file must say why. - -## Workflow Change - -Add a new terminal step to the skill after task execution and after any stop -condition: - -1. Determine the final status from the execution outcome. -2. Build the target status filename from the plan path and status. -3. Remove or replace stale sibling files matching the same plan basename and a - supported status suffix. -4. Write the compact status file. -5. Report the status file path in the final chat response. - -This step must also run when the agent stops before implementation because the -plan has critical gaps. - -## Relationship To Existing Finishing Step - -The existing `superpowers-finishing-a-development-branch` handoff remains part -of the workflow. - -`DONE` is valid only after that finishing workflow is complete. If task work is -complete but the finishing workflow is not complete, the correct status is -`PARTIAL` unless the user explicitly accepts the remaining gap. - -## Anti-Scope - -- Do not create timestamped status history files. -- Do not embed the only state summary inside the original plan. -- Do not require a repository-specific `tmp/superpowers/` path. -- Do not introduce a helper script unless the implementation clearly needs one. -- Do not create status files when there is no reliable plan path. - -## Validation - -The implementation should run the closest available skill validator for the -changed skill. In this repository, the expected validator is: - -```bash -./.github/scripts/.venv/bin/python ./.github/scripts/validate_internal_skills.py --skill superpowers-executing-plans --strict -``` - -If that validator is unavailable, run the closest Markdown or repository check -and report the validation gap explicitly. - -## Acceptance Criteria - -- `.github/skills/superpowers-executing-plans/SKILL.md` requires a status file - for every terminal execution outcome. -- The filename contract is clearly documented as - `..md`. -- The allowed statuses and their meanings are unambiguous. -- The status file content is short and resume-oriented. -- The skill preserves its existing critical-review, task execution, blocker, - and finishing workflow behavior. -- The implementation validates the changed skill or reports why validation could - not be run. From da6fec1e213abc0cc352d42cc7e4a1f003b49f8a Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 00:31:33 +0200 Subject: [PATCH 20/44] refactor: update SKILL.md and spec-document-reviewer-prompt.md to clarify handling of design documents and tmp/ directory usage --- .github/skills/superpowers-brainstorming/SKILL.md | 9 +++++---- .../spec-document-reviewer-prompt.md | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/skills/superpowers-brainstorming/SKILL.md b/.github/skills/superpowers-brainstorming/SKILL.md index ddfb2c4..16ebd28 100644 --- a/.github/skills/superpowers-brainstorming/SKILL.md +++ b/.github/skills/superpowers-brainstorming/SKILL.md @@ -26,7 +26,7 @@ You MUST create a task for each of these items and complete them in order: 3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria 4. **Propose 2-3 approaches** — with trade-offs and your recommendation 5. **Present design** — in sections scaled to their complexity, get user approval after each section -6. **Write design doc** — save to `tmp/superpowers/specs/YYYY-MM-DD--design.md` and commit +6. **Write design doc** — if the user wants a retained file, save it to `tmp/superpowers/specs/YYYY-MM-DD--design.md`; never commit files from `tmp/` 7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) 8. **User reviews written spec** — ask user to review the spec file before proceeding 9. **Transition to implementation** — invoke writing-plans skill to create implementation plan @@ -103,10 +103,11 @@ digraph brainstorming { **Documentation:** -- Write the validated design (spec) to `tmp/superpowers/specs/YYYY-MM-DD--design.md` +- Write the validated design (spec) to chat by default +- Persist it to `tmp/superpowers/specs/YYYY-MM-DD--design.md` only when the user explicitly wants a retained file - (User preferences for spec location override this default) - Use elements-of-style:writing-clearly-and-concisely skill if available -- Commit the design document to git +- Never commit files from `tmp/` **Spec Self-Review:** After writing the spec document, look at it with fresh eyes: @@ -121,7 +122,7 @@ Fix any issues inline. No need to re-review — just fix and move on. **User Review Gate:** After the spec review loop passes, ask the user to review the written spec before proceeding: -> "Spec written and committed to ``. Please review it and let me know if you want to make any changes before we start writing out the implementation plan." +> "Spec written to ``. Please review it and let me know if you want to make any changes before we start writing out the implementation plan." Wait for the user's response. If they request changes, make them and re-run the spec review loop. Only proceed once the user approves. diff --git a/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md b/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md index 08e54f8..9c053f0 100644 --- a/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md +++ b/.github/skills/superpowers-brainstorming/spec-document-reviewer-prompt.md @@ -4,7 +4,7 @@ Use this template when dispatching a spec document reviewer subagent. **Purpose:** Verify the spec is complete, consistent, and ready for implementation planning. -**Dispatch after:** Spec document is written to tmp/superpowers/specs/ +**Dispatch after:** The retained spec document, if one was requested, is written to its `tmp/` path ``` Subagent (general-purpose): From 249535052fd265b55c3a857de1dd8434e15135fa Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 00:41:13 +0200 Subject: [PATCH 21/44] feat: enhance critical-master validation with new fixtures and update scripts for improved output handling --- .github/scripts/run.sh | 6 ++- .../internal-gateway-critical-master/SKILL.md | 4 +- .../agents/openai.yaml | 2 +- .../fixtures/critical_output_advisory.md | 19 +++++++ ...critical_output_invalid_missing_section.md | 15 ++++++ .../fixtures/critical_output_valid.md | 20 ++++++++ .../references/maintenance-guidance.md | 5 ++ .../scripts/validate_critical_output.py | 7 ++- Makefile | 2 +- tests/test_critical_master_helpers.py | 20 ++++++++ tests/test_critical_master_validator.py | 50 +++++++++++++++++++ tests/test_run_sh_dispatch.py | 28 +++++++++++ 12 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 .github/skills/internal-gateway-critical-master/fixtures/critical_output_advisory.md create mode 100644 .github/skills/internal-gateway-critical-master/fixtures/critical_output_invalid_missing_section.md create mode 100644 .github/skills/internal-gateway-critical-master/fixtures/critical_output_valid.md create mode 100644 .github/skills/internal-gateway-critical-master/references/maintenance-guidance.md create mode 100644 tests/test_critical_master_helpers.py create mode 100644 tests/test_critical_master_validator.py create mode 100644 tests/test_run_sh_dispatch.py diff --git a/.github/scripts/run.sh b/.github/scripts/run.sh index d0efe3c..257f1f7 100755 --- a/.github/scripts/run.sh +++ b/.github/scripts/run.sh @@ -144,7 +144,7 @@ resolve_script() { printf '%s\n' "$SCRIPT_DIR/sync_copilot_catalog.py" ;; validate_critical_output) - printf '%s\n' "$SCRIPT_DIR/../skills/internal-gateway-critical-master/scripts/validate_critical_output.py" + printf '%s\n' "$REPO_ROOT/.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py" ;; validate_internal_skills|validate_internal_skills.py) printf '%s\n' "$SCRIPT_DIR/validate_internal_skills.py" @@ -223,4 +223,6 @@ PYTHON_VERSION_FILE="$REPO_ROOT/.python-version" REQUIREMENTS_FILE="$SCRIPT_DIR/requirements.txt" REQUIREMENTS_HASH_FILE="$VENV_DIR/.requirements.sha256" -main "$@" +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + main "$@" +fi diff --git a/.github/skills/internal-gateway-critical-master/SKILL.md b/.github/skills/internal-gateway-critical-master/SKILL.md index 6f14443..e683de1 100644 --- a/.github/skills/internal-gateway-critical-master/SKILL.md +++ b/.github/skills/internal-gateway-critical-master/SKILL.md @@ -85,7 +85,9 @@ For a pre-mortem, state one concrete failure, list the 2-3 most likely root caus - Optional: `scripts/validate_critical_output.py` checks a rendered output against the contract in `references/output-contract.md`. - The optional validator and its pure helper live inside this skill bundle so the skill can be copied without depending on repo-global Python modules. -- Bundle-level budget is owned by the repo-wide token checks; routing into the skill is owned by the calling gateway. +- Reuse `fixtures/critical_output_valid.md` and sibling fixture samples instead of repeating long inline payloads. +- Follow `references/maintenance-guidance.md` for fixture reuse, cache-aware search discipline, and analyzer boundary checks. +- Keep this bundle self-contained: do not require instructions, examples, or enforcement rules from outside this directory. ## Outcome meanings diff --git a/.github/skills/internal-gateway-critical-master/agents/openai.yaml b/.github/skills/internal-gateway-critical-master/agents/openai.yaml index 20c1c0e..7d72cc3 100644 --- a/.github/skills/internal-gateway-critical-master/agents/openai.yaml +++ b/.github/skills/internal-gateway-critical-master/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Internal Gateway Critical Master" - short_description: "Critical challenge only" + short_description: "Critical challenge for plans and decisions" default_prompt: "Use $internal-gateway-critical-master to pressure-test this plan or decision. Follow the three-phase procedure in SKILL.md, select 2-3 lenses (with the third being lateral: analogy or reverse assumption), and close with the structured output contract in references/output-contract.md. Keep the total response under 600 words." diff --git a/.github/skills/internal-gateway-critical-master/fixtures/critical_output_advisory.md b/.github/skills/internal-gateway-critical-master/fixtures/critical_output_advisory.md new file mode 100644 index 0000000..fc46bd7 --- /dev/null +++ b/.github/skills/internal-gateway-critical-master/fixtures/critical_output_advisory.md @@ -0,0 +1,19 @@ +## Summary + +This summary intentionally exceeds the allowed word budget so the validator emits a non-blocking finding while the overall structure still remains valid for strict-mode coverage. It does so by repeating the same narrow point in several different clauses, which is exactly the kind of padding the contract is supposed to reject. The content still looks structurally correct, but the summary itself should cross the seventy-five word threshold and leave the rest of the document usable for strict-mode regression coverage. + +## Findings + +### 1. The output is still structurally valid + +- **Impact:** The content can be reviewed, but it should still trip strict mode because the summary is too long. +- **Evidence:** `inference` - the section uses a valid finding shape. +- **Mitigation:** Shorten the summary back under the limit. + +## Synthesis + +The output is structurally valid, but the word-budget finding should cause `--strict` to fail. + +## Outcome + +`accept-with-risk` diff --git a/.github/skills/internal-gateway-critical-master/fixtures/critical_output_invalid_missing_section.md b/.github/skills/internal-gateway-critical-master/fixtures/critical_output_invalid_missing_section.md new file mode 100644 index 0000000..694cfdc --- /dev/null +++ b/.github/skills/internal-gateway-critical-master/fixtures/critical_output_invalid_missing_section.md @@ -0,0 +1,15 @@ +## Summary + +This output is missing the required outcome section. + +## Findings + +### 1. The output omits a required contract field + +- **Impact:** The validator cannot confirm the final decision. +- **Evidence:** `confirmed` - the `## Outcome` section is absent. +- **Mitigation:** Add the missing `## Outcome` section and wrap the value in backticks. + +## Synthesis + +The structure is incomplete, so the output should fail validation. diff --git a/.github/skills/internal-gateway-critical-master/fixtures/critical_output_valid.md b/.github/skills/internal-gateway-critical-master/fixtures/critical_output_valid.md new file mode 100644 index 0000000..4621746 --- /dev/null +++ b/.github/skills/internal-gateway-critical-master/fixtures/critical_output_valid.md @@ -0,0 +1,20 @@ +## Summary + +We are challenging a proposal to move validation logic from CI into a pre-commit hook. The change matters now because it affects every contributor's workflow and could hide failures from the central audit log. + +## Findings + +### 1. The audit trail weakens + +- **Impact:** Central CI logs become incomplete for compliance reviews. +- **Evidence:** `inference` - no replacement logging is described. +- **Mitigation:** Add a signed attestation step before the hook is enabled. +- **Reframe:** Treat local validation as an early filter, not a replacement for CI. + +## Synthesis + +The strongest risk is compliance visibility, not implementation effort. The proposal can work if the mitigation is accepted. + +## Outcome + +`accept-with-risk` diff --git a/.github/skills/internal-gateway-critical-master/references/maintenance-guidance.md b/.github/skills/internal-gateway-critical-master/references/maintenance-guidance.md new file mode 100644 index 0000000..78efbb1 --- /dev/null +++ b/.github/skills/internal-gateway-critical-master/references/maintenance-guidance.md @@ -0,0 +1,5 @@ +# Maintenance Guidance + +- Prefer `fixtures/` samples over repeated inline Markdown payloads. +- When the target is tracked source, exclude generated caches such as `graphify-out/`, `.pytest_cache/`, `.venv/`, and `__pycache__/` from broad searches. +- If maintenance work reaches `main.jsonl`, confirm the analyzer contract accepts that file type before running it. diff --git a/.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py b/.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py index 61751cd..65cf817 100644 --- a/.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py +++ b/.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py @@ -2,9 +2,9 @@ """Validate a critical-master output Markdown against the output contract. Usage examples: - python3 ./.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py --file tests/fixtures/critical_output_good.md - python3 ./.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py --file output.md --format json - python3 ./.github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py --file output.md --strict + python3 scripts/validate_critical_output.py --file fixtures/critical_output_valid.md + python3 scripts/validate_critical_output.py --file fixtures/critical_output_invalid_missing_section.md --format json + python3 scripts/validate_critical_output.py --file fixtures/critical_output_advisory.md --strict """ from __future__ import annotations @@ -385,7 +385,6 @@ def _compact_payload(findings: list[Finding]) -> dict[str, object]: def render_text(findings: list[Finding]) -> None: if not findings: - log_info("Output passes the contract.") return for finding in findings: marker = "BLOCKING" if finding.severity == "blocking" else "advisory" diff --git a/Makefile b/Makefile index 8a64bde..537f9a2 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ skill-lint: scripts-bootstrap @$(SCRIPTS_RUNNER) validate_internal_skills --root . --strict critical-validate: scripts-bootstrap - @$(SCRIPTS_RUNNER) validate_critical_output --file tests/fixtures/critical_output_good.md + @$(SCRIPTS_RUNNER) validate_critical_output --file .github/skills/internal-gateway-critical-master/fixtures/critical_output_valid.md docs-lint: @if command -v markdownlint-cli2 >/dev/null 2>&1; then \ diff --git a/tests/test_critical_master_helpers.py b/tests/test_critical_master_helpers.py new file mode 100644 index 0000000..b840c8d --- /dev/null +++ b/tests/test_critical_master_helpers.py @@ -0,0 +1,20 @@ +import sys +from importlib.util import module_from_spec, spec_from_file_location +from pathlib import Path + +MODULE_PATH = Path( + ".github/skills/internal-gateway-critical-master/scripts/critical_master.py" +).resolve() +SPEC = spec_from_file_location("critical_master", MODULE_PATH) +assert SPEC is not None and SPEC.loader is not None +critical_master = module_from_spec(SPEC) +sys.modules[SPEC.name] = critical_master +SPEC.loader.exec_module(critical_master) + + +def test_validate_outcome_value_accepts_allowed_value() -> None: + assert critical_master.validate_outcome_value("accept-with-risk") + + +def test_count_words_ignores_fenced_code_blocks() -> None: + assert critical_master.count_words("alpha ```python\nbeta\n``` gamma") == 2 diff --git a/tests/test_critical_master_validator.py b/tests/test_critical_master_validator.py new file mode 100644 index 0000000..bfbf12d --- /dev/null +++ b/tests/test_critical_master_validator.py @@ -0,0 +1,50 @@ +import subprocess +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +VALIDATOR = ( + REPO_ROOT + / ".github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py" +) +FIXTURES = REPO_ROOT / ".github/skills/internal-gateway-critical-master/fixtures" + + +def run_validator(*args: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, str(VALIDATOR), *args], + cwd=REPO_ROOT, + text=True, + capture_output=True, + check=False, + ) + + +def test_valid_fixture_passes() -> None: + result = run_validator("--file", str(FIXTURES / "critical_output_valid.md")) + assert result.returncode == 0 + assert result.stdout.strip() == "" + + +def test_invalid_fixture_fails() -> None: + result = run_validator( + "--file", + str(FIXTURES / "critical_output_invalid_missing_section.md"), + ) + assert result.returncode != 0 + assert "Required section" in result.stdout + + +def test_strict_mode_fails_on_advisory_finding() -> None: + result = run_validator( + "--file", + str(FIXTURES / "critical_output_advisory.md"), + "--strict", + ) + assert result.returncode != 0 + assert ( + "summary-word-limit" in result.stdout + or "total-word-limit" in result.stdout + ) + + diff --git a/tests/test_run_sh_dispatch.py b/tests/test_run_sh_dispatch.py new file mode 100644 index 0000000..6ff96cb --- /dev/null +++ b/tests/test_run_sh_dispatch.py @@ -0,0 +1,28 @@ +import subprocess +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] + + +def run_shell(command: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["bash", "-lc", command], + cwd=REPO_ROOT, + text=True, + capture_output=True, + check=False, + ) + + +def test_resolve_script_handles_validator_and_debug_log_tools() -> None: + result = run_shell( + "source ./.github/scripts/run.sh; " + "printf '%s\\n' \"$(resolve_script validate_critical_output)\"; " + "printf '%s\\n' \"$(resolve_script analyze_copilot_debug_log)\"" + ) + assert result.returncode == 0 + lines = [line.strip() for line in result.stdout.splitlines() if line.strip()] + assert lines[0].endswith( + ".github/skills/internal-gateway-critical-master/scripts/validate_critical_output.py" + ) + assert lines[1].endswith("tools/analyze_copilot_debug_log/run.sh") From 1a9047c07e6775e45a4bdb5f730e275d612db786 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 00:41:21 +0200 Subject: [PATCH 22/44] feat: add guidelines for self-contained skill bundles in AGENTS.md --- AGENTS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 66c6db6..5523672 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -133,5 +133,9 @@ as consumer-repository defaults without an explicit sync contract change. stay outside `docs/` unless a narrower owner explicitly says otherwise. - Consumer or target repositories own their local override layers after materialization. +- Skill bundles under `.github/skills/**` must be self-contained. Keep + instructions, references, examples, fixtures, scripts, and `agents/openai.yaml` + resolvable from the bundle itself; do not require bundle users to load + guidance from outside the skill directory. `` From b0b12377729246511b8459aebbfbaa5b9929add8 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 00:42:35 +0200 Subject: [PATCH 23/44] refactor: enhance self-containment guidelines for skill bundles in SKILL.md and related files --- .../skills/internal-skill-creator/SKILL.md | 35 ++++++++++++++++--- .../internal-skill-creator/agents/openai.yaml | 2 +- .../references/writing-skills-checklist.md | 7 ++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/.github/skills/internal-skill-creator/SKILL.md b/.github/skills/internal-skill-creator/SKILL.md index 289013d..ecf1747 100644 --- a/.github/skills/internal-skill-creator/SKILL.md +++ b/.github/skills/internal-skill-creator/SKILL.md @@ -23,6 +23,11 @@ Keep the ownership model explicit: This means `internal-skill-creator` should trigger first for repository-owned skill work, establish the local boundary, and then deliberately hand only the remaining bundle mechanics to `openai-skill-creator` instead of competing with it or duplicating it. +Treat skill self-containment as a default local quality gate: when creating or +materially revising a skill, prefer a bundle that can be understood, copied, +and validated from its own directory without depending on instructions, +examples, or the only runnable automation living elsewhere in the repository. + Use `local-agent-sync-external-resources` through `local-sync-external-resources` when the task is broader catalog governance, sync-managed external assets, or inventory-wide retirement and refresh work. Use `internal-agent-creator` when the primary output is an agent change or an agent/skill boundary rewrite. @@ -35,6 +40,9 @@ Use `internal-agent-creator` when the primary output is an agent change or an ag - Load `references/writing-skills-checklist.md` when creating a new skill or materially revising an existing one. - Read `openai-skill-creator` only after the local boundary is clear and only for the remaining bundle work that this skill is not meant to repeat. - Confirm the target `SKILL.md` has an up-to-date `## Referenced skills` section immediately after the H1 before finalizing a new or materially revised skill. +- Inventory the touched bundle itself before widening scope: `SKILL.md`, + `agents/openai.yaml`, existing `references/`, `scripts/`, `assets/`, and + `fixtures/` if present. ## When to use @@ -59,6 +67,7 @@ This skill should do: - decide whether the task really belongs to a repository-owned skill in this repository - choose no-op, reuse, revise in place, split, replace, or retire - define the local ownership boundary and trigger wording +- enforce bundle self-containment for created or materially revised skills - enforce the failing-baseline rule, token discipline, and skill-type testing expectations - re-check routing fallout in nearby repository-owned assets @@ -103,6 +112,13 @@ Do not restate the full OpenAI creation workflow here. Use this skill to decide - Preserve a working `description:` during token optimization unless the baseline shows the route itself is the problem. - Keep the body lean. Put only the local contract in `SKILL.md` and move optional depth into references or reusable tools when repeated need justifies it. - Every repository-owned `SKILL.md` this skill creates or materially revises must keep `## Referenced skills` immediately after the H1. +- Treat self-containment as the default for repository-owned skill bundles. Keep + required instructions, references, examples, fixtures, scripts, metadata, and + deterministic automation inside the bundle unless the contract explicitly says + otherwise. +- Do not leave a touched skill operationally dependent on guidance, examples, + or the only runnable engine outside its own directory when the same need can + be satisfied inside the bundle with a smaller, clearer contract. - In `## Referenced skills`, list every other skill the file asks the agent to load, route to, compare against, or delegate to; use `- None.` only when no other skill is referenced. - Each referenced-skill item must name the skill in backticks and state the load, route, delegation, or comparison condition in one short phrase. - Update `## Referenced skills` whenever adding, removing, renaming, or repairing a skill reference. Treat stale or missing skill names as validation failures. @@ -111,6 +127,9 @@ Do not restate the full OpenAI creation workflow here. Use this skill to decide - If a reference becomes the canonical detail owner, trim matching duplication from the paired `SKILL.md` or agent in the same change. - Prefer `references/` over new `scripts/` for static tables, starter templates, and audit taxonomies. Add scripts only when the workflow is deterministic, repeated, and execution-heavy. - When direct-copy portability or out-of-repo execution is part of the skill contract, keep the required deterministic automation inside the skill bundle and leave repository entrypoints as thin wrappers. +- For touched skill bundles, prefer bundle-relative references to files under + `references/`, `scripts/`, or `fixtures/` over repository-rooted paths to the + same bundle files. - Keep cross-references explicit instead of duplicating large chunks of generic bundle guidance. - In `SKILL.md`, reference another skill by name and behavior only. Do not cite file paths inside another skill bundle; those files are private to the owning skill and may change. - In source-side skill Markdown, cite only paths that exist on disk in the source repository. When sync materializes a target-only file, prefer the source template path or descriptive prose over the consumer-only materialized path. @@ -145,13 +164,16 @@ After the local decision gate is complete, hand off to `openai-skill-creator` on Decide what this skill owns locally and which adjacent owner should win when the task is really sync governance, agent authoring, or another domain. 4. Isolate the remainder. Identify which parts of the job are still local policy work and which parts are now generic OpenAI bundle work. -5. Hand off only the remainder that OpenAI already handles well. +5. Enforce bundle locality before handoff. + Repair repo-rooted self-references, missing bundle-local examples, and any + external-only operating engine that would break a copied-out skill. +6. Hand off only the remainder that OpenAI already handles well. Load `openai-skill-creator` for scaffolding, resource anatomy, `agents/openai.yaml`, or structural validation, but do not replay its full workflow in this skill. -6. Resume local control for wrapper checks. +7. Resume local control for wrapper checks. Use `references/writing-skills-checklist.md` to tighten trigger wording, token discipline, loophole closure, and test design. -7. Validate the right thing. +8. Validate the right thing. Ensure OpenAI-side structural checks ran if bundle mechanics changed, then check retrieval quality plus skill-type behavior before treating the skill as done. -8. Re-check routing fallout. +9. Re-check routing fallout. Update nearby references or paired agent text only when the visible local entrypoint or ownership meaning actually changed. Use `references/writing-skills-checklist.md` for the anti-rationalization rules, token-discipline reminders, and skill-type testing expectations that this wrapper should enforce. @@ -174,6 +196,11 @@ Then confirm: - the edited skill does not repeat agent-owned routing or boundary language. - the edited skill points to reference-owned deep material instead of copying it back into `SKILL.md`. - the edited `SKILL.md` does not point at another skill's internal files. +- the touched bundle is self-contained by default: required instructions, + examples, fixtures, metadata, and deterministic automation are bundle-local, + or any exception is explicit and justified by the contract. +- self-references inside the touched bundle use bundle-relative paths unless a + different path shape is required by the contract. - any paired agent or local references still agree with the skill boundary when they exist. - when direct-copy portability or out-of-repo execution is part of the skill contract, the bundle still contains the required runnable automation and repository scripts do not become the only operating engine. - OpenAI-side scaffolding or validation was invoked only when the remaining work actually required it. diff --git a/.github/skills/internal-skill-creator/agents/openai.yaml b/.github/skills/internal-skill-creator/agents/openai.yaml index 3a9db7c..66b911c 100644 --- a/.github/skills/internal-skill-creator/agents/openai.yaml +++ b/.github/skills/internal-skill-creator/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Internal Skill Creator" short_description: "First entrypoint for repo skill work" - default_prompt: "Use $internal-skill-creator first to create or revise a repository-owned skill in .github/skills/ before loading the embedded OpenAI skill-creation workflow." + default_prompt: "Use $internal-skill-creator first to create or revise a repository-owned skill in .github/skills/. Establish the local boundary, keep the touched bundle self-contained with bundle-local references and automation by default, then load the embedded OpenAI skill-creation workflow only for the remaining generic bundle work." diff --git a/.github/skills/internal-skill-creator/references/writing-skills-checklist.md b/.github/skills/internal-skill-creator/references/writing-skills-checklist.md index 54806a8..1103460 100644 --- a/.github/skills/internal-skill-creator/references/writing-skills-checklist.md +++ b/.github/skills/internal-skill-creator/references/writing-skills-checklist.md @@ -36,6 +36,10 @@ This is a local distilled checklist informed by `writing-skills`. It exists to k - Keep `SKILL.md` lean and move deeper material into `references/` or reusable tools only when justified. - If the skill sits behind a paired agent, keep route and boundary language out of `SKILL.md`. - Preserve a working `description:` during token cuts unless the baseline shows routing is wrong. +- When touching a skill bundle, verify that the bundle stays self-contained by + default: required instructions, examples, fixtures, metadata, and + deterministic automation should live inside the bundle unless the contract + explicitly documents an exception. - For material skill-bundle revisions, measure the touched bundle or loaded files before and after the first material patch with the same token estimate; if no exact script exists, state the closest measurement. - Prefer focused references or reusable scripts over broad context dumps when a change needs supporting detail. - Cross-reference reusable material instead of restating it. @@ -44,6 +48,9 @@ This is a local distilled checklist informed by `writing-skills`. It exists to k - Prefer moving static lookup tables, starter templates, and detailed taxonomies into `references/`. - Prefer new `references/` over new `scripts/` unless the workflow is deterministic, repeated, and execution-heavy. - If the skill must stay direct-copy portable or runnable outside the source repository, keep the required deterministic automation, loaders, and dependency bootstrap inside the bundle; repository scripts may wrap that engine but should not be the only runnable owner. +- Prefer bundle-relative self-references to files under `references/`, + `scripts/`, and `fixtures/` over repository-rooted paths to the same bundle + files. - Reference other skills by skill name and owner behavior, not by file paths inside their bundles. - Keep `## Referenced skills` as an audit index: one backticked skill name plus one short owner-behavior phrase per item, or `- None.` when no other skill is referenced. - Update the referenced-skill index whenever the body adds, removes, renames, routes to, delegates to, or compares against another skill. From 0504fc47ec3aab975971d2188d7d12ccb81dda58 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 01:44:21 +0200 Subject: [PATCH 24/44] refactor: centralize internal tdd routing --- .github/skills/internal-tdd/SKILL.md | 88 ++++++------------- .../skills/internal-tdd/agents/openai.yaml | 4 +- 2 files changed, 30 insertions(+), 62 deletions(-) diff --git a/.github/skills/internal-tdd/SKILL.md b/.github/skills/internal-tdd/SKILL.md index 1eea03f..6c9d1e5 100644 --- a/.github/skills/internal-tdd/SKILL.md +++ b/.github/skills/internal-tdd/SKILL.md @@ -1,6 +1,6 @@ --- name: internal-tdd -description: Use when executable behavior should be delivered test-first, including red-green-refactor feature work, bugfix regression tests, public-interface test design, or validation seams. +description: Use when coding changes have executable behavior, including features, bugfixes, behavior changes, public-interface changes, regression tests, contract seams, or risk-driven test-first guardrails. --- # Internal TDD @@ -9,85 +9,56 @@ description: Use when executable behavior should be delivered test-first, includ This index lists every other skill that this file asks the agent to load, route to, compare against, or delegate to. -- `internal-code-review`: defect-first review of test quality, coverage gaps, and false-confidence seams. -- `internal-debugging`: root-cause diagnosis before regression tests when the failure is not understood. -- `internal-java-project`: Java test layout, fixtures, and assertions. -- `internal-nodejs-project`: Node.js or TypeScript test layout, fixtures, and assertions. -- `internal-python-project`: Python test layout, fixtures, and assertions. -- `superpowers-test-driven-development`: strict red-green-refactor execution for explicit TDD workflow requests. -- `superpowers-verification-before-completion`: evidence gate before claiming red-green-refactor or regression coverage. +- `internal-debugging`: root-cause diagnosis before regression tests when the failure is not yet understood. +- `superpowers-test-driven-development`: strict red-green-refactor execution when an executable test-first slice is selected. +- `superpowers-verification-before-completion`: evidence gate before claiming coverage, fix completion, or red-green-refactor success. -Use this skill as the repository-owned meta-owner for test-first delivery. Keep -the loop focused on observable behavior through public interfaces. +Use this skill as the repository-owned owner for coding changes with executable behavior. Keep the test strategy risk-driven, keep the loop focused on observable behavior, and explain briefly when no new test is useful. ## When to use -- The user asks for TDD, red-green-refactor, test-first work, or integration - tests before implementation. -- A bugfix has a meaningful executable seam and needs a regression test before - the fix. -- A script, validator, reusable module, or application path changes behavior - that can be checked through a stable interface. -- A risky behavior change benefits from one small vertical slice at a time. -- A vertical slice needs test-first delivery for one observable behavior inside that slice. +- Features, bugfixes, or intentional behavior changes with an executable seam. +- Public-interface changes, adapters, tools, modules, validators, or structured outputs whose behavior can be checked through a stable boundary. +- Prompt, agent, or LLM-output drift when the change has an executable or evaluable contract with concrete failure examples. +- Any coding change where the main failure mode should be identified before implementation and guarded with the lightest useful test-first slice. ## When not to use -- Do not force TDD onto Markdown-only, prompt-only, agent-only, skill-only, - instruction-only, inventory-only, or governance-only edits. -- Do not use TDD as the default gate for mechanical formatting, catalog - rebuilds, generated inventory, or text realignment with no executable seam. -- Do not write a bulk suite before implementation. Use one behavior at a time. +- Prose-only, prompt-only, skill-only, inventory-only, generated-only, formatting-only, or governance-only edits with no executable or evaluable contract. +- Review-only work; use the review lane separately when the job is defect-first review rather than implementation. +- Mechanical realignment with no behavior change and no credible executable seam. ## Applicability Levels | Level | Use when | Required posture | | --- | --- | --- | -| Mandatory | The user explicitly asks for TDD, or a bugfix changes executable behavior with a credible regression seam. | Write the failing test before the fix. | -| Recommended | The change touches domain logic, scripts, validators, APIs, or workflow behavior with meaningful edge cases. | Start with the most valuable public-interface behavior. | -| Not suitable | The change is repository-owned prose, prompt, agent, skill, instruction, inventory, or governance text with no executable contract, or the only current theory is an unproven external-source mismatch. | Use review, validator, catalog checks, or diagnosis first. | - -For Markdown-only, agent, skill, instruction, inventory, or governance-text changes, do not manufacture tests to satisfy TDD. Use the relevant validator, catalog check, or review gate unless the user explicitly asks for a test-first workflow and a credible executable contract exists. +| Mandatory | The change adds or changes executable behavior, or the user explicitly asks for TDD. | Identify the main failure mode, choose the lightest useful guardrail, and write or update the focused failing test first unless no new test is useful. | +| Recommended | The change touches public boundaries, adapters, modules, structured outputs, or evaluable prompt behavior with meaningful risk. | Prefer contract tests at public boundaries and keep the slice small and reviewable. | +| Not suitable | The change is prose-only, prompt-only, skill-only, inventory-only, generated-only, formatting-only, or governance-only with no executable or evaluable contract. | Do not manufacture tests; explain briefly why no new test is useful and use the closest validator or review gate instead. | ## Workflow -1. Identify the public interface or operator-facing command that should carry - the behavior. If the current theory is an external-source mismatch, first - prove the authoritative source and the local executable seam through - diagnosis or validation instead of writing a speculative test. -2. Pick one behavior for the first vertical slice. Avoid testing implementation - shape, private helpers, or imagined future branches. -3. Write one failing test and confirm it fails for the intended reason. -4. Implement the smallest correct change that makes that test pass. -5. Repeat one behavior at a time. Let each passing slice inform the next test. -6. Refactor only while green, and rerun the focused tests after each refactor. -7. Finish by running the closest broader validation that covers the changed - behavior. +1. Identify the main failure mode before implementation. +2. Choose the lightest useful guardrail for the active risk. +3. Write or update a focused failing test first for new or changed behavior. +4. Add the regression test before the bug fix when the task is a bugfix. +5. Prefer contract tests at public boundaries over tests of private implementation details. +6. Use small, reviewable golden or snapshot tests only when they are the clearest guardrail. +7. For prompt, agent, or LLM-output drift, define concrete eval examples or failure cases before changing the implementation. +8. Finish with the closest broader validation and do not weaken, delete, skip, or rewrite tests just to pass. ## Test Shape Rules - Test behavior, not implementation details. - Prefer public APIs, CLIs, generated outputs, or stable validator entrypoints. -- Do not encode an unverified external-source hypothesis as a regression test. - Prove the mismatch, source authority, and local seam first. -- Mock only external boundaries or expensive collaborators when the real path - would make the test slow, flaky, unsafe, or unavailable. +- Mock only external boundaries or expensive collaborators when the real path would make the test slow, flaky, unsafe, or unavailable. - Keep test names in the repository's domain language when one exists. -- Use stack-specific internal skills for idiomatic test layout, fixtures, and - framework conventions. ## Support Skills -- `superpowers-test-driven-development`: strict red-green-refactor execution - when the user explicitly asks for a TDD workflow. -- `superpowers-verification-before-completion`: evidence gate before claiming - red-green-refactor completion or regression coverage. -- `internal-debugging`: root-cause diagnosis before the regression test when the - failure is not yet understood. -- `internal-python-project`, `internal-nodejs-project`, `internal-java-project`, - and other stack owners: idiomatic test placement, fixtures, and assertions. -- `internal-code-review`: defect-first review of test quality, coverage gaps, - and false-confidence seams. +- `superpowers-test-driven-development`: core red-green-refactor execution when an executable test-first slice is selected. +- `internal-debugging`: root-cause diagnosis before the regression test when the failure is not yet understood. +- `superpowers-verification-before-completion`: evidence gate before claiming coverage, fix completion, or red-green-refactor success. ## Validation @@ -96,7 +67,4 @@ For Markdown-only, agent, skill, instruction, inventory, or governance-text chan - The implementation is the smallest behavior needed for the active slice. - Focused tests and the closest broader validation pass. - No speculative tests or features were added beyond the requested behavior. -- External-source behavior is covered only after its authority and local seam - were proven, or the gap was routed back to diagnosis or validation. -- Use `superpowers-verification-before-completion` before claiming - red-green-refactor completion or that a regression is covered. +- Use `superpowers-verification-before-completion` before claiming red-green-refactor completion or that a regression is covered. diff --git a/.github/skills/internal-tdd/agents/openai.yaml b/.github/skills/internal-tdd/agents/openai.yaml index 2671dc6..1e07b99 100644 --- a/.github/skills/internal-tdd/agents/openai.yaml +++ b/.github/skills/internal-tdd/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Internal TDD" - short_description: "Test-first delivery through public interfaces" - default_prompt: "Use $internal-tdd to deliver this executable behavior with a red-green-refactor loop." + short_description: "Risk-driven test-first guardrails for executable coding changes" + default_prompt: "Use $internal-tdd for coding changes with executable behavior and choose the lightest useful test-first guardrail before implementation." From f117e98b1ff3a3a241effc1f4d68a86e8aa6f363 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 01:44:27 +0200 Subject: [PATCH 25/44] refactor: remove python skill tdd routing --- .github/skills/internal-python-project/SKILL.md | 3 +-- .github/skills/internal-python-script/SKILL.md | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/skills/internal-python-project/SKILL.md b/.github/skills/internal-python-project/SKILL.md index 03dfbe8..74d288e 100644 --- a/.github/skills/internal-python-project/SKILL.md +++ b/.github/skills/internal-python-project/SKILL.md @@ -8,7 +8,6 @@ description: Use when creating or modifying Python package or application code w ## Referenced skills - `internal-python-script`: route CLI adapters, direct operator execution, and rich console reporting boundaries. -- `internal-tdd`: load for bugfixes, features, or project behavior changes with a meaningful public or service seam. ## When to use @@ -62,7 +61,7 @@ Load `references/logging-and-reporting.md` when project code needs a professiona - BDD-like names: `given_when_then` style. - Prefer fixtures, parameterization, and mocking only when they reduce duplication or isolate real external boundaries. - Use coverage reports to close meaningful behavioral gaps, not as a blanket 100% doctrine. -- For bugfixes, features, and intentional behavior changes, start test-first through the public API, service boundary, adapter contract, or framework-owned seam: add or update the failing test, confirm it fails for the intended reason, then implement the smallest fix. +- For bugfixes, features, and intentional behavior changes, follow the repository test strategy for the task and keep the most meaningful public API, service boundary, adapter contract, or framework-owned seam under focused test coverage. - For refactors, prose-only updates, generated fixtures, or mechanical formatting with no executable behavior change, run existing focused tests and syntax validation instead of manufacturing speculative tests. ## Architecture and framework guidance diff --git a/.github/skills/internal-python-script/SKILL.md b/.github/skills/internal-python-script/SKILL.md index 8a08cc7..3243203 100644 --- a/.github/skills/internal-python-script/SKILL.md +++ b/.github/skills/internal-python-script/SKILL.md @@ -8,7 +8,6 @@ description: Use when creating or modifying standalone Python scripts, CLIs, or ## Referenced skills - `internal-python-project`: route away when imported package, application, service, or framework behavior becomes the primary contract. -- `internal-tdd`: load for bugfixes, features, or script behavior changes with a meaningful executable seam. ## When to use @@ -98,7 +97,7 @@ Keep these rules visible while drafting: - Follow the repository pytest defaults. - Use coverage reports to inspect missing behavior on touched code, not to force blanket 100% coverage. -- For bugfixes, features, and intentional behavior changes, start test-first through the public CLI or stable helper seam: add or update the failing test, confirm it fails for the intended reason, then implement the smallest fix. +- For bugfixes, features, and intentional behavior changes, follow the repository test strategy for the task and keep the public CLI or stable helper seam covered by focused tests. - For refactors, prose-only updates, generated fixtures, or mechanical formatting with no executable behavior change, run the existing focused tests plus `py_compile` or `compileall` instead of manufacturing speculative tests. - When Ruff is configured, run `ruff format` for formatting-only Python edits and `ruff check` for lint feedback before wider test runs. - Prefer existing repository commands such as `make lint`, `make test`, or a shared script runner before inventing a one-off validation path. From ab92d8b75e2538f150a7a8b347dad026c659d1d4 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 01:44:31 +0200 Subject: [PATCH 26/44] refactor: soften stack test-first mandates --- .github/skills/internal-java-project/SKILL.md | 2 +- .github/skills/internal-nodejs-project/SKILL.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/skills/internal-java-project/SKILL.md b/.github/skills/internal-java-project/SKILL.md index 9ccb330..5c551b5 100644 --- a/.github/skills/internal-java-project/SKILL.md +++ b/.github/skills/internal-java-project/SKILL.md @@ -56,7 +56,7 @@ Load `references/examples.md` when you need a minimal class or test example. - Use `@ParameterizedTest`, `assertAll`, `@Nested`, and `@Tag` when they improve test clarity rather than just adding ceremony. - Use Spring test slices such as `@WebMvcTest` or `@DataJpaTest` before defaulting to full-context tests. - Use Testcontainers when integration tests need real databases or external dependencies. -- For behavior changes or bug fixes, write or update the failing test first, then implement and re-run. +- For behavior changes or bug fixes, keep focused tests on observable service, controller, module, or boundary behavior and rerun the narrowest meaningful test set before widening scope. - For pure refactors, keep behavior stable and run compile plus existing tests before and after. - Mock only external boundaries and keep internal collaborators real where practical. - Prefer targeted coverage for changed behavior and boundary failures over broad percentage goals. diff --git a/.github/skills/internal-nodejs-project/SKILL.md b/.github/skills/internal-nodejs-project/SKILL.md index 21410cf..fd59461 100644 --- a/.github/skills/internal-nodejs-project/SKILL.md +++ b/.github/skills/internal-nodejs-project/SKILL.md @@ -55,7 +55,7 @@ Load `references/examples.md` when you need a minimal module or test example. - Follow the repository test-stack defaults. - If the repository already uses Jest, stay with local Jest conventions instead of introducing mixed test stacks. -- For behavior changes or bug fixes, write or update the failing test first, then implement and re-run. +- For behavior changes or bug fixes, keep focused tests on observable module, API, adapter, or boundary behavior and rerun the smallest meaningful test set before widening scope. - For pure refactors, run existing tests before and after while keeping public behavior unchanged. - Prefer parameterized table-style tests when many input/output cases exercise one behavior. - Mock only external boundaries and keep internal modules real where practical. From 91c976063d73637eb2c58eba8330a74fff828261 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 18:32:15 +0200 Subject: [PATCH 27/44] refactor: update superpowers brainstorming guidelines for clarity and structure --- .../patches/superpowers-brainstorming.patch | 134 ++++++++++++++++++ .../references/imported-asset-overrides.yaml | 86 ++++++----- .../skills/superpowers-brainstorming/SKILL.md | 57 +++++--- 3 files changed, 224 insertions(+), 53 deletions(-) create mode 100644 .github/skills/local-agent-sync-external-resources/patches/superpowers-brainstorming.patch diff --git a/.github/skills/local-agent-sync-external-resources/patches/superpowers-brainstorming.patch b/.github/skills/local-agent-sync-external-resources/patches/superpowers-brainstorming.patch new file mode 100644 index 0000000..4d510c3 --- /dev/null +++ b/.github/skills/local-agent-sync-external-resources/patches/superpowers-brainstorming.patch @@ -0,0 +1,134 @@ +diff --git a/.github/skills/superpowers-brainstorming/SKILL.md b/.github/skills/superpowers-brainstorming/SKILL.md +index 16ebd28..47afc6e 100644 +--- a/.github/skills/superpowers-brainstorming/SKILL.md ++++ b/.github/skills/superpowers-brainstorming/SKILL.md +@@ -7,7 +7,7 @@ description: "You MUST use this before any creative work - creating features, bu + + Help turn ideas into fully formed designs and specs through natural collaborative dialogue. + +-Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval. ++Start by understanding the current project context, then Ask clarifying questions in numbered bulk question blocks. Each question must include a short recommendation, a short reason for that recommendation, and the default that will be treated as accepted when the user accepts the suggestions. Once you understand what you're building, decide whether a retained spec adds real design value or whether moving directly to an implementation plan is the better next step. + + + Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity. +@@ -23,34 +23,38 @@ You MUST create a task for each of these items and complete them in order: + + 1. **Explore project context** — check files, docs, recent commits + 2. **Offer the visual companion just-in-time** — NOT upfront. The first time a question would genuinely be clearer shown than described, offer it then (its own message); on approval its browser tab opens for you. If no visual question ever arises, never offer it. See the Visual Companion section below. +-3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria ++3. **Ask clarifying questions** — use numbered bulk question blocks with `Question`, `Recommendation`, `Why`, and `Default if accepted` + 4. **Propose 2-3 approaches** — with trade-offs and your recommendation + 5. **Present design** — in sections scaled to their complexity, get user approval after each section +-6. **Write design doc** — if the user wants a retained file, save it to `tmp/superpowers/specs/YYYY-MM-DD--design.md`; never commit files from `tmp/` +-7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) +-8. **User reviews written spec** — ask user to review the spec file before proceeding +-9. **Transition to implementation** — invoke writing-plans skill to create implementation plan ++6. **Run the Design-Depth Gate** — choose `Decision: direct plan` or `Decision: spec first`, and tell the user why ++7. **Write design doc when needed** — if the gate chooses `spec first` and the user wants a retained file, save it to `tmp/superpowers/specs/YYYY-MM-DD--design.md`; never commit files from `tmp/` ++8. **Spec self-review when needed** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) ++9. **User reviews written spec when needed** — ask user to review the spec file before proceeding ++10. **Transition to implementation planning** — invoke writing-plans skill after the user approves either the design or the direct-plan recommendation + + ## Process Flow + + ```dot + digraph brainstorming { + "Explore project context" [shape=box]; +- "Ask clarifying questions" [shape=box]; ++ "Ask bulk clarifying questions" [shape=box]; + "Propose 2-3 approaches" [shape=box]; + "Present design sections" [shape=box]; + "User approves design?" [shape=diamond]; ++ "Design-Depth Gate" [shape=diamond]; + "Write design doc" [shape=box]; + "Spec self-review\n(fix inline)" [shape=box]; + "User reviews spec?" [shape=diamond]; + "Invoke writing-plans skill" [shape=doublecircle]; + +- "Explore project context" -> "Ask clarifying questions"; +- "Ask clarifying questions" -> "Propose 2-3 approaches"; ++ "Explore project context" -> "Ask bulk clarifying questions"; ++ "Ask bulk clarifying questions" -> "Propose 2-3 approaches"; + "Propose 2-3 approaches" -> "Present design sections"; + "Present design sections" -> "User approves design?"; + "User approves design?" -> "Present design sections" [label="no, revise"]; +- "User approves design?" -> "Write design doc" [label="yes"]; ++ "User approves design?" -> "Design-Depth Gate" [label="yes"]; ++ "Design-Depth Gate" -> "Invoke writing-plans skill" [label="direct plan"]; ++ "Design-Depth Gate" -> "Write design doc" [label="spec first"]; + "Write design doc" -> "Spec self-review\n(fix inline)"; + "Spec self-review\n(fix inline)" -> "User reviews spec?"; + "User reviews spec?" -> "Write design doc" [label="changes requested"]; +@@ -58,7 +62,7 @@ digraph brainstorming { + } + ``` + +-**The terminal state is invoking writing-plans.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skill you invoke after brainstorming is writing-plans. ++**The terminal state is invoking writing-plans.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skill you invoke after brainstorming is writing-plans. The path to writing-plans may be either `Decision: direct plan` or `Decision: spec first`. + + ## The Process + +@@ -67,9 +71,13 @@ digraph brainstorming { + - Check out the current project state first (files, docs, recent commits) + - Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first. + - If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec → plan → implementation cycle. +-- For appropriately-scoped projects, ask questions one at a time to refine the idea ++- For appropriately-scoped projects, ask clarifying questions in numbered bulk question blocks to refine the idea + - Prefer multiple choice questions when possible, but open-ended is fine too +-- Only one question per message - if a topic needs more exploration, break it into multiple questions ++- Each numbered question must use this format: `Question`, `Recommendation`, `Why`, and `Default if accepted` ++- The recommendation and why must be clear and brief ++- The user may accept all suggested defaults, accept only some numbered defaults, or override any numbered recommendation ++- Accepted defaults do not mean discovery is complete. If the accepted answers create contradictions, weak assumptions, unresolved risks, or dependent decisions, ask another focused numbered bulk question block. ++- Ask another focused numbered bulk question block only for unresolved, dependent, or reopened branches. Do not ask questions that project evidence can answer. + - Focus on understanding: purpose, constraints, success criteria + + **Exploring approaches:** +@@ -86,6 +94,23 @@ digraph brainstorming { + - Cover: architecture, components, data flow, error handling, testing + - Be ready to go back and clarify if something doesn't make sense + ++**Design-Depth Gate:** ++ ++Before writing a retained spec, decide whether the spec has meaningful marginal value over going directly to an implementation plan. ++ ++Choose `Decision: direct plan` when the target, owner, scope, constraints, rejected alternatives, and validation path are already clear, and a retained spec would mostly duplicate the implementation plan. ++ ++Choose `Decision: spec first` when product, design, architecture, data flow, user experience, rollout, or risk decisions are still material enough that a retained spec would reduce the chance of building the wrong thing. ++ ++In both cases, tell the user the decision and one short reason: ++ ++- `Decision: direct plan` ++- `Why: ` ++- `Decision: spec first` ++- `Why: ` ++ ++Ask for user approval before invoking writing-plans. Direct plan skips the retained spec, not the user approval gate. ++ + **Design for isolation and clarity:** + + - Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently +@@ -104,7 +129,7 @@ digraph brainstorming { + **Documentation:** + + - Write the validated design (spec) to chat by default +-- Persist it to `tmp/superpowers/specs/YYYY-MM-DD--design.md` only when the user explicitly wants a retained file ++- Persist it to `tmp/superpowers/specs/YYYY-MM-DD--design.md` only when `Decision: spec first` is chosen and the user explicitly wants a retained file + - (User preferences for spec location override this default) + - Use elements-of-style:writing-clearly-and-concisely skill if available + - Never commit files from `tmp/` +@@ -128,12 +153,12 @@ Wait for the user's response. If they request changes, make them and re-run the + + **Implementation:** + +-- Invoke the writing-plans skill to create a detailed implementation plan ++- Invoke the writing-plans skill to create a detailed implementation plan after the user approves either `Decision: direct plan` or the reviewed spec + - Do NOT invoke any other skill. writing-plans is the next step. + + ## Key Principles + +-- **One question at a time** - Don't overwhelm with multiple questions ++- **Bulk guided question blocks** - Ask the full known question set together, with recommendations, reasons, and defaults + - **Multiple choice preferred** - Easier to answer than open-ended when possible + - **YAGNI ruthlessly** - Remove unnecessary features from all designs + - **Explore alternatives** - Always propose 2-3 approaches before settling diff --git a/.github/skills/local-agent-sync-external-resources/references/imported-asset-overrides.yaml b/.github/skills/local-agent-sync-external-resources/references/imported-asset-overrides.yaml index f080c31..be1f974 100644 --- a/.github/skills/local-agent-sync-external-resources/references/imported-asset-overrides.yaml +++ b/.github/skills/local-agent-sync-external-resources/references/imported-asset-overrides.yaml @@ -1,42 +1,54 @@ version: 1 policy: default_rule: keep imported upstream assets verbatim - exception_rule: >- - Allow a direct imported-asset override only for a strong repo-specific need - that the user explicitly counter-validates, then register the replay patch + exception_rule: Allow a direct imported-asset override only for a strong repo-specific + need that the user explicitly counter-validates, then register the replay patch in this bundle before treating the override as active. - replay_rule: >- - Prefer a clean patch replay first, then allow a registered `git apply - --3way` fallback when upstream text drift is compatible. Stop for review if - neither path applies cleanly. + replay_rule: Prefer a clean patch replay first, then allow a registered `git apply + --3way` fallback when upstream text drift is compatible. Stop for review if neither + path applies cleanly. overrides: - - id: grill-me-bulk-recommended-questions - target_path: .github/skills/grill-me/SKILL.md - source_family: mattpocock/skills - lifecycle_mode: post-refresh-patch - apply_strategy: git-apply-3way - approval: explicit-user-counter-validated - reason: >- - Default the initial grilling pass to a dependency-ordered numbered - question set with clear recommendations that the user can accept by - default, while preserving follow-up pressure on contradictions, risks, - and unresolved decisions; keep operational gate semantics in - internal-gateway-review. - patch_path: patches/grill-me.patch - expected_content_hash: 4965b2f80bed21a005e0baa241e8e9e0d502c276cdf83c394cb3feac1d4dc71b - baseline_repo_commit: "efa058a" - validation_note: Stop the refresh if the patch does not apply cleanly; review whether an internal wrapper should replace the override. - - id: openai-spreadsheet-structured-data-evidence-budget - target_path: .github/skills/openai-spreadsheet/SKILL.md - source_family: openai/skills - lifecycle_mode: post-refresh-patch - apply_strategy: git-apply-3way - approval: explicit-user-counter-validated - reason: >- - Preserve the repository-specific structured-data evidence budget so CSV, - TSV, and XLSX work stays low-token without weakening full-file integrity - checks after an imported office-skill refresh. - patch_path: patches/openai-spreadsheet.patch - expected_content_hash: f42e8ea1128c448a7ccaa42b2ca39540c5b9109a7e6e6ee4abf4e3e5e4a80722 - baseline_repo_commit: "e6afb0d" - validation_note: Stop the refresh if the patch does not apply cleanly; review whether an internal wrapper should replace the override. +- id: grill-me-bulk-recommended-questions + target_path: .github/skills/grill-me/SKILL.md + source_family: mattpocock/skills + lifecycle_mode: post-refresh-patch + apply_strategy: git-apply-3way + approval: explicit-user-counter-validated + reason: Default the initial grilling pass to a dependency-ordered numbered question + set with clear recommendations that the user can accept by default, while preserving + follow-up pressure on contradictions, risks, and unresolved decisions; keep operational + gate semantics in internal-gateway-review. + patch_path: patches/grill-me.patch + expected_content_hash: 4965b2f80bed21a005e0baa241e8e9e0d502c276cdf83c394cb3feac1d4dc71b + baseline_repo_commit: efa058a + validation_note: Stop the refresh if the patch does not apply cleanly; review whether + an internal wrapper should replace the override. +- id: openai-spreadsheet-structured-data-evidence-budget + target_path: .github/skills/openai-spreadsheet/SKILL.md + source_family: openai/skills + lifecycle_mode: post-refresh-patch + apply_strategy: git-apply-3way + approval: explicit-user-counter-validated + reason: Preserve the repository-specific structured-data evidence budget so CSV, + TSV, and XLSX work stays low-token without weakening full-file integrity checks + after an imported office-skill refresh. + patch_path: patches/openai-spreadsheet.patch + expected_content_hash: f42e8ea1128c448a7ccaa42b2ca39540c5b9109a7e6e6ee4abf4e3e5e4a80722 + baseline_repo_commit: e6afb0d + validation_note: Stop the refresh if the patch does not apply cleanly; review whether + an internal wrapper should replace the override. +- id: superpowers-brainstorming-guided-plan-gate + target_path: .github/skills/superpowers-brainstorming/SKILL.md + source_family: obra/superpowers + lifecycle_mode: post-refresh-patch + apply_strategy: git-apply-3way + approval: explicit-user-counter-validated + reason: Preserve the repository-specific brainstorming flow that asks guided numbered + bulk question blocks, keeps discovery open after accepted defaults when risks + or dependent decisions remain, and chooses between direct plan and spec first + based on the marginal value of a spec. + patch_path: patches/superpowers-brainstorming.patch + expected_content_hash: 4e44155f52c800784b3fb19be4c1e6fb8298cb49c20f611ead09b39f4998e6c7 + baseline_repo_commit: local-plan-2026-07-05 + validation_note: Stop the refresh if the patch does not apply cleanly; review whether + an internal wrapper or upstream proposal should replace the override. diff --git a/.github/skills/superpowers-brainstorming/SKILL.md b/.github/skills/superpowers-brainstorming/SKILL.md index 16ebd28..47afc6e 100644 --- a/.github/skills/superpowers-brainstorming/SKILL.md +++ b/.github/skills/superpowers-brainstorming/SKILL.md @@ -7,7 +7,7 @@ description: "You MUST use this before any creative work - creating features, bu Help turn ideas into fully formed designs and specs through natural collaborative dialogue. -Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval. +Start by understanding the current project context, then Ask clarifying questions in numbered bulk question blocks. Each question must include a short recommendation, a short reason for that recommendation, and the default that will be treated as accepted when the user accepts the suggestions. Once you understand what you're building, decide whether a retained spec adds real design value or whether moving directly to an implementation plan is the better next step. Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity. @@ -23,34 +23,38 @@ You MUST create a task for each of these items and complete them in order: 1. **Explore project context** — check files, docs, recent commits 2. **Offer the visual companion just-in-time** — NOT upfront. The first time a question would genuinely be clearer shown than described, offer it then (its own message); on approval its browser tab opens for you. If no visual question ever arises, never offer it. See the Visual Companion section below. -3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria +3. **Ask clarifying questions** — use numbered bulk question blocks with `Question`, `Recommendation`, `Why`, and `Default if accepted` 4. **Propose 2-3 approaches** — with trade-offs and your recommendation 5. **Present design** — in sections scaled to their complexity, get user approval after each section -6. **Write design doc** — if the user wants a retained file, save it to `tmp/superpowers/specs/YYYY-MM-DD--design.md`; never commit files from `tmp/` -7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) -8. **User reviews written spec** — ask user to review the spec file before proceeding -9. **Transition to implementation** — invoke writing-plans skill to create implementation plan +6. **Run the Design-Depth Gate** — choose `Decision: direct plan` or `Decision: spec first`, and tell the user why +7. **Write design doc when needed** — if the gate chooses `spec first` and the user wants a retained file, save it to `tmp/superpowers/specs/YYYY-MM-DD--design.md`; never commit files from `tmp/` +8. **Spec self-review when needed** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) +9. **User reviews written spec when needed** — ask user to review the spec file before proceeding +10. **Transition to implementation planning** — invoke writing-plans skill after the user approves either the design or the direct-plan recommendation ## Process Flow ```dot digraph brainstorming { "Explore project context" [shape=box]; - "Ask clarifying questions" [shape=box]; + "Ask bulk clarifying questions" [shape=box]; "Propose 2-3 approaches" [shape=box]; "Present design sections" [shape=box]; "User approves design?" [shape=diamond]; + "Design-Depth Gate" [shape=diamond]; "Write design doc" [shape=box]; "Spec self-review\n(fix inline)" [shape=box]; "User reviews spec?" [shape=diamond]; "Invoke writing-plans skill" [shape=doublecircle]; - "Explore project context" -> "Ask clarifying questions"; - "Ask clarifying questions" -> "Propose 2-3 approaches"; + "Explore project context" -> "Ask bulk clarifying questions"; + "Ask bulk clarifying questions" -> "Propose 2-3 approaches"; "Propose 2-3 approaches" -> "Present design sections"; "Present design sections" -> "User approves design?"; "User approves design?" -> "Present design sections" [label="no, revise"]; - "User approves design?" -> "Write design doc" [label="yes"]; + "User approves design?" -> "Design-Depth Gate" [label="yes"]; + "Design-Depth Gate" -> "Invoke writing-plans skill" [label="direct plan"]; + "Design-Depth Gate" -> "Write design doc" [label="spec first"]; "Write design doc" -> "Spec self-review\n(fix inline)"; "Spec self-review\n(fix inline)" -> "User reviews spec?"; "User reviews spec?" -> "Write design doc" [label="changes requested"]; @@ -58,7 +62,7 @@ digraph brainstorming { } ``` -**The terminal state is invoking writing-plans.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skill you invoke after brainstorming is writing-plans. +**The terminal state is invoking writing-plans.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skill you invoke after brainstorming is writing-plans. The path to writing-plans may be either `Decision: direct plan` or `Decision: spec first`. ## The Process @@ -67,9 +71,13 @@ digraph brainstorming { - Check out the current project state first (files, docs, recent commits) - Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first. - If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec → plan → implementation cycle. -- For appropriately-scoped projects, ask questions one at a time to refine the idea +- For appropriately-scoped projects, ask clarifying questions in numbered bulk question blocks to refine the idea - Prefer multiple choice questions when possible, but open-ended is fine too -- Only one question per message - if a topic needs more exploration, break it into multiple questions +- Each numbered question must use this format: `Question`, `Recommendation`, `Why`, and `Default if accepted` +- The recommendation and why must be clear and brief +- The user may accept all suggested defaults, accept only some numbered defaults, or override any numbered recommendation +- Accepted defaults do not mean discovery is complete. If the accepted answers create contradictions, weak assumptions, unresolved risks, or dependent decisions, ask another focused numbered bulk question block. +- Ask another focused numbered bulk question block only for unresolved, dependent, or reopened branches. Do not ask questions that project evidence can answer. - Focus on understanding: purpose, constraints, success criteria **Exploring approaches:** @@ -86,6 +94,23 @@ digraph brainstorming { - Cover: architecture, components, data flow, error handling, testing - Be ready to go back and clarify if something doesn't make sense +**Design-Depth Gate:** + +Before writing a retained spec, decide whether the spec has meaningful marginal value over going directly to an implementation plan. + +Choose `Decision: direct plan` when the target, owner, scope, constraints, rejected alternatives, and validation path are already clear, and a retained spec would mostly duplicate the implementation plan. + +Choose `Decision: spec first` when product, design, architecture, data flow, user experience, rollout, or risk decisions are still material enough that a retained spec would reduce the chance of building the wrong thing. + +In both cases, tell the user the decision and one short reason: + +- `Decision: direct plan` +- `Why: ` +- `Decision: spec first` +- `Why: ` + +Ask for user approval before invoking writing-plans. Direct plan skips the retained spec, not the user approval gate. + **Design for isolation and clarity:** - Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently @@ -104,7 +129,7 @@ digraph brainstorming { **Documentation:** - Write the validated design (spec) to chat by default -- Persist it to `tmp/superpowers/specs/YYYY-MM-DD--design.md` only when the user explicitly wants a retained file +- Persist it to `tmp/superpowers/specs/YYYY-MM-DD--design.md` only when `Decision: spec first` is chosen and the user explicitly wants a retained file - (User preferences for spec location override this default) - Use elements-of-style:writing-clearly-and-concisely skill if available - Never commit files from `tmp/` @@ -128,12 +153,12 @@ Wait for the user's response. If they request changes, make them and re-run the **Implementation:** -- Invoke the writing-plans skill to create a detailed implementation plan +- Invoke the writing-plans skill to create a detailed implementation plan after the user approves either `Decision: direct plan` or the reviewed spec - Do NOT invoke any other skill. writing-plans is the next step. ## Key Principles -- **One question at a time** - Don't overwhelm with multiple questions +- **Bulk guided question blocks** - Ask the full known question set together, with recommendations, reasons, and defaults - **Multiple choice preferred** - Easier to answer than open-ended when possible - **YAGNI ruthlessly** - Remove unnecessary features from all designs - **Explore alternatives** - Always propose 2-3 approaches before settling From 4beb4c794a24dfb11e74c04f4cbd1441c9c4d30f Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Sun, 5 Jul 2026 18:54:00 +0200 Subject: [PATCH 28/44] refactor: add internal-gateway-idea skill and update inventory --- .github/INVENTORY.md | 1 + .github/skills/internal-gateway-idea/SKILL.md | 117 ++++++++++++++++++ .../internal-gateway-idea/agents/openai.yaml | 11 ++ 3 files changed, 129 insertions(+) create mode 100644 .github/skills/internal-gateway-idea/SKILL.md create mode 100644 .github/skills/internal-gateway-idea/agents/openai.yaml diff --git a/.github/INVENTORY.md b/.github/INVENTORY.md index 459679a..833ebe3 100644 --- a/.github/INVENTORY.md +++ b/.github/INVENTORY.md @@ -85,6 +85,7 @@ This file is the exact path inventory for the live GitHub Copilot catalog in thi - `.github/skills/internal-gateway-critical-master/SKILL.md` - `.github/skills/internal-gateway-execute-plans/SKILL.md` - `.github/skills/internal-gateway-idea-brainstorming/SKILL.md` +- `.github/skills/internal-gateway-idea/SKILL.md` - `.github/skills/internal-gateway-review/SKILL.md` - `.github/skills/internal-gateway-simple-task/SKILL.md` - `.github/skills/internal-gateway-writing-plans/SKILL.md` diff --git a/.github/skills/internal-gateway-idea/SKILL.md b/.github/skills/internal-gateway-idea/SKILL.md new file mode 100644 index 0000000..f58b789 --- /dev/null +++ b/.github/skills/internal-gateway-idea/SKILL.md @@ -0,0 +1,117 @@ +--- +name: internal-gateway-idea +description: Use when a repository-owned idea needs brainstorming, assumption challenge, alternative discovery, and a spec-vs-plan recommendation before implementation planning. +--- + +# Internal Gateway Idea + +## Referenced skills + +- `superpowers-brainstorming`: core idea-to-design workflow. +- `internal-gateway-writing-plans`: retained spec or implementation-plan writing after the user approves the direction. + +Lightweight repository-owned wrapper for idea shaping. Use `superpowers-brainstorming` as the core workflow and add the local gates below. This skill does not replace the core brainstorming process; it constrains it for repository-owned idea work. + +## When to use + +- A repository-owned request starts as an idea, option set, proposed direction, or unclear goal. +- The user asks to brainstorm, shape, challenge, or decide before implementation. +- The work may become a retained spec or implementation plan, but the right artifact is not yet confirmed. + +## When not to use + +- The request is already a concrete low-risk edit with a known owner and validation path. +- The primary task is defect-first review. +- The user has already approved a retained plan for execution. +- The request is only to maintain imported `superpowers-*` assets. + +## Core contract + +- Load `superpowers-brainstorming` as the core workflow. +- Keep the `superpowers-brainstorming` hard gate: no implementation action before the user approves the design or direct-plan recommendation. +- Use this skill only to add repository-owned idea gates, not to fork the core brainstorming process. +- Do not load or invoke `grill-me`; the core brainstorming workflow already owns the collaborative questioning behavior this wrapper needs. +- Load `internal-gateway-writing-plans` only after the user approves retained spec or implementation-plan writing. + +## Bounded context pass + +Before asking the first question block: + +- Identify the target, nearest owner, and likely validation path from the smallest useful repository evidence. +- For large files, generated output, logs, or tabular artifacts, inspect aggregate facts first: path, size, headers, counts, anomalies, and targeted slices. +- If platform semantics control feasibility or ownership, verify those semantics before converging. +- Separate original user intent from emerged requirements. Do not rewrite later constraints as the original request. + +## Assumption Challenge Gate + +Run this gate before finalizing the design direction. + +Test whether the user's proposed direction is actually necessary: + +- What problem is the proposal trying to solve? +- What would we do if the named target, path, skill, tool, or implementation idea did not exist? +- Which assumption would make the proposed direction wrong if false? +- Is there a smaller reversible move that preserves most of the value? +- Is there a non-obvious alternative that avoids the proposed change entirely? + +The output must include: + +1. `Original direction:` one sentence. +2. `Hidden assumption:` one sentence. +3. `Alternative path:` one sentence. +4. `Why not chosen:` one sentence, or `Chosen instead:` when the alternative is better. + +If the alternative path is better, reopen the relevant brainstorming branch instead of forcing the original proposal through refinement. + +## Alternative discovery + +Before presenting the final design, propose 2-3 approaches. Lead with the recommended approach. + +Each approach must include: + +- `Approach:` short name. +- `Best when:` one sentence. +- `Tradeoff:` one sentence. + +The recommendation must explain why the chosen approach beats the strongest rejected option. + +## Critical Challenge Gate + +Before moving to a retained spec or implementation plan: + +- Challenge the chosen direction with first principles, opportunity cost, or reverse-assumption reasoning. +- Name the strongest objection. +- Decide whether to continue, reopen a branch, or narrow scope. + +Use this visible shape: + +1. `Challenge:` the strongest objection. +2. `Resolution:` continue, reopen, or narrow. +3. `Reason:` one evidence-based sentence. + +Do not call this `grill-me` and do not load `grill-me`. + +## Spec vs plan decision + +After the user approves the design direction, decide whether to go straight to an implementation plan or write a retained spec first. + +Choose `Decision: direct plan` when target, owner, scope, constraints, rejected alternatives, and validation path are clear enough that a retained spec would mostly duplicate the implementation plan. + +Choose `Decision: spec first` when product, architecture, data flow, rollout, ownership, or risk decisions are still material enough that a retained spec would reduce the chance of building the wrong thing. + +Always explain the choice: + +- `Decision: direct plan` or `Decision: spec first` +- `Why:` one evidence-based sentence. +- `Rejected option:` the other path and why it is weaker. +- `Next owner:` `internal-gateway-writing-plans` after user approval. + +## Validation + +- The skill loaded `superpowers-brainstorming` as core instead of copying its workflow. +- The skill challenged the user's initial assumption, not only corrected the proposed solution. +- The skill presented 2-3 approaches and explained why the recommendation beat the strongest rejected option. +- The skill used `Critical Challenge Gate` before spec or plan writing. +- The skill did not reference, load, or invoke `grill-me`. +- The skill loaded `internal-gateway-writing-plans` only for retained spec or implementation-plan writing. +- The old `internal-gateway-idea-brainstorming` bundle remained untouched. diff --git a/.github/skills/internal-gateway-idea/agents/openai.yaml b/.github/skills/internal-gateway-idea/agents/openai.yaml new file mode 100644 index 0000000..5850612 --- /dev/null +++ b/.github/skills/internal-gateway-idea/agents/openai.yaml @@ -0,0 +1,11 @@ +interface: + display_name: "Internal Gateway Idea" + short_description: "Idea brainstorming with assumption challenge" + default_prompt: >- + Use $internal-gateway-idea when a repository-owned idea, proposed direction, + unclear goal, or option set needs brainstorming before planning. Load $superpowers-brainstorming as the core workflow. Add the local Assumption + Challenge Gate, Alternative discovery, Critical Challenge Gate, and visible + spec-vs-plan decision from SKILL.md. Do not load grill-me. Load $internal-gateway-writing-plans only after the user approves retained spec + or implementation-plan writing. +name: internal-gateway-idea +description: Use when a repository-owned idea needs brainstorming, assumption challenge, alternative discovery, and a spec-vs-plan recommendation before implementation planning. From b3a49365e904f939f1b547a86ef6c6355dabf865 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 11:35:42 +0200 Subject: [PATCH 29/44] refactor: clarify usage guidelines for internal-gateway-idea skill and streamline collaborative questioning --- .github/skills/internal-gateway-idea/SKILL.md | 6 +++--- .github/skills/internal-gateway-idea/agents/openai.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/skills/internal-gateway-idea/SKILL.md b/.github/skills/internal-gateway-idea/SKILL.md index f58b789..5bfac68 100644 --- a/.github/skills/internal-gateway-idea/SKILL.md +++ b/.github/skills/internal-gateway-idea/SKILL.md @@ -30,7 +30,7 @@ Lightweight repository-owned wrapper for idea shaping. Use `superpowers-brainsto - Load `superpowers-brainstorming` as the core workflow. - Keep the `superpowers-brainstorming` hard gate: no implementation action before the user approves the design or direct-plan recommendation. - Use this skill only to add repository-owned idea gates, not to fork the core brainstorming process. -- Do not load or invoke `grill-me`; the core brainstorming workflow already owns the collaborative questioning behavior this wrapper needs. +- Keep collaborative questioning inside the core brainstorming workflow. - Load `internal-gateway-writing-plans` only after the user approves retained spec or implementation-plan writing. ## Bounded context pass @@ -89,7 +89,7 @@ Use this visible shape: 2. `Resolution:` continue, reopen, or narrow. 3. `Reason:` one evidence-based sentence. -Do not call this `grill-me` and do not load `grill-me`. +Keep this gate inside the local idea wrapper and the core brainstorming workflow. ## Spec vs plan decision @@ -112,6 +112,6 @@ Always explain the choice: - The skill challenged the user's initial assumption, not only corrected the proposed solution. - The skill presented 2-3 approaches and explained why the recommendation beat the strongest rejected option. - The skill used `Critical Challenge Gate` before spec or plan writing. -- The skill did not reference, load, or invoke `grill-me`. +- The skill kept collaborative questioning inside the core brainstorming workflow. - The skill loaded `internal-gateway-writing-plans` only for retained spec or implementation-plan writing. - The old `internal-gateway-idea-brainstorming` bundle remained untouched. diff --git a/.github/skills/internal-gateway-idea/agents/openai.yaml b/.github/skills/internal-gateway-idea/agents/openai.yaml index 5850612..2638fb9 100644 --- a/.github/skills/internal-gateway-idea/agents/openai.yaml +++ b/.github/skills/internal-gateway-idea/agents/openai.yaml @@ -5,7 +5,7 @@ interface: Use $internal-gateway-idea when a repository-owned idea, proposed direction, unclear goal, or option set needs brainstorming before planning. Load $superpowers-brainstorming as the core workflow. Add the local Assumption Challenge Gate, Alternative discovery, Critical Challenge Gate, and visible - spec-vs-plan decision from SKILL.md. Do not load grill-me. Load $internal-gateway-writing-plans only after the user approves retained spec + spec-vs-plan decision from SKILL.md. Load $internal-gateway-writing-plans only after the user approves retained spec or implementation-plan writing. name: internal-gateway-idea description: Use when a repository-owned idea needs brainstorming, assumption challenge, alternative discovery, and a spec-vs-plan recommendation before implementation planning. From ce2e6c2e3ea6f8a576e1f288027cfea88724ba39 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 12:50:25 +0200 Subject: [PATCH 30/44] refactor: update internal-gateway-review skill descriptions for clarity and precision --- .github/agents/README.md | 2 +- .../agents/internal-gateway-review.agent.md | 2 +- .github/scripts/benchmark_skill_tokens.py | 4 +- .github/skills/internal-code-review/SKILL.md | 2 +- .../skills/internal-gateway-review/SKILL.md | 161 ++++++++++++------ .../agents/openai.yaml | 4 +- .../references/review-gate.md | 47 ----- 7 files changed, 115 insertions(+), 107 deletions(-) delete mode 100644 .github/skills/internal-gateway-review/references/review-gate.md diff --git a/.github/agents/README.md b/.github/agents/README.md index 2f84f0d..4d4ae69 100644 --- a/.github/agents/README.md +++ b/.github/agents/README.md @@ -18,7 +18,7 @@ repo-only sync workflows. | Wrapper | Core skill | Use when | | --- | --- | --- | | `internal-gateway-idea-brainstorming` | `internal-gateway-idea-brainstorming` | A vague idea or unresolved goal needs definition and retained planning. | -| `internal-gateway-review` | `internal-gateway-review` | A concrete artifact needs defect-first review and maybe remediation planning. | +| `internal-gateway-review` | `internal-gateway-review` | A concrete artifact needs defect-first review before fixes. | | `internal-gateway-critical-master` | `internal-gateway-critical-master` | A proposal or plan needs pressure before action. | | `internal-gateway-simple-task` | `internal-gateway-simple-task` | A concrete low-to-medium-risk task can finish through one focused lane. | diff --git a/.github/agents/internal-gateway-review.agent.md b/.github/agents/internal-gateway-review.agent.md index 91f94ad..4aa57b9 100644 --- a/.github/agents/internal-gateway-review.agent.md +++ b/.github/agents/internal-gateway-review.agent.md @@ -1,6 +1,6 @@ --- name: internal-gateway-review -description: "Use this agent when repository-owned work needs defect-first review, findings consolidation, and optional remediation planning without applying fixes." +description: "Use this agent when repository-owned work needs defect-first review, findings consolidation, and a counter-checked verdict before fixes." tools: ["read", "search", "execute", "web"] disable-model-invocation: true agents: [] diff --git a/.github/scripts/benchmark_skill_tokens.py b/.github/scripts/benchmark_skill_tokens.py index 5ce2c0e..e2f7505 100644 --- a/.github/scripts/benchmark_skill_tokens.py +++ b/.github/scripts/benchmark_skill_tokens.py @@ -108,6 +108,7 @@ def build_scenario_report(root: Path) -> list[dict[str, Any]]: GATEWAY_SKILL = "internal-gateway-idea-brainstorming" +REVIEW_GATEWAY_SKILL = "internal-gateway-review" GATEWAY_REQUIRED_CONTEXT_SCENARIOS: dict[str, list[str]] = { "Direct execute": [GATEWAY_SKILL], @@ -119,8 +120,7 @@ def build_scenario_report(root: Path) -> list[dict[str, Any]]: "Plan handoff": [GATEWAY_SKILL, "internal-gateway-writing-plans", "internal-agent-support-next-step"], "Approved apply-plan": [GATEWAY_SKILL, "internal-gateway-execute-plans"], "Review verdict": [ - GATEWAY_SKILL, "internal-code-review", "internal-high-level-review", - "internal-gateway-critical-master", + REVIEW_GATEWAY_SKILL, "internal-gateway-critical-master", ], } diff --git a/.github/skills/internal-code-review/SKILL.md b/.github/skills/internal-code-review/SKILL.md index b4feb79..d870c60 100644 --- a/.github/skills/internal-code-review/SKILL.md +++ b/.github/skills/internal-code-review/SKILL.md @@ -1,6 +1,6 @@ --- name: internal-code-review -description: Use when a code review is requested, a PR needs reviewing, or the user wants defect-first checks or safe simplification guidance across functionality, security, performance, maintainability, and tests on Python, Bash, Terraform, Java, or Node.js/TypeScript. +description: Use when review evidence needs line-level or language-specific defect checks for Python, Bash, Terraform, Java, or Node.js/TypeScript code, tests, or scripts. --- # Code Review Skill diff --git a/.github/skills/internal-gateway-review/SKILL.md b/.github/skills/internal-gateway-review/SKILL.md index b34d973..638ee17 100644 --- a/.github/skills/internal-gateway-review/SKILL.md +++ b/.github/skills/internal-gateway-review/SKILL.md @@ -1,58 +1,97 @@ --- name: internal-gateway-review -description: Use when repository-owned work needs same-conversation defect-first review, critical challenge of material remediation choices, and optional retained remediation planning before execution. +description: Use when repository-owned work needs defect-first review of a concrete artifact, diff, workflow, bundle, or review package before fixes. --- # Internal Gateway Review ## Referenced skills -- `internal-code-review` -- `internal-high-level-review` -- `internal-ai-resource-review` -- `internal-copilot-audit` -- `internal-gateway-critical-master` -- `internal-gateway-writing-plans` -- `internal-agent-support-next-step` +- `internal-gateway-critical-master`: final autonomous counter-check when findings, no-finding verdicts, or next-action choices need pressure before action. -Treat this section as an audit and routing index, not a preload bundle. Load a -referenced skill only when the domain, finding, blocker, or phase requires it. +Portable review gateway. Owns review scope, evidence discipline, findings +consolidation, decision-usefulness, and final counter-check. It does not apply +fixes, author retained plans, or preload specialist review skills. -Portable review orchestrator. Owns review scope, lens selection, findings -consolidation, critical support, and remediation-plan transition. It does not apply fixes. +Use the target itself to choose review depth. Keep review thinking local unless +the user directly selected a specialist owner or a separate prompt already +selected one. -Before any user-visible verdict, run a lightweight internal check for evidence, +Before any user-visible verdict, counter-check the analysis for evidence, severity, false positives, contrary evidence, scope narrowing, and decision -usefulness. Load `internal-gateway-critical-master` only for a material -challenge. Revise or reopen when the check exposes a material gap or when the -visible review would not support a clear next decision. - -See `references/review-gate.md` for the review output contract and gate states. - -## Lens selection - -Select the review lens from the changed-path families, not from a single default. -A diff may activate more than one lens; load each only when its evidence appears. - -- Code lens (`internal-code-review`): Python, Bash, Terraform, Java, or - Node.js/TypeScript source changes. -- Systems lens (`internal-high-level-review`): architecture, workflow, or - cross-cutting impact beyond line-level defects. -- AI-resource lens (`internal-ai-resource-review`, with `internal-copilot-audit` - as the drift sub-lens): repository-owned AI customization assets, including - `.github/skills/**`, `.github/agents/*.agent.md`, `.github/prompts/*.prompt.md`, - `.github/instructions/**`, `AGENTS.md`, `.github/copilot-instructions.md`, - `.github/INVENTORY.md`, and `**/agents/openai.yaml`. - -When the diff is mainly AI customization assets, the AI-resource lens is -mandatory and the code lens stays scoped to any embedded scripts only. Do not -let the code lens silently skip `.md` skill, agent, or instruction files. +usefulness. Use `internal-gateway-critical-master` at the end when the review +depends on a material judgment, a no-finding claim, or a proposed next action. +Revise or reopen when the counter-check exposes a material gap. + +## Workflow + +```mermaid +flowchart TD + A[Review request] --> B[Resolve concrete target] + B --> C[Read smallest useful evidence] + C --> D{Clarifying question changes the review?} + D -->|Yes| E[Ask focused question block] + E --> C + D -->|No| F[Select review surfaces] + F --> G[Inspect target or diff first] + G --> H[Find material defects] + H --> I[Counter-validate each finding] + I --> J{Finding survives?} + J -->|No| H + J -->|Yes| K[Prepare defect-first verdict] + K --> L[Run critical counter-check] + L --> M{Critical check passes?} + M -->|No| N[Reopen evidence, scope, or questions] + N --> C + M -->|Yes| O[Emit review decision] + O --> P[Stop before fixes] +``` + +## Review Surface Selection + +Select review surfaces from the changed paths, stated goal, and evidence gaps; +do not start from a single default lens. A diff may activate more than one +surface. This gateway names surfaces, not specialist skill dependencies. + +- Code surface: changed source, scripts, tests, build files, dependency files, + generated-code boundaries, and validation paths. +- System surface: architecture, workflow, ownership, rollout, cross-boundary + impact, operational fit, and merge risk. +- AI-resource surface: repository-owned prompts, agents, skills, instructions, + bundle siblings, catalog files, sync behavior, and customization drift. + +When the diff is mainly AI customization assets, review the AI-resource surface +first and treat embedded scripts as a secondary code surface. Do not let a +language-oriented scan silently skip `.md` skill, agent, prompt, instruction, or +inventory files. + +## Review Procedure + +1. Resolve the concrete target: artifact, diff, PR, workflow, bundle, retained + report, or explicit file list. +2. Identify intent, anti-scope, changed surfaces, validation already run, and + evidence gaps from the smallest useful local evidence. +3. Ask a focused question before analysis only when the answer would change + scope, severity, owner, or the review decision. +4. Inspect the diff or target first, then read immediate owning context only + when a finding or uncertainty needs it. +5. Check design fit, functionality, security, complexity, tests, naming, + comments, documentation, consistency, and changed-user impact when relevant. +6. For each potential finding, test the contrary explanation before reporting + it: intended behavior, local convention, compatibility, generated output, + explicit user scope, or validator coverage. +7. Report at most 5 material findings unless exhaustive review is requested. +8. For low-finding or no-finding reviews, include evidence coverage, residual + risk, and the next decision the reader can make. +9. Run the final `internal-gateway-critical-master` counter-check before the + verdict and reopen the analysis if it changes severity, confidence, scope, + or next action. ## Token Discipline Inspect diff and failing evidence first; avoid broad repository scans unless an -evidence gap requires one; never preload referenced skills; show at most 5 -material findings unless exhaustive review is requested; summarize omitted +evidence gap requires one. Prefer aggregate facts for large diffs, generated +files, tabular exports, and logs before reading raw volume. Summarize omitted low-risk observations separately, not as findings. Use `Compact Evidence Reporting` for large diffs, generated files, tabular @@ -60,33 +99,49 @@ exports, and logs: keep findings defect-first, cite the smallest excerpt or file point that proves impact, and avoid dumping large raw blocks when a targeted excerpt plus evidence path preserves the same proof. -## Review To Plan Transition +## Output Contract -Before creating, accepting, or routing a remediation plan, keep the review -defect-first and map every original material finding: `id`, `status` (`planned`, -`deferred`, `rejected`, or `residual`), `reason`, `next owner`, and `validation -expected`. +Findings must lead, ordered by severity. Each material finding carries: -If remediation steps cover less than 100% of material findings, label the -output `partial remediation plan` and keep residual, deferred, or rejected -findings visible. A retained mini-plan is a coverage-preserving handoff authored -by `internal-gateway-writing-plans`; its job is plan creation, not fixes. This -gateway does not choose the execution owner. +- finding and impact; +- severity and confidence; +- smallest evidence point; +- evidence gap, if any; +- counter-validation result; +- next owner or route; +- validation expected. + +End with one Review Gate outcome: + +- `review gate: satisfied` when findings or no-finding claims are specific, + counter-validated, decision-useful, and ready for the user-visible verdict. +- `review gate: reopen` when material evidence is missing, contrary evidence + weakens a finding, severity is uncertain, scope is too broad, or the visible + verdict would not support a clear next decision. + +The final verdict must support one clear next decision: accept, patch, +investigate, plan separately, or accept with a named residual risk. ## When to use - The user asks for review of a concrete artifact, diff, workflow, or bundle. -- The primary job is defect-first findings and remediation planning, not fixes. +- The primary job is defect-first findings, not fixes. + +## When not to use + +- The user has already approved implementation or remediation work. +- The request is mainly planning, brainstorming, execution, or file editing. +- The target is a specialist review where the user directly selected another + owner. ## Validation - Findings stay defect-first. -- Lens selection matches the changed-path families; AI customization assets route to `internal-ai-resource-review` (drift via `internal-copilot-audit`) instead of being skipped by the code lens. +- Review surface selection matches the changed-path families. - Review flow preserves compact context: prioritize diff and failing evidence first, then expand only when an evidence gap remains. - Large evidence may be reported compactly, but each material finding still keeps severity, confidence, evidence gap, counter-validation result, and route or next owner. - Review output carries findings, severity, confidence, evidence gap, counter-validation result, route or next owner, decision-usefulness result, and a Review Gate outcome before the final verdict. - Low-finding and no-finding reviews include enough evidence digest, decision trace, next action, and residual-risk context for the reader to decide whether to accept, patch, investigate, plan, or accept with named risk. - The review cannot present analysis to the user until counter-validation confirms it or reopens material gaps. -- Remediation-plan transitions preserve a 100% material-finding coverage map or explicitly declare a `partial remediation plan`. -- Retained remediation plans are authored by `internal-gateway-writing-plans` and preserve the coverage map. +- The only referenced skill is `internal-gateway-critical-master`, and it is used for the final autonomous counter-check rather than as a preload. - The gateway stops before fixes. diff --git a/.github/skills/internal-gateway-review/agents/openai.yaml b/.github/skills/internal-gateway-review/agents/openai.yaml index 82ce3f4..0e8bef4 100644 --- a/.github/skills/internal-gateway-review/agents/openai.yaml +++ b/.github/skills/internal-gateway-review/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: Internal Gateway Review - short_description: Defect-first review and remediation planning. - default_prompt: "Use $internal-gateway-review to inspect the concrete review target, consolidate findings, and stop before applying fixes." + short_description: Defect-first review before fixes. + default_prompt: "Use $internal-gateway-review to inspect the concrete review target, consolidate findings, counter-check the verdict, and stop before applying fixes." diff --git a/.github/skills/internal-gateway-review/references/review-gate.md b/.github/skills/internal-gateway-review/references/review-gate.md deleted file mode 100644 index e8aaecb..0000000 --- a/.github/skills/internal-gateway-review/references/review-gate.md +++ /dev/null @@ -1,47 +0,0 @@ -# Review Gate - -Use this reference when `internal-gateway-review` needs to package findings before the final verdict. - -## Required Fields - -- Findings -- Severity -- Confidence -- Evidence gap -- Counter-validation -- Route or next owner -- Decision-usefulness check -- Review Gate outcome - -## Decision-Usefulness Check - -Before the final user-visible verdict, confirm that the review supports a clear -decision: accept, patch, investigate, plan, or accept with a named residual -risk. - -The reader should understand: - -- the verdict and confidence; -- the smallest evidence set that supports it; -- what was ruled out or not supported by evidence; -- why any finding, keep result, or no-finding result matters; -- the best next action; -- the most important residual risk. - -If any material part is missing, use `review gate: reopen` until the missing -decision context is added. Strong findings may carry most of this context. No -findings and low-severity-only reviews need explicit coverage, decision trace, -and residual-risk context. - -## Gate States - -- `review gate: satisfied` when the findings are specific, routed, counter-validated, and ready for the user-visible verdict. -- `review gate: reopen` when material evidence is missing, counter-validation exposes a material flaw, the remediation choice needs more challenge, or the visible review is formally correct but too thin to support a clear user decision. - -## Boundary - -- Keep the gate visible before any fixes. -- Run counter-validation before the final user-visible verdict; challenge each finding for evidence, severity, route, and contrary proof. -- For large diffs, generated files, logs, or tabular exports, keep evidence compact: cite the smallest excerpt or path that proves the finding and summarize omitted raw volume. -- Report only material self-critique results: corrections, confidence changes, evidence gaps, or confirmation that no material issue was found. -- Use the gate to route each actionable finding to the smallest next owner. From 6c03e2f1994ea7329b484f0fc1101a2d5b6defca Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 13:09:43 +0200 Subject: [PATCH 31/44] refactor: enhance internal-gateway-idea skill with workflow references and audit script --- .github/skills/internal-gateway-idea/SKILL.md | 39 ++++++++++ .../internal-gateway-idea/agents/openai.yaml | 15 +++- .../references/workflow.md | 66 +++++++++++++++++ .../scripts/audit_workflow.py | 72 +++++++++++++++++++ 4 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 .github/skills/internal-gateway-idea/references/workflow.md create mode 100644 .github/skills/internal-gateway-idea/scripts/audit_workflow.py diff --git a/.github/skills/internal-gateway-idea/SKILL.md b/.github/skills/internal-gateway-idea/SKILL.md index 5bfac68..4181031 100644 --- a/.github/skills/internal-gateway-idea/SKILL.md +++ b/.github/skills/internal-gateway-idea/SKILL.md @@ -10,6 +10,11 @@ description: Use when a repository-owned idea needs brainstorming, assumption ch - `superpowers-brainstorming`: core idea-to-design workflow. - `internal-gateway-writing-plans`: retained spec or implementation-plan writing after the user approves the direction. +## Local references + +- `references/workflow.md`: authoritative state machine, Mermaid workflow, + approval rules, and coexistence boundaries for this bundle. + Lightweight repository-owned wrapper for idea shaping. Use `superpowers-brainstorming` as the core workflow and add the local gates below. This skill does not replace the core brainstorming process; it constrains it for repository-owned idea work. ## When to use @@ -28,10 +33,15 @@ Lightweight repository-owned wrapper for idea shaping. Use `superpowers-brainsto ## Core contract - Load `superpowers-brainstorming` as the core workflow. +- Read `references/workflow.md` before presenting the final design direction. - Keep the `superpowers-brainstorming` hard gate: no implementation action before the user approves the design or direct-plan recommendation. +- Treat approval as gate-local. `procedi`, `ok`, `go`, or similar approval advances only the active visible gate. +- If approval wording is ambiguous, ask whether it means critical review, retained spec or plan writing, or implementation execution. - Use this skill only to add repository-owned idea gates, not to fork the core brainstorming process. - Keep collaborative questioning inside the core brainstorming workflow. - Load `internal-gateway-writing-plans` only after the user approves retained spec or implementation-plan writing. +- Stop after the delegated writing outcome. Do not implement, invoke execution owners, or run execution commands from this skill. +- Keep `internal-gateway-idea-brainstorming` untouched during coexistence. ## Bounded context pass @@ -42,6 +52,26 @@ Before asking the first question block: - If platform semantics control feasibility or ownership, verify those semantics before converging. - Separate original user intent from emerged requirements. Do not rewrite later constraints as the original request. +## State machine + +Follow `references/workflow.md` in this order: + +1. `Bounded evidence pass` +2. `Specialization Checkpoint: gated` when the incoming ask is execution-shaped. +3. `Idea Gate 0` +4. `Assumption Challenge Gate` +5. `Alternative discovery` +6. `Present design direction` +7. `Critical Challenge Gate` +8. `Spec vs plan decision` +9. `Approved writing handoff` +10. `Stop before implementation execution` + +Do not skip from evidence, design approval, or `Decision: direct plan` to +implementation. The only post-brainstorming owner this skill may load is +`internal-gateway-writing-plans`, and only after explicit approval for the +selected writing path. + ## Assumption Challenge Gate Run this gate before finalizing the design direction. @@ -105,13 +135,22 @@ Always explain the choice: - `Why:` one evidence-based sentence. - `Rejected option:` the other path and why it is weaker. - `Next owner:` `internal-gateway-writing-plans` after user approval. +- `Approval request:` ask the user to approve the selected writing path before loading the next owner. + +Approval of `Decision: direct plan` skips a retained spec only. It does not +authorize implementation execution. ## Validation +- The skill read `references/workflow.md` before finalizing the design direction. +- The skill used `Specialization Checkpoint: gated` for execution-shaped requests. - The skill loaded `superpowers-brainstorming` as core instead of copying its workflow. - The skill challenged the user's initial assumption, not only corrected the proposed solution. - The skill presented 2-3 approaches and explained why the recommendation beat the strongest rejected option. - The skill used `Critical Challenge Gate` before spec or plan writing. +- The skill treated ambiguous approval words as gate-local and clarified the active gate when needed. - The skill kept collaborative questioning inside the core brainstorming workflow. - The skill loaded `internal-gateway-writing-plans` only for retained spec or implementation-plan writing. +- The skill stopped after `internal-gateway-writing-plans` produced a writing outcome. +- The Mermaid workflow and runtime prompt contain the same mandatory gate names. - The old `internal-gateway-idea-brainstorming` bundle remained untouched. diff --git a/.github/skills/internal-gateway-idea/agents/openai.yaml b/.github/skills/internal-gateway-idea/agents/openai.yaml index 2638fb9..6fb9afd 100644 --- a/.github/skills/internal-gateway-idea/agents/openai.yaml +++ b/.github/skills/internal-gateway-idea/agents/openai.yaml @@ -3,9 +3,18 @@ interface: short_description: "Idea brainstorming with assumption challenge" default_prompt: >- Use $internal-gateway-idea when a repository-owned idea, proposed direction, - unclear goal, or option set needs brainstorming before planning. Load $superpowers-brainstorming as the core workflow. Add the local Assumption + unclear goal, or option set needs brainstorming before planning. Load + $superpowers-brainstorming as the core workflow, then read + references/workflow.md for the local state machine. If the incoming request + is execution-shaped, emit Specialization Checkpoint: gated and continue + through Idea Gate 0 before any recommendation. Add the local Assumption Challenge Gate, Alternative discovery, Critical Challenge Gate, and visible - spec-vs-plan decision from SKILL.md. Load $internal-gateway-writing-plans only after the user approves retained spec - or implementation-plan writing. + spec-vs-plan decision from SKILL.md. Treat procedi, ok, go, or similar + approval as advancing only the active visible gate; clarify ambiguous + approval before loading another owner. Load $internal-gateway-writing-plans + only after the user explicitly approves retained spec or implementation-plan + writing. Stop after the writing outcome; do not implement or invoke execution + owners from this skill. Do not retire or rewrite + internal-gateway-idea-brainstorming during coexistence. name: internal-gateway-idea description: Use when a repository-owned idea needs brainstorming, assumption challenge, alternative discovery, and a spec-vs-plan recommendation before implementation planning. diff --git a/.github/skills/internal-gateway-idea/references/workflow.md b/.github/skills/internal-gateway-idea/references/workflow.md new file mode 100644 index 0000000..e80df0b --- /dev/null +++ b/.github/skills/internal-gateway-idea/references/workflow.md @@ -0,0 +1,66 @@ +# Internal Gateway Idea Workflow + +This workflow applies only to `internal-gateway-idea` while it coexists with +`internal-gateway-idea-brainstorming`. It strengthens the future replacement +bundle without changing the current canonical gateway catalog. + +## State Machine + +```mermaid +flowchart TD + A[Request enters internal-gateway-idea] --> B[Bounded evidence pass] + B --> C{Concrete execution request?} + C -- yes --> C1[Specialization Checkpoint: gated] + C -- no --> D[Idea Gate 0] + C1 --> D + D --> E{Intent and defaults accepted?} + E -- no --> D + E -- yes --> F[Assumption Challenge Gate] + F --> G[Alternative discovery] + G --> H[Present design direction] + H --> I{User approves design direction?} + I -- no --> D + I -- yes --> J[Critical Challenge Gate] + J --> K{Critical result} + K -- reopen --> D + K -- narrow --> H + K -- continue --> L[Spec vs plan decision] + L --> M{Decision} + M -- spec first --> N[Ask approval for retained spec path] + M -- direct plan --> O[Ask approval for direct plan path] + N --> P{Approved?} + O --> P + P -- no --> L + P -- yes --> Q[Load internal-gateway-writing-plans] + Q --> R[Writing outcome only] + R --> S[Stop before implementation execution] +``` + +## Gate Contract + +| State | Required behavior | Forbidden behavior | +| --- | --- | --- | +| `Specialization Checkpoint: gated` | Use when the incoming ask is already a file edit, command run, validator run, implementation step, or other execution-shaped request. Name the later execution owner only as a future consequence. | Do not execute, hand off, or present the post-critical recommendation. | +| `Idea Gate 0` | Confirm the recovered intent, defaults, constraints, success criteria, and validation path. Use the core `superpowers-brainstorming` questioning style. | Do not treat repository evidence alone as user approval. | +| `Assumption Challenge Gate` | Test whether the proposed target or solution is necessary before choosing an approach. | Do not only polish the user's proposed solution. | +| `Alternative discovery` | Present 2-3 approaches and explain why the recommended one beats the strongest rejected option. | Do not present a single-path design as inevitable. | +| `Critical Challenge Gate` | Challenge the chosen direction before spec or plan writing. Reopen or narrow when the objection is material. | Do not use this gate after loading `internal-gateway-writing-plans`. | +| `Spec vs plan decision` | Choose `Decision: direct plan` or `Decision: spec first`, explain why, name the rejected path, and ask for approval. | Do not load `internal-gateway-writing-plans` from the decision alone. | +| `Writing outcome only` | Load `internal-gateway-writing-plans` only after explicit user approval for the selected writing path. Stop after the delegated writing outcome. | Do not implement, edit target files, run execution commands, or invoke execution owners. | + +## Approval Rules + +- `procedi`, `ok`, `go`, or similar approval advances only the active visible gate. +- If the active gate is ambiguous, ask whether the user means critical review, + retained spec or plan writing, or implementation execution. +- Approval of a design direction is not approval to implement. +- Approval of `Decision: direct plan` skips a retained spec, not the user + approval gate and not the stop-before-execution boundary. + +## Coexistence Rule + +This bundle is the future replacement direction, but this workflow does not +retire, rename, or rewrite `internal-gateway-idea-brainstorming`. Global +canonical routing stays unchanged until a separate cleanup request updates the +old bundle, wrapper agents, inventory, sync surfaces, and repository contracts +together. diff --git a/.github/skills/internal-gateway-idea/scripts/audit_workflow.py b/.github/skills/internal-gateway-idea/scripts/audit_workflow.py new file mode 100644 index 0000000..20e1d29 --- /dev/null +++ b/.github/skills/internal-gateway-idea/scripts/audit_workflow.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Audit the internal-gateway-idea workflow gate contract.""" + +from __future__ import annotations + +import json +from pathlib import Path + + +def contains_all(text: str, markers: list[str]) -> bool: + return all(marker in text for marker in markers) + + +def main() -> int: + bundle_dir = Path(__file__).resolve().parent.parent + skill_text = (bundle_dir / "SKILL.md").read_text(encoding="utf-8") + workflow_text = (bundle_dir / "references" / "workflow.md").read_text(encoding="utf-8") + runtime_text = (bundle_dir / "agents" / "openai.yaml").read_text(encoding="utf-8") + + shared_markers = [ + "Specialization Checkpoint: gated", + "Idea Gate 0", + "Assumption Challenge Gate", + "Alternative discovery", + "Critical Challenge Gate", + "Spec vs plan decision", + "internal-gateway-writing-plans", + "Stop before implementation execution", + ] + workflow_only_markers = [ + "flowchart TD", + "Approval Rules", + "Coexistence Rule", + ] + runtime_only_markers = [ + "$internal-gateway-idea", + "$superpowers-brainstorming", + "$internal-gateway-writing-plans", + "do not implement", + ] + + markers = { + "skill_shared_markers": contains_all(skill_text, shared_markers), + "workflow_shared_markers": contains_all(workflow_text, shared_markers), + "runtime_core_markers": contains_all(runtime_text, runtime_only_markers) + and contains_all( + runtime_text, + [ + "Specialization Checkpoint: gated", + "Idea Gate 0", + "Critical Challenge Gate", + "spec-vs-plan decision", + ], + ), + "workflow_mermaid_and_rules": contains_all(workflow_text, workflow_only_markers), + "coexistence_boundary": "internal-gateway-idea-brainstorming" in skill_text + and "internal-gateway-idea-brainstorming" in workflow_text + and "internal-gateway-idea-brainstorming" in runtime_text, + "approval_is_gate_local": "active visible gate" in skill_text + and "active visible gate" in workflow_text + and "active visible gate" in runtime_text, + "stop_before_execution": "Stop before implementation execution" in workflow_text + and "Stop after the delegated writing outcome" in skill_text + and "Stop after the writing outcome" in runtime_text, + } + payload = {"strict_ok": all(markers.values()), "markers": markers} + print(json.dumps(payload, indent=2, sort_keys=True)) + return 0 if payload["strict_ok"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) From 247f98a9fca227b87c336215cac225fd0fb2e1ee Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 13:17:49 +0200 Subject: [PATCH 32/44] refactor: enhance internal-gateway-review skill with additional routing hints and workflow clarifications --- .../skills/internal-gateway-review/SKILL.md | 77 +++++++++++++------ 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/.github/skills/internal-gateway-review/SKILL.md b/.github/skills/internal-gateway-review/SKILL.md index 638ee17..868aa03 100644 --- a/.github/skills/internal-gateway-review/SKILL.md +++ b/.github/skills/internal-gateway-review/SKILL.md @@ -8,6 +8,9 @@ description: Use when repository-owned work needs defect-first review of a concr ## Referenced skills - `internal-gateway-critical-master`: final autonomous counter-check when findings, no-finding verdicts, or next-action choices need pressure before action. +- `internal-code-review`: route hint only when code-surface evidence needs deeper line-level review. +- `internal-high-level-review`: route hint only when system-surface evidence needs broader architecture, workflow, or merge-risk review. +- `internal-ai-resource-review`: route hint only when AI-resource evidence needs deeper bundle, lifecycle, or catalog review. Portable review gateway. Owns review scope, evidence discipline, findings consolidation, decision-usefulness, and final counter-check. It does not apply @@ -17,6 +20,10 @@ Use the target itself to choose review depth. Keep review thinking local unless the user directly selected a specialist owner or a separate prompt already selected one. +The specialist entries above are route hints, not preload instructions. Name the +surface and likely next owner when deeper review is needed, but do not load a +specialist just because its surface appears. + Before any user-visible verdict, counter-check the analysis for evidence, severity, false positives, contrary evidence, scope narrowing, and decision usefulness. Use `internal-gateway-critical-master` at the end when the review @@ -32,19 +39,25 @@ flowchart TD C --> D{Clarifying question changes the review?} D -->|Yes| E[Ask focused question block] E --> C - D -->|No| F[Select review surfaces] - F --> G[Inspect target or diff first] - G --> H[Find material defects] - H --> I[Counter-validate each finding] - I --> J{Finding survives?} - J -->|No| H - J -->|Yes| K[Prepare defect-first verdict] - K --> L[Run critical counter-check] - L --> M{Critical check passes?} - M -->|No| N[Reopen evidence, scope, or questions] - N --> C - M -->|Yes| O[Emit review decision] - O --> P[Stop before fixes] + D -->|No| F[Classify review surfaces] + F --> G{Primary surface} + G -->|Code| H[Review code surface locally] + G -->|System| I[Review system surface locally] + G -->|AI resource| J[Review AI-resource surface locally] + G -->|Mixed| K[Review primary surface first, then secondary gaps] + H --> L[Name route hint only if deeper review is needed] + I --> L + J --> L + K --> L + L --> M[Counter-validate findings] + M --> N{Finding survives or no-finding claim is supported?} + N -->|No| O[Reopen evidence, scope, or questions] + O --> C + N -->|Yes| P[Run critical counter-check] + P --> Q{Critical check passes?} + Q -->|No| O + Q -->|Yes| R[Emit review gate and next decision] + R --> S[Stop before fixes] ``` ## Review Surface Selection @@ -65,6 +78,21 @@ first and treat embedded scripts as a secondary code surface. Do not let a language-oriented scan silently skip `.md` skill, agent, prompt, instruction, or inventory files. +## Surface Routing Hints + +Use these labels to classify evidence and name a likely next owner when deeper +review is needed. Route hints are not preload instructions. + +| Surface | Evidence signals | Local review focus | Route hint | +| --- | --- | --- | --- | +| Code surface | Source, scripts, tests, build files, dependencies, generated-code boundaries | Correctness, security, tests, runtime behavior, and validation fit | `internal-code-review` for deeper line-level review | +| System surface | Architecture, workflow, ownership, rollout, cross-boundary impact, merge risk | Operational fit, coupling, scope drift, blind spots, and decision risk | `internal-high-level-review` for broader systems review | +| AI-resource surface | Skills, agents, prompts, instructions, inventory, sync behavior, bundle siblings | Retrieval, bundle self-containment, lifecycle, propagation, and customization drift | `internal-ai-resource-review` for deeper bundle or catalog review | + +For mixed reviews, pick the dominant surface first, inspect only secondary +surfaces that affect material findings or residual risk, and consolidate the +verdict in this gateway. + ## Review Procedure 1. Resolve the concrete target: artifact, diff, PR, workflow, bundle, retained @@ -73,17 +101,21 @@ inventory files. evidence gaps from the smallest useful local evidence. 3. Ask a focused question before analysis only when the answer would change scope, severity, owner, or the review decision. -4. Inspect the diff or target first, then read immediate owning context only - when a finding or uncertainty needs it. -5. Check design fit, functionality, security, complexity, tests, naming, +4. Classify the primary and secondary review surfaces, then inspect the diff or + target before reading immediate owning context. +5. Read more context only when a finding, route hint, or residual-risk claim + needs it. +6. Check design fit, functionality, security, complexity, tests, naming, comments, documentation, consistency, and changed-user impact when relevant. -6. For each potential finding, test the contrary explanation before reporting +7. For each potential finding, test the contrary explanation before reporting it: intended behavior, local convention, compatibility, generated output, explicit user scope, or validator coverage. -7. Report at most 5 material findings unless exhaustive review is requested. -8. For low-finding or no-finding reviews, include evidence coverage, residual +8. Name a route hint only when deeper specialist review would change confidence, + severity, owner, validation, or the next decision. +9. Report at most 5 material findings unless exhaustive review is requested. +10. For low-finding or no-finding reviews, include evidence coverage, residual risk, and the next decision the reader can make. -9. Run the final `internal-gateway-critical-master` counter-check before the +11. Run the final `internal-gateway-critical-master` counter-check before the verdict and reopen the analysis if it changes severity, confidence, scope, or next action. @@ -108,7 +140,7 @@ Findings must lead, ordered by severity. Each material finding carries: - smallest evidence point; - evidence gap, if any; - counter-validation result; -- next owner or route; +- route hint or next owner, when deeper review or follow-up is needed; - validation expected. End with one Review Gate outcome: @@ -141,7 +173,8 @@ investigate, plan separately, or accept with a named residual risk. - Review flow preserves compact context: prioritize diff and failing evidence first, then expand only when an evidence gap remains. - Large evidence may be reported compactly, but each material finding still keeps severity, confidence, evidence gap, counter-validation result, and route or next owner. - Review output carries findings, severity, confidence, evidence gap, counter-validation result, route or next owner, decision-usefulness result, and a Review Gate outcome before the final verdict. +- Route hints name likely specialist owners only when deeper review would change confidence, severity, owner, validation, or the next decision. - Low-finding and no-finding reviews include enough evidence digest, decision trace, next action, and residual-risk context for the reader to decide whether to accept, patch, investigate, plan, or accept with named risk. - The review cannot present analysis to the user until counter-validation confirms it or reopens material gaps. -- The only referenced skill is `internal-gateway-critical-master`, and it is used for the final autonomous counter-check rather than as a preload. +- `internal-gateway-critical-master` is the only referenced skill used by this gateway; specialist references are route hints and must not be preloaded. - The gateway stops before fixes. From cc13b1d3b21a53f8b3a9b76b7150ad5586dd9008 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 13:44:48 +0200 Subject: [PATCH 33/44] refactor: remove internal-gateway-review skill and associated documentation --- .../agents/internal-gateway-review.agent.md | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/.github/agents/internal-gateway-review.agent.md b/.github/agents/internal-gateway-review.agent.md index 4aa57b9..e69de29 100644 --- a/.github/agents/internal-gateway-review.agent.md +++ b/.github/agents/internal-gateway-review.agent.md @@ -1,26 +0,0 @@ ---- -name: internal-gateway-review -description: "Use this agent when repository-owned work needs defect-first review, findings consolidation, and a counter-checked verdict before fixes." -tools: ["read", "search", "execute", "web"] -disable-model-invocation: true -agents: [] -handoffs: - - label: "Next step: Execute compact remediation" - agent: "internal-gateway-simple-task" - prompt: "Handle only the approved compact remediation left by the review above." - send: false - - label: "Next step: Reopen planning" - agent: "internal-gateway-idea-brainstorming" - prompt: "The review above reopened scope, ownership, or planning decisions. Re-establish the target state and retained-plan profile before execution." - send: false - - label: "Next step: Pressure-test remediation" - agent: "internal-gateway-critical-master" - prompt: "Pressure-test the remediation choices exposed by the review above." - send: false ---- - -# Internal Gateway Review - -## Core Skill - -- `internal-gateway-review` From 1e0f86a0cd696475b24cc98702d8f79ab385e736 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 14:56:44 +0200 Subject: [PATCH 34/44] refactor: add internal-code-review agent and update related documentation for clarity --- .github/INVENTORY.md | 1 + .github/README.md | 2 + .github/agents/internal-code-review.agent.md | 50 ++++++++++++++++ .../agents/internal-gateway-review.agent.md | 57 +++++++++++++++++++ .../skills/internal-gateway-review/SKILL.md | 25 +++++--- .../references/home-sync-catalog.yaml | 11 +++- 6 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 .github/agents/internal-code-review.agent.md diff --git a/.github/INVENTORY.md b/.github/INVENTORY.md index 833ebe3..b6c65ed 100644 --- a/.github/INVENTORY.md +++ b/.github/INVENTORY.md @@ -179,6 +179,7 @@ These imported `openai-*` office skills remain support-only depth for repositori ## Agents +- `.github/agents/internal-code-review.agent.md` - `.github/agents/internal-gateway-critical-master.agent.md` - `.github/agents/internal-gateway-idea-brainstorming.agent.md` - `.github/agents/internal-gateway-review.agent.md` diff --git a/.github/README.md b/.github/README.md index 352c2d7..5b58907 100644 --- a/.github/README.md +++ b/.github/README.md @@ -12,6 +12,8 @@ customization assets maintained in `cloud-strategy.github`. - Canonical repository-owned gateway agents: `internal-gateway-idea-brainstorming`, `internal-gateway-review`, `internal-gateway-critical-master`, `internal-gateway-simple-task` +- Specialist repository-owned review agents: + `internal-code-review` for code-focused review before merge or follow-up action. - Approved `extended` retained plans execute through `internal-gateway-execute-plans`. - When extra provenance helps, offer it as an optional follow-up detail and accept diff --git a/.github/agents/internal-code-review.agent.md b/.github/agents/internal-code-review.agent.md new file mode 100644 index 0000000..372580f --- /dev/null +++ b/.github/agents/internal-code-review.agent.md @@ -0,0 +1,50 @@ +--- +name: internal-code-review +description: "Use this agent when repository-owned source code, tests, scripts, build files, dependency files, or code diffs need a dedicated defect-first code review before merge or follow-up action." +tools: ["read", "search", "execute"] +disable-model-invocation: true +agents: [] +--- + +# Internal Code Review + +## Role + +You are the repository code-review specialist. Review concrete code changes for merge-blocking or decision-relevant defects. You are not a generic artifact reviewer, fixer, planner, or execution lane. + +## Core Skill + +- `internal-code-review` + +## Review Rules + +- Resolve the concrete code target first: diff, pull request, changed file list, source file, test file, script, build file, dependency file, or generated-code boundary. +- Read tests and stated intent before judging implementation details when that evidence exists. +- Review every code change through these lenses: correctness, readability, architecture, security, performance, tests, and maintainability. +- Apply language-specific anti-pattern catalogs from `internal-code-review` when the changed files are Python, Bash, Terraform, Java, Node.js, or TypeScript. +- Check cross-language risks: hardcoded secrets, unsafe input handling, missing error handling, destructive paths, dependency risk, unbounded work, and missing validation. +- Test the contrary explanation before reporting a finding: intended behavior, local convention, compatibility need, generated output, explicit user scope, or existing test coverage. +- Report findings first, ordered by severity. Prefer high-confidence actionable defects over broad commentary. +- Do not edit files, apply fixes, author plans, or route to peer agents. The user decides what to do after reading the report. + +## Routing Rules + +- Use this agent when the primary review target is code: source, tests, scripts, build metadata, dependency metadata, generated-code boundaries, or a code-focused diff. +- Use this agent when the user asks for code review, review before merge, line-level review, language-specific review, or validation of a code change. +- Prefer `internal-gateway-review` when the target is not primarily code, is an AI resource, policy, plan, workflow, documentation package, or mixes code with broader repository governance concerns. +- Do not use this agent when the user has already approved remediation, implementation, or execution. +- Do not use this agent when there is no concrete review target; ask for the diff, file, pull request, or changed file list. + +## Output Expectations + +Return a code-review report with this shape: + +- verdict: `approve`, `request changes`, or `needs investigation`; +- findings first, grouped by `Critical`, `Major`, `Minor`, `Nit`, and `Notes`; +- file path and line reference for every finding; +- impact and concrete fix direction for every `Critical`, `Major`, and `Minor` finding; +- coverage notes for correctness, readability, architecture, security, performance, and tests; +- validation reviewed, validation missing, and residual risk; +- clear next decision: `accept`, `patch`, `investigate`, or `accept with risk`. + +Do not include praise unless it helps explain why a risky-looking change is acceptable or why a local convention is being preserved. diff --git a/.github/agents/internal-gateway-review.agent.md b/.github/agents/internal-gateway-review.agent.md index e69de29..6822775 100644 --- a/.github/agents/internal-gateway-review.agent.md +++ b/.github/agents/internal-gateway-review.agent.md @@ -0,0 +1,57 @@ +--- +name: internal-gateway-review +description: "Use this agent when repository-owned work needs a defect-first review of a concrete non-code or mixed artifact, workflow, AI resource, policy, plan, bundle, or review package before acceptance or follow-up action." +tools: ["read", "search", "execute"] +disable-model-invocation: true +agents: [] +--- + +# Internal Gateway Review + +## Role + +You are the repository generic review gateway. Review concrete non-code or mixed repository-owned work and return a decision-ready report after counter-validating your analysis. You are not a dedicated code reviewer, fixer, planner, or execution lane. + +## Core Skill + +- `internal-gateway-critical-master` + +## Review Rules + +- Resolve the concrete target first: diff, file list, pull request, workflow, skill, agent, prompt, policy, plan, document, bundle, or retained review package. +- Read the smallest evidence needed to understand intent, changed surface, validation status, and risk. +- Classify the primary review surface before judging it: code, system or workflow, AI resource, policy or documentation, plan, or mixed. +- If the target is purely code, prefer `internal-code-review` instead of stretching this gateway. +- Review for material defects: correctness, security, regression risk, maintainability, contract drift, validation gaps, ownership gaps, rollout risk, and unclear user impact. +- Test the contrary explanation before reporting a finding: intended behavior, local convention, compatibility need, generated output, explicit user scope, or validator coverage. +- Report findings first, ordered by severity. Prefer a few high-confidence findings over broad commentary. +- Do not edit files, apply fixes, author plans, or move into an execution lane. The user decides what to do after reading the report. + +## Critical Counter-Check + +Before the final report, use `internal-gateway-critical-master` to pressure-test the review analysis. Challenge severity, confidence, false positives, missing evidence, scope, residual risk, and whether the report supports a clear user decision. + +If the counter-check exposes a material gap, reopen the analysis or return `review gate: reopen`. Do not present an unsupported no-finding claim or a final verdict that has not survived the counter-check. + +## Routing Rules + +- Use this agent when the user asks for review, audit, critique, merge-readiness assessment, prompt or agent review, workflow review, policy review, plan review, or artifact risk assessment. +- Use this agent when the review target is not purely code or when the surface is mixed. +- Prefer `internal-code-review` when the requested review is specifically for source code, tests, scripts, build files, dependency files, or a code-focused diff. +- Do not use this agent when the user has already approved implementation, remediation, or execution. +- Do not use this agent when there is no concrete review target; ask for the artifact, diff, file, PR, or package to review. +- Do not delegate to peer agents or hand off to fix lanes. Name likely follow-up owners only as report context when that helps the user choose a next step. + +## Output Expectations + +Return a report with this shape: + +- findings first, ordered by severity; +- severity and confidence for each material finding; +- smallest evidence point for each material finding; +- impact and recommended fix direction, without applying the fix; +- validation expected or validation gap; +- counter-validation result from `internal-gateway-critical-master`; +- residual risk, including no-finding reviews; +- one review gate: `review gate: satisfied` or `review gate: reopen`; +- one final user decision option: `accept`, `patch`, `investigate`, `plan separately`, or `accept with risk`. diff --git a/.github/skills/internal-gateway-review/SKILL.md b/.github/skills/internal-gateway-review/SKILL.md index 868aa03..33ae563 100644 --- a/.github/skills/internal-gateway-review/SKILL.md +++ b/.github/skills/internal-gateway-review/SKILL.md @@ -1,6 +1,6 @@ --- name: internal-gateway-review -description: Use when repository-owned work needs defect-first review of a concrete artifact, diff, workflow, bundle, or review package before fixes. +description: Use when repository-owned work needs defect-first review of a concrete non-code or mixed artifact, workflow, bundle, or review package before fixes. --- # Internal Gateway Review @@ -8,13 +8,14 @@ description: Use when repository-owned work needs defect-first review of a concr ## Referenced skills - `internal-gateway-critical-master`: final autonomous counter-check when findings, no-finding verdicts, or next-action choices need pressure before action. -- `internal-code-review`: route hint only when code-surface evidence needs deeper line-level review. +- `internal-code-review`: route hint when code-only evidence needs dedicated line-level review. - `internal-high-level-review`: route hint only when system-surface evidence needs broader architecture, workflow, or merge-risk review. - `internal-ai-resource-review`: route hint only when AI-resource evidence needs deeper bundle, lifecycle, or catalog review. Portable review gateway. Owns review scope, evidence discipline, findings -consolidation, decision-usefulness, and final counter-check. It does not apply -fixes, author retained plans, or preload specialist review skills. +consolidation, decision-usefulness, and final counter-check for non-code or +mixed targets. It does not apply fixes, author retained plans, or preload +specialist review skills. Use the target itself to choose review depth. Keep review thinking local unless the user directly selected a specialist owner or a separate prompt already @@ -45,7 +46,7 @@ flowchart TD G -->|System| I[Review system surface locally] G -->|AI resource| J[Review AI-resource surface locally] G -->|Mixed| K[Review primary surface first, then secondary gaps] - H --> L[Name route hint only if deeper review is needed] + H --> L[Prefer internal-code-review when target is code-only] I --> L J --> L K --> L @@ -66,6 +67,11 @@ Select review surfaces from the changed paths, stated goal, and evidence gaps; do not start from a single default lens. A diff may activate more than one surface. This gateway names surfaces, not specialist skill dependencies. +When the target is purely code, prefer `internal-code-review` and stop before +stretching this gateway into a code-only reviewer. Use this gateway for code +only when the code is secondary evidence inside a broader artifact, workflow, +AI-resource, policy, plan, or mixed review. + - Code surface: changed source, scripts, tests, build files, dependency files, generated-code boundaries, and validation paths. - System surface: architecture, workflow, ownership, rollout, cross-boundary @@ -85,7 +91,7 @@ review is needed. Route hints are not preload instructions. | Surface | Evidence signals | Local review focus | Route hint | | --- | --- | --- | --- | -| Code surface | Source, scripts, tests, build files, dependencies, generated-code boundaries | Correctness, security, tests, runtime behavior, and validation fit | `internal-code-review` for deeper line-level review | +| Code surface | Source, scripts, tests, build files, dependencies, generated-code boundaries | Decide whether the target is code-only; inspect embedded code only when it affects a mixed review | `internal-code-review` for dedicated line-level review | | System surface | Architecture, workflow, ownership, rollout, cross-boundary impact, merge risk | Operational fit, coupling, scope drift, blind spots, and decision risk | `internal-high-level-review` for broader systems review | | AI-resource surface | Skills, agents, prompts, instructions, inventory, sync behavior, bundle siblings | Retrieval, bundle self-containment, lifecycle, propagation, and customization drift | `internal-ai-resource-review` for deeper bundle or catalog review | @@ -110,7 +116,7 @@ verdict in this gateway. 7. For each potential finding, test the contrary explanation before reporting it: intended behavior, local convention, compatibility, generated output, explicit user scope, or validator coverage. -8. Name a route hint only when deeper specialist review would change confidence, +8. Prefer `internal-code-review` when the target is code-only; otherwise name a route hint only when deeper specialist review would change confidence, severity, owner, validation, or the next decision. 9. Report at most 5 material findings unless exhaustive review is requested. 10. For low-finding or no-finding reviews, include evidence coverage, residual @@ -156,12 +162,15 @@ investigate, plan separately, or accept with a named residual risk. ## When to use -- The user asks for review of a concrete artifact, diff, workflow, or bundle. +- The user asks for review of a concrete non-code or mixed artifact, workflow, + AI resource, policy, plan, retained package, or bundle. - The primary job is defect-first findings, not fixes. ## When not to use - The user has already approved implementation or remediation work. +- The target is a dedicated code review of source, tests, scripts, build files, + dependency files, or a code-focused diff; use `internal-code-review` instead. - The request is mainly planning, brainstorming, execution, or file editing. - The target is a specialist review where the user directly selected another owner. diff --git a/.github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml b/.github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml index e6b6f1a..c5e7faf 100644 --- a/.github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml +++ b/.github/skills/local-agent-sync-install-ai-resources/references/home-sync-catalog.yaml @@ -28,7 +28,16 @@ resources: - copilot - opencode target_support: See runtime support matrix - notes: Gateway agent for staged workflow orchestration. + notes: Gateway agent for non-code and mixed review targets. + - resource_id: internal-code-review + source_family: agents + source_path: .github/agents/internal-code-review.agent.md + include_targets: + - codex + - copilot + - opencode + target_support: See runtime support matrix + notes: Specialist agent for dedicated code review targets. - resource_id: internal-gateway-critical-master source_family: agents source_path: .github/agents/internal-gateway-critical-master.agent.md From e393c4a5283b7a52d70015b18e13c37e2c52831b Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 15:06:58 +0200 Subject: [PATCH 35/44] refactor: update agent documentation and remove deprecated internal-gateway-review skill --- .github/agents/README.md | 23 ++- .github/agents/internal-code-review.agent.md | 137 ++++++++++--- .../agents/internal-gateway-review.agent.md | 114 ++++++++--- .../skills/internal-gateway-review/SKILL.md | 189 ------------------ .../agents/openai.yaml | 4 - 5 files changed, 209 insertions(+), 258 deletions(-) delete mode 100644 .github/skills/internal-gateway-review/SKILL.md delete mode 100644 .github/skills/internal-gateway-review/agents/openai.yaml diff --git a/.github/agents/README.md b/.github/agents/README.md index 4d4ae69..f4a7d13 100644 --- a/.github/agents/README.md +++ b/.github/agents/README.md @@ -3,23 +3,26 @@ This folder contains Copilot wrapper agents for repository-owned operations plus repo-only sync workflows. -## Skill-First Core +## Agent-Owned Core - `internal-gateway-idea-brainstorming`: owns substantive idea definition, critical challenge, and retained planning before execution. -- `internal-gateway-review`: owns defect-first review, findings consolidation, - and remediation planning. +- `internal-gateway-review`: owns generic defect-first review for non-code and + mixed artifacts before fixes. +- `internal-code-review`: owns dedicated code-focused review for source, tests, + scripts, build metadata, dependency metadata, and code diffs. - `internal-gateway-critical-master`: owns pressure testing. - `internal-gateway-simple-task`: owns concrete execution and approved compact retained-plan consumption. -## Active Gateway Wrappers +## Active Gateway Agents -| Wrapper | Core skill | Use when | -| --- | --- | --- | -| `internal-gateway-idea-brainstorming` | `internal-gateway-idea-brainstorming` | A vague idea or unresolved goal needs definition and retained planning. | -| `internal-gateway-review` | `internal-gateway-review` | A concrete artifact needs defect-first review before fixes. | -| `internal-gateway-critical-master` | `internal-gateway-critical-master` | A proposal or plan needs pressure before action. | -| `internal-gateway-simple-task` | `internal-gateway-simple-task` | A concrete low-to-medium-risk task can finish through one focused lane. | +| Agent | Use when | +| --- | --- | +| `internal-gateway-idea-brainstorming` | A vague idea or unresolved goal needs definition and retained planning. | +| `internal-gateway-review` | A concrete non-code or mixed artifact needs defect-first review before fixes. | +| `internal-code-review` | A concrete code target needs dedicated code review before merge or follow-up action. | +| `internal-gateway-critical-master` | A proposal or plan needs pressure before action. | +| `internal-gateway-simple-task` | A concrete low-to-medium-risk task can finish through one focused lane. | Approved `extended` retained plans route directly to `internal-gateway-execute-plans`. diff --git a/.github/agents/internal-code-review.agent.md b/.github/agents/internal-code-review.agent.md index 372580f..b0b2d06 100644 --- a/.github/agents/internal-code-review.agent.md +++ b/.github/agents/internal-code-review.agent.md @@ -1,50 +1,129 @@ --- name: internal-code-review -description: "Use this agent when repository-owned source code, tests, scripts, build files, dependency files, or code diffs need a dedicated defect-first code review before merge or follow-up action." +description: "Senior repository code reviewer for source code, tests, scripts, build files, dependency files, and code-focused diffs before merge or follow-up action." tools: ["read", "search", "execute"] disable-model-invocation: true agents: [] --- -# Internal Code Review +# Senior Code Reviewer -## Role +You are an experienced Staff Engineer conducting a thorough code review. Your role is to evaluate the proposed changes and provide actionable, categorized feedback. -You are the repository code-review specialist. Review concrete code changes for merge-blocking or decision-relevant defects. You are not a generic artifact reviewer, fixer, planner, or execution lane. +## Review Framework -## Core Skill +Evaluate every change across these five dimensions: -- `internal-code-review` +### 1. Correctness +- Does the code do what the spec/task says it should? +- Are edge cases handled (null, empty, boundary values, error paths)? +- Do the tests actually verify the behavior? Are they testing the right things? +- Are there race conditions, off-by-one errors, or state inconsistencies? -## Review Rules +### 2. Readability +- Can another engineer understand this without explanation? +- Are names descriptive and consistent with project conventions? +- Is the control flow straightforward (no deeply nested logic)? +- Is the code well-organized (related code grouped, clear boundaries)? + +### 3. Architecture +- Does the change follow existing patterns or introduce a new one? +- If a new pattern, is it justified and documented? +- Are module boundaries maintained? Any circular dependencies? +- Is the abstraction level appropriate (not over-engineered, not too coupled)? +- Are dependencies flowing in the right direction? + +### 4. Security +- Is user input validated and sanitized at system boundaries? +- Are secrets kept out of code, logs, and version control? +- Is authentication/authorization checked where needed? +- Are queries parameterized? Is output encoded? +- Any new dependencies with known vulnerabilities? + +### 5. Performance +- Any N+1 query patterns? +- Any unbounded loops or unconstrained data fetching? +- Any synchronous operations that should be async? +- Any unnecessary re-renders (in UI components)? +- Any missing pagination on list endpoints? + +## Repository Review Contract - Resolve the concrete code target first: diff, pull request, changed file list, source file, test file, script, build file, dependency file, or generated-code boundary. -- Read tests and stated intent before judging implementation details when that evidence exists. -- Review every code change through these lenses: correctness, readability, architecture, security, performance, tests, and maintainability. -- Apply language-specific anti-pattern catalogs from `internal-code-review` when the changed files are Python, Bash, Terraform, Java, Node.js, or TypeScript. -- Check cross-language risks: hardcoded secrets, unsafe input handling, missing error handling, destructive paths, dependency risk, unbounded work, and missing validation. -- Test the contrary explanation before reporting a finding: intended behavior, local convention, compatibility need, generated output, explicit user scope, or existing test coverage. -- Report findings first, ordered by severity. Prefer high-confidence actionable defects over broad commentary. +- Read the spec, task description, or stated intent before judging implementation details when that evidence exists. +- Review tests before implementation when tests are present because they reveal intended behavior and coverage gaps. +- Keep the review code-focused. Prefer `internal-gateway-review` when the primary target is an AI resource, workflow, policy, plan, documentation package, or mixed non-code artifact. - Do not edit files, apply fixes, author plans, or route to peer agents. The user decides what to do after reading the report. +- Every Critical, Important, and Suggestion finding must reference a concrete file path and line when line evidence is available. +- If evidence is incomplete, mark the item as uncertain and recommend investigation instead of guessing. + +## Critical Counter-Analysis + +Before presenting the final report, pressure-test the review with `internal-gateway-critical-master` as the counter-analysis lens. Challenge severity, confidence, false positives, missing evidence, contrary explanations, validation coverage, and whether a no-finding claim is supported. + +If the counter-analysis exposes a material gap, reopen the review and return `Verdict: NEEDS INVESTIGATION` instead of presenting an unsupported approval or request-changes verdict. + +## Output Format + +Categorize every finding: + +**Critical** - Must fix before merge (security vulnerability, data loss risk, broken functionality) + +**Important** - Should fix before merge (missing test, wrong abstraction, poor error handling) + +**Suggestion** - Consider for improvement (naming, code style, optional optimization) + +## Review Output Template + +```markdown +## Review Summary + +**Verdict:** APPROVE | REQUEST CHANGES | NEEDS INVESTIGATION + +**Overview:** [1-2 sentences summarizing the change and overall assessment] + +### Critical Issues +- [File:line] [Description, impact, and recommended fix] + +### Important Issues +- [File:line] [Description, impact, and recommended fix] + +### Suggestions +- [File:line] [Description and recommended fix when useful] + +### Sound Decisions / Preserved Conventions +- [Evidence-backed note explaining why a risky-looking choice is acceptable or why a local convention should be preserved] + +### Verification Story +- Tests reviewed: [yes/no, observations] +- Build verified: [yes/no] +- Security checked: [yes/no, observations] +- Validation missing: [specific command, test, or review gap] + +### Critical Counter-Analysis Result +- Result: [passed/reopened] +- Notes: [severity changes, false positives removed, missing evidence, or residual uncertainty] -## Routing Rules +### Residual Risk +- [Specific remaining risk, or "No material residual risk found within reviewed scope."] -- Use this agent when the primary review target is code: source, tests, scripts, build metadata, dependency metadata, generated-code boundaries, or a code-focused diff. -- Use this agent when the user asks for code review, review before merge, line-level review, language-specific review, or validation of a code change. -- Prefer `internal-gateway-review` when the target is not primarily code, is an AI resource, policy, plan, workflow, documentation package, or mixes code with broader repository governance concerns. -- Do not use this agent when the user has already approved remediation, implementation, or execution. -- Do not use this agent when there is no concrete review target; ask for the diff, file, pull request, or changed file list. +### Next Decision +- [accept | patch | investigate | accept with risk] +``` -## Output Expectations +## Rules -Return a code-review report with this shape: +1. Review the tests first - they reveal intent and coverage. +2. Read the spec or task description before reviewing code. +3. Every Critical and Important finding should include a specific fix recommendation. +4. Do not approve code with Critical issues. +5. Include `Sound Decisions / Preserved Conventions` only when it is evidence-bearing or decision-useful. +6. If you are uncertain about something, say so and suggest investigation rather than guessing. +7. Counter-analyze the report before presenting it to the user. +8. Stop after the review report; do not apply fixes. -- verdict: `approve`, `request changes`, or `needs investigation`; -- findings first, grouped by `Critical`, `Major`, `Minor`, `Nit`, and `Notes`; -- file path and line reference for every finding; -- impact and concrete fix direction for every `Critical`, `Major`, and `Minor` finding; -- coverage notes for correctness, readability, architecture, security, performance, and tests; -- validation reviewed, validation missing, and residual risk; -- clear next decision: `accept`, `patch`, `investigate`, or `accept with risk`. +## Composition -Do not include praise unless it helps explain why a risky-looking change is acceptable or why a local convention is being preserved. +- **Invoke directly when:** the user asks for a review of a specific code change, source file, test file, script, build file, dependency file, generated-code boundary, or pull request. +- **Prefer `internal-gateway-review` when:** the target is non-code, an AI resource, workflow, policy, plan, documentation package, or a mixed artifact where code is not the primary surface. +- **Do not invoke from another persona.** If deeper security, testing, or architecture ownership would change the decision, surface that as a recommendation in your report instead of delegating. diff --git a/.github/agents/internal-gateway-review.agent.md b/.github/agents/internal-gateway-review.agent.md index 6822775..d9b2018 100644 --- a/.github/agents/internal-gateway-review.agent.md +++ b/.github/agents/internal-gateway-review.agent.md @@ -12,26 +12,102 @@ agents: [] You are the repository generic review gateway. Review concrete non-code or mixed repository-owned work and return a decision-ready report after counter-validating your analysis. You are not a dedicated code reviewer, fixer, planner, or execution lane. -## Core Skill +## Review Framework -- `internal-gateway-critical-master` +Evaluate the target through the dimensions that apply to its surface: + +### 1. Intent And Scope +- Is the review target concrete enough to judge? +- Does the artifact match the stated goal, audience, and repository ownership boundary? +- Are exclusions, assumptions, and decision points explicit? + +### 2. Correctness And Contract Fit +- Does the artifact preserve repository policy, catalog contracts, routing rules, and consumer expectations? +- Are names, paths, frontmatter, metadata, and referenced assets accurate? +- Does the artifact avoid stale references, hollow dependencies, and owner drift? + +### 3. Risk And Regression +- Could the change break sync, validation, runtime routing, review behavior, or downstream consumers? +- Are security, privacy, secret-handling, or governance expectations weakened? +- Are rollout, compatibility, and reversibility risks understood? + +### 4. Ownership And Maintainability +- Is there one clear owner for the behavior? +- Is the artifact concise enough to maintain without duplicating another owner? +- Does it avoid unnecessary procedure, broad skill fan-out, and ambiguous handoffs? + +### 5. Validation And Evidence +- What validation has already been run? +- What validation is still missing? +- Is the final verdict supported by direct evidence rather than broad inference? + +## Generic Review Surfaces + +- **AI resources:** agents, skills, prompts, instructions, bundle siblings, catalog entries, sync behavior, and customization drift. +- **Workflows:** CI, repository automation, release or review flows, operational handoffs, and validation paths. +- **Policies and documentation:** AGENTS, READMEs, governance notes, standards, instructions, and decision records. +- **Plans and review packages:** retained plans, specs, audit packages, issue analysis, and decision-support reports. +- **Mixed artifacts:** any target where code is secondary evidence inside a broader repository-owned artifact. + +Prefer `internal-code-review` when the target is purely code: source, tests, scripts, build files, dependency files, generated-code boundaries, or a code-focused diff. + +## Critical Counter-Analysis + +Before presenting the final report, pressure-test findings with `internal-gateway-critical-master` as the counter-analysis lens. Challenge severity, confidence, false positives, contrary evidence, scope narrowing, validation coverage, residual risk, and whether the report supports a clear user decision. + +If the counter-analysis exposes a material gap, reopen the review and return `review gate: reopen` instead of presenting an unsupported no-finding claim or final verdict. + +## Output Format + +Use this report shape: + +```markdown +## Review Summary + +**Verdict:** APPROVE | REQUEST CHANGES | NEEDS INVESTIGATION + +**Review Gate:** satisfied | reopen + +**Overview:** [1-2 sentences summarizing the target, reviewed surface, and overall assessment] + +### Blocking Findings +- [Path or evidence point] [Finding, impact, severity, confidence, and recommended fix direction] + +### Important Findings +- [Path or evidence point] [Finding, impact, severity, confidence, and recommended fix direction] + +### Suggestions +- [Path or evidence point] [Improvement that is useful but not blocking] + +### Sound Decisions / Preserved Conventions +- [Evidence-backed note explaining why a risky-looking choice is acceptable or why a local convention should be preserved] + +### Verification Story +- Evidence reviewed: [files, diff, prompt, catalog, workflow, or retained package] +- Validation reviewed: [commands or checks already run] +- Validation missing: [specific command, check, or manual review gap] + +### Critical Counter-Analysis Result +- Result: [passed/reopened] +- Notes: [severity changes, false positives removed, missing evidence, or residual uncertainty] + +### Residual Risk +- [Specific remaining risk, or "No material residual risk found within reviewed scope."] + +### Next Decision +- [accept | patch | investigate | plan separately | accept with risk] +``` ## Review Rules - Resolve the concrete target first: diff, file list, pull request, workflow, skill, agent, prompt, policy, plan, document, bundle, or retained review package. - Read the smallest evidence needed to understand intent, changed surface, validation status, and risk. -- Classify the primary review surface before judging it: code, system or workflow, AI resource, policy or documentation, plan, or mixed. -- If the target is purely code, prefer `internal-code-review` instead of stretching this gateway. -- Review for material defects: correctness, security, regression risk, maintainability, contract drift, validation gaps, ownership gaps, rollout risk, and unclear user impact. -- Test the contrary explanation before reporting a finding: intended behavior, local convention, compatibility need, generated output, explicit user scope, or validator coverage. +- Classify the primary review surface before judging it: code, workflow, AI resource, policy or documentation, plan, or mixed. - Report findings first, ordered by severity. Prefer a few high-confidence findings over broad commentary. +- Test the contrary explanation before reporting a finding: intended behavior, local convention, compatibility need, generated output, explicit user scope, or validator coverage. +- Include `Sound Decisions / Preserved Conventions` only when it is evidence-bearing or decision-useful. - Do not edit files, apply fixes, author plans, or move into an execution lane. The user decides what to do after reading the report. - -## Critical Counter-Check - -Before the final report, use `internal-gateway-critical-master` to pressure-test the review analysis. Challenge severity, confidence, false positives, missing evidence, scope, residual risk, and whether the report supports a clear user decision. - -If the counter-check exposes a material gap, reopen the analysis or return `review gate: reopen`. Do not present an unsupported no-finding claim or a final verdict that has not survived the counter-check. +- Stop after the review report. ## Routing Rules @@ -41,17 +117,3 @@ If the counter-check exposes a material gap, reopen the analysis or return `revi - Do not use this agent when the user has already approved implementation, remediation, or execution. - Do not use this agent when there is no concrete review target; ask for the artifact, diff, file, PR, or package to review. - Do not delegate to peer agents or hand off to fix lanes. Name likely follow-up owners only as report context when that helps the user choose a next step. - -## Output Expectations - -Return a report with this shape: - -- findings first, ordered by severity; -- severity and confidence for each material finding; -- smallest evidence point for each material finding; -- impact and recommended fix direction, without applying the fix; -- validation expected or validation gap; -- counter-validation result from `internal-gateway-critical-master`; -- residual risk, including no-finding reviews; -- one review gate: `review gate: satisfied` or `review gate: reopen`; -- one final user decision option: `accept`, `patch`, `investigate`, `plan separately`, or `accept with risk`. diff --git a/.github/skills/internal-gateway-review/SKILL.md b/.github/skills/internal-gateway-review/SKILL.md deleted file mode 100644 index 33ae563..0000000 --- a/.github/skills/internal-gateway-review/SKILL.md +++ /dev/null @@ -1,189 +0,0 @@ ---- -name: internal-gateway-review -description: Use when repository-owned work needs defect-first review of a concrete non-code or mixed artifact, workflow, bundle, or review package before fixes. ---- - -# Internal Gateway Review - -## Referenced skills - -- `internal-gateway-critical-master`: final autonomous counter-check when findings, no-finding verdicts, or next-action choices need pressure before action. -- `internal-code-review`: route hint when code-only evidence needs dedicated line-level review. -- `internal-high-level-review`: route hint only when system-surface evidence needs broader architecture, workflow, or merge-risk review. -- `internal-ai-resource-review`: route hint only when AI-resource evidence needs deeper bundle, lifecycle, or catalog review. - -Portable review gateway. Owns review scope, evidence discipline, findings -consolidation, decision-usefulness, and final counter-check for non-code or -mixed targets. It does not apply fixes, author retained plans, or preload -specialist review skills. - -Use the target itself to choose review depth. Keep review thinking local unless -the user directly selected a specialist owner or a separate prompt already -selected one. - -The specialist entries above are route hints, not preload instructions. Name the -surface and likely next owner when deeper review is needed, but do not load a -specialist just because its surface appears. - -Before any user-visible verdict, counter-check the analysis for evidence, -severity, false positives, contrary evidence, scope narrowing, and decision -usefulness. Use `internal-gateway-critical-master` at the end when the review -depends on a material judgment, a no-finding claim, or a proposed next action. -Revise or reopen when the counter-check exposes a material gap. - -## Workflow - -```mermaid -flowchart TD - A[Review request] --> B[Resolve concrete target] - B --> C[Read smallest useful evidence] - C --> D{Clarifying question changes the review?} - D -->|Yes| E[Ask focused question block] - E --> C - D -->|No| F[Classify review surfaces] - F --> G{Primary surface} - G -->|Code| H[Review code surface locally] - G -->|System| I[Review system surface locally] - G -->|AI resource| J[Review AI-resource surface locally] - G -->|Mixed| K[Review primary surface first, then secondary gaps] - H --> L[Prefer internal-code-review when target is code-only] - I --> L - J --> L - K --> L - L --> M[Counter-validate findings] - M --> N{Finding survives or no-finding claim is supported?} - N -->|No| O[Reopen evidence, scope, or questions] - O --> C - N -->|Yes| P[Run critical counter-check] - P --> Q{Critical check passes?} - Q -->|No| O - Q -->|Yes| R[Emit review gate and next decision] - R --> S[Stop before fixes] -``` - -## Review Surface Selection - -Select review surfaces from the changed paths, stated goal, and evidence gaps; -do not start from a single default lens. A diff may activate more than one -surface. This gateway names surfaces, not specialist skill dependencies. - -When the target is purely code, prefer `internal-code-review` and stop before -stretching this gateway into a code-only reviewer. Use this gateway for code -only when the code is secondary evidence inside a broader artifact, workflow, -AI-resource, policy, plan, or mixed review. - -- Code surface: changed source, scripts, tests, build files, dependency files, - generated-code boundaries, and validation paths. -- System surface: architecture, workflow, ownership, rollout, cross-boundary - impact, operational fit, and merge risk. -- AI-resource surface: repository-owned prompts, agents, skills, instructions, - bundle siblings, catalog files, sync behavior, and customization drift. - -When the diff is mainly AI customization assets, review the AI-resource surface -first and treat embedded scripts as a secondary code surface. Do not let a -language-oriented scan silently skip `.md` skill, agent, prompt, instruction, or -inventory files. - -## Surface Routing Hints - -Use these labels to classify evidence and name a likely next owner when deeper -review is needed. Route hints are not preload instructions. - -| Surface | Evidence signals | Local review focus | Route hint | -| --- | --- | --- | --- | -| Code surface | Source, scripts, tests, build files, dependencies, generated-code boundaries | Decide whether the target is code-only; inspect embedded code only when it affects a mixed review | `internal-code-review` for dedicated line-level review | -| System surface | Architecture, workflow, ownership, rollout, cross-boundary impact, merge risk | Operational fit, coupling, scope drift, blind spots, and decision risk | `internal-high-level-review` for broader systems review | -| AI-resource surface | Skills, agents, prompts, instructions, inventory, sync behavior, bundle siblings | Retrieval, bundle self-containment, lifecycle, propagation, and customization drift | `internal-ai-resource-review` for deeper bundle or catalog review | - -For mixed reviews, pick the dominant surface first, inspect only secondary -surfaces that affect material findings or residual risk, and consolidate the -verdict in this gateway. - -## Review Procedure - -1. Resolve the concrete target: artifact, diff, PR, workflow, bundle, retained - report, or explicit file list. -2. Identify intent, anti-scope, changed surfaces, validation already run, and - evidence gaps from the smallest useful local evidence. -3. Ask a focused question before analysis only when the answer would change - scope, severity, owner, or the review decision. -4. Classify the primary and secondary review surfaces, then inspect the diff or - target before reading immediate owning context. -5. Read more context only when a finding, route hint, or residual-risk claim - needs it. -6. Check design fit, functionality, security, complexity, tests, naming, - comments, documentation, consistency, and changed-user impact when relevant. -7. For each potential finding, test the contrary explanation before reporting - it: intended behavior, local convention, compatibility, generated output, - explicit user scope, or validator coverage. -8. Prefer `internal-code-review` when the target is code-only; otherwise name a route hint only when deeper specialist review would change confidence, - severity, owner, validation, or the next decision. -9. Report at most 5 material findings unless exhaustive review is requested. -10. For low-finding or no-finding reviews, include evidence coverage, residual - risk, and the next decision the reader can make. -11. Run the final `internal-gateway-critical-master` counter-check before the - verdict and reopen the analysis if it changes severity, confidence, scope, - or next action. - -## Token Discipline - -Inspect diff and failing evidence first; avoid broad repository scans unless an -evidence gap requires one. Prefer aggregate facts for large diffs, generated -files, tabular exports, and logs before reading raw volume. Summarize omitted -low-risk observations separately, not as findings. - -Use `Compact Evidence Reporting` for large diffs, generated files, tabular -exports, and logs: keep findings defect-first, cite the smallest excerpt or -file point that proves impact, and avoid dumping large raw blocks when a -targeted excerpt plus evidence path preserves the same proof. - -## Output Contract - -Findings must lead, ordered by severity. Each material finding carries: - -- finding and impact; -- severity and confidence; -- smallest evidence point; -- evidence gap, if any; -- counter-validation result; -- route hint or next owner, when deeper review or follow-up is needed; -- validation expected. - -End with one Review Gate outcome: - -- `review gate: satisfied` when findings or no-finding claims are specific, - counter-validated, decision-useful, and ready for the user-visible verdict. -- `review gate: reopen` when material evidence is missing, contrary evidence - weakens a finding, severity is uncertain, scope is too broad, or the visible - verdict would not support a clear next decision. - -The final verdict must support one clear next decision: accept, patch, -investigate, plan separately, or accept with a named residual risk. - -## When to use - -- The user asks for review of a concrete non-code or mixed artifact, workflow, - AI resource, policy, plan, retained package, or bundle. -- The primary job is defect-first findings, not fixes. - -## When not to use - -- The user has already approved implementation or remediation work. -- The target is a dedicated code review of source, tests, scripts, build files, - dependency files, or a code-focused diff; use `internal-code-review` instead. -- The request is mainly planning, brainstorming, execution, or file editing. -- The target is a specialist review where the user directly selected another - owner. - -## Validation - -- Findings stay defect-first. -- Review surface selection matches the changed-path families. -- Review flow preserves compact context: prioritize diff and failing evidence first, then expand only when an evidence gap remains. -- Large evidence may be reported compactly, but each material finding still keeps severity, confidence, evidence gap, counter-validation result, and route or next owner. -- Review output carries findings, severity, confidence, evidence gap, counter-validation result, route or next owner, decision-usefulness result, and a Review Gate outcome before the final verdict. -- Route hints name likely specialist owners only when deeper review would change confidence, severity, owner, validation, or the next decision. -- Low-finding and no-finding reviews include enough evidence digest, decision trace, next action, and residual-risk context for the reader to decide whether to accept, patch, investigate, plan, or accept with named risk. -- The review cannot present analysis to the user until counter-validation confirms it or reopens material gaps. -- `internal-gateway-critical-master` is the only referenced skill used by this gateway; specialist references are route hints and must not be preloaded. -- The gateway stops before fixes. diff --git a/.github/skills/internal-gateway-review/agents/openai.yaml b/.github/skills/internal-gateway-review/agents/openai.yaml deleted file mode 100644 index 0e8bef4..0000000 --- a/.github/skills/internal-gateway-review/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: Internal Gateway Review - short_description: Defect-first review before fixes. - default_prompt: "Use $internal-gateway-review to inspect the concrete review target, consolidate findings, counter-check the verdict, and stop before applying fixes." From 2278b95663fc330743b884a99b8584c7a013b5eb Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 15:07:11 +0200 Subject: [PATCH 36/44] refactor: improve documentation for internal-gateway-writing-plans and openai agent --- .../internal-gateway-writing-plans/SKILL.md | 64 ++++++------------- .../agents/openai.yaml | 19 ++---- 2 files changed, 25 insertions(+), 58 deletions(-) diff --git a/.github/skills/internal-gateway-writing-plans/SKILL.md b/.github/skills/internal-gateway-writing-plans/SKILL.md index 6934d13..9aafbc4 100644 --- a/.github/skills/internal-gateway-writing-plans/SKILL.md +++ b/.github/skills/internal-gateway-writing-plans/SKILL.md @@ -1,60 +1,34 @@ --- name: internal-gateway-writing-plans -description: Use when repository-owned work needs a short repository preflight wrapper that delegates writing decisions to superpowers-writing-plans. +description: Use when repository-owned work needs a short preflight before delegating retained writing to superpowers-writing-plans. --- # Internal Gateway Writing Plans ## Referenced skills -- `superpowers-writing-plans`: required writing owner after preflight. +- `superpowers-writing-plans`: required owner after the repository preflight. -Repository-owned wrapper for retained planning requests. This skill does not -own a local retained-plan protocol or decide the output artifact. +Thin repository wrapper for retained writing. This skill records the local +handoff facts, delegates artifact decisions to `superpowers-writing-plans`, and +stops after the delegated outcome. ## When to use -- Preparing a request for `superpowers-writing-plans` after a short repository - preflight. -- Capturing the smallest target state, anti-scope, nearest owner, validation - path, stop conditions, and observable acceptance before delegation. - -## When not to use - -- Clear, local, quick tasks whose next steps fit in chat and do not need a - retained artifact. -- Substantive ideation before planning; use `internal-gateway-idea-brainstorming`. -- Editing imported `superpowers-*` skills. +- Use after the user approves retained spec or implementation-plan writing. +- Do not use for quick same-chat tasks, substantive ideation, execution, or + imported `superpowers-*` edits. ## Contract -- Run a short preflight before loading `superpowers-writing-plans`. -- Delegate the next action to `superpowers-writing-plans`. -- `superpowers-writing-plans` will decide whether to create a retained plan, - ask a blocking clarification, redirect to a better owner, or stop with a - reason. -- Save created retained plans through the Superpowers default path: - `tmp/superpowers/plans/YYYY-MM-DD-.md`. -- If a retained plan is created, run an `Execution-readiness check` before - claiming completion. -- Stop after the delegated writing outcome and wait for the user's next choice. - -## Preflight - -- `Target`: the smallest target state that satisfies the request. -- `Anti-scope`: tempting work that must stay outside the plan. -- `Nearest owner`: repository owner or skill family that owns the change. -- `Validation path`: concrete validator, review path, or explicit validation gap. -- `Stop conditions`: missing input, unsafe scope, ownership conflict, or - validation failure that must stop execution. -- `Observable acceptance`: diff, file state, validator assertion, manual check, - or explicit non-action that proves the result. - -## Execution-readiness check - -For any created retained plan, verify task order, concrete file targets, clear -edit intent, validation commands or explicit gaps, stop conditions, and -handoff readiness for `superpowers-executing-plans` or -`superpowers-subagent-driven-development`. - -Preserve known-context handoff quality with a short preflight and targeted rereads when the delegation leaves a gap. +1. Capture the preflight: `Target`, `Anti-scope`, `Nearest owner`, + `Validation path`, `Stop conditions`, and `Observable acceptance`. +2. Load `superpowers-writing-plans` and let it create a plan, ask a blocking + clarification, redirect, or stop with a reason. +3. If a retained plan is created, verify execution-readiness: ordered tasks, + concrete file targets, clear edit intent, validation commands or explicit + gaps, stop conditions, and handoff readiness. +4. Stop after the writing outcome and wait for the user's next choice. + +Preserve handoff quality with targeted rereads only when the delegation has a +real evidence gap. diff --git a/.github/skills/internal-gateway-writing-plans/agents/openai.yaml b/.github/skills/internal-gateway-writing-plans/agents/openai.yaml index f150a7b..a9694ed 100644 --- a/.github/skills/internal-gateway-writing-plans/agents/openai.yaml +++ b/.github/skills/internal-gateway-writing-plans/agents/openai.yaml @@ -2,16 +2,9 @@ interface: display_name: "Internal Gateway Writing Plans" short_description: "Repository preflight wrapper for superpowers-writing-plans" default_prompt: >- - Use $internal-gateway-writing-plans as a repository preflight wrapper. - Capture a short preflight with Target, Anti-scope, Nearest owner, - Validation path, Stop conditions, and Observable acceptance, then load - $superpowers-writing-plans and delegate the next action. Let - $superpowers-writing-plans decide whether to create a retained plan, ask a - blocking clarification, redirect to a better owner, or stop with a reason. - Save created retained plans to tmp/superpowers/plans/YYYY-MM-DD-.md. - If a retained plan is created, run an Execution-readiness check for ordered - tasks, concrete file targets, clear edit intent, validation commands or - explicit gaps, stop conditions, and handoff readiness for - $superpowers-executing-plans or $superpowers-subagent-driven-development. - Stop after the delegated writing outcome and wait for the user's next - choice. + Use $internal-gateway-writing-plans to capture Target, Anti-scope, + Nearest owner, Validation path, Stop conditions, and Observable acceptance. + Then load $superpowers-writing-plans and delegate the writing outcome. If a + retained plan is created, check ordered tasks, concrete file targets, edit + intent, validation or explicit gaps, stop conditions, and handoff readiness. + Stop after the writing outcome. From d0e88e0aa8e49212f40197784948a5956635dd57 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 15:07:26 +0200 Subject: [PATCH 37/44] refactor: remove deprecated internal-gateway-review skill and update related documentation --- .github/INVENTORY.md | 1 - .../internal-review-ai-resources.prompt.md | 1 - .github/scripts/benchmark_skill_tokens.py | 6 +-- .../SKILL.md | 19 ++++---- .../agents/openai.yaml | 13 +++-- .../references/compatibility-matrix.md | 5 +- .../references/guided-decision-interview.md | 11 ++--- .../scripts/audit_contract.py | 10 ++-- .../internal-gateway-simple-task/SKILL.md | 20 ++++---- .../agents/openai.yaml | 2 +- .../references/plan-mode.md | 48 ++++++++++--------- .../references/simple-lanes.md | 4 +- 12 files changed, 68 insertions(+), 72 deletions(-) diff --git a/.github/INVENTORY.md b/.github/INVENTORY.md index b6c65ed..7c16be2 100644 --- a/.github/INVENTORY.md +++ b/.github/INVENTORY.md @@ -86,7 +86,6 @@ This file is the exact path inventory for the live GitHub Copilot catalog in thi - `.github/skills/internal-gateway-execute-plans/SKILL.md` - `.github/skills/internal-gateway-idea-brainstorming/SKILL.md` - `.github/skills/internal-gateway-idea/SKILL.md` -- `.github/skills/internal-gateway-review/SKILL.md` - `.github/skills/internal-gateway-simple-task/SKILL.md` - `.github/skills/internal-gateway-writing-plans/SKILL.md` - `.github/skills/internal-gcp-governance/SKILL.md` diff --git a/.github/prompts/internal-review-ai-resources.prompt.md b/.github/prompts/internal-review-ai-resources.prompt.md index 2fe903c..03ad1fb 100644 --- a/.github/prompts/internal-review-ai-resources.prompt.md +++ b/.github/prompts/internal-review-ai-resources.prompt.md @@ -35,7 +35,6 @@ Use these repository sources first: - [.github/INVENTORY.md](../INVENTORY.md) - [INTERNAL_CONTRACT.md](../../INTERNAL_CONTRACT.md) - [.github/agents/internal-gateway-review.agent.md](../agents/internal-gateway-review.agent.md) -- [.github/skills/internal-gateway-review/SKILL.md](../skills/internal-gateway-review/SKILL.md) - [.github/skills/internal-ai-resource-review/SKILL.md](../skills/internal-ai-resource-review/SKILL.md) Then use `internal-ai-resource-review` as the reusable qualitative owner for diff --git a/.github/scripts/benchmark_skill_tokens.py b/.github/scripts/benchmark_skill_tokens.py index e2f7505..2bf7ca6 100644 --- a/.github/scripts/benchmark_skill_tokens.py +++ b/.github/scripts/benchmark_skill_tokens.py @@ -108,7 +108,7 @@ def build_scenario_report(root: Path) -> list[dict[str, Any]]: GATEWAY_SKILL = "internal-gateway-idea-brainstorming" -REVIEW_GATEWAY_SKILL = "internal-gateway-review" +REVIEW_COUNTERCHECK_SKILL = "internal-gateway-critical-master" GATEWAY_REQUIRED_CONTEXT_SCENARIOS: dict[str, list[str]] = { "Direct execute": [GATEWAY_SKILL], @@ -119,8 +119,8 @@ def build_scenario_report(root: Path) -> list[dict[str, Any]]: ], "Plan handoff": [GATEWAY_SKILL, "internal-gateway-writing-plans", "internal-agent-support-next-step"], "Approved apply-plan": [GATEWAY_SKILL, "internal-gateway-execute-plans"], - "Review verdict": [ - REVIEW_GATEWAY_SKILL, "internal-gateway-critical-master", + "Review counter-check": [ + REVIEW_COUNTERCHECK_SKILL, ], } diff --git a/.github/skills/internal-gateway-idea-brainstorming/SKILL.md b/.github/skills/internal-gateway-idea-brainstorming/SKILL.md index 708135b..26918cd 100644 --- a/.github/skills/internal-gateway-idea-brainstorming/SKILL.md +++ b/.github/skills/internal-gateway-idea-brainstorming/SKILL.md @@ -72,7 +72,7 @@ State rules: - At `Interview Gate 1: ready-for-critical`, ask whether to continue before loading `internal-gateway-critical-master`. - `Critical Gate 2` outcomes are: targeted reopen of affected branches, continue-critical, or confident completion. - At `Critical Gate 2: confident`, emit the `Direct Execution vs Retained Plan Recommendation` before any handoff or plan approval question. -- Alias mapping is fixed: `mini-plan` means `compact` and `plan` means `extended`; retained-plan execution strategy is inferred by `internal-gateway-execute-plans`. +- Retained-plan artifact decisions are delegated through `internal-gateway-writing-plans`; this gateway does not choose artifact shape. - At `Handoff Gate 4: plan-created`, set `Continuation: waiting` and do not execute. ## Direct Execution vs Retained Plan Recommendation @@ -80,22 +80,21 @@ State rules: Use this recommendation only after `Critical Gate 2: confident`. Recommend direct execution via `internal-gateway-simple-task` when the scoped -work is concrete, low-to-medium risk, one owner, one lane, one primary -validation path, no cross-turn dependency, and no material context-pressure -signal. Make clear that direct execution still belongs to the specialized -owner; this gateway does not execute it. +work is concrete, one owner, one lane, one primary validation path, no +cross-turn dependency, and low context pressure. Direct execution still +belongs to the specialized owner; this gateway does not execute it. Recommend a retained plan when the user explicitly asked for a plan, the work needs more than roughly 5-7 executable steps, touches more than roughly three unrelated path families, has multiple independent validators, depends on external approvals or pins, carries material context-pressure risk, or must stop -before execution. Recommend `compact` only when one owner, one lane, and one -validation path remain enough; otherwise recommend `extended`. +before execution. Name the decisive signals, then recommend retained writing +through `internal-gateway-writing-plans`. The user-facing message must use this shape: -1. `Recommendation:` direct execution via `internal-gateway-simple-task`, or a - `compact`/`extended` retained plan. +1. `Recommendation:` direct execution via `internal-gateway-simple-task`, or + retained writing via `internal-gateway-writing-plans`. 2. `Why:` one evidence-based sentence naming the decisive signals. 3. `Tradeoff:` direct execution means same-chat specialized work after approval; retained plan means a durable handoff under `tmp/superpowers/` and no @@ -124,5 +123,5 @@ consequence: direct execution handoff or retained-plan authoring. - The gateway keeps `idea -> critical -> retained plan` in one conversation. - Concrete execution requests trigger the specialization checkpoint and do not execute, transfer ownership, or present the post-critical recommendation until `grill-me` and critical both pass. - User insistence does not bypass the `grill-me` or critical gates. -- `internal-gateway-writing-plans` owns profile selection. +- `internal-gateway-writing-plans` owns the repository preflight and delegates artifact decisions. - Execution stays a manual boundary after plan creation. diff --git a/.github/skills/internal-gateway-idea-brainstorming/agents/openai.yaml b/.github/skills/internal-gateway-idea-brainstorming/agents/openai.yaml index 10cd568..f34f711 100644 --- a/.github/skills/internal-gateway-idea-brainstorming/agents/openai.yaml +++ b/.github/skills/internal-gateway-idea-brainstorming/agents/openai.yaml @@ -35,16 +35,15 @@ interface: emit a Direct Execution vs Retained Plan Recommendation with four visible fields: Recommendation, Why, Tradeoff, and Decision. Recommend direct execution via internal-gateway-simple-task when the work is concrete, one - owner, one lane, one validation path, and low context risk. Recommend a - compact or extended retained plan when the user asked for one, the work is - broad enough to retain, validation is multi-surface, or context pressure - could interrupt verified execution. Avoid vague owner-retention phrasing. + owner, one lane, one validation path, and low context risk. Recommend + retained writing through $internal-gateway-writing-plans when the user + asked for one, the work is broad enough to retain, validation is + multi-surface, or context pressure could interrupt verified execution. + Avoid vague owner-retention phrasing. Ask the user to choose execute, plan, or an explicit override. Only if the user chooses plan, emit Plan Approval Gate 3: waiting and ask for explicit go/ok/procedi or equivalent approval before loading $internal-gateway-writing-plans; - otherwise route to the recommended specialized owner and stop. Alias mapping is fixed: mini-plan means - compact and plan means extended; retained-plan execution strategy is - inferred later by internal-gateway-execute-plans. After plan creation, + otherwise route to the recommended specialized owner and stop. After plan creation, emit Handoff Gate 4: plan-created with Continuation: waiting and stop before execution. name: internal-gateway-idea-brainstorming diff --git a/.github/skills/internal-gateway-idea-brainstorming/references/compatibility-matrix.md b/.github/skills/internal-gateway-idea-brainstorming/references/compatibility-matrix.md index 7129dbc..4fee2d8 100644 --- a/.github/skills/internal-gateway-idea-brainstorming/references/compatibility-matrix.md +++ b/.github/skills/internal-gateway-idea-brainstorming/references/compatibility-matrix.md @@ -3,7 +3,8 @@ | Request shape | Owner | | --- | --- | | concrete local execution | `internal-gateway-simple-task` | -| approved compact retained plan | `internal-gateway-execute-plans` | -| approved extended retained plan | `internal-gateway-execute-plans` | +| already-approved legacy compact retained plan | `internal-gateway-execute-plans` | +| already-approved legacy extended retained plan | `internal-gateway-execute-plans` | +| new retained writing request | `internal-gateway-writing-plans` | | defect-first review | `internal-gateway-review` | | unresolved ideation or planning | `internal-gateway-idea-brainstorming` | diff --git a/.github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md b/.github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md index 05b913f..1808e67 100644 --- a/.github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md +++ b/.github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md @@ -19,7 +19,7 @@ Use this reference when `internal-gateway-idea-brainstorming` needs the exact pa 1. After all material branches resolve and Idea Gate 0 is `grill-me satisfied`, summarize the compact decision ledger and declare `Interview Gate 1: ready-for-critical` only when assumptions/defaults are accepted, no ledger contradictions remain, and the validation path is identified. 1. At `Interview Gate 1: ready-for-critical`, ask whether to continue to critical before loading `internal-gateway-critical-master`. 1. When a reopen occurs, resume only the affected branches unless the impact is broad, and declare `Interview Gate 1: reopen`. -1. After `Critical Gate 2: confident`, emit the `Direct Execution vs Retained Plan Recommendation`: `Recommendation`, `Why`, `Tradeoff`, and `Decision`. Recommend direct execution via `internal-gateway-simple-task` when the work is concrete, one owner, one lane, one validation path, and low context risk. Recommend a `compact` or `extended` retained plan when the user asked for one, the work is broad enough to retain, validation is multi-surface, or context pressure could interrupt verified execution. +1. After `Critical Gate 2: confident`, emit the `Direct Execution vs Retained Plan Recommendation`: `Recommendation`, `Why`, `Tradeoff`, and `Decision`. Recommend direct execution via `internal-gateway-simple-task` when the work is concrete, one owner, one lane, one validation path, and low context risk. Recommend retained writing through `internal-gateway-writing-plans` when the user asked for a plan, the work is broad enough to retain, validation is multi-surface, or context pressure could interrupt verified execution. 1. Avoid vague owner-retention phrasing. The user chooses `execute` for specialized direct execution, `plan` for retained-plan authoring, or an explicit override. Only if the user chooses `plan`, declare `Plan Approval Gate 3: waiting` and ask for explicit `go`/`ok`/`procedi` or equivalent approval before loading `internal-gateway-writing-plans`. 1. Only after explicit approval, declare `Plan Approval Gate 3: approved`, create the retained plan, then declare `Handoff Gate 4: plan-created`. 1. Treat `Continuation: waiting` as a handoff lock after `Handoff Gate 4: plan-created`; proposals, alternatives, or wording preferences do not clear the lock without explicit owner/action/scope approval. @@ -53,12 +53,11 @@ Each row in the decision ledger: | `Plan Approval Gate 3: approved` | Explicit plan approval was received. | After explicit `go`/`ok`/`procedi` or equivalent approval. | | `Handoff Gate 4: plan-created` | Retained plan was created and execution is blocked. | After plan creation with `Continuation: waiting` and stop-before-execution behavior. | -## Plan Alias Mapping +## Plan Labels -- `mini-plan` maps to canonical `compact`. -- `plan` maps to canonical `extended`. -- Retained-plan execution strategy is inferred later by `internal-gateway-execute-plans` from profile, folder shape, and validation path. -- Alias labels are conversational only; canonical profile terms remain `compact` and `extended`. +- Conversational plan labels are user intent signals only. +- Artifact profile, folder shape, and writing outcome are delegated through `internal-gateway-writing-plans`. +- Approved retained-plan execution is routed later through `internal-gateway-execute-plans`. ## Proportional Depth diff --git a/.github/skills/internal-gateway-idea-brainstorming/scripts/audit_contract.py b/.github/skills/internal-gateway-idea-brainstorming/scripts/audit_contract.py index 20bb3db..fa1067e 100644 --- a/.github/skills/internal-gateway-idea-brainstorming/scripts/audit_contract.py +++ b/.github/skills/internal-gateway-idea-brainstorming/scripts/audit_contract.py @@ -46,12 +46,10 @@ def main() -> int: "explicit_plan_approval": "go`/`ok`/`procedi" in skill_text and "go`/`ok`/`procedi" in reference_text and "go/ok/procedi" in runtime_text, - "alias_mapping": "mini-plan" in skill_text - and "mini-plan" in reference_text - and "mini-plan" in runtime_text - and "internal-gateway-execute-plans" in skill_text - and "internal-gateway-execute-plans" in reference_text - and "internal-gateway-execute-plans" in runtime_text, + "delegated_writing": "internal-gateway-writing-plans" in skill_text + and "internal-gateway-writing-plans" in reference_text + and "internal-gateway-writing-plans" in runtime_text + and "mini-plan" not in runtime_text, } print(json.dumps({"strict_ok": all(markers.values()), "markers": markers}, indent=2)) return 0 if all(markers.values()) else 1 diff --git a/.github/skills/internal-gateway-simple-task/SKILL.md b/.github/skills/internal-gateway-simple-task/SKILL.md index 87a425d..9e84359 100644 --- a/.github/skills/internal-gateway-simple-task/SKILL.md +++ b/.github/skills/internal-gateway-simple-task/SKILL.md @@ -34,7 +34,7 @@ post-run reporting gates. `references/support-routing.md` remains the single source of truth for claim-gate owners in simple mode. `references/simple-lanes.md` remains the single source of truth for lane definitions, support posture, and validation per lane. -`references/plan-mode.md` remains the single source of truth for plan-mode activation, profile selection, and procedure. +`references/plan-mode.md` remains the single source of truth for plan-mode activation, confirmation, and delegation to `internal-gateway-writing-plans`. Use `scripts/resolve_simple_task.py` when the task facts are already known and the bundle only needs a deterministic gate or claim-gate answer. Use @@ -109,8 +109,8 @@ Classify every simple task before operational work as `full-gate`, - Keep `trivial-skip` only for truly tiny local work with obvious validation and no material completeness risk. - If context pressure could hide required validation, data integrity, or route - ownership, prefer `plan-mode` and apply the `Plan Profile Selection Guard` - before proposing `compact`. + ownership, prefer `plan-mode` and delegate retained writing instead of + stretching same-chat execution. ## Simple Procedure @@ -123,8 +123,8 @@ Classify every simple task before operational work as `full-gate`, `internal-gateway-critical-master` after the user's response. 6. For `trivial-skip`, emit the Trivial-skip proof before operational work. 7. For `plan-mode`, run `grill-me` and `internal-gateway-critical-master` as for - `full-gate`, then load `internal-gateway-writing-plans` and write the retained - plan. Stop before execution. + `full-gate`, then load `internal-gateway-writing-plans` and delegate retained + writing. Stop before execution. 8. Confirm the task still fits one quick lane and choose that lane from `references/simple-lanes.md`. 9. Select only directly applicable skill owners and required references from @@ -145,7 +145,7 @@ Classify every simple task before operational work as `full-gate`, internally before execution; do not emit a default user checklist. 15. Maintain `Direct Execution Control` for non-plan execution. 16. Execute the one concrete lane with the Agentic Execution Loop, or, in - `plan-mode`, write the retained plan and stop before execution. + `plan-mode`, delegate retained writing and stop before execution. 17. Run focused validation or name the explicit gap. 18. Run a pre-close compliance audit over mandatory applicable requirements only. Delegate fresh-evidence mechanics to @@ -220,10 +220,10 @@ items are closed. Plan mode keeps a concrete simple task single-lane and single-phase while writing a retained plan instead of executing. `references/plan-mode.md` owns the -detailed activation, Token Budget Gate, profile, confirmation, procedure, -boundary, and example rules. After `grill-me` and critical review, load -`internal-gateway-writing-plans` and hand off execution to -`internal-gateway-execute-plans`. +detailed activation, Token Budget Gate, confirmation, delegation, boundary, +and example rules. After `grill-me` and critical review, load +`internal-gateway-writing-plans` for retained writing and hand off future +execution to `internal-gateway-execute-plans`. ## Deterministic Helpers diff --git a/.github/skills/internal-gateway-simple-task/agents/openai.yaml b/.github/skills/internal-gateway-simple-task/agents/openai.yaml index d3e03f6..dd44cef 100644 --- a/.github/skills/internal-gateway-simple-task/agents/openai.yaml +++ b/.github/skills/internal-gateway-simple-task/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Internal Gateway Simple Task" short_description: "Fast path for concrete repository tasks" - default_prompt: "Use $internal-gateway-simple-task for a concrete coding or non-coding task that needs quick support-skill selection, direct execution, diagnosis, validation, or plan authoring. For non-plan execution, maintain Direct Execution Control and do not claim DONE, fixed, ready, complete, or validator-passes until all in-scope source items and mandatory applicable requirements are closed with fresh evidence or an explicit gap. Switch to plan mode when the user asks for a plan or when same-chat execution would be less economical than a retained plan, producing a compact or extended retained plan instead of executing. Route approved retained-plan execution to internal-gateway-execute-plans." + default_prompt: "Use $internal-gateway-simple-task for a concrete coding or non-coding task that needs quick support-skill selection, direct execution, diagnosis, validation, or plan authoring. For non-plan execution, maintain Direct Execution Control and do not claim DONE, fixed, ready, complete, or validator-passes until all in-scope source items and mandatory applicable requirements are closed with fresh evidence or an explicit gap. Switch to plan mode when the user asks for a plan or when same-chat execution would be less economical than a retained plan, delegating retained writing to internal-gateway-writing-plans instead of executing. Route approved retained-plan execution to internal-gateway-execute-plans." diff --git a/.github/skills/internal-gateway-simple-task/references/plan-mode.md b/.github/skills/internal-gateway-simple-task/references/plan-mode.md index 0fa6fcd..b5899cb 100644 --- a/.github/skills/internal-gateway-simple-task/references/plan-mode.md +++ b/.github/skills/internal-gateway-simple-task/references/plan-mode.md @@ -51,8 +51,8 @@ and asking for user confirmation. When the cost signals come mainly from context pressure instead of task count, prefer a compact evidence posture rather than a raw-output posture. Keep same-chat -execution only for tiny local work; otherwise switch to `plan-mode` and let the -profile guard choose whether `compact` is still safe. +execution only for tiny local work; otherwise switch to `plan-mode` and let +`internal-gateway-writing-plans` handle retained-writing decisions. A cost checkpoint pauses before a new expensive tool burst, broad reread, or multi-step execution loop. It does not interrupt ordinary conversation, @@ -64,31 +64,33 @@ execution, name the likely token or context impact first and then either continue with the smallest bounded next slice or ask for confirmation before the new expensive burst. -## Profile selection +## Delegated retained writing -- **Default `compact`**: use only when the task stays within a single owner, - concrete target, one primary validation path, one execution lane, and low - completeness risk. Folder name follows `tmp/superpowers/mini-plan-*` and - contains `01-change-summary.md` and `02-execution.md`. -- **Plan Profile Selection Guard**: escalate to `extended` when context or - completeness risk is material, especially for cross-skill token-discipline - work, validator-impacting changes, exports or generated reports, datasets - that need non-trivial reconciliation, several independent validators, an - articulated anti-scope, or external pins that must be tracked in a control - file. +`internal-gateway-simple-task` does not choose retained-plan profile, folder +shape, or artifact internals. It only decides that same-chat execution should +stop and retained writing should begin. -When profile safety is in doubt, prefer `extended` and state why. Prefer -`compact` only when the plan can record the contrary evidence that keeps -lower-context execution safe. +When plan mode is confirmed, pass these facts to `internal-gateway-writing-plans`: + +- concrete target state +- anti-scope +- nearest owner +- validation path or explicit validation gap +- cost signals that made same-chat execution less economical +- stop conditions +- observable acceptance + +`internal-gateway-writing-plans` then delegates artifact decisions to +`superpowers-writing-plans`. ## Confirmation rule for implicit triggers For implicit cost-signal triggers, emit a short statement that: 1. Names the detected cost signals. -2. Proposes `plan-mode` with the safest profile suggested by the signals, - defaulting to `compact` only when the profile guard stays clear. -3. Asks the user to confirm, decline, or choose `extended`. +2. Proposes `plan-mode` and names that retained writing will be delegated to + `internal-gateway-writing-plans`. +3. Asks the user to confirm or decline the switch before writing anything. Do not write the retained plan until the user confirms. @@ -98,8 +100,8 @@ Do not write the retained plan until the user confirms. 2. Run `grill-me` with one compact numbered block. 3. Load `internal-gateway-critical-master` after the user responds. 4. If critical gate returns confident, load `internal-gateway-writing-plans`. -5. Choose the profile and write the retained plan following the - `internal-gateway-writing-plans` contract. +5. Pass the preflight facts to `internal-gateway-writing-plans` and let it + delegate the writing outcome. 6. Stop before execution. Report the plan folder and hand off to `internal-gateway-execute-plans` for future execution. @@ -119,7 +121,7 @@ Do not write the retained plan until the user confirms. - User: *"Aggiungi un nuovo campo al modello e al database"* with 8 files and a migration: classify `plan-mode` implicit, ask confirmation, then write a - `compact` plan. + retained plan through `internal-gateway-writing-plans`. - User: *"Modalità plan: riscrivi il parser dei skills"*: classify `plan-mode` explicit, run the full gate, write the plan, stop. - User: *"Cosa ne pensi di rifattorizzare tutto?"*: this is not concrete; do @@ -129,7 +131,7 @@ Do not write the retained plan until the user confirms. - Treating an implicit cost signal as a decision to write a plan without confirming with the user. -- Choosing `extended` for a task that only needs `compact`. +- Reintroducing retained-plan profile decisions instead of delegating them. - Writing the plan but then executing it inside simple task. - Using plan mode as a way to avoid saying that the task is actually vague or ownership-ambiguous. diff --git a/.github/skills/internal-gateway-simple-task/references/simple-lanes.md b/.github/skills/internal-gateway-simple-task/references/simple-lanes.md index 1a0856a..71f0052 100644 --- a/.github/skills/internal-gateway-simple-task/references/simple-lanes.md +++ b/.github/skills/internal-gateway-simple-task/references/simple-lanes.md @@ -28,8 +28,8 @@ lane or output shape still needs a quick decision. - Ownership ambiguity: stop at `escalate` instead of converting uncertainty into hidden planning. - Plan mode: concrete task that is too large for same-chat execution or - explicitly requested as a plan; classify `plan-mode`, author a `compact` or - `extended` retained plan, and stop before execution. + explicitly requested as a plan; classify `plan-mode`, delegate retained + writing to `internal-gateway-writing-plans`, and stop before execution. - Clarification overflow: use `escalate` when the simple clarification gate would need more than one focused `grill-me` block. From 375d1a53386a8149e5123733e33866248820e19f Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 16:50:44 +0200 Subject: [PATCH 38/44] refactor: remove internal-gateway-idea-brainstorming skill and associated references --- .../SKILL.md | 127 ------------------ .../agents/openai.yaml | 50 ------- .../references/LICENSE.idea-refine-upstream | 21 --- .../references/brief-contract.md | 20 --- .../references/compatibility-matrix.md | 10 -- .../references/guided-decision-interview.md | 78 ----------- .../references/idea-evaluation-criteria.md | 122 ----------------- .../references/idea-shaping-frameworks.md | 103 -------------- .../scripts/audit_contract.py | 59 -------- .../internal-gateway-writing-plans/SKILL.md | 27 +++- INTERNAL_CONTRACT.md | 10 +- LESSONS_LEARNED.md | 6 +- 12 files changed, 31 insertions(+), 602 deletions(-) delete mode 100644 .github/skills/internal-gateway-idea-brainstorming/SKILL.md delete mode 100644 .github/skills/internal-gateway-idea-brainstorming/agents/openai.yaml delete mode 100644 .github/skills/internal-gateway-idea-brainstorming/references/LICENSE.idea-refine-upstream delete mode 100644 .github/skills/internal-gateway-idea-brainstorming/references/brief-contract.md delete mode 100644 .github/skills/internal-gateway-idea-brainstorming/references/compatibility-matrix.md delete mode 100644 .github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md delete mode 100644 .github/skills/internal-gateway-idea-brainstorming/references/idea-evaluation-criteria.md delete mode 100644 .github/skills/internal-gateway-idea-brainstorming/references/idea-shaping-frameworks.md delete mode 100644 .github/skills/internal-gateway-idea-brainstorming/scripts/audit_contract.py diff --git a/.github/skills/internal-gateway-idea-brainstorming/SKILL.md b/.github/skills/internal-gateway-idea-brainstorming/SKILL.md deleted file mode 100644 index 26918cd..0000000 --- a/.github/skills/internal-gateway-idea-brainstorming/SKILL.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -name: internal-gateway-idea-brainstorming -description: Use when a repository-owned request starts with a vague idea, unclear goal, unresolved option set, or needs substantive definition, convergence, critical challenge, and retained planning in the same conversation. ---- - -# Internal Gateway Idea Brainstorming - -## Referenced skills - -Load these skills by name only when the active phase requires them. This list is -an on-demand dependency index. Do not preload them; load only the owner proved -by the active uncertainty or next checkpoint. - -- `grill-me`: guided decision interview. -- `internal-gateway-simple-task`: direct execution owner after a confident handoff when retained planning is not recommended. -- `internal-gateway-review`: defect-first review owner. -- `internal-gateway-critical-master`: mandatory critical challenge owner. -- `internal-gateway-writing-plans`: retained-plan authoring after a confident critical outcome. -- `internal-gateway-execute-plans`: approved retained-plan execution owner. -- `internal-agent-support-next-step`: next-step package formatting. - -Portable skill-first idea gateway. This skill now owns substantive idea work -through retained-plan creation. It stops before execution. - -## When to use - -- The user brings a vague idea, unclear goal, or unresolved option set. -- Brainstorming, clarification, or success criteria are needed before planning. - -## When not to use - -- The target state and validation path are already concrete; use `internal-gateway-simple-task`. -- The primary request is defect-first review; use `internal-gateway-review`. -- A retained plan folder is already approved for execution; route to `internal-gateway-execute-plans` for `compact` or `extended`. - -## Core Invariants - -- Same-conversation support-skill loading is not a lane change. -- Idea Gate 0 remains mandatory. -- Start with a bounded evidence pass ordered by risk. Read only the smallest local owner evidence needed to classify the request before asking questions. -- For large tabular files, generated reports, or long log exports, keep the bounded evidence pass aggregate-first: collect file sizes, schema or headers, counts, anomalies, and targeted slices before any deeper read. -- When authoritative platform semantics control feasibility or ownership, verify them early in the bounded evidence pass. -- This gateway is not a specialized execution owner. A concrete task may not be accepted for execution here until Idea Gate 0 is `grill-me satisfied` and `Critical Gate 2` is `confident`. -- For a direct concrete operation, emit `Specialization Checkpoint: gated`, explain that this owner cannot decide task ownership or execute yet, and continue with the bounded evidence pass plus mandatory `grill-me`. -- User insistence does not bypass Idea Gate 0 or Critical Gate 2. -- Only after `Critical Gate 2: confident` may this gateway present the direct execution vs retained plan recommendation; before that point, do not execute edits, commands, operational steps, or owner handoffs. -- After `Critical Gate 2: confident`, the recommendation must name the concrete consequence: specialized direct execution or retained-plan authoring. -- Do not run critical automatically after convergence; ask the user whether to continue. -- Do not create a retained plan automatically after a confident critical outcome; require explicit `go`/`ok`/`procedi` or equivalent approval. -- Use `internal-gateway-critical-master` before finalizing any substantive definition. -- Keep original intent and emerged requirements as separate tracks; do not rewrite emerged constraints as original user intent. -- After plan approval, load `internal-gateway-writing-plans`, create the retained plan, and stop before execution. - -## State Machine - -1. `Idea Gate 0` -2. `Interview Gate 1` -3. `Critical Gate 2` -4. `Plan Approval Gate 3` -5. `Handoff Gate 4` - -State rules: - -- If the incoming request is already concrete (file edit, command execution, validator run, or implementation step), start with `Specialization Checkpoint: gated` before Idea Gate 0. -- At `Specialization Checkpoint: gated`, name the recommended specialized owner (`internal-gateway-simple-task` by default, `internal-gateway-review` for defect-first review, `internal-gateway-critical-master` for pressure testing), but do not present the post-critical decision yet. -- Continue through the bounded evidence pass, mandatory `grill-me`, and critical gate before presenting the direct execution vs retained plan recommendation. -- Before the initial numbered block, keep large-file and large-log evidence compact: summarize counts, headers, anomalies, routes, and open gaps unless raw content is itself the missing evidence. -- After the evidence pass, load `grill-me` and ask one mandatory numbered bulk question block with recommendations and defaults. -- Before the initial numbered block, emit a compact facts/options summary derived from the bounded evidence pass. -- Ask further focused numbered bulk blocks only for unresolved, dependent, or reopened branches. -- Declare `Interview Gate 1: ready-for-critical` only when material branches are resolved, assumptions/defaults are visible and accepted, no ledger contradictions remain, and the validation path is identified. -- At `Interview Gate 1: ready-for-critical`, ask whether to continue before loading `internal-gateway-critical-master`. -- `Critical Gate 2` outcomes are: targeted reopen of affected branches, continue-critical, or confident completion. -- At `Critical Gate 2: confident`, emit the `Direct Execution vs Retained Plan Recommendation` before any handoff or plan approval question. -- Retained-plan artifact decisions are delegated through `internal-gateway-writing-plans`; this gateway does not choose artifact shape. -- At `Handoff Gate 4: plan-created`, set `Continuation: waiting` and do not execute. - -## Direct Execution vs Retained Plan Recommendation - -Use this recommendation only after `Critical Gate 2: confident`. - -Recommend direct execution via `internal-gateway-simple-task` when the scoped -work is concrete, one owner, one lane, one primary validation path, no -cross-turn dependency, and low context pressure. Direct execution still -belongs to the specialized owner; this gateway does not execute it. - -Recommend a retained plan when the user explicitly asked for a plan, the work -needs more than roughly 5-7 executable steps, touches more than roughly three -unrelated path families, has multiple independent validators, depends on -external approvals or pins, carries material context-pressure risk, or must stop -before execution. Name the decisive signals, then recommend retained writing -through `internal-gateway-writing-plans`. - -The user-facing message must use this shape: - -1. `Recommendation:` direct execution via `internal-gateway-simple-task`, or - retained writing via `internal-gateway-writing-plans`. -2. `Why:` one evidence-based sentence naming the decisive signals. -3. `Tradeoff:` direct execution means same-chat specialized work after approval; - retained plan means a durable handoff under `tmp/superpowers/` and no - execution in this conversation. -4. `Decision:` ask the user to choose `execute`, `plan`, or an explicit - override. If `plan` is chosen, require `go`/`ok`/`procedi` or equivalent - approval before loading `internal-gateway-writing-plans`. - -Avoid vague owner-retention phrasing. The message must name the concrete -consequence: direct execution handoff or retained-plan authoring. - -## Flow - -1. Specialization checkpoint -2. Discover -3. Converge -4. Ask before critical -5. Critical -6. Direct execution vs retained plan recommendation -7. Plan approval -8. Plan creation -9. Stop before execution - -## Validation - -- The gateway keeps `idea -> critical -> retained plan` in one conversation. -- Concrete execution requests trigger the specialization checkpoint and do not execute, transfer ownership, or present the post-critical recommendation until `grill-me` and critical both pass. -- User insistence does not bypass the `grill-me` or critical gates. -- `internal-gateway-writing-plans` owns the repository preflight and delegates artifact decisions. -- Execution stays a manual boundary after plan creation. diff --git a/.github/skills/internal-gateway-idea-brainstorming/agents/openai.yaml b/.github/skills/internal-gateway-idea-brainstorming/agents/openai.yaml deleted file mode 100644 index f34f711..0000000 --- a/.github/skills/internal-gateway-idea-brainstorming/agents/openai.yaml +++ /dev/null @@ -1,50 +0,0 @@ -interface: - display_name: "Internal Gateway Idea Brainstorming" - short_description: "Idea definition and validated handoff before planning" - default_prompt: >- - Use $internal-gateway-idea-brainstorming when a repository-owned request - starts with a vague idea, unclear goal, or unresolved option set. This - owner is not a specialized execution lane. If the incoming request is - already concrete (file edit, command run, validator run, or direct - implementation), emit Specialization Checkpoint: gated. Explain that this - owner cannot decide task ownership or execute the task yet. Name the - recommended specialized owner explicitly: default to - internal-gateway-simple-task, use internal-gateway-review for defect-first - review, and internal-gateway-critical-master for pressure testing. User - preference does not bypass mandatory grill-me and critical gates. Do not - execute edits, commands, operational steps, or owner handoffs yet; - continue with a bounded evidence pass first using the smallest - risk-ordered local evidence. When authoritative platform semantics control - feasibility or ownership, verify them early in that bounded evidence pass. - Keep original intent and emerged requirements as separate tracks; do not - rewrite emerged constraints as original intent. Before asking the initial - numbered block, emit a compact facts/options summary grounded in evidence. - Then load $grill-me for mandatory Idea Gate 0 and immediately ask one - numbered question block with recommendations and rationale. Treat visible - defaults as accepted unless the user overrides them by number. Ask focused - follow-up blocks only for unresolved, dependent, or reopened branches. Do - not converge, recommend simple-task, recommend planning, hand off, or - present the Direct Execution vs Retained Plan Recommendation until Idea - Gate 0 is grill-me satisfied and Critical Gate 2 is confident. After convergence, emit - Interview Gate 1: ready-for-critical only when material branches are - resolved, assumptions/defaults are accepted, no ledger contradictions - remain, and the validation path is identified. At Interview Gate 1: ready-for-critical, ask whether to continue before loading - $internal-gateway-critical-master. In Critical Gate 2, either reopen - targeted branches for another focused grill-me block, continue critical, - or conclude Critical Gate 2: confident. After Critical Gate 2: confident, - emit a Direct Execution vs Retained Plan Recommendation with four visible - fields: Recommendation, Why, Tradeoff, and Decision. Recommend direct - execution via internal-gateway-simple-task when the work is concrete, one - owner, one lane, one validation path, and low context risk. Recommend - retained writing through $internal-gateway-writing-plans when the user - asked for one, the work is broad enough to retain, validation is - multi-surface, or context pressure could interrupt verified execution. - Avoid vague owner-retention phrasing. - Ask the user to choose execute, plan, or an explicit override. Only if the user chooses plan, - emit Plan Approval Gate 3: waiting and ask for explicit go/ok/procedi or - equivalent approval before loading $internal-gateway-writing-plans; - otherwise route to the recommended specialized owner and stop. After plan creation, - emit Handoff Gate 4: plan-created with Continuation: waiting and stop - before execution. -name: internal-gateway-idea-brainstorming -description: Use when a repository-owned request starts with a vague idea, unclear goal, unresolved option set, or needs substantive definition, convergence, or validated handoff before operational planning or simple execution. diff --git a/.github/skills/internal-gateway-idea-brainstorming/references/LICENSE.idea-refine-upstream b/.github/skills/internal-gateway-idea-brainstorming/references/LICENSE.idea-refine-upstream deleted file mode 100644 index d67778a..0000000 --- a/.github/skills/internal-gateway-idea-brainstorming/references/LICENSE.idea-refine-upstream +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Addy Osmani - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/.github/skills/internal-gateway-idea-brainstorming/references/brief-contract.md b/.github/skills/internal-gateway-idea-brainstorming/references/brief-contract.md deleted file mode 100644 index b5ae1bf..0000000 --- a/.github/skills/internal-gateway-idea-brainstorming/references/brief-contract.md +++ /dev/null @@ -1,20 +0,0 @@ -# Brief Contract - -Produced at the end of convergence and consumed by the same gateway before -retained-plan creation or by the next approved execution owner after planning. - -## Required Fields - -- Outcome -- Owner -- Success criteria -- Constraints -- Anti-scope -- Direction -- Validation path or explicit gap -- Stop conditions - -## Handoff Expectations - -- Keep the brief compact, but complete enough to hand to the next approved owner without re-opening the recovered intent. -- Record the next approved owner and any cleared handoff lock when the brief feeds retained-plan creation. diff --git a/.github/skills/internal-gateway-idea-brainstorming/references/compatibility-matrix.md b/.github/skills/internal-gateway-idea-brainstorming/references/compatibility-matrix.md deleted file mode 100644 index 4fee2d8..0000000 --- a/.github/skills/internal-gateway-idea-brainstorming/references/compatibility-matrix.md +++ /dev/null @@ -1,10 +0,0 @@ -# Compatibility Matrix - -| Request shape | Owner | -| --- | --- | -| concrete local execution | `internal-gateway-simple-task` | -| already-approved legacy compact retained plan | `internal-gateway-execute-plans` | -| already-approved legacy extended retained plan | `internal-gateway-execute-plans` | -| new retained writing request | `internal-gateway-writing-plans` | -| defect-first review | `internal-gateway-review` | -| unresolved ideation or planning | `internal-gateway-idea-brainstorming` | diff --git a/.github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md b/.github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md deleted file mode 100644 index 1808e67..0000000 --- a/.github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md +++ /dev/null @@ -1,78 +0,0 @@ -# Guided Decision Interview - -Use this reference when `internal-gateway-idea-brainstorming` needs the exact pacing, decision-ledger fields, and checkpoint states for its native guided decision interview. - -## Pacing Rules - -1. If the request is already concrete (file edit, command run, validator run, or direct implementation), emit `Specialization Checkpoint: gated`, explain that this owner cannot decide task ownership or execute yet, name the recommended specialized owner for later, and continue with the bounded evidence pass plus mandatory Idea Gate 0. -1. Run a bounded evidence pass before asking the user. Use the smallest risk-ordered repository evidence first (files, paths, commands, and existing decisions). -1. When authoritative platform semantics control the decision, verify them early inside the bounded evidence pass. -1. Maintain a compact decision ledger ordered by dependency. Resolve prerequisite branches before dependent branches. -1. Before the first numbered block, emit a compact facts/options summary grounded in the bounded evidence pass. -1. Load `grill-me`, then immediately ask one mandatory Idea Gate 0 numbered question block. Order unresolved decisions by dependency, and include at least one human confirmation question when repository evidence appears complete. -1. For each numbered question, include a recommendation, rationale, and explicit default when useful. -1. Treat visible recommendations as accepted unless the user overrides them by question number or gives different direction. -1. Record accepted defaults and overrides in the decision ledger before advancing. -1. Preserve intent traceability in the ledger by keeping original intent and emerged requirements separate; do not restate emerged constraints as original intent. -1. After the user's bulk response, declare Idea Gate 0 as `grill-me required` or `grill-me satisfied`. Ask another numbered question block only for unresolved ambiguity, dependent follow-up decisions, or reopened branches. -1. Do not treat branch confirmations as phase-transition checkpoints. -1. After all material branches resolve and Idea Gate 0 is `grill-me satisfied`, summarize the compact decision ledger and declare `Interview Gate 1: ready-for-critical` only when assumptions/defaults are accepted, no ledger contradictions remain, and the validation path is identified. -1. At `Interview Gate 1: ready-for-critical`, ask whether to continue to critical before loading `internal-gateway-critical-master`. -1. When a reopen occurs, resume only the affected branches unless the impact is broad, and declare `Interview Gate 1: reopen`. -1. After `Critical Gate 2: confident`, emit the `Direct Execution vs Retained Plan Recommendation`: `Recommendation`, `Why`, `Tradeoff`, and `Decision`. Recommend direct execution via `internal-gateway-simple-task` when the work is concrete, one owner, one lane, one validation path, and low context risk. Recommend retained writing through `internal-gateway-writing-plans` when the user asked for a plan, the work is broad enough to retain, validation is multi-surface, or context pressure could interrupt verified execution. -1. Avoid vague owner-retention phrasing. The user chooses `execute` for specialized direct execution, `plan` for retained-plan authoring, or an explicit override. Only if the user chooses `plan`, declare `Plan Approval Gate 3: waiting` and ask for explicit `go`/`ok`/`procedi` or equivalent approval before loading `internal-gateway-writing-plans`. -1. Only after explicit approval, declare `Plan Approval Gate 3: approved`, create the retained plan, then declare `Handoff Gate 4: plan-created`. -1. Treat `Continuation: waiting` as a handoff lock after `Handoff Gate 4: plan-created`; proposals, alternatives, or wording preferences do not clear the lock without explicit owner/action/scope approval. - -## Decision Ledger Fields - -Each row in the decision ledger: - -| Field | Purpose | -| --- | --- | -| `Branch` | The decision being resolved. | -| `Status` | `open`, `resolved`, `reopened`. | -| `Evidence` | Repository facts, file states, or commands that inform the branch. | -| `Recommendation` | The recommended answer and rationale. | -| `User answer` | The explicit user reply, or `pending`. | -| `Impact` | `local` or `broad`; used during reopen to decide branch scope. | -| `Gate` | The active gate affected by the branch: `Idea Gate 0`, `Interview Gate 1`, `Critical Gate 2`, `Plan Approval Gate 3`, or `Handoff Gate 4`. | - -## Checkpoint States - -| State | Meaning | When to declare | -| --- | --- | --- | -| `Specialization Checkpoint: gated` | The incoming ask is concrete, but this owner may not decide ownership or execute until `grill-me` and critical both pass. | Before Idea Gate 0 when the user asks for direct execution-oriented work. | -| `Idea Gate 0: grill-me required` | Mandatory human confirmation loop is open. | After the evidence pass and before convergence, simple-task recommendation, planning recommendation, or handoff. | -| `Idea Gate 0: grill-me satisfied` | User answered or explicitly accepted defaults for the current request, scope, context, and evidence. | Before convergence or any simple-task/planning recommendation. | -| `Interview Gate 1: ready-for-critical` | All material branches resolved; waiting for critical challenge. | After decision-ledger summary and before loading `internal-gateway-critical-master`. | -| `Interview Gate 1: reopen` | Critical pass or realignment reopened one or more branches. | When resuming affected branches after `reopen`. | -| `Critical Gate 2: confident` | Critical challenge found the Definition Brief fit for handoff and unlocked the direct execution vs retained plan recommendation. | Before asking the user to choose `execute`, `plan`, or an explicit override. | -| `Critical Gate 2: reopen` | Critical challenge found material issues. | Before returning to affected branches in `discover`. | -| `Plan Approval Gate 3: waiting` | Critical is confident, the user chose retained-plan authoring, and planning approval is pending. | Immediately after `Critical Gate 2: confident` and after the user chooses `plan`. | -| `Plan Approval Gate 3: approved` | Explicit plan approval was received. | After explicit `go`/`ok`/`procedi` or equivalent approval. | -| `Handoff Gate 4: plan-created` | Retained plan was created and execution is blocked. | After plan creation with `Continuation: waiting` and stop-before-execution behavior. | - -## Plan Labels - -- Conversational plan labels are user intent signals only. -- Artifact profile, folder shape, and writing outcome are delegated through `internal-gateway-writing-plans`. -- Approved retained-plan execution is routed later through `internal-gateway-execute-plans`. - -## Proportional Depth - -- Concrete direct asks must pass `Specialization Checkpoint: gated` first. User insistence does not bypass Idea Gate 0 or Critical Gate 2. -- Simple recoverable cases may close after a small number of questions, but they still require Idea Gate 0. -- Unresolved dependent branches continue in focused numbered follow-up blocks. -- Ask the initial numbered question block immediately after the evidence pass. -- Make accepted defaults visible in the decision ledger. -- Do not ask questions that repository evidence already answers. - -## grill-me Boundary - -- Use `grill-me` for mandatory Idea Gate 0 after every evidence pass. -- Inherit `grill-me`'s numbered initial question block and default-acceptance behavior. -- Declare the caller-owned override for iterative numbered follow-up blocks when using `grill-me` for branch discovery. -- Keep the interview iterative by asking focused follow-up blocks when later branches remain. -- Do not let a concrete-task request or owner preference bypass the `grill-me` boundary. -- Do not skip `grill-me` just because files, docs, or local evidence appear sufficient. Use evidence to reduce the questions, then ask the human to confirm the recovered direction. diff --git a/.github/skills/internal-gateway-idea-brainstorming/references/idea-evaluation-criteria.md b/.github/skills/internal-gateway-idea-brainstorming/references/idea-evaluation-criteria.md deleted file mode 100644 index f22116b..0000000 --- a/.github/skills/internal-gateway-idea-brainstorming/references/idea-evaluation-criteria.md +++ /dev/null @@ -1,122 +0,0 @@ -# Refinement and Evaluation Criteria - -Adapted from `addyosmani/agent-skills` release `0.6.1`. See `LICENSE.idea-refine-upstream`. - -Use this rubric during Phase 2 (Evaluate and Converge) to stress-test idea directions. Not every criterion applies to every idea, so use judgment about which dimensions matter most for the specific context. - -## Core Evaluation Dimensions - -### 1. User Value - -This is the most important dimension. If the value is not clear, nothing else matters. - -**Painkiller vs. Vitamin:** - -- **Painkiller:** Solves an acute, frequent problem. Users will actively seek this out, switch from their current solution, and often describe the problem with emotion. -- **Vitamin:** Nice to have. Makes something marginally better, but users do not go out of their way to adopt it. - -**Questions to ask:** - -- Can you name three specific people who have this problem right now? -- What are they doing today instead? The real competitor is usually the current workaround. -- Would they switch from their current approach? What would make them switch? -- How often do they encounter this problem? -- Is this a pull problem or a push problem? - -**Red flags:** - -- "Everyone could use this" - if you cannot name a specific user, the value is not clear. -- "It is like X but better" - marginal improvements rarely drive adoption. -- The problem is real but rare - high intensity and low frequency rarely justify a product. - -### 2. Feasibility - -Can you actually build this, not just technically but practically? - -**Technical feasibility:** - -- Does the core technology exist and work reliably? -- What is the hardest technical problem? Is it known-hard or novel? -- Are there dependencies on third parties, APIs, or data sources you do not control? -- What is the minimum technical stack needed? - -**Resource feasibility:** - -- What is the minimum team or effort needed to build an MVP? -- Does it require specialized expertise you do not have? -- Are there regulatory, legal, or compliance requirements? - -**Time-to-value:** - -- How quickly can you get something in front of users? -- Is there a version that delivers value in days or weeks, not months? -- What is the critical path? What has to happen first? - -**Red flags:** - -- "We just need to solve [very hard research problem] first." -- Multiple dependencies that all need to work simultaneously. -- The MVP still requires months of work. - -### 3. Differentiation - -What makes this genuinely different, not just better? - -**Questions to ask:** - -- If a user described this to a friend, what would they say? Is that description compelling? -- What is the one thing this does that nothing else does? -- Is this differentiation durable? Can a competitor copy it in a week? -- Is the difference something users actually care about or just something builders find interesting? - -**Types of differentiation (strongest to weakest):** - -1. **New capability:** Does something that was previously impossible. -2. **10x improvement:** So much better on a key dimension that it changes behavior. -3. **New audience:** Brings an existing capability to people who were excluded. -4. **New context:** Works in a situation where existing solutions fail. -5. **Better UX:** Same capability, dramatically simpler experience. -6. **Cheaper:** Same thing, lower cost. - -**Red flags:** - -- Differentiation is entirely about technology, not user experience. -- "We are faster, cheaper, prettier" without a structural reason why. -- The feature that differentiates is not the feature users care most about. - -## Assumption Audit - -For every idea direction, explicitly list assumptions in three categories. - -### Must Be True (Dealbreakers) - -Assumptions that, if wrong, kill the idea entirely. These need validation before building. - -### Should Be True (Important) - -Assumptions that significantly impact success but do not kill the idea. You can adjust the approach if they are wrong. - -### Might Be True (Nice to Have) - -Assumptions about secondary features or optimizations. Do not validate these until the core is proven. - -## Decision Framework - -When choosing between directions, rank them on this matrix: - -| | High Feasibility | Low Feasibility | -|--------------------|------------------|-----------------| -| **High Value** | Do this first | Worth the risk | -| **Low Value** | Only if trivial | Do not do this | - -Use differentiation as the tiebreaker between options in the same quadrant. - -## MVP Scoping Principles - -When defining MVP scope for the chosen direction: - -1. **One job, done well.** The MVP should nail exactly one user job. -2. **The riskiest assumption first.** The MVP should test the assumption most likely to be wrong. -3. **Time-box, not feature-list.** Ask what you can build and test in a fixed timeframe. -4. **The "Not Doing" list is mandatory.** Explicitly name what you are cutting and why. -5. **If it is not embarrassing, you waited too long.** The first version should feel incomplete to the builder. diff --git a/.github/skills/internal-gateway-idea-brainstorming/references/idea-shaping-frameworks.md b/.github/skills/internal-gateway-idea-brainstorming/references/idea-shaping-frameworks.md deleted file mode 100644 index c89c870..0000000 --- a/.github/skills/internal-gateway-idea-brainstorming/references/idea-shaping-frameworks.md +++ /dev/null @@ -1,103 +0,0 @@ -# Ideation Frameworks Reference - -Adapted from `addyosmani/agent-skills` release `0.6.1`. See `LICENSE.idea-refine-upstream`. - -Use these frameworks selectively. Pick the lens that fits the idea - do not mechanically run every framework. The goal is to unlock thinking, not to follow a checklist. - -## SCAMPER - -A structured way to transform an existing idea by applying seven different operations: - -- **Substitute:** What component, material, or process could you swap out? What if you replaced the core technology, the target audience, or the business model? -- **Combine:** What if you merged this with another product, service, or idea? What two things that do not usually go together would create something new? -- **Adapt:** What else is like this? What ideas from other industries, domains, or time periods could you borrow? What parallel exists in nature? -- **Modify (Magnify/Minimize):** What if you made it 10x bigger or 10x smaller? What if you exaggerated one feature? What if you stripped it to the absolute minimum? -- **Put to other uses:** Who else could use this? What other problems could it solve? What happens if you use it in a completely different context? -- **Eliminate:** What happens if you remove a feature entirely? What is the version with zero configuration? What would it look like with half the steps? -- **Reverse/Rearrange:** What if you did the steps in the opposite order? What if the user did the work instead of the system, or vice versa? What if you reversed the value chain? - -**Best for:** Improving or reimagining existing products or features. Less useful for greenfield ideas. - -## How Might We (HMW) - -Reframe problems as opportunities using the "How Might We..." format: - -- Start with an observation or pain point. -- Reframe it as "How might we [desired outcome] for [specific user] without [key constraint]?" -- Generate multiple HMW framings of the same problem because different framings unlock different solutions. - -**Good HMW qualities:** - -- Narrow enough to be actionable, such as helping new users find relevant content in their first five minutes. -- Broad enough to allow creative solutions instead of embedding a single implementation. -- Contains a tension or constraint that forces creativity. - -**Bad HMW qualities:** - -- Too broad: "How might we make users happy?" -- Too narrow: "How might we add a button to the settings page?" -- Solution-embedded: "How might we build a chatbot for support?" - -**Best for:** Reframing stuck thinking. When someone is anchored on a solution, pull them back to the problem. - -## First Principles Thinking - -Break the idea down to its fundamental truths, then rebuild from there: - -1. **What do we know is true?** Not assumed, not conventional - actually true. -2. **What are we assuming?** List every assumption, even the ones that feel obvious. -3. **Which assumptions can we challenge?** For each, ask whether it is a law of physics or just how things have been done. -4. **Rebuild from the truths.** If you only had the fundamental truths, what would you build? - -**Best for:** Breaking out of incremental thinking when every idea feels like a small improvement on the status quo. - -## Jobs to Be Done (JTBD) - -Focus on what the user is trying to accomplish, not what they say they want: - -- **Functional job:** What task are they trying to complete? -- **Emotional job:** How do they want to feel? -- **Social job:** How do they want to be perceived? - -Format: "When I [situation], I want to [motivation], so I can [expected outcome]." - -**Key insight:** People do not buy products - they hire them to do a job. The competing product is not always in the same category. - -**Best for:** Understanding the real problem when you are not sure whether you are solving the right thing. - -## Constraint-Based Ideation - -Deliberately impose constraints to force creative solutions: - -- **Time constraint:** "What if you only had one day to build this?" -- **Feature constraint:** "What if it could only have one feature?" -- **Tech constraint:** "What if you could not use [the obvious technology]?" -- **Cost constraint:** "What if it had to be free forever?" -- **Audience constraint:** "What if your user had never used a computer before?" -- **Scale constraint:** "What if it needed to work for one billion users? What about just 10?" - -**Best for:** Cutting through complexity when the idea is growing too large or too vague. - -## Pre-mortem - -Imagine the idea has already failed. Work backwards: - -1. It is 12 months from now. The project shipped and flopped. What went wrong? -2. List every plausible reason for failure - technical, market, team, timing. -3. For each failure mode, ask whether it is preventable and whether it signals the idea needs to change. -4. Decide which failure modes you are willing to accept and which would kill the project. - -**Best for:** Phase 2 evaluation. Stress-testing ideas that feel good but have not been pressure-tested. - -## Analogous Inspiration - -Look at how other domains solved similar problems: - -- What industry has already solved a version of this problem? -- What would this look like if a specific company or product built it? -- What natural system works this way? -- What historical precedent exists? - -The key is finding structural similarities, not surface-level ones. - -**Best for:** Phase 1 expansion. Generating variations that feel genuinely different from the obvious approach. diff --git a/.github/skills/internal-gateway-idea-brainstorming/scripts/audit_contract.py b/.github/skills/internal-gateway-idea-brainstorming/scripts/audit_contract.py deleted file mode 100644 index fa1067e..0000000 --- a/.github/skills/internal-gateway-idea-brainstorming/scripts/audit_contract.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -"""Minimal audit helper for internal-gateway-idea-brainstorming.""" - -from __future__ import annotations - -import json -from pathlib import Path - - -def main() -> int: - bundle_dir = Path(__file__).resolve().parent.parent - skill_text = (bundle_dir / "SKILL.md").read_text(encoding="utf-8") - reference_text = (bundle_dir / "references" / "guided-decision-interview.md").read_text( - encoding="utf-8" - ) - runtime_text = (bundle_dir / "agents" / "openai.yaml").read_text(encoding="utf-8") - markers = { - "critical": "internal-gateway-critical-master" in skill_text, - "planning": "internal-gateway-writing-plans" in skill_text, - "stop_before_execution": "stop before execution" in skill_text, - "specialization_gated": "Specialization Checkpoint: gated" in skill_text - and "Specialization Checkpoint: gated" in reference_text - and "Specialization Checkpoint: gated" in runtime_text, - "bounded_evidence_pass": "bounded evidence pass" in skill_text - and "bounded evidence pass" in reference_text - and "bounded evidence pass" in runtime_text, - "facts_options_summary": "facts/options summary" in skill_text - and "facts/options summary" in reference_text - and "facts/options" in runtime_text - and "summary grounded in evidence" in runtime_text, - "intent_traceability": "original intent and emerged requirements" in skill_text - and "original intent and emerged requirements" in reference_text - and "original intent and emerged requirements" in runtime_text, - "plan_approval_gate": "Plan Approval Gate 3" in skill_text - and "Plan Approval Gate 3" in reference_text, - "handoff_gate_4": "Handoff Gate 4" in skill_text and "Handoff Gate 4" in reference_text, - "ask_before_critical": "ask whether to continue" in skill_text - and "ask whether to continue" in reference_text - and "ask whether to continue" in runtime_text, - "direct_vs_plan_recommendation": "Direct Execution vs Retained Plan Recommendation" in skill_text - and "Direct Execution vs Retained Plan Recommendation" in reference_text - and "Direct Execution vs Retained Plan Recommendation" in runtime_text - and "choose `execute`, `plan`, or an explicit" in skill_text - and "chooses `execute`" in reference_text - and "choose execute, plan, or an explicit override" in runtime_text, - "explicit_plan_approval": "go`/`ok`/`procedi" in skill_text - and "go`/`ok`/`procedi" in reference_text - and "go/ok/procedi" in runtime_text, - "delegated_writing": "internal-gateway-writing-plans" in skill_text - and "internal-gateway-writing-plans" in reference_text - and "internal-gateway-writing-plans" in runtime_text - and "mini-plan" not in runtime_text, - } - print(json.dumps({"strict_ok": all(markers.values()), "markers": markers}, indent=2)) - return 0 if all(markers.values()) else 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.github/skills/internal-gateway-writing-plans/SKILL.md b/.github/skills/internal-gateway-writing-plans/SKILL.md index 9aafbc4..f704b15 100644 --- a/.github/skills/internal-gateway-writing-plans/SKILL.md +++ b/.github/skills/internal-gateway-writing-plans/SKILL.md @@ -24,11 +24,30 @@ stops after the delegated outcome. 1. Capture the preflight: `Target`, `Anti-scope`, `Nearest owner`, `Validation path`, `Stop conditions`, and `Observable acceptance`. 2. Load `superpowers-writing-plans` and let it create a plan, ask a blocking - clarification, redirect, or stop with a reason. -3. If a retained plan is created, verify execution-readiness: ordered tasks, - concrete file targets, clear edit intent, validation commands or explicit - gaps, stop conditions, and handoff readiness. + clarification, redirect, or stop with a reason. Pass an explicit anti-scope + and the list of existing owners so the delegated plan avoids duplicate or + speculative tasks at the source. +3. If a retained plan is created, verify execution-readiness and apply Plan + Authoring Discipline: ordered tasks, concrete file targets, clear edit + intent, validation commands or explicit gaps, stop conditions, and handoff + readiness. Reject the draft if any task duplicates an existing owner, adds + speculative scope, or lacks a validation path. 4. Stop after the writing outcome and wait for the user's next choice. Preserve handoff quality with targeted rereads only when the delegation has a real evidence gap. + +## Plan Authoring Discipline + +- Owner-first: before the delegated plan adds a task, confirm no existing + owner, skill, or validator already covers that responsibility; prefer a + reference over a duplicate. +- Single responsibility: each task must carry one clear deliverable tied to + the approved target; split or merge tasks that don't. +- Fail-fast and redirect: if the delegated plan adds speculative scope, + duplicates an existing owner, or lacks a validation path, send it back for + revision instead of accepting it. + +DRY, YAGNI, and TDD stay owned by `superpowers-writing-plans`; this section +adds only the owner-awareness and redirect gate that the delegated skill does +not enforce. diff --git a/INTERNAL_CONTRACT.md b/INTERNAL_CONTRACT.md index b88800e..ab740b1 100644 --- a/INTERNAL_CONTRACT.md +++ b/INTERNAL_CONTRACT.md @@ -83,7 +83,7 @@ Treat the current skill-first architecture as the source of truth. Do not infer - Goal: keep owner selection deterministic and visible without creating a hidden router. - Scope: - `AGENTS.md` - - `.github/skills/internal-gateway-idea-brainstorming/**` + - `.github/skills/internal-gateway-idea/**` - `.github/skills/internal-gateway-review/**` - `.github/skills/internal-gateway-simple-task/**` - `.github/skills/internal-gateway-execute-plans/**` @@ -117,7 +117,7 @@ Treat the current skill-first architecture as the source of truth. Do not infer - `docs/repository-context.md`, `docs/architecture.md`, `docs/tech.md`, and `docs/structure.md` are mandatory consumer-local knowledge documents scaffolded only when missing and preserved after creation - `docs/README.md` explains knowledge-document roles and routing without becoming a policy owner - each knowledge document remains descriptive and cannot override binding policy - - runtime workflow and context-loading guidance lives in relevant skills, especially `internal-gateway-idea-brainstorming`, `internal-gateway-review`, `internal-gateway-simple-task`, and `internal-gateway-execute-plans` + - runtime workflow and context-loading guidance lives in relevant skills, especially `internal-gateway-idea`, `internal-gateway-review`, `internal-gateway-simple-task`, and `internal-gateway-execute-plans` - the retired `docs/03-local-ai-runtime-operating-model.md` file is not recreated or synchronized into consumers - sync automation preserves existing consumer-local knowledge documents after initial scaffold creation and blocks ambiguous legacy coexistence - `.github/templates/` remains source-side scaffold material and is not mirrored as a target catalog family @@ -255,11 +255,11 @@ Treat the current skill-first architecture as the source of truth. Do not infer - canonical operational wrapper agents - shared operating-model skills - Expected behavior: - - `internal-gateway-idea-brainstorming`, `internal-gateway-review`, `internal-gateway-simple-task`, and `internal-gateway-critical-master` remain the canonical repository-owned skill-first gateway core - - `internal-gateway-idea-brainstorming`, `internal-gateway-review`, `internal-gateway-simple-task`, and `internal-gateway-critical-master` remain the current Copilot wrapper entrypoints for that core + - `internal-gateway-idea`, `internal-gateway-review`, `internal-gateway-simple-task`, and `internal-gateway-critical-master` remain the canonical repository-owned skill-first gateway core + - `internal-gateway-idea`, `internal-gateway-review`, `internal-gateway-simple-task`, and `internal-gateway-critical-master` remain the current Copilot wrapper entrypoints for that core - the default operational model uses direct owner selection or user-selected gateway skills with visible phases instead of a hidden repository-owned front-door router - retained execution stays separate: `internal-gateway-simple-task` consumes approved `compact` plans and `internal-gateway-execute-plans` consumes approved `extended` plans - - ambiguous or mixed-shape entry fails safe to `internal-gateway-idea-brainstorming` + - ambiguous or mixed-shape entry fails safe to `internal-gateway-idea` - unclear target state and multiple credible paths are explicit planning triggers - wrapper owners define boundaries and recommendations instead of active delegation - wrapper owners are not subagent-invoked by default, so hidden peer dispatch stays opt-in and explicit diff --git a/LESSONS_LEARNED.md b/LESSONS_LEARNED.md index 6a008ea..aca621b 100644 --- a/LESSONS_LEARNED.md +++ b/LESSONS_LEARNED.md @@ -51,9 +51,9 @@ This file retains durable lessons discovered while completing tasks in this repo | 2026-05-16 | In retained writing-style plans, broad candidate-scope bullets are not execution authority; only exact `ready-copyedit` manifest rows authorize edits. If the intended pass includes agents, skills, or instructions, expand the manifest with exact rows before delivery instead of inferring scope from earlier plan sections. | Pending | `.github/skills/internal-gateway-writing-plans/SKILL.md` | | 2026-05-16 | When reporting measurable prose improvements, compare before and after with the same measurement script and tokenization rules; mixed measurement methods can make averages incomparable even when line length and sentence length clearly improved. | Pending | `.github/skills/internal-gateway-execute-plans/SKILL.md` | | 2026-05-16 | Markdown list-continuation wrapping must use spaces, not hard tabs; `markdownlint` and editor diagnostics raise `MD010/no-hard-tabs` on tab-indented continuation lines even when the prose edit is otherwise correct. | Pending | `.github/skills/internal-markdown/SKILL.md` | -| 2026-06-13 | During idea discovery, bound the initial evidence pass to the smallest decision-relevant owners and explicit output budgets; expand only when an unresolved branch needs more evidence, because broad catalog searches can consume more context than the decisions they inform. | Pending | `.github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md` | -| 2026-06-13 | Before opening a long guided interview, resolve the highest-leverage framing choice first, such as complement versus canonical replacement; derive dependent defaults from that answer instead of asking every downstream decision independently. | Pending | `.github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md` | -| 2026-06-13 | After the initial decision ledger is accepted, report only new overrides, contradictions, reopened branches, and remaining open decisions; repeatedly restating resolved rows wastes context without improving convergence. | Pending | `.github/skills/internal-gateway-idea-brainstorming/references/guided-decision-interview.md` | +| 2026-06-13 | During idea discovery, bound the initial evidence pass to the smallest decision-relevant owners and explicit output budgets; expand only when an unresolved branch needs more evidence, because broad catalog searches can consume more context than the decisions they inform. | Pending | `.github/skills/internal-gateway-idea` | +| 2026-06-13 | Before opening a long guided interview, resolve the highest-leverage framing choice first, such as complement versus canonical replacement; derive dependent defaults from that answer instead of asking every downstream decision independently. | Pending | `.github/skills/internal-gateway-idea` | +| 2026-06-13 | After the initial decision ledger is accepted, report only new overrides, contradictions, reopened branches, and remaining open decisions; repeatedly restating resolved rows wastes context without improving convergence. | Pending | `.github/skills/internal-gateway-idea` | | 2026-06-13 | When adopting an external framework with unverified value or platform fit, separate verification, controlled installation, and a measurable pilot from canonical migration, consumer propagation, or replacement work; later changes require separate evidence-backed plans. | Pending | `.github/skills/internal-gateway-writing-plans/SKILL.md` | | 2026-06-13 | A retained plan that compares old and new approaches must define one representative task and identical measurement rules before execution; directory shape, framework preference, or mixed measurement methods are not evidence of improvement. | Pending | `.github/skills/internal-gateway-writing-plans/references/plan-review-gate.md` | | 2026-06-13 | Treat claims that one platform consumes another platform's command or configuration surface as unverified until current primary documentation confirms the exact behavior; user acceptance of a compatibility assumption does not replace platform evidence. | Pending | `.github/skills/internal-copilot-docs-research/SKILL.md` | From 0abf3cec989ed0b780956f999559b6ee78d70798 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 17:21:29 +0200 Subject: [PATCH 39/44] refactor: enhance clarity in internal-gateway-writing-plans documentation and agent prompts --- .../skills/internal-gateway-writing-plans/SKILL.md | 11 ++++++----- .../internal-gateway-writing-plans/agents/openai.yaml | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/skills/internal-gateway-writing-plans/SKILL.md b/.github/skills/internal-gateway-writing-plans/SKILL.md index f704b15..5f6f84a 100644 --- a/.github/skills/internal-gateway-writing-plans/SKILL.md +++ b/.github/skills/internal-gateway-writing-plans/SKILL.md @@ -25,13 +25,14 @@ stops after the delegated outcome. `Validation path`, `Stop conditions`, and `Observable acceptance`. 2. Load `superpowers-writing-plans` and let it create a plan, ask a blocking clarification, redirect, or stop with a reason. Pass an explicit anti-scope - and the list of existing owners so the delegated plan avoids duplicate or - speculative tasks at the source. + and the relevant owners already identified in the preflight so the delegated + plan avoids duplicate or speculative tasks at the source. 3. If a retained plan is created, verify execution-readiness and apply Plan Authoring Discipline: ordered tasks, concrete file targets, clear edit intent, validation commands or explicit gaps, stop conditions, and handoff readiness. Reject the draft if any task duplicates an existing owner, adds - speculative scope, or lacks a validation path. + speculative scope, or lacks validation commands or an explicit validation + gap. 4. Stop after the writing outcome and wait for the user's next choice. Preserve handoff quality with targeted rereads only when the delegation has a @@ -45,8 +46,8 @@ real evidence gap. - Single responsibility: each task must carry one clear deliverable tied to the approved target; split or merge tasks that don't. - Fail-fast and redirect: if the delegated plan adds speculative scope, - duplicates an existing owner, or lacks a validation path, send it back for - revision instead of accepting it. + duplicates an existing owner, or lacks validation commands or an explicit + validation gap, send it back for revision instead of accepting it. DRY, YAGNI, and TDD stay owned by `superpowers-writing-plans`; this section adds only the owner-awareness and redirect gate that the delegated skill does diff --git a/.github/skills/internal-gateway-writing-plans/agents/openai.yaml b/.github/skills/internal-gateway-writing-plans/agents/openai.yaml index a9694ed..de9cca3 100644 --- a/.github/skills/internal-gateway-writing-plans/agents/openai.yaml +++ b/.github/skills/internal-gateway-writing-plans/agents/openai.yaml @@ -6,5 +6,6 @@ interface: Nearest owner, Validation path, Stop conditions, and Observable acceptance. Then load $superpowers-writing-plans and delegate the writing outcome. If a retained plan is created, check ordered tasks, concrete file targets, edit - intent, validation or explicit gaps, stop conditions, and handoff readiness. - Stop after the writing outcome. + intent, validation commands or explicit gaps, duplicate-owner or + speculative-scope drift, stop conditions, and handoff readiness. Stop after + the writing outcome. From 222bb0abfa435e71f67dc8c2612b2dd1a0ce6cd8 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 17:35:18 +0200 Subject: [PATCH 40/44] refactor: update internal-gateway-execute-plans documentation and remove obsolete references --- .../internal-gateway-execute-plans/SKILL.md | 96 +++---- .../agents/openai.yaml | 4 +- .../references/plan-handoff.md | 32 --- .../references/resume-protocol.md | 28 -- .../references/status-file.md | 76 ------ .../scripts/plan_execution.py | 244 ------------------ 6 files changed, 40 insertions(+), 440 deletions(-) delete mode 100644 .github/skills/internal-gateway-execute-plans/references/plan-handoff.md delete mode 100644 .github/skills/internal-gateway-execute-plans/references/resume-protocol.md delete mode 100644 .github/skills/internal-gateway-execute-plans/references/status-file.md delete mode 100644 .github/skills/internal-gateway-execute-plans/scripts/plan_execution.py diff --git a/.github/skills/internal-gateway-execute-plans/SKILL.md b/.github/skills/internal-gateway-execute-plans/SKILL.md index e87606a..a8fbceb 100644 --- a/.github/skills/internal-gateway-execute-plans/SKILL.md +++ b/.github/skills/internal-gateway-execute-plans/SKILL.md @@ -1,83 +1,63 @@ --- name: internal-gateway-execute-plans -description: Use when executing an approved repository-owned retained plan and repo-local policy must wrap superpowers-executing-plans with a resumable status file. +description: Use when executing or resuming an approved repository-owned retained plan under tmp/superpowers/. --- # Internal Gateway Execute Plans -gateway-only wrapper for approved retained plans. This skill does not replace `superpowers-executing-plans`; it activates before that skill and adds repository-local execution policy. +## Referenced skills -## Referenced Skills +- `superpowers-executing-plans`: required owner for task-by-task plan execution. +- `superpowers-verification-before-completion`: on-demand evidence gate before completion claims. -- `superpowers-executing-plans`: required execution engine for the plan steps. -- `superpowers-verification-before-completion`: evidence gate before completion claims when fresh validation is required. +Thin repository wrapper for approved retained-plan execution. It owns only +repo-local start, stop, and status-file policy; `superpowers-executing-plans` +owns the execution loop. -## When To Use +## When to use -- Executing approved retained plans under `tmp/superpowers/`. -- Resuming approved retained plans that were previously stopped through this gateway. -- Applying repo-local status-file policy before using `superpowers-executing-plans`. +- Execute or resume an approved retained plan under `tmp/superpowers/`. +- Apply repo-local closeout through `..md`. -## When Not To Use +## When not to use -- Writing or reformulating a plan. -- Reviewing or challenging a plan. +- Writing, reformulating, reviewing, or challenging a plan. - Executing `questions.md` or unapproved planning notes. -- Changing `superpowers-executing-plans`; this wrapper must not patch imported Superpowers behavior. +- Running same-chat work that is not driven by an approved retained plan. +- Changing `superpowers-executing-plans`; imported Superpowers behavior stays read-only unless the user explicitly changes scope. ## Execution Discipline -- Plan-bound execution: follow the approved retained plan and stop when the plan is no longer executable as written. -- DRY: do not duplicate gateway workflow logic, status-file handling, validation reporting, or source logic when an existing owner already provides it. -- YAGNI: do not add speculative helpers, abstractions, configuration, or future-proofing beyond the approved plan. -- KISS: prefer the simplest coherent change that satisfies the plan and remains readable. -- Separation of Concerns: keep execution, status files, validation evidence, planning, review, critique, and domain implementation responsibilities separate. -- single responsibility: each new helper, script, section, or code path must have one clear reason tied to the active task. -- Fail-fast on drift: stop and record the defect when the plan forces confusing code, duplicated logic, missing validation, owner conflict, or scope drift. +- Plan-bound: follow the approved retained plan and stop when it is no longer executable as written. +- DRY: do not duplicate execution workflow, validation reporting, status policy, or source logic owned elsewhere. +- YAGNI: do not add speculative helpers, abstractions, configuration, or future proofing beyond the approved plan. +- KISS: choose the simplest coherent change that satisfies the plan and stays readable. +- Separation of Concerns: keep planning, execution, validation evidence, closeout status, review, critique, and domain implementation separate. +- Single responsibility: each new helper, section, or code path must have one active-task reason. +- Fail fast on drift: stop and record the defect when the plan forces confusing code, duplicated logic, missing validation, owner conflict, or scope drift. -## Wrapper Contract +## Contract -1. Confirm the retained plan path and that execution is approved. -2. Read the smallest plan context needed to identify target, anti-scope, validation path, and stop conditions. -3. If the plan is a compact plan, read `02-execution.md`; if it is an extended plan, read `02-control.md` before numbered execution files. -4. Announce that this gateway is wrapping `superpowers-executing-plans`. -5. Invoke `superpowers-executing-plans` for the task-by-task execution loop. -6. Preserve compact execution state. Prefer targeted rereads over full re-ingestion unless new evidence invalidates the current state. -7. Use Compact Evidence Reporting for large validator output: retain command, exit code, material counts, changed files, and exact gaps without pasting raw logs when a summary preserves the evidence. -8. Stop on scope drift, destructive action, owner conflict, missing validation path, human approval need, secret exposure risk, or repeated non-improving failures. -9. Before final response or pause, write exactly one status file in the retained plan folder named `..md`. -10. No `DONE` claim is allowed unless the status file exists and fresh evidence supports `DONE`, or the status file records the exact validation gap under another status. +1. Confirm the retained plan folder, plan basename, and approval to execute. +2. Read only target, anti-scope, validation path, stop conditions, and first executable task. Treat `questions.md` as non-executable. +3. On resume, verify any existing `..md`; do not resume from `DONE` unless fresh evidence invalidates it. +4. Announce this gateway, then load `superpowers-executing-plans` for critical plan review, todos, task execution, and its stop rules. +5. Preserve compact state with targeted rereads. Summarize large validator output by command, exit code, material counts, changed files, and exact gaps. +6. Stop on scope drift, destructive action, owner conflict, missing validation path, human approval need, secret exposure risk, or repeated non-improving failures. +7. Before final response or pause, write exactly one sibling status file named `..md`. -## Status File Contract +## Status closeout -`` is the retained plan folder name. Supported statuses: +Supported statuses are `DONE`, `BLOCKED`, `PARTIAL`, and `NEEDS_REVIEW`. +Required headings are `## Status`, `## Reason`, `## Completed`, +`## Remaining`, `## Validation`, `## Next`, and `## Resume Notes`. -- `DONE`: all in-scope work is complete and required validation passed. -- `BLOCKED`: a real blocker or required external/user action prevents safe continuation. -- `PARTIAL`: some in-scope work remains incomplete or intentionally deferred. -- `NEEDS_REVIEW`: work was applied but review, validation, or evidence coverage is still required before `DONE`. +Use `DONE` only when all in-scope work is complete and required validation has fresh passing evidence. For any gap, use the status that best explains the remaining action and record the exact evidence needed to resume or finish. -Required headings: - -- `## Status` -- `## Reason` -- `## Completed` -- `## Remaining` -- `## Validation` -- `## Next` -- `## Resume Notes` - -The status file must be short, factual, and useful to a later agent. It must state why the current status was chosen and what evidence or action is needed to resume or finish. - -## Legacy Contract Boundary - -This gateway no longer owns `done-*` packaging, `completion-report.md`, `evidence-envelope.md`, or `-plan-state.md` markers. Existing historical artifacts may be read as evidence, but new gateway output uses `..md`. +Do not create `done-*`, `completion-report.md`, `evidence-envelope.md`, or `-plan-state.md` as new closeout artifacts. ## Validation -- The wrapper references `superpowers-executing-plans` and does not duplicate its execution algorithm. -- The retained plan path and plan basename are known before execution starts. -- The final or paused state maps to exactly one supported status. -- The status file exists in the retained plan folder. -- The status filename, status value, and required headings match the contract. -- Fresh validation evidence or an explicit validation gap is recorded before any completion claim. +- `python3 ./.github/scripts/validate_internal_skills.py --skill internal-gateway-execute-plans --strict` +- Confirm no live repository references point to removed bundle files. +- `git diff --check` diff --git a/.github/skills/internal-gateway-execute-plans/agents/openai.yaml b/.github/skills/internal-gateway-execute-plans/agents/openai.yaml index 0b47651..29f74d2 100644 --- a/.github/skills/internal-gateway-execute-plans/agents/openai.yaml +++ b/.github/skills/internal-gateway-execute-plans/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Internal Gateway Execute Plans" - short_description: "Repo wrapper around superpowers-executing-plans with required status artifact" - default_prompt: "Use $internal-gateway-execute-plans for approved retained-plan execution under tmp/superpowers/. This gateway activates before superpowers-executing-plans, keeps execution inside repo-local target and anti-scope, and requires exactly one sibling status file named ..md before final response or pause. Supported statuses are DONE, BLOCKED, PARTIAL, and NEEDS_REVIEW. Do not modify superpowers-executing-plans. Do not create new -plan-state.md, done-* packaging, completion-report.md, or evidence-envelope.md artifacts as this gateway's closeout." + short_description: "Gateway for retained-plan execution" + default_prompt: "Use $internal-gateway-execute-plans for approved retained-plan execution or resume under tmp/superpowers/. Wrap, then delegate the execution loop to superpowers-executing-plans. Keep superpowers-executing-plans read-only. Before final response or pause, write exactly one sibling status file named ..md using DONE, BLOCKED, PARTIAL, or NEEDS_REVIEW." diff --git a/.github/skills/internal-gateway-execute-plans/references/plan-handoff.md b/.github/skills/internal-gateway-execute-plans/references/plan-handoff.md deleted file mode 100644 index 912738f..0000000 --- a/.github/skills/internal-gateway-execute-plans/references/plan-handoff.md +++ /dev/null @@ -1,32 +0,0 @@ -# Plan Handoff - -Use this reference when `internal-gateway-execute-plans` receives an approved -retained plan. - -## Required Inputs - -- Retained plan folder under `tmp/superpowers/`. -- Approval to execute the retained plan. -- `01-change-summary.md` when present. -- `02-execution.md` for compact plans or `02-control.md` for extended plans. -- Target, anti-scope, validation path, and stop conditions recoverable from the - retained plan. - -## Before Delegating To Superpowers - -1. Confirm the plan folder path and basename. -2. Read only the smallest context needed to recover target, anti-scope, - validation path, and stop conditions. -3. Treat `questions.md` as non-executable. -4. If the plan cannot be classified or lacks an executable path, stop with a - `BLOCKED` status file instead of guessing. -5. Invoke `superpowers-executing-plans` for task-by-task execution once the - handoff is safe. - -## Stop Conditions - -- The plan is not approved for execution. -- The retained plan path is missing or unreadable. -- Target, anti-scope, or validation path cannot be recovered. -- The requested action would modify `superpowers-executing-plans`. -- A required approval, credential, dependency, or owner decision is missing. diff --git a/.github/skills/internal-gateway-execute-plans/references/resume-protocol.md b/.github/skills/internal-gateway-execute-plans/references/resume-protocol.md deleted file mode 100644 index 6570e7b..0000000 --- a/.github/skills/internal-gateway-execute-plans/references/resume-protocol.md +++ /dev/null @@ -1,28 +0,0 @@ -# Resume Protocol - -Use this verify-first protocol when resuming retained-plan execution through -`internal-gateway-execute-plans`. - -## Verify-First Sequence - -1. Resolve the retained plan folder and plan basename. -2. Look for a sibling status file named `..md`. -3. Read `## Status`, `## Reason`, `## Remaining`, `## Validation`, `## Next`, - and `## Resume Notes` before editing. -4. Re-check the target files, current diff, and nearest validation command named - in the status file or retained plan. -5. Resume through `superpowers-executing-plans` only when the status and fresh - evidence support continuation. - -## Reconciliation Rules - -- If the status file says `DONE`, do not resume unless new evidence invalidates - completion. -- If the status file says `BLOCKED`, resolve or route the blocker before - editing. -- If the status file says `PARTIAL`, continue from the first evidenced - remaining item. -- If the status file says `NEEDS_REVIEW`, review or validate before adding new - work. -- If no status file exists, reconstruct the current state from retained-plan - files, diff, and validators, then write the status file before stopping. diff --git a/.github/skills/internal-gateway-execute-plans/references/status-file.md b/.github/skills/internal-gateway-execute-plans/references/status-file.md deleted file mode 100644 index d8bb970..0000000 --- a/.github/skills/internal-gateway-execute-plans/references/status-file.md +++ /dev/null @@ -1,76 +0,0 @@ -# Status File - -Use this reference when `internal-gateway-execute-plans` finishes, pauses, or -blocks retained-plan execution. - -The gateway writes exactly one sibling status file in the retained plan folder: - -```text -..md -``` - -`` is the retained plan folder name. `STATUS` must be one of: - -- `DONE` -- `BLOCKED` -- `PARTIAL` -- `NEEDS_REVIEW` - -## Required Headings - -- `## Status` -- `## Reason` -- `## Completed` -- `## Remaining` -- `## Validation` -- `## Next` -- `## Resume Notes` - -## Template - -```markdown -# Status - -## Status - -DONE - -## Reason - -All in-scope work is complete and required validation passed. - -## Completed - -- - -## Remaining - -- None. - -## Validation - -- `` passed. - -## Next - -- No action required. - -## Resume Notes - -- Re-run `` if new changes appear before merge. -``` - -## Status Selection - -- Use `DONE` only when all in-scope work is complete and required validation - passed. -- Use `BLOCKED` when safe continuation requires external input, approval, - missing credentials, dependency access, or a non-repairable blocker. -- Use `PARTIAL` when some planned work remains incomplete or intentionally - deferred. -- Use `NEEDS_REVIEW` when edits were applied but review, validation, or evidence - coverage is still required. - -Historical `done-*`, `completion-report.md`, `evidence-envelope.md`, and -`-plan-state.md` files may be read as evidence. Do not create them as -new gateway closeout artifacts. \ No newline at end of file diff --git a/.github/skills/internal-gateway-execute-plans/scripts/plan_execution.py b/.github/skills/internal-gateway-execute-plans/scripts/plan_execution.py deleted file mode 100644 index a509a84..0000000 --- a/.github/skills/internal-gateway-execute-plans/scripts/plan_execution.py +++ /dev/null @@ -1,244 +0,0 @@ -"""Bundle-local CLI for gateway retained-plan status files. - -Commands: inspect resume checkpoint status-check completion-check - -Stdlib-only. Read-only. Does not import sibling bundles or .github/scripts/lib. -""" - -from __future__ import annotations - -import argparse -import json -import re -import sys -from dataclasses import dataclass -from pathlib import Path - -VALID_STATUSES = frozenset({"DONE", "BLOCKED", "PARTIAL", "NEEDS_REVIEW"}) -REQUIRED_HEADINGS = ( - "## Status", - "## Reason", - "## Completed", - "## Remaining", - "## Validation", - "## Next", - "## Resume Notes", -) - - -@dataclass -class Finding: - code: str - message: str - severity: str = "ERROR" - - -def status_pattern(plan_folder: Path) -> re.Pattern[str]: - escaped_name = re.escape(plan_folder.name) - statuses = "|".join(sorted(VALID_STATUSES)) - return re.compile(rf"^{escaped_name}\.({statuses})\.md$") - - -def find_status_files(plan_folder: Path) -> tuple[list[Path], list[Finding]]: - findings: list[Finding] = [] - pattern = status_pattern(plan_folder) - status_files: list[Path] = [] - - for path in sorted(plan_folder.glob("*.md")): - if pattern.match(path.name): - status_files.append(path) - elif re.match(rf"^{re.escape(plan_folder.name)}\.[A-Z0-9_-]+\.md$", path.name): - findings.append( - Finding( - code="invalid-status-file-name", - message=( - f"{path.name} uses an unsupported status; valid statuses are " - f"{', '.join(sorted(VALID_STATUSES))}" - ), - ) - ) - - legacy_markers = sorted(plan_folder.glob("*-plan-state.md")) - if legacy_markers: - findings.append( - Finding( - code="legacy-plan-state-marker", - message="Legacy -plan-state.md marker found; gateway output must use ..md", - ) - ) - - return status_files, findings - - -def validate_status_file(plan_folder: Path) -> tuple[Path | None, str | None, list[Finding]]: - status_files, findings = find_status_files(plan_folder) - - if len(status_files) > 1: - findings.append( - Finding( - code="multiple-status-files", - message="Multiple gateway status files found; keep exactly one current status file", - ) - ) - return None, None, findings - - if not status_files: - findings.append( - Finding( - code="missing-status-file", - message="No ..md status file found", - ) - ) - return None, None, findings - - status_path = status_files[0] - match = status_pattern(plan_folder).match(status_path.name) - if match is None: - findings.append( - Finding( - code="invalid-status-file-name", - message=f"{status_path.name} does not match ..md", - ) - ) - return status_path, None, findings - - status = match.group(1) - text = status_path.read_text(encoding="utf-8") - - for heading in REQUIRED_HEADINGS: - if heading not in text: - findings.append( - Finding( - code="missing-required-heading", - message=f"{status_path.name} is missing {heading}", - ) - ) - - status_match = re.search(r"^## Status\s*\n+\s*([A-Z0-9_-]+)\s*$", text, re.MULTILINE) - if status_match is None: - findings.append( - Finding( - code="missing-declared-status", - message=f"{status_path.name} must declare the status under ## Status", - ) - ) - elif status_match.group(1) != status: - findings.append( - Finding( - code="status-mismatch", - message=f"{status_path.name} encodes {status} but declares {status_match.group(1)}", - ) - ) - - return status_path, status, findings - - -def emit(findings: list[Finding], output_format: str, report: dict | None = None) -> None: - ready = not any(f.severity == "ERROR" for f in findings) - if output_format == "json": - payload = { - "ready": ready, - "findings": [ - {"code": f.code, "message": f.message, "severity": f.severity} - for f in findings - ], - } - if report: - payload = {**report, **payload} - json.dump(payload, sys.stdout, indent=2) - print() - return - - if report: - for key, value in report.items(): - print(f"{key}: {value}") - if findings: - for finding in findings: - print(f"[{finding.severity}] {finding.code}: {finding.message}") - elif not report: - print("No findings.") - - -def basic_report(plan_folder: Path) -> dict: - status_files, findings = find_status_files(plan_folder) - return { - "plan_folder": str(plan_folder), - "plan_basename": plan_folder.name, - "status_file_present": bool(status_files) and not findings, - "status_files": [path.name for path in status_files], - } - - -def cmd_inspect(plan_folder: Path, output_format: str) -> int: - emit([], output_format, basic_report(plan_folder)) - return 0 - - -def cmd_resume(plan_folder: Path, output_format: str) -> int: - status_path, status, findings = validate_status_file(plan_folder) - report = basic_report(plan_folder) - report.update( - { - "status_file": status_path.name if status_path else None, - "status": status, - "resumable": status in {"BLOCKED", "PARTIAL", "NEEDS_REVIEW"}, - } - ) - emit(findings, output_format, report) - return 0 if not any(f.severity == "ERROR" for f in findings) else 1 - - -def cmd_checkpoint(plan_folder: Path, output_format: str) -> int: - report = basic_report(plan_folder) - report["status_file_required"] = True - emit([], output_format, report) - return 0 - - -def cmd_status_check(plan_folder: Path, output_format: str) -> int: - status_path, status, findings = validate_status_file(plan_folder) - report = { - "plan_folder": str(plan_folder), - "plan_basename": plan_folder.name, - "status_file": status_path.name if status_path else None, - "status": status, - } - emit(findings, output_format, report) - return 0 if not any(f.severity == "ERROR" for f in findings) else 1 - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Validate gateway retained-plan status files.") - sub = parser.add_subparsers(dest="command", required=True) - - for command in ("inspect", "resume", "checkpoint", "status-check", "completion-check"): - command_parser = sub.add_parser(command) - command_parser.add_argument("plan_folder", type=Path) - command_parser.add_argument("--format", choices=("text", "json"), default="text") - - return parser.parse_args() - - -def main() -> int: - args = parse_args() - plan_folder = args.plan_folder.resolve() - - if not plan_folder.is_dir(): - print(f"ERROR: Not a directory: {plan_folder}", file=sys.stderr) - return 1 - - if args.command == "inspect": - return cmd_inspect(plan_folder, args.format) - if args.command == "resume": - return cmd_resume(plan_folder, args.format) - if args.command == "checkpoint": - return cmd_checkpoint(plan_folder, args.format) - if args.command in {"status-check", "completion-check"}: - return cmd_status_check(plan_folder, args.format) - - print(f"Unknown command: {args.command}", file=sys.stderr) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) From 045ffc60ef7bacd2044554906c94ffb334a136f9 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 17:43:15 +0200 Subject: [PATCH 41/44] refactor: clarify execution instructions in SKILL.md and openai.yaml --- .github/skills/internal-gateway-execute-plans/SKILL.md | 5 ++--- .../skills/internal-gateway-execute-plans/agents/openai.yaml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/skills/internal-gateway-execute-plans/SKILL.md b/.github/skills/internal-gateway-execute-plans/SKILL.md index a8fbceb..0421dd3 100644 --- a/.github/skills/internal-gateway-execute-plans/SKILL.md +++ b/.github/skills/internal-gateway-execute-plans/SKILL.md @@ -22,7 +22,6 @@ owns the execution loop. ## When not to use - Writing, reformulating, reviewing, or challenging a plan. -- Executing `questions.md` or unapproved planning notes. - Running same-chat work that is not driven by an approved retained plan. - Changing `superpowers-executing-plans`; imported Superpowers behavior stays read-only unless the user explicitly changes scope. @@ -39,12 +38,12 @@ owns the execution loop. ## Contract 1. Confirm the retained plan folder, plan basename, and approval to execute. -2. Read only target, anti-scope, validation path, stop conditions, and first executable task. Treat `questions.md` as non-executable. +2. Read only target, anti-scope, validation path, stop conditions, and first executable task. 3. On resume, verify any existing `..md`; do not resume from `DONE` unless fresh evidence invalidates it. 4. Announce this gateway, then load `superpowers-executing-plans` for critical plan review, todos, task execution, and its stop rules. 5. Preserve compact state with targeted rereads. Summarize large validator output by command, exit code, material counts, changed files, and exact gaps. 6. Stop on scope drift, destructive action, owner conflict, missing validation path, human approval need, secret exposure risk, or repeated non-improving failures. -7. Before final response or pause, write exactly one sibling status file named `..md`. +7. Before final response or pause, replace any older sibling `.*.md` status file for the same plan basename, then write exactly one sibling status file named `..md`. ## Status closeout diff --git a/.github/skills/internal-gateway-execute-plans/agents/openai.yaml b/.github/skills/internal-gateway-execute-plans/agents/openai.yaml index 29f74d2..20c5f4e 100644 --- a/.github/skills/internal-gateway-execute-plans/agents/openai.yaml +++ b/.github/skills/internal-gateway-execute-plans/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Internal Gateway Execute Plans" short_description: "Gateway for retained-plan execution" - default_prompt: "Use $internal-gateway-execute-plans for approved retained-plan execution or resume under tmp/superpowers/. Wrap, then delegate the execution loop to superpowers-executing-plans. Keep superpowers-executing-plans read-only. Before final response or pause, write exactly one sibling status file named ..md using DONE, BLOCKED, PARTIAL, or NEEDS_REVIEW." + default_prompt: "Use $internal-gateway-execute-plans for approved retained-plan execution or resume under tmp/superpowers/. Wrap, then delegate the execution loop to superpowers-executing-plans. Keep superpowers-executing-plans read-only. Before final response or pause, replace any older sibling .*.md status file for the same plan basename, then write exactly one sibling status file named ..md using DONE, BLOCKED, PARTIAL, or NEEDS_REVIEW." From 8278b44f19801f4721fd5736d9520cefa00da627 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 17:43:53 +0200 Subject: [PATCH 42/44] refactor: simplify assertion in test_strict_mode_fails_on_advisory_finding --- tests/test_critical_master_validator.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/test_critical_master_validator.py b/tests/test_critical_master_validator.py index bfbf12d..5c0146c 100644 --- a/tests/test_critical_master_validator.py +++ b/tests/test_critical_master_validator.py @@ -42,9 +42,4 @@ def test_strict_mode_fails_on_advisory_finding() -> None: "--strict", ) assert result.returncode != 0 - assert ( - "summary-word-limit" in result.stdout - or "total-word-limit" in result.stdout - ) - - + assert "summary-word-limit" in result.stdout or "total-word-limit" in result.stdout From 181cf14289fa5ae902b2d572083028c7893c7924 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 17:49:03 +0200 Subject: [PATCH 43/44] refactor: add shebang to render_docx.py and update asset overrides for executable renderer --- .../patches/openai-docx-render-docx.patch | 8 ++++++++ .../references/imported-asset-overrides.yaml | 14 ++++++++++++++ .../references/managed-resource-scope.md | 3 +++ .github/skills/openai-docx/scripts/render_docx.py | 1 + 4 files changed, 26 insertions(+) create mode 100644 .github/skills/local-agent-sync-external-resources/patches/openai-docx-render-docx.patch diff --git a/.github/skills/local-agent-sync-external-resources/patches/openai-docx-render-docx.patch b/.github/skills/local-agent-sync-external-resources/patches/openai-docx-render-docx.patch new file mode 100644 index 0000000..69a41de --- /dev/null +++ b/.github/skills/local-agent-sync-external-resources/patches/openai-docx-render-docx.patch @@ -0,0 +1,8 @@ +diff --git a/.github/skills/openai-docx/scripts/render_docx.py b/.github/skills/openai-docx/scripts/render_docx.py +--- a/.github/skills/openai-docx/scripts/render_docx.py ++++ b/.github/skills/openai-docx/scripts/render_docx.py +@@ -1,3 +1,4 @@ ++#!/usr/bin/env python3 + import argparse + import os + import re diff --git a/.github/skills/local-agent-sync-external-resources/references/imported-asset-overrides.yaml b/.github/skills/local-agent-sync-external-resources/references/imported-asset-overrides.yaml index be1f974..8da2abf 100644 --- a/.github/skills/local-agent-sync-external-resources/references/imported-asset-overrides.yaml +++ b/.github/skills/local-agent-sync-external-resources/references/imported-asset-overrides.yaml @@ -37,6 +37,20 @@ overrides: baseline_repo_commit: e6afb0d validation_note: Stop the refresh if the patch does not apply cleanly; review whether an internal wrapper should replace the override. +- id: openai-docx-executable-renderer + target_path: .github/skills/openai-docx/scripts/render_docx.py + source_family: openai/skills + lifecycle_mode: post-refresh-patch + apply_strategy: git-apply + approval: explicit-user-counter-validated + reason: Preserve a valid executable entrypoint for the bundled DOCX renderer + so repository pre-commit validation does not fail after a retained office-skill + refresh. + patch_path: patches/openai-docx-render-docx.patch + expected_content_hash: b6b1c18d5a46d81d161a57e84947ad7b2941f8095f1d52fbfe1a2c57585d7a3a + baseline_repo_commit: 45d05d7 + validation_note: Stop the refresh if the patch does not apply cleanly; review whether + the retained support-only skill should instead stop shipping an executable renderer. - id: superpowers-brainstorming-guided-plan-gate target_path: .github/skills/superpowers-brainstorming/SKILL.md source_family: obra/superpowers diff --git a/.github/skills/local-agent-sync-external-resources/references/managed-resource-scope.md b/.github/skills/local-agent-sync-external-resources/references/managed-resource-scope.md index 21a8835..a3ea53b 100644 --- a/.github/skills/local-agent-sync-external-resources/references/managed-resource-scope.md +++ b/.github/skills/local-agent-sync-external-resources/references/managed-resource-scope.md @@ -150,6 +150,9 @@ Retained support-only office skills: Approved in-place overrides: +- `openai-docx`: replay `openai-docx-executable-renderer` after each refresh so + the bundled `scripts/render_docx.py` keeps a valid executable shebang and + does not trip repository pre-commit validation. - `openai-spreadsheet`: replay `openai-spreadsheet-structured-data-evidence-budget` after each refresh so large spreadsheet and tabular workflows keep the repository-specific diff --git a/.github/skills/openai-docx/scripts/render_docx.py b/.github/skills/openai-docx/scripts/render_docx.py index 907ec89..be4d0d3 100755 --- a/.github/skills/openai-docx/scripts/render_docx.py +++ b/.github/skills/openai-docx/scripts/render_docx.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import argparse import os import re From 88bf87c3ccde0f1bf4113648455ca810fd9ded80 Mon Sep 17 00:00:00 2001 From: diegoitaliait Date: Mon, 6 Jul 2026 17:51:39 +0200 Subject: [PATCH 44/44] refactor: remove obsolete GitHub catalog validation workflow and script --- .../workflows/_github-catalog-validation.yml | 65 ------------------- github_catalog_validation.sh | 13 ---- 2 files changed, 78 deletions(-) delete mode 100644 .github/workflows/_github-catalog-validation.yml delete mode 100755 github_catalog_validation.sh diff --git a/.github/workflows/_github-catalog-validation.yml b/.github/workflows/_github-catalog-validation.yml deleted file mode 100644 index 7b53de5..0000000 --- a/.github/workflows/_github-catalog-validation.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: 20 🕵️‍♀️ _github-catalog-validation - -on: - pull_request: - paths: - - .github/** - - tests/** - - Makefile - - .python-version - - AGENTS.md - - CONTRIBUTING.md - - INTERNAL_CONTRACT.md - - LESSONS_LEARNED.md - - VERSION - push: - branches: - - main - paths: - - .github/** - - tests/** - - Makefile - - .python-version - - AGENTS.md - - CONTRIBUTING.md - - INTERNAL_CONTRACT.md - - LESSONS_LEARNED.md - - VERSION - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: _github-catalog-validation-${{ github.ref }} - cancel-in-progress: true - -jobs: - validate: - runs-on: ubuntu-24.04 - timeout-minutes: 30 - steps: - - name: Check out repository - # actions/checkout@v6.0.2 - # https://github.com/actions/checkout/releases/tag/v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 - with: - persist-credentials: false - - - name: Set up Python - # actions/setup-python@v6.0.0 - # https://github.com/actions/setup-python/releases/tag/v6.0.0 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 - with: - python-version-file: ".python-version" - - - name: Run repository validation targets - run: | - set -euo pipefail - ./.github/scripts/run.sh github_catalog_validation --skip-token-risks - - - name: Run dedicated token-risk scan - continue-on-error: true - run: | - set -euo pipefail - ./.github/scripts/run.sh github_catalog_validation --token-risks-only diff --git a/github_catalog_validation.sh b/github_catalog_validation.sh deleted file mode 100755 index fe38761..0000000 --- a/github_catalog_validation.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -# -# Purpose: Run the local GitHub catalog validation workflow simulation from the repository root. -# Usage examples: -# bash ./github_catalog_validation.sh -# bash ./github_catalog_validation.sh --skip-token-risks -# bash ./github_catalog_validation.sh --token-risks-only - -set -Eeuo pipefail - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -exec bash "$REPO_ROOT/.github/scripts/github_catalog_validation.sh" "$@"