diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 7996438..e87a594 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -6,6 +6,13 @@ Use this format for new updates: - One bullet per meaningful change. - Include file/path scope when useful. +## 2026-04-04 +- Added a mandatory end-of-operation completion report contract to `.github/copilot-instructions.md`, documented the same emoji-based `Outcome` / `Agents` / `Instructions` / `Skills` structure in `.github/README.md`, and kept root `AGENTS.md` on a thin bridge pointer to the detailed policy. +- Extended `INTERNAL_CONTRACT.md`, `tests/test_contract_runner.py`, `tests/test_validate_copilot_customizations.py`, and `.github/scripts/validate-copilot-customizations.py` so the completion-report contract is now source-governed and strict-validator enforced. +- Updated `internal-sync-control-center`, `internal-sync-global-copilot-configs-into-repo`, and the sync skill workflow so completed sync runs must also end with the same completion-report categories and explicit unused-category explanations. +- Updated sync governance and `.github/scripts/internal-sync-copilot-configs.py` so retained sync plans now live under repository-root `tmp/` instead of `.github/`, and the tracking-plan flow creates `tmp/` automatically when it is missing. +- Updated `.github/copilot-instructions.md`, `.github/instructions/internal-python.instructions.md`, and `.github/skills/internal-script-python/SKILL.md` so new Python scripts must make an explicit stdlib-vs-library decision, prefer mature third-party packages when they clearly simplify the final code, and record that choice in a short dependency decision note before implementation. + ## 2026-03-19 - Updated `.github/copilot-instructions.md`, `.github/instructions/python.instructions.md`, and `.github/prompts/tech-ai-python.prompt.md` so Python tasks now standardize on human-readable hash-locked `requirements.txt` files for external dependencies, clarify that the lock file should capture the full dependency closure, and treat third-party libraries as a recommendation only when they materially simplify the code. - Updated `.github/prompts/tech-ai-python-script.prompt.md`, `.github/skills/tech-ai-script-python/SKILL.md`, and `.github/instructions/bash.instructions.md` so new standalone Python tools default to a self-contained folder with a `run.sh` launcher, add a local `requirements.txt` only when external packages are used, and bootstrap `.venv` plus locked dependency installation only when that file exists. diff --git a/.github/README.md b/.github/README.md index 0128464..361dc26 100644 --- a/.github/README.md +++ b/.github/README.md @@ -219,6 +219,29 @@ These agents manage the **lifecycle** of Copilot customization assets. They are --- +## Completion Report Contract + +Completed operations must end with a concise recap. +If a category was not used, explicitly say so and explain why. + +### ✅ Outcome + +- Summarize the completed operation and any relevant validation status or blockers. + +### 🤖 Agents + +- State which agents were used and why they were relevant. + +### 📘 Instructions + +- State which instructions were used and why they mattered. + +### 🧩 Skills + +- State which skills were used and why they were relevant. + +--- + ## Notes - `repo-profiles.yml` is advisory-only (human-readable profile catalog, not enforced by validators). diff --git a/.github/agents/internal-ai-resource-creator.agent.md b/.github/agents/internal-ai-resource-creator.agent.md index c80d16b..f3da471 100644 --- a/.github/agents/internal-ai-resource-creator.agent.md +++ b/.github/agents/internal-ai-resource-creator.agent.md @@ -12,11 +12,11 @@ You are the repository's focused authoring command center for Copilot customizat ## Preferred/Optional Skills -- `obra-simplification-cascades` -- `obra-meta-pattern-recognition` -- `obra-tracing-knowledge-lineages` -- `obra-preserving-productive-tensions` +- `obra-brainstorming` +- `obra-writing-plans` - `obra-subagent-driven-development` +- `obra-writing-skills` +- `obra-verification-before-completion` - `internal-agent-development` - `internal-agents-md-bridge` - `internal-copilot-audit` @@ -25,12 +25,12 @@ You are the repository's focused authoring command center for Copilot customizat ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane resource-authoring toolkit: use `obra-*` for pattern recognition, lineage checks, simplification, and multi-step execution discipline; use `internal-*` as the repository-owned tactical authors; use imported skills only when a skill-specific authoring loop still adds distinct value. -- `obra-simplification-cascades`: Use when one stronger abstraction can remove overlapping sections, duplicated rules, or local exceptions across Copilot resources. -- `obra-meta-pattern-recognition`: Use when the same authoring pattern appears across agents, skills, prompts, or instructions and should become one reusable rule. -- `obra-tracing-knowledge-lineages`: Use before replacing, renaming, or materially rewriting a resource so the original reason still gets checked. -- `obra-preserving-productive-tensions`: Use when two resource shapes remain valid and the decision should keep the real tradeoff explicit. +- Treat preferred or optional skills as a three-lane resource-authoring toolkit: use `obra-*` for option framing, plan shape, skill-authoring discipline, and final verification; use `internal-*` as the repository-owned tactical authors; use imported skills only when a skill-specific authoring loop still adds distinct value. +- `obra-brainstorming`: Use when the resource direction is still open and the authoring task needs options, constraints, or tradeoffs before picking one shape. +- `obra-writing-plans`: Use when a larger authoring change needs a staged implementation plan with explicit files, checkpoints, or rollout order. - `obra-subagent-driven-development`: Use when a multi-step resource-authoring change benefits from fresh subagents per task with review gates between them. +- `obra-writing-skills`: Use when the output is a skill, when an agent procedure should be extracted into a skill, or when a refreshed imported skill needs authoring-quality review. +- `obra-verification-before-completion`: Use before claiming the authored resource is ready so naming, drift, and validation evidence are explicitly checked. - `internal-agent-development`: Use when the target artifact is an agent, when frontmatter or tool contracts need revision, or when routing boundaries must be tightened. - `internal-agents-md-bridge`: Use when authoring work changes root `AGENTS.md` or the bridge between root guidance and `.github/copilot-instructions.md`. - `internal-copilot-audit`: Use when overlap, hollow references, stale tool contracts, or governance drift could make the authored resource misleading. diff --git a/.github/agents/internal-architect.agent.md b/.github/agents/internal-architect.agent.md index 57627b7..0b14cca 100644 --- a/.github/agents/internal-architect.agent.md +++ b/.github/agents/internal-architect.agent.md @@ -12,13 +12,9 @@ You are the strategic architecture command center for software, platform, and cl ## Preferred/Optional Skills -- `obra-collision-zone-thinking` -- `obra-inversion-exercise` -- `obra-scale-game` -- `obra-simplification-cascades` -- `obra-meta-pattern-recognition` -- `obra-preserving-productive-tensions` -- `obra-tracing-knowledge-lineages` +- `obra-brainstorming` +- `obra-writing-plans` +- `obra-verification-before-completion` - `internal-pair-architect` - `antigravity-domain-driven-design` - `awesome-copilot-cloud-design-patterns` @@ -26,14 +22,10 @@ You are the strategic architecture command center for software, platform, and cl ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane architecture toolkit: use `obra-*` to frame competing models, challenge assumptions, and test scale conditions; use `internal-*` for repository-owned change-impact and risk analysis; pull in imported specialists only when the architecture question still needs their specific depth. -- `obra-collision-zone-thinking`: Use when multiple viable architectural models collide and the answer needs the sharpest tension map before choosing. -- `obra-inversion-exercise`: Use when the current default architecture may be anchored on an untested assumption and the safer move is to challenge it first. -- `obra-scale-game`: Use when the decision could change materially at different growth, concurrency, or organizational scale thresholds. -- `obra-simplification-cascades`: Use when the current design looks over-layered and one stronger abstraction may remove duplicated complexity. -- `obra-meta-pattern-recognition`: Use when similar architectural patterns appear across multiple domains or subsystems and should be generalized into one principle. -- `obra-preserving-productive-tensions`: Use when multiple valid architectures should remain visible instead of being flattened into one premature recommendation. -- `obra-tracing-knowledge-lineages`: Use before replacing an existing architecture pattern so the original constraint, tradeoff, or reason is verified first. +- Treat preferred or optional skills as a three-lane architecture toolkit: use `obra-*` to frame competing models, turn approved direction into phased guidance, and keep the final recommendation evidence-backed; use `internal-*` for repository-owned change-impact and risk analysis; pull in imported specialists only when the architecture question still needs their specific depth. +- `obra-brainstorming`: Use when the architectural direction is still open and the answer needs competing models, constraints, and tradeoffs surfaced before choosing. +- `obra-writing-plans`: Use when the architectural recommendation needs a phased adoption plan, migration sequence, or implementation-ready follow-through. +- `obra-verification-before-completion`: Use before finalizing the recommendation so claims about risks, scale, and rollout are grounded in explicit evidence. - `internal-pair-architect`: Use when the core need is change-impact analysis, blind-spot review, or architecture-risk evaluation of a concrete change set. - `antigravity-domain-driven-design`: Use when bounded contexts, aggregates, domain ownership, or event flows are central to the architecture decision. - `awesome-copilot-cloud-design-patterns`: Use when the answer depends on distributed-systems patterns such as retries, queues, sagas, caching, failover, or messaging topology. @@ -42,15 +34,14 @@ You are the strategic architecture command center for software, platform, and cl ## Routing Rules - Use this agent when the task is primarily about architecture quality, not code editing speed. -- Start with strategic framing: surface competing models, question default assumptions, and state scale conditions before recommending structure. -- Use the `obra-*` lane to preserve productive tension, simplify over-layered designs, and trace why current patterns exist before replacing them. +- Start with strategic framing: use `obra-brainstorming` to surface competing models, question default assumptions, and state scale conditions before recommending structure. +- Use the `obra-*` lane to turn approved architecture into phased rollout guidance and keep the final recommendation evidence-backed. - Use `internal-pair-architect` as the tactical owner when the work becomes concrete change-impact analysis or architecture-risk review of a specific change set. - Pull in imported architecture specialists only when DDD, distributed-systems patterns, or API-contract depth materially changes the recommendation. - Use `internal-pair-architect` when the core need is change impact, blind spots, health scoring, or architecture-risk evaluation of a concrete change set. - Start with boundaries, constraints, and failure modes before proposing structure. - Prefer explicit tradeoffs over generic best-practice lists. -- Use the declared obra analysis skills when complexity may collapse under a better abstraction or when multiple valid approaches should stay visible instead of being flattened too early. -- Before replacing an existing pattern, check why it exists and whether current constraints still justify it. +- Before replacing an existing pattern, re-check the original constraints and why the current shape exists instead of assuming the design drifted by accident. ## Output Expectations diff --git a/.github/agents/internal-aws-org-governance.agent.md b/.github/agents/internal-aws-org-governance.agent.md index c9dee63..36b1ec3 100644 --- a/.github/agents/internal-aws-org-governance.agent.md +++ b/.github/agents/internal-aws-org-governance.agent.md @@ -20,23 +20,13 @@ You are the strategic AWS organization-governance command center for control-pla - `internal-pair-architect` - `antigravity-cloudformation-best-practices` - `obra-brainstorming` -- `obra-tracing-knowledge-lineages` -- `obra-preserving-productive-tensions` -- `obra-defense-in-depth` -- `obra-simplification-cascades` -- `obra-meta-pattern-recognition` - `obra-writing-plans` - `obra-verification-before-completion` ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane AWS governance toolkit: use `obra-*` for option framing, tradeoff preservation, simplification, planning, and verification; use `internal-*` as the tactical owners for repository-aligned governance, policy, and rollout execution; use imported skills only for narrow StackSet or CloudFormation support. +- Treat preferred or optional skills as a three-lane AWS governance toolkit: use `obra-*` for option framing, rollout planning, and verification discipline; use `internal-*` as the tactical owners for repository-aligned governance, policy, and rollout execution; use imported skills only for narrow StackSet or CloudFormation support. - `obra-brainstorming`: Use when the governance or process question is still under-specified and the user needs options, constraints, and tradeoffs surfaced before a model is chosen. -- `obra-tracing-knowledge-lineages`: Use before replacing existing AWS organization patterns, OU structures, access models, delegation boundaries, or rollout mechanics. -- `obra-preserving-productive-tensions`: Use when multiple valid operating models remain viable, such as centralization versus delegation or tighter guardrails versus delivery autonomy. -- `obra-defense-in-depth`: Use when the governance solution must layer SCPs, IAM policies, trust policies, permissions boundaries, detective controls, and rollout guardrails instead of relying on a single control surface. -- `obra-simplification-cascades`: Use when AWS governance or platform process has accumulated overlapping exceptions, duplicated controls, or too many bespoke account patterns and one abstraction may remove them. -- `obra-meta-pattern-recognition`: Use when the same governance or control pattern appears across multiple AWS services, OUs, or accounts and should be abstracted into one principle. - `obra-writing-plans`: Use when the strategic recommendation needs a phased adoption plan, migration sequence, or control-plane rollout with explicit checkpoints. - `obra-verification-before-completion`: Use before claiming the governance recommendation is safe, especially when the answer mixes AWS facts, inferred org constraints, and rollout steps. - `internal-aws-control-plane-governance`: Use when the question centers on management-account responsibilities, payer concerns, delegated administrators, SCP strategy, IAM operating model, or StackSets across the organization. diff --git a/.github/agents/internal-aws-platform-engineering.agent.md b/.github/agents/internal-aws-platform-engineering.agent.md index ce0a07d..8aa2485 100644 --- a/.github/agents/internal-aws-platform-engineering.agent.md +++ b/.github/agents/internal-aws-platform-engineering.agent.md @@ -21,17 +21,13 @@ You are the AWS platform-engineering command center for tactical architecture, i - `antigravity-aws-serverless` - `antigravity-aws-cost-optimizer` - `antigravity-cloudformation-best-practices` -- `obra-defense-in-depth` - `obra-systematic-debugging` -- `obra-root-cause-tracing` - `obra-verification-before-completion` ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane AWS engineering toolkit: use `obra-*` for tactical investigation, layered safeguards, and evidence discipline; use `internal-*` as the tactical owners for repository-aligned research, delivery, review, and performance work; use imported skills only for narrow AWS service-specific support. -- `obra-defense-in-depth`: Use when tactical remediation must combine network controls, IAM, encryption, runtime hardening, deployment checks, or guardrails rather than rely on one fix. -- `obra-systematic-debugging`: Use for incident analysis, service malfunction, runtime regressions, or unexpected AWS behavior. -- `obra-root-cause-tracing`: Use when symptoms cross layers and the failure chain must be followed from trigger to blast radius. +- Treat preferred or optional skills as a three-lane AWS engineering toolkit: use `obra-*` for root-cause-first investigation, remediation framing, and evidence discipline; use `internal-*` as the tactical owners for repository-aligned research, delivery, review, and performance work; use imported skills only for narrow AWS service-specific support. +- `obra-systematic-debugging`: Use for incident analysis, service malfunction, runtime regressions, or cross-layer AWS failure investigation before proposing fixes. - `obra-verification-before-completion`: Use before claiming the recommendation is safe, especially when the answer mixes AWS facts, assumptions, and implementation steps. - `internal-aws-mcp-research`: Mandatory whenever the answer depends on current AWS documentation, service behavior, regional availability, IAM semantics, managed-service constraints, or best-practice confirmation before remediation. - `internal-terraform`: Use when the recommendation must become Terraform, StackSet, pipeline, or infrastructure implementation guidance. @@ -78,7 +74,7 @@ For tactical recommendations, make the main optimization explicit and state the - Use `internal-aws-mcp-research` before locking in service recommendations that depend on current AWS behavior or documentation details. - Use `internal-pair-architect` when the tactical fix spans multiple AWS services, accounts, or teams and the ripple effects need explicit analysis. - State the main tradeoff explicitly when balancing resilience, cost, performance, and delivery complexity. -- Prefer defense in depth when reliability, security, and delivery risk intersect across runtime, IAM, networking, and automation. +- When reliability, security, and delivery risk intersect across runtime, IAM, networking, and automation, keep the remediation layered instead of relying on one fix. - Trace root cause before suggesting refactors, migrations, or service swaps. - Use imported support only when networking, serverless, cost, or CloudFormation depth materially changes the tactical recommendation. - End with a tactical implementation sequence the platform team can actually run. diff --git a/.github/agents/internal-azure-platform-engineering.agent.md b/.github/agents/internal-azure-platform-engineering.agent.md index 169b77c..939bf96 100644 --- a/.github/agents/internal-azure-platform-engineering.agent.md +++ b/.github/agents/internal-azure-platform-engineering.agent.md @@ -20,17 +20,13 @@ You are the Azure platform-engineering command center for tactical architecture, - `internal-pair-architect` - `antigravity-network-engineer` - `awesome-copilot-azure-pricing` -- `obra-defense-in-depth` - `obra-systematic-debugging` -- `obra-root-cause-tracing` - `obra-verification-before-completion` ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane Azure engineering toolkit: use `obra-*` for tactical investigation, layered safeguards, and evidence discipline; use `internal-*` as the tactical owners for repository-aligned rollout, review, and performance work; use imported skills only for narrow Azure support surfaces. -- `obra-defense-in-depth`: Use when tactical remediation must combine identity, network controls, encryption, deployment checks, and runtime protections rather than rely on one fix. -- `obra-systematic-debugging`: Use for incident analysis, unexpected Azure behavior, or tactical fault isolation. -- `obra-root-cause-tracing`: Use when the failure chain crosses layers such as identity, networking, runtime, and deployment. +- Treat preferred or optional skills as a three-lane Azure engineering toolkit: use `obra-*` for root-cause-first investigation and evidence discipline; use `internal-*` as the tactical owners for repository-aligned rollout, review, and performance work; use imported skills only for narrow Azure support surfaces. +- `obra-systematic-debugging`: Use for incident analysis, unexpected Azure behavior, or cross-layer Azure fault isolation before proposing fixes. - `obra-verification-before-completion`: Use before finalizing the answer when it mixes current Azure facts, assumptions, and implementation steps. - `internal-terraform`: Use when the recommendation must become Terraform, pipeline, rollout, or infrastructure implementation guidance. - `internal-kubernetes-deployment`: Use when the decision centers on AKS, Kubernetes rollout strategy, cluster operating guidance, or container-platform remediation. @@ -76,7 +72,7 @@ For tactical recommendations, make the main optimization explicit and state the - Do not use this agent to redesign Azure governance model, identity operating model across the estate, or organization-wide guardrail placement; prefer `internal-azure-platform-strategy`. - Use `internal-pair-architect` when the tactical fix spans multiple Azure services, subscriptions, environments, or teams and the ripple effects need explicit analysis. - State the main tradeoff explicitly when balancing resilience, cost, performance, and delivery complexity. -- Prefer defense in depth when security, reliability, and delivery risk intersect across runtime, identity, networking, and automation. +- When security, reliability, and delivery risk intersect across runtime, identity, networking, and automation, keep the remediation layered instead of relying on one fix. - Trace root cause before suggesting migrations, service swaps, or broader refactors. - Prefer `internal-architect` when the cloud-provider choice is still open or the question is cross-cloud rather than Azure-specific. - Prefer `internal-infrastructure` when the main task is direct Terraform, Kubernetes, or delivery implementation rather than principal-level Azure guidance. diff --git a/.github/agents/internal-azure-platform-strategy.agent.md b/.github/agents/internal-azure-platform-strategy.agent.md index 5afd598..4dc2148 100644 --- a/.github/agents/internal-azure-platform-strategy.agent.md +++ b/.github/agents/internal-azure-platform-strategy.agent.md @@ -19,17 +19,13 @@ You are the strategic Azure command center for platform topology, governance dir - `internal-devops-core-principles` - `internal-pair-architect` - `obra-brainstorming` -- `obra-preserving-productive-tensions` -- `obra-defense-in-depth` - `obra-writing-plans` - `obra-verification-before-completion` ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane Azure strategy toolkit: use `obra-*` for option framing, tradeoff preservation, planning, and verification; use `internal-*` as the tactical owners for repository-aligned rollout and impact analysis; use imported skills only for narrow Azure architectural, pricing, or RBAC support. +- Treat preferred or optional skills as a three-lane Azure strategy toolkit: use `obra-*` for option framing, rollout planning, and verification; use `internal-*` as the tactical owners for repository-aligned rollout and impact analysis; use imported skills only for narrow Azure architectural, pricing, or RBAC support. - `obra-brainstorming`: Use when the Azure strategy question is exploratory or under-specified and viable options need to be surfaced before convergence. -- `obra-preserving-productive-tensions`: Use when multiple valid Azure operating models remain viable, such as stronger centralization versus team autonomy or tighter guardrails versus faster delivery. -- `obra-defense-in-depth`: Use when the strategic answer must layer policy, identity, network segmentation, guardrails, detective controls, and rollout protections rather than rely on one control surface. - `obra-writing-plans`: Use when the recommendation needs a phased adoption path, migration sequence, or platform-governance rollout with explicit checkpoints. - `obra-verification-before-completion`: Use before finalizing the answer when it mixes current Azure facts, inferred constraints, and staged implementation guidance. - `internal-terraform`: Use when the strategic target state must become landing-zone rollout guidance, policy deployment sequencing, or infrastructure delivery guardrails. diff --git a/.github/agents/internal-cicd.agent.md b/.github/agents/internal-cicd.agent.md index fa471e8..5a74be4 100644 --- a/.github/agents/internal-cicd.agent.md +++ b/.github/agents/internal-cicd.agent.md @@ -13,10 +13,9 @@ You are the command center for CI/CD workflow authoring and delivery automation. ## Preferred/Optional Skills - `obra-executing-plans` -- `obra-condition-based-waiting` - `obra-finishing-a-development-branch` - `obra-using-git-worktrees` -- `obra-defense-in-depth` +- `obra-systematic-debugging` - `obra-verification-before-completion` - `internal-cicd-workflow` - `internal-composite-action` @@ -26,12 +25,11 @@ You are the command center for CI/CD workflow authoring and delivery automation. ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane CI/CD toolkit: use `obra-*` for execution sequencing, readiness gates, branch safety, and evidence discipline; use `internal-*` as the tactical delivery owners; use imported skills only for distinct support surfaces such as Dependabot policy. +- Treat preferred or optional skills as a three-lane CI/CD toolkit: use `obra-*` for staged execution, branch safety, pipeline investigation, and evidence discipline; use `internal-*` as the tactical delivery owners; use imported skills only for distinct support surfaces such as Dependabot policy. - `obra-executing-plans`: Use when the user already has a concrete delivery or release plan and the workflow changes should be applied in deliberate batches. -- `obra-condition-based-waiting`: Use when rollout steps depend on asynchronous workflow completion, protected-environment approvals, or other explicit readiness conditions. - `obra-finishing-a-development-branch`: Use when the task includes closing release-flow work, final branch hygiene, or preparing verified delivery changes for handoff. - `obra-using-git-worktrees`: Use when parallel workflow changes or isolated pipeline experiments are safer in separate worktrees. -- `obra-defense-in-depth`: Use when the delivery path needs layered controls across workflow, artifact, deployment, approvals, and rollback safeguards. +- `obra-systematic-debugging`: Use when a pipeline failure, flaky gate, or rollout condition is unclear and the safest next move is root-cause-first investigation. - `obra-verification-before-completion`: Use before claiming pipeline success so workflow runs, validation commands, and rollout evidence are actually checked. - `internal-cicd-workflow`: Use when a GitHub Actions workflow needs a durable behavior contract, job structure, permissions model, or rollout pattern. - `internal-composite-action`: Use when the task involves creating or refactoring reusable composite actions, or when workflow shell logic should move into an action. @@ -43,7 +41,7 @@ You are the command center for CI/CD workflow authoring and delivery automation. - Use this agent for pipeline authoring, workflow hardening, release flow changes, and deployment-stage design. - Separate pipeline design from broader Copilot governance. -- Start with the strategic execution lane when the work already arrives as a staged rollout plan, depends on readiness conditions, or needs safer branch isolation. +- Start with the strategic execution lane when the work already arrives as a staged rollout plan, is blocked by unclear pipeline behavior, or needs safer branch isolation. - Use the repository-owned internal skills as the tactical delivery owners for workflows, composite actions, delivery process, and changelog automation. - Pull in imported support only when dependency-update policy materially changes the CI/CD recommendation. - Prefer secure, low-noise, observable pipelines with explicit rollback behavior. diff --git a/.github/agents/internal-code-review.agent.md b/.github/agents/internal-code-review.agent.md index df2e244..fd2ddb4 100644 --- a/.github/agents/internal-code-review.agent.md +++ b/.github/agents/internal-code-review.agent.md @@ -17,22 +17,18 @@ You are the code-review and risk-gating command center. - `internal-code-review` - `obra-verification-before-completion` - `obra-systematic-debugging` -- `obra-root-cause-tracing` -- `obra-defense-in-depth` -- `obra-testing-anti-patterns` +- `obra-test-driven-development` - `awesome-copilot-codeql` - `awesome-copilot-secret-scanning` ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane review toolkit: use `obra-*` for review framing, response handling, investigation, and evidence discipline; use `internal-*` as the tactical defect-first owner; use imported skills only for narrow security-analysis support. +- Treat preferred or optional skills as a three-lane review toolkit: use `obra-*` for review framing, response handling, investigation, stronger regression discipline, and evidence; use `internal-*` as the tactical defect-first owner; use imported skills only for narrow security-analysis support. - `obra-requesting-code-review`: Use when the work needs a structured review ask tied to a plan, diff, or risk focus before starting the review. - `obra-receiving-code-review`: Use when the task is to process review feedback, reconcile findings, or decide which comments must be addressed before merge. - `obra-verification-before-completion`: Use before closing the review so findings, evidence, and residual risks are grounded in what was actually verified. -- `obra-systematic-debugging`: Use when the failure mechanism is unclear and the review needs a disciplined investigation path before concluding. -- `obra-root-cause-tracing`: Use when a visible defect likely comes from an earlier assumption, dependency, or regression source rather than the immediate symptom alone. -- `obra-defense-in-depth`: Use when a single missing validation can reappear through multiple code paths and the review should recommend layered safeguards. -- `obra-testing-anti-patterns`: Use when tests may hide defects through brittle mocks, false confidence, test-only APIs, or weak behavioral coverage. +- `obra-systematic-debugging`: Use when the failure mechanism is unclear and the review needs a root-cause-first investigation path before concluding. +- `obra-test-driven-development`: Use when review findings expose missing failing tests, brittle regression coverage, or weak behavioral proof around the change. - `internal-code-review`: Use as the default defect-first workflow for functional bugs, security issues, regressions, unsafe simplifications, and merge-readiness checks. - `awesome-copilot-codeql`: Support-only; use when static-analysis findings, CodeQL workflow behavior, query coverage, or security-pattern detection materially change the review. - `awesome-copilot-secret-scanning`: Support-only; use when the review includes leaked credentials, push protection, custom secret patterns, or broader secret-handling risk. diff --git a/.github/agents/internal-developer.agent.md b/.github/agents/internal-developer.agent.md index 7047909..4e32714 100644 --- a/.github/agents/internal-developer.agent.md +++ b/.github/agents/internal-developer.agent.md @@ -15,9 +15,7 @@ You are the repository's implementation command center for application and scrip - `obra-dispatching-parallel-agents` - `obra-executing-plans` - `obra-subagent-driven-development` -- `obra-when-stuck` - `obra-systematic-debugging` -- `obra-root-cause-tracing` - `obra-verification-before-completion` - `internal-project-java` - `internal-project-nodejs` @@ -27,13 +25,11 @@ You are the repository's implementation command center for application and scrip ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane implementation toolkit: use `obra-*` for plan execution, subtask decomposition, unblock strategy, debugging, and verification discipline; use `internal-*` as the tactical owners for each runtime; add outside support only if no repository-owned owner covers a required capability. +- Treat preferred or optional skills as a three-lane implementation toolkit: use `obra-*` for plan execution, subtask decomposition, debugging, and verification discipline; use `internal-*` as the tactical owners for each runtime; add outside support only if no repository-owned owner covers a required capability. - `obra-dispatching-parallel-agents`: Use when independent implementation or debugging subproblems can be split safely into parallel investigations. - `obra-executing-plans`: Use when the user already supplied a concrete implementation plan and the work should follow it in ordered batches. - `obra-subagent-driven-development`: Use when a multi-step implementation benefits from fresh subagents per task plus review gates between tasks. -- `obra-when-stuck`: Use when implementation stalls and the safest next step is to reframe or unblock before adding more speculative edits. - `obra-systematic-debugging`: Use when implementation is blocked by an unclear failure mode and the work needs stepwise diagnosis. -- `obra-root-cause-tracing`: Use when a bug symptom likely comes from a deeper earlier change, hidden assumption, or upstream trigger. - `obra-verification-before-completion`: Use before claiming the fix is complete, especially after multi-file edits, indirect reproductions, or partial rollback risk. - `internal-project-java`: Use when the task is about structured Java services, libraries, Spring components, or module-level implementation work. - `internal-project-nodejs`: Use when the task targets Node.js or TypeScript application components, handlers, middleware, or modules. diff --git a/.github/agents/internal-gcp-platform-engineering.agent.md b/.github/agents/internal-gcp-platform-engineering.agent.md index ed604c5..185884e 100644 --- a/.github/agents/internal-gcp-platform-engineering.agent.md +++ b/.github/agents/internal-gcp-platform-engineering.agent.md @@ -18,17 +18,13 @@ You are the GCP platform-engineering command center for tactical architecture, i - `internal-code-review` - `internal-pair-architect` - `antigravity-network-engineer` -- `obra-defense-in-depth` - `obra-systematic-debugging` -- `obra-root-cause-tracing` - `obra-verification-before-completion` ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane GCP engineering toolkit: use `obra-*` for tactical investigation, layered safeguards, and evidence discipline; use `internal-*` as the tactical owners for repository-aligned rollout, review, and performance work; use imported skills only for narrow networking support. -- `obra-defense-in-depth`: Use when tactical remediation must combine IAM, network controls, encryption, deployment safeguards, and runtime protections rather than rely on one fix. -- `obra-systematic-debugging`: Use for incident analysis, unexpected GCP behavior, or tactical fault isolation. -- `obra-root-cause-tracing`: Use when the failure chain crosses layers such as IAM, networking, runtime, and deployment. +- Treat preferred or optional skills as a three-lane GCP engineering toolkit: use `obra-*` for root-cause-first investigation and evidence discipline; use `internal-*` as the tactical owners for repository-aligned rollout, review, and performance work; use imported skills only for narrow networking support. +- `obra-systematic-debugging`: Use for incident analysis, unexpected GCP behavior, or cross-layer GCP fault isolation before proposing fixes. - `obra-verification-before-completion`: Use before finalizing the answer when it mixes current GCP facts, assumptions, and implementation steps. - `internal-terraform`: Use when the recommendation must become Terraform, pipeline, rollout, or infrastructure implementation guidance. - `internal-kubernetes-deployment`: Use when the decision centers on GKE, Kubernetes rollout strategy, or cluster-operating guidance. @@ -72,7 +68,7 @@ For tactical recommendations, make the main optimization explicit and state the - Do not use this agent to redesign GCP governance model, IAM operating model across the estate, or organization-wide guardrail placement; prefer `internal-gcp-platform-strategy`. - Use `internal-pair-architect` when the tactical fix spans multiple GCP services, projects, environments, or teams and the ripple effects need explicit analysis. - State the main tradeoff explicitly when balancing resilience, cost, performance, and delivery complexity. -- Prefer defense in depth when security, reliability, and delivery risk intersect across runtime, IAM, networking, and automation. +- When security, reliability, and delivery risk intersect across runtime, IAM, networking, and automation, keep the remediation layered instead of relying on one fix. - Trace root cause before suggesting migrations, service swaps, or broader refactors. - Prefer `internal-architect` when the cloud-provider choice is still open or the question is cross-cloud rather than GCP-specific. - Prefer `internal-infrastructure` when the main task is direct Terraform, Kubernetes, or delivery implementation rather than principal-level GCP guidance. diff --git a/.github/agents/internal-gcp-platform-strategy.agent.md b/.github/agents/internal-gcp-platform-strategy.agent.md index 78c13b6..08f6048 100644 --- a/.github/agents/internal-gcp-platform-strategy.agent.md +++ b/.github/agents/internal-gcp-platform-strategy.agent.md @@ -17,17 +17,13 @@ You are the strategic GCP command center for platform topology, governance direc - `internal-devops-core-principles` - `internal-pair-architect` - `obra-brainstorming` -- `obra-preserving-productive-tensions` -- `obra-defense-in-depth` - `obra-writing-plans` - `obra-verification-before-completion` ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane GCP strategy toolkit: use `obra-*` for option framing, tradeoff preservation, planning, and verification; use `internal-*` as the tactical owners for repository-aligned rollout and impact analysis; use imported skills only for narrow GCP architectural support. +- Treat preferred or optional skills as a three-lane GCP strategy toolkit: use `obra-*` for option framing, rollout planning, and verification; use `internal-*` as the tactical owners for repository-aligned rollout and impact analysis; use imported skills only for narrow GCP architectural support. - `obra-brainstorming`: Use when the GCP strategy question is exploratory or under-specified and viable options need to be surfaced before convergence. -- `obra-preserving-productive-tensions`: Use when multiple valid GCP operating models remain viable, such as stronger centralization versus team autonomy or tighter guardrails versus faster delivery. -- `obra-defense-in-depth`: Use when the strategic answer must layer organization policy, IAM boundaries, network segmentation, detective controls, and rollout protections rather than rely on one control surface. - `obra-writing-plans`: Use when the recommendation needs a phased adoption path, migration sequence, or platform-governance rollout with explicit checkpoints. - `obra-verification-before-completion`: Use before finalizing the answer when it mixes current GCP facts, inferred constraints, and staged implementation guidance. - `internal-terraform`: Use when the strategic target state must become landing-zone rollout guidance, policy deployment sequencing, or infrastructure delivery guardrails. diff --git a/.github/agents/internal-infrastructure.agent.md b/.github/agents/internal-infrastructure.agent.md index 45166e9..fe6faae 100644 --- a/.github/agents/internal-infrastructure.agent.md +++ b/.github/agents/internal-infrastructure.agent.md @@ -12,8 +12,8 @@ You are the infrastructure delivery command center for IaC, container, cluster, ## Preferred/Optional Skills -- `obra-when-stuck` -- `obra-defense-in-depth` +- `obra-writing-plans` +- `obra-systematic-debugging` - `obra-verification-before-completion` - `internal-terraform` - `terraform-terraform-test` @@ -26,9 +26,9 @@ You are the infrastructure delivery command center for IaC, container, cluster, ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane infrastructure toolkit: use `obra-*` for unblock strategy, layered safeguards, and evidence discipline; use `internal-*` as the tactical owners for repository-aligned infrastructure work; use imported skills only for exact support surfaces not already owned internally. -- `obra-when-stuck`: Use when rollout or troubleshooting work is blocked and the safest next move is to reframe, narrow scope, or unblock systematically. -- `obra-defense-in-depth`: Use when the infrastructure change needs layered security controls instead of relying on a single guardrail or validation point. +- Treat preferred or optional skills as a three-lane infrastructure toolkit: use `obra-*` for rollout planning, root-cause-first troubleshooting, and evidence discipline; use `internal-*` as the tactical owners for repository-aligned infrastructure work; use imported skills only for exact support surfaces not already owned internally. +- `obra-writing-plans`: Use when the infrastructure change needs phased rollout sequencing, explicit validation points, or rollback-aware implementation order. +- `obra-systematic-debugging`: Use when rollout or troubleshooting work is blocked by an unclear failure mode and the safest next move is disciplined investigation. - `obra-verification-before-completion`: Use before claiming rollout, hardening, or recovery guidance is complete so the validation and rollback path are explicit. - `internal-terraform`: Use when the task is Terraform authoring, refactoring, validation, module design, or state-safe infrastructure rollout guidance. - `internal-docker`: Use when the task is about Dockerfiles, Compose assets, image hardening, or container build/runtime strategy. @@ -42,7 +42,7 @@ You are the infrastructure delivery command center for IaC, container, cluster, ## Routing Rules - Use this agent when the user needs infrastructure authoring, hardening, rollout planning, or troubleshooting. -- Use the `obra-*` lane when the infrastructure work is blocked, needs layered safeguards, or requires explicit evidence before closure. +- Use the `obra-*` lane when the infrastructure work needs phased rollout planning, root-cause-first troubleshooting, or explicit evidence before closure. - Use `internal-terraform` as the canonical Terraform owner; add `terraform-terraform-test` or `terraform-terraform-search-import` only when the task specifically needs those workflows. - Use imported infrastructure skills as support-only specialists, not as peer owners for domains already covered by repository-owned internal skills. - Use the cloud-policy skill when the infrastructure task includes guardrails, organization policy, or policy-as-code changes. diff --git a/.github/agents/internal-quality-engineering.agent.md b/.github/agents/internal-quality-engineering.agent.md index ad5e915..3c43bef 100644 --- a/.github/agents/internal-quality-engineering.agent.md +++ b/.github/agents/internal-quality-engineering.agent.md @@ -13,12 +13,9 @@ You are the command center for quality engineering, performance, and observabili ## Preferred/Optional Skills - `obra-dispatching-parallel-agents` -- `obra-when-stuck` - `obra-verification-before-completion` - `obra-systematic-debugging` -- `obra-root-cause-tracing` - `obra-test-driven-development` -- `obra-testing-anti-patterns` - `internal-project-java` - `internal-project-nodejs` - `internal-project-python` @@ -27,14 +24,11 @@ You are the command center for quality engineering, performance, and observabili ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane quality-engineering toolkit: use `obra-*` for decomposition, unblock strategy, debugging, testing discipline, and verification; use `internal-*` as the tactical owners for language-specific quality and performance work; use imported skills only for observability support that still adds distinct depth. +- Treat preferred or optional skills as a three-lane quality-engineering toolkit: use `obra-*` for decomposition, debugging, testing discipline, and verification; use `internal-*` as the tactical owners for language-specific quality and performance work; use imported skills only for observability support that still adds distinct depth. - `obra-dispatching-parallel-agents`: Use when independent quality investigations, benchmark tracks, or test-coverage work can be split safely into parallel subproblems. -- `obra-when-stuck`: Use when diagnosis stalls and the safest move is to unblock before adding more speculative test or performance changes. - `obra-verification-before-completion`: Use before claiming improvement so tests, benchmarks, traces, or measurements are actually checked. - `obra-systematic-debugging`: Use when the failure mode or bottleneck cause is not yet understood and the work needs a structured investigation path. -- `obra-root-cause-tracing`: Use when flaky tests, regressions, or performance symptoms likely originate deeper than the visible symptom. - `obra-test-driven-development`: Use when the safest path is to drive the change from a failing test or an explicit missing-behavior test case. -- `obra-testing-anti-patterns`: Use when tests may be brittle, over-mocked, mis-scoped, or otherwise giving false confidence. - `internal-project-java`: Use when Java or Spring testing shape, code structure, or runtime-specific quality decisions influence the answer. - `internal-project-nodejs`: Use when Node.js or TypeScript testing shape, async behavior, or runtime-specific quality decisions influence the answer. - `internal-project-python`: Use when Python test-shape, coverage, or module-boundary quality decisions influence the answer. diff --git a/.github/agents/internal-sync-control-center.agent.md b/.github/agents/internal-sync-control-center.agent.md index 60decba..cbb7aa0 100644 --- a/.github/agents/internal-sync-control-center.agent.md +++ b/.github/agents/internal-sync-control-center.agent.md @@ -10,7 +10,7 @@ tools: ["read", "edit", "search", "execute", "web", "agent"] You are the source-side command center for this repository's Copilot customization catalog and `.github/` governance surface. -Use the current repository state as the bootstrap input for catalog analysis, not as the only long-term source of truth. The durable contract is the combination of this agent, `.github/copilot-instructions.md`, root `AGENTS.md`, and the managed resource map declared below. When sync work is requested, compare the repo state against that contract, then update both the catalog and the governance files together. +Use the current repository state as the bootstrap input for catalog analysis, not as the only long-term source of truth. The durable contract is the combination of this agent, `.github/copilot-instructions.md`, root `AGENTS.md`, `.github/obra-superpowers-source-of-truth.json`, and the managed resource map declared below. When sync work is requested, compare the repo state against that contract, then update both the catalog and the governance files together. Treat root `AGENTS.md` and `.github/copilot-instructions.md` as governed sync targets, not just reference inputs. When managed catalog changes create drift or stale policy references, update those files in the same sync pass. Treat `.github/copilot-instructions.md` as the primary detailed GitHub Copilot policy layer and root `AGENTS.md` as the lightweight bridge for naming, routing, discovery, and inventory. If detailed policy, validation, or workflow guidance needs revision, update `.github/copilot-instructions.md` first and refresh root `AGENTS.md` second. @@ -19,9 +19,11 @@ Treat `.github/skills/internal-skill-management/SKILL.md` as the default workflo ## Preferred/Optional Skills -- `obra-simplification-cascades` -- `obra-meta-pattern-recognition` +- `obra-brainstorming` +- `obra-writing-plans` - `obra-executing-plans` +- `obra-verification-before-completion` +- `obra-writing-skills` - `internal-skill-management` - `internal-copilot-audit` - `internal-agent-development` @@ -46,18 +48,22 @@ Treat `.github/skills/internal-skill-management/SKILL.md` as the default workflo - When the intended managed scope changes, update this file so the policy remains self-consistent over time. - Govern the catalog with the declared three-layer model: `obra-*` for strategic framing, `internal-*` for tactical ownership, and imported non-`internal-*` assets for support-only depth. - Treat `internal-pr-editor` as intentionally prompt-routed and `internal-data-registry` as intentionally dormant tactical capacity until a concrete routing owner is declared. -- Treat explicitly dormant `obra-*` skills as installed-but-unrouted on purpose; do not paper over that state with decorative routing. +- Use `.github/obra-superpowers-source-of-truth.json` as the pinned OBRA scope contract and treat any stale OBRA mapping or reference as blocking drift. - Before changing root `AGENTS.md`, decide whether the change belongs in `.github/copilot-instructions.md`; if it does, update `.github/copilot-instructions.md` first through `internal-ai-resource-creator`, then refresh root `AGENTS.md` through `internal-agents-md-bridge`. - When any managed resource changes, always re-check `.github/copilot-instructions.md` and root `AGENTS.md` for drift, stale references, and routing fallout in the same sync workflow. - Do not call a run `apply` unless `internal-copilot-audit` has completed its mandatory preflight and no unresolved `blocking` findings remain. - Do not report `apply` as complete unless the final output states whether `.github/copilot-instructions.md` and root `AGENTS.md` were reviewed, changed, or intentionally left unchanged. +- When a sync workflow needs a retained plan or auxiliary support file, write it under repository-root `tmp/` and create the directory if it does not exist. +- Do not report any completed governance or sync operation unless the final response ends with `✅ Outcome`, `🤖 Agents`, `📘 Instructions`, and `🧩 Skills`. If a category was not used, explicitly say so and explain why. ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane governance toolkit: use `obra-*` to simplify the catalog model, surface recurring drift patterns, and execute approved plans deliberately; use `internal-*` as the repository-owned tactical owners; use imported skills only for the narrow support role still declared by managed scope. -- `obra-simplification-cascades`: Use when one stronger governance rule can eliminate multiple overlaps, aliases, or decorative routing cases. -- `obra-meta-pattern-recognition`: Use when the same drift or layering problem appears across agents, skills, prompts, and governance files and should collapse into one reusable rule. +- Treat preferred or optional skills as a three-lane governance toolkit: use `obra-*` to frame catalog decisions, plan multi-step changes, execute approved batches deliberately, keep skill authoring aligned to upstream, and verify outcomes; use `internal-*` as the repository-owned tactical owners; use imported skills only for the narrow support role still declared by managed scope. +- `obra-brainstorming`: Use when the catalog direction is still open and the sync needs option framing, tradeoffs, or replacement candidates before deciding. +- `obra-writing-plans`: Use when the sync needs a staged governance plan with explicit file batches, checkpoints, or cleanup order. - `obra-executing-plans`: Use when the user already supplied a concrete catalog plan and the sync should apply it in deliberate batches instead of ad hoc edits. +- `obra-verification-before-completion`: Use before reporting apply success so catalog state, governance updates, and validation outcomes are backed by fresh evidence. +- `obra-writing-skills`: Use when the sync refreshes an imported skill bundle, extracts repo logic into a skill, or materially rewrites one skill as part of catalog governance. - `internal-skill-management`: Default operating workflow for `keep`, `update`, `extract`, and `retire` decisions across the managed catalog. - `internal-copilot-audit`: Mandatory preflight before any `apply`; classify findings as `blocking` or `non-blocking`; block `apply` when decorative skills, hollow references, or skipped governance review remain unresolved. - `internal-agent-development`: Use only when the sync changes an agent file, modifies agent routing boundaries, or rewrites skill-guidance sections or contracts. @@ -112,40 +118,28 @@ Managed instructions: Source repository: -- Skills: `https://github.com/obra/superpowers/tree/main/skills` +- Skills: `https://github.com/obra/superpowers/tree/v5.0.7/skills` + +Pinned source-of-truth file: + +- `.github/obra-superpowers-source-of-truth.json` Managed skills: - `brainstorming` -> `obra-brainstorming` -- `collision-zone-thinking` -> `obra-collision-zone-thinking` -- `condition-based-waiting` -> `obra-condition-based-waiting` -- `defense-in-depth` -> `obra-defense-in-depth` - `dispatching-parallel-agents` -> `obra-dispatching-parallel-agents` - `executing-plans` -> `obra-executing-plans` - `finishing-a-development-branch` -> `obra-finishing-a-development-branch` -- `gardening-skills-wiki` -> `obra-gardening-skills-wiki` -- `inversion-exercise` -> `obra-inversion-exercise` -- `meta-pattern-recognition` -> `obra-meta-pattern-recognition` -- `preserving-productive-tensions` -> `obra-preserving-productive-tensions` -- `pulling-updates-from-skills-repository` -> `obra-pulling-updates-from-skills-repository` - `receiving-code-review` -> `obra-receiving-code-review` -- `remembering-conversations` -> `obra-remembering-conversations` - `requesting-code-review` -> `obra-requesting-code-review` -- `root-cause-tracing` -> `obra-root-cause-tracing` -- `scale-game` -> `obra-scale-game` -- `sharing-skills` -> `obra-sharing-skills` -- `simplification-cascades` -> `obra-simplification-cascades` - `subagent-driven-development` -> `obra-subagent-driven-development` - `systematic-debugging` -> `obra-systematic-debugging` - `test-driven-development` -> `obra-test-driven-development` -- `testing-anti-patterns` -> `obra-testing-anti-patterns` -- `testing-skills-with-subagents` -> `obra-testing-skills-with-subagents` -- `tracing-knowledge-lineages` -> `obra-tracing-knowledge-lineages` - `using-git-worktrees` -> `obra-using-git-worktrees` -- `using-skills` -> `obra-using-skills` +- `using-superpowers` -> `obra-using-superpowers` - `verification-before-completion` -> `obra-verification-before-completion` -- `when-stuck` -> `obra-when-stuck` - `writing-plans` -> `obra-writing-plans` +- `writing-skills` -> `obra-writing-skills` ### `hashicorp/agent-skills` @@ -194,6 +188,7 @@ Managed skills: - This agent file, including the managed resource map above - Root `AGENTS.md` for routing, naming, and inventory - `.github/copilot-instructions.md` for non-negotiable policy +- `.github/obra-superpowers-source-of-truth.json` for the pinned OBRA skill scope - `.github/scripts/validate-copilot-customizations.py` for structural validation - The actual `.github/` catalog on disk as audit input and execution target @@ -204,7 +199,7 @@ When repository state drifts from the declared governance contract, treat the dr - Use this agent when creating, refreshing, renaming, consolidating, or retiring `.github/` Copilot assets in this repository. - Use this agent when the task is about catalog coherence, naming normalization, overlap removal, governance drift, or repo-owned replacements. - Use this agent when declared approved external-prefixed assets need to be refreshed, reduced, or normalized without expanding scope. -- Start with the strategic lane when the catalog problem is really about recurring overlap, excessive exceptions, or a user-supplied multi-step remediation plan. +- Start with the strategic lane when the catalog problem needs option framing, a staged governance plan, specific skill-refresh work, or a user-supplied multi-step remediation plan. - When a governance change depends on current GitHub Copilot or MCP platform behavior, validate it through `internal-copilot-docs-research` before hardening the repo policy. - Treat `sync` as `apply` by default unless the user explicitly asks for an audit, plan, or dry run. - Treat `apply` as invalid until `internal-copilot-audit` has completed its preflight and any remaining `blocking` findings are resolved. @@ -233,10 +228,26 @@ If a rule exists only to preserve history, remove it unless the current reposito ## Output Expectations +End every completed run with the completion-report contract below. +If a category was not used, explicitly say so and explain why. + +### ✅ Outcome + - `Mode`: `apply`, `audit`, or `plan` - `Catalog scope`: files reviewed and why -- `Skills invoked`: which declared skills were used and why - `Governance files reviewed`: whether `.github/copilot-instructions.md` and root `AGENTS.md` were reviewed, changed, or intentionally left unchanged - `Canonical decisions`: `keep`, `update`, `extract`, `retire` - `Validation`: commands run and remaining gaps - `Remaining blockers or drift`: unresolved issues that prevent or narrow `apply` + +### 🤖 Agents + +- `Agents used`: which agents were used and why. If none were used, say so and explain why. + +### 📘 Instructions + +- `Instructions used`: which instruction or policy files shaped the run and why. If none were used, say so and explain why. + +### 🧩 Skills + +- `Skills invoked`: which declared skills were used and why. If none were used, say so and explain why. diff --git a/.github/agents/internal-sync-global-copilot-configs-into-repo.agent.md b/.github/agents/internal-sync-global-copilot-configs-into-repo.agent.md index ae18cf5..d9d2571 100644 --- a/.github/agents/internal-sync-global-copilot-configs-into-repo.agent.md +++ b/.github/agents/internal-sync-global-copilot-configs-into-repo.agent.md @@ -1,5 +1,5 @@ --- -description: Propagate the shared Copilot baseline from this standards repo into a consumer repo. Keep `.github/copilot-instructions.md` as the primary policy layer and keep root `AGENTS.md` intentionally light as a bridge that routes assistants to the Copilot-owned configuration. +description: Mirror the shared Copilot catalog from this standards repo into a consumer repo. Treat source assets under `.github/agents`, `.github/instructions`, `.github/prompts`, and `.github/skills` as authoritative, preserve only target `local-*` assets, keep `.github/copilot-instructions.md` primary, and keep root `AGENTS.md` as a light bridge. name: internal-sync-global-copilot-configs-into-repo tools: ["read", "edit", "search", "execute", "web", "agent"] --- @@ -7,11 +7,11 @@ tools: ["read", "edit", "search", "execute", "web", "agent"] # Internal Sync Copilot Configs Agent ## Objective -Analyze a local target repository, select the minimum Copilot customization assets from this standards repository, and align them with conservative merge rules plus a final report that also audits unmanaged target-local Copilot assets. For target-repository root guidance, keep `.github/copilot-instructions.md` as the primary detailed policy file and keep root `AGENTS.md` intentionally light as a bridge that helps generic coding assistants discover and apply the Copilot configuration without duplicating it. +Analyze a local target repository, mirror the full Copilot customization catalog from this standards repository, and align it with source-authoritative rules plus a final report that calls out preserved target `local-*` assets. This agent is target-agnostic: it only assumes the target stores Copilot resources under `.github/` and keeps `AGENTS.md` at repository root. For target-repository root guidance, keep `.github/copilot-instructions.md` as the primary detailed policy file and keep root `AGENTS.md` intentionally light as a bridge that helps generic coding assistants discover and apply the Copilot configuration without duplicating it. ## Preferred/Optional Skills +- `obra-writing-plans` - `obra-executing-plans` -- `obra-condition-based-waiting` - `obra-verification-before-completion` - `internal-sync-global-copilot-configs-into-repo` - `internal-copilot-audit` @@ -19,11 +19,11 @@ Analyze a local target repository, select the minimum Copilot customization asse - `internal-agents-md-bridge` ## Skill Usage Contract -- Treat preferred or optional skills as a three-lane sync toolkit: use `obra-*` for staged execution, condition gates, and evidence discipline; use `internal-*` as the tactical sync owners; no imported support lane is declared here unless the user explicitly expands scope. +- Treat preferred or optional skills as a three-lane sync toolkit: use `obra-*` for staged planning, staged execution, and evidence discipline; use `internal-*` as the tactical sync owners; no imported support lane is declared here unless the user explicitly expands scope. +- `obra-writing-plans`: Use when the sync needs a retained tracking plan with explicit phases, checks, or cleanup order before apply starts. - `obra-executing-plans`: Use when the source-to-target sync already has a concrete plan and should run in deliberate batches. -- `obra-condition-based-waiting`: Use when the next sync step depends on explicit repository state, approvals, validation completion, or other asynchronous readiness conditions. - `obra-verification-before-completion`: Use before reporting apply success so sync actions, file outcomes, and validation results are grounded in fresh evidence. -- `internal-sync-global-copilot-configs-into-repo`: Use as the workflow anchor for stack detection, manifest rules, conflict analysis, and conservative merge behavior. +- `internal-sync-global-copilot-configs-into-repo`: Use as the workflow anchor for full source mirroring, manifest rules, local-asset preservation, and deterministic reporting. - `internal-copilot-audit`: Use when source or target catalogs show overlap, hollow references, stale inventory, or bridge drift that changes the sync recommendation. - `internal-copilot-docs-research`: Use when source or target decisions depend on current GitHub Copilot or MCP behavior rather than repository-local policy alone. - `internal-agents-md-bridge`: Use when root `AGENTS.md` needs to be generated, reviewed, or refreshed as a thin bridge after `.github/copilot-instructions.md` is updated. @@ -31,29 +31,54 @@ Analyze a local target repository, select the minimum Copilot customization asse ## Restrictions - Do not modify `README.md` files unless explicitly requested. - Do not sync workflows, templates, changelog files, or bootstrap helpers in v1. -- Do not overwrite unmanaged divergent files. +- Do not preserve target-owned non-`local-*` resources under mirrored categories; remove them so the mirrored source catalog stays authoritative. - Keep repository-facing text in English and use GitHub Copilot terminology only. -- Do not remove, flatten, or silently rewrite target-local resources or target-local configuration that sit outside the managed sync baseline; preserve them unless an explicit conflict-safe migration is part of the plan. +- Do not remove, flatten, or silently rewrite target `local-*` resources or target-local configuration that sit outside the mirrored source catalog; preserve them unless an explicit migration is part of the plan. - Do not let root `AGENTS.md` become a second full copy of `.github/copilot-instructions.md`; keep detailed operational policy in the Copilot files first and use `AGENTS.md` only as the bridge layer that points assistants to them. - Do not describe the target repository as using a specific assistant runtime inside `AGENTS.md`; keep the bridge tool-agnostic and lightweight. +- Do not report a completed sync unless the final response ends with `✅ Outcome`, `🤖 Agents`, `📘 Instructions`, and `🧩 Skills`. If a category was not used, explicitly say so and explain why. ## Routing - Use this agent only for cross-repository Copilot-core alignment work. -- Use the `obra-*` lane when the sync must follow a supplied plan, wait on explicit readiness conditions, or prove completion from fresh validation evidence. +- Use the `obra-*` lane when the sync must produce or follow an explicit plan and prove completion from fresh validation evidence. - Treat `.github/skills/internal-sync-global-copilot-configs-into-repo/SKILL.md` as the tactical workflow anchor for this agent. - Use `internal-copilot-audit` when source-side overlap, hollow references, or bridge drift affect the baseline you plan to propagate. - When source or target decisions depend on current GitHub Copilot or MCP behavior, validate them through `internal-copilot-docs-research` before updating policy files. - Treat `.github/scripts/internal-sync-copilot-configs.py` as the deterministic execution path. - Start with `plan` mode and move to `apply` only on explicit request and only when the plan is conflict-safe. -- When the target sync includes root guidance files, refresh target `.github/copilot-instructions.md` through the repository-local authoring workflow anchored in `internal-ai-resource-creator`, then refresh `internal-agents-md-bridge` before updating target root `AGENTS.md`. -- In target repositories, update `.github/copilot-instructions.md` before root `AGENTS.md`, and keep target-local unmanaged assets visible and preserved in the final plan or apply report. +- Mirror every source asset under `.github/agents`, `.github/instructions`, `.github/prompts`, and `.github/skills`, including skill support files such as `references/`, `assets/`, and `scripts/`. +- Preserve only target `local-*` assets under mirrored categories; delete other target-only assets under those categories during apply. +- Before changing mirrored target assets, write `tmp/internal-sync-copilot-configs.plan.md` in the target repository with the planned operations and checks. +- When the sync needs retained auxiliary support files in addition to the tracking plan, place them under repository-root `tmp/` and create the directory if it does not exist. +- After apply, re-check the plan objectives; remove completed sections from `tmp/internal-sync-copilot-configs.plan.md`, delete the whole file only when nothing remains pending, otherwise keep it for user follow-up. +- When the target sync includes root guidance files, rebuild target `.github/copilot-instructions.md` through the repository-local authoring workflow anchored in `internal-ai-resource-creator`, then refresh `internal-agents-md-bridge` before updating target root `AGENTS.md`. +- In target repositories, update `.github/copilot-instructions.md` before root `AGENTS.md`, and keep preserved target `local-*` assets visible in the final plan or apply report. ## Output Expectations + +End every completed run with the completion-report contract below. +If a category was not used, explicitly say so and explain why. + +### ✅ Outcome + - `Target analysis`: repo shape, selected profile, stacks, git state, and AGENTS location. -- `Root guidance strategy`: how target `.github/copilot-instructions.md` remains primary, how root `AGENTS.md` bridges to it, and which local target assets must remain untouched. +- `Root guidance strategy`: how target `.github/copilot-instructions.md` remains primary, how root `AGENTS.md` bridges to it, and which target `local-*` assets must remain untouched. +- `Tracking plan`: the content and lifecycle of `tmp/internal-sync-copilot-configs.plan.md` for the target repository. - `Source audit`: canonical assets, legacy aliases, role overlaps, AGENTS.md repeats, and source-side recommendations. -- `Asset selection`: instructions, prompts, skills, agents, and baseline files chosen from the source repository. -- `Unmanaged target asset issues`: target-local instructions, prompts, skills, or agents outside the selected sync baseline, including strict validation gaps, origin-prefix naming violations for repository-owned prompt/skill/agent assets, and legacy alias drift. -- `Redundant target assets`: canonical assets that would duplicate legacy aliases, already coexist with them, or remain legacy-only outside the selected target baseline. -- `File actions`: create, update, adopt, unchanged, and conflict results. +- `Asset selection`: all mirrored instructions, prompts, skills, skill support files, agents, and baseline files sourced from the standards repository. +- `Unmanaged target asset issues`: preserved target `local-*` instructions, prompts, skills, or agents, including strict validation gaps and origin-prefix naming violations. +- `Redundant target assets`: legacy aliases or duplicates found in the target catalog before cleanup. +- `File actions`: create, update, adopt, unchanged, and delete results. - `Recommendations`: categorized source-repository improvements. + +### 🤖 Agents + +- `Agents used`: which agents were used during the sync workflow and why. If none were used, say so and explain why. + +### 📘 Instructions + +- `Instructions used`: which instruction or policy files shaped the sync or target-guidance refresh and why. If none were used, say so and explain why. + +### 🧩 Skills + +- `Skills invoked`: which declared skills were used and why. If none were used, say so and explain why. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b05c48c..e6e616b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -21,6 +21,24 @@ You are an expert software and platform engineer. You are the user's technical p - Keep repository-facing wording GitHub Copilot-based and do not make repository artifacts say or imply that the repository uses a different assistant runtime. - If detailed policy, validation, or workflow guidance is duplicated in root `AGENTS.md`, move that detail here and keep only the bridge-level pointer there. +## Operation Completion Report +- After every completed operation, end with a concise completion report. +- The completion report may follow the user's chat language. +- If a category was not used, explicitly say so and explain why. +- Keep the report specific to the completed operation; do not paste full repository inventories. + +### ✅ Outcome +- Summarize what changed, what was verified, or what remains blocked. + +### 🤖 Agents +- State which agents were used and why they were relevant to the completed operation. + +### 📘 Instructions +- State which instructions were used and why they mattered, including `.github/copilot-instructions.md` when it materially shaped the work. + +### 🧩 Skills +- State which skills were used and why they were relevant to the completed operation. + ## Catalog layering model - Use a three-layer model for command-center routing and skill contracts: - `obra-*` skills are the strategic lane for framing, decomposition, planning, simplification, and verification discipline. @@ -105,8 +123,16 @@ These apply to every code change, regardless of language or technology: - Use simple control flow and early returns. - Bash: always `#!/usr/bin/env bash` (never POSIX `sh`). - Python: add unit tests for testable logic. +- Python dependency choice for new scripts: evaluate standard library vs mature third-party libraries explicitly before implementation. Do not treat `stdlib-first` as an absolute default. +- Python dependency choice for new scripts: prefer a mature, well-maintained, widely used third-party library when it clearly reduces boilerplate, edge-case handling, or custom logic in the final code. +- Python dependency choice for new scripts: keep the standard library only when the resulting implementation is genuinely simpler, more readable, and safer than the third-party alternative. +- Python dependency choice for new scripts: optimize for simpler final code and less bespoke logic, not for the lowest possible dependency count. +- Python dependency choice for new scripts: do not hand-roll parsing, validation, CLI handling, serialization, HTTP clients, retry behavior, date handling, table rendering, Excel/CSV processing, or formatting when a mature library is the clearer solution. +- Python dependency choice for new scripts: before writing code, include a short dependency decision note that lists candidate libraries, the final choice, and the reason for that choice. - Python dependencies: when external packages are introduced, standardize on a compiled `requirements.txt` with exact pins, full transitive dependency closure, and `--hash` entries, plus short comment lines that make the pinned versions readable to humans. -- Python dependencies: third-party libraries are recommended when they materially simplify parsing, validation, HTTP, CLI, serialization, or retry logic; keep the standard library when it is simpler and safer. +- Python dependencies: if the dependency decision note selects external packages, create or update the local `requirements.txt` consistently with the repository lock-file policy. +- Python dependencies: third-party libraries are recommended when they materially simplify parsing, validation, HTTP, CLI, serialization, retry logic, date handling, table rendering, Excel/CSV processing, or formatting; keep the standard library when it is simpler and safer. +- Python dependencies: avoid marginal or unjustified packages; each dependency should earn its place through a clear value-versus-setup tradeoff. - New standalone Python scripts should default to a self-contained folder that includes the Python entry point, a Bash launcher, and a local `requirements.txt` only when external packages are required. The launcher should bootstrap `.venv` and install from `requirements.txt` only when that file exists. - Prefer immutable dependency and image pins; keep stack-specific locking details in the matching instruction file. @@ -134,4 +160,6 @@ These apply to every code change, regardless of language or technology: - Keep assistant-facing language mapped through `AGENTS.md` and avoid mentioning internal runtime names. - `internal-pr-editor` remains intentionally prompt-routed; keep PR body generation on the prompt-plus-skill path unless the repository adds a dedicated agent. - `internal-data-registry` remains intentionally dormant tactical capacity until the repository adds a concrete routing owner. -- Keep these installed `obra-*` skills intentionally dormant until a concrete workflow owner is declared: `obra-gardening-skills-wiki`, `obra-pulling-updates-from-skills-repository`, `obra-sharing-skills`, `obra-testing-skills-with-subagents`, `obra-using-skills`, and `obra-remembering-conversations`. +- Use `.github/obra-superpowers-source-of-truth.json` as the pinned OBRA import contract; stale OBRA mappings or references should fail validation instead of drifting silently. +- Keep `obra-using-superpowers` as the repository-wide OBRA bootstrap reference and `obra-writing-skills` as the skill-authoring reference; do not pad unrelated agent skill lists with them decoratively. +- For sync workflows that need retained plans or auxiliary output files, use repository-root `tmp/` and create it if missing instead of scattering those artifacts under governed `.github/` paths. diff --git a/.github/instructions/internal-python.instructions.md b/.github/instructions/internal-python.instructions.md index 2f9588f..248360c 100644 --- a/.github/instructions/internal-python.instructions.md +++ b/.github/instructions/internal-python.instructions.md @@ -9,8 +9,10 @@ applyTo: "**/*.py" - Use emoji logs for key execution states. - Prefer early return and clear guard clauses. - Keep code explicit and readable. -- Prefer the standard library first. Introduce well-maintained third-party libraries when they materially simplify code and reduce custom logic. -- Do not reinvent common parsing/validation/serialization behavior when the standard library or a well-maintained library provides a clearer solution. +- For new Python scripts, explicitly evaluate the standard library versus mature third-party libraries before implementation; `stdlib-first` is not an absolute default. +- Prefer a mature, well-maintained, widely used third-party library when it materially reduces boilerplate, edge cases, or custom logic in the final code. +- Keep the standard library only when the final implementation is genuinely simpler, more readable, and safer. +- Do not reinvent common parsing, validation, CLI handling, serialization, HTTP, retry, date handling, table rendering, Excel/CSV processing, or formatting behavior when a standard-library or mature third-party solution is clearer. - Prefer simple, readable, and easily modifiable code over clever abstractions. - Accept additional lines or mild redundancy when it improves clarity, maintainability, and safe future changes. - Unit tests are required for testable logic. @@ -27,6 +29,8 @@ applyTo: "**/*.py" - Start scripts with a module docstring containing purpose and usage examples. - Keep CLI parsing and orchestration explicit. - Avoid embedding domain rules that belong to reusable application modules. +- Before writing a new script, produce a short dependency decision note with candidate libraries, the final choice, and the reason for the choice. +- Optimize for the simplest final script, not for the smallest dependency list. ## Style - Follow PEP8. @@ -38,10 +42,13 @@ applyTo: "**/*.py" ## Dependencies - Standardize on `requirements.txt` as the Python dependency lock artifact in this baseline. +- For new scripts, evaluate stdlib versus third-party options explicitly before coding and record that choice in the dependency decision note. - If external libraries are introduced, prefer a compiled `requirements.txt` with exact `==` pins, full transitive dependency closure, and `--hash` entries for every locked requirement. - Keep a short comment above each introduced dependency block so the pinned version is readable without parsing the full hash line. -- Recommend third-party libraries when they materially reduce custom parsing, validation, HTTP, CLI, serialization, or retry code. -- Do not force third-party libraries over the standard library when the standard library is simpler, clearer, or safer. +- Recommend third-party libraries when they materially reduce custom parsing, validation, HTTP, CLI, serialization, retry, date handling, table rendering, Excel/CSV, or formatting code. +- If the dependency decision note selects external libraries, create or update the local `requirements.txt` accordingly. +- Do not force third-party libraries over the standard library when the standard library is simpler, clearer, or safer in the final implementation. +- Avoid marginal dependencies whose setup cost exceeds their practical simplification value. - When a fully hash-locked `requirements.txt` is not feasible, use exact `==` pins in `requirements.txt` and document the reason in the closest technical note or workflow comment. ## Dependency example diff --git a/.github/obra-superpowers-source-of-truth.json b/.github/obra-superpowers-source-of-truth.json new file mode 100644 index 0000000..4eafba1 --- /dev/null +++ b/.github/obra-superpowers-source-of-truth.json @@ -0,0 +1,63 @@ +{ + "version": 1, + "source_repository": "https://github.com/obra/superpowers", + "source_ref": "v5.0.7", + "managed_skills": [ + { + "upstream": "brainstorming", + "local": "obra-brainstorming" + }, + { + "upstream": "dispatching-parallel-agents", + "local": "obra-dispatching-parallel-agents" + }, + { + "upstream": "executing-plans", + "local": "obra-executing-plans" + }, + { + "upstream": "finishing-a-development-branch", + "local": "obra-finishing-a-development-branch" + }, + { + "upstream": "receiving-code-review", + "local": "obra-receiving-code-review" + }, + { + "upstream": "requesting-code-review", + "local": "obra-requesting-code-review" + }, + { + "upstream": "subagent-driven-development", + "local": "obra-subagent-driven-development" + }, + { + "upstream": "systematic-debugging", + "local": "obra-systematic-debugging" + }, + { + "upstream": "test-driven-development", + "local": "obra-test-driven-development" + }, + { + "upstream": "using-git-worktrees", + "local": "obra-using-git-worktrees" + }, + { + "upstream": "using-superpowers", + "local": "obra-using-superpowers" + }, + { + "upstream": "verification-before-completion", + "local": "obra-verification-before-completion" + }, + { + "upstream": "writing-plans", + "local": "obra-writing-plans" + }, + { + "upstream": "writing-skills", + "local": "obra-writing-skills" + } + ] +} diff --git a/.github/repo-profiles.yml b/.github/repo-profiles.yml index 06fd185..9607664 100644 --- a/.github/repo-profiles.yml +++ b/.github/repo-profiles.yml @@ -19,7 +19,7 @@ _common_skills: &common_skills - skills/obra-systematic-debugging/SKILL.md - skills/obra-test-driven-development/SKILL.md - skills/obra-using-git-worktrees/SKILL.md - - skills/obra-using-skills/SKILL.md + - skills/obra-using-superpowers/SKILL.md - skills/obra-verification-before-completion/SKILL.md - skills/obra-writing-plans/SKILL.md diff --git a/.github/scripts/internal-sync-copilot-configs.py b/.github/scripts/internal-sync-copilot-configs.py index 016abbf..2ca9633 100644 --- a/.github/scripts/internal-sync-copilot-configs.py +++ b/.github/scripts/internal-sync-copilot-configs.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Purpose: Align portable Copilot customization assets with a local target repository. +"""Purpose: Mirror Copilot customization assets from the standards repository into a local target repository. Usage examples: python .github/scripts/internal-sync-copilot-configs.py --target /path/to/repo @@ -23,8 +23,11 @@ SCRIPT_NAME = "internal-sync-global-copilot-configs-into-repo" MANIFEST_RELATIVE_PATH = ".github/internal-sync-copilot-configs.manifest.json" +TMP_OUTPUT_DIRECTORY = "tmp" +PLAN_RELATIVE_PATH = f"{TMP_OUTPUT_DIRECTORY}/internal-sync-copilot-configs.plan.md" SUPPORTED_SCOPE = "copilot-core" -SUPPORTED_CONFLICT_POLICY = "conservative-merge" +DEFAULT_CONFLICT_POLICY = "source-authoritative-preserve-local" +LEGACY_CONFLICT_POLICY = "conservative-merge" VSCODE_SETTINGS_RELATIVE_PATH = ".vscode/settings.json" PR_DESCRIPTION_SETTING_KEY = "githubPullRequests.pullRequestDescription" PR_DESCRIPTION_SETTING_VALUE = "template" @@ -32,27 +35,12 @@ ".github/copilot-instructions.md", ".github/copilot-commit-message-instructions.md", ".github/copilot-code-review-instructions.md", + ".github/obra-superpowers-source-of-truth.json", ".github/security-baseline.md", ".github/DEPRECATION.md", ".github/repo-profiles.yml", ".github/scripts/validate-copilot-customizations.py", ) -SOURCE_ONLY_AGENT_PATHS = { - ".github/agents/internal-ai-resource-development.agent.md", - ".github/agents/internal-sync-global-copilot-configs-into-repo.agent.md", -} -SOURCE_ONLY_PROMPT_PATHS = { - ".github/prompts/internal-add-platform.prompt.md", - ".github/prompts/internal-add-report-script.prompt.md", -} -SOURCE_ONLY_SKILL_PATHS = { - ".github/skills/internal-agent-development/SKILL.md", - ".github/skills/internal-agents-md-bridge/SKILL.md", - ".github/skills/internal-copilot-audit/SKILL.md", - ".github/skills/internal-skill-management/SKILL.md", - ".github/skills/internal-sync-global-copilot-configs-into-repo/SKILL.md", - ".github/skills/openai-skill-creator/SKILL.md", -} ALWAYS_EXCLUDED_RELATIVE_PATHS = { ".github/README.md", ".github/CHANGELOG.md", @@ -144,6 +132,7 @@ class AssetSelection: prompts: list[str] skills: list[str] agents: list[str] + supporting_files: list[str] baseline_files: list[str] validation_commands: list[str] preferred_prompts: list[str] @@ -158,6 +147,7 @@ def managed_source_paths(self) -> list[str]: | set(self.prompts) | set(self.skills) | set(self.agents) + | set(self.supporting_files) ) @@ -165,7 +155,7 @@ def managed_source_paths(self) -> list[str]: class PlannedFile: source_relative_path: str | None target_relative_path: str - desired_content: str + desired_bytes: bytes category: str generated: bool = False @@ -201,6 +191,22 @@ def action_counts(self) -> dict[str, int]: return counts +@dataclass +class PlanTrackingState: + pending_sync_actions: list[str] + pending_validation_checks: list[str] + pending_manual_follow_up: list[str] + relative_path: str = PLAN_RELATIVE_PATH + + @property + def has_pending_items(self) -> bool: + return bool( + self.pending_sync_actions + or self.pending_validation_checks + or self.pending_manual_follow_up + ) + + @dataclass class RedundantAsset: category: str @@ -281,8 +287,8 @@ class SourceAudit: recommendations: list[str] -def sha256_text(value: str) -> str: - return hashlib.sha256(value.encode("utf-8")).hexdigest() +def sha256_bytes(value: bytes) -> str: + return hashlib.sha256(value).hexdigest() def sha256_path(path: Path) -> str: @@ -358,8 +364,11 @@ def parse_args(argv: list[str]) -> argparse.Namespace: ) parser.add_argument( "--conflict-policy", - default=SUPPORTED_CONFLICT_POLICY, - help=f"Supported value: {SUPPORTED_CONFLICT_POLICY}.", + default=DEFAULT_CONFLICT_POLICY, + help=( + "Supported values: " + f"{DEFAULT_CONFLICT_POLICY} (default), {LEGACY_CONFLICT_POLICY} (legacy alias)." + ), ) parser.add_argument( "--report-format", @@ -373,10 +382,13 @@ def parse_args(argv: list[str]) -> argparse.Namespace: if args.scope != SUPPORTED_SCOPE: raise CliError(f"Unsupported scope '{args.scope}'. Expected '{SUPPORTED_SCOPE}'.") - if args.conflict_policy != SUPPORTED_CONFLICT_POLICY: + if args.conflict_policy == LEGACY_CONFLICT_POLICY: + args.conflict_policy = DEFAULT_CONFLICT_POLICY + + if args.conflict_policy != DEFAULT_CONFLICT_POLICY: raise CliError( "Unsupported conflict policy " - f"'{args.conflict_policy}'. Expected '{SUPPORTED_CONFLICT_POLICY}'." + f"'{args.conflict_policy}'. Expected '{DEFAULT_CONFLICT_POLICY}'." ) return args @@ -487,6 +499,53 @@ def prompt_skill_refs(path: Path) -> list[str]: return sorted(refs) +def extract_markdown_h2_section(text: str, heading: str) -> str | None: + lines = text.splitlines() + inside_section = False + collected: list[str] = [] + + for line in lines: + if re.match(r"^##\s+", line): + if line.strip() == heading: + inside_section = True + collected = [] + continue + if inside_section: + break + + if inside_section: + collected.append(line) + + if not inside_section: + return None + + return "\n".join(collected).strip() + + +def agent_skill_refs(path: Path) -> list[str] | None: + section = extract_markdown_h2_section(path.read_text(encoding="utf-8"), "## Preferred/Optional Skills") + if section is None: + return None + + refs: list[str] = [] + for raw_line in section.splitlines(): + match = re.fullmatch(r"\s*-\s+`([^`]+)`\s*", raw_line) + if match: + refs.append(match.group(1)) + return refs + + +def skill_names_from_selection(relative_paths: set[str]) -> set[str]: + return {Path(relative_path).parent.name for relative_path in relative_paths if relative_path.endswith("/SKILL.md")} + + +def agent_supported_by_selection(source_root: Path, relative_path: str, selected_skill_names: set[str]) -> bool: + required_skills = agent_skill_refs(source_root / relative_path) + if required_skills is None: + return True + return all(skill_name in selected_skill_names for skill_name in required_skills) + + def source_asset_paths(source_root: Path, category: str) -> list[str]: category_root = source_root / ".github" / category if not category_root.is_dir(): @@ -516,6 +575,20 @@ def source_named_assets(source_root: Path, category: str) -> dict[str, str]: return assets +def source_skill_support_paths(source_root: Path) -> list[str]: + skills_root = source_root / ".github" / "skills" + if not skills_root.is_dir(): + return [] + + support_paths: list[str] = [] + for path in skills_root.rglob("*"): + if not path.is_file() or path.name == "SKILL.md": + continue + support_paths.append(str(path.relative_to(source_root))) + + return sorted(support_paths) + + def markdown_named_section_items(path: Path, heading: str) -> list[str]: items: list[str] = [] inside_section = False @@ -617,6 +690,8 @@ def internal_asset_identifier(relative_path: str) -> str | None: path = Path(relative_path) category = asset_category(relative_path) + if category == "instructions" and path.name.endswith(".instructions.md"): + return path.name[: -len(".instructions.md")] if category == "prompts" and path.name.endswith(".prompt.md"): return path.name[: -len(".prompt.md")] if category == "agents" and path.name.endswith(".agent.md"): @@ -626,6 +701,10 @@ def internal_asset_identifier(relative_path: str) -> str | None: return None +def is_local_asset_identifier(identifier: str | None) -> bool: + return bool(identifier and identifier.startswith("local-")) + + def has_supported_origin_prefix(identifier: str) -> bool: return identifier.startswith(("internal-", "local-", "obra-", "terraform-", "tech-ai-")) @@ -635,6 +714,29 @@ def is_internal_asset_path(relative_path: str) -> bool: return bool(identifier and has_supported_origin_prefix(identifier)) +def is_local_asset_path(relative_path: str) -> bool: + path = Path(relative_path) + category = asset_category(relative_path) + + if category == "skills": + parts = path.parts + if len(parts) >= 3 and parts[0] == ".github" and parts[1] == "skills": + return parts[2].startswith("local-") + return False + + return is_local_asset_identifier(internal_asset_identifier(relative_path)) + + +def is_mirrored_resource_path(relative_path: str) -> bool: + if relative_path in MANAGED_ALWAYS: + return True + if relative_path == "AGENTS.md": + return True + + parts = Path(relative_path).parts + return len(parts) >= 3 and parts[0] == ".github" and parts[1] in AGENTS_INVENTORY_CATEGORIES + + def scan_repo_files(repo_root: Path) -> list[Path]: files: list[Path] = [] for path in repo_root.rglob("*"): @@ -648,6 +750,29 @@ def scan_repo_files(repo_root: Path) -> list[Path]: return files +def is_target_analysis_path(relative_path: str) -> bool: + if relative_path == "AGENTS.md": + return False + if relative_path in MANAGED_ALWAYS: + return False + if relative_path in {MANIFEST_RELATIVE_PATH, PLAN_RELATIVE_PATH}: + return False + + parts = Path(relative_path).parts + if len(parts) >= 3 and parts[0] == ".github" and parts[1] in AGENTS_INVENTORY_CATEGORIES: + return False + + return True + + +def scan_target_analysis_files(repo_root: Path) -> list[Path]: + return [ + path + for path in scan_repo_files(repo_root) + if is_target_analysis_path(str(path.relative_to(repo_root))) + ] + + def detect_stacks(repo_root: Path, files: list[Path]) -> tuple[list[str], list[str], dict[str, int]]: extension_counts = { ".tf": 0, @@ -774,7 +899,7 @@ def detect_priority_paths(repo_root: Path, stacks: list[str]) -> list[str]: def rank_content_paths(repo_root: Path) -> list[str]: scores: dict[str, int] = {} - for path in scan_repo_files(repo_root): + for path in scan_target_analysis_files(repo_root): relative = path.relative_to(repo_root) if relative.parts[0].startswith("."): continue @@ -882,6 +1007,8 @@ def detect_target_only_assets(source_root: Path, target_root: Path) -> dict[str, continue if relative_path in manifest_paths: continue + if is_local_asset_path(relative_path): + continue if relative_path not in source_relative_paths: result[category].append(relative_path) return result @@ -1172,7 +1299,7 @@ def audit_source_configuration(source_root: Path) -> SourceAudit: def build_analysis(source_root: Path, target_root: Path, profiles: dict[str, RepoProfile]) -> TargetAnalysis: - files = scan_repo_files(target_root) + files = scan_target_analysis_files(target_root) stacks, unsupported_stacks, extension_counts = detect_stacks(target_root, files) profile_name = detect_profile_name(stacks) if profile_name not in profiles: @@ -1211,106 +1338,13 @@ def build_analysis(source_root: Path, target_root: Path, profiles: dict[str, Rep def select_assets(source_root: Path, analysis: TargetAnalysis, profiles: dict[str, RepoProfile]) -> AssetSelection: profile = profiles[analysis.profile_name] - stacks = set(analysis.stacks) source_preferred_prompts = source_preferred_assets_from_agents_md(source_root, "prompts") source_preferred_skills = source_preferred_assets_from_agents_md(source_root, "skills") - portable_source_instructions = source_asset_paths(source_root, "instructions") - portable_source_agents = { - agent - for agent in source_asset_paths(source_root, "agents") - if agent not in SOURCE_ONLY_AGENT_PATHS - } - instructions = { - ".github/instructions/internal-markdown.instructions.md", - ".github/instructions/internal-yaml.instructions.md", - } - profile_extra_instructions: set[str] = set() - - if "json" in stacks: - instructions.add(".github/instructions/internal-json.instructions.md") - if "bash" in stacks: - instructions.add(".github/instructions/internal-bash.instructions.md") - if "python" in stacks: - instructions.add(".github/instructions/internal-python.instructions.md") - if "terraform" in stacks: - instructions.add(".github/instructions/internal-terraform.instructions.md") - if "github-actions" in stacks: - instructions.add(".github/instructions/internal-github-actions.instructions.md") - if "composite-action" in stacks: - instructions.add(".github/instructions/internal-github-action-composite.instructions.md") - if "makefile" in stacks: - instructions.add(".github/instructions/internal-makefile.instructions.md") - if "nodejs" in stacks and (source_root / ".github" / "instructions" / "internal-nodejs.instructions.md").is_file(): - instructions.add(".github/instructions/internal-nodejs.instructions.md") - if "java" in stacks and (source_root / ".github" / "instructions" / "internal-java.instructions.md").is_file(): - instructions.add(".github/instructions/internal-java.instructions.md") - - for recommended in profile.recommended_instructions: - prefixed = ensure_github_prefix(recommended) - if (source_root / prefixed).is_file(): - instructions.add(prefixed) - elif prefixed != recommended and (source_root / recommended).is_file(): - instructions.add(recommended) - - for instruction_path in portable_source_instructions: - apply_to = frontmatter_value(source_root / instruction_path, "applyTo") - if apply_to and repo_matches_apply_to(analysis.repo_root, apply_to): - instructions.add(instruction_path) - - profile_expected = {ensure_github_prefix(item) for item in profile.recommended_instructions} - for item in instructions: - if item not in profile_expected: - profile_extra_instructions.add(item) - - prompts: set[str] = set() - for recommended in profile.recommended_prompts: - prefixed = ensure_github_prefix(recommended) - if (source_root / prefixed).is_file(): - prompts.add(prefixed) - prompts.update(source_preferred_prompts) - - if {"python", "java", "nodejs"} & set(stacks): - prompts.add(".github/prompts/internal-add-unit-tests.prompt.md") - if "terraform" in stacks: - prompts.add(".github/prompts/internal-terraform-module.prompt.md") - if "github-actions" in stacks or "composite-action" in stacks: - prompts.add(".github/prompts/internal-github-action.prompt.md") - - prompts = { - prompt - for prompt in prompts - if prompt not in SOURCE_ONLY_PROMPT_PATHS and (source_root / prompt).is_file() - } - - skills: set[str] = set() - for recommended in profile.recommended_skills: - prefixed = ensure_github_prefix(recommended) - if (source_root / prefixed).is_file(): - skills.add(prefixed) - skills.update(source_preferred_skills) - - for prompt in prompts: - skills.update(path for path in prompt_skill_refs(source_root / prompt) if (source_root / path).is_file()) - - skills = {skill for skill in skills if skill not in SOURCE_ONLY_SKILL_PATHS} - - agents: set[str] = { - ".github/agents/internal-planner.agent.md", - ".github/agents/internal-implementer.agent.md", - ".github/agents/internal-reviewer.agent.md", - ".github/agents/internal-security-reviewer.agent.md", - } - if "github-actions" in stacks: - agents.add(".github/agents/internal-github-workflow-supply-chain.agent.md") - if "terraform" in stacks: - agents.add(".github/agents/internal-terraform-guardrails.agent.md") - if repo_needs_iam_review(analysis.repo_root): - agents.add(".github/agents/internal-iam-least-privilege.agent.md") - if target_has_pr_template(analysis.repo_root): - agents.add(".github/agents/internal-pr-editor.agent.md") - agents.update(portable_source_agents) - - agents = {agent for agent in agents if agent not in SOURCE_ONLY_AGENT_PATHS and (source_root / agent).is_file()} + instructions = set(source_asset_paths(source_root, "instructions")) + prompts = set(source_asset_paths(source_root, "prompts")) + skills = set(source_asset_paths(source_root, "skills")) + agents = set(source_asset_paths(source_root, "agents")) + supporting_files = source_skill_support_paths(source_root) baseline_files = [path for path in MANAGED_ALWAYS if (source_root / path).is_file()] validation_commands = build_validation_commands(analysis, instructions) @@ -1331,11 +1365,12 @@ def select_assets(source_root: Path, analysis: TargetAnalysis, profiles: dict[st prompts=sorted(prompts), skills=sorted(skills), agents=sorted(agents), + supporting_files=supporting_files, baseline_files=baseline_files, validation_commands=validation_commands, preferred_prompts=preferred_prompts, preferred_skills=preferred_skills, - profile_extra_instructions=sorted(profile_extra_instructions), + profile_extra_instructions=[], ) @@ -1432,11 +1467,15 @@ def build_validation_commands(analysis: TargetAnalysis, instruction_paths: set[s def merged_inventory_paths(target_root: Path, selection: AssetSelection) -> dict[str, list[str]]: target_assets = collect_target_config_assets(target_root) + + def preserved_local_assets(category: str) -> set[str]: + return {path for path in target_assets[category] if is_local_asset_path(path)} + return { - "instructions": sorted(set(selection.instructions) | set(target_assets["instructions"])), - "prompts": sorted(set(selection.prompts) | set(target_assets["prompts"])), - "skills": sorted(set(selection.skills) | set(target_assets["skills"])), - "agents": sorted(set(selection.agents) | set(target_assets["agents"])), + "instructions": sorted(set(selection.instructions) | preserved_local_assets("instructions")), + "prompts": sorted(set(selection.prompts) | preserved_local_assets("prompts")), + "skills": sorted(set(selection.skills) | preserved_local_assets("skills")), + "agents": sorted(set(selection.agents) | preserved_local_assets("agents")), } @@ -1570,7 +1609,6 @@ def detect_unmanaged_target_asset_issues( target_root: Path, selection: AssetSelection, ) -> list[TargetAssetIssue]: - alias_map = known_legacy_alias_map(source_root) managed_paths = set(selection.managed_source_paths) managed_paths.update(manifest_managed_paths(load_manifest(target_root))) validators = { @@ -1587,14 +1625,13 @@ def detect_unmanaged_target_asset_issues( if relative_path in managed_paths: continue + if not is_local_asset_path(relative_path): + continue + issue_types: list[str] = [] details: list[str] = [] - canonical_source_path = alias_map.get(relative_path) - repo_local = canonical_source_path is None and not (source_root / relative_path).is_file() - - if canonical_source_path: - issue_types.append("legacy_alias") - details.append(f"Legacy alias of `{canonical_source_path}`.") + canonical_source_path = None + repo_local = True validation_issues = validator(target_root, relative_path, repo_local=repo_local) if validation_issues: @@ -1705,12 +1742,11 @@ def build_planned_files( if source_relative_path == ".github/AGENTS.md": continue - desired_content = (source_root / source_relative_path).read_text(encoding="utf-8") planned_files.append( PlannedFile( source_relative_path=source_relative_path, target_relative_path=source_relative_path, - desired_content=desired_content, + desired_bytes=(source_root / source_relative_path).read_bytes(), category=asset_category(source_relative_path), ) ) @@ -1719,7 +1755,7 @@ def build_planned_files( PlannedFile( source_relative_path=None, target_relative_path=analysis.agents_relative_path, - desired_content=render_agents_markdown(analysis, selection, source_root), + desired_bytes=render_agents_markdown(analysis, selection, source_root).encode("utf-8"), category="agents", generated=True, ) @@ -1796,35 +1832,6 @@ def apply_redundancy_conflicts( redundant_assets: list[RedundantAsset], agents_relative_path: str, ) -> list[FileAction]: - action_by_target_path = {action.target_relative_path: action for action in actions} - blocks_agents_inventory = False - - for redundant_asset in redundant_assets: - if not redundant_asset.selected_for_sync: - continue - - action = action_by_target_path.get(redundant_asset.canonical_target_path) - if action is None: - continue - - if action.status == "conflict": - action.reason = f"{action.reason} Also, {redundant_asset.reason}" - else: - action.status = "conflict" - action.reason = redundant_asset.reason - blocks_agents_inventory = True - - if not blocks_agents_inventory: - return actions - - agents_action = action_by_target_path.get(agents_relative_path) - if agents_action is not None and agents_action.status != "conflict": - agents_action.status = "conflict" - agents_action.reason = ( - "Redundant legacy prompt, skill, or agent aliases were detected in the target. Resolve duplicate " - "configuration families before regenerating `AGENTS.md` inventory." - ) - return actions @@ -1862,7 +1869,7 @@ def plan_actions(target_root: Path, planned_files: list[PlannedFile], manifest: for planned_file in planned_files: target_path = target_root / planned_file.target_relative_path - desired_sha256 = sha256_text(planned_file.desired_content) + desired_sha256 = sha256_bytes(planned_file.desired_bytes) current_sha256 = sha256_path(target_path) if target_path.is_file() else None managed_entry = managed_files.get(planned_file.target_relative_path) @@ -1871,21 +1878,6 @@ def plan_actions(target_root: Path, planned_files: list[PlannedFile], manifest: if managed_entry: recorded_sha256 = str(managed_entry.get("sha256", "")) - if current_sha256 and current_sha256 != recorded_sha256 and current_sha256 != desired_sha256: - actions.append( - FileAction( - target_relative_path=planned_file.target_relative_path, - source_relative_path=planned_file.source_relative_path, - status="conflict", - category=planned_file.category, - reason="Source-managed file changed locally after the last sync.", - desired_sha256=desired_sha256, - current_sha256=current_sha256, - generated=planned_file.generated, - ) - ) - continue - if current_sha256 == desired_sha256: actions.append( FileAction( @@ -1893,7 +1885,7 @@ def plan_actions(target_root: Path, planned_files: list[PlannedFile], manifest: source_relative_path=planned_file.source_relative_path, status="unchanged", category=planned_file.category, - reason="Target content already matches the desired content.", + reason="Target content already matches the mirrored source content.", desired_sha256=desired_sha256, current_sha256=current_sha256, generated=planned_file.generated, @@ -1901,13 +1893,18 @@ def plan_actions(target_root: Path, planned_files: list[PlannedFile], manifest: ) continue + reason = ( + "Overwrite target drift to restore the source-authoritative mirrored catalog." + if current_sha256 != recorded_sha256 + else "Update an existing source-managed file to the latest mirrored source content." + ) actions.append( FileAction( target_relative_path=planned_file.target_relative_path, source_relative_path=planned_file.source_relative_path, status="update", category=planned_file.category, - reason="Update an existing source-managed file.", + reason=reason, desired_sha256=desired_sha256, current_sha256=current_sha256, generated=planned_file.generated, @@ -1945,56 +1942,67 @@ def plan_actions(target_root: Path, planned_files: list[PlannedFile], manifest: ) continue + if planned_file.generated: + reason = "Regenerate governed file from the current mirrored source catalog." + else: + reason = "Replace target file with the source-authoritative mirrored catalog version." + actions.append( FileAction( target_relative_path=planned_file.target_relative_path, source_relative_path=planned_file.source_relative_path, - status="conflict", + status="update", category=planned_file.category, - reason="Existing target file differs and is not source-managed yet.", + reason=reason, desired_sha256=desired_sha256, current_sha256=current_sha256, generated=planned_file.generated, ) ) - for managed_relative_path, managed_entry in sorted(managed_files.items()): - if managed_relative_path in planned_target_paths or managed_relative_path == MANIFEST_RELATIVE_PATH: + target_mirrored_files: set[str] = set() + for category in AGENTS_INVENTORY_CATEGORIES: + category_root = target_root / ".github" / category + if not category_root.is_dir(): + continue + target_mirrored_files.update( + str(path.relative_to(target_root)) + for path in category_root.rglob("*") + if path.is_file() + ) + + deletion_candidates = (set(managed_files) | target_mirrored_files) - planned_target_paths + for managed_relative_path in sorted(deletion_candidates): + if managed_relative_path == MANIFEST_RELATIVE_PATH: continue - if not isinstance(managed_entry, dict): + if not is_mirrored_resource_path(managed_relative_path): + continue + if is_local_asset_path(managed_relative_path): continue + managed_entry = managed_files.get(managed_relative_path) + if managed_entry is not None and not isinstance(managed_entry, dict): + managed_entry = None + target_path = target_root / managed_relative_path if not target_path.is_file(): continue current_sha256 = sha256_path(target_path) - recorded_sha256 = str(managed_entry.get("sha256", "")) - if current_sha256 != recorded_sha256: - actions.append( - FileAction( - target_relative_path=managed_relative_path, - source_relative_path=managed_entry.get("source_relative_path"), - status="conflict", - category=asset_category(managed_relative_path), - reason="Source-managed file was removed from the desired baseline but changed locally after the last sync.", - desired_sha256="", - current_sha256=current_sha256, - generated=bool(managed_entry.get("generated", False)), - ) - ) - continue - actions.append( FileAction( target_relative_path=managed_relative_path, - source_relative_path=managed_entry.get("source_relative_path"), + source_relative_path=(managed_entry or {}).get("source_relative_path") if isinstance(managed_entry, dict) else None, status="delete", category=asset_category(managed_relative_path), - reason="Remove a source-managed file that is no longer part of the desired baseline.", + reason=( + "Remove a file that is no longer present in the mirrored source catalog." + if managed_entry + else "Remove a target-only file outside the preserved local-* namespace." + ), desired_sha256="", current_sha256=current_sha256, - generated=bool(managed_entry.get("generated", False)), + generated=bool((managed_entry or {}).get("generated", False)) if isinstance(managed_entry, dict) else False, ) ) @@ -2064,6 +2072,7 @@ def render_agents_markdown(analysis: TargetAnalysis, selection: AssetSelection, f"- Primary focus: {analysis.focus}", f"- Profile hint: `{selection.profile.name}`", "- AGENTS.md is the external bridge for assistant behavior and naming; keep runtime references abstract.", + "- Completion-report details live in `.github/copilot-instructions.md`; keep the category schema there, not in this bridge.", "- Resolve stack from target files and explicit prompt inputs; the agent role remains behavioral, not language-specific.", "- Prioritize these paths:", ] @@ -2081,7 +2090,7 @@ def render_agents_markdown(analysis: TargetAnalysis, selection: AssetSelection, [ "", "## Repository Inventory (Auto-generated)", - "This inventory reflects the desired managed baseline plus repository-owned internal Copilot assets already present in the target repository.", + "This inventory reflects the mirrored source catalog plus preserved target local-* Copilot assets.", "", "### Instructions", ] @@ -2299,6 +2308,130 @@ def write_report(path: Path, content: str) -> None: path.write_text(content, encoding="utf-8") +def build_tracking_state( + plan: SyncPlan, + validation_errors: list[str] | None = None, + validation_performed: bool = False, +) -> PlanTrackingState: + pending_sync_actions = [ + f"`{action.status}` `{action.target_relative_path}`: {action.reason}" + for action in plan.actions + if action.status in {"create", "update", "delete", "adopt", "conflict"} + ] + + pending_validation_checks: list[str] = [] + manifest_path = plan.analysis.repo_root / MANIFEST_RELATIVE_PATH + agents_path = plan.analysis.repo_root / plan.analysis.agents_relative_path + if not validation_performed: + pending_validation_checks.extend( + [ + f"Confirm `{MANIFEST_RELATIVE_PATH}` is written after apply.", + f"Confirm `{plan.analysis.agents_relative_path}` exists and reflects the mirrored catalog.", + "Run strict Copilot validation: `python3 .github/scripts/validate-copilot-customizations.py --scope root --mode strict`.", + ] + ) + else: + if not manifest_path.is_file(): + pending_validation_checks.append(f"Missing `{MANIFEST_RELATIVE_PATH}` after apply.") + if not agents_path.is_file(): + pending_validation_checks.append( + f"Missing `{plan.analysis.agents_relative_path}` after apply." + ) + pending_validation_checks.extend(validation_errors or []) + + pending_manual_follow_up: list[str] = [] + for issue in plan.target_asset_issues: + if issue.severity != "error": + continue + details = "; ".join(issue.details) + pending_manual_follow_up.append( + f"`{issue.target_relative_path}` [{issue.severity}]: {details}" + ) + + for redundant_asset in plan.redundant_assets: + pending_manual_follow_up.append(redundant_asset.reason) + + return PlanTrackingState( + pending_sync_actions=pending_sync_actions, + pending_validation_checks=pending_validation_checks, + pending_manual_follow_up=pending_manual_follow_up, + ) + + +def render_tracking_plan(target_root: Path, tracking: PlanTrackingState) -> str: + lines = [ + f"# Internal Sync Plan - {target_root.name}", + "", + f"- Tool: `{SCRIPT_NAME}`", + f"- Updated at: `{utc_now()}`", + "- This file is pruned automatically as sections are satisfied.", + "- If all sections disappear, the file is deleted automatically.", + "", + ] + + if tracking.pending_sync_actions: + lines.extend(["## Pending synchronization actions"]) + lines.extend(f"- {item}" for item in tracking.pending_sync_actions) + lines.append("") + + if tracking.pending_validation_checks: + lines.extend(["## Pending validation checks"]) + lines.extend(f"- {item}" for item in tracking.pending_validation_checks) + lines.append("") + + if tracking.pending_manual_follow_up: + lines.extend(["## Pending manual follow-up"]) + lines.extend(f"- {item}" for item in tracking.pending_manual_follow_up) + lines.append("") + + return "\n".join(lines).rstrip() + "\n" + + +def update_tracking_plan_file(target_root: Path, tracking: PlanTrackingState) -> bool: + plan_path = target_root / tracking.relative_path + if not tracking.has_pending_items: + if plan_path.is_file(): + plan_path.unlink() + return False + + write_report(plan_path, render_tracking_plan(target_root, tracking)) + return True + + +def run_strict_target_validation(target_root: Path) -> list[str]: + validator_path = target_root / ".github" / "scripts" / "validate-copilot-customizations.py" + if not validator_path.is_file(): + return [ + "Missing `.github/scripts/validate-copilot-customizations.py`; strict Copilot validation could not run." + ] + + result = subprocess.run( + [ + "python3", + ".github/scripts/validate-copilot-customizations.py", + "--scope", + "root", + "--mode", + "strict", + ], + cwd=target_root, + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0: + return [] + + combined_output = [ + line.strip() + for line in f"{result.stdout}\n{result.stderr}".splitlines() + if line.strip() + ] + if not combined_output: + combined_output = [f"Strict Copilot validation failed with exit code {result.returncode}."] + return combined_output[:20] + + def apply_plan(target_root: Path, plan: SyncPlan, planned_files: list[PlannedFile], source_root: Path) -> None: content_map = {item.target_relative_path: item for item in planned_files} for action in plan.actions: @@ -2306,7 +2439,7 @@ def apply_plan(target_root: Path, plan: SyncPlan, planned_files: list[PlannedFil if action.status in {"create", "update"}: planned_file = content_map[action.target_relative_path] target_path.parent.mkdir(parents=True, exist_ok=True) - target_path.write_text(planned_file.desired_content, encoding="utf-8") + target_path.write_bytes(planned_file.desired_bytes) continue if action.status == "delete" and target_path.is_file(): @@ -2315,7 +2448,7 @@ def apply_plan(target_root: Path, plan: SyncPlan, planned_files: list[PlannedFil manifest = { "tool": SCRIPT_NAME, - "version": 1, + "version": 2, "generated_at_utc": utc_now(), "target_repo": str(target_root), "profile": plan.selection.profile.name, @@ -2354,6 +2487,7 @@ def render_markdown_report(plan: SyncPlan) -> str: f"- Detected stacks: {', '.join(plan.analysis.stacks) if plan.analysis.stacks else 'none'}", f"- Unsupported stacks: {', '.join(plan.analysis.unsupported_stacks) if plan.analysis.unsupported_stacks else 'none'}", f"- AGENTS location: `{plan.analysis.agents_relative_path}`", + f"- Tracking plan: `{PLAN_RELATIVE_PATH}`", f"- Git worktree state: {'dirty' if plan.analysis.git_dirty else 'clean'}", f"- Priority paths: {', '.join(plan.analysis.priority_paths)}", "", @@ -2363,6 +2497,7 @@ def render_markdown_report(plan: SyncPlan) -> str: f"- Prompts: {', '.join(plan.selection.prompts)}", f"- Skills: {', '.join(plan.selection.skills)}", f"- Agents: {', '.join(plan.selection.agents)}", + f"- Skill support files: {len(plan.selection.supporting_files)} mirrored file(s)", "", ] lines.extend(render_source_audit_markdown(plan.source_audit)) @@ -2563,6 +2698,7 @@ def render_json_report(plan: SyncPlan) -> str: "prompts": plan.selection.prompts, "skills": plan.selection.skills, "agents": plan.selection.agents, + "supporting_files": plan.selection.supporting_files, "validation_commands": plan.selection.validation_commands, }, "actions": [ @@ -2576,6 +2712,7 @@ def render_json_report(plan: SyncPlan) -> str: for action in plan.actions ], "recommendations": plan.recommendations, + "plan_relative_path": PLAN_RELATIVE_PATH, "manifest_relative_path": plan.manifest_relative_path, } return json.dumps(payload, indent=2, sort_keys=True) + "\n" @@ -2644,12 +2781,40 @@ def main(argv: list[str] | None = None) -> int: log_error(str(error)) return 2 + tracking_path = target_root / PLAN_RELATIVE_PATH + update_tracking_plan_file(target_root, build_tracking_state(plan)) + log_info(f"Tracking plan written to {tracking_path}") + if args.mode == "apply": - log_info("Applying conservative merge for source-managed files.") + log_info("Applying source-authoritative mirror for managed Copilot files.") apply_plan(target_root, plan, planned_files, source_root) log_success(f"Manifest written to {target_root / MANIFEST_RELATIVE_PATH}") + + try: + post_apply_plan, _post_apply_files = build_plan(source_root, target_root) + except CliError as error: + log_error(str(error)) + return 2 + + validation_errors = run_strict_target_validation(target_root) + tracking_written = update_tracking_plan_file( + target_root, + build_tracking_state( + post_apply_plan, + validation_errors=validation_errors, + validation_performed=True, + ), + ) + if validation_errors: + log_warn("Strict Copilot validation left pending items in the tracking plan.") + if tracking_written: + log_warn(f"Tracking plan retained at {tracking_path}") + else: + log_success(f"Tracking plan completed and removed from {tracking_path}") else: - log_info("Plan mode selected - no repository files will be changed.") + log_info( + "Plan mode selected - mirrored files will not be changed, but the tracking plan is written to the target repository." + ) report = emit_report(plan, args.report_format) sys.stdout.write(report) diff --git a/.github/scripts/validate-copilot-customizations.py b/.github/scripts/validate-copilot-customizations.py index dac0347..a8d0f6e 100755 --- a/.github/scripts/validate-copilot-customizations.py +++ b/.github/scripts/validate-copilot-customizations.py @@ -35,6 +35,12 @@ "internal-agents-md-bridge", } LEGACY_SKILL_IDENTIFIER = "internal-skill-development" +OBRA_SOURCE_OF_TRUTH_PATH = Path(".github/obra-superpowers-source-of-truth.json") +OBRA_MANAGED_RESOURCE_SECTION = "### `obra/superpowers`" +OBRA_GOVERNANCE_REFERENCE_PATHS = ( + Path("AGENTS.md"), + Path(".github/copilot-instructions.md"), +) INTERNAL_CODE_REVIEW_REFERENCE_PATHS = ( Path(".github/skills/internal-code-review/references/anti-patterns-python.md"), Path(".github/skills/internal-code-review/references/anti-patterns-bash.md"), @@ -42,6 +48,24 @@ Path(".github/skills/internal-code-review/references/anti-patterns-java.md"), Path(".github/skills/internal-code-review/references/anti-patterns-nodejs.md"), ) +OPERATION_COMPLETION_REPORT_SECTION = "## Operation Completion Report" +COMPLETION_REPORT_CONTRACT_SECTION = "## Completion Report Contract" +COMPLETION_REPORT_CATEGORY_HEADINGS = ( + "### ✅ Outcome", + "### 🤖 Agents", + "### 📘 Instructions", + "### 🧩 Skills", +) +COMPLETION_REPORT_UNUSED_REASON = ( + "If a category was not used, explicitly say so and explain why." +) +AGENTS_COMPLETION_REPORT_POINTER = ( + "Completion-report details live in `.github/copilot-instructions.md`" +) +SYNC_AGENT_COMPLETION_REPORT_PATHS = ( + Path(".github/agents/internal-sync-control-center.agent.md"), + Path(".github/agents/internal-sync-global-copilot-configs-into-repo.agent.md"), +) @dataclass @@ -127,6 +151,29 @@ def extract_markdown_h2_section(text: str, heading: str) -> str | None: return "\n".join(collected).strip() +def extract_markdown_h3_section(text: str, heading: str) -> str | None: + lines = text.splitlines() + inside_section = False + collected: list[str] = [] + + for line in lines: + if re.match(r"^###\s+", line): + if line.strip() == heading: + inside_section = True + collected = [] + continue + if inside_section: + break + + if inside_section: + collected.append(line) + + if not inside_section: + return None + + return "\n".join(collected).strip() + + def extract_agent_skill_guidance(text: str) -> list[str] | None: section = None for heading in AGENT_SKILL_SECTION_HEADINGS: @@ -159,6 +206,30 @@ def extract_skill_usage_contract(text: str) -> list[str] | None: return declared_skills +def extract_managed_skill_mappings(text: str, resource_heading: str) -> list[tuple[str, str]] | None: + section = extract_markdown_h3_section(text, resource_heading) + if section is None: + return None + + inside_managed_skills = False + mappings: list[tuple[str, str]] = [] + + for raw_line in section.splitlines(): + stripped = raw_line.strip() + if stripped == "Managed skills:": + inside_managed_skills = True + continue + + if not inside_managed_skills: + continue + + match = re.fullmatch(r"-\s+`([^`]+)`\s+->\s+`([^`]+)`", stripped) + if match: + mappings.append((match.group(1), match.group(2))) + + return mappings + + def extract_inventory_paths() -> list[str]: inventory_paths: list[str] = [] @@ -183,6 +254,34 @@ def extract_inventory_paths() -> list[str]: return sorted(set(inventory_paths)) +def local_catalog_inventory_paths() -> set[str]: + paths: set[str] = set() + paths.update( + str(path.relative_to(REPO_ROOT)).replace("\\", "/") + for path in instruction_files() + ) + paths.update( + str(path.relative_to(REPO_ROOT)).replace("\\", "/") + for path in sorted((REPO_ROOT / ".github" / "prompts").glob("*.prompt.md")) + ) + paths.update( + str(path.relative_to(REPO_ROOT)).replace("\\", "/") + for path in sorted((REPO_ROOT / ".github" / "skills").glob("*/SKILL.md")) + ) + paths.update( + str(path.relative_to(REPO_ROOT)).replace("\\", "/") + for path in sorted((REPO_ROOT / ".github" / "agents").glob("*.agent.md")) + ) + return paths + + +def has_declared_inventory() -> bool: + agents_path = REPO_ROOT / "AGENTS.md" + if agents_path.exists() and "## Repository Inventory" in read_text(agents_path): + return True + return (REPO_ROOT / ".github" / "INVENTORY.md").exists() + + def instruction_files() -> list[Path]: instructions_dir = REPO_ROOT / ".github" / "instructions" if not instructions_dir.exists(): @@ -313,10 +412,159 @@ def validate_named_resources(errors: list[str]) -> None: def validate_inventory(errors: list[str]) -> None: - for relative in extract_inventory_paths(): + inventory_paths = set(extract_inventory_paths()) + + for relative in sorted(inventory_paths): if not (REPO_ROOT / relative).exists(): errors.append(f"Inventory path missing on disk: {relative}") + if not has_declared_inventory(): + return + + for relative in sorted(local_catalog_inventory_paths() - inventory_paths): + errors.append(f"Inventory path missing from declared inventory: {relative}") + + +def validate_repo_profile_references(errors: list[str]) -> None: + repo_profiles_path = REPO_ROOT / ".github" / "repo-profiles.yml" + if not repo_profiles_path.exists(): + return + + for raw_line in read_text(repo_profiles_path).splitlines(): + stripped = raw_line.split("#", 1)[0].strip() + if not stripped.startswith("- "): + continue + + candidate = stripped[2:].strip().strip("\"'") + if not candidate.startswith(("instructions/", "prompts/", "skills/")): + continue + + absolute_path = REPO_ROOT / ".github" / candidate + if not absolute_path.exists(): + errors.append(f"Repo profile path missing on disk: .github/{candidate}") + + +def should_validate_obra_source_of_truth() -> bool: + if (REPO_ROOT / OBRA_SOURCE_OF_TRUTH_PATH).exists(): + return True + + if (REPO_ROOT / INTERNAL_SYNC_CONTROL_CENTER_AGENT).exists(): + return True + + return any((REPO_ROOT / ".github" / "skills").glob("obra-*/SKILL.md")) + + +def validate_obra_source_of_truth(errors: list[str]) -> None: + if not should_validate_obra_source_of_truth(): + return + + source_of_truth_path = REPO_ROOT / OBRA_SOURCE_OF_TRUTH_PATH + if not source_of_truth_path.exists(): + errors.append(f"Missing OBRA source-of-truth file: {OBRA_SOURCE_OF_TRUTH_PATH}") + return + + try: + source_of_truth = json.loads(read_text(source_of_truth_path)) + except json.JSONDecodeError as error: + errors.append(f"Invalid JSON in {OBRA_SOURCE_OF_TRUTH_PATH}: {error}") + return + + if not isinstance(source_of_truth, dict): + errors.append(f"Invalid OBRA source-of-truth payload in {OBRA_SOURCE_OF_TRUTH_PATH}") + return + + if not isinstance(source_of_truth.get("source_repository"), str) or not source_of_truth.get( + "source_repository" + ): + errors.append( + f"Missing `source_repository` string in {OBRA_SOURCE_OF_TRUTH_PATH}" + ) + + if not isinstance(source_of_truth.get("source_ref"), str) or not source_of_truth.get( + "source_ref" + ): + errors.append(f"Missing `source_ref` string in {OBRA_SOURCE_OF_TRUTH_PATH}") + + raw_managed_skills = source_of_truth.get("managed_skills") + if not isinstance(raw_managed_skills, list) or not raw_managed_skills: + errors.append(f"Missing `managed_skills` list in {OBRA_SOURCE_OF_TRUTH_PATH}") + return + + expected_mappings: set[tuple[str, str]] = set() + for entry in raw_managed_skills: + if not isinstance(entry, dict): + errors.append(f"Invalid managed skill entry in {OBRA_SOURCE_OF_TRUTH_PATH}: {entry!r}") + continue + + upstream = entry.get("upstream") + local = entry.get("local") + if not isinstance(upstream, str) or not upstream or not isinstance(local, str) or not local: + errors.append( + "Managed skill entries must contain non-empty `upstream` and `local` strings in " + f"{OBRA_SOURCE_OF_TRUTH_PATH}" + ) + continue + + expected_mappings.add((upstream, local)) + + expected_local_skills = {local for _upstream, local in expected_mappings} + actual_local_skills = { + path.parent.name + for path in sorted((REPO_ROOT / ".github" / "skills").glob("obra-*/SKILL.md")) + } + + for skill_name in sorted(expected_local_skills - actual_local_skills): + errors.append( + f"OBRA source-of-truth skill missing on disk: {skill_name} ({OBRA_SOURCE_OF_TRUTH_PATH})" + ) + + for skill_name in sorted(actual_local_skills - expected_local_skills): + errors.append( + f"Unexpected local OBRA skill outside source-of-truth: {skill_name} ({OBRA_SOURCE_OF_TRUTH_PATH})" + ) + + control_center_path = REPO_ROOT / INTERNAL_SYNC_CONTROL_CENTER_AGENT + if not control_center_path.exists(): + return + + managed_mappings = extract_managed_skill_mappings(read_text(control_center_path), OBRA_MANAGED_RESOURCE_SECTION) + if managed_mappings is None: + errors.append( + f"Missing `{OBRA_MANAGED_RESOURCE_SECTION}` section: {INTERNAL_SYNC_CONTROL_CENTER_AGENT}" + ) + return + + managed_mapping_set = set(managed_mappings) + for upstream, local in sorted(expected_mappings - managed_mapping_set): + errors.append( + "OBRA managed skill mapping missing from " + f"{INTERNAL_SYNC_CONTROL_CENTER_AGENT}: `{upstream}` -> `{local}`" + ) + + for upstream, local in sorted(managed_mapping_set - expected_mappings): + errors.append( + "Unexpected OBRA managed skill mapping in " + f"{INTERNAL_SYNC_CONTROL_CENTER_AGENT}: `{upstream}` -> `{local}`" + ) + + +def validate_governance_obra_skill_references(errors: list[str]) -> None: + available_obra_skills = { + path.parent.name + for path in sorted((REPO_ROOT / ".github" / "skills").glob("obra-*/SKILL.md")) + } + if not available_obra_skills: + return + + for relative_path in OBRA_GOVERNANCE_REFERENCE_PATHS: + absolute_path = REPO_ROOT / relative_path + if not absolute_path.exists(): + continue + + for skill_name in sorted(set(re.findall(r"`(obra-[a-z0-9-]+)`", read_text(absolute_path)))): + if skill_name not in available_obra_skills: + errors.append(f"Unknown obra skill `{skill_name}` referenced in {relative_path}") + def validate_required_paths(errors: list[str]) -> None: required_paths = [ @@ -390,6 +638,9 @@ def validate_legacy_skill_references(errors: list[str]) -> None: def validate_internal_skill_reference_files(errors: list[str]) -> None: + if not (REPO_ROOT / ".github" / "skills" / "internal-code-review" / "SKILL.md").exists(): + return + for reference_path in INTERNAL_CODE_REVIEW_REFERENCE_PATHS: if not (REPO_ROOT / reference_path).exists(): errors.append( @@ -398,6 +649,67 @@ def validate_internal_skill_reference_files(errors: list[str]) -> None: ) +def validate_operation_completion_report_contract(errors: list[str]) -> None: + copilot_instructions_path = REPO_ROOT / ".github" / "copilot-instructions.md" + if copilot_instructions_path.exists(): + text = read_text(copilot_instructions_path) + if OPERATION_COMPLETION_REPORT_SECTION not in text: + errors.append( + f"Missing `{OPERATION_COMPLETION_REPORT_SECTION}` in {copilot_instructions_path}" + ) + for heading in COMPLETION_REPORT_CATEGORY_HEADINGS: + if heading not in text: + errors.append( + f"Missing completion report category `{heading}` in {copilot_instructions_path}" + ) + if COMPLETION_REPORT_UNUSED_REASON not in text: + errors.append( + "Missing unused-category explanation requirement in " + f"{copilot_instructions_path}" + ) + + agents_path = REPO_ROOT / "AGENTS.md" + if agents_path.exists() and AGENTS_COMPLETION_REPORT_POINTER not in read_text(agents_path): + errors.append(f"Missing completion-report bridge pointer in {agents_path}") + + readme_path = REPO_ROOT / ".github" / "README.md" + if readme_path.exists(): + text = read_text(readme_path) + if COMPLETION_REPORT_CONTRACT_SECTION not in text: + errors.append(f"Missing `{COMPLETION_REPORT_CONTRACT_SECTION}` in {readme_path}") + for heading in COMPLETION_REPORT_CATEGORY_HEADINGS: + if heading not in text: + errors.append(f"Missing completion report category `{heading}` in {readme_path}") + if COMPLETION_REPORT_UNUSED_REASON not in text: + errors.append( + "Missing unused-category explanation requirement in " + f"{readme_path}" + ) + + +def validate_sync_agent_completion_report_contract(errors: list[str]) -> None: + for relative_path in SYNC_AGENT_COMPLETION_REPORT_PATHS: + agent_path = REPO_ROOT / relative_path + if not agent_path.exists(): + continue + + text = read_text(agent_path) + if "## Output Expectations" not in text: + errors.append(f"Missing `## Output Expectations` in {relative_path}") + + for heading in COMPLETION_REPORT_CATEGORY_HEADINGS: + if heading not in text: + errors.append( + f"Missing completion report category `{heading}` in {relative_path}" + ) + + if COMPLETION_REPORT_UNUSED_REASON not in text: + errors.append( + "Missing unused-category explanation requirement in " + f"{relative_path}" + ) + + def build_report(scope: str, mode: str) -> ValidationReport: normalize_scope(scope) normalize_mode(mode) @@ -407,9 +719,14 @@ def build_report(scope: str, mode: str) -> ValidationReport: validate_required_paths(errors) validate_named_resources(errors) validate_inventory(errors) + validate_repo_profile_references(errors) + validate_obra_source_of_truth(errors) + validate_governance_obra_skill_references(errors) validate_internal_sync_control_center_contract(errors) validate_legacy_skill_references(errors) validate_internal_skill_reference_files(errors) + validate_operation_completion_report_contract(errors) + validate_sync_agent_completion_report_contract(errors) warnings.extend(build_instruction_load_warnings()) return ValidationReport(errors=errors, warnings=warnings) diff --git a/.github/skills/internal-script-python/SKILL.md b/.github/skills/internal-script-python/SKILL.md index 8a79d98..59554ad 100644 --- a/.github/skills/internal-script-python/SKILL.md +++ b/.github/skills/internal-script-python/SKILL.md @@ -23,11 +23,30 @@ description: Create or modify standalone Python scripts with purpose docstring, - Add unit tests for testable behavior. - New standalone tools should default to a dedicated folder, not a loose top-level `.py` file. - The folder should include the Python entry point, a `run.sh` launcher, and `tests/` when test scope applies. Add a local `requirements.txt` only when external packages are used. +- For new scripts, do an explicit dependency decision before implementation; do not assume `stdlib-first` as the automatic default. +- Prefer mature, well-maintained, widely used third-party libraries when they clearly reduce boilerplate, edge cases, or custom logic in the finished script. +- Keep the standard library only when the final code is genuinely simpler, more readable, and safer than the third-party alternative. +- Optimize for less bespoke code and a simpler final script, not for the fewest possible dependencies. - If external packages are used, keep them in the local `requirements.txt` with exact pins, full transitive dependency closure, `--hash` entries, and short comment lines that make pinned versions readable. -- Recommend third-party libraries when they materially simplify parsing, validation, HTTP, CLI, serialization, or retry behavior; do not replace a simpler standard-library solution just to satisfy the preference. +- Recommend third-party libraries when they materially simplify parsing, validation, CLI handling, serialization, HTTP, retry behavior, date handling, table rendering, Excel/CSV processing, or formatting; do not replace a simpler standard-library solution just to satisfy the preference. +- Avoid weak or marginal dependencies; every package should have a clear value-versus-setup justification. - Make new `run.sh` launchers executable, and make them install from `requirements.txt` only when that file exists. - For Python template tasks, use Jinja templates named `..j2`. +## Dependency decision note +Before writing a new script, include a short dependency decision note such as: + +```text +Dependency decision note +- Candidates: argparse (stdlib), click, typer +- Final choice: typer +- Why: cleaner CLI structure, less boilerplate, better help output, and less custom parsing code than argparse for this script. +``` + +- Keep the note short and task-specific. +- Compare the standard library with realistic third-party candidates. +- If the final choice uses external libraries, create or update the local `requirements.txt` before finishing the task. + ## Default layout ```text {script_name}/ @@ -120,7 +139,10 @@ exec "$VENV_DIR/bin/python" "$SCRIPT_DIR/{script_name}.py" "$@" - For modify tasks: edit implementation first, run existing tests, then update tests only for intentional behavior changes. ## Runtime guidance -- Prefer the standard library first for simple scripts; add third-party packages only when they materially simplify parsing, validation, HTTP, CLI, or serialization work. +- Evaluate stdlib and third-party options explicitly for each new script instead of defaulting blindly to stdlib. +- Prefer mature third-party packages when they clearly produce a smaller, safer, easier-to-maintain script than a custom stdlib-based implementation. +- Keep stdlib when it wins on simplicity, clarity, and safety in the final result. +- Reach for libraries instead of custom logic when they solve parsing, validation, CLI handling, serialization, HTTP, retry, date handling, table rendering, Excel/CSV, or formatting better. - Use `asyncio` only when the script truly coordinates multiple I/O-bound tasks. - Reach for `pathlib`, context managers, and small helper functions before adding framework-like structure to a script. @@ -136,6 +158,8 @@ exec "$VENV_DIR/bin/python" "$SCRIPT_DIR/{script_name}.py" "$@" | Installing deps globally or without hash-locked version pinning | Non-reproducible environment and hidden setup drift | Keep dependencies in the local `requirements.txt` with exact pins and hashes | | Adding an empty `requirements.txt` to a stdlib-only tool | Adds noise and implies missing setup steps | Omit `requirements.txt` when the script uses only the standard library | | Shipping a loose `.py` file with undocumented setup steps | Users must guess how to create the environment and run the tool | Generate a self-contained folder with `run.sh` and add `requirements.txt` only when external packages are needed | +| Defaulting to stdlib without comparing mature libraries | Leaves avoidable boilerplate, edge cases, and custom parsing logic in the script | Write the dependency decision note first and choose the option that makes the final code simpler | +| Rejecting a useful dependency just to keep dependency count low | Optimizes the wrong thing and increases custom code | Optimize for simpler final code and justified value, not dependency minimization | | Forcing async or framework abstractions into a simple tool | Raises complexity without improving the script | Keep the script synchronous and direct unless concurrency is essential | ## Cross-references diff --git a/.github/skills/internal-sync-global-copilot-configs-into-repo/SKILL.md b/.github/skills/internal-sync-global-copilot-configs-into-repo/SKILL.md index 9cd3c69..c073986 100644 --- a/.github/skills/internal-sync-global-copilot-configs-into-repo/SKILL.md +++ b/.github/skills/internal-sync-global-copilot-configs-into-repo/SKILL.md @@ -1,6 +1,6 @@ --- name: internal-sync-global-copilot-configs-into-repo -description: Sync shared Copilot baseline into consumer repos — dynamic stack detection, manifest-based conservative merge, conflict detection, and deterministic reporting. Use when syncing Copilot configs, aligning repos with the baseline, or running the sync script. +description: Mirror the shared Copilot catalog into consumer repos — dynamic repo analysis, source-authoritative mirroring, local-* preservation, full skill-bundle copying, and deterministic reporting. Use when syncing Copilot configs, aligning repos with the standards repo, or running the sync script. --- # Internal Sync Global Copilot Configs Into Repo @@ -9,31 +9,38 @@ description: Sync shared Copilot baseline into consumer repos — dynamic stack - Align a consumer repository with shared Copilot assets from this standards repository. - Audit source-side or target-side asset health before or after sync. - Produce deterministic dry-run or apply reports for Copilot-core alignment. +- Rebuild target `.github/copilot-instructions.md` and then refresh root `AGENTS.md` after a raw mirror completes. ## Three-phase sync model ### Phase 1 — Analyze 1. Inspect target repository: `.github` contents, `AGENTS.md`, git state. -2. Detect stacks dynamically from file extensions and project manifests (no hardcoded profiles — detect `*.tf`, `*.py`, `*.java`, `*.js`, `*.ts`, `Dockerfile`, `*.sh`, etc.). -3. Classify target assets into: managed (synced baseline), origin-prefixed (`internal-*`, `local-*`, or supported external prefixes), and unmanaged (everything else). -4. Audit source standards repository: detect legacy aliases, canonical overlaps, and source-only assets. +2. Detect stacks dynamically from file extensions and project manifests so reporting and validation stay target-aware even though mirroring is full-catalog. +3. Classify target assets into: mirrored source-managed, preserved `local-*`, and target-only non-local assets that must be removed during apply. +4. Audit source standards repository: detect legacy aliases, canonical overlaps, and stale governance references. ### Phase 2 — Plan -1. Select minimum Copilot-core assets the target actually needs based on detected stacks. -2. Compute SHA-256 checksums for both source and target versions of each managed file. -3. Flag conflicts: target file diverged from last-synced version (manifest mismatch). -4. Flag redundancies: legacy aliases coexisting with canonical assets. -5. Flag origin-prefix violations: repo-owned assets missing `internal-*`, `local-*`, or a supported external short-repo prefix. -6. Plan root-guidance refresh in this order: target `.github/copilot-instructions.md` first via the repository-local authoring workflow anchored in `internal-ai-resource-creator`, then target root `AGENTS.md` via `internal-agents-md-bridge`. -7. Generate plan report (JSON or Markdown). +1. Select every source asset under `.github/agents`, `.github/instructions`, `.github/prompts`, and `.github/skills`. +2. Expand each mirrored skill to include bundled support files such as `references/`, `assets/`, `scripts/`, licenses, and other on-disk resources. +3. Compute SHA-256 checksums for both source and target versions of each mirrored file. +4. Plan source-authoritative updates for every mirrored non-local target file that differs from source. +5. Plan deletions for target-only non-local assets inside mirrored categories. +6. Preserve target `local-*` assets and validate them as unmanaged local extensions. +7. Write `tmp/internal-sync-copilot-configs.plan.md` in the target repo with the pending sync actions, checks, and manual follow-up items. +8. When the sync needs retained auxiliary files such as saved reports, place them under target-root `tmp/` and create that directory if it does not exist. +9. Plan root-guidance refresh in this order: target `.github/copilot-instructions.md` first via the repository-local authoring workflow anchored in `internal-ai-resource-creator`, then target root `AGENTS.md` via `internal-agents-md-bridge`. +10. Generate plan report (JSON or Markdown). ### Phase 3 — Apply (opt-in) -1. Copy selected assets using conservative merge (never overwrite unmanaged divergent files). -2. Update manifest with new SHA-256 checksums and timestamp. -3. Refresh target `.github/copilot-instructions.md` as the primary detailed Copilot policy file, preserving target-local rules that are still valid and do not conflict with the managed baseline. -4. Refresh target-specific root `AGENTS.md` from the managed baseline plus existing target-local assets, keeping it concise, bridge-oriented, and runtime-agnostic instead of duplicating Copilot policy text. -5. Preserve target-local unmanaged resources, prompts, skills, agents, and configuration unless the approved plan explicitly migrates them. -6. Produce final report: actions taken, conflicts skipped, preserved local assets, and recommendations. +1. Copy every mirrored source asset, including binary skill support files. +2. Overwrite non-local target drift inside mirrored categories so the source catalog remains authoritative. +3. Delete target-only non-local assets inside mirrored categories. +4. Update manifest with new SHA-256 checksums and timestamp. +5. Refresh target `.github/copilot-instructions.md` as the primary detailed Copilot policy file, deriving target-specific content from repository evidence when needed. +6. Refresh target-specific root `AGENTS.md` from the mirrored baseline plus preserved target-local assets, keeping it concise, bridge-oriented, and runtime-agnostic instead of duplicating Copilot policy text. +7. Re-check the objectives recorded in `tmp/internal-sync-copilot-configs.plan.md`; remove sections whose checks now pass and delete the file only when nothing remains pending. +8. Preserve target-local `local-*` resources and configuration unless the approved plan explicitly migrates them. +9. Produce final report: actions taken, preserved local assets, deleted target-only assets, and recommendations. End the report with `✅ Outcome`, `🤖 Agents`, `📘 Instructions`, and `🧩 Skills`; if a category was not used, explicitly say so and explain why. ## Managed always-sync files These files are always synced regardless of detected stacks: @@ -42,51 +49,45 @@ These files are always synced regardless of detected stacks: - `copilot-code-review-instructions.md` - `security-baseline.md` - `DEPRECATION.md` +- `repo-profiles.yml` - `scripts/validate-copilot-customizations.py` -## Stack-to-asset mapping -The sync script detects stacks dynamically and selects assets accordingly: +## Target assumptions +- The source of truth is always this `cloud-strategy.github` repository. +- The target stores Copilot customization under `.github/`. +- The target keeps `AGENTS.md` in repository root. +- Stack detection still matters for reporting, validation commands, and target-specific `copilot-instructions` authoring, but not for mirror selection. -| Detected stack | Instructions | Skills | Prompts | -|---|---|---|---| -| Terraform (`*.tf`) | `internal-terraform.instructions.md` | `internal-terraform`, `internal-cloud-policy` | `internal-terraform-module.prompt.md` | -| Python (`*.py`) | `internal-python.instructions.md` | `internal-project-python`, `internal-script-python` | `internal-add-unit-tests.prompt.md` | -| Java (`*.java`) | `internal-java.instructions.md` | `internal-project-java` | `internal-add-unit-tests.prompt.md` | -| Node.js (`*.js`, `*.ts`) | `internal-nodejs.instructions.md` | `internal-project-nodejs` | `internal-add-unit-tests.prompt.md` | -| Docker (`Dockerfile`) | `internal-docker.instructions.md` | `internal-docker` | none | -| Bash (`*.sh`) | `internal-bash.instructions.md` | `internal-script-bash` | none | -| GitHub Actions (`workflows/`) | `internal-github-actions.instructions.md` | `internal-cicd-workflow` | `internal-github-action.prompt.md` | - -Always included: `internal-markdown.instructions.md`, `internal-yaml.instructions.md`, `internal-json.instructions.md`. - -## Source-only assets (never synced) -These assets exist only in this standards repository: -- Agents: `internal-sync-global-copilot-configs-into-repo` -- Skills: `internal-agent-development`, `internal-agents-md-bridge`, `internal-copilot-audit`, `openai-skill-creator`, `internal-skill-management`, `internal-sync-global-copilot-configs-into-repo` -- Prompts: `internal-add-platform`, `internal-add-report-script` +## Mirrored categories +- `.github/agents/**/*.agent.md` +- `.github/instructions/**/*.instructions.md` +- `.github/prompts/**/*.prompt.md` +- `.github/skills/**`, including `SKILL.md` and bundled support files +- Tracking artifact: `tmp/internal-sync-copilot-configs.plan.md` ## Scope rules - Manage Copilot-core assets only. - Exclude README, changelog, templates, workflows, and source-only agents from sync. - Prefer existing root `AGENTS.md` over creating a second managed file under `.github/`. -- Keep origin-prefixed assets visible in rendered AGENTS.md inventory. -- Never overwrite unmanaged divergent files — flag as conflicts instead. +- Keep preserved `local-*` assets visible in rendered AGENTS.md inventory. +- Overwrite non-local divergent files inside mirrored categories. - Treat target `.github/copilot-instructions.md` as the primary home for detailed behavioral, validation, and implementation guidance. - Treat target root `AGENTS.md` as a thin bridge for generic assistants: routing, naming, priority, and discovery of the Copilot-owned `.github` assets. - Keep target root `AGENTS.md` light on purpose because some repositories cannot or should not declare a specific assistant runtime there. -- Preserve target-local unmanaged resources and configuration even when they are not part of the selected sync baseline; report them instead of deleting or folding them into managed files. +- Preserve target-local `local-*` resources and configuration even when they are not part of the mirrored source catalog; report them instead of deleting or folding them into managed files. ## Common mistakes | Mistake | Why it matters | Instead | |---|---|---| | Running apply without reviewing the plan first | Unintended overwrites or deletions | Always run plan mode first, review the report | -| Syncing source-only agents to consumer repos | Consumer gets assets meant for standards repo only | Check exclusion lists | -| Ignoring manifest checksum mismatches | Target edits get silently overwritten | Flag as conflict, require manual resolution | +| Treating skill bundles as `SKILL.md` only | Mirrored skills break because references, assets, or scripts are missing | Mirror the full skill directory contents | +| Preserving target-owned non-local assets under mirrored categories | The target drifts away from the standards catalog | Delete non-local target-only assets during apply | +| Generating a plan only in stdout | The user loses visibility on pending or failed sync steps after the run ends | Persist `tmp/internal-sync-copilot-configs.plan.md` until every section is cleared | | Updating root AGENTS.md before copilot-instructions.md | The bridge can drift from the source policy | Refresh target `.github/copilot-instructions.md` first, then regenerate root `AGENTS.md` | | Copying detailed Copilot policy into root AGENTS.md | The root bridge becomes redundant and harder to maintain | Keep detailed policy in `.github/copilot-instructions.md` and keep `AGENTS.md` concise | -| Treating target-local unmanaged assets as disposable noise | Local configuration gets lost during alignment | Preserve unmanaged local assets and surface them in the report or as conflicts | -| Hardcoding profiles instead of detecting stacks | New stacks in target repo get no coverage | Detect dynamically from file extensions | +| Treating target `local-*` assets as disposable noise | Local configuration gets lost during alignment | Preserve `local-*` assets and surface them in the report | +| Hardcoding target assumptions beyond `.github/` and root `AGENTS.md` | The sync agent becomes repo-specific and brittle | Keep the agent target-agnostic and derive everything else from the repository state | ## Cross-references - **internal-pair-architect** (`.github/skills/internal-pair-architect/SKILL.md`): for impact analysis when sync changes baseline behavior. diff --git a/.github/skills/obra-brainstorming/SKILL.md b/.github/skills/obra-brainstorming/SKILL.md index 32b5eef..dc61439 100644 --- a/.github/skills/obra-brainstorming/SKILL.md +++ b/.github/skills/obra-brainstorming/SKILL.md @@ -1,75 +1,164 @@ --- name: obra-brainstorming -description: Interactive idea refinement using Socratic method to develop fully-formed designs -when_to_use: when partner describes any feature or project idea, before writing code or implementation plans -version: 2.2.0 +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 -## Overview +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. + + +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: "This Is Too Simple To Need A Design" + +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 + +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 visual companion** (if topic will involve visual questions) — this is its own message, not combined with a clarifying question. See the Visual Companion section below. +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 +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 + +## Process Flow + +```dot +digraph brainstorming { + "Explore project context" [shape=box]; + "Visual questions ahead?" [shape=diamond]; + "Offer Visual Companion\n(own message, no other content)" [shape=box]; + "Ask clarifying questions" [shape=box]; + "Propose 2-3 approaches" [shape=box]; + "Present design sections" [shape=box]; + "User approves design?" [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" -> "Visual questions ahead?"; + "Visual questions ahead?" -> "Offer Visual Companion\n(own message, no other content)" [label="yes"]; + "Visual questions ahead?" -> "Ask clarifying questions" [label="no"]; + "Offer Visual Companion\n(own message, no other content)" -> "Ask clarifying questions"; + "Ask 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"]; + "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"]; + "User reviews spec?" -> "Invoke writing-plans skill" [label="approved"]; +} +``` + +**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. -Transform rough ideas into fully-formed designs through structured questioning and alternative exploration. +## The Process -**Core principle:** Ask questions to understand, explore alternatives, present design incrementally for validation. +**Understanding the idea:** -**Announce at start:** "I'm using the Brainstorming skill to refine your idea into a design." +- 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 +- 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 +- Focus on understanding: purpose, constraints, success criteria -## The Process +**Exploring approaches:** + +- Propose 2-3 different approaches with trade-offs +- Present options conversationally with your recommendation and reasoning +- Lead with your recommended option and explain why + +**Presenting the design:** + +- Once you believe you understand what you're building, present the design +- Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced +- Ask after each section whether it looks right so far +- Cover: architecture, components, data flow, error handling, testing +- Be ready to go back and clarify if something doesn't make sense + +**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 +- For each unit, you should be able to answer: what does it do, how do you use it, and what does it depend on? +- Can someone understand what a unit does without reading its internals? Can you change the internals without breaking consumers? If not, the boundaries need work. +- Smaller, well-bounded units are also easier for you to work with - you reason better about code you can hold in context at once, and your edits are more reliable when files are focused. When a file grows large, that's often a signal that it's doing too much. + +**Working in existing codebases:** + +- Explore the current structure before proposing changes. Follow existing patterns. +- Where existing code has problems that affect the work (e.g., a file that's grown too large, unclear boundaries, tangled responsibilities), include targeted improvements as part of the design - the way a good developer improves code they're working in. +- Don't propose unrelated refactoring. Stay focused on what serves the current goal. + +## After the Design + +**Documentation:** + +- 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 + +**Spec Self-Review:** +After writing the spec document, look at it with fresh eyes: + +1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them. +2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions? +3. **Scope check:** Is this focused enough for a single implementation plan, or does it need decomposition? +4. **Ambiguity check:** Could any requirement be interpreted two different ways? If so, pick one and make it explicit. + +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." + +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. + +**Implementation:** + +- Invoke the writing-plans skill to create a detailed implementation plan +- 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 +- **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 +- **Incremental validation** - Present design, get approval before moving on +- **Be flexible** - Go back and clarify when something doesn't make sense + +## Visual Companion + +A browser-based companion for showing mockups, diagrams, and visual options during brainstorming. Available as a tool — not a mode. Accepting the companion means it's available for questions that benefit from visual treatment; it does NOT mean every question goes through the browser. + +**Offering the companion:** When you anticipate that upcoming questions will involve visual content (mockups, layouts, diagrams), offer it once for consent: +> "Some of what we're working on might be easier to explain if I can show it to you in a web browser. I can put together mockups, diagrams, comparisons, and other visuals as we go. This feature is still new and can be token-intensive. Want to try it? (Requires opening a local URL)" + +**This offer MUST be its own message.** Do not combine it with clarifying questions, context summaries, or any other content. The message should contain ONLY the offer above and nothing else. Wait for the user's response before continuing. If they decline, proceed with text-only brainstorming. + +**Per-question decision:** Even after the user accepts, decide FOR EACH QUESTION whether to use the browser or the terminal. The test: **would the user understand this better by seeing it than reading it?** + +- **Use the browser** for content that IS visual — mockups, wireframes, layout comparisons, architecture diagrams, side-by-side visual designs +- **Use the terminal** for content that is text — requirements questions, conceptual choices, tradeoff lists, A/B/C/D text options, scope decisions + +A question about a UI topic is not automatically a visual question. "What does personality mean in this context?" is a conceptual question — use the terminal. "Which wizard layout works better?" is a visual question — use the browser. -### Phase 1: Understanding -- Check current project state in working directory -- Ask ONE question at a time to refine the idea -- Prefer multiple choice when possible -- Gather: Purpose, constraints, success criteria - -### Phase 2: Exploration -- Propose 2-3 different approaches -- For each: Core architecture, trade-offs, complexity assessment -- Ask your human partner which approach resonates - -### Phase 3: Design Presentation -- Present in 200-300 word sections -- Cover: Architecture, components, data flow, error handling, testing -- Ask after each section: "Does this look right so far?" - -### Phase 4: Worktree Setup (for implementation) -When design is approved and implementation will follow: -- Announce: "I'm using the Using Git Worktrees skill to set up an isolated workspace." -- Switch to skills/collaboration/using-git-worktrees -- Follow that skill's process for directory selection, safety verification, and setup -- Return here when worktree ready - -### Phase 5: Planning Handoff -Ask: "Ready to create the implementation plan?" - -When your human partner confirms (any affirmative response): -- Announce: "I'm using the Writing Plans skill to create the implementation plan." -- Switch to skills/collaboration/writing-plans skill -- Create detailed plan in the worktree - -## When to Revisit Earlier Phases - -**You can and should go backward when:** -- Partner reveals new constraint during Phase 2 or 3 → Return to Phase 1 to understand it -- Validation shows fundamental gap in requirements → Return to Phase 1 -- Partner questions approach during Phase 3 → Return to Phase 2 to explore alternatives -- Something doesn't make sense → Go back and clarify - -**Don't force forward linearly** when going backward would give better results. - -## Related Skills - -**During exploration:** -- When approaches have genuine trade-offs: skills/architecture/preserving-productive-tensions - -**Before proposing changes to existing code:** -- Understand why it exists: skills/research/tracing-knowledge-lineages - -## Remember -- One question per message during Phase 1 -- Apply YAGNI ruthlessly -- Explore 2-3 alternatives before settling -- Present incrementally, validate as you go -- Go backward when needed - flexibility > rigid progression -- Announce skill usage at start +If they agree to the companion, read the detailed guide before proceeding: +`skills/brainstorming/visual-companion.md` diff --git a/.github/skills/obra-brainstorming/scripts/frame-template.html b/.github/skills/obra-brainstorming/scripts/frame-template.html new file mode 100644 index 0000000..dcfe018 --- /dev/null +++ b/.github/skills/obra-brainstorming/scripts/frame-template.html @@ -0,0 +1,214 @@ + + + + + Superpowers Brainstorming + + + +
+

Superpowers Brainstorming

+
Connected
+
+ +
+
+ +
+
+ +
+ Click an option above, then return to the terminal +
+ + + diff --git a/.github/skills/obra-brainstorming/scripts/helper.js b/.github/skills/obra-brainstorming/scripts/helper.js new file mode 100644 index 0000000..111f97f --- /dev/null +++ b/.github/skills/obra-brainstorming/scripts/helper.js @@ -0,0 +1,88 @@ +(function() { + const WS_URL = 'ws://' + window.location.host; + let ws = null; + let eventQueue = []; + + function connect() { + ws = new WebSocket(WS_URL); + + ws.onopen = () => { + eventQueue.forEach(e => ws.send(JSON.stringify(e))); + eventQueue = []; + }; + + ws.onmessage = (msg) => { + const data = JSON.parse(msg.data); + if (data.type === 'reload') { + window.location.reload(); + } + }; + + ws.onclose = () => { + setTimeout(connect, 1000); + }; + } + + function sendEvent(event) { + event.timestamp = Date.now(); + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(event)); + } else { + eventQueue.push(event); + } + } + + // Capture clicks on choice elements + document.addEventListener('click', (e) => { + const target = e.target.closest('[data-choice]'); + if (!target) return; + + sendEvent({ + type: 'click', + text: target.textContent.trim(), + choice: target.dataset.choice, + id: target.id || null + }); + + // Update indicator bar (defer so toggleSelect runs first) + setTimeout(() => { + const indicator = document.getElementById('indicator-text'); + if (!indicator) return; + const container = target.closest('.options') || target.closest('.cards'); + const selected = container ? container.querySelectorAll('.selected') : []; + if (selected.length === 0) { + indicator.textContent = 'Click an option above, then return to the terminal'; + } else if (selected.length === 1) { + const label = selected[0].querySelector('h3, .content h3, .card-body h3')?.textContent?.trim() || selected[0].dataset.choice; + indicator.innerHTML = '' + label + ' selected — return to terminal to continue'; + } else { + indicator.innerHTML = '' + selected.length + ' selected — return to terminal to continue'; + } + }, 0); + }); + + // Frame UI: selection tracking + window.selectedChoice = null; + + window.toggleSelect = function(el) { + const container = el.closest('.options') || el.closest('.cards'); + const multi = container && container.dataset.multiselect !== undefined; + if (container && !multi) { + container.querySelectorAll('.option, .card').forEach(o => o.classList.remove('selected')); + } + if (multi) { + el.classList.toggle('selected'); + } else { + el.classList.add('selected'); + } + window.selectedChoice = el.dataset.choice; + }; + + // Expose API for explicit use + window.brainstorm = { + send: sendEvent, + choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata }) + }; + + connect(); +})(); diff --git a/.github/skills/obra-brainstorming/scripts/server.cjs b/.github/skills/obra-brainstorming/scripts/server.cjs new file mode 100644 index 0000000..562c17f --- /dev/null +++ b/.github/skills/obra-brainstorming/scripts/server.cjs @@ -0,0 +1,354 @@ +const crypto = require('crypto'); +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +// ========== WebSocket Protocol (RFC 6455) ========== + +const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A }; +const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + +function computeAcceptKey(clientKey) { + return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64'); +} + +function encodeFrame(opcode, payload) { + const fin = 0x80; + const len = payload.length; + let header; + + if (len < 126) { + header = Buffer.alloc(2); + header[0] = fin | opcode; + header[1] = len; + } else if (len < 65536) { + header = Buffer.alloc(4); + header[0] = fin | opcode; + header[1] = 126; + header.writeUInt16BE(len, 2); + } else { + header = Buffer.alloc(10); + header[0] = fin | opcode; + header[1] = 127; + header.writeBigUInt64BE(BigInt(len), 2); + } + + return Buffer.concat([header, payload]); +} + +function decodeFrame(buffer) { + if (buffer.length < 2) return null; + + const secondByte = buffer[1]; + const opcode = buffer[0] & 0x0F; + const masked = (secondByte & 0x80) !== 0; + let payloadLen = secondByte & 0x7F; + let offset = 2; + + if (!masked) throw new Error('Client frames must be masked'); + + if (payloadLen === 126) { + if (buffer.length < 4) return null; + payloadLen = buffer.readUInt16BE(2); + offset = 4; + } else if (payloadLen === 127) { + if (buffer.length < 10) return null; + payloadLen = Number(buffer.readBigUInt64BE(2)); + offset = 10; + } + + const maskOffset = offset; + const dataOffset = offset + 4; + const totalLen = dataOffset + payloadLen; + if (buffer.length < totalLen) return null; + + const mask = buffer.slice(maskOffset, dataOffset); + const data = Buffer.alloc(payloadLen); + for (let i = 0; i < payloadLen; i++) { + data[i] = buffer[dataOffset + i] ^ mask[i % 4]; + } + + return { opcode, payload: data, bytesConsumed: totalLen }; +} + +// ========== Configuration ========== + +const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383)); +const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1'; +const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST); +const SESSION_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm'; +const CONTENT_DIR = path.join(SESSION_DIR, 'content'); +const STATE_DIR = path.join(SESSION_DIR, 'state'); +let ownerPid = process.env.BRAINSTORM_OWNER_PID ? Number(process.env.BRAINSTORM_OWNER_PID) : null; + +const MIME_TYPES = { + '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', + '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml' +}; + +// ========== Templates and Constants ========== + +const WAITING_PAGE = ` + +Brainstorm Companion + + +

Brainstorm Companion

+

Waiting for the agent to push a screen...

`; + +const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8'); +const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8'); +const helperInjection = ''; + +// ========== Helper Functions ========== + +function isFullDocument(html) { + const trimmed = html.trimStart().toLowerCase(); + return trimmed.startsWith('', content); +} + +function getNewestScreen() { + const files = fs.readdirSync(CONTENT_DIR) + .filter(f => f.endsWith('.html')) + .map(f => { + const fp = path.join(CONTENT_DIR, f); + return { path: fp, mtime: fs.statSync(fp).mtime.getTime() }; + }) + .sort((a, b) => b.mtime - a.mtime); + return files.length > 0 ? files[0].path : null; +} + +// ========== HTTP Request Handler ========== + +function handleRequest(req, res) { + touchActivity(); + if (req.method === 'GET' && req.url === '/') { + const screenFile = getNewestScreen(); + let html = screenFile + ? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, 'utf-8')) + : WAITING_PAGE; + + if (html.includes('')) { + html = html.replace('', helperInjection + '\n'); + } else { + html += helperInjection; + } + + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(html); + } else if (req.method === 'GET' && req.url.startsWith('/files/')) { + const fileName = req.url.slice(7); + const filePath = path.join(CONTENT_DIR, path.basename(fileName)); + if (!fs.existsSync(filePath)) { + res.writeHead(404); + res.end('Not found'); + return; + } + const ext = path.extname(filePath).toLowerCase(); + const contentType = MIME_TYPES[ext] || 'application/octet-stream'; + res.writeHead(200, { 'Content-Type': contentType }); + res.end(fs.readFileSync(filePath)); + } else { + res.writeHead(404); + res.end('Not found'); + } +} + +// ========== WebSocket Connection Handling ========== + +const clients = new Set(); + +function handleUpgrade(req, socket) { + const key = req.headers['sec-websocket-key']; + if (!key) { socket.destroy(); return; } + + const accept = computeAcceptKey(key); + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + 'Sec-WebSocket-Accept: ' + accept + '\r\n\r\n' + ); + + let buffer = Buffer.alloc(0); + clients.add(socket); + + socket.on('data', (chunk) => { + buffer = Buffer.concat([buffer, chunk]); + while (buffer.length > 0) { + let result; + try { + result = decodeFrame(buffer); + } catch (e) { + socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); + clients.delete(socket); + return; + } + if (!result) break; + buffer = buffer.slice(result.bytesConsumed); + + switch (result.opcode) { + case OPCODES.TEXT: + handleMessage(result.payload.toString()); + break; + case OPCODES.CLOSE: + socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); + clients.delete(socket); + return; + case OPCODES.PING: + socket.write(encodeFrame(OPCODES.PONG, result.payload)); + break; + case OPCODES.PONG: + break; + default: { + const closeBuf = Buffer.alloc(2); + closeBuf.writeUInt16BE(1003); + socket.end(encodeFrame(OPCODES.CLOSE, closeBuf)); + clients.delete(socket); + return; + } + } + } + }); + + socket.on('close', () => clients.delete(socket)); + socket.on('error', () => clients.delete(socket)); +} + +function handleMessage(text) { + let event; + try { + event = JSON.parse(text); + } catch (e) { + console.error('Failed to parse WebSocket message:', e.message); + return; + } + touchActivity(); + console.log(JSON.stringify({ source: 'user-event', ...event })); + if (event.choice) { + const eventsFile = path.join(STATE_DIR, 'events'); + fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n'); + } +} + +function broadcast(msg) { + const frame = encodeFrame(OPCODES.TEXT, Buffer.from(JSON.stringify(msg))); + for (const socket of clients) { + try { socket.write(frame); } catch (e) { clients.delete(socket); } + } +} + +// ========== Activity Tracking ========== + +const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes +let lastActivity = Date.now(); + +function touchActivity() { + lastActivity = Date.now(); +} + +// ========== File Watching ========== + +const debounceTimers = new Map(); + +// ========== Server Startup ========== + +function startServer() { + if (!fs.existsSync(CONTENT_DIR)) fs.mkdirSync(CONTENT_DIR, { recursive: true }); + if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true }); + + // Track known files to distinguish new screens from updates. + // macOS fs.watch reports 'rename' for both new files and overwrites, + // so we can't rely on eventType alone. + const knownFiles = new Set( + fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html')) + ); + + const server = http.createServer(handleRequest); + server.on('upgrade', handleUpgrade); + + const watcher = fs.watch(CONTENT_DIR, (eventType, filename) => { + if (!filename || !filename.endsWith('.html')) return; + + if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename)); + debounceTimers.set(filename, setTimeout(() => { + debounceTimers.delete(filename); + const filePath = path.join(CONTENT_DIR, filename); + + if (!fs.existsSync(filePath)) return; // file was deleted + touchActivity(); + + if (!knownFiles.has(filename)) { + knownFiles.add(filename); + const eventsFile = path.join(STATE_DIR, 'events'); + if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile); + console.log(JSON.stringify({ type: 'screen-added', file: filePath })); + } else { + console.log(JSON.stringify({ type: 'screen-updated', file: filePath })); + } + + broadcast({ type: 'reload' }); + }, 100)); + }); + watcher.on('error', (err) => console.error('fs.watch error:', err.message)); + + function shutdown(reason) { + console.log(JSON.stringify({ type: 'server-stopped', reason })); + const infoFile = path.join(STATE_DIR, 'server-info'); + if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile); + fs.writeFileSync( + path.join(STATE_DIR, 'server-stopped'), + JSON.stringify({ reason, timestamp: Date.now() }) + '\n' + ); + watcher.close(); + clearInterval(lifecycleCheck); + server.close(() => process.exit(0)); + } + + function ownerAlive() { + if (!ownerPid) return true; + try { process.kill(ownerPid, 0); return true; } catch (e) { return e.code === 'EPERM'; } + } + + // Check every 60s: exit if owner process died or idle for 30 minutes + const lifecycleCheck = setInterval(() => { + if (!ownerAlive()) shutdown('owner process exited'); + else if (Date.now() - lastActivity > IDLE_TIMEOUT_MS) shutdown('idle timeout'); + }, 60 * 1000); + lifecycleCheck.unref(); + + // Validate owner PID at startup. If it's already dead, the PID resolution + // was wrong (common on WSL, Tailscale SSH, and cross-user scenarios). + // Disable monitoring and rely on the idle timeout instead. + if (ownerPid) { + try { process.kill(ownerPid, 0); } + catch (e) { + if (e.code !== 'EPERM') { + console.log(JSON.stringify({ type: 'owner-pid-invalid', pid: ownerPid, reason: 'dead at startup' })); + ownerPid = null; + } + } + } + + server.listen(PORT, HOST, () => { + const info = JSON.stringify({ + type: 'server-started', port: Number(PORT), host: HOST, + url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT, + screen_dir: CONTENT_DIR, state_dir: STATE_DIR + }); + console.log(info); + fs.writeFileSync(path.join(STATE_DIR, 'server-info'), info + '\n'); + }); +} + +if (require.main === module) { + startServer(); +} + +module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES }; diff --git a/.github/skills/obra-brainstorming/scripts/start-server.sh b/.github/skills/obra-brainstorming/scripts/start-server.sh new file mode 100755 index 0000000..9ef6dcb --- /dev/null +++ b/.github/skills/obra-brainstorming/scripts/start-server.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +# Start the brainstorm server and output connection info +# Usage: start-server.sh [--project-dir ] [--host ] [--url-host ] [--foreground] [--background] +# +# Starts server on a random high port, outputs JSON with URL. +# Each session gets its own directory to avoid conflicts. +# +# Options: +# --project-dir Store session files under /.superpowers/brainstorm/ +# instead of /tmp. Files persist after server stops. +# --host Host/interface to bind (default: 127.0.0.1). +# Use 0.0.0.0 in remote/containerized environments. +# --url-host Hostname shown in returned URL JSON. +# --foreground Run server in the current terminal (no backgrounding). +# --background Force background mode (overrides Codex auto-foreground). + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Parse arguments +PROJECT_DIR="" +FOREGROUND="false" +FORCE_BACKGROUND="false" +BIND_HOST="127.0.0.1" +URL_HOST="" +while [[ $# -gt 0 ]]; do + case "$1" in + --project-dir) + PROJECT_DIR="$2" + shift 2 + ;; + --host) + BIND_HOST="$2" + shift 2 + ;; + --url-host) + URL_HOST="$2" + shift 2 + ;; + --foreground|--no-daemon) + FOREGROUND="true" + shift + ;; + --background|--daemon) + FORCE_BACKGROUND="true" + shift + ;; + *) + echo "{\"error\": \"Unknown argument: $1\"}" + exit 1 + ;; + esac +done + +if [[ -z "$URL_HOST" ]]; then + if [[ "$BIND_HOST" == "127.0.0.1" || "$BIND_HOST" == "localhost" ]]; then + URL_HOST="localhost" + else + URL_HOST="$BIND_HOST" + fi +fi + +# Some environments reap detached/background processes. Auto-foreground when detected. +if [[ -n "${CODEX_CI:-}" && "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then + FOREGROUND="true" +fi + +# Windows/Git Bash reaps nohup background processes. Auto-foreground when detected. +if [[ "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then + case "${OSTYPE:-}" in + msys*|cygwin*|mingw*) FOREGROUND="true" ;; + esac + if [[ -n "${MSYSTEM:-}" ]]; then + FOREGROUND="true" + fi +fi + +# Generate unique session directory +SESSION_ID="$$-$(date +%s)" + +if [[ -n "$PROJECT_DIR" ]]; then + SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}" +else + SESSION_DIR="/tmp/brainstorm-${SESSION_ID}" +fi + +STATE_DIR="${SESSION_DIR}/state" +PID_FILE="${STATE_DIR}/server.pid" +LOG_FILE="${STATE_DIR}/server.log" + +# Create fresh session directory with content and state peers +mkdir -p "${SESSION_DIR}/content" "$STATE_DIR" + +# Kill any existing server +if [[ -f "$PID_FILE" ]]; then + old_pid=$(cat "$PID_FILE") + kill "$old_pid" 2>/dev/null + rm -f "$PID_FILE" +fi + +cd "$SCRIPT_DIR" + +# Resolve the harness PID (grandparent of this script). +# $PPID is the ephemeral shell the harness spawned to run us — it dies +# when this script exits. The harness itself is $PPID's parent. +OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ')" +if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then + OWNER_PID="$PPID" +fi + +# Foreground mode for environments that reap detached/background processes. +if [[ "$FOREGROUND" == "true" ]]; then + echo "$$" > "$PID_FILE" + env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs + exit $? +fi + +# Start server, capturing output to log file +# Use nohup to survive shell exit; disown to remove from job table +nohup env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs > "$LOG_FILE" 2>&1 & +SERVER_PID=$! +disown "$SERVER_PID" 2>/dev/null +echo "$SERVER_PID" > "$PID_FILE" + +# Wait for server-started message (check log file) +for i in {1..50}; do + if grep -q "server-started" "$LOG_FILE" 2>/dev/null; then + # Verify server is still alive after a short window (catches process reapers) + alive="true" + for _ in {1..20}; do + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + alive="false" + break + fi + sleep 0.1 + done + if [[ "$alive" != "true" ]]; then + echo "{\"error\": \"Server started but was killed. Retry in a persistent terminal with: $SCRIPT_DIR/start-server.sh${PROJECT_DIR:+ --project-dir $PROJECT_DIR} --host $BIND_HOST --url-host $URL_HOST --foreground\"}" + exit 1 + fi + grep "server-started" "$LOG_FILE" | head -1 + exit 0 + fi + sleep 0.1 +done + +# Timeout - server didn't start +echo '{"error": "Server failed to start within 5 seconds"}' +exit 1 diff --git a/.github/skills/obra-brainstorming/scripts/stop-server.sh b/.github/skills/obra-brainstorming/scripts/stop-server.sh new file mode 100755 index 0000000..a6b94e6 --- /dev/null +++ b/.github/skills/obra-brainstorming/scripts/stop-server.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Stop the brainstorm server and clean up +# Usage: stop-server.sh +# +# Kills the server process. Only deletes session directory if it's +# under /tmp (ephemeral). Persistent directories (.superpowers/) are +# kept so mockups can be reviewed later. + +SESSION_DIR="$1" + +if [[ -z "$SESSION_DIR" ]]; then + echo '{"error": "Usage: stop-server.sh "}' + exit 1 +fi + +STATE_DIR="${SESSION_DIR}/state" +PID_FILE="${STATE_DIR}/server.pid" + +if [[ -f "$PID_FILE" ]]; then + pid=$(cat "$PID_FILE") + + # Try to stop gracefully, fallback to force if still alive + kill "$pid" 2>/dev/null || true + + # Wait for graceful shutdown (up to ~2s) + for i in {1..20}; do + if ! kill -0 "$pid" 2>/dev/null; then + break + fi + sleep 0.1 + done + + # If still running, escalate to SIGKILL + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + + # Give SIGKILL a moment to take effect + sleep 0.1 + fi + + if kill -0 "$pid" 2>/dev/null; then + echo '{"status": "failed", "error": "process still running"}' + exit 1 + fi + + rm -f "$PID_FILE" "${STATE_DIR}/server.log" + + # Only delete ephemeral /tmp directories + if [[ "$SESSION_DIR" == /tmp/* ]]; then + rm -rf "$SESSION_DIR" + fi + + echo '{"status": "stopped"}' +else + echo '{"status": "not_running"}' +fi diff --git a/.github/skills/obra-brainstorming/spec-document-reviewer-prompt.md b/.github/skills/obra-brainstorming/spec-document-reviewer-prompt.md new file mode 100644 index 0000000..35acbb6 --- /dev/null +++ b/.github/skills/obra-brainstorming/spec-document-reviewer-prompt.md @@ -0,0 +1,49 @@ +# Spec Document Reviewer Prompt Template + +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/ + +``` +Task tool (general-purpose): + description: "Review spec document" + prompt: | + You are a spec document reviewer. Verify this spec is complete and ready for planning. + + **Spec to review:** [SPEC_FILE_PATH] + + ## What to Check + + | Category | What to Look For | + |----------|------------------| + | Completeness | TODOs, placeholders, "TBD", incomplete sections | + | Consistency | Internal contradictions, conflicting requirements | + | Clarity | Requirements ambiguous enough to cause someone to build the wrong thing | + | Scope | Focused enough for a single plan — not covering multiple independent subsystems | + | YAGNI | Unrequested features, over-engineering | + + ## Calibration + + **Only flag issues that would cause real problems during implementation planning.** + A missing section, a contradiction, or a requirement so ambiguous it could be + interpreted two different ways — those are issues. Minor wording improvements, + stylistic preferences, and "sections less detailed than others" are not. + + Approve unless there are serious gaps that would lead to a flawed plan. + + ## Output Format + + ## Spec Review + + **Status:** Approved | Issues Found + + **Issues (if any):** + - [Section X]: [specific issue] - [why it matters for planning] + + **Recommendations (advisory, do not block approval):** + - [suggestions for improvement] +``` + +**Reviewer returns:** Status, Issues (if any), Recommendations diff --git a/.github/skills/obra-brainstorming/visual-companion.md b/.github/skills/obra-brainstorming/visual-companion.md new file mode 100644 index 0000000..2113863 --- /dev/null +++ b/.github/skills/obra-brainstorming/visual-companion.md @@ -0,0 +1,287 @@ +# Visual Companion Guide + +Browser-based visual brainstorming companion for showing mockups, diagrams, and options. + +## When to Use + +Decide per-question, not per-session. The test: **would the user understand this better by seeing it than reading it?** + +**Use the browser** when the content itself is visual: + +- **UI mockups** — wireframes, layouts, navigation structures, component designs +- **Architecture diagrams** — system components, data flow, relationship maps +- **Side-by-side visual comparisons** — comparing two layouts, two color schemes, two design directions +- **Design polish** — when the question is about look and feel, spacing, visual hierarchy +- **Spatial relationships** — state machines, flowcharts, entity relationships rendered as diagrams + +**Use the terminal** when the content is text or tabular: + +- **Requirements and scope questions** — "what does X mean?", "which features are in scope?" +- **Conceptual A/B/C choices** — picking between approaches described in words +- **Tradeoff lists** — pros/cons, comparison tables +- **Technical decisions** — API design, data modeling, architectural approach selection +- **Clarifying questions** — anything where the answer is words, not a visual preference + +A question *about* a UI topic is not automatically a visual question. "What kind of wizard do you want?" is conceptual — use the terminal. "Which of these wizard layouts feels right?" is visual — use the browser. + +## How It Works + +The server watches a directory for HTML files and serves the newest one to the browser. You write HTML content to `screen_dir`, the user sees it in their browser and can click to select options. Selections are recorded to `state_dir/events` that you read on your next turn. + +**Content fragments vs full documents:** If your HTML file starts with `/.superpowers/brainstorm/` for the session directory. + +**Note:** Pass the project root as `--project-dir` so mockups persist in `.superpowers/brainstorm/` and survive server restarts. Without it, files go to `/tmp` and get cleaned up. Remind the user to add `.superpowers/` to `.gitignore` if it's not already there. + +**Launching the server by platform:** + +**Claude Code (macOS / Linux):** +```bash +# Default mode works — the script backgrounds the server itself +scripts/start-server.sh --project-dir /path/to/project +``` + +**Claude Code (Windows):** +```bash +# Windows auto-detects and uses foreground mode, which blocks the tool call. +# Use run_in_background: true on the Bash tool call so the server survives +# across conversation turns. +scripts/start-server.sh --project-dir /path/to/project +``` +When calling this via the Bash tool, set `run_in_background: true`. Then read `$STATE_DIR/server-info` on the next turn to get the URL and port. + +**Codex:** +```bash +# Codex reaps background processes. The script auto-detects CODEX_CI and +# switches to foreground mode. Run it normally — no extra flags needed. +scripts/start-server.sh --project-dir /path/to/project +``` + +**Gemini CLI:** +```bash +# Use --foreground and set is_background: true on your shell tool call +# so the process survives across turns +scripts/start-server.sh --project-dir /path/to/project --foreground +``` + +**Other environments:** The server must keep running in the background across conversation turns. If your environment reaps detached processes, use `--foreground` and launch the command with your platform's background execution mechanism. + +If the URL is unreachable from your browser (common in remote/containerized setups), bind a non-loopback host: + +```bash +scripts/start-server.sh \ + --project-dir /path/to/project \ + --host 0.0.0.0 \ + --url-host localhost +``` + +Use `--url-host` to control what hostname is printed in the returned URL JSON. + +## The Loop + +1. **Check server is alive**, then **write HTML** to a new file in `screen_dir`: + - Before each write, check that `$STATE_DIR/server-info` exists. If it doesn't (or `$STATE_DIR/server-stopped` exists), the server has shut down — restart it with `start-server.sh` before continuing. The server auto-exits after 30 minutes of inactivity. + - Use semantic filenames: `platform.html`, `visual-style.html`, `layout.html` + - **Never reuse filenames** — each screen gets a fresh file + - Use Write tool — **never use cat/heredoc** (dumps noise into terminal) + - Server automatically serves the newest file + +2. **Tell user what to expect and end your turn:** + - Remind them of the URL (every step, not just first) + - Give a brief text summary of what's on screen (e.g., "Showing 3 layout options for the homepage") + - Ask them to respond in the terminal: "Take a look and let me know what you think. Click to select an option if you'd like." + +3. **On your next turn** — after the user responds in the terminal: + - Read `$STATE_DIR/events` if it exists — this contains the user's browser interactions (clicks, selections) as JSON lines + - Merge with the user's terminal text to get the full picture + - The terminal message is the primary feedback; `state_dir/events` provides structured interaction data + +4. **Iterate or advance** — if feedback changes current screen, write a new file (e.g., `layout-v2.html`). Only move to the next question when the current step is validated. + +5. **Unload when returning to terminal** — when the next step doesn't need the browser (e.g., a clarifying question, a tradeoff discussion), push a waiting screen to clear the stale content: + + ```html + +
+

Continuing in terminal...

+
+ ``` + + This prevents the user from staring at a resolved choice while the conversation has moved on. When the next visual question comes up, push a new content file as usual. + +6. Repeat until done. + +## Writing Content Fragments + +Write just the content that goes inside the page. The server wraps it in the frame template automatically (header, theme CSS, selection indicator, and all interactive infrastructure). + +**Minimal example:** + +```html +

Which layout works better?

+

Consider readability and visual hierarchy

+ +
+
+
A
+
+

Single Column

+

Clean, focused reading experience

+
+
+
+
B
+
+

Two Column

+

Sidebar navigation with main content

+
+
+
+``` + +That's it. No ``, no CSS, no `