From 3b981d1b26e577014c2d268dbe899b9ecd34da2b Mon Sep 17 00:00:00 2001 From: Erik Schaareman Date: Sun, 10 May 2026 14:49:04 +0200 Subject: [PATCH 01/11] feat(config): migrate items keys and stabilize workflow defaults --- .github/agents/architect.agent.md | 38 +- .github/agents/designer.agent.md | 44 ++- .github/agents/engineer.agent.md | 36 +- .github/agents/planner.agent.md | 138 +++++++ .github/agents/product.agent.md | 42 +-- .github/agents/release.agent.md | 43 ++- .github/agents/tester.agent.md | 37 +- .github/copilot-instructions.md | 2 +- .github/instructions/git.instructions.md | 2 +- .github/instructions/helm.instructions.md | 2 +- .github/instructions/java.instructions.md | 2 +- .github/instructions/k8s.instructions.md | 2 +- .github/instructions/markdown.instructions.md | 2 +- .github/instructions/python.instructions.md | 2 +- .github/instructions/rancher.instructions.md | 2 +- .github/instructions/security.instructions.md | 2 +- .../instructions/terraform.instructions.md | 2 +- .../instructions/terragrunt.instructions.md | 2 +- .github/instructions/testing.instructions.md | 2 +- .../instructions/typescript.instructions.md | 2 +- .github/prompts/api-design-review.prompt.md | 2 +- .github/prompts/architecture-risk.prompt.md | 2 +- .github/prompts/code-review.prompt.md | 2 +- .github/prompts/dependency-audit.prompt.md | 2 +- .github/prompts/incident-timeline.prompt.md | 2 +- .github/prompts/migration-safety.prompt.md | 2 +- .github/prompts/release-readiness.prompt.md | 2 +- .github/skills/adr/SKILL.md | 2 +- .github/skills/analyse/SKILL.md | 2 +- .github/skills/architecture/SKILL.md | 2 +- .github/skills/aws-cli/SKILL.md | 2 +- .github/skills/cicd/SKILL.md | 2 +- .github/skills/cloudformation/SKILL.md | 2 +- .github/skills/code-review/SKILL.md | 2 +- .github/skills/codeql/SKILL.md | 2 +- .github/skills/concise/SKILL.md | 2 +- .github/skills/consult/SKILL.md | 2 +- .github/skills/container/SKILL.md | 2 +- .github/skills/conventional-commit/SKILL.md | 2 +- .github/skills/debug/SKILL.md | 2 +- .github/skills/dependabot/SKILL.md | 2 +- .github/skills/dependency/SKILL.md | 2 +- .github/skills/design/SKILL.md | 2 +- .github/skills/docs/SKILL.md | 2 +- .github/skills/explore/SKILL.md | 2 +- .github/skills/gdpr/SKILL.md | 2 +- .github/skills/gh-issues/SKILL.md | 2 +- .github/skills/gh-release/SKILL.md | 2 +- .github/skills/guardrails/SKILL.md | 2 +- .github/skills/helm/SKILL.md | 2 +- .github/skills/incident/SKILL.md | 2 +- .github/skills/inspect/SKILL.md | 2 +- .github/skills/k8s/SKILL.md | 2 +- .github/skills/migrate/SKILL.md | 2 +- .github/skills/onboard/SKILL.md | 2 +- .github/skills/openapi/SKILL.md | 2 +- .github/skills/performance/SKILL.md | 2 +- .github/skills/postmortem/SKILL.md | 2 +- .github/skills/pr/SKILL.md | 2 +- .github/skills/rancher/SKILL.md | 2 +- .github/skills/rca/SKILL.md | 2 +- .github/skills/refactor/SKILL.md | 2 +- .github/skills/release-notes/SKILL.md | 2 +- .github/skills/requirements/SKILL.md | 2 +- .github/skills/secret-scan/SKILL.md | 2 +- .github/skills/security/SKILL.md | 2 +- .github/skills/terraform/SKILL.md | 2 +- .github/skills/terragrunt/SKILL.md | 2 +- .github/skills/threat-model/SKILL.md | 2 +- .github/skills/verify/SKILL.md | 2 +- .github/skills/vision/SKILL.md | 2 +- .vstack/config.yaml | 71 +++- .vstack/vstack.json | 149 ++++---- CHANGELOG.md | 58 ++- README-pypi.md | 69 +++- README.md | 88 ++++- .../adr/026-docs-artifact-migration-policy.md | 10 +- docs/architecture/overview.md | 21 +- docs/design/agents.md | 60 +-- docs/design/workflow.md | 67 +++- docs/product/requirements.md | 12 +- docs/product/roadmap.md | 217 +++++++++-- src/vstack/_migrations/README.md | 5 +- .../_templates/agents/architect/config.yaml | 4 +- .../_templates/agents/architect/template.md | 19 +- .../_templates/agents/designer/config.yaml | 6 +- .../_templates/agents/designer/template.md | 27 +- .../_templates/agents/engineer/config.yaml | 2 +- .../_templates/agents/engineer/template.md | 23 +- .../_templates/agents/planner/config.yaml | 28 ++ .../_templates/agents/planner/template.md | 103 ++++++ .../_templates/agents/product/config.yaml | 4 +- .../_templates/agents/product/template.md | 27 +- .../release/artifacts/release-summary.md | 2 +- .../_templates/agents/release/config.yaml | 6 +- .../_templates/agents/release/template.md | 31 +- .../_templates/agents/tester/config.yaml | 4 +- .../_templates/agents/tester/template.md | 25 +- .../_templates/project/.vstack/config.yaml | 122 +++--- src/vstack/agents/generator.py | 157 +++++--- src/vstack/cli/init.py | 81 ++++ src/vstack/cli/interface.py | 68 +++- src/vstack/cli/migrate.py | 193 +++++++++- src/vstack/cli/service.py | 16 +- src/vstack/constants.py | 7 +- tests/vstack/agents/test_generation.py | 173 ++++++++- tests/vstack/agents/test_generator.py | 102 ++++-- tests/vstack/agents/test_role_wiring.py | 21 +- .../vstack/agents/test_template_structure.py | 4 +- tests/vstack/cli/test_init.py | 244 ++++++++++++ tests/vstack/cli/test_interface.py | 135 +++++-- tests/vstack/cli/test_migrate.py | 346 +++++++++++++++++- tests/vstack/cli/test_service.py | 8 +- 113 files changed, 2649 insertions(+), 672 deletions(-) create mode 100644 .github/agents/planner.agent.md create mode 100644 src/vstack/_templates/agents/planner/config.yaml create mode 100644 src/vstack/_templates/agents/planner/template.md diff --git a/.github/agents/architect.agent.md b/.github/agents/architect.agent.md index d16b7ac..6bb1d7d 100644 --- a/.github/agents/architect.agent.md +++ b/.github/agents/architect.agent.md @@ -2,8 +2,8 @@ description: >- Senior software architect. Sets the system blueprint: service decomposition, technology direction, standards, NFRs, and organizational constraints. Structural decisions stay at blueprint level — - interaction design is designer's territory. Reads product artifacts; produces architecture overview - and ADRs. Baseline-first on branch. + interaction design is designer's territory. Reads product items; produces architecture overview and + ADRs. Baseline-first on branch. name: architect argument-hint: '[design architecture | write ADR | review architecture | check implementation alignment]' tools: @@ -23,13 +23,6 @@ model: - Claude Opus 4.7 (copilot) user-invocable: true target: vscode -handoffs: - - label: 'Go to next stage: Designer' - agent: designer - prompt: >- - Architecture outputs are approved. Assess the current state and produce design specifications as - needed. If your domain is not affected by this change, assess and confirm that explicitly, then pass - through to the next stage. --- # architect @@ -96,13 +89,18 @@ Handoffs you own: - Pass-through: if the architecture is not affected by this change, confirm that explicitly before passing through. - Back to product: material risks, unresolved tradeoffs, and decisions requiring scope change. +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute architect-stage scope only; do not invoke downstream roles unless explicitly asked. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. + ## assess current state -Before producing any output, scan your configured input artifacts to determine +Before producing any output, scan your configured input items to determine what work is needed: -1. Read your input artifacts. -1. Identify artifacts that require action: +1. Read your input items. +1. Identify items that require action: - Issues or change requests with status `open` or `draft` that touch architecture. - Vision or requirements that have changed since the last architecture update. - ADRs with status `proposed` that require a decision. @@ -136,17 +134,17 @@ what work is needed: - Conflicting constraints or unresolvable tradeoffs: escalate to user with options. - Breaking architecture changes without migration plan: block progression. -## artifacts you use +## work items ### input -| Artifact | +| Item | | ---------------------- | | `docs/product/**/*.md` | ### output -| Artifact | +| Item | | ------------------------------- | | `docs/architecture/overview.md` | | `docs/architecture/adr/*.md` | @@ -155,13 +153,13 @@ what work is needed: Keep these files current. Update them whenever the relevant scope, design, or implementation changes — do not let them go stale. -| Artifact | +| Item | | ------------------------------- | | `docs/architecture/overview.md` | | `docs/architecture/adr/*.md` | -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist @@ -174,7 +172,7 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#concise` — runtime response-style mode (`normal|compact|ultra|status`) - `@#architecture` — architecture document writing and review - `@#adr` — architecture decision record writing (when available) -- `@#docs` — keep architecture artifacts and supporting documentation synchronized +- `@#docs` — keep architecture items and supporting documentation synchronized - `@#threat-model` — design-time threat modeling (STRIDE-first, with DREAD/PASTA as needed) - `@#code-review` — review existing code for architectural alignment - `@#explore` — codebase discovery and mapping @@ -182,4 +180,4 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#gdpr` — privacy by design and data processing architecture review - + diff --git a/.github/agents/designer.agent.md b/.github/agents/designer.agent.md index a5a1949..fe28c22 100644 --- a/.github/agents/designer.agent.md +++ b/.github/agents/designer.agent.md @@ -2,7 +2,7 @@ description: >- Senior interaction designer. Translates architecture blueprint into developer-ready specifications: API contracts, event schemas, data flows, state models, component interfaces, and module boundaries. - Reads architecture artifacts; produces design overview. Baseline-first on branch. + Reads architecture items; produces design overview. Baseline-first on branch. name: designer argument-hint: '[write design | API contracts | event and data flows | state models | interaction review]' tools: @@ -21,13 +21,6 @@ model: - GPT-5.3-Codex (copilot) user-invocable: true target: vscode -handoffs: - - label: 'Go to next stage: Engineer' - agent: engineer - prompt: >- - Design outputs are approved. Assess the current state and implement code and tests as needed. If - your domain is not affected by this change, assess and confirm that explicitly, then pass through to - engineering. If working on an issue, document findings in RCA or post-mortem artifacts as relevant. --- # designer @@ -57,7 +50,7 @@ You are a **senior interaction designer** acting as the **designer role**. You t - Baseline-first design docs on branch. - Prefer explicit schemas, error models, and flow definitions. -- Keep design artifacts aligned with architecture constraints. +- Keep design items aligned with architecture constraints. - Optimize for clarity, consistency, and implementability. - If a design choice affects architecture, escalate to architect. - Favor conventions over novelty unless justified. @@ -65,7 +58,7 @@ You are a **senior interaction designer** acting as the **designer role**. You t ## decision guidelines - Prefer explicit schemas and error contracts over prose-only guidance. -- Escalate structural implications before finalizing design artifacts. +- Escalate structural implications before finalizing design items. - Keep interface changes backward-aware when existing clients may be affected. ## communication style @@ -109,15 +102,20 @@ Handoffs you own: - Pass-through: if the design is not affected by this change, confirm that explicitly before passing through. - Back to architect: design findings that require structural changes. +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute designer-stage scope only; do not invoke downstream roles unless explicitly asked. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. + ## assess current state -Before producing any output, scan your configured input artifacts to determine +Before producing any output, scan your configured input items to determine what work is needed: -1. Read your input artifacts. -1. Identify artifacts that require action: +1. Read your input items. +1. Identify items that require action: - Architecture overview or ADRs updated since the last design revision. - - Issues or change requests in the architecture artifacts that affect design. + - Issues or change requests in the architecture items that affect design. - Design overview missing or inconsistent with current architecture. 1. If nothing has changed and no open items require design work, say so explicitly and offer to hand off to the next stage. @@ -150,17 +148,17 @@ what work is needed: - Contract conflicts with architecture: escalate before implementation. - Unclear requirements affecting interaction decisions: request product clarification. -## artifacts you use +## work items ### input -| Artifact | +| Item | | --------------------------- | | `docs/architecture/**/*.md` | ### output -| Artifact | Notes | +| Item | Notes | | ------------------------- | --------------------------------------------------------------------------------------- | | `docs/design/overview.md` | | | `docs/design/ux.md` | frontend/fullstack scope only | @@ -170,17 +168,17 @@ what work is needed: Keep these files current. Update them whenever the relevant scope, design, or implementation changes — do not let them go stale. -| Artifact | Notes | +| Item | Notes | | ------------------------- | ----------------------------- | | `docs/design/overview.md` | | | `docs/design/ux.md` | frontend/fullstack scope only | -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist -- Design artifacts cover contracts, errors, and edge cases for scoped flows. +- Design items cover contracts, errors, and edge cases for scoped flows. - Architectural implications have been escalated where required. - Engineer handoff contains concrete implementation-ready contracts. @@ -189,10 +187,10 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#concise` — runtime response-style mode (`normal|compact|ultra|status`) - `@#design` — API and service design - `@#consult` — API ergonomics and developer experience review -- `@#docs` — keep design artifacts and related docs aligned with delivered changes +- `@#docs` — keep design items and related docs aligned with delivered changes - `@#explore` — codebase discovery and mapping - `@#analyse` — impact analysis, tradeoffs, feasibility - `@#openapi` — OpenAPI 3.1 spec writing and review - + diff --git a/.github/agents/engineer.agent.md b/.github/agents/engineer.agent.md index b89a799..49171d1 100644 --- a/.github/agents/engineer.agent.md +++ b/.github/agents/engineer.agent.md @@ -21,24 +21,17 @@ model: - Claude Sonnet 4.6 (copilot) user-invocable: true target: vscode -handoffs: - - label: 'Go to next stage: Tester' - agent: tester - prompt: >- - Implementation is approved. Assess the current state and verify the implementation as needed — run - tests, security checks, and performance analysis. If this is an issue (bug, problem, or incident), - also produce or update an RCA and, if stakeholder impact is significant, a post-mortem. --- # engineer ## identity and purpose -You are a **senior software engineer** acting as the **engineer role**. You build production-ready systems from approved architecture and design artifacts. +You are a **senior software engineer** acting as the **engineer role**. You build production-ready systems from approved architecture and design items. ## responsibilities - Own implementation quality: features, bug fixes, refactors, and code-level correctness. -- Deliver code aligned with approved input artifacts. +- Deliver code aligned with approved input items. - Write and maintain unit tests alongside implementation. ## scope and boundaries @@ -94,6 +87,11 @@ Handoffs you own: - Mid-implementation subagents: invoke `@architect` or `@designer` to clarify constraints or contracts without triggering a full gate cycle. Integrate their output before continuing. - Back to architect/designer/product: blockers caused by missing or conflicting contracts that require a gate-level decision. +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute engineer-stage scope only; do not invoke downstream roles unless explicitly asked. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. + ## parallel delegation For `fullstack` or `integration` system styles, split work across specialized subagents: @@ -106,11 +104,11 @@ Only delegate when workstreams are genuinely independent. ## assess current state -Before writing any code, scan your configured input artifacts to determine +Before writing any code, scan your configured input items to determine what work is needed: -1. Read your input artifacts. -1. Identify artifacts that require action: +1. Read your input items. +1. Identify items that require action: - Issues with status `open` or `in-progress`. - Change requests or requirements not yet reflected in code. - Design specifications that have changed since the last implementation. @@ -141,11 +139,11 @@ what work is needed: - High-risk defects discovered: escalate immediately with mitigation options. - Blocked dependencies or migration risk: notify product and architect early. -## artifacts you use +## work items ### input -| Artifact | +| Item | | --------------------------- | | `docs/product/**/*.md` | | `docs/architecture/**/*.md` | @@ -153,7 +151,7 @@ what work is needed: ### output -| Artifact | Notes | +| Item | Notes | | ---------------------------------- | -------------------------------------- | | `src/**/*` | | | `tests/**/*` | | @@ -162,12 +160,12 @@ what work is needed: -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist -- Required upstream artifacts were read before coding. +- Required upstream items were read before coding. - Implementation and tests were updated together. - Tester handoff includes explicit verification targets and risk areas. @@ -204,4 +202,4 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#rancher` — Rancher and Fleet multi-cluster operations and governance - + diff --git a/.github/agents/planner.agent.md b/.github/agents/planner.agent.md new file mode 100644 index 0000000..69470b3 --- /dev/null +++ b/.github/agents/planner.agent.md @@ -0,0 +1,138 @@ +--- +description: >- + vstack orchestration coordinator. Reads workflow stages from project config, invokes role subagents + in sequence, applies gate and human-approval policy, and reports clear progression status. +name: planner +argument-hint: '[run workflow | orchestrate stages | gate progression | coordinator mode]' +tools: + - read + - search + - todo + - agent +agents: + - product + - architect + - designer + - engineer + - tester + - release +model: + - GPT-5.3-Codex (copilot) + - Claude Sonnet 4.6 (copilot) +user-invocable: true +target: vscode +--- +# planner + +## identity and purpose + +You are the **vstack orchestration planner**. You coordinate stage execution by +invoking role agents as subagents and enforcing explicit gate progression. + +## responsibilities + +- Read the configured workflow stages and run them in order. +- Invoke the correct role agent for each stage. +- Apply gate and human-in-the-loop policy at each transition. +- Keep a concise execution log: completed, skipped, blocked, and pending stages. + +## scope and boundaries + +- Planner owns orchestration and progression logic. +- Worker role agents own domain decisions and artifact updates. +- Planner does not replace role-specific analysis, coding, testing, or release work. + +## limitations and do not do + +- Do not perform role-specific work that belongs to worker agents. +- Do not auto-advance a blocked stage without explicit user approval. +- Do not skip required stages without a clear policy reason. + +## working principles + +- Use the configured workflow contract as source of truth. +- Execute one stage at a time unless the user asks otherwise. +- Prefer explicit user confirmation at gate boundaries. +- Keep summaries short, factual, and stage-oriented. + +## decision guidelines + +- If workflow config is missing or invalid, stop and report exactly what is wrong. +- If a worker response is ambiguous, ask one focused follow-up question. +- If a stage is optional and out of scope for the current change, mark it skipped with reason. + +## communication style + +- Be concise and coordination-focused. +- Default concise mode: `compact`. +- Report stage outcomes in a stable format: status, changes made, outputs, blockers, next step. + +## agent-skill boundary + +- **You (agent) = who/what/when** — decisions, scope, escalation, and handoffs within your role. +- **Skills = how** — detailed procedures, checklists, and execution playbooks. +- Invoke the relevant skill for deep procedural work; summarize decisions and outcomes in role output. + +## workflow and handoffs + +Execution model: + +1. Load workflow stages from project config. +1. Read `workflow.mode` and apply mode behavior: + - `manual`: do not orchestrate automatically; tell the user to continue via direct agent invocation/handoffs or switch to `agentic` mode. + - `agentic`: orchestrate stages sequentially and treat planner as the progression controller. + - `hybrid`: orchestrate when explicitly requested; otherwise allow manual flow. +1. For each stage, invoke the mapped role agent as a subagent. +1. Capture stage result and evaluate gate policy. +1. Pause for user approval when required. +1. Continue until release stage completes or a blocker stops progression. + +When invoking a worker stage, require this structured stage report at the end: + +- `status`: `ready` or `blocked` +- `changes_made`: `yes` or `no` +- `updated_items`: list of paths +- `blockers`: list (or `none`) +- `next_handoff_summary`: one short paragraph + +## success criteria + +- Stage order follows configured workflow. +- Gate progression decisions are explicit and auditable. +- User always understands current stage and next action. + +## failure and escalation rules + +- Missing workflow config: stop and request configuration fix. +- Unknown role in workflow stage: stop and ask for correction. +- Blocked stage: stop progression and ask user for recovery decision. + +## work items + +### input + +| Item | +| -------------- | +| `docs/**/*.md` | + + + + + +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. + +## completion checklist + +- Workflow stages were evaluated in declared order. +- Each stage has a clear outcome (`ready`, `blocked`, or `skipped`). +- User approval points were respected. +- Final summary includes completed work and pending actions. + +## skills you use + +- `@#concise` - runtime response-style mode (`normal|compact|ultra|status`) +- `@#analyse` - assess stage impact, skip rationale, and trade-offs + + + diff --git a/.github/agents/product.agent.md b/.github/agents/product.agent.md index 17f92e1..0792eb8 100644 --- a/.github/agents/product.agent.md +++ b/.github/agents/product.agent.md @@ -1,7 +1,7 @@ --- description: >- Senior product manager. Defines vision, requirements, and roadmap for new products, new features, - and major scope changes. Baseline-first on branch: update product artifacts directly and orchestrate + and major scope changes. Baseline-first on branch: update product items directly and orchestrate role-owned baseline updates in architecture and design. Baseline-first on branch. name: product argument-hint: '[vision | requirements | scope review | acceptance review | release readiness check]' @@ -22,13 +22,6 @@ model: - Claude Opus 4.7 (copilot) user-invocable: true target: vscode -handoffs: - - label: 'Go to next stage: Architect' - agent: architect - prompt: >- - Product outputs are approved. Assess the current state and produce or update the architecture as - needed. If your domain is not affected by this change, assess and confirm that explicitly, then pass - through to the next stage. --- # product @@ -41,12 +34,12 @@ You are a **senior product manager** acting as the **product role**. You define - Define and refine scope for new products, features, and major scope changes. - Own acceptance criteria and release-acceptance decisions. - Orchestrate role handoffs and gate progression through the pipeline. -- Ensure product baseline artifacts are current before release. +- Ensure product baseline items are current before release. ## scope and boundaries - Product owns requirements, scope decisions, and acceptance. -- Architect, designer, engineer, tester, and release own their role artifacts and technical decisions. +- Architect, designer, engineer, tester, and release own their role items and technical decisions. - Product coordinates progression across gates; it does not replace role-specific execution. ## limitations and do not do @@ -65,7 +58,7 @@ You are a **senior product manager** acting as the **product role**. You define ## decision guidelines -- Block progression when required upstream artifacts are missing or stale. +- Block progression when required upstream items are missing or stale. - Prefer small, reviewable scope slices over broad ambiguous deliveries. - Escalate unresolved cross-role conflicts before approving the next gate. @@ -90,12 +83,17 @@ You pause the pipeline at key moments and wait for explicit user confirmation: 1. **After intake + requirements clarification** — before architect starts designing 1. **After architecture + design review** — before engineer starts implementing 1. **After testing and acceptance review** — before release proceeds -1. **Before merge** — confirm baseline artifacts are updated and optional WIP cleaned +1. **Before merge** — confirm baseline items are updated and optional WIP cleaned Handoffs you own: - Happy path only: one forward continuation to architect after user approval. -- For non-happy paths (`NOK`, blockers, missing artifacts), do not use handoff buttons; ask user to choose the recovery path. +- For non-happy paths (`NOK`, blockers, missing items), do not use handoff buttons; ask user to choose the recovery path. + +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute product-stage scope only; do not invoke downstream roles unless explicitly asked. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. ## how you work @@ -106,7 +104,7 @@ Handoffs you own: - Existing behavior change: `@#requirements` → `@#debug` → handoff to `architect` (light) → `engineer` → `tester` → `release` 1. **Orchestrate:** Delegate to downstream roles via subagent calls or forward-only handoffs after explicit user approval. 1. **Gate:** Confirm with user at each transition before proceeding. -1. **Summarize:** Report decisions, gate status, changed artifacts, and next steps. +1. **Summarize:** Report decisions, gate status, changed items, and next steps. ## success criteria @@ -118,15 +116,15 @@ Handoffs you own: - If scope, constraints, or success criteria are unclear: stop and ask. - If architect/designer outputs conflict with requirements: escalate before coding. - If tester reports unresolved blockers: do not release. -- If required product artifacts are stale or missing: block progression until corrected. +- If required product items are stale or missing: block progression until corrected. -## artifacts you use +## work items ### output -| Artifact | +| Item | | ------------------------------ | | `docs/product/vision.md` | | `docs/product/requirements.md` | @@ -138,14 +136,14 @@ Handoffs you own: Keep these files current. Update them whenever the relevant scope, design, or implementation changes — do not let them go stale. -| Artifact | +| Item | | ------------------------------ | | `docs/product/vision.md` | | `docs/product/requirements.md` | | `docs/product/roadmap.md` | -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist @@ -158,7 +156,7 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#concise` — runtime response-style mode (`normal|compact|ultra|status`) - `@#vision` — vision document writing and review - `@#requirements` — requirements gathering and writing -- `@#docs` — keep product artifacts and release-facing documentation aligned +- `@#docs` — keep product items and release-facing documentation aligned - `@#explore` — codebase discovery and mapping (brownfield intake) - `@#analyse` — impact analysis, tradeoffs, feasibility - `@#adr` — architecture decision record writing (if significant decisions) @@ -166,4 +164,4 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#gh-issues` — create and manage GitHub Issues for requirements, tasks, and user stories - + diff --git a/.github/agents/release.agent.md b/.github/agents/release.agent.md index add10cb..da8e602 100644 --- a/.github/agents/release.agent.md +++ b/.github/agents/release.agent.md @@ -1,9 +1,9 @@ --- description: >- - Senior platform and release engineer. Acts as release gatekeeper: verifies baseline artifacts are + Senior platform and release engineer. Acts as release gatekeeper: verifies baseline items are complete across all roles, collects explicit cross-role sign-off reviews, then produces a dated - release document and creates the PR. Ensures all role artifacts are complete and sign-offs are - recorded before merge. + release document and creates the PR. Ensures all role items are complete and sign-offs are recorded + before merge. name: release argument-hint: '[release readiness | compile release notes | collect sign-offs | open release PR]' tools: @@ -43,7 +43,7 @@ You are a **senior platform and release engineer** acting as the **release role* ## limitations and do not do -- Do not proceed if required artifacts are missing or stale. +- Do not proceed if required items are missing or stale. - Do not override NOK sign-offs. - Do not perform ad-hoc production changes in place of the release process. @@ -66,7 +66,7 @@ You are a **senior platform and release engineer** acting as the **release role* - Gate-oriented and explicit about pass/fail state. - Default concise mode: `compact`. -- Record sign-off rationale in release artifacts. +- Record sign-off rationale in release items. - Provide concise blocker summaries with owners. ## agent-skill boundary @@ -79,17 +79,22 @@ You are a **senior platform and release engineer** acting as the **release role* Signal readiness at each release gate: -1. **Ready for sign-off collection** — required artifacts are present and current. +1. **Ready for sign-off collection** — required items are present and current. 1. **Ready for PR creation** — required sign-off perspectives return explicit OK. Release does not expose cross-role handoff buttons for escalation paths. -For non-happy paths (`NOK`, blockers, missing artifacts), report blocker details +For non-happy paths (`NOK`, blockers, missing items), report blocker details and wait for explicit user routing decisions. +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute release-stage scope only. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. + ## how you work -1. Baseline artifacts to check: the requirements doc, architecture overview, design overview, test report, security report, and changelog. Use your input artifacts (see `## artifacts you use`) to locate them. -1. Validate required-for-scope artifacts: require the performance baseline only when performance validation is in scope; require observability evidence in the test report (or a dedicated observability report if your process uses one). +1. Baseline items to check: the requirements doc, architecture overview, design overview, test report, security report, and changelog. Use your input items (see `## work items`) to locate them. +1. Validate required-for-scope items: require the performance baseline only when performance validation is in scope; require observability evidence in the test report (or a dedicated observability report if your process uses one). 1. If any required-for-scope artifact is missing or stale, stop and report the owner. 1. Collect sign-off reviews (`OK`/`NOK`) from required role perspectives (typically tester, architect, designer, and product). 1. Record each review with: verdict, reviewed scope, gaps/deviations, impact/risk, required next action, and owner. @@ -99,40 +104,40 @@ and wait for explicit user routing decisions. ## success criteria -- Output artifacts are produced, accurate, and up to date (see output artifacts). -- Required-for-scope artifacts are present and current before sign-off. +- Output items are produced, accurate, and up to date (see output items). +- Required-for-scope items are present and current before sign-off. - Required sign-off reviews are explicit and recorded with verdict and rationale. - Release notes and changelog accurately reflect shipped scope. ## failure and escalation rules -- Missing required-for-scope artifacts: block and report owner. +- Missing required-for-scope items: block and report owner. - Any NOK sign-off: stop and hand back with rationale. - Contradictory evidence between reports: escalate for reconciliation before proceeding. -## artifacts you use +## work items ### input -| Artifact | +| Item | | -------------- | | `docs/**/*.md` | ### output -| Artifact | Notes | +| Item | Notes | | -------------------- | ------------------------------------------ | | `docs/releases/*.md` | includes release notes and sign-off record | -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist - Required evidence and sign-offs are explicitly recorded. -- Release artifacts are current and traceable. +- Release items are current and traceable. - PR handoff includes final scope summary and residual risks. ## skills you use @@ -149,4 +154,4 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#gh-issues` — create and manage GitHub Issues for tracking work and bug reports - + diff --git a/.github/agents/tester.agent.md b/.github/agents/tester.agent.md index baae432..6f01d20 100644 --- a/.github/agents/tester.agent.md +++ b/.github/agents/tester.agent.md @@ -21,12 +21,6 @@ model: - GPT-5.3-Codex (copilot) user-invocable: true target: vscode -handoffs: - - label: 'Go to next stage: Release' - agent: release - prompt: >- - Verification outputs are approved. Assess the current state and prepare the release as needed. - Create and/or update the relevant artifacts if needed, as well as any sign-offs. --- # tester @@ -38,7 +32,7 @@ You are a **senior QA, security, and reliability engineer** acting as the **test - Own verification evidence and release-readiness findings. - Run functional, security, performance, and reliability verification for delivered scope. -- Produce output reports (see output artifacts); include the performance baseline when performance validation is in scope. +- Produce output reports (see output items); include the performance baseline when performance validation is in scope. - Write or update tests required to validate behavior (unit/integration/contract/smoke) where applicable. ## scope and boundaries @@ -91,15 +85,20 @@ Signal readiness before release proceeds: Handoffs you own: - Happy path only: one forward continuation to release readiness after user approval. -- For non-happy paths (`NOK`, blockers, missing artifacts), do not use handoff buttons; provide blocker details and let the user choose the recovery path. +- For non-happy paths (`NOK`, blockers, missing items), do not use handoff buttons; provide blocker details and let the user choose the recovery path. + +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute tester-stage scope only; do not invoke downstream roles unless explicitly asked. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. ## assess current state -Before running any checks, scan your configured input artifacts to determine +Before running any checks, scan your configured input items to determine what work is needed: -1. Read your input artifacts. -1. Identify artifacts that require action: +1. Read your input items. +1. Identify items that require action: - Implementation changes since the last test report. - New components or contracts not yet covered in the test report. - Security or performance findings that are unresolved. @@ -114,7 +113,7 @@ what work is needed: 1. Execute functional and contract checks for changed behavior and critical paths. 1. Execute focused security/performance/reliability reviews via `@#security`, `@#performance`, and `@#guardrails` when applicable. 1. Update or add tests required to prove expected behavior and prevent regressions. -1. Write your baseline reports (see output artifacts); include the performance baseline when performance validation is in scope. Include observability evidence in the test report unless a dedicated observability report is used. +1. Write your baseline reports (see output items); include the performance baseline when performance validation is in scope. Include observability evidence in the test report unless a dedicated observability report is used. 1. Publish verdict and hand off blockers or release-readiness status. ## success criteria @@ -127,28 +126,28 @@ what work is needed: - Cannot execute required checks: escalate with explicit gap and risk. - Security-critical issue found: escalate immediately and block release. -- Missing or stale required-for-scope artifacts: stop and report owners. +- Missing or stale required-for-scope items: stop and report owners. -## artifacts you use +## work items ### input -| Artifact | +| Item | | --------------------------- | | `docs/architecture/**/*.md` | | `docs/design/**/*.md` | ### output -| Artifact | +| Item | | ---------------------- | | `docs/reports/**/*.md` | | `tests/**/*` | -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist @@ -181,4 +180,4 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#rancher` — Rancher/Fleet configuration and multi-cluster governance review - + diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 7392bcb..95759d1 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,7 +4,7 @@ You are assisting in the development of **vstack**, a VS Code–native AI engine ## Identity -vstack provides structured skills for backend/microservice development, executable via GitHub Copilot Agent Mode. It exposes 6 fixed roles — product, architect, designer, engineer, tester, release — each backed by hand-authored agent files that invoke skill templates. +vstack provides structured skills for backend/microservice development, executable via GitHub Copilot Agent Mode. It exposes six delivery roles — product, architect, designer, engineer, tester, release — plus a planner coordinator agent, each backed by hand-authored agent files that invoke skill templates. ## Core Principles diff --git a/.github/instructions/git.instructions.md b/.github/instructions/git.instructions.md index ee0ecde..ea3b781 100644 --- a/.github/instructions/git.instructions.md +++ b/.github/instructions/git.instructions.md @@ -41,4 +41,4 @@ Use these Git and release hygiene conventions in this project. 1. Prefer local verification before pushing release-impacting changes. - + diff --git a/.github/instructions/helm.instructions.md b/.github/instructions/helm.instructions.md index 1b37194..a323785 100644 --- a/.github/instructions/helm.instructions.md +++ b/.github/instructions/helm.instructions.md @@ -45,4 +45,4 @@ Use these Helm conventions in this project. - [Helm chart best practices](https://helm.sh/docs/chart_best_practices/) - + diff --git a/.github/instructions/java.instructions.md b/.github/instructions/java.instructions.md index 98a498a..43b2ae4 100644 --- a/.github/instructions/java.instructions.md +++ b/.github/instructions/java.instructions.md @@ -56,4 +56,4 @@ Use these Java conventions in this project. 1. Do not suppress static analysis warnings without a documented, task-specific reason. - + diff --git a/.github/instructions/k8s.instructions.md b/.github/instructions/k8s.instructions.md index 5bcc17e..45925cb 100644 --- a/.github/instructions/k8s.instructions.md +++ b/.github/instructions/k8s.instructions.md @@ -51,4 +51,4 @@ Use these Kubernetes conventions in this project. - [Kubernetes API reference](https://kubernetes.io/docs/reference/kubernetes-api/) - + diff --git a/.github/instructions/markdown.instructions.md b/.github/instructions/markdown.instructions.md index 64b29d5..4d9ac9a 100644 --- a/.github/instructions/markdown.instructions.md +++ b/.github/instructions/markdown.instructions.md @@ -51,4 +51,4 @@ Use these Markdown conventions in this project. 1. Keep examples accurate and runnable — a broken example is worse than no example. - + diff --git a/.github/instructions/python.instructions.md b/.github/instructions/python.instructions.md index a9a809c..87e668c 100644 --- a/.github/instructions/python.instructions.md +++ b/.github/instructions/python.instructions.md @@ -42,4 +42,4 @@ Use these Python conventions in this project. 1. Do not silence lint/type errors unless there is a documented, task-specific reason. - + diff --git a/.github/instructions/rancher.instructions.md b/.github/instructions/rancher.instructions.md index 3f52dd8..cc5663c 100644 --- a/.github/instructions/rancher.instructions.md +++ b/.github/instructions/rancher.instructions.md @@ -44,4 +44,4 @@ Use these Rancher conventions in this project. - [Fleet docs](https://fleet.rancher.io/) - + diff --git a/.github/instructions/security.instructions.md b/.github/instructions/security.instructions.md index dd02e2c..cc4695b 100644 --- a/.github/instructions/security.instructions.md +++ b/.github/instructions/security.instructions.md @@ -42,4 +42,4 @@ Apply these security policies in this project. 1. Isolate privileged logic; keep it minimal, auditable, and separate from business logic. - + diff --git a/.github/instructions/terraform.instructions.md b/.github/instructions/terraform.instructions.md index da8fe19..5a98069 100644 --- a/.github/instructions/terraform.instructions.md +++ b/.github/instructions/terraform.instructions.md @@ -60,4 +60,4 @@ Use these Terraform conventions in this project. - [tfsec](https://aquasecurity.github.io/tfsec/) · [checkov](https://www.checkov.io/) - + diff --git a/.github/instructions/terragrunt.instructions.md b/.github/instructions/terragrunt.instructions.md index 9770cb6..7f67a3a 100644 --- a/.github/instructions/terragrunt.instructions.md +++ b/.github/instructions/terragrunt.instructions.md @@ -57,4 +57,4 @@ Use these Terragrunt conventions in this project. - [Terragrunt CLI reference](https://terragrunt.gruntwork.io/docs/reference/cli-options/) - + diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index e673737..bae8c88 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -43,4 +43,4 @@ Use these testing conventions in this project. 1. Treat flaky tests as bugs; do not merge code with known test reliability issues. - + diff --git a/.github/instructions/typescript.instructions.md b/.github/instructions/typescript.instructions.md index adb935d..a718e94 100644 --- a/.github/instructions/typescript.instructions.md +++ b/.github/instructions/typescript.instructions.md @@ -49,4 +49,4 @@ Use these TypeScript conventions in this project. 1. Do not suppress lint or type errors with inline disable comments unless there is a documented, task-specific reason. - + diff --git a/.github/prompts/api-design-review.prompt.md b/.github/prompts/api-design-review.prompt.md index 70e505c..321dc53 100644 --- a/.github/prompts/api-design-review.prompt.md +++ b/.github/prompts/api-design-review.prompt.md @@ -55,4 +55,4 @@ List fields or objects that are missing required constraints, descriptions, or e - top priority fix in one sentence - + diff --git a/.github/prompts/architecture-risk.prompt.md b/.github/prompts/architecture-risk.prompt.md index 9d9f919..6e8c226 100644 --- a/.github/prompts/architecture-risk.prompt.md +++ b/.github/prompts/architecture-risk.prompt.md @@ -52,4 +52,4 @@ List security-specific risks not covered above: auth boundaries, sensitive data - one-sentence rationale - + diff --git a/.github/prompts/code-review.prompt.md b/.github/prompts/code-review.prompt.md index 4f6287e..248c682 100644 --- a/.github/prompts/code-review.prompt.md +++ b/.github/prompts/code-review.prompt.md @@ -51,4 +51,4 @@ End with: - Biggest remaining risk: one sentence - + diff --git a/.github/prompts/dependency-audit.prompt.md b/.github/prompts/dependency-audit.prompt.md index d8744b5..a059ec3 100644 --- a/.github/prompts/dependency-audit.prompt.md +++ b/.github/prompts/dependency-audit.prompt.md @@ -60,4 +60,4 @@ List packages with unusual provenance concerns: abandoned maintainers, single-ma Ordered list of actions by priority (critical first). - + diff --git a/.github/prompts/incident-timeline.prompt.md b/.github/prompts/incident-timeline.prompt.md index ebe3980..dc955ac 100644 --- a/.github/prompts/incident-timeline.prompt.md +++ b/.github/prompts/incident-timeline.prompt.md @@ -57,4 +57,4 @@ For each action: List the minimum controls needed to reduce repeat probability. - + diff --git a/.github/prompts/migration-safety.prompt.md b/.github/prompts/migration-safety.prompt.md index e602ce8..f57b5e4 100644 --- a/.github/prompts/migration-safety.prompt.md +++ b/.github/prompts/migration-safety.prompt.md @@ -52,4 +52,4 @@ List missing migration tests (forward, backward, data invariants, load-sensitive - biggest remaining risk in one sentence - + diff --git a/.github/prompts/release-readiness.prompt.md b/.github/prompts/release-readiness.prompt.md index 8552f4f..74efd78 100644 --- a/.github/prompts/release-readiness.prompt.md +++ b/.github/prompts/release-readiness.prompt.md @@ -46,4 +46,4 @@ For each expected artifact that is missing, flag it explicitly as: MISSING — [ One clear next step for the team. - + diff --git a/.github/skills/adr/SKILL.md b/.github/skills/adr/SKILL.md index 7bd56f5..93a9a76 100644 --- a/.github/skills/adr/SKILL.md +++ b/.github/skills/adr/SKILL.md @@ -180,4 +180,4 @@ is a kebab-case title. After writing, state the file path and summary so the architect or product role can review. - + diff --git a/.github/skills/analyse/SKILL.md b/.github/skills/analyse/SKILL.md index 3856249..4efda59 100644 --- a/.github/skills/analyse/SKILL.md +++ b/.github/skills/analyse/SKILL.md @@ -214,4 +214,4 @@ State conclusions with confidence level: ``` - + diff --git a/.github/skills/architecture/SKILL.md b/.github/skills/architecture/SKILL.md index e55a8d3..c435ee2 100644 --- a/.github/skills/architecture/SKILL.md +++ b/.github/skills/architecture/SKILL.md @@ -278,4 +278,4 @@ For each significant structural decision made during this review (technology cho - Update `docs/architecture/overview.md` to reflect the final decisions. - + diff --git a/.github/skills/aws-cli/SKILL.md b/.github/skills/aws-cli/SKILL.md index c28e4af..7211362 100644 --- a/.github/skills/aws-cli/SKILL.md +++ b/.github/skills/aws-cli/SKILL.md @@ -374,4 +374,4 @@ aws ce get-cost-and-usage \ - [AWS CLI named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) - + diff --git a/.github/skills/cicd/SKILL.md b/.github/skills/cicd/SKILL.md index 421c19a..4a61e8a 100644 --- a/.github/skills/cicd/SKILL.md +++ b/.github/skills/cicd/SKILL.md @@ -221,4 +221,4 @@ Configure these in GitHub → Settings → Branches. - [GitHub-hosted runners](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners) - + diff --git a/.github/skills/cloudformation/SKILL.md b/.github/skills/cloudformation/SKILL.md index 2ad5f01..6907f44 100644 --- a/.github/skills/cloudformation/SKILL.md +++ b/.github/skills/cloudformation/SKILL.md @@ -344,4 +344,4 @@ AppSecurityGroup: - [AWS SAM documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/) - + diff --git a/.github/skills/code-review/SKILL.md b/.github/skills/code-review/SKILL.md index 4d3a47b..7a7b5e4 100644 --- a/.github/skills/code-review/SKILL.md +++ b/.github/skills/code-review/SKILL.md @@ -218,4 +218,4 @@ Confidence: [HIGH/MEDIUM/LOW — explain if not HIGH] ``` - + diff --git a/.github/skills/codeql/SKILL.md b/.github/skills/codeql/SKILL.md index 855da54..49ecf8f 100644 --- a/.github/skills/codeql/SKILL.md +++ b/.github/skills/codeql/SKILL.md @@ -251,4 +251,4 @@ GITHUB_TOKEN= codeql github upload-results \ - [Supported languages and frameworks](https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/codeql-code-scanning-for-compiled-languages) - + diff --git a/.github/skills/concise/SKILL.md b/.github/skills/concise/SKILL.md index dac193d..dd46d43 100644 --- a/.github/skills/concise/SKILL.md +++ b/.github/skills/concise/SKILL.md @@ -147,4 +147,4 @@ Current mode unchanged: - [ ] User confirmation/status returned in deterministic format - + diff --git a/.github/skills/consult/SKILL.md b/.github/skills/consult/SKILL.md index 2571f4e..bf7ba0e 100644 --- a/.github/skills/consult/SKILL.md +++ b/.github/skills/consult/SKILL.md @@ -218,4 +218,4 @@ reason: [one sentence] ``` - + diff --git a/.github/skills/container/SKILL.md b/.github/skills/container/SKILL.md index 2d9c924..34adbf0 100644 --- a/.github/skills/container/SKILL.md +++ b/.github/skills/container/SKILL.md @@ -153,4 +153,4 @@ For production-like local testing, write a separate `docker-compose.prod.yml` wi - [Docker official images](https://hub.docker.com/search?image_filter=official) - + diff --git a/.github/skills/conventional-commit/SKILL.md b/.github/skills/conventional-commit/SKILL.md index 77a4b00..d8c07d4 100644 --- a/.github/skills/conventional-commit/SKILL.md +++ b/.github/skills/conventional-commit/SKILL.md @@ -155,4 +155,4 @@ Remaining changes: If commit is blocked, report exact reason and proposed fix. - + diff --git a/.github/skills/debug/SKILL.md b/.github/skills/debug/SKILL.md index 76ec3b7..4740db2 100644 --- a/.github/skills/debug/SKILL.md +++ b/.github/skills/debug/SKILL.md @@ -258,4 +258,4 @@ Prevention: [any follow-up items] ``` - + diff --git a/.github/skills/dependabot/SKILL.md b/.github/skills/dependabot/SKILL.md index ad9aeff..945a621 100644 --- a/.github/skills/dependabot/SKILL.md +++ b/.github/skills/dependabot/SKILL.md @@ -320,4 +320,4 @@ updates: - [Dependabot security updates](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates) - + diff --git a/.github/skills/dependency/SKILL.md b/.github/skills/dependency/SKILL.md index 3313f88..ae4fd2b 100644 --- a/.github/skills/dependency/SKILL.md +++ b/.github/skills/dependency/SKILL.md @@ -318,4 +318,4 @@ Action items (priority order): - [PyPI / npm / crates.io / Maven Central](https://pypi.org) (replace with the relevant registry) - + diff --git a/.github/skills/design/SKILL.md b/.github/skills/design/SKILL.md index e732ac8..c9ff1d5 100644 --- a/.github/skills/design/SKILL.md +++ b/.github/skills/design/SKILL.md @@ -243,4 +243,4 @@ Output a complete design document to `docs/design/overview.md` or `openapi.yaml` ``` - + diff --git a/.github/skills/docs/SKILL.md b/.github/skills/docs/SKILL.md index be97300..cfdefcc 100644 --- a/.github/skills/docs/SKILL.md +++ b/.github/skills/docs/SKILL.md @@ -151,4 +151,4 @@ Skipped (n/a): ``` - + diff --git a/.github/skills/explore/SKILL.md b/.github/skills/explore/SKILL.md index aa6af6a..4855992 100644 --- a/.github/skills/explore/SKILL.md +++ b/.github/skills/explore/SKILL.md @@ -224,4 +224,4 @@ Stack: [language, framework, runtime versions] ``` - + diff --git a/.github/skills/gdpr/SKILL.md b/.github/skills/gdpr/SKILL.md index b6ff4fd..7ecb298 100644 --- a/.github/skills/gdpr/SKILL.md +++ b/.github/skills/gdpr/SKILL.md @@ -246,4 +246,4 @@ Use `@example.com` for all test email addresses. - [EDPB guidelines](https://www.edpb.europa.eu/our-work-tools/general-guidance/guidelines-recommendations-best-practices_en) - + diff --git a/.github/skills/gh-issues/SKILL.md b/.github/skills/gh-issues/SKILL.md index 3b5a19a..57cc1de 100644 --- a/.github/skills/gh-issues/SKILL.md +++ b/.github/skills/gh-issues/SKILL.md @@ -230,4 +230,4 @@ https://github.com///issues/ - [GitHub Issues documentation](https://docs.github.com/en/issues) - + diff --git a/.github/skills/gh-release/SKILL.md b/.github/skills/gh-release/SKILL.md index e3b1c68..29b9d4d 100644 --- a/.github/skills/gh-release/SKILL.md +++ b/.github/skills/gh-release/SKILL.md @@ -214,4 +214,4 @@ If blocked, report exact blocker and required user action. - [GitHub Releases documentation](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases) - + diff --git a/.github/skills/guardrails/SKILL.md b/.github/skills/guardrails/SKILL.md index 820fe2e..c257719 100644 --- a/.github/skills/guardrails/SKILL.md +++ b/.github/skills/guardrails/SKILL.md @@ -72,4 +72,4 @@ Activate careful mode for this session. Two behaviors are now enabled. Explicitly ask to "disable guardrails". - + diff --git a/.github/skills/helm/SKILL.md b/.github/skills/helm/SKILL.md index 1746edd..804e65b 100644 --- a/.github/skills/helm/SKILL.md +++ b/.github/skills/helm/SKILL.md @@ -140,4 +140,4 @@ Practices: - [Chart best practices](https://helm.sh/docs/chart_best_practices/) - + diff --git a/.github/skills/incident/SKILL.md b/.github/skills/incident/SKILL.md index 784e4ac..fc24744 100644 --- a/.github/skills/incident/SKILL.md +++ b/.github/skills/incident/SKILL.md @@ -251,4 +251,4 @@ Next: invoke @#rca and @#postmortem to produce written artifacts. ``` - + diff --git a/.github/skills/inspect/SKILL.md b/.github/skills/inspect/SKILL.md index 9a2346a..f7a4bfb 100644 --- a/.github/skills/inspect/SKILL.md +++ b/.github/skills/inspect/SKILL.md @@ -155,4 +155,4 @@ Confirm for changed paths: ``` - + diff --git a/.github/skills/k8s/SKILL.md b/.github/skills/k8s/SKILL.md index 42ca2a6..eb83bce 100644 --- a/.github/skills/k8s/SKILL.md +++ b/.github/skills/k8s/SKILL.md @@ -144,4 +144,4 @@ Common failure classes: - [Kubernetes API reference](https://kubernetes.io/docs/reference/kubernetes-api/) - + diff --git a/.github/skills/migrate/SKILL.md b/.github/skills/migrate/SKILL.md index 07e1934..b9f18ac 100644 --- a/.github/skills/migrate/SKILL.md +++ b/.github/skills/migrate/SKILL.md @@ -320,4 +320,4 @@ Pre-deploy checklist: ``` - + diff --git a/.github/skills/onboard/SKILL.md b/.github/skills/onboard/SKILL.md index da290f0..9101cb3 100644 --- a/.github/skills/onboard/SKILL.md +++ b/.github/skills/onboard/SKILL.md @@ -302,4 +302,4 @@ Gaps remaining (if any): ``` - + diff --git a/.github/skills/openapi/SKILL.md b/.github/skills/openapi/SKILL.md index 77a8b91..b0107bc 100644 --- a/.github/skills/openapi/SKILL.md +++ b/.github/skills/openapi/SKILL.md @@ -403,4 +403,4 @@ Summary: [N critical, N warnings, N info] - [Redocly CLI (linting)](https://redocly.com/docs/cli/) - + diff --git a/.github/skills/performance/SKILL.md b/.github/skills/performance/SKILL.md index 4bd99dc..2404a01 100644 --- a/.github/skills/performance/SKILL.md +++ b/.github/skills/performance/SKILL.md @@ -242,4 +242,4 @@ For each bottleneck identified: ``` - + diff --git a/.github/skills/postmortem/SKILL.md b/.github/skills/postmortem/SKILL.md index 12508ec..4f47ea6 100644 --- a/.github/skills/postmortem/SKILL.md +++ b/.github/skills/postmortem/SKILL.md @@ -184,4 +184,4 @@ Status: Draft — ready for team review ``` - + diff --git a/.github/skills/pr/SKILL.md b/.github/skills/pr/SKILL.md index bd8368a..24f179f 100644 --- a/.github/skills/pr/SKILL.md +++ b/.github/skills/pr/SKILL.md @@ -151,4 +151,4 @@ Next steps depend on the repository CI/CD configuration: ``` - + diff --git a/.github/skills/rancher/SKILL.md b/.github/skills/rancher/SKILL.md index 3d58218..01a5624 100644 --- a/.github/skills/rancher/SKILL.md +++ b/.github/skills/rancher/SKILL.md @@ -113,4 +113,4 @@ Checks: - [Fleet documentation](https://fleet.rancher.io/) - + diff --git a/.github/skills/rca/SKILL.md b/.github/skills/rca/SKILL.md index 0a302a5..5cac050 100644 --- a/.github/skills/rca/SKILL.md +++ b/.github/skills/rca/SKILL.md @@ -207,4 +207,4 @@ Status: Draft — ready for review ``` - + diff --git a/.github/skills/refactor/SKILL.md b/.github/skills/refactor/SKILL.md index 1110f6b..c85f4e8 100644 --- a/.github/skills/refactor/SKILL.md +++ b/.github/skills/refactor/SKILL.md @@ -372,4 +372,4 @@ Behavior changed: No ``` - + diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index 39d4c98..59a1fd8 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -147,4 +147,4 @@ Prepend a new entry at the top of `CHANGELOG.md`: Keep existing entries intact. - + diff --git a/.github/skills/requirements/SKILL.md b/.github/skills/requirements/SKILL.md index 5d1e7d7..979ab3e 100644 --- a/.github/skills/requirements/SKILL.md +++ b/.github/skills/requirements/SKILL.md @@ -199,4 +199,4 @@ Write all findings to `docs/product/requirements.md`: After writing, summarize what was decided so the architect role can start. - + diff --git a/.github/skills/secret-scan/SKILL.md b/.github/skills/secret-scan/SKILL.md index 0c0674b..664bb22 100644 --- a/.github/skills/secret-scan/SKILL.md +++ b/.github/skills/secret-scan/SKILL.md @@ -240,4 +240,4 @@ credential formats. - [Supported secret patterns](https://docs.github.com/en/code-security/secret-scanning/introduction/supported-secret-scanning-patterns) - + diff --git a/.github/skills/security/SKILL.md b/.github/skills/security/SKILL.md index eb35c48..c6c0f90 100644 --- a/.github/skills/security/SKILL.md +++ b/.github/skills/security/SKILL.md @@ -295,4 +295,4 @@ Scope: [full/diff/dependency/config] - [STRIDE threat modeling (Microsoft)](https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats) - + diff --git a/.github/skills/terraform/SKILL.md b/.github/skills/terraform/SKILL.md index 3ba5145..8fbf89a 100644 --- a/.github/skills/terraform/SKILL.md +++ b/.github/skills/terraform/SKILL.md @@ -335,4 +335,4 @@ Run `terraform plan` after every state operation to verify the outcome. - [tfsec rules](https://aquasecurity.github.io/tfsec/latest/checks/aws/) · [checkov checks](https://www.checkov.io/5.Policy%20Index/terraform.html) - + diff --git a/.github/skills/terragrunt/SKILL.md b/.github/skills/terragrunt/SKILL.md index 77326de..8b499b9 100644 --- a/.github/skills/terragrunt/SKILL.md +++ b/.github/skills/terragrunt/SKILL.md @@ -307,4 +307,4 @@ Use `--terragrunt-non-interactive` in CI to prevent hanging on prompts. - [Gruntwork module registry](https://www.gruntwork.io/) - + diff --git a/.github/skills/threat-model/SKILL.md b/.github/skills/threat-model/SKILL.md index d2a81e5..a21b337 100644 --- a/.github/skills/threat-model/SKILL.md +++ b/.github/skills/threat-model/SKILL.md @@ -245,4 +245,4 @@ For each high-priority threat include: - Final report is written to `docs/architecture/threat-model.md`. - + diff --git a/.github/skills/verify/SKILL.md b/.github/skills/verify/SKILL.md index 585a7df..a9cfb65 100644 --- a/.github/skills/verify/SKILL.md +++ b/.github/skills/verify/SKILL.md @@ -266,4 +266,4 @@ scope: [path/component/full] ``` - + diff --git a/.github/skills/vision/SKILL.md b/.github/skills/vision/SKILL.md index 16dbb1b..33260a9 100644 --- a/.github/skills/vision/SKILL.md +++ b/.github/skills/vision/SKILL.md @@ -206,4 +206,4 @@ For each finding: explain the tradeoff, give an opinionated recommendation, ask Present as: "Overall assessment: [READY/NEEDS REVISION/SCOPE CHANGE] because [1-2 sentence reason]." - + diff --git a/.vstack/config.yaml b/.vstack/config.yaml index f6a2fb5..521566f 100644 --- a/.vstack/config.yaml +++ b/.vstack/config.yaml @@ -1,3 +1,4 @@ +# ----------------------------------------------------------------------------- # vstack project configuration # # vstack install — first-run setup. Seeds this file if missing (never overwrites). @@ -9,9 +10,14 @@ # # This file is committed to git. It expresses stable project preferences — # editing it is a deliberate choice that affects all future vstack init runs. +# ----------------------------------------------------------------------------- -# Selective install exclusions — remove or comment out to install everything. +# ----------------------------------------------------------------------------- +# SELECTIVE INSTALL EXCLUSIONS # +# Remove or comment out to install everything. +# ----------------------------------------------------------------------------- + # exclude: # skills: # - terraform @@ -21,13 +27,47 @@ # instructions: all # prompts: all -# Root directory for generated agent artifact paths. +# ----------------------------------------------------------------------------- +# ROOT DIRECTORY FOR GENERATED WORK ITEMS +# # Default: docs +# ----------------------------------------------------------------------------- + +# items: +# root: docs # +# Legacy fallback (still supported): # artifacts: # root: docs -# Pipeline workflow — seeded by vstack install, owned by this project. +# ----------------------------------------------------------------------------- +# PIPELINE WORKFLOW +# +# Seeded by vstack install, owned by this project. +# +# mode: agentic — default; planner orchestrates stage progression; worker handoff buttons are omitted +# mode: manual — keep worker handoff buttons; planner agent is not generated +# mode: hybrid — support both (planner optional + worker handoffs) +# +# Execution order: +# - Stage order in workflow.stages is the canonical progression order. +# - agentic is stage-sequential by default (planner advances per stage in order). +# - Parallel work can still happen inside a stage (for independent subtasks), +# but cross-stage progression remains ordered by workflow.stages. +# +# Handoffs: +# - handoffs.prompt is the message used when transitioning to the next stage. +# - If handoffs.agent is omitted, target defaults to the next role in workflow.stages. +# - By default, leave handoffs unset and let each agent's defaults drive prompts. +# - Add overrides only when your team needs custom wording. +# Example: +# handoffs: +# prompt: "" +# - Keep handoffs.agent unset in normal use so workflow order stays canonical. +# - Explicit handoffs.agent overrides are advanced/exceptional and usually unnecessary. +# - In agentic mode, worker handoff buttons are hidden; planner drives progression. +# - handoffs.prompt values remain in this file in agentic mode for compatibility +# when switching to manual or hybrid. # # gate: required — stage always runs # gate: optional — stage may be skipped when its domain is unaffected @@ -37,7 +77,27 @@ # hitl: on-change — pipeline pauses only when the stage made changes (default for optional) # hitl: never — pipeline continues without human approval (explicit opt-out) # +# Notes: +# - Lines starting with # are documentation/examples and are not active config. +# - Active configuration starts below at workflow:. +# - After edits, run: vstack init +# +# Minimal active config example: +# workflow: +# mode: agentic +# version: 1 +# stages: +# - role: product +# gate: required +# hitl: always +# - role: release +# gate: required +# hitl: always +# +# ----------------------------------------------------------------------------- + workflow: + mode: agentic version: 1 stages: - role: product @@ -66,7 +126,8 @@ workflow: Design outputs are approved. Assess the current state and implement code and tests as needed. If your domain is not affected by this change, assess and confirm that explicitly, then pass through to - engineering. + engineering. If working on an issue, document findings in RCA or + post-mortem items as relevant. - role: engineer gate: required hitl: always @@ -81,7 +142,7 @@ workflow: handoffs: prompt: > Verification outputs are approved. Assess the current state and prepare - the release as needed. Create and/or update the relevant artifacts if + the release as needed. Create and/or update the relevant items if needed, as well as any sign-offs. - role: release gate: required diff --git a/.vstack/vstack.json b/.vstack/vstack.json index 85f21fa..4cbf93d 100644 --- a/.vstack/vstack.json +++ b/.vstack/vstack.json @@ -1,316 +1,316 @@ { "manifest_version": 2, "hash_algorithm": "sha256", - "vstack_version": "0.0.0.post3.dev0+df3fe6e", - "installed_at": "2026-05-09T22:36:50.257153+00:00", + "vstack_version": "3.1.1", + "installed_at": "2026-05-10T12:08:00.906817+00:00", "artifacts": { "skills": [ { "name": "adr", "file": "skills/adr/SKILL.md", "version": "20260421003", - "checksum": "871263fddee0b3ae8e2c2e2243aad4f2b3aa9e0dea0d2519c7d21c3e98430a83", + "checksum": "3e039f697174956e5f51a23783e2ee71192d79ec0bca6c94a02f727f7d207277", "checksum_algorithm": "sha256" }, { "name": "analyse", "file": "skills/analyse/SKILL.md", "version": "20260421004", - "checksum": "79ced25692671a5286a26cbcd0e59ed4d641ac7493fd8e2b58b232f97e467224", + "checksum": "f95d3e3d1bc243635bebf531f7feae04348da6f8a4c0985ad2ae84f2aaafa298", "checksum_algorithm": "sha256" }, { "name": "architecture", "file": "skills/architecture/SKILL.md", "version": "20260421005", - "checksum": "e2d0342184829c888a70ece6f82ad6a7450ad5d8c958ff6e028d7f245f1e5960", + "checksum": "b1c78e0c74be480a938ba0fc08791d032b9bfc2682ceca923b0a5a6664609f4c", "checksum_algorithm": "sha256" }, { "name": "aws-cli", "file": "skills/aws-cli/SKILL.md", "version": "20260502033", - "checksum": "4868a5adc17b6ac52839c85f3a09986991c2498501913ef48a8f85a6291e1b21", + "checksum": "a75a1931fa01f416a80964d8119e1a099ac4eefb0f4969179a034e39c3b028eb", "checksum_algorithm": "sha256" }, { "name": "cicd", "file": "skills/cicd/SKILL.md", "version": "20260421006", - "checksum": "ba6c0cf0e644e24b1290c1b78969bad86dd7d5b39030edc7bd430d4d1832cfd7", + "checksum": "ac3911f5b9997ad3875c67e36c1dd23096ed70cd66f3130299a657c6d234c59d", "checksum_algorithm": "sha256" }, { "name": "cloudformation", "file": "skills/cloudformation/SKILL.md", "version": "20260502032", - "checksum": "0e61163cfc356105a309bbecf89ee26a5b10b9b5db58c331d76efa4a508e760e", + "checksum": "ff0359c101814cb47cbce1fa41a80c001a59d8bbdb8eb16a645e5122976b806b", "checksum_algorithm": "sha256" }, { "name": "code-review", "file": "skills/code-review/SKILL.md", "version": "20260421007", - "checksum": "58b7caa9dd3a5da6bef8d8d4d1f7b6d513740d33ad16731a06b1b2a907d93a13", + "checksum": "c951124bd93bf7f3138e9dce304fb730b675f655bd9051f8a6bc5969ea4aca9c", "checksum_algorithm": "sha256" }, { "name": "codeql", "file": "skills/codeql/SKILL.md", "version": "20260502026", - "checksum": "95f29c975e5850307620c21a68df174b90ecaafd5b1e36659bb3c34d0027b5df", + "checksum": "d3dfc82e43d3e4645bf180699ee2666a48aa26cf115add49b0258fbcbfe184c9", "checksum_algorithm": "sha256" }, { "name": "concise", "file": "skills/concise/SKILL.md", "version": "20260421008", - "checksum": "9555de7da6a925e76992b6f5e51ddccd6b4de3887ea31ed2ceb9113c9fe9bc7e", + "checksum": "aca2ef118be257adc352ba0641f457faf6973ae033e3cb22960a23a7a8ee3261", "checksum_algorithm": "sha256" }, { "name": "consult", "file": "skills/consult/SKILL.md", "version": "20260421009", - "checksum": "a340883900309261213ec0ea0d4e9e5b72ca83fb25c7cd6c3506e1f95f40cf76", + "checksum": "443616170149445f17e1b445a695b5b9b6a7bae25c2989c2dd9030a7b64f9771", "checksum_algorithm": "sha256" }, { "name": "container", "file": "skills/container/SKILL.md", "version": "20260421010", - "checksum": "f0a4408a756195faca9a58dca76e56f950df227415d77af293bf55280191abd4", + "checksum": "47e0518a4ce5ac36309aacb59d6d81f16f62cefbf3ad2868ec9ba1b291b82468", "checksum_algorithm": "sha256" }, { "name": "conventional-commit", "file": "skills/conventional-commit/SKILL.md", "version": "20260502024", - "checksum": "8f1ce569106b5210a8d4ffe5bc9123eb0dec6b24ff56cf680922eccdd9891012", + "checksum": "a630e8317e2dbffd6856a2ad820af81b6dad15e355ec060f09fdf59d900dccc2", "checksum_algorithm": "sha256" }, { "name": "debug", "file": "skills/debug/SKILL.md", "version": "20260421011", - "checksum": "b4c1c6de7142b7b74326d1c19a5cc27f3957d1faa48aa48bd6b1fc45832edf3b", + "checksum": "d5835aaa59841c5c22d12562afece77ba0d539fbc17be6c9ba46f08c65752c7b", "checksum_algorithm": "sha256" }, { "name": "dependabot", "file": "skills/dependabot/SKILL.md", "version": "20260502027", - "checksum": "fa3f595740faa0b8093ffe4f32fb8e4b85f5834ea10817752ee9c94be8f38c2d", + "checksum": "3bda392b46bdee53a47e586cf1dc1ec59ed4b142c8e8afa2093333997c5f1a0f", "checksum_algorithm": "sha256" }, { "name": "dependency", "file": "skills/dependency/SKILL.md", "version": "20260421012", - "checksum": "2cbbecca8e6697b8523301325a889a5ca88c520f6d35a3b98807b6c3449239f5", + "checksum": "2d26955a7f97b2a880ff66c470f7c93336948817b7f2c816e14a9df2b33df18e", "checksum_algorithm": "sha256" }, { "name": "design", "file": "skills/design/SKILL.md", "version": "20260421013", - "checksum": "2d56e34aca8d3cf5a3f327e3b69ab15707393ffc47607c91ec91dd5be86cd65b", + "checksum": "367877b19c7fdc444bc24c833f025f3d11f43d0b3e625eecde5bdc283badc971", "checksum_algorithm": "sha256" }, { "name": "docs", "file": "skills/docs/SKILL.md", "version": "20260421014", - "checksum": "d6ebf7d270eac7911aea09a34cb7ec4006c70064b7e6f58dfa84a405e6620d18", + "checksum": "8784d94cce824b53e82fdf22a66708fdee9cc03df1761b0d71c775db30b90872", "checksum_algorithm": "sha256" }, { "name": "explore", "file": "skills/explore/SKILL.md", "version": "20260421015", - "checksum": "914323f796ac95a0f73d404f1b7eb38d94ba8950ed1a4acc5d80600ba03fae8f", + "checksum": "99762b3f7c25adf214b89567e88e5d0f2e53544505bd1662440aea44d00be59f", "checksum_algorithm": "sha256" }, { "name": "gdpr", "file": "skills/gdpr/SKILL.md", "version": "20260502029", - "checksum": "dfe60c4c59b7ae4dc66e98a885ec42b0e60eccc61fa1cb6fd269cd7eb3e0a94d", + "checksum": "34e2ba426beddb8efcc3e399b3536544344cbb160f164a13520e6d1e8892f678", "checksum_algorithm": "sha256" }, { "name": "gh-issues", "file": "skills/gh-issues/SKILL.md", "version": "20260502025", - "checksum": "bc1f595283e7643de63c7519e994500b9434a0343154bfc404b842ce8e7028a8", + "checksum": "8e478886b9a2039fa4c7858da0d05a3cf6e8773dcb7c67367f907b7d0db5187c", "checksum_algorithm": "sha256" }, { "name": "gh-release", "file": "skills/gh-release/SKILL.md", "version": "20260502023", - "checksum": "10abe52f681d261588608418a546580e7798e883179888061ed6d2285050d870", + "checksum": "30fa374af26d17ec5025398218eb319d18aa913d75fcf36c129ececfaafe25e3", "checksum_algorithm": "sha256" }, { "name": "guardrails", "file": "skills/guardrails/SKILL.md", "version": "20260421016", - "checksum": "1c1a44c95461eb4eeef7ad64b5846a1efbb202cf1b75186421c5d0b8f9fd03f1", + "checksum": "c1536c4c7b19b5f95ba0d06852698edf3c0f42cb3e485f61fdf8c04be09eae8c", "checksum_algorithm": "sha256" }, { "name": "helm", "file": "skills/helm/SKILL.md", "version": "20260502037", - "checksum": "6e6579ede2dadb6dbe895d07c362d208e62414338445bd035f47bd1404be501b", + "checksum": "5bc0c8a900890d8faa65ea459645a19d026523b2da3d40258cc1406840e719b6", "checksum_algorithm": "sha256" }, { "name": "incident", "file": "skills/incident/SKILL.md", "version": "20260503002", - "checksum": "546229fe91121dae2192aceb0b215096cc8cf5ba0cfb3d1300e30c73ff3f6276", + "checksum": "8e645512e59cf858587a93c3375a829c1350264af6e92d7179b1ad8e904f17c1", "checksum_algorithm": "sha256" }, { "name": "inspect", "file": "skills/inspect/SKILL.md", "version": "20260421018", - "checksum": "3a4aee3620bbd669602001c041cf81788da5fac9dc055a9f1b0c155e50e88b46", + "checksum": "9a2ec789958de371eadc9cdf68eb52c8b41904e18901990cb5972da7e31be43b", "checksum_algorithm": "sha256" }, { "name": "k8s", "file": "skills/k8s/SKILL.md", "version": "20260502036", - "checksum": "2bfbe14c187809211a7296578644262e2e47c89b4a2fc28fc3a30a79fdaeb580", + "checksum": "10075706cbcc555bac8e2c6515bc35d919ad4acf53678d9abcdc8c218f2902e3", "checksum_algorithm": "sha256" }, { "name": "migrate", "file": "skills/migrate/SKILL.md", "version": "20260421019", - "checksum": "eecc00aae2c58c573000a434fbf7ae891a3e85dcc99a42fec0e8bcadc3047b7d", + "checksum": "278b4b217ba16e56c1bd28ed2a6a8180bd3cb65e848c91c7dd2992bdcce72637", "checksum_algorithm": "sha256" }, { "name": "onboard", "file": "skills/onboard/SKILL.md", "version": "20260421020", - "checksum": "f16f2d78002e3be8e7d1d9e2bf376c1b37eb181d821f3cd113e0d5aecf5236ae", + "checksum": "dc37d9e77e6f35c32ce12e040afa6f90dbf0cc575bc0b1ead1dbfefda7fd2e5c", "checksum_algorithm": "sha256" }, { "name": "openapi", "file": "skills/openapi/SKILL.md", "version": "20260421021", - "checksum": "a3dbcded8e1a8b268a1976b9185f41de74ad0ad6e7aae98c399d6135de920529", + "checksum": "12e9875af27880b84a31ecd1c7d7967955d207f05f04c8dbe7b753d2415ae179", "checksum_algorithm": "sha256" }, { "name": "performance", "file": "skills/performance/SKILL.md", "version": "20260421022", - "checksum": "fa4062c77381facacedac289b54a3ce7e6cf242dfa2b36f55401aebfe5fb8ae1", + "checksum": "6bc6da196ea01b3c2c1d4f27c6ab36acec0709ff7fb730bdcc2b5597ea5c0614", "checksum_algorithm": "sha256" }, { "name": "postmortem", "file": "skills/postmortem/SKILL.md", "version": "20260503001", - "checksum": "fc70f26420e705e0979ff76476ea8176386258f2315fcb57f5ac9049a37f6c90", + "checksum": "ba62c73a9b1e3c3c9e40b698e4b0cbcdfe9d8350a0a55ff7ff00216921665bc0", "checksum_algorithm": "sha256" }, { "name": "pr", "file": "skills/pr/SKILL.md", "version": "20260502013", - "checksum": "fc7b2b08696982afd48033a3cc29b9fa4ac8b590fc61c828b84135585c88b345", + "checksum": "dd99ce60d20e3508ed88902d0dc2ad30bf96fab9d8a704548825bb7dbb4c6566", "checksum_algorithm": "sha256" }, { "name": "rancher", "file": "skills/rancher/SKILL.md", "version": "20260502038", - "checksum": "3000f24c2ee2f99fdb363f30d6075993ed26533663d081089b7800098a28e648", + "checksum": "8e9894d47f00fe08bb8ae44b847810226a27ecbe0c748287c9b0a83748dd0868", "checksum_algorithm": "sha256" }, { "name": "rca", "file": "skills/rca/SKILL.md", "version": "20260503001", - "checksum": "8bebb57c166c153d89e4589097ea419266b249df871a79e24c840112b5f8ad0c", + "checksum": "68aa25538b134f91e09602aef428a504845744741fb52ac9cdfcfd2a31b2dcb5", "checksum_algorithm": "sha256" }, { "name": "refactor", "file": "skills/refactor/SKILL.md", "version": "20260421023", - "checksum": "aa7fb995b6ac649b24b61066f15a6fe7fddab81bdcec8af27eba65092b957d68", + "checksum": "81c4839028edce9022f895f9a471a0b2a6b539153161f2cdee3381379e45030a", "checksum_algorithm": "sha256" }, { "name": "release-notes", "file": "skills/release-notes/SKILL.md", "version": "20260502014", - "checksum": "589f001bcbf0e27c4da6ff9c530371a1f183b7936e3a7fc421879282bd7f9af3", + "checksum": "7947a3e9f5f7fd911efcb257c990a8eec4954e28d6dee4690a724f2b273c2ef8", "checksum_algorithm": "sha256" }, { "name": "requirements", "file": "skills/requirements/SKILL.md", "version": "20260421024", - "checksum": "7d3338f0b7c5d493ab714a90b7d1732de56467503a3fe4dacdd40031bfea356c", + "checksum": "8bf772becd69317c7337d5e83af58a77c88cd6912d16f77875d5b547639ca384", "checksum_algorithm": "sha256" }, { "name": "secret-scan", "file": "skills/secret-scan/SKILL.md", "version": "20260502028", - "checksum": "95df047af89a93a8fa3716899b8fe703b270d7f40b93fe65915f7d1bdb849370", + "checksum": "8706f7b53aa87ec62175748c6d819a6f7ffcffb986eeb2a04f67d71636c80750", "checksum_algorithm": "sha256" }, { "name": "security", "file": "skills/security/SKILL.md", "version": "20260421025", - "checksum": "e9bcd451ee25b1c752239e76e20ca67302061356b59214fae7b134a180022df8", + "checksum": "9872abef1cb00cbf581a50fe82c10f9563ae4bb6e3810a9eacbffb4e5b527c7a", "checksum_algorithm": "sha256" }, { "name": "terraform", "file": "skills/terraform/SKILL.md", "version": "20260502030", - "checksum": "2bb933512f72b61dae3a3b632a7afd43c62aad066de003ee0a565fc8f1895215", + "checksum": "669e6ffc1f294ffb7dd903448be1557aed9fd5be0233aa950040bf1892e54f1f", "checksum_algorithm": "sha256" }, { "name": "terragrunt", "file": "skills/terragrunt/SKILL.md", "version": "20260502031", - "checksum": "e0d3dc0feefeafb9cd41d8d6885d8b4d53baed52ba70df7dc8d33bfc7410bae8", + "checksum": "bc91cb7fb63dbb44c6eb92c85283871605b8f7317947213d9c70db5b3cbbb086", "checksum_algorithm": "sha256" }, { "name": "threat-model", "file": "skills/threat-model/SKILL.md", "version": "20260502021", - "checksum": "7fdf69113167e9bb8c866681a86f4b4d6416145bb9def0ebe18ceba690c8fbe9", + "checksum": "ec1fc2114ed6c86cf3d6c3790130ee81f5bca6e574b9ad3efb870fc488550663", "checksum_algorithm": "sha256" }, { "name": "verify", "file": "skills/verify/SKILL.md", "version": "20260421026", - "checksum": "aa56d1a674ed827606af34b8e7c5bd9c2de61a6cb6f92f5fd9bdce28e46bcb30", + "checksum": "a7bfc7214a07bac2650c84b34aa044b8aa4f573fff0af956c5f9fe8429ba81d0", "checksum_algorithm": "sha256" }, { "name": "vision", "file": "skills/vision/SKILL.md", "version": "20260421027", - "checksum": "c0058202130905d8495a3e565b5db546ad2d0ff6d654596358a0b12c484ce310", + "checksum": "f13be0922aeff7d9c8f59466fdf0bf491d6b4d3f8788ab254135f9bd8e94e96e", "checksum_algorithm": "sha256" } ], @@ -319,42 +319,49 @@ "name": "architect", "file": "agents/architect.agent.md", "version": "20260503022", - "checksum": "95f6d8b98e8cbd8e6309b1708ff58462791959868b4052b44199c62dea4e0960", + "checksum": "ec1a08761409dbc8a47562b3dfbe53957833d34c3f64b34f2ea6e877d17bd617", "checksum_algorithm": "sha256" }, { "name": "designer", "file": "agents/designer.agent.md", "version": "20260503024", - "checksum": "2d593399bcf4c16475e0139d255bff80e4180856720e5bbc0735e226349c2727", + "checksum": "b48cdca585f1760f3d581d61b9bdf43df9e4669cb78f4d7fad4f5199a270f6de", "checksum_algorithm": "sha256" }, { "name": "engineer", "file": "agents/engineer.agent.md", "version": "20260503024", - "checksum": "08e905f3f6e724d02f774717c6c1cdd9c4bc5139f60053d9ec2864cf0d80f930", + "checksum": "a8f8bcf22aebcc9dd6eabfa47ebbe57e6f58926bbac750b89d50175f25b2022a", + "checksum_algorithm": "sha256" + }, + { + "name": "planner", + "file": "agents/planner.agent.md", + "version": "20260510001", + "checksum": "d46f0fdb8d4ee743f599c4d1243061063d783f3d194f8fb99925f37521bb8f5e", "checksum_algorithm": "sha256" }, { "name": "product", "file": "agents/product.agent.md", "version": "20260503021", - "checksum": "4bc343ed7b912988016aad7572da280a3824e46b00c86fa305fb334499a973b2", + "checksum": "d859ceb31e29cb125d854b2b9dc1bf92dc81e331292533db9b30ffb987d20ff6", "checksum_algorithm": "sha256" }, { "name": "release", "file": "agents/release.agent.md", "version": "20260503020", - "checksum": "f8496412e799033356ac67eeaaeb5addf29981d7f22235bf72971a533f6efc04", + "checksum": "661b802eb7380cccc78165ba354c509e4d968df3eccdd80c134980563bfb4a7d", "checksum_algorithm": "sha256" }, { "name": "tester", "file": "agents/tester.agent.md", "version": "20260503026", - "checksum": "6c31332714089743d7c5bfe2e45fec5471f03b21795f30edc920d110199f1d27", + "checksum": "6a620dc7e37b8c0f31e927fe991d1dfdd4d520e86d640d7139bc8eeab4c96767", "checksum_algorithm": "sha256" } ], @@ -363,84 +370,84 @@ "name": "git", "file": "instructions/git.instructions.md", "version": "20260421001", - "checksum": "ed6a191176e32631e2d572cb21278b555f6696a859839579821631156a8b35a5", + "checksum": "337b8e0bd880a694e6a334b6417d70b005984fbef10e8172efc899c623fa51f4", "checksum_algorithm": "sha256" }, { "name": "helm", "file": "instructions/helm.instructions.md", "version": "20260502040", - "checksum": "a60520853b79751517f089136ba7fb182feb1ea8e9c9495890e5c14ff32afd7b", + "checksum": "e42683a6d6f4e579b5d4baf93e00541e6c4089aaa196512b38bff9ee0e16101f", "checksum_algorithm": "sha256" }, { "name": "java", "file": "instructions/java.instructions.md", "version": "20260502001", - "checksum": "69dc2bf3a5428ed03d77c2789871985ea7c5881af89c2b414548fca6e9488464", + "checksum": "634ac626ed19d4b912c463b80c9a0df93e1ece4ffcbc8ff2b8ba3f97bfba62dc", "checksum_algorithm": "sha256" }, { "name": "k8s", "file": "instructions/k8s.instructions.md", "version": "20260502039", - "checksum": "f19f1060bf2a1424950496290a43a3535f9749e4ed838cf7475fe35eb9487d67", + "checksum": "1300ccae1257f75f6406a54d00985e6243f8cf8d367d8c4dbb7ff00e67ef0521", "checksum_algorithm": "sha256" }, { "name": "markdown", "file": "instructions/markdown.instructions.md", "version": "20260502002", - "checksum": "59c93c5b0e63360aff18ab3a7a207f4e798efd1f2c26b0a561a4ef4ea688c5c5", + "checksum": "ca88d33becf42fea73ef6863bdd9f9c7155f762c384a7047106af6395417f3af", "checksum_algorithm": "sha256" }, { "name": "python", "file": "instructions/python.instructions.md", "version": "20260421002", - "checksum": "ac40fffd3d3a3f9f8ca43e10ca603a187578c9d517533529a3d647b61cedf56c", + "checksum": "a6237f0642e18c35aaf44cd6e92927e6eb5d8bbcfccba0bb0e9a1c775e286d69", "checksum_algorithm": "sha256" }, { "name": "rancher", "file": "instructions/rancher.instructions.md", "version": "20260502041", - "checksum": "4e643b1e078e9e8f127f1f2cf7707b36109e28b8697e208d8d3804848b867eec", + "checksum": "322c68a0b56afa120cff6b26abe0ff882416aa9af8f14cc7c9f338c3c389417a", "checksum_algorithm": "sha256" }, { "name": "security", "file": "instructions/security.instructions.md", "version": "20260502003", - "checksum": "a943df637e44cb23c8e82f8b39bf70558bc502ece5af9a03a15d8492cffb51a4", + "checksum": "ec403a2a6784dc4365202c2bb4886d84383dcdbd9e1eec19fb6a26d46583be52", "checksum_algorithm": "sha256" }, { "name": "terraform", "file": "instructions/terraform.instructions.md", "version": "20260502034", - "checksum": "1bada82da46a6359bb1814b6642074be6c0b7de9c2c6ca59e031a4d05ea4ea8c", + "checksum": "5b0eb238a2c335cfad2309a453be4cbc23c0bdd0cd19741ab3e4c4a62f06d3c1", "checksum_algorithm": "sha256" }, { "name": "terragrunt", "file": "instructions/terragrunt.instructions.md", "version": "20260502035", - "checksum": "13c0059c366624ab482c2b3d24a353bcb0a2ffbb6bf735abd18796969a3e4020", + "checksum": "d5b97886b042f4c2b73b3207ed9b0eb157e03a82e23af3609f9fe5e7b5b8a69a", "checksum_algorithm": "sha256" }, { "name": "testing", "file": "instructions/testing.instructions.md", "version": "20260502004", - "checksum": "cbd1948f367c32c39032209e5ed9fcfff8ce6c46c6324a4aa9a8550365873ca3", + "checksum": "d10d57bf7dae2edd74653080ea38727668e9e84a4d703a4f9747d351f8eb1ddf", "checksum_algorithm": "sha256" }, { "name": "typescript", "file": "instructions/typescript.instructions.md", "version": "20260502005", - "checksum": "fe412ba2e60baea66d0d07ae0c153fa2a9157bd57556fa7476f46ce466160ad3", + "checksum": "3de62a3fbc5bcac74644d03de44e80f8bd32cf87b547c9676821408c0329a6a9", "checksum_algorithm": "sha256" } ], @@ -449,49 +456,49 @@ "name": "api-design-review", "file": "prompts/api-design-review.prompt.md", "version": "20260502006", - "checksum": "1ba62a6f78b836256fe578c4ca312de24599c79b018119b81a100f6de4e0da11", + "checksum": "84a94cb6a93fac1a67829b40a4c51c31f74deb271760bad3504937bab1eef72f", "checksum_algorithm": "sha256" }, { "name": "architecture-risk", "file": "prompts/architecture-risk.prompt.md", "version": "20260502007", - "checksum": "14fa36e36948309827c1c1296ff2c8b1f1306cfb19057ef190a5e60f6cc04a61", + "checksum": "cf4f36f1c2f2e4aa795ccf1ad75a9dcbc824f89655d26b60410b0b1c3cf3ffc3", "checksum_algorithm": "sha256" }, { "name": "code-review", "file": "prompts/code-review.prompt.md", "version": "20260502008", - "checksum": "b6499ca66706a08ced7b9bf69bd288c2fecb684e1c85e333ee91d36ba7e66c3f", + "checksum": "2c29ada8aca812513c2b6c2c78b61569ec0bba265b674af060e171c018ba3ba8", "checksum_algorithm": "sha256" }, { "name": "dependency-audit", "file": "prompts/dependency-audit.prompt.md", "version": "20260502009", - "checksum": "51cead168dbbe52b455813cc620a8426ec287ceb2994831aa6ae115b4e49d0b0", + "checksum": "b9775792c34a46dc3dd9e573675510109a7ef9da65a65860dd6af25d3a9fd4d0", "checksum_algorithm": "sha256" }, { "name": "incident-timeline", "file": "prompts/incident-timeline.prompt.md", "version": "20260502010", - "checksum": "28627dc7c362d510f312281fe0bda30e1927998bae440808af3a92d06c455393", + "checksum": "080ebbfb919d353f041e3a007fefa3b31c418590ce64dfbb490c54d96dbd084b", "checksum_algorithm": "sha256" }, { "name": "migration-safety", "file": "prompts/migration-safety.prompt.md", "version": "20260502011", - "checksum": "814258882774ff98724b14caa0bc9ee35dc68f46e72e62b24884052e79ffacfa", + "checksum": "7ed20c0929b56df847e6c34018c2a59c7eb6b2070789f6fcd8d4e0c7117e23d6", "checksum_algorithm": "sha256" }, { "name": "release-readiness", "file": "prompts/release-readiness.prompt.md", "version": "20260502012", - "checksum": "49898a07169504acba1c2195abf730be77d1e17298cb4676f3ed926a27e31dc6", + "checksum": "d8d9f484bb977b01e3d8dcb034b5ba7cf33b163d6c2fb689bcdc1ae511656bbe", "checksum_algorithm": "sha256" } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 08bac98..be41bb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,57 +4,51 @@ ## [3.1.1](https://github.com/eschaar/vstack/compare/3.1.0...3.1.1) (2026-05-10) - ### Fixes -* **ci:** install wheel dependencies in publish smoke test ([8fa6502](https://github.com/eschaar/vstack/commit/8fa6502609184075cd2771e724ff3e7758b3a1df)) +- **ci:** install wheel dependencies in publish smoke test ([8fa6502](https://github.com/eschaar/vstack/commit/8fa6502609184075cd2771e724ff3e7758b3a1df)) ## [3.1.0](https://github.com/eschaar/vstack/compare/3.0.0...3.1.0) (2026-05-09) - ### Features -* **agents:** add missing execute/web tools to product, architect, designer, release ([6ac17de](https://github.com/eschaar/vstack/commit/6ac17de8e7a747b0406d6abc743ae7b9ee3680f8)) -* **agents:** restructure config to defaults block; add baseline artifact flag ([86e1cf6](https://github.com/eschaar/vstack/commit/86e1cf65c9cbe89d8321a1fc9694965e8e1dee16)) -* **parser:** replace hand-rolled YAML parser with PyYAML ([17de97d](https://github.com/eschaar/vstack/commit/17de97d418030983971f23479b30624e7cce7d0c)) -* **parser:** support nested block YAML in object-list items ([a5f2df0](https://github.com/eschaar/vstack/commit/a5f2df00f0b8cc78a513efcf9ed5fae85dda3893)) -* **skills:** add allowed-tools field to SKILL_SCHEMA and all 44 skill configs ([aa02d46](https://github.com/eschaar/vstack/commit/aa02d464dcd5f36ed1aa88c29c2a8474ca6d4bbe)) -* **workflow:** add workflow contract schema to .vstack/config.yaml ([acc859f](https://github.com/eschaar/vstack/commit/acc859f8eb0c0de4d698cdebdab642c65364ebc1)) - +- **agents:** add missing execute/web tools to product, architect, designer, release ([6ac17de](https://github.com/eschaar/vstack/commit/6ac17de8e7a747b0406d6abc743ae7b9ee3680f8)) +- **agents:** restructure config to defaults block; add baseline artifact flag ([86e1cf6](https://github.com/eschaar/vstack/commit/86e1cf65c9cbe89d8321a1fc9694965e8e1dee16)) +- **parser:** replace hand-rolled YAML parser with PyYAML ([17de97d](https://github.com/eschaar/vstack/commit/17de97d418030983971f23479b30624e7cce7d0c)) +- **parser:** support nested block YAML in object-list items ([a5f2df0](https://github.com/eschaar/vstack/commit/a5f2df00f0b8cc78a513efcf9ed5fae85dda3893)) +- **skills:** add allowed-tools field to SKILL_SCHEMA and all 44 skill configs ([aa02d46](https://github.com/eschaar/vstack/commit/aa02d464dcd5f36ed1aa88c29c2a8474ca6d4bbe)) +- **workflow:** add workflow contract schema to .vstack/config.yaml ([acc859f](https://github.com/eschaar/vstack/commit/acc859f8eb0c0de4d698cdebdab642c65364ebc1)) ### Fixes -* **agents:** restore _resolve_handoffs fallback; fix None guard in migrate ([8f2dbaf](https://github.com/eschaar/vstack/commit/8f2dbafe161814a903551f28e47afbe1cc77f303)) -* **ci:** add FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 to all workflows; fix security.yml env ([b2e6ccd](https://github.com/eschaar/vstack/commit/b2e6ccd3c73e0cf984d27cb7439b0ac9fc349540)) -* **frontmatter:** restore [@staticmethod](https://github.com/staticmethod) on FrontmatterParser; fix dict/list serialization ([8711e3e](https://github.com/eschaar/vstack/commit/8711e3e7d3a32117c024bd5dbdc2fc8ed9723d58)) -* **manifest:** re-export removed helpers as deprecated shims ([d4face3](https://github.com/eschaar/vstack/commit/d4face3a9608dd84aacd8b4981adff65467f0c1a)) -* **review:** remove dead str-fallback in _extract_defaults; sync docs with pyyaml dep ([993c2fc](https://github.com/eschaar/vstack/commit/993c2fce69fdf57bdbe407ff2104e09c997cd4a6)) - +- **agents:** restore \_resolve_handoffs fallback; fix None guard in migrate ([8f2dbaf](https://github.com/eschaar/vstack/commit/8f2dbafe161814a903551f28e47afbe1cc77f303)) +- **ci:** add FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 to all workflows; fix security.yml env ([b2e6ccd](https://github.com/eschaar/vstack/commit/b2e6ccd3c73e0cf984d27cb7439b0ac9fc349540)) +- **frontmatter:** restore [@staticmethod](https://github.com/staticmethod) on FrontmatterParser; fix dict/list serialization ([8711e3e](https://github.com/eschaar/vstack/commit/8711e3e7d3a32117c024bd5dbdc2fc8ed9723d58)) +- **manifest:** re-export removed helpers as deprecated shims ([d4face3](https://github.com/eschaar/vstack/commit/d4face3a9608dd84aacd8b4981adff65467f0c1a)) +- **review:** remove dead str-fallback in \_extract_defaults; sync docs with pyyaml dep ([993c2fc](https://github.com/eschaar/vstack/commit/993c2fce69fdf57bdbe407ff2104e09c997cd4a6)) ### Refactoring -* OOP cleanup — instance methods, inline registry, extracted utils ([2d13679](https://github.com/eschaar/vstack/commit/2d13679b770fed5ed8488184ef1a8e1eae180ac9)) - +- OOP cleanup — instance methods, inline registry, extracted utils ([2d13679](https://github.com/eschaar/vstack/commit/2d13679b770fed5ed8488184ef1a8e1eae180ac9)) ### Documentation -* add install and upgrade guide with quickstart and quick upgrade paths ([39ea7f1](https://github.com/eschaar/vstack/commit/39ea7f13ce41d42634b12426d97211cdc269c9f1)) -* **adr:** add ADR-023 and ADR-024; supersede ADR-004; update overview ([bf199f2](https://github.com/eschaar/vstack/commit/bf199f222a2360d4d63b18b863d9938c9cc0a127)) -* **adr:** align ADR-023 schema; mark ADR-026 shipped; update migrations README ([ce5fcda](https://github.com/eschaar/vstack/commit/ce5fcda2da8ba7ccffa323e56d2ed94ab5a82841)) -* correct runtime dependency badge and text — PyYAML is required ([27d60e3](https://github.com/eschaar/vstack/commit/27d60e3701c387d76c11ed42350bf00cc606709b)) -* **roadmap:** correct orchestrated pipeline status from in-progress to candidate ([51a4780](https://github.com/eschaar/vstack/commit/51a4780d4d775a9ea8b2d22b2f16757234e85591)) -* **roadmap:** mark workflow contract source-of-truth as shipped ([e57755b](https://github.com/eschaar/vstack/commit/e57755b6751e0879f31ce3e0932b5c3c108411d4)) - +- add install and upgrade guide with quickstart and quick upgrade paths ([39ea7f1](https://github.com/eschaar/vstack/commit/39ea7f13ce41d42634b12426d97211cdc269c9f1)) +- **adr:** add ADR-023 and ADR-024; supersede ADR-004; update overview ([bf199f2](https://github.com/eschaar/vstack/commit/bf199f222a2360d4d63b18b863d9938c9cc0a127)) +- **adr:** align ADR-023 schema; mark ADR-026 shipped; update migrations README ([ce5fcda](https://github.com/eschaar/vstack/commit/ce5fcda2da8ba7ccffa323e56d2ed94ab5a82841)) +- correct runtime dependency badge and text — PyYAML is required ([27d60e3](https://github.com/eschaar/vstack/commit/27d60e3701c387d76c11ed42350bf00cc606709b)) +- **roadmap:** correct orchestrated pipeline status from in-progress to candidate ([51a4780](https://github.com/eschaar/vstack/commit/51a4780d4d775a9ea8b2d22b2f16757234e85591)) +- **roadmap:** mark workflow contract source-of-truth as shipped ([e57755b](https://github.com/eschaar/vstack/commit/e57755b6751e0879f31ce3e0932b5c3c108411d4)) ### Maintenance -* **docs:** apply mdformat to adr-026 and migrations readme ([55ed664](https://github.com/eschaar/vstack/commit/55ed66415d29f621e8d2a662cc1fde880966dbdf)) -* expand PyPI keywords and classifiers for discoverability ([a2bcd7e](https://github.com/eschaar/vstack/commit/a2bcd7e279284e91046b90027de8903e3ca0f9bb)) -* **install:** regenerate artifacts after agent and skill tool config changes ([f4c62f4](https://github.com/eschaar/vstack/commit/f4c62f4955fad34a600408effaed60ccf0a7d646)) -* **install:** regenerate artifacts with quoted wildcard agents ([b1b2024](https://github.com/eschaar/vstack/commit/b1b20249329f7ad067ef91f30238090bf67317a6)) -* **install:** regenerate artifacts; update manifest and changelog formatting ([d1c2cf2](https://github.com/eschaar/vstack/commit/d1c2cf215b65c90a948e5bfee83c249a0f106782)) -* **install:** regenerate skills artifacts and manifest ([d5c2a6e](https://github.com/eschaar/vstack/commit/d5c2a6e5c27b87ebee883a64c1b5ad8e68bc1a2c)) -* regenerate poetry.lock after adding pyyaml runtime dependency ([f34b181](https://github.com/eschaar/vstack/commit/f34b1818f9fc161bc5fa05ba740c3eee61e9fa79)) +- **docs:** apply mdformat to adr-026 and migrations readme ([55ed664](https://github.com/eschaar/vstack/commit/55ed66415d29f621e8d2a662cc1fde880966dbdf)) +- expand PyPI keywords and classifiers for discoverability ([a2bcd7e](https://github.com/eschaar/vstack/commit/a2bcd7e279284e91046b90027de8903e3ca0f9bb)) +- **install:** regenerate artifacts after agent and skill tool config changes ([f4c62f4](https://github.com/eschaar/vstack/commit/f4c62f4955fad34a600408effaed60ccf0a7d646)) +- **install:** regenerate artifacts with quoted wildcard agents ([b1b2024](https://github.com/eschaar/vstack/commit/b1b20249329f7ad067ef91f30238090bf67317a6)) +- **install:** regenerate artifacts; update manifest and changelog formatting ([d1c2cf2](https://github.com/eschaar/vstack/commit/d1c2cf215b65c90a948e5bfee83c249a0f106782)) +- **install:** regenerate skills artifacts and manifest ([d5c2a6e](https://github.com/eschaar/vstack/commit/d5c2a6e5c27b87ebee883a64c1b5ad8e68bc1a2c)) +- regenerate poetry.lock after adding pyyaml runtime dependency ([f34b181](https://github.com/eschaar/vstack/commit/f34b1818f9fc161bc5fa05ba740c3eee61e9fa79)) ## [3.0.0](https://github.com/eschaar/vstack/compare/2.2.0...3.0.0) (2026-05-06) diff --git a/README-pypi.md b/README-pypi.md index eb6c8ac..298bd19 100644 --- a/README-pypi.md +++ b/README-pypi.md @@ -14,7 +14,7 @@ The VS Code-native AI workflow system for backend engineering. vstack installs structured agents, skills, instructions, and prompts into `.github/` so GitHub Copilot Agent Mode can run repeatable backend workflows with clear role boundaries. -It provides a fixed role model for end-to-end software delivery: `product`, `architect`, `designer`, `engineer`, `tester`, and `release`. +It provides six delivery roles for end-to-end software work: `product`, `architect`, `designer`, `engineer`, `tester`, and `release`, coordinated by `planner`. ## Best for @@ -24,7 +24,7 @@ It provides a fixed role model for end-to-end software delivery: `product`, `arc ## What you get -- Fixed role model: `product`, `architect`, `designer`, `engineer`, `tester`, `release` +- Fixed role model: six delivery roles plus a planner coordinator - Template-driven install model from `src/vstack/_templates/` - Backend-first verification, security, and release discipline - One runtime dependency: PyYAML @@ -198,6 +198,71 @@ vstack install --dry-run The summary lists preserved files as `type/name` selectors (e.g. `agent/engineer`). Resolve each conflict with `--force-name type/name` to overwrite, `--adopt-name type/name` to take ownership without overwriting, or `--force` to overwrite everything. +## Reading `.vstack/config.yaml` + +- Lines starting with `#` are comments, explanation, or example configuration and are not active. +- Only uncommented YAML keys are active configuration. +- To enable an example block, remove `#` from that block and keep valid YAML indentation. +- After any config change, run `vstack init` to apply it to generated `.github/` artifacts. + +## Workflow modes + +vstack supports three workflow modes via `.vstack/config.yaml`: + +```yaml +workflow: + mode: agentic # default +``` + +After changing `workflow.mode`, regenerate artifacts: + +```bash +vstack init +``` + +| Mode | Behavior | Planner file | Worker handoff buttons | +| --------- | ------------------------------------------------------------ | ------------- | ---------------------- | +| `agentic` | Planner orchestrates stage progression using subagents | generated | omitted | +| `manual` | User progresses stage-by-stage manually | not generated | shown | +| `hybrid` | Both planner orchestration and manual handoffs are available | generated | shown | + +Execution semantics: + +- `workflow.stages` order is the canonical progression order. +- `agentic` is stage-sequential by default: planner advances one stage at a time in configured order. +- Parallelization is still possible inside a stage (independent subtasks), but cross-stage progression remains ordered. + +Handoff target semantics: + +- `handoffs.prompt` is the transition prompt text. +- If `handoffs.agent` is omitted, the target defaults to the next role in `workflow.stages`. +- You can set `handoffs.agent` explicitly to override that default target in `manual`/`hybrid`. +- In `agentic`, worker handoff buttons are hidden; planner controls progression. + +Mode quickstart in Copilot Agent Mode: + +| Mode | Start here | First prompt example | +| --------- | ------------------------ | ------------------------------------------------------- | +| `agentic` | `@planner` | `@planner Run the workflow for this repository change.` | +| `manual` | `@product` | `@product Define requirements for this change.` | +| `hybrid` | `@planner` or `@product` | `@planner Run the workflow for this repository change.` | + +Usage guidance: + +- Use `agentic` when you want one deterministic orchestration path. +- Use `manual` when your team prefers explicit user-controlled stage transitions. +- Use `hybrid` only when your team intentionally wants both options. + +Hybrid operating rule: + +- Choose one path per session (planner-led or manual handoffs) and stay on it. +- Mixing both paths in one session increases the chance of duplicate stage transitions. + +Hybrid warning: + +- In `hybrid`, users can click handoff buttons while a planner-led flow is also available. +- This can cause unintended progression jumps or duplicated transitions if your process assumes one strict path. + ## Fast troubleshooting - Command not found after install: ensure your `pipx` binary path is in `PATH` diff --git a/README.md b/README.md index 5a43a8c..9d16e9e 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ has a clear operating model instead of ad hoc chat prompts. What gets built is determined by the product vision. vstack fixes the delivery roles and boundaries: `product`, `architect`, `designer`, `engineer`, `tester`, -and `release`. +and `release`, coordinated by `planner`. vstack started as a rethink inspired by [gstack](https://github.com/garrytan/gstack), but was rebuilt around a template-driven, VS Code-first workflow model. @@ -510,6 +510,13 @@ Both commands default to the current working directory when `--target` is omitte When you run `vstack install`, a `.vstack/config.yaml` file is seeded in your project. This file is yours: vstack never overwrites it. Commit it to version control — it expresses stable project preferences that apply on every future `vstack init` run. +How to read this file: + +- Lines starting with `#` are comments, explanation, or example configuration and are not active. +- Only uncommented YAML keys are active configuration. +- To enable an example block, remove `#` from that block and keep valid YAML indentation. +- After any config change, run `vstack init` to apply it to generated `.github/` artifacts. + The two most useful settings: **Exclude specific artifacts** — skip artifact types or individual artifacts you do not need: @@ -526,13 +533,88 @@ exclude: Type-level entries (`all`) remove the type from generation entirely. Name-level entries skip individual artifacts within a type while keeping the rest. -**Override the docs root path** — change where agent artifact paths point (default: `docs`): +**Override the docs root path** — change where agent work-item paths point (default: `docs`): ```yaml -artifacts: +items: root: documentation # use a different path prefix in generated agent files ``` +Legacy compatibility: `artifacts.root` is still supported as a fallback for older configs. + +Migration path: + +1. Keep existing `artifacts.root` configs unchanged — current versions continue to read them. +1. Move to `items.root` when convenient; `items.root` takes precedence when both are present. +1. For teams with mixed versions, either key is accepted until all projects standardize on `items.root`. + +**Select workflow mode** — control whether users progress manually, via planner orchestration, or both: + +```yaml +workflow: + mode: agentic # default +``` + +After changing `workflow.mode`, regenerate artifacts: + +```bash +vstack init +``` + +Supported values: + +| Mode | Behavior | Planner file | Worker handoff buttons | +| --------- | ---------------------------------------- | ------------- | ---------------------- | +| `agentic` | Planner orchestrates stages as subagents | generated | omitted | +| `manual` | User progresses stage-by-stage manually | not generated | shown | +| `hybrid` | Both patterns are available | generated | shown | + +Execution semantics: + +- `workflow.stages` order is the canonical progression order. +- `agentic` is stage-sequential by default: planner advances one stage at a time in configured order. +- Parallelization is still possible inside a stage (independent subtasks), but cross-stage progression remains ordered. + +Handoff target semantics: + +- `handoffs.prompt` is the transition prompt text. +- If `handoffs.agent` is omitted, the target defaults to the next role in `workflow.stages`. +- You can set `handoffs.agent` explicitly to override that default target in `manual`/`hybrid`. +- In `agentic`, worker handoff buttons are hidden; planner controls progression. + +Mode quickstart in Copilot Agent Mode: + +| Mode | Start here | First prompt example | +| --------- | ------------------------ | ------------------------------------------------------- | +| `agentic` | `@planner` | `@planner Run the workflow for this repository change.` | +| `manual` | `@product` | `@product Define requirements for this change.` | +| `hybrid` | `@planner` or `@product` | `@planner Run the workflow for this repository change.` | + +Hybrid operating rule: + +- Choose one path per session (planner-led or manual handoffs) and stay on it. +- Mixing both paths in one session increases the chance of duplicate stage transitions. + +Hybrid mode warning: + +- In `hybrid`, users can continue via planner orchestration and via handoff buttons. +- This dual-path UX can create unintended jumps or duplicate progression in teams that expect a single strict path. +- If you want one deterministic flow, prefer `agentic`. + +```mermaid +flowchart LR + A[workflow.mode] --> B{Selected mode} + B --> C[agentic] + B --> D[manual] + B --> E[hybrid] + C --> C1[Planner generated] + C --> C2[Worker handoffs hidden] + D --> D1[Planner omitted] + D --> D2[Worker handoffs visible] + E --> E1[Planner generated] + E --> E2[Worker handoffs visible] +``` + All fields are optional. An absent or commented-out block restores the default behaviour. ______________________________________________________________________ diff --git a/docs/architecture/adr/026-docs-artifact-migration-policy.md b/docs/architecture/adr/026-docs-artifact-migration-policy.md index 4d888ca..a3113b9 100644 --- a/docs/architecture/adr/026-docs-artifact-migration-policy.md +++ b/docs/architecture/adr/026-docs-artifact-migration-policy.md @@ -60,7 +60,7 @@ This commitment applies to: It does **not** apply to: - `.github/` artifact paths, which follow VS Code / Copilot conventions (covered by ADR-002) -- Project-specific paths the user configures via `artifacts.root` or workflow overrides +- Project-specific paths the user configures via `items.root` or workflow overrides ### 2. Agent config.yaml is the source of truth; skill prose is informational @@ -135,9 +135,9 @@ the same moves in prose for users who prefer to migrate manually. ### 5. Skill prose references are resolved at LLM runtime, not install time No variable substitution is applied to skill template prose at install time (e.g. -`{{artifacts_root}}/architecture/overview.md`). The agent config `artifacts.dir` is +`{{items_root}}/architecture/overview.md`). The agent config `items.dir` is the machine-readable source; prose is a human-readable aid for the LLM. If a user -sets a custom `artifacts.root` in `.vstack/config.yaml`, the agent's generated +sets a custom `items.root` in `.vstack/config.yaml`, the agent's generated `.agent.md` body will contain the resolved root because the agent template uses the config value at generation time. Skill markdown bodies remain as authored. @@ -154,10 +154,10 @@ current execution model. ### B — Variable substitution in skill template prose Replace hardcoded paths in skill markdown (e.g. `docs/architecture/overview.md`) with -install-time variables (`{{artifacts_root}}/architecture/overview.md`). Rejected because: +install-time variables (`{{items_root}}/architecture/overview.md`). Rejected because: the agent config is already the machine-readable authority; prose is LLM guidance only; adding a substitution pass increases template complexity for marginal gain. The LLM reads -the agent's own `.agent.md` (which does resolve `artifacts_root`) before executing a skill — +the agent's own `.agent.md` (which does resolve `items.root`) before executing a skill — the agent already has the correct context. ### C — Stability promise only, no migration tooling diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index fe4eed1..a44cc19 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -87,8 +87,10 @@ Key resolvers defined inline in the generator: ### 4. role model -vstack uses 6 fixed agent roles. Each role has defined skill access and artifact ownership. -See `docs/architecture/adr/009-role-model.md` for the decision record. +vstack uses six delivery roles plus a planner coordinator agent. Delivery roles +have defined skill access and artifact ownership. See +`docs/architecture/adr/009-role-model.md` for the role-model decision and +`docs/architecture/adr/024-subagent-orchestration.md` for planner orchestration. | Role | Artifact ownership | | --------- | -------------------------------------------------------------------------------------------------------- | @@ -98,6 +100,7 @@ See `docs/architecture/adr/009-role-model.md` for the decision record. | engineer | code, unit tests | | tester | `docs/reports/test-report.md`, `docs/reports/security-report.md`, `docs/reports/performance-baseline.md` | | release | `docs/releases/YYYYMMDDNN.md`, `CHANGELOG.md`, release PR | +| planner | none (coordination only; reads workflow config and stage outputs) | ### 5. manifest (`.vstack/vstack.json`) @@ -124,8 +127,11 @@ manifest. See ADR-016. ### 6. VS Code agent files (`.github/agents/.agent.md`) -Generated output — one file per role (6 total: product, architect, designer, engineer, -tester, release). Installed to `.github/agents/` in a project. +Generated output — mode-dependent role set in `.github/agents/`. + +- `workflow.mode=agentic` (default): 7 files (`planner` + 6 worker roles) +- `workflow.mode=manual`: 6 files (worker roles only; planner omitted) +- `workflow.mode=hybrid`: 7 files (`planner` + 6 worker roles) ```yaml --- @@ -183,9 +189,10 @@ ______________________________________________________________________ ## execution model -### current execution model — single-call +### execution models by workflow mode -Copilot executes the selected role or skill in a single context window. +Manual mode executes selected roles/skills in single calls; agentic mode adds +planner-led orchestration. ```mermaid flowchart LR @@ -194,7 +201,7 @@ flowchart LR C --> D[Writes docs, code, or reports to disk] ``` -### target operating model — stage-gated role pipeline +### planner-orchestrated model — stage-gated role pipeline Each role is a separate model call. Output artifacts from one role become the input context for the next, and progression only happens after explicit user approval at each stage gate. diff --git a/docs/design/agents.md b/docs/design/agents.md index fce46b1..4cfbdbf 100644 --- a/docs/design/agents.md +++ b/docs/design/agents.md @@ -12,7 +12,17 @@ Agents are VS Code custom agents (`.agent.md`) that adopt a specific role or per - **Instructions** in the file body (role, responsibilities, how to work) - Optional **handoffs** to transition the user to the next agent in a workflow -In vstack, agents map to the 6 engineering roles: `product`, `architect`, `designer`, `engineer`, `tester`, `release`. +In vstack, agents include six delivery roles (`product`, `architect`, `designer`, +`engineer`, `tester`, `release`) plus a coordinator role (`planner`). + +Generation is mode-aware via `.vstack/config.yaml` `workflow.mode`: + +- `agentic` (default): planner is generated; worker handoff buttons are omitted. +- `manual`: planner is not generated; worker handoff buttons are generated. +- `hybrid`: planner is generated; worker handoff buttons are also generated. + +In `hybrid`, the UI exposes both progression paths (planner and handoff buttons). +Use it only when your process explicitly allows both. Canonical names are the source of truth. Historical or compatibility aliases should remain exceptional and temporary. See `docs/architecture/adr/002-artifact-naming-and-compatibility-policy.md`. @@ -61,25 +71,27 @@ Style rule: long `description` and `handoffs.prompt` values should use YAML bloc ### vstack-internal only (not emitted) -| Field | Notes | -| ----------- | ------------------------------------------------------------------------------ | -| `version` | Semantic version for vstack change tracking — never reaches the generated file | -| `artifacts` | Declares artifact ownership for this agent — see [artifacts](#artifacts) below | +| Field | Notes | +| --------- | ------------------------------------------------------------------------------ | +| `version` | Semantic version for vstack change tracking — never reaches the generated file | +| `items` | Declares work-item ownership for this agent — see [items](#items) below | Frontmatter multiline rendering is configured in generator code (`ArtifactTypeConfig.preserve_multiline_frontmatter`), not per-agent `config.yaml`. ______________________________________________________________________ -## artifacts +## items -The optional `artifacts:` block declares which paths an agent reads and writes. +The optional `items:` block declares which paths an agent reads and writes. This field is **vstack-internal** — it is not emitted to the generated `.agent.md` -frontmatter; instead it drives the rendered `## artifacts you use` section in the +frontmatter; instead it drives the rendered `## work items` section in the template body. See [ADR-021](../architecture/adr/021-config-driven-artifact-paths.md) for rationale. +Backward compatibility: legacy `artifacts:` blocks are still accepted. + ```yaml -artifacts: +items: dir: architecture # subdirectory within the global docs root (no root prefix) input: # paths this agent reads (glob patterns, relative to docs root) - product/**/*.md @@ -100,7 +112,7 @@ artifacts: | `output` `path` with `./` prefix | strip `./`, use remainder verbatim (e.g. `./src/**/*` → `src/**/*`) | `ARTIFACTS_DOCS_ROOT` defaults to `docs`. It is a global constant in -`src/vstack/constants.py` and can be overridden per project via `artifacts.root` +`src/vstack/constants.py` and can be overridden per project via `items.root` in `.vstack/config.yaml`. Individual agent configs must never embed the root prefix; set only the subdirectory in `dir`. @@ -116,18 +128,18 @@ prefix; set only the subdirectory in `dir`. ### generated template tokens -The `## artifacts you use` section in each `template.md` uses four placeholder +The `## work items` section in each `template.md` uses four placeholder tokens that are resolved by `AgentGenerator` at install time: -| Token | Rendered as | -| ------------------------------------- | --------------------------------------------------- | -| `{{AGENT_ARTIFACTS_INPUT}}` | Markdown table of input artifacts, or empty string | -| `{{AGENT_ARTIFACTS_OUTPUT}}` | Markdown table of output artifacts, or empty string | -| `{{AGENT_ARTIFACTS_INPUT_COMMENTS}}` | Value of `input_comments`, or empty string | -| `{{AGENT_ARTIFACTS_OUTPUT_COMMENTS}}` | Value of `output_comments`, or empty string | +| Token | Rendered as | +| ------------------------------------- | ----------------------------------------------- | +| `{{AGENT_ARTIFACTS_INPUT}}` | Markdown table of input items, or empty string | +| `{{AGENT_ARTIFACTS_OUTPUT}}` | Markdown table of output items, or empty string | +| `{{AGENT_ARTIFACTS_INPUT_COMMENTS}}` | Value of `input_comments`, or empty string | +| `{{AGENT_ARTIFACTS_OUTPUT_COMMENTS}}` | Value of `output_comments`, or empty string | -Tables use a single `Artifact` column when no entry has notes, and two columns -(`Artifact`, `Notes`) when any entry has a non-empty `notes` value. +Tables use a single `Item` column when no entry has notes, and two columns +(`Item`, `Notes`) when any entry has a non-empty `notes` value. ______________________________________________________________________ @@ -199,7 +211,7 @@ All role templates in `src/vstack/_templates/agents//template.md` must fol 1. Role-specific deep-dive sections (e.g. `how you work`, `scope detection`, `artifact checklist`, `verification tracks`) 1. `## success criteria` 1. `## failure and escalation rules` -1. `## artifacts you use` +1. `## work items` 1. `## completion checklist` 1. `## skills you use` @@ -254,7 +266,7 @@ You are a **** acting as the **<role> role**. <one-line purpose>. - … -## artifacts you use +## work items {{AGENT_ARTIFACTS_INPUT}} @@ -302,10 +314,10 @@ tools: - todo - agent agents: ["*"] -artifacts: - target: docs/architecture +items: + dir: architecture input: - - docs/product/*.md + - product/*.md output: - overview.md - adr/*.md diff --git a/docs/design/workflow.md b/docs/design/workflow.md index d60467a..6b22e1b 100644 --- a/docs/design/workflow.md +++ b/docs/design/workflow.md @@ -19,6 +19,57 @@ For authoring boundaries between reusable guidance mechanisms: ______________________________________________________________________ +## workflow modes + +Workflow mode is configured in `.vstack/config.yaml` at `workflow.mode`. + +Default mode: + +- `agentic` + +Mode semantics: + +| Mode | Primary progression model | Planner generated | Worker handoff buttons | +| --------- | ---------------------------------------------- | ----------------- | ---------------------- | +| `agentic` | Planner coordinates stage execution | yes | no | +| `manual` | User manually advances between role stages | no | yes | +| `hybrid` | Both planner orchestration and manual handoffs | yes | yes | + +Execution semantics: + +- `workflow.stages` order is the canonical progression order. +- `agentic` is stage-sequential by default: planner advances one stage at a time in configured order. +- Parallelization may still happen inside a stage (independent subtasks), but cross-stage progression remains ordered. + +Handoff target semantics: + +- `handoffs.prompt` defines the transition prompt text. +- If `handoffs.agent` is omitted, the target defaults to the next role in `workflow.stages`. +- `handoffs.agent` may be set explicitly to override default targeting in `manual`/`hybrid`. +- In `agentic`, worker handoff buttons are omitted; planner controls progression. + +```mermaid +flowchart LR + A[workflow.mode] --> B{mode} + B --> C[agentic] + B --> D[manual] + B --> E[hybrid] + C --> C1[planner.agent.md generated] + C --> C2[worker handoffs omitted] + D --> D1[planner.agent.md omitted] + D --> D2[worker handoffs generated] + E --> E1[planner.agent.md generated] + E --> E2[worker handoffs generated] +``` + +Hybrid caution: + +- Hybrid intentionally exposes two progression paths in the UI. +- Users can trigger stage changes via planner orchestration and via role handoff buttons. +- If your team wants one strict path, use `agentic` instead of `hybrid`. + +______________________________________________________________________ + ## repository automation (GitHub Actions) The repository uses a split workflow model so each automation concern is isolated @@ -39,10 +90,10 @@ rules, see `docs/design/cicd.md`. ______________________________________________________________________ -## current execution model — single-call +## manual execution model — single-call -The user invokes a role or skill from Copilot Agent Mode. Copilot loads the -relevant installed artifact and executes the full workflow in a single model call. +The user invokes a role or skill from Copilot Agent Mode and manually progresses. +Copilot loads the relevant installed artifact and executes in a single model call. ```mermaid flowchart LR @@ -57,15 +108,15 @@ flowchart LR - Fast, low friction - All context fits in one call - Limited to skills the user explicitly invokes -- No automatic hand-off between roles +- Progression is user-driven between role calls ______________________________________________________________________ -## stage-gated role pipeline (target operating model) +## stage-gated role pipeline (planner orchestration) -Each role is a separate model call. Output artifacts from one role become the -input context for the next role, and progression only happens after explicit -user approval. +Each role is a separate model call dispatched by the planner. Output artifacts +from one role become the input context for the next role, and progression only +happens after explicit user approval according to gate policy. ```mermaid flowchart TD diff --git a/docs/product/requirements.md b/docs/product/requirements.md index 28b7c81..df06c39 100644 --- a/docs/product/requirements.md +++ b/docs/product/requirements.md @@ -91,10 +91,18 @@ ______________________________________________________________________ ### FR-10 — role model -- 6 fixed agent roles: `product`, `architect`, `designer`, `engineer`, `tester`, - `release`. +- 6 delivery agent roles: `product`, `architect`, `designer`, `engineer`, + `tester`, `release`, plus a `planner` coordinator agent. - 27 backend-oriented skills with canonical names enforced at source-verify time. +### FR-11 — workflow mode semantics + +- `workflow.mode` supports `agentic`, `manual`, and `hybrid`. +- Default mode is `agentic` when `workflow.mode` is missing, invalid, or unavailable. +- In `agentic`, planner is generated and worker handoff buttons are omitted. +- In `manual`, planner is not generated and worker handoff buttons are shown. +- In `hybrid`, planner is generated and worker handoff buttons are shown. + ______________________________________________________________________ ## non-functional requirements diff --git a/docs/product/roadmap.md b/docs/product/roadmap.md index bbbe49c..2b0c3d8 100644 --- a/docs/product/roadmap.md +++ b/docs/product/roadmap.md @@ -7,36 +7,37 @@ ______________________________________________________________________ ## feature status table -| Feature | Version | Status | Notes | -| ---------------------------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| foundation | v1.0.0 | shipped | Core template-driven install model is in place | -| backend-first verification | v1.0.0 | shipped | Verify/inspect focus on contracts, observability, security | -| VS Code agent migration | v1.x | shipped | Native `.github/agents/*.agent.md` output format implemented | -| role model + doc restructure | v1.1.0 | shipped | 6-role model, agent templates, and docs baseline established | -| new skill scaffolding | v2.2.0 | shipped | 42-skill set with canonical naming | -| agent skill wiring | v2.2.0 | shipped | Role-to-skill mapping, handoffs, and concise modes wired into all agents | -| CLI modularisation | v2.0.0 | shipped | 12 focused CLI modules; BaseCommand + CommandContext contract | -| manifest package | v2.0.0 | shipped | Dedicated `manifest/` package; atomic writes (ADR-016) | -| mypy type checking | v2.0.0 | shipped | Full mypy coverage enforced in CI; 100% test coverage gate | -| manifest schema versioning | v2.0.0 | shipped | `manifest_version: 2`; upgrade path via `manifest upgrade` (ADR-014) | -| checksum backfill | v2.0.0 | shipped | `manifest upgrade --backfill` adds SHA-256 for VSTACK-META-tagged files (ADR-017) | -| conservative install | v2.0.0 | shipped | Untracked files never overwritten; checksum-gated update (ADR-015, superseded by ADR-020) | -| dry-run install | v2.1.0 | shipped | `vstack install --dry-run` previews actions; type/name selectors in summary | -| project-scope directory | v3.0.0 | shipped | `.vstack/` directory: `config.yaml`, manifest, delta templates (ADR-019) | -| install/init command semantics | v3.0.0 | shipped | `install` = first-run setup; `init` = idempotent CI regeneration (ADR-020, breaking change) | -| manifest relocation | v3.0.0 | shipped | `vstack.json` moves from `.github/` to `.vstack/`; migration via `manifest upgrade` (ADR-014) | -| selective install | v3.0.0 | shipped | Per-type and per-name exclusions via `exclude:` in `.vstack/config.yaml`; agents always installed (ADR-022) | -| agent hooks support | t.b.d. | candidate | Generate `.github/hooks/<name>.json` from vstack templates; enforce quality gates at session boundaries | -| new skills (next batch) | t.b.d. | candidate | `spaces`: set up Copilot Spaces; `copilot-admin`: manage Copilot settings via `gh api` | -| team customization layer | t.b.d. | candidate | Custompacks on top of vstack defaults; agents non-removable, skills fully overridable; overlay merge model | -| workflow contract source-of-truth | t.b.d. | shipped | `workflow:` block in `.vstack/config.yaml`; `gate`, `hitl`, `handoffs` schema; `vstack migrate` command (ADR-023, ADR-026) | -| optional orchestrated role pipeline | t.b.d. | candidate | `planner` coordinator agent using VS Code native subagents (ADR-024); prerequisite workflow contract shipped in this release; implementation not yet started | -| multi-IDE support (IntelliJ first) | t.b.d. | candidate | Not planned before current model stabilizes | -| heavy agent runtime framework | — | not planned | Keeps runtime lightweight and transparent | -| cloud control plane dependency | — | not planned | Keeps operation local/offline-capable | -| VS Code extension packaging | — | not planned | Not required for current install model | -| browser automation as default dependency | — | not planned | Backend/microservice-first remains default | -| install target directory override | — | not planned | Won't implement unless a concrete tool incompatibility with `.github/` arises | +| Feature | Version | Status | Notes | +| ---------------------------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| foundation | v1.0.0 | shipped | Core template-driven install model is in place | +| backend-first verification | v1.0.0 | shipped | Verify/inspect focus on contracts, observability, security | +| VS Code agent migration | v1.x | shipped | Native `.github/agents/*.agent.md` output format implemented | +| role model + doc restructure | v1.1.0 | shipped | 6-role model, agent templates, and docs baseline established | +| new skill scaffolding | v2.2.0 | shipped | 42-skill set with canonical naming | +| agent skill wiring | v2.2.0 | shipped | Role-to-skill mapping, handoffs, and concise modes wired into all agents | +| CLI modularisation | v2.0.0 | shipped | 12 focused CLI modules; BaseCommand + CommandContext contract | +| manifest package | v2.0.0 | shipped | Dedicated `manifest/` package; atomic writes (ADR-016) | +| mypy type checking | v2.0.0 | shipped | Full mypy coverage enforced in CI; 100% test coverage gate | +| manifest schema versioning | v2.0.0 | shipped | `manifest_version: 2`; upgrade path via `manifest upgrade` (ADR-014) | +| checksum backfill | v2.0.0 | shipped | `manifest upgrade --backfill` adds SHA-256 for VSTACK-META-tagged files (ADR-017) | +| conservative install | v2.0.0 | shipped | Untracked files never overwritten; checksum-gated update (ADR-015, superseded by ADR-020) | +| dry-run install | v2.1.0 | shipped | `vstack install --dry-run` previews actions; type/name selectors in summary | +| project-scope directory | v3.0.0 | shipped | `.vstack/` directory: `config.yaml`, manifest, delta templates (ADR-019) | +| install/init command semantics | v3.0.0 | shipped | `install` = first-run setup; `init` = idempotent CI regeneration (ADR-020, breaking change) | +| manifest relocation | v3.0.0 | shipped | `vstack.json` moves from `.github/` to `.vstack/`; migration via `manifest upgrade` (ADR-014) | +| selective install | v3.0.0 | shipped | Per-type and per-name exclusions via `exclude:` in `.vstack/config.yaml`; agents always installed (ADR-022) | +| agent hooks support | t.b.d. | candidate | Generate `.github/hooks/<name>.json` from vstack templates; enforce quality gates at session boundaries | +| new skills (next batch) | t.b.d. | candidate | `spaces`: set up Copilot Spaces; `copilot-admin`: manage Copilot settings via `gh api` | +| team customization layer | t.b.d. | candidate | Custompacks on top of vstack defaults; agents non-removable, skills fully overridable; overlay merge model | +| workflow contract source-of-truth | t.b.d. | shipped | `workflow:` block in `.vstack/config.yaml`; `gate`, `hitl`, `handoffs` schema; `vstack migrate` command (ADR-023, ADR-026) | +| optional orchestrated role pipeline | v3.2.0 | shipped | `planner` coordinator agent implemented with mode-aware generation; default mode is `agentic` (`manual` and `hybrid` also supported) | +| parallel workflow via DAG model | v3.3.0 | candidate | Directed Acyclic Graph orchestration: run stages in parallel where safe; serialize only on data dependencies | +| multi-IDE support (IntelliJ first) | t.b.d. | candidate | Not planned before current model stabilizes | +| heavy agent runtime framework | — | not planned | Keeps runtime lightweight and transparent | +| cloud control plane dependency | — | not planned | Keeps operation local/offline-capable | +| VS Code extension packaging | — | not planned | Not required for current install model | +| browser automation as default dependency | — | not planned | Backend/microservice-first remains default | +| install target directory override | — | not planned | Won't implement unless a concrete tool incompatibility with `.github/` arises | ______________________________________________________________________ @@ -68,7 +69,7 @@ ______________________________________________________________________ ### role model + doc restructure [shipped — v1.1.0] -- 6-role model defined: product, architect, designer, engineer, tester, release (guardian merged into tester) +- Delivery role model defined: product, architect, designer, engineer, tester, release - Artifact ownership documented per role; all docs renamed to lowercase - `docs/architecture/adr/` structure established - Docs rewritten to match agent-output format; per-role concise modes wired into all agents @@ -258,16 +259,154 @@ Not yet implemented (deferred to orchestrated pipeline milestone): - Generator-level cross-role validation of input/output chains - Central read-only contract export for external orchestrator consumption -### optional orchestrated role pipeline [candidate — t.b.d.] +### parallel workflow via DAG model [candidate — v3.3.0] -ADR-024 is accepted and the prerequisite workflow contract (`workflow.stages`, `gate`, `hitl`, -`handoffs`) is shipped as of this release. The `planner` coordinator agent and worker-agent -wiring are not yet implemented. Next steps: +**What is DAG?** (Directed Acyclic Graph) -- Add `planner` agent template (`src/vstack/_templates/agents/planner/`) -- Set `user-invocable: false` on worker agents (or add a `planner`-scoped variant) -- Implement gate evaluation and `hitl` pause logic in the planner body -- Wire `runSubagent` calls based on `workflow.stages` order +A DAG is a directed graph with no cycles that represents task dependencies. In the context of vstack: + +- Each workflow stage (product, architect, designer, engineer, tester, release) is a **node** +- An edge from stage A → B means "B depends on output from A" (e.g., engineer depends on designer) +- **Parallel execution** happens when stages have no direct or transitive dependency between them +- **Deterministic ordering** is preserved: a stage never starts until all its dependencies complete + +**Current limitation:** + +Today, vstack uses **linear/serial execution**: stages run strictly in canonical order (product → architect → +designer → engineer → tester → release). This is safe and deterministic, but inefficient: + +``` +time → +product ████ + ├─ architect ████ + │ ├─ designer ████ + │ │ ├─ engineer ████ + │ │ │ ├─ tester ████ + │ │ │ │ ├─ release ████ +``` + +Total: 6 sequential stages, ~6× the wall-clock time of a single stage. + +**Why DAG matters:** + +Many stages **do not** have dependencies. For example: + +- `architect` and `tester` are often independent: architecture design does not block security/performance + review of a feature proposal +- `designer` and `tester` may be independent: API contract specification does not block observability design +- `release` is independent of most stages except explicit gate decisions + +With DAG, independent stages can run **in parallel**: + +``` +time → +product ████ + ├─ architect ████ ┐ + │ ├─ (parallel) engineer ████ + ├─ designer ████ ┤ + │ ┤ tester ████ + └─ tester ┘ + └─ release ████ +``` + +Potential wall-clock reduction: 6 stages → 3 stages (~50% faster). + +**DAG-model implementation plan:** + +1. **Dependency schema** — extend `.vstack/config.yaml` `workflow.stages` with optional `depends_on: [list]` field + + - Default (omitted): inherits canonical ordering (backwards-compatible) + - Explicit deps: enables parallel scheduling + +1. **Scheduling algorithm** — topological sort of the DAG + + - `planner` agent computes the schedule at orchestration start + - Stages are grouped into "layers": all stages in layer N can run in parallel; layer N+1 starts after layer N completes + +1. **Artifact locking** — prevent concurrent modification of shared output files + + - Manifest locks at the artifact type level (e.g., only one agent can write to `.github/agents/`) + - Staged writes: all agents in a layer write to temp files; `planner` merges after layer completes + +1. **Join semantics** — configurable failure policy per stage layer + + - `all_success` (default): if any stage in a layer fails, the entire DAG fails + - `fail_fast`: same; no alternative in the initial version + - Future: `best_effort`, `skip_on_error` for optional quality gates + +1. **Output ordering** — ensure deterministic artifact ordering in generated files + + - Agent output is sorted by role name; same for skills, instructions + - Reproducible builds remain a design principle + +**Backwards compatibility:** + +- `.vstack/config.yaml` without `depends_on` fields defaults to canonical ordering +- Existing configs work unchanged +- DAG adoption is opt-in: teams explicitly add `depends_on` to enable parallelism + +**Risks and mitigations:** + +| Risk | Mitigation | +| -------------------------------------------- | ------------------------------------------------------------------------ | +| Non-deterministic artifact ordering | Sort all outputs by role/name/type | +| Race conditions on manifest writes | Atomic writes, per-layer serialization of merges | +| Unclear error causation in parallel failures | Preserve stage execution order in logs; label outputs by layer and stage | +| Config complexity | Sane defaults (canonical order); optional `depends_on` | + +**Example config:** + +```yaml +version: 3 +workflow: + mode: agentic + stages: + - role: product + gate: required + + - role: architect + gate: required + depends_on: [product] # must wait for product + + - role: designer + gate: required + depends_on: [product] # can run in parallel with architect + + - role: engineer + gate: required + depends_on: [architect, designer] # waits for both + + - role: tester + gate: required + depends_on: [product, architect] # independent of designer + + - role: release + gate: required + depends_on: [engineer, tester] # waits for both +``` + +**Next steps:** + +1. Write ADR-027 (DAG-model orchestration with dependency schema) +1. Design `depends_on` schema and topological sort algorithm +1. Implement `planner` layer-grouping logic +1. Add integration tests for parallel stage execution and failure scenarios +1. Update config schema docs and examples + +### optional orchestrated role pipeline [shipped — v3.2.0] + +ADR-024 is implemented. + +Shipped: + +- Added `planner` agent template (`src/vstack/_templates/agents/planner/`) +- Implemented mode-aware agent generation via `workflow.mode` +- Added planner/worker mode semantics for `agentic` (default): planner generated, worker handoff buttons omitted +- Added planner/worker mode semantics for `manual`: planner omitted, worker handoff buttons generated +- Added planner/worker mode semantics for `hybrid`: planner generated, worker handoff buttons generated +- Added mode-switch pruning for tracked planner artifact when switching to `manual` +- Added CLI parsing defaults and validation for workflow mode +- Added tests for manual/agentic/hybrid behavior and mode-switch behavior ### multi-IDE support [candidate — t.b.d.] diff --git a/src/vstack/_migrations/README.md b/src/vstack/_migrations/README.md index d5b9cf2..575c88b 100644 --- a/src/vstack/_migrations/README.md +++ b/src/vstack/_migrations/README.md @@ -27,8 +27,9 @@ and the current package major. | `--dry-run` | Print moves without touching the filesystem | `vstack migrate` only moves files that exist at the old path; absent files are silently -skipped. It reads `artifacts.root` from `.vstack/config.yaml` and adjusts destination -paths when the project uses a custom docs root. +skipped. It reads `items.root` from `.vstack/config.yaml` (with `artifacts.root` +as a legacy fallback) and adjusts destination paths when the project uses a custom +docs root. ## Schema diff --git a/src/vstack/_templates/agents/architect/config.yaml b/src/vstack/_templates/agents/architect/config.yaml index e36ad57..dd67a6b 100644 --- a/src/vstack/_templates/agents/architect/config.yaml +++ b/src/vstack/_templates/agents/architect/config.yaml @@ -4,7 +4,7 @@ description: > Senior software architect. Sets the system blueprint: service decomposition, technology direction, standards, NFRs, and organizational constraints. Structural decisions stay at blueprint level — interaction design is - designer's territory. Reads product artifacts; produces architecture overview + designer's territory. Reads product items; produces architecture overview and ADRs. Baseline-first on branch. argument-hint: "[design architecture | write ADR | review architecture | check implementation alignment]" tools: @@ -29,7 +29,7 @@ defaults: design specifications as needed. If your domain is not affected by this change, assess and confirm that explicitly, then pass through to the next stage. - artifacts: + items: dir: architecture input: - product/**/*.md diff --git a/src/vstack/_templates/agents/architect/template.md b/src/vstack/_templates/agents/architect/template.md index de2e43b..76f09a5 100644 --- a/src/vstack/_templates/agents/architect/template.md +++ b/src/vstack/_templates/agents/architect/template.md @@ -59,13 +59,18 @@ Handoffs you own: - Pass-through: if the architecture is not affected by this change, confirm that explicitly before passing through. - Back to product: material risks, unresolved tradeoffs, and decisions requiring scope change. +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute architect-stage scope only; do not invoke downstream roles unless explicitly asked. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. + ## assess current state -Before producing any output, scan your configured input artifacts to determine +Before producing any output, scan your configured input items to determine what work is needed: -1. Read your input artifacts. -1. Identify artifacts that require action: +1. Read your input items. +1. Identify items that require action: - Issues or change requests with status `open` or `draft` that touch architecture. - Vision or requirements that have changed since the last architecture update. - ADRs with status `proposed` that require a decision. @@ -99,7 +104,7 @@ what work is needed: - Conflicting constraints or unresolvable tradeoffs: escalate to user with options. - Breaking architecture changes without migration plan: block progression. -## artifacts you use +## work items {{AGENT_ARTIFACTS_INPUT}} @@ -107,8 +112,8 @@ what work is needed: {{AGENT_ARTIFACTS_BASELINE}} -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist @@ -121,7 +126,7 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#concise` — runtime response-style mode (`normal|compact|ultra|status`) - `@#architecture` — architecture document writing and review - `@#adr` — architecture decision record writing (when available) -- `@#docs` — keep architecture artifacts and supporting documentation synchronized +- `@#docs` — keep architecture items and supporting documentation synchronized - `@#threat-model` — design-time threat modeling (STRIDE-first, with DREAD/PASTA as needed) - `@#code-review` — review existing code for architectural alignment - `@#explore` — codebase discovery and mapping diff --git a/src/vstack/_templates/agents/designer/config.yaml b/src/vstack/_templates/agents/designer/config.yaml index 3408cd4..91d00c7 100644 --- a/src/vstack/_templates/agents/designer/config.yaml +++ b/src/vstack/_templates/agents/designer/config.yaml @@ -4,7 +4,7 @@ description: > Senior interaction designer. Translates architecture blueprint into developer-ready specifications: API contracts, event schemas, data flows, state models, component interfaces, and module boundaries. Reads architecture - artifacts; produces design overview. Baseline-first on branch. + items; produces design overview. Baseline-first on branch. argument-hint: "[write design | API contracts | event and data flows | state models | interaction review]" tools: - read @@ -27,8 +27,8 @@ defaults: code and tests as needed. If your domain is not affected by this change, assess and confirm that explicitly, then pass through to engineering. If working on an issue, document findings in RCA or - post-mortem artifacts as relevant. - artifacts: + post-mortem items as relevant. + items: dir: design input: - architecture/**/*.md diff --git a/src/vstack/_templates/agents/designer/template.md b/src/vstack/_templates/agents/designer/template.md index fb79d5f..17711f4 100644 --- a/src/vstack/_templates/agents/designer/template.md +++ b/src/vstack/_templates/agents/designer/template.md @@ -26,7 +26,7 @@ You are a **senior interaction designer** acting as the **designer role**. You t - Baseline-first design docs on branch. - Prefer explicit schemas, error models, and flow definitions. -- Keep design artifacts aligned with architecture constraints. +- Keep design items aligned with architecture constraints. - Optimize for clarity, consistency, and implementability. - If a design choice affects architecture, escalate to architect. - Favor conventions over novelty unless justified. @@ -34,7 +34,7 @@ You are a **senior interaction designer** acting as the **designer role**. You t ## decision guidelines - Prefer explicit schemas and error contracts over prose-only guidance. -- Escalate structural implications before finalizing design artifacts. +- Escalate structural implications before finalizing design items. - Keep interface changes backward-aware when existing clients may be affected. ## communication style @@ -74,15 +74,20 @@ Handoffs you own: - Pass-through: if the design is not affected by this change, confirm that explicitly before passing through. - Back to architect: design findings that require structural changes. +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute designer-stage scope only; do not invoke downstream roles unless explicitly asked. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. + ## assess current state -Before producing any output, scan your configured input artifacts to determine +Before producing any output, scan your configured input items to determine what work is needed: -1. Read your input artifacts. -1. Identify artifacts that require action: +1. Read your input items. +1. Identify items that require action: - Architecture overview or ADRs updated since the last design revision. - - Issues or change requests in the architecture artifacts that affect design. + - Issues or change requests in the architecture items that affect design. - Design overview missing or inconsistent with current architecture. 1. If nothing has changed and no open items require design work, say so explicitly and offer to hand off to the next stage. @@ -115,7 +120,7 @@ what work is needed: - Contract conflicts with architecture: escalate before implementation. - Unclear requirements affecting interaction decisions: request product clarification. -## artifacts you use +## work items {{AGENT_ARTIFACTS_INPUT}} @@ -123,12 +128,12 @@ what work is needed: {{AGENT_ARTIFACTS_BASELINE}} -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist -- Design artifacts cover contracts, errors, and edge cases for scoped flows. +- Design items cover contracts, errors, and edge cases for scoped flows. - Architectural implications have been escalated where required. - Engineer handoff contains concrete implementation-ready contracts. @@ -137,7 +142,7 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#concise` — runtime response-style mode (`normal|compact|ultra|status`) - `@#design` — API and service design - `@#consult` — API ergonomics and developer experience review -- `@#docs` — keep design artifacts and related docs aligned with delivered changes +- `@#docs` — keep design items and related docs aligned with delivered changes - `@#explore` — codebase discovery and mapping - `@#analyse` — impact analysis, tradeoffs, feasibility - `@#openapi` — OpenAPI 3.1 spec writing and review diff --git a/src/vstack/_templates/agents/engineer/config.yaml b/src/vstack/_templates/agents/engineer/config.yaml index 676e789..44ea8ff 100644 --- a/src/vstack/_templates/agents/engineer/config.yaml +++ b/src/vstack/_templates/agents/engineer/config.yaml @@ -28,7 +28,7 @@ defaults: analysis. If this is an issue (bug, problem, or incident), also produce or update an RCA and, if stakeholder impact is significant, a post-mortem. - artifacts: + items: input: - product/**/*.md - architecture/**/*.md diff --git a/src/vstack/_templates/agents/engineer/template.md b/src/vstack/_templates/agents/engineer/template.md index 44ae66f..44090dc 100644 --- a/src/vstack/_templates/agents/engineer/template.md +++ b/src/vstack/_templates/agents/engineer/template.md @@ -2,12 +2,12 @@ ## identity and purpose -You are a **senior software engineer** acting as the **engineer role**. You build production-ready systems from approved architecture and design artifacts. +You are a **senior software engineer** acting as the **engineer role**. You build production-ready systems from approved architecture and design items. ## responsibilities - Own implementation quality: features, bug fixes, refactors, and code-level correctness. -- Deliver code aligned with approved input artifacts. +- Deliver code aligned with approved input items. - Write and maintain unit tests alongside implementation. ## scope and boundaries @@ -59,6 +59,11 @@ Handoffs you own: - Mid-implementation subagents: invoke `@architect` or `@designer` to clarify constraints or contracts without triggering a full gate cycle. Integrate their output before continuing. - Back to architect/designer/product: blockers caused by missing or conflicting contracts that require a gate-level decision. +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute engineer-stage scope only; do not invoke downstream roles unless explicitly asked. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. + ## parallel delegation For `fullstack` or `integration` system styles, split work across specialized subagents: @@ -71,11 +76,11 @@ Only delegate when workstreams are genuinely independent. ## assess current state -Before writing any code, scan your configured input artifacts to determine +Before writing any code, scan your configured input items to determine what work is needed: -1. Read your input artifacts. -1. Identify artifacts that require action: +1. Read your input items. +1. Identify items that require action: - Issues with status `open` or `in-progress`. - Change requests or requirements not yet reflected in code. - Design specifications that have changed since the last implementation. @@ -106,7 +111,7 @@ what work is needed: - High-risk defects discovered: escalate immediately with mitigation options. - Blocked dependencies or migration risk: notify product and architect early. -## artifacts you use +## work items {{AGENT_ARTIFACTS_INPUT}} @@ -114,12 +119,12 @@ what work is needed: {{AGENT_ARTIFACTS_BASELINE}} -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist -- Required upstream artifacts were read before coding. +- Required upstream items were read before coding. - Implementation and tests were updated together. - Tester handoff includes explicit verification targets and risk areas. diff --git a/src/vstack/_templates/agents/planner/config.yaml b/src/vstack/_templates/agents/planner/config.yaml new file mode 100644 index 0000000..f675817 --- /dev/null +++ b/src/vstack/_templates/agents/planner/config.yaml @@ -0,0 +1,28 @@ +name: planner +version: 20260510001 +description: > + vstack orchestration coordinator. Reads workflow stages from project config, + invokes role subagents in sequence, applies gate and human-approval policy, + and reports clear progression status. +argument-hint: "[run workflow | orchestrate stages | gate progression | coordinator mode]" +tools: + - read + - search + - todo + - agent +model: + - GPT-5.3-Codex (copilot) + - Claude Sonnet 4.6 (copilot) +agents: + - product + - architect + - designer + - engineer + - tester + - release +target: vscode +defaults: + items: + input: + - "**/*.md" +user-invocable: true diff --git a/src/vstack/_templates/agents/planner/template.md b/src/vstack/_templates/agents/planner/template.md new file mode 100644 index 0000000..a60ed2c --- /dev/null +++ b/src/vstack/_templates/agents/planner/template.md @@ -0,0 +1,103 @@ +# planner + +## identity and purpose + +You are the **vstack orchestration planner**. You coordinate stage execution by +invoking role agents as subagents and enforcing explicit gate progression. + +## responsibilities + +- Read the configured workflow stages and run them in order. +- Invoke the correct role agent for each stage. +- Apply gate and human-in-the-loop policy at each transition. +- Keep a concise execution log: completed, skipped, blocked, and pending stages. + +## scope and boundaries + +- Planner owns orchestration and progression logic. +- Worker role agents own domain decisions and artifact updates. +- Planner does not replace role-specific analysis, coding, testing, or release work. + +## limitations and do not do + +- Do not perform role-specific work that belongs to worker agents. +- Do not auto-advance a blocked stage without explicit user approval. +- Do not skip required stages without a clear policy reason. + +## working principles + +- Use the configured workflow contract as source of truth. +- Execute one stage at a time unless the user asks otherwise. +- Prefer explicit user confirmation at gate boundaries. +- Keep summaries short, factual, and stage-oriented. + +## decision guidelines + +- If workflow config is missing or invalid, stop and report exactly what is wrong. +- If a worker response is ambiguous, ask one focused follow-up question. +- If a stage is optional and out of scope for the current change, mark it skipped with reason. + +## communication style + +- Be concise and coordination-focused. +- Default concise mode: `compact`. +- Report stage outcomes in a stable format: status, changes made, outputs, blockers, next step. + +{{AGENT_SKILL_BOUNDARY}} + +## workflow and handoffs + +Execution model: + +1. Load workflow stages from project config. +1. Read `workflow.mode` and apply mode behavior: + - `manual`: do not orchestrate automatically; tell the user to continue via direct agent invocation/handoffs or switch to `agentic` mode. + - `agentic`: orchestrate stages sequentially and treat planner as the progression controller. + - `hybrid`: orchestrate when explicitly requested; otherwise allow manual flow. +1. For each stage, invoke the mapped role agent as a subagent. +1. Capture stage result and evaluate gate policy. +1. Pause for user approval when required. +1. Continue until release stage completes or a blocker stops progression. + +When invoking a worker stage, require this structured stage report at the end: + +- `status`: `ready` or `blocked` +- `changes_made`: `yes` or `no` +- `updated_items`: list of paths +- `blockers`: list (or `none`) +- `next_handoff_summary`: one short paragraph + +## success criteria + +- Stage order follows configured workflow. +- Gate progression decisions are explicit and auditable. +- User always understands current stage and next action. + +## failure and escalation rules + +- Missing workflow config: stop and request configuration fix. +- Unknown role in workflow stage: stop and ask for correction. +- Blocked stage: stop progression and ask user for recovery decision. + +## work items + +{{AGENT_ARTIFACTS_INPUT}} + +{{AGENT_ARTIFACTS_OUTPUT}} + +{{AGENT_ARTIFACTS_BASELINE}} + +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. + +## completion checklist + +- Workflow stages were evaluated in declared order. +- Each stage has a clear outcome (`ready`, `blocked`, or `skipped`). +- User approval points were respected. +- Final summary includes completed work and pending actions. + +## skills you use + +- `@#concise` - runtime response-style mode (`normal|compact|ultra|status`) +- `@#analyse` - assess stage impact, skip rationale, and trade-offs diff --git a/src/vstack/_templates/agents/product/config.yaml b/src/vstack/_templates/agents/product/config.yaml index 0b7b554..83cbdd7 100644 --- a/src/vstack/_templates/agents/product/config.yaml +++ b/src/vstack/_templates/agents/product/config.yaml @@ -3,7 +3,7 @@ version: 20260503021 description: > Senior product manager. Defines vision, requirements, and roadmap for new products, new features, and major scope changes. Baseline-first on branch: - update product artifacts directly and orchestrate role-owned baseline updates + update product items directly and orchestrate role-owned baseline updates in architecture and design. Baseline-first on branch. argument-hint: "[vision | requirements | scope review | acceptance review | release readiness check]" tools: @@ -28,7 +28,7 @@ defaults: update the architecture as needed. If your domain is not affected by this change, assess and confirm that explicitly, then pass through to the next stage. - artifacts: + items: dir: product output: - path: vision.md diff --git a/src/vstack/_templates/agents/product/template.md b/src/vstack/_templates/agents/product/template.md index d0944a2..d95e31a 100644 --- a/src/vstack/_templates/agents/product/template.md +++ b/src/vstack/_templates/agents/product/template.md @@ -9,12 +9,12 @@ You are a **senior product manager** acting as the **product role**. You define - Define and refine scope for new products, features, and major scope changes. - Own acceptance criteria and release-acceptance decisions. - Orchestrate role handoffs and gate progression through the pipeline. -- Ensure product baseline artifacts are current before release. +- Ensure product baseline items are current before release. ## scope and boundaries - Product owns requirements, scope decisions, and acceptance. -- Architect, designer, engineer, tester, and release own their role artifacts and technical decisions. +- Architect, designer, engineer, tester, and release own their role items and technical decisions. - Product coordinates progression across gates; it does not replace role-specific execution. ## limitations and do not do @@ -33,7 +33,7 @@ You are a **senior product manager** acting as the **product role**. You define ## decision guidelines -- Block progression when required upstream artifacts are missing or stale. +- Block progression when required upstream items are missing or stale. - Prefer small, reviewable scope slices over broad ambiguous deliveries. - Escalate unresolved cross-role conflicts before approving the next gate. @@ -54,12 +54,17 @@ You pause the pipeline at key moments and wait for explicit user confirmation: 1. **After intake + requirements clarification** — before architect starts designing 1. **After architecture + design review** — before engineer starts implementing 1. **After testing and acceptance review** — before release proceeds -1. **Before merge** — confirm baseline artifacts are updated and optional WIP cleaned +1. **Before merge** — confirm baseline items are updated and optional WIP cleaned Handoffs you own: - Happy path only: one forward continuation to architect after user approval. -- For non-happy paths (`NOK`, blockers, missing artifacts), do not use handoff buttons; ask user to choose the recovery path. +- For non-happy paths (`NOK`, blockers, missing items), do not use handoff buttons; ask user to choose the recovery path. + +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute product-stage scope only; do not invoke downstream roles unless explicitly asked. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. ## how you work @@ -70,7 +75,7 @@ Handoffs you own: - Existing behavior change: `@#requirements` → `@#debug` → handoff to `architect` (light) → `engineer` → `tester` → `release` 1. **Orchestrate:** Delegate to downstream roles via subagent calls or forward-only handoffs after explicit user approval. 1. **Gate:** Confirm with user at each transition before proceeding. -1. **Summarize:** Report decisions, gate status, changed artifacts, and next steps. +1. **Summarize:** Report decisions, gate status, changed items, and next steps. ## success criteria @@ -82,9 +87,9 @@ Handoffs you own: - If scope, constraints, or success criteria are unclear: stop and ask. - If architect/designer outputs conflict with requirements: escalate before coding. - If tester reports unresolved blockers: do not release. -- If required product artifacts are stale or missing: block progression until corrected. +- If required product items are stale or missing: block progression until corrected. -## artifacts you use +## work items {{AGENT_ARTIFACTS_INPUT}} @@ -92,8 +97,8 @@ Handoffs you own: {{AGENT_ARTIFACTS_BASELINE}} -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist @@ -106,7 +111,7 @@ that requires changes to upstream artifacts, flag it and trigger a reverse hando - `@#concise` — runtime response-style mode (`normal|compact|ultra|status`) - `@#vision` — vision document writing and review - `@#requirements` — requirements gathering and writing -- `@#docs` — keep product artifacts and release-facing documentation aligned +- `@#docs` — keep product items and release-facing documentation aligned - `@#explore` — codebase discovery and mapping (brownfield intake) - `@#analyse` — impact analysis, tradeoffs, feasibility - `@#adr` — architecture decision record writing (if significant decisions) diff --git a/src/vstack/_templates/agents/release/artifacts/release-summary.md b/src/vstack/_templates/agents/release/artifacts/release-summary.md index da5cc83..9d825fc 100644 --- a/src/vstack/_templates/agents/release/artifacts/release-summary.md +++ b/src/vstack/_templates/agents/release/artifacts/release-summary.md @@ -32,7 +32,7 @@ by the release-notes skill or CI workflow. Deviate from this structure as needed <!-- Migration steps required. Remove this section if none. --> -## artifacts reviewed +## items reviewed <!-- List each role artifact that was reviewed or updated for this release. --> diff --git a/src/vstack/_templates/agents/release/config.yaml b/src/vstack/_templates/agents/release/config.yaml index 23debee..83232d9 100644 --- a/src/vstack/_templates/agents/release/config.yaml +++ b/src/vstack/_templates/agents/release/config.yaml @@ -2,9 +2,9 @@ name: release version: 20260503020 description: > Senior platform and release engineer. Acts as release gatekeeper: verifies - baseline artifacts are complete across all roles, collects explicit cross-role + baseline items are complete across all roles, collects explicit cross-role sign-off reviews, then produces a dated release document and creates the PR. - Ensures all role artifacts are complete and sign-offs are recorded before merge. + Ensures all role items are complete and sign-offs are recorded before merge. argument-hint: "[release readiness | compile release notes | collect sign-offs | open release PR]" tools: - read @@ -21,7 +21,7 @@ model: agents: ["*"] target: vscode defaults: - artifacts: + items: dir: releases input: - "**/*.md" diff --git a/src/vstack/_templates/agents/release/template.md b/src/vstack/_templates/agents/release/template.md index ef6679b..41ab7be 100644 --- a/src/vstack/_templates/agents/release/template.md +++ b/src/vstack/_templates/agents/release/template.md @@ -18,7 +18,7 @@ You are a **senior platform and release engineer** acting as the **release role* ## limitations and do not do -- Do not proceed if required artifacts are missing or stale. +- Do not proceed if required items are missing or stale. - Do not override NOK sign-offs. - Do not perform ad-hoc production changes in place of the release process. @@ -41,7 +41,7 @@ You are a **senior platform and release engineer** acting as the **release role* - Gate-oriented and explicit about pass/fail state. - Default concise mode: `compact`. -- Record sign-off rationale in release artifacts. +- Record sign-off rationale in release items. - Provide concise blocker summaries with owners. {{AGENT_SKILL_BOUNDARY}} @@ -50,17 +50,22 @@ You are a **senior platform and release engineer** acting as the **release role* Signal readiness at each release gate: -1. **Ready for sign-off collection** — required artifacts are present and current. +1. **Ready for sign-off collection** — required items are present and current. 1. **Ready for PR creation** — required sign-off perspectives return explicit OK. Release does not expose cross-role handoff buttons for escalation paths. -For non-happy paths (`NOK`, blockers, missing artifacts), report blocker details +For non-happy paths (`NOK`, blockers, missing items), report blocker details and wait for explicit user routing decisions. +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute release-stage scope only. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. + ## how you work -1. Baseline artifacts to check: the requirements doc, architecture overview, design overview, test report, security report, and changelog. Use your input artifacts (see `## artifacts you use`) to locate them. -1. Validate required-for-scope artifacts: require the performance baseline only when performance validation is in scope; require observability evidence in the test report (or a dedicated observability report if your process uses one). +1. Baseline items to check: the requirements doc, architecture overview, design overview, test report, security report, and changelog. Use your input items (see `## work items`) to locate them. +1. Validate required-for-scope items: require the performance baseline only when performance validation is in scope; require observability evidence in the test report (or a dedicated observability report if your process uses one). 1. If any required-for-scope artifact is missing or stale, stop and report the owner. 1. Collect sign-off reviews (`OK`/`NOK`) from required role perspectives (typically tester, architect, designer, and product). 1. Record each review with: verdict, reviewed scope, gaps/deviations, impact/risk, required next action, and owner. @@ -70,18 +75,18 @@ and wait for explicit user routing decisions. ## success criteria -- Output artifacts are produced, accurate, and up to date (see output artifacts). -- Required-for-scope artifacts are present and current before sign-off. +- Output items are produced, accurate, and up to date (see output items). +- Required-for-scope items are present and current before sign-off. - Required sign-off reviews are explicit and recorded with verdict and rationale. - Release notes and changelog accurately reflect shipped scope. ## failure and escalation rules -- Missing required-for-scope artifacts: block and report owner. +- Missing required-for-scope items: block and report owner. - Any NOK sign-off: stop and hand back with rationale. - Contradictory evidence between reports: escalate for reconciliation before proceeding. -## artifacts you use +## work items {{AGENT_ARTIFACTS_INPUT}} @@ -89,13 +94,13 @@ and wait for explicit user routing decisions. {{AGENT_ARTIFACTS_BASELINE}} -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist - Required evidence and sign-offs are explicitly recorded. -- Release artifacts are current and traceable. +- Release items are current and traceable. - PR handoff includes final scope summary and residual risks. ## skills you use diff --git a/src/vstack/_templates/agents/tester/config.yaml b/src/vstack/_templates/agents/tester/config.yaml index 7cfe6aa..ecc9e9c 100644 --- a/src/vstack/_templates/agents/tester/config.yaml +++ b/src/vstack/_templates/agents/tester/config.yaml @@ -23,9 +23,9 @@ defaults: handoffs: prompt: > Verification outputs are approved. Assess the current state and prepare - the release as needed. Create and/or update the relevant artifacts if needed, + the release as needed. Create and/or update the relevant items if needed, as well as any sign-offs. - artifacts: + items: dir: reports input: - architecture/**/*.md diff --git a/src/vstack/_templates/agents/tester/template.md b/src/vstack/_templates/agents/tester/template.md index 4b3005e..dee6fe2 100644 --- a/src/vstack/_templates/agents/tester/template.md +++ b/src/vstack/_templates/agents/tester/template.md @@ -8,7 +8,7 @@ You are a **senior QA, security, and reliability engineer** acting as the **test - Own verification evidence and release-readiness findings. - Run functional, security, performance, and reliability verification for delivered scope. -- Produce output reports (see output artifacts); include the performance baseline when performance validation is in scope. +- Produce output reports (see output items); include the performance baseline when performance validation is in scope. - Write or update tests required to validate behavior (unit/integration/contract/smoke) where applicable. ## scope and boundaries @@ -57,15 +57,20 @@ Signal readiness before release proceeds: Handoffs you own: - Happy path only: one forward continuation to release readiness after user approval. -- For non-happy paths (`NOK`, blockers, missing artifacts), do not use handoff buttons; provide blocker details and let the user choose the recovery path. +- For non-happy paths (`NOK`, blockers, missing items), do not use handoff buttons; provide blocker details and let the user choose the recovery path. + +Planner-coordinated mode (`@planner` invokes this role as a subagent): + +- Execute tester-stage scope only; do not invoke downstream roles unless explicitly asked. +- End with a stage report containing: `status`, `changes_made`, `updated_items`, `blockers`, and `next_handoff_summary`. ## assess current state -Before running any checks, scan your configured input artifacts to determine +Before running any checks, scan your configured input items to determine what work is needed: -1. Read your input artifacts. -1. Identify artifacts that require action: +1. Read your input items. +1. Identify items that require action: - Implementation changes since the last test report. - New components or contracts not yet covered in the test report. - Security or performance findings that are unresolved. @@ -80,7 +85,7 @@ what work is needed: 1. Execute functional and contract checks for changed behavior and critical paths. 1. Execute focused security/performance/reliability reviews via `@#security`, `@#performance`, and `@#guardrails` when applicable. 1. Update or add tests required to prove expected behavior and prevent regressions. -1. Write your baseline reports (see output artifacts); include the performance baseline when performance validation is in scope. Include observability evidence in the test report unless a dedicated observability report is used. +1. Write your baseline reports (see output items); include the performance baseline when performance validation is in scope. Include observability evidence in the test report unless a dedicated observability report is used. 1. Publish verdict and hand off blockers or release-readiness status. ## success criteria @@ -93,9 +98,9 @@ what work is needed: - Cannot execute required checks: escalate with explicit gap and risk. - Security-critical issue found: escalate immediately and block release. -- Missing or stale required-for-scope artifacts: stop and report owners. +- Missing or stale required-for-scope items: stop and report owners. -## artifacts you use +## work items {{AGENT_ARTIFACTS_INPUT}} @@ -103,8 +108,8 @@ what work is needed: {{AGENT_ARTIFACTS_BASELINE}} -Agents do not write to artifacts owned by other roles. If you discover something -that requires changes to upstream artifacts, flag it and trigger a reverse handoff. +Agents do not write to items owned by other roles. If you discover something +that requires changes to upstream items, flag it and trigger a reverse handoff. ## completion checklist diff --git a/src/vstack/_templates/project/.vstack/config.yaml b/src/vstack/_templates/project/.vstack/config.yaml index e02a856..1edd881 100644 --- a/src/vstack/_templates/project/.vstack/config.yaml +++ b/src/vstack/_templates/project/.vstack/config.yaml @@ -10,8 +10,12 @@ # This file is committed to git. It expresses stable project preferences — # editing it is a deliberate choice that affects all future vstack init runs. -# Selective install exclusions — remove or comment out to install everything. +# ----------------------------------------------------------------------------- +# SELECTIVE INSTALL EXCLUSIONS # +# Remove or comment out to install everything. +# ----------------------------------------------------------------------------- + # exclude: # skills: # - terraform @@ -21,16 +25,49 @@ # instructions: all # prompts: all -# Root directory for generated agent artifact paths. +# ----------------------------------------------------------------------------- +# ROOT DIRECTORY FOR GENERATED WORK ITEMS +# # Default: docs +# ----------------------------------------------------------------------------- + +# Root directory for generated agent work-item paths. +# +# items: +# root: docs # +# Legacy fallback (still supported): # artifacts: # root: docs -# Pipeline workflow — seeded by vstack install, owned by this project. +# ----------------------------------------------------------------------------- +# PIPELINE WORKFLOW +# +# Seeded by vstack install, owned by this project. +# +# mode: agentic — default; planner orchestrates stage progression; worker handoff buttons are omitted +# mode: manual — keep worker handoff buttons; planner agent is not generated +# mode: hybrid — support both (planner optional + worker handoffs) +# +# Execution order: +# - Stage order in workflow.stages is the canonical progression order. +# - agentic is stage-sequential by default (planner advances per stage in order). +# - Parallel work can still happen inside a stage (for independent subtasks), +# but cross-stage progression remains ordered by workflow.stages. # -# Edit stages, gates, hitl policy, and handoff prompts to match your team's process. -# version allows vstack to warn on schema changes after upgrades. +# Handoffs: +# - handoffs.prompt is the message used when transitioning to the next stage. +# - If handoffs.agent is omitted, target defaults to the next role in workflow.stages. +# - By default, leave handoffs unset and let each agent's defaults drive prompts. +# - Add overrides only when your team needs custom wording. +# Example: +# handoffs: +# prompt: "<replace with your team-specific transition prompt>" +# - Keep handoffs.agent unset in normal use so workflow order stays canonical. +# - Explicit handoffs.agent overrides are advanced/exceptional and usually unnecessary. +# - In agentic mode, worker handoff buttons are hidden; planner drives progression. +# - handoffs.prompt values remain in this file in agentic mode for compatibility +# when switching to manual or hybrid. # # gate: required — stage always runs # gate: optional — stage may be skipped when its domain is unaffected @@ -40,57 +77,42 @@ # hitl: on-change — pipeline pauses only when the stage made changes (default for optional) # hitl: never — pipeline continues without human approval (explicit opt-out) # +# Notes: +# - Lines starting with # are documentation/examples and are not active config. +# - Active configuration starts below at workflow:. +# - After edits, run: vstack init +# +# Minimal active config example: # workflow: +# mode: agentic # version: 1 # stages: # - role: product # gate: required # hitl: always -# handoffs: -# prompt: > -# Product outputs are approved. Assess the current state and produce or -# update the architecture as needed. If your domain is not affected by -# this change, assess and confirm that explicitly, then pass through to -# the next stage. -# - role: architect -# gate: required -# hitl: always -# handoffs: -# prompt: > -# Architecture outputs are approved. Assess the current state and produce -# design specifications as needed. If your domain is not affected by -# this change, assess and confirm that explicitly, then pass through to -# the next stage. -# - role: designer -# gate: optional -# hitl: on-change -# handoffs: -# prompt: > -# Design outputs are approved. Assess the current state and implement -# code and tests as needed. If your domain is not affected by this -# change, assess and confirm that explicitly, then pass through to -# engineering. If working on an issue, document findings in RCA or -# post-mortem artifacts as relevant. -# - role: engineer -# gate: required -# hitl: always -# handoffs: -# prompt: > -# Implementation is approved. Assess the current state and verify the -# implementation as needed — run tests, security checks, and performance -# analysis. If this is an issue (bug, problem, or incident), also produce -# or update an RCA and, if stakeholder impact is significant, a -# post-mortem. -# - role: tester -# gate: required -# hitl: always -# handoffs: -# prompt: > -# Verification outputs are approved. Assess the current state and prepare -# the release as needed. Create and/or update the relevant artifacts if -# needed, as well as any sign-offs. # - role: release # gate: required # hitl: always -# handoffs: -# prompt: "" +# +workflow: + mode: agentic + version: 1 + stages: + - role: product + gate: required + hitl: always + - role: architect + gate: required + hitl: always + - role: designer + gate: optional + hitl: on-change + - role: engineer + gate: required + hitl: always + - role: tester + gate: required + hitl: always + - role: release + gate: required + hitl: always diff --git a/src/vstack/agents/generator.py b/src/vstack/agents/generator.py index f226682..592053a 100644 --- a/src/vstack/agents/generator.py +++ b/src/vstack/agents/generator.py @@ -1,44 +1,51 @@ -"""AgentGenerator — generator for agent artifacts with artifacts-section support. +"""AgentGenerator — generator for agent artifacts with work-items support. Import :class:`AgentGenerator` to get a generator pre-configured for the ``agents`` artifact type. The generator extends :class:`~vstack.artifacts.generator.GenericArtifactGenerator` with -per-template placeholder injection that builds the ``## artifacts you use`` -subsections from each agent's ``config.yaml`` ``artifacts:`` block. +per-template placeholder injection that builds the ``## work items`` +subsections from each agent's ``config.yaml`` ``items:`` block. Placeholder tokens injected per template: ``{{AGENT_ARTIFACTS_INPUT}}`` - Markdown ``### input`` block (header + table) built from ``artifacts.input``, + Markdown ``### input`` block (header + table) built from ``items.input``, or an empty string when no input entries are configured. ``{{AGENT_ARTIFACTS_OUTPUT}}`` - Markdown ``### output`` block (header + table) built from ``artifacts.output``, + Markdown ``### output`` block (header + table) built from ``items.output``, or an empty string when no output entries are configured. ``{{AGENT_ARTIFACTS_INPUT_COMMENTS}}`` - Verbatim text from ``artifacts.input_comments``, or an empty string. + Verbatim text from ``items.input_comments``, or an empty string. ``{{AGENT_ARTIFACTS_OUTPUT_COMMENTS}}`` - Verbatim text from ``artifacts.output_comments``, or an empty string. + Verbatim text from ``items.output_comments``, or an empty string. Path construction rules ----------------------- -Input items in ``artifacts.input`` are relative to :data:`~vstack.constants.ARTIFACTS_DOCS_ROOT` +Input items in ``items.input`` are relative to :data:`~vstack.constants.ARTIFACTS_DOCS_ROOT` (default ``"docs"``), so ``product/**/*.md`` renders as ``docs/product/**/*.md``. -Output items in ``artifacts.output`` are interpreted as follows: +Output items in ``items.output`` are interpreted as follows: -- When ``artifacts.dir`` is set: items are relative to ``{root}/{dir}/``, e.g. +- When ``items.dir`` is set: items are relative to ``{root}/{dir}/``, e.g. ``overview.md`` → ``docs/architecture/overview.md``. - When an item (string or ``path`` key) starts with ``./``, the ``./`` prefix is stripped and the remainder is used verbatim — allowing paths outside the docs root (e.g. ``./src/**/*``, ``./tests/**/*``). -- When ``artifacts.dir`` is absent: all output items are used verbatim. +- When ``items.dir`` is absent: all output items are used verbatim. + +Backward compatibility +---------------------- +Legacy ``artifacts:`` blocks in agent template ``config.yaml`` files are still +accepted. When both ``items:`` and ``artifacts:`` are present, ``items:`` takes +precedence. """ from __future__ import annotations +import warnings from pathlib import Path from vstack.agents.config import AGENT_TYPE @@ -49,23 +56,29 @@ class AgentGenerator(GenericArtifactGenerator): """Generate agent artifacts using the built-in agent type configuration.""" + _legacy_items_block_warned = False + def __init__( self, templates_root: Path | None = None, *, - artifacts_root: str = ARTIFACTS_DOCS_ROOT, + items_root: str = ARTIFACTS_DOCS_ROOT, + artifacts_root: str | None = None, workflow_stages: list[dict] | None = None, + workflow_mode: str = "agentic", ) -> None: """Create an agent generator bound to *templates_root*. Args: templates_root: Root directory containing the source templates. When ``None``, the built-in package template root is used. - artifacts_root: Root directory for all agent artifacts. Defaults + items_root: Root directory for all agent work-item paths. Defaults to :data:`~vstack.constants.ARTIFACTS_DOCS_ROOT`. Override - via ``artifacts.root`` in ``.vstack/config.yaml`` to relocate - generated artifact paths (e.g. ``"documentation"`` instead of + via ``items.root`` in ``.vstack/config.yaml`` to relocate + generated item paths (e.g. ``"documentation"`` instead of ``"docs"``). + artifacts_root: Deprecated alias for ``items_root``. Retained for + backward compatibility. workflow_stages: Ordered list of pipeline stage dicts read from ``workflow.stages`` in ``.vstack/config.yaml``. Each dict has ``role`` and ``gate`` string keys, a ``handoffs`` key containing @@ -73,15 +86,37 @@ def __init__( keys, and an optional ``hitl`` string key. When ``None`` or empty the generator falls back to v3 behaviour: a generic handoff label without an explicit ``agent:`` target. + workflow_mode: Workflow execution mode read from + ``workflow.mode`` in ``.vstack/config.yaml``. Supported values + are ``manual``, ``agentic``, and ``hybrid``. In ``agentic`` + mode, worker-agent handoff buttons are omitted. """ super().__init__( AGENT_TYPE, templates_root if templates_root is not None else TEMPLATES_ROOT ) - self.artifacts_root = artifacts_root + if artifacts_root is not None: + self.items_root = artifacts_root + else: + self.items_root = items_root + # Backward-compatible attribute used by older tests/callers. + self.artifacts_root = self.items_root self.workflow_stages: list[dict] = workflow_stages or [] + self.workflow_mode = workflow_mode.strip().lower() + + def find_templates(self) -> list[Path]: + """Return agent template dirs filtered by workflow mode. + + In ``manual`` mode, the planner coordinator agent is not generated. + In ``agentic`` and ``hybrid`` modes, planner is generated alongside + worker agents. + """ + templates = super().find_templates() + if self.workflow_mode != "manual": + return templates + return [p for p in templates if p.name != "planner"] def template_partials(self, tmpl_dir: Path) -> dict[str, str]: - """Inject per-template artifact placeholder tokens. + """Inject per-template work-item placeholder tokens. Returns a dict with five keys: ``AGENT_ARTIFACTS_INPUT``, ``AGENT_ARTIFACTS_OUTPUT``, @@ -90,18 +125,30 @@ def template_partials(self, tmpl_dir: Path) -> dict[str, str]: """ artifact_config = self.load_artifact_config(tmpl_dir) - artifacts = artifact_config.get("artifacts") or {} - - if not isinstance(artifacts, dict): - artifacts = {} - - doc_root = self.artifacts_root - agent_dir: str = str(artifacts.get("dir", "")).strip() - - raw_inputs: list = artifacts.get("input", []) + items = artifact_config.get("items") or {} + + # Backward compatibility: legacy ``artifacts`` block. + if not items: + items = artifact_config.get("artifacts") or {} + if items and not AgentGenerator._legacy_items_block_warned: + warnings.warn( + "Legacy defaults.artifacts is deprecated for agent work items; " + "use defaults.items instead.", + FutureWarning, + stacklevel=2, + ) + AgentGenerator._legacy_items_block_warned = True + + if not isinstance(items, dict): + items = {} + + doc_root = self.items_root + agent_dir: str = str(items.get("dir", "")).strip() + + raw_inputs: list = items.get("input", []) if not isinstance(raw_inputs, list): raw_inputs = [] - raw_outputs: list = artifacts.get("output", []) + raw_outputs: list = items.get("output", []) if not isinstance(raw_outputs, list): raw_outputs = [] @@ -116,8 +163,8 @@ def template_partials(self, tmpl_dir: Path) -> dict[str, str]: return { "AGENT_ARTIFACTS_INPUT": self._build_section("input", input_entries), "AGENT_ARTIFACTS_OUTPUT": self._build_section("output", output_entries), - "AGENT_ARTIFACTS_INPUT_COMMENTS": str(artifacts.get("input_comments", "") or ""), - "AGENT_ARTIFACTS_OUTPUT_COMMENTS": str(artifacts.get("output_comments", "") or ""), + "AGENT_ARTIFACTS_INPUT_COMMENTS": str(items.get("input_comments", "") or ""), + "AGENT_ARTIFACTS_OUTPUT_COMMENTS": str(items.get("output_comments", "") or ""), "AGENT_ARTIFACTS_BASELINE": self._build_baseline_section(baseline_entries), } @@ -152,12 +199,16 @@ def load_artifact_config(self, tmpl_dir: Path) -> dict: agent_role = tmpl_dir.name defaults = self._extract_defaults(config) config.pop("defaults", None) - # Re-expose artifacts at top level so template_partials can read them - # via the standard artifact_config.get("artifacts") path. - if "artifacts" not in config: - artifacts_from_defaults = defaults.get("artifacts") - if artifacts_from_defaults: - config["artifacts"] = artifacts_from_defaults + # Re-expose items at top level so template_partials can read them. + # Fallback to legacy ``artifacts`` for backward compatibility. + if "items" not in config: + items_from_defaults = defaults.get("items") + if items_from_defaults: + config["items"] = items_from_defaults + elif "artifacts" not in config: + artifacts_from_defaults = defaults.get("artifacts") + if artifacts_from_defaults: + config["artifacts"] = artifacts_from_defaults handoffs_block = defaults.get("handoffs") or {} if isinstance(handoffs_block, dict): handoff_prompt: str = str(handoffs_block.get("prompt", "") or "") @@ -190,6 +241,11 @@ def _resolve_handoffs(self, agent_role: str, handoff_prompt: str) -> list[dict[s :returns: List of handoff dicts suitable for frontmatter serialization, or an empty list when this is the last stage or no prompts exist. """ + # In agentic mode the planner orchestrates stage progression, so worker + # agents should not expose forward handoff buttons. + if self.workflow_mode == "agentic" and agent_role != "planner": + return [] + if not self.workflow_stages: # No workflow configured — emit a generic handoff without an # explicit ``agent:`` target when a prompt is available, so that @@ -217,6 +273,19 @@ def _resolve_handoffs(self, agent_role: str, handoff_prompt: str) -> list[dict[s if not isinstance(stage_handoffs, list): stage_handoffs = [] + # If workflow stages are configured but this stage has no explicit + # handoffs block, fall back to the agent's own default prompt. + if not stage_handoffs: + if not handoff_prompt.strip(): + return [] + return [ + { + "label": f"Go to next stage: {next_role.capitalize()}", + "agent": next_role, + "prompt": handoff_prompt.strip(), + } + ] + result: list[dict[str, str]] = [] agent_override_applied = False @@ -249,12 +318,12 @@ def _resolve_output_entries( ) -> list[dict[str, str | bool]]: """Resolve raw output config items to display-path dicts. - :param raw_outputs: List of strings or dicts from ``artifacts.output``. + :param raw_outputs: List of strings or dicts from ``items.output``. :param agent_dir: Subdirectory for this agent (e.g. ``"architecture"``). When empty, output paths are used verbatim. :returns: List of ``{"path": ..., "notes": ..., "baseline": ...}`` dicts. """ - doc_root = self.artifacts_root + doc_root = self.items_root result: list[dict[str, str | bool]] = [] for item in raw_outputs: if isinstance(item, str): @@ -280,8 +349,8 @@ def _resolve_output_entries( def _build_table(self, entries: list[dict[str, str | bool]]) -> str: """Build a Markdown table from normalised artifact entry dicts. - Produces a single-column ``Artifact`` table when no entry has notes, - or a two-column ``Artifact | Notes`` table when any entry does. + Produces a single-column ``Item`` table when no entry has notes, + or a two-column ``Item | Notes`` table when any entry does. All rows are padded to equal column widths. """ cells = [f"`{e['path']}`" for e in entries] @@ -289,10 +358,10 @@ def _build_table(self, entries: list[dict[str, str | bool]]) -> str: has_notes = any(notes_cells) if has_notes: - art_w = max(len("Artifact"), *(len(c) for c in cells)) + art_w = max(len("Item"), *(len(c) for c in cells)) notes_w = max(len("Notes"), *(len(n) for n in notes_cells)) lines = [ - f"| {'Artifact':<{art_w}} | {'Notes':<{notes_w}} |", + f"| {'Item':<{art_w}} | {'Notes':<{notes_w}} |", f"| {'-' * art_w} | {'-' * notes_w} |", *( f"| {cell:<{art_w}} | {note:<{notes_w}} |" @@ -300,9 +369,9 @@ def _build_table(self, entries: list[dict[str, str | bool]]) -> str: ), ] else: - col_w = max(len("Artifact"), *(len(c) for c in cells)) + col_w = max(len("Item"), *(len(c) for c in cells)) lines = [ - f"| {'Artifact':<{col_w}} |", + f"| {'Item':<{col_w}} |", f"| {'-' * col_w} |", *(f"| {cell:<{col_w}} |" for cell in cells), ] @@ -321,7 +390,7 @@ def _build_section(self, heading: str, entries: list[dict[str, str | bool]]) -> def _build_baseline_section(self, entries: list[dict[str, str | bool]]) -> str: """Build the ``### baseline docs you maintain`` subsection. - Renders a table of output artifacts flagged with ``baseline: true``. + Renders a table of output items flagged with ``baseline: true``. Returns an empty string when no baseline entries are present. :param entries: Resolved output entries where ``baseline`` is ``True``. diff --git a/src/vstack/cli/init.py b/src/vstack/cli/init.py index b3af947..6f5d394 100644 --- a/src/vstack/cli/init.py +++ b/src/vstack/cli/init.py @@ -439,6 +439,77 @@ def _warn_unknown_workflow_roles( file=sys.stderr, ) + @staticmethod + def _prune_planner_when_manual_mode( + *, + install_dir: Path, + gen, + existing_entries, + new_entries, + colors, + prefix: str, + dry_run: bool, + ) -> None: + """Remove tracked planner artifact when agent workflow mode is ``manual``. + + In manual mode planner is not generated. If a tracked planner file + exists from a previous non-manual mode, remove it when unchanged. + Locally modified planner files are preserved and kept tracked. + """ + from vstack.manifest import hash_with_algorithm + + if gen.config.type_name != "agent": + return + if getattr(gen, "workflow_mode", "manual") != "manual": + return + + existing_entry = existing_entries.get("agent/planner") + if existing_entry is None: + return + + planner_file = install_dir / existing_entry.file + rel = str(existing_entry.file) + + if not planner_file.exists(): + return + + removable = True + checksum = existing_entry.checksum + if checksum: + checksum_algorithm = (existing_entry.checksum_algorithm or "sha256").lower() + try: + current = hash_with_algorithm( + planner_file.read_text(encoding="utf-8"), checksum_algorithm + ) + removable = current == checksum + except (OSError, ValueError): + removable = False + + if not removable: + print( + f" {prefix}{colors.YELLOW}↷{colors.RESET} {rel}" + f" {colors.DIM}preserved — local changes detected{colors.RESET}" + ) + Manifest.preserve_existing_entry( + new_entries=new_entries, + manifest_key=gen.config.manifest_key, + existing_entry=existing_entry, + ) + return + + print( + f" {prefix}{colors.CYAN}−{colors.RESET} {rel}" + f" {colors.DIM}removed — planner disabled in workflow.mode=manual{colors.RESET}" + ) + if not dry_run: + planner_file.unlink(missing_ok=True) + parent = planner_file.parent + try: + if parent.exists() and not any(parent.iterdir()): + parent.rmdir() + except OSError: + pass + @staticmethod def execute( service: CommandService, @@ -527,6 +598,16 @@ def execute( if artifact_action == "preserve": preserved_selectors.add(f"{gen.config.type_name}/{artifact.name}") + InitCommand._prune_planner_when_manual_mode( + install_dir=install_dir, + gen=gen, + existing_entries=existing_entries, + new_entries=new_entries, + colors=colors, + prefix=prefix, + dry_run=dry_run, + ) + # Verify source for unresolvable issues. verify_result = gen.verify_input() for msg in verify_result.messages: diff --git a/src/vstack/cli/interface.py b/src/vstack/cli/interface.py index 704ec57..3098f6b 100644 --- a/src/vstack/cli/interface.py +++ b/src/vstack/cli/interface.py @@ -3,7 +3,9 @@ from __future__ import annotations import argparse +import sys from pathlib import Path +from typing import Any, cast from vstack.cli.base import BaseCommand, CommandContext from vstack.cli.catalog import COMMAND_CATALOG @@ -25,6 +27,8 @@ class CommandLineInterface: """Facade that coordinates parser, service construction, and dispatch.""" + _legacy_items_root_warned = False + def __init__( self, *, @@ -112,7 +116,7 @@ def _read_exclude( if type_name == "agent": raise ValueError( "exclude: agents is not supported. " - "The six-role agent chain is an atomic unit and cannot be partially " + "The role-agent set is an atomic unit and cannot be partially " "excluded. See ADR-022." ) if isinstance(value, str) and value.strip().lower() == "all": @@ -124,12 +128,15 @@ def _read_exclude( return frozenset(excluded_types), excluded_names @staticmethod - def _read_artifacts_root(install_dir: Path | None) -> str: - """Read ``artifacts.root`` from ``.vstack/config.yaml`` when available. + def _read_items_root(install_dir: Path | None) -> str: + """Read ``items.root`` for agent work-item paths from ``.vstack/config.yaml``. Returns :data:`~vstack.constants.ARTIFACTS_DOCS_ROOT` when *install_dir* is ``None``, when the config file does not exist, or when the key is absent or blank. + + Backward compatibility: if ``items.root`` is not set, the legacy + ``artifacts.root`` key is still accepted and triggers a deprecation warning. """ if install_dir is None: return ARTIFACTS_DOCS_ROOT @@ -137,12 +144,25 @@ def _read_artifacts_root(install_dir: Path | None) -> str: if not config_path.exists(): return ARTIFACTS_DOCS_ROOT parsed = FrontmatterParser.parse_yaml(config_path.read_text(encoding="utf-8")) + items = parsed.get("items", "") + if isinstance(items, dict): + value = items.get("root", "") + if isinstance(value, str) and value.strip(): + return value.strip() + artifacts = parsed.get("artifacts", "") - if not isinstance(artifacts, dict): - return ARTIFACTS_DOCS_ROOT - value = artifacts.get("root", "") - if isinstance(value, str) and value.strip(): - return value.strip() + if isinstance(artifacts, dict): + value = artifacts.get("root", "") + if isinstance(value, str) and value.strip(): + if not CommandLineInterface._legacy_items_root_warned: + print( + "WARNING: '.vstack/config.yaml' uses legacy 'artifacts.root'. " + "Use 'items.root' for agent work-item paths. " + "Run 'vstack migrate --target <dir>' to auto-upgrade.", + file=sys.stderr, + ) + CommandLineInterface._legacy_items_root_warned = True + return value.strip() return ARTIFACTS_DOCS_ROOT @staticmethod @@ -184,6 +204,30 @@ def _read_workflow_stages(install_dir: Path | None) -> list[dict]: result.append(stage) return result + @staticmethod + def _read_workflow_mode(install_dir: Path | None) -> str: + """Read ``workflow.mode`` from ``.vstack/config.yaml`` when available. + + Supported values are ``manual``, ``agentic``, and ``hybrid``. + Returns ``agentic`` when missing, invalid, or unavailable. + """ + if install_dir is None: + return "agentic" + config_path = install_dir.parent / ".vstack" / "config.yaml" + if not config_path.exists(): + return "agentic" + parsed = FrontmatterParser.parse_yaml(config_path.read_text(encoding="utf-8")) + workflow = parsed.get("workflow", "") + if not isinstance(workflow, dict): + return "agentic" + raw_mode = workflow.get("mode", "agentic") + if not isinstance(raw_mode, str): + return "agentic" + mode = raw_mode.strip().lower() + if mode in {"manual", "agentic", "hybrid"}: + return mode + return "agentic" + @staticmethod def _parse_stage_handoffs(item: dict) -> list[dict[str, str]]: """Parse the ``handoffs`` entry from a workflow stage dict. @@ -250,12 +294,14 @@ def run(self) -> int: args=args, requires_install_dir=command_config.requires_install_dir, ) - artifacts_root = self._read_artifacts_root(resolved_install_dir) + items_root = self._read_items_root(resolved_install_dir) workflow_stages = self._read_workflow_stages(resolved_install_dir) - service = self._service_cls( + workflow_mode = self._read_workflow_mode(resolved_install_dir) + service = cast(Any, self._service_cls)( templates_root=self._templates_root, - artifacts_root=artifacts_root, + items_root=items_root, workflow_stages=workflow_stages, + workflow_mode=workflow_mode, ) commands = self._build_command_registry(service) effective_only = self._resolve_only_filter( diff --git a/src/vstack/cli/migrate.py b/src/vstack/cli/migrate.py index b35525a..141af8a 100644 --- a/src/vstack/cli/migrate.py +++ b/src/vstack/cli/migrate.py @@ -20,13 +20,15 @@ class MigrateCommand(BaseCommand): - """Apply docs artifact path migrations between major vstack versions. + """Apply agent work-item path migrations between major vstack versions. Reads migration records from the package ``_migrations/`` directory and - moves files at old docs paths to their new locations. Only files that - exist at the old path are moved; absent files are silently skipped. + moves files at old paths to their new locations. Only files that exist + at the old path are moved; absent files are silently skipped. """ + _legacy_items_root_warned = False + def __init__(self, service: CommandService) -> None: self._service = service @@ -82,45 +84,160 @@ def _load_migration_record(from_major: int, to_major: int) -> list[dict] | None: return [m for m in moves if isinstance(m, dict)] @staticmethod - def _resolve_new_path(new_rel: str, artifacts_root: str) -> str: - """Substitute the default docs root in *new_rel* with *artifacts_root*. + def _resolve_new_path(new_rel: str, items_root: str) -> str: + """Substitute the default docs root in *new_rel* with *items_root*. Migration records store new paths using the default ``docs/`` root. When the project uses a custom root, the first path component is replaced so the file lands under the configured directory. """ - if artifacts_root == ARTIFACTS_DOCS_ROOT: + if items_root == ARTIFACTS_DOCS_ROOT: return new_rel parts = Path(new_rel).parts if parts and parts[0] == ARTIFACTS_DOCS_ROOT: - return str(Path(artifacts_root).joinpath(*parts[1:])) + return str(Path(items_root).joinpath(*parts[1:])) return new_rel @staticmethod - def _read_artifacts_root(project_root: Path) -> str: - """Read ``artifacts.root`` from ``.vstack/config.yaml``. + def _read_items_root(project_root: Path) -> str: + """Read ``items.root`` for agent work-item paths from ``.vstack/config.yaml``. Returns :data:`~vstack.constants.ARTIFACTS_DOCS_ROOT` when the config is absent or the key is not set. + + Backward compatibility: if ``items.root`` is not set, ``artifacts.root`` + is still accepted and triggers a deprecation warning. """ config_path = project_root / VSTACK_DIR_NAME / "config.yaml" if not config_path.exists(): return ARTIFACTS_DOCS_ROOT parsed = FrontmatterParser.parse_yaml(config_path.read_text(encoding="utf-8")) + items = parsed.get("items", "") + if isinstance(items, dict): + value = items.get("root", "") + if isinstance(value, str) and value.strip(): + return value.strip() artifacts = parsed.get("artifacts", "") - if not isinstance(artifacts, dict): - return ARTIFACTS_DOCS_ROOT - value = artifacts.get("root", "") - if isinstance(value, str) and value.strip(): - return value.strip() + if isinstance(artifacts, dict): + value = artifacts.get("root", "") + if isinstance(value, str) and value.strip(): + if not MigrateCommand._legacy_items_root_warned: + print( + "WARNING: '.vstack/config.yaml' uses legacy 'artifacts.root'. " + "Use 'items.root' for agent work-item paths.", + file=sys.stderr, + ) + MigrateCommand._legacy_items_root_warned = True + return value.strip() return ARTIFACTS_DOCS_ROOT + @staticmethod + def _upgrade_project_items_root_key(*, project_root: Path, dry_run: bool) -> int: + """Add ``items.root`` when only legacy ``artifacts.root`` is configured. + + Returns the number of files changed (``0`` or ``1``). + """ + config_path = project_root / VSTACK_DIR_NAME / "config.yaml" + if not config_path.exists(): + return 0 + parsed = FrontmatterParser.parse_yaml(config_path.read_text(encoding="utf-8")) + items = parsed.get("items", "") + if isinstance(items, dict): + value = items.get("root", "") + if isinstance(value, str) and value.strip(): + return 0 + artifacts = parsed.get("artifacts", "") + if not isinstance(artifacts, dict): + return 0 + legacy_root = artifacts.get("root", "") + if not isinstance(legacy_root, str) or not legacy_root.strip(): + return 0 + if dry_run: + return 1 + raw = config_path.read_text(encoding="utf-8") + suffix = "" if raw.endswith("\n") else "\n" + raw += f"{suffix}items:\n root: {legacy_root.strip()}\n" + config_path.write_text(raw, encoding="utf-8") + return 1 + + @staticmethod + def _upgrade_agent_template_items_keys(*, project_root: Path, dry_run: bool) -> int: + """Add ``defaults.items`` from legacy ``defaults.artifacts`` in local templates. + + Returns the number of files changed. + """ + templates_dir = project_root / VSTACK_DIR_NAME / "templates" + if not templates_dir.exists(): + return 0 + changed = 0 + for config_path in sorted(templates_dir.glob("*/config.yaml")): + parsed = FrontmatterParser.parse_yaml(config_path.read_text(encoding="utf-8")) + defaults = parsed.get("defaults", "") + if not isinstance(defaults, dict): + continue + if "items" in defaults: + continue + artifacts = defaults.get("artifacts", "") + if not isinstance(artifacts, dict): + continue + defaults["items"] = artifacts + changed += 1 + if dry_run: + continue + config_path.write_text( + yaml.safe_dump(parsed, sort_keys=False, allow_unicode=False), + encoding="utf-8", + ) + return changed + + @staticmethod + def _cleanup_commented_workflow_example(*, project_root: Path, dry_run: bool) -> int: + """Remove legacy commented workflow example blocks from project config. + + Older seeded config variants contained a trailing fully-commented + ``ACTIVE CONFIGURATION (UNCOMMENT AND EDIT)`` block. This helper removes + that obsolete commented block while leaving active YAML settings intact. + + Returns the number of files changed (``0`` or ``1``). + """ + config_path = project_root / VSTACK_DIR_NAME / "config.yaml" + if not config_path.exists(): + return 0 + raw = config_path.read_text(encoding="utf-8") + marker = "# ACTIVE CONFIGURATION (UNCOMMENT AND EDIT)" + lines = raw.splitlines(keepends=True) + + marker_idx = -1 + for idx, line in enumerate(lines): + if marker in line and line.lstrip().startswith("#"): + marker_idx = idx + break + + if marker_idx < 0: + return 0 + + # Only remove the marker and the immediately following comment/blank + # lines. As soon as a non-comment YAML line appears, stop cleanup. + end_idx = marker_idx + 1 + while end_idx < len(lines): + stripped = lines[end_idx].strip() + if stripped.startswith("#") or stripped == "": + end_idx += 1 + continue + break + + out = lines[:marker_idx] + lines[end_idx:] + if dry_run: + return 1 + config_path.write_text("".join(out), encoding="utf-8") + return 1 + @staticmethod def _apply_moves( *, moves: list[dict], project_root: Path, - artifacts_root: str, + items_root: str, dry_run: bool, colors: type[Colors], ) -> tuple[int, int]: @@ -138,7 +255,7 @@ def _apply_moves( if not old_rel or not new_rel_raw: continue - new_rel = MigrateCommand._resolve_new_path(new_rel_raw, artifacts_root) + new_rel = MigrateCommand._resolve_new_path(new_rel_raw, items_root) old_path = project_root / old_rel new_path = project_root / new_rel @@ -194,10 +311,45 @@ def run(self, *, context: CommandContext) -> int: to_major = from_major + 1 if from_major >= to_major: - print(f"Nothing to migrate: already at v{to_major}.") + config_upgraded = ( + self._upgrade_project_items_root_key( + project_root=project_root, + dry_run=dry_run, + ) + + self._upgrade_agent_template_items_keys( + project_root=project_root, + dry_run=dry_run, + ) + + self._cleanup_commented_workflow_example( + project_root=project_root, + dry_run=dry_run, + ) + ) + if config_upgraded: + print( + f"Updated legacy config keys in {config_upgraded} file(s). " + f"No path moves required for v{to_major}." + ) + else: + print(f"Nothing to migrate: already at v{to_major}.") return 0 - artifacts_root = self._read_artifacts_root(project_root) + config_upgraded = ( + self._upgrade_project_items_root_key( + project_root=project_root, + dry_run=dry_run, + ) + + self._upgrade_agent_template_items_keys( + project_root=project_root, + dry_run=dry_run, + ) + + self._cleanup_commented_workflow_example( + project_root=project_root, + dry_run=dry_run, + ) + ) + + items_root = self._read_items_root(project_root) print(f"\n {colors.BOLD}Migrating docs paths v{from_major} → v{to_major}{colors.RESET}") if dry_run: @@ -219,7 +371,7 @@ def run(self, *, context: CommandContext) -> int: moved, skipped = self._apply_moves( moves=moves, project_root=project_root, - artifacts_root=artifacts_root, + items_root=items_root, dry_run=dry_run, colors=colors, ) @@ -227,6 +379,7 @@ def run(self, *, context: CommandContext) -> int: total_skipped += skipped print( - f"\n {colors.BOLD}Done.{colors.RESET} Moved: {total_moved} Skipped: {total_skipped}" + f"\n {colors.BOLD}Done.{colors.RESET} " + f"Moved: {total_moved} Skipped: {total_skipped} Config upgraded: {config_upgraded}" ) return 0 diff --git a/src/vstack/cli/service.py b/src/vstack/cli/service.py index 6a2147e..6a09768 100644 --- a/src/vstack/cli/service.py +++ b/src/vstack/cli/service.py @@ -38,8 +38,10 @@ def __init__( self, templates_root: Path, *, - artifacts_root: str = ARTIFACTS_DOCS_ROOT, + items_root: str = ARTIFACTS_DOCS_ROOT, + artifacts_root: str | None = None, workflow_stages: list[dict[str, str]] | None = None, + workflow_mode: str = "agentic", ) -> None: """Create generators for all known artifact families. @@ -50,23 +52,29 @@ def __init__( Args: templates_root: Root directory containing the source templates. - artifacts_root: Root directory for agent artifact paths. Passed + items_root: Root directory for agent work-item paths. Passed through to :class:`~vstack.agents.generator.AgentGenerator`. Defaults to :data:`~vstack.constants.ARTIFACTS_DOCS_ROOT`; - override via ``artifacts.root`` in ``.vstack/config.yaml``. + override via ``items.root`` in ``.vstack/config.yaml``. + artifacts_root: Deprecated alias for ``items_root``. workflow_stages: Ordered list of pipeline stage dicts read from the ``workflow.stages`` block in ``.vstack/config.yaml``. Each dict has ``role`` and ``gate`` string keys, a ``handoffs`` key containing a list of dicts with ``prompt``, ``agent``, and ``label`` string keys, and an optional ``hitl`` string key. When ``None`` or empty the generator falls back to v3 behaviour. + workflow_mode: Workflow execution mode from ``workflow.mode`` in + ``.vstack/config.yaml``. Supported values: ``manual``, + ``agentic``, and ``hybrid``. """ self.root = templates_root + resolved_items_root = artifacts_root or items_root self.generators: list[GenericArtifactGenerator] = [ AgentGenerator( templates_root, - artifacts_root=artifacts_root, + items_root=resolved_items_root, workflow_stages=workflow_stages or [], + workflow_mode=workflow_mode, ) if tc is AGENT_TYPE else GenericArtifactGenerator(tc, templates_root) diff --git a/src/vstack/constants.py b/src/vstack/constants.py index 08cb53a..8472398 100644 --- a/src/vstack/constants.py +++ b/src/vstack/constants.py @@ -15,9 +15,10 @@ TEMPLATES_ROOT = Path(str(_PACKAGE_ROOT / "_templates")) MIGRATIONS_ROOT = Path(str(_PACKAGE_ROOT / "_migrations")) -# Default root directory for all role artifacts. Individual agent configs specify -# only the subdirectory via ``artifacts.dir``; this root is applied at render time. -# Override per project via ``artifacts.root`` in ``.vstack/config.yaml``. +# Default root directory for all role work items. Individual agent configs +# specify only the subdirectory via ``items.dir``; this root is applied at +# render time. Override per project via ``items.root`` in +# ``.vstack/config.yaml`` (legacy ``artifacts.root`` is still accepted). ARTIFACTS_DOCS_ROOT = "docs" _SEMVER_TAG_RE = re.compile(r"^\d+\.\d+\.\d+$") diff --git a/tests/vstack/agents/test_generation.py b/tests/vstack/agents/test_generation.py index cb93d29..3045d05 100644 --- a/tests/vstack/agents/test_generation.py +++ b/tests/vstack/agents/test_generation.py @@ -5,6 +5,8 @@ from pathlib import Path from tests.conftest import run_vstack +from vstack.agents.generator import AgentGenerator +from vstack.constants import TEMPLATES_ROOT from vstack.frontmatter import FrontmatterParser @@ -18,6 +20,7 @@ def test_architect_agent_includes_model_and_handoffs(self, tmp_path: Path) -> No vstack_dir.mkdir(parents=True, exist_ok=True) (vstack_dir / "config.yaml").write_text( "workflow:\n" + " mode: manual\n" " version: 1\n" " stages:\n" " - role: product\n" @@ -71,10 +74,8 @@ def test_all_agents_include_generated_artifacts_section(self, tmp_path: Path) -> agents_dir = tmp_path / ".github" / "agents" for agent_file in agents_dir.glob("*.agent.md"): content = agent_file.read_text(encoding="utf-8") - assert "## artifacts you use" in content, ( - f"{agent_file.name} missing '## artifacts you use' section" - ) - assert "Agents do not write to artifacts owned by other roles." in content, ( + assert "## work items" in content, f"{agent_file.name} missing '## work items' section" + assert "Agents do not write to items owned by other roles." in content, ( f"{agent_file.name} missing role-boundary notice" ) # Placeholders must not appear in rendered output @@ -82,3 +83,167 @@ def test_all_agents_include_generated_artifacts_section(self, tmp_path: Path) -> assert f"{{{{{token}}}}}" not in content, ( f"{agent_file.name} has unresolved {{{{{token}}}}} placeholder" ) + + def test_agentic_mode_disables_worker_handoffs(self, tmp_path: Path) -> None: + """In workflow.mode=agentic, worker agents should not emit handoff buttons.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir(parents=True, exist_ok=True) + (vstack_dir / "config.yaml").write_text( + "workflow:\n" + " mode: agentic\n" + " version: 1\n" + " stages:\n" + " - role: product\n" + " gate: required\n" + " handoffs:\n" + " prompt: Product done.\n" + " - role: architect\n" + " gate: required\n" + " handoffs:\n" + " prompt: Architecture done.\n", + encoding="utf-8", + ) + + result = run_vstack(["install", "--only", "agent", "--target", str(tmp_path)], timeout=60) + assert result.returncode == 0, ( + f"vstack install --only agent failed:\n{result.stdout}\n{result.stderr}" + ) + + out = tmp_path / ".github" / "agents" / "product.agent.md" + parsed = FrontmatterParser.parse(out.read_text(encoding="utf-8")) + assert not parsed.metadata.get("handoffs") + assert (tmp_path / ".github" / "agents" / "planner.agent.md").exists() + + def test_manual_mode_keeps_worker_handoffs(self, tmp_path: Path) -> None: + """In workflow.mode=manual, worker agents keep handoff buttons.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir(parents=True, exist_ok=True) + (vstack_dir / "config.yaml").write_text( + "workflow:\n" + " mode: manual\n" + " version: 1\n" + " stages:\n" + " - role: product\n" + " gate: required\n" + " handoffs:\n" + " prompt: Product done.\n" + " - role: architect\n" + " gate: required\n" + " handoffs:\n" + " prompt: Architecture done.\n", + encoding="utf-8", + ) + + result = run_vstack(["install", "--only", "agent", "--target", str(tmp_path)], timeout=60) + assert result.returncode == 0, ( + f"vstack install --only agent failed:\n{result.stdout}\n{result.stderr}" + ) + + out = tmp_path / ".github" / "agents" / "product.agent.md" + parsed = FrontmatterParser.parse(out.read_text(encoding="utf-8")) + handoffs = parsed.metadata.get("handoffs") + assert isinstance(handoffs, list) + assert handoffs + assert not (tmp_path / ".github" / "agents" / "planner.agent.md").exists() + + def test_hybrid_mode_generates_planner_and_keeps_worker_handoffs(self, tmp_path: Path) -> None: + """In workflow.mode=hybrid, planner exists and worker handoffs remain.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir(parents=True, exist_ok=True) + (vstack_dir / "config.yaml").write_text( + "workflow:\n" + " mode: hybrid\n" + " version: 1\n" + " stages:\n" + " - role: product\n" + " gate: required\n" + " handoffs:\n" + " prompt: Product done.\n" + " - role: architect\n" + " gate: required\n" + " handoffs:\n" + " prompt: Architecture done.\n", + encoding="utf-8", + ) + + result = run_vstack(["install", "--only", "agent", "--target", str(tmp_path)], timeout=60) + assert result.returncode == 0, ( + f"vstack install --only agent failed:\n{result.stdout}\n{result.stderr}" + ) + + assert (tmp_path / ".github" / "agents" / "planner.agent.md").exists() + out = tmp_path / ".github" / "agents" / "product.agent.md" + parsed = FrontmatterParser.parse(out.read_text(encoding="utf-8")) + handoffs = parsed.metadata.get("handoffs") + assert isinstance(handoffs, list) + assert handoffs + + def test_mode_switch_agentic_to_manual_prunes_planner(self, tmp_path: Path) -> None: + """Switching from agentic to manual removes tracked planner artifact on init.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir(parents=True, exist_ok=True) + config = vstack_dir / "config.yaml" + + config.write_text( + "workflow:\n" + " mode: agentic\n" + " version: 1\n" + " stages:\n" + " - role: product\n" + " gate: required\n" + " handoffs:\n" + " prompt: Product done.\n" + " - role: architect\n" + " gate: required\n" + " handoffs:\n" + " prompt: Architecture done.\n", + encoding="utf-8", + ) + first = run_vstack(["install", "--only", "agent", "--target", str(tmp_path)], timeout=60) + assert first.returncode == 0 + planner_file = tmp_path / ".github" / "agents" / "planner.agent.md" + assert planner_file.exists() + + config.write_text( + "workflow:\n" + " mode: manual\n" + " version: 1\n" + " stages:\n" + " - role: product\n" + " gate: required\n" + " handoffs:\n" + " prompt: Product done.\n" + " - role: architect\n" + " gate: required\n" + " handoffs:\n" + " prompt: Architecture done.\n", + encoding="utf-8", + ) + second = run_vstack(["init", "--only", "agent", "--target", str(tmp_path)], timeout=60) + assert second.returncode == 0, f"init failed:\n{second.stdout}\n{second.stderr}" + assert not planner_file.exists() + + def test_agentic_mode_directly_suppresses_worker_handoffs(self, tmp_path: Path) -> None: + """AgentGenerator suppresses worker handoffs when workflow_mode is agentic.""" + gen = AgentGenerator( + tmp_path, + workflow_mode="agentic", + workflow_stages=[ + {"role": "product", "gate": "required", "handoffs": []}, + {"role": "architect", "gate": "required", "handoffs": []}, + ], + ) + handoffs = gen._resolve_handoffs("product", "Product done.") + assert handoffs == [] + + def test_find_templates_excludes_planner_in_manual_mode(self) -> None: + """Manual mode should hide planner template from rendered artifact list.""" + gen = AgentGenerator(TEMPLATES_ROOT, workflow_mode="manual") + names = {p.name for p in gen.find_templates()} + assert "planner" not in names + + def test_find_templates_includes_planner_in_hybrid_mode(self) -> None: + """Hybrid mode should keep planner in the rendered artifact list.""" + gen = AgentGenerator(TEMPLATES_ROOT, workflow_mode="hybrid") + names = {p.name for p in gen.find_templates()} + assert "planner" in names diff --git a/tests/vstack/agents/test_generator.py b/tests/vstack/agents/test_generator.py index e91f163..4278545 100644 --- a/tests/vstack/agents/test_generator.py +++ b/tests/vstack/agents/test_generator.py @@ -5,6 +5,8 @@ from pathlib import Path from typing import Any +import pytest + from vstack.agents.generator import AgentGenerator from vstack.constants import ARTIFACTS_DOCS_ROOT @@ -54,7 +56,7 @@ def test_input_prefixed_with_docs_root(self, tmp_path: Path) -> None: tmpl_dir = tmp_path / "architect" tmpl_dir.mkdir() (tmpl_dir / "config.yaml").write_text( - "name: architect\ndefaults:\n artifacts:\n dir: architecture\n input:\n - product/**/*.md\n", + "name: architect\ndefaults:\n items:\n dir: architecture\n input:\n - product/**/*.md\n", encoding="utf-8", ) @@ -67,7 +69,7 @@ def test_output_prefixed_with_root_and_dir(self, tmp_path: Path) -> None: tmpl_dir = tmp_path / "architect" tmpl_dir.mkdir() (tmpl_dir / "config.yaml").write_text( - "name: architect\ndefaults:\n artifacts:\n dir: architecture\n output:\n - overview.md\n", + "name: architect\ndefaults:\n items:\n dir: architecture\n output:\n - overview.md\n", encoding="utf-8", ) @@ -83,7 +85,7 @@ def test_output_verbatim_when_no_dir(self, tmp_path: Path) -> None: tmpl_dir = tmp_path / "engineer" tmpl_dir.mkdir() (tmpl_dir / "config.yaml").write_text( - "name: engineer\ndefaults:\n artifacts:\n output:\n - path: ./src/**/*\n", + "name: engineer\ndefaults:\n items:\n output:\n - path: ./src/**/*\n", encoding="utf-8", ) @@ -97,7 +99,7 @@ def test_dotslash_strips_prefix_with_dir(self, tmp_path: Path) -> None: tmpl_dir = tmp_path / "tester" tmpl_dir.mkdir() (tmpl_dir / "config.yaml").write_text( - "name: tester\ndefaults:\n artifacts:\n dir: reports\n output:\n - ./tests/**/*\n", + "name: tester\ndefaults:\n items:\n dir: reports\n output:\n - ./tests/**/*\n", encoding="utf-8", ) @@ -111,7 +113,7 @@ def test_input_comments_from_config(self, tmp_path: Path) -> None: tmpl_dir = tmp_path / "custom" tmpl_dir.mkdir() (tmpl_dir / "config.yaml").write_text( - "name: custom\ndefaults:\n artifacts:\n input_comments: 'Read in order.'\n", + "name: custom\ndefaults:\n items:\n input_comments: 'Read in order.'\n", encoding="utf-8", ) @@ -124,7 +126,7 @@ def test_output_comments_from_config(self, tmp_path: Path) -> None: tmpl_dir = tmp_path / "custom" tmpl_dir.mkdir() (tmpl_dir / "config.yaml").write_text( - "name: custom\ndefaults:\n artifacts:\n output_comments: 'See ADR-001.'\n", + "name: custom\ndefaults:\n items:\n output_comments: 'See ADR-001.'\n", encoding="utf-8", ) @@ -137,7 +139,7 @@ def test_handles_non_dict_artifacts_gracefully(self, tmp_path: Path) -> None: tmpl_dir = tmp_path / "broken" tmpl_dir.mkdir() (tmpl_dir / "config.yaml").write_text( - "name: broken\ndefaults:\n artifacts:\n - foo\n - bar\n", encoding="utf-8" + "name: broken\ndefaults:\n items:\n - foo\n - bar\n", encoding="utf-8" ) result = AgentGenerator().template_partials(tmpl_dir) @@ -150,7 +152,7 @@ def test_handles_non_list_input_output_gracefully(self, tmp_path: Path) -> None: tmpl_dir = tmp_path / "scalar" tmpl_dir.mkdir() (tmpl_dir / "config.yaml").write_text( - "name: scalar\ndefaults:\n artifacts:\n dir: architecture\n input: not-a-list\n output: not-a-list\n", + "name: scalar\ndefaults:\n items:\n dir: architecture\n input: not-a-list\n output: not-a-list\n", encoding="utf-8", ) @@ -164,7 +166,7 @@ def test_product_has_no_input_section(self, tmp_path: Path) -> None: tmpl_dir = tmp_path / "product" tmpl_dir.mkdir() (tmpl_dir / "config.yaml").write_text( - "name: product\ndefaults:\n artifacts:\n dir: product\n output:\n - vision.md\n", + "name: product\ndefaults:\n items:\n dir: product\n output:\n - vision.md\n", encoding="utf-8", ) @@ -178,7 +180,7 @@ def test_uses_custom_artifacts_root(self, tmp_path: Path) -> None: tmpl_dir = tmp_path / "architect" tmpl_dir.mkdir() (tmpl_dir / "config.yaml").write_text( - "name: architect\ndefaults:\n artifacts:\n dir: architecture\n input:\n - product/**/*.md\n" + "name: architect\ndefaults:\n items:\n dir: architecture\n input:\n - product/**/*.md\n" " output:\n - overview.md\n", encoding="utf-8", ) @@ -190,6 +192,39 @@ def test_uses_custom_artifacts_root(self, tmp_path: Path) -> None: assert "docs/" not in result["AGENT_ARTIFACTS_INPUT"] assert "docs/" not in result["AGENT_ARTIFACTS_OUTPUT"] + def test_legacy_artifacts_block_is_still_supported(self, tmp_path: Path) -> None: + """Legacy defaults.artifacts still renders input/output tables.""" + tmpl_dir = tmp_path / "architect" + tmpl_dir.mkdir() + (tmpl_dir / "config.yaml").write_text( + "name: architect\ndefaults:\n artifacts:\n dir: architecture\n input:\n - product/**/*.md\n" + " output:\n - overview.md\n", + encoding="utf-8", + ) + + AgentGenerator._legacy_items_block_warned = False + with pytest.warns(FutureWarning, match="defaults.artifacts"): + result = AgentGenerator().template_partials(tmpl_dir) + + assert "`docs/product/**/*.md`" in result["AGENT_ARTIFACTS_INPUT"] + assert "`docs/architecture/overview.md`" in result["AGENT_ARTIFACTS_OUTPUT"] + + def test_items_block_takes_precedence_over_legacy_artifacts(self, tmp_path: Path) -> None: + """When both blocks exist, defaults.items wins over defaults.artifacts.""" + tmpl_dir = tmp_path / "architect" + tmpl_dir.mkdir() + (tmpl_dir / "config.yaml").write_text( + "name: architect\ndefaults:\n" + " items:\n dir: design\n output:\n - overview.md\n" + " artifacts:\n dir: architecture\n output:\n - legacy.md\n", + encoding="utf-8", + ) + + result = AgentGenerator().template_partials(tmpl_dir) + + assert "`docs/design/overview.md`" in result["AGENT_ARTIFACTS_OUTPUT"] + assert "legacy.md" not in result["AGENT_ARTIFACTS_OUTPUT"] + class TestExtractDefaults: """Tests for AgentGenerator._extract_defaults.""" @@ -269,7 +304,7 @@ def test_handoffs_injected_when_workflow_resolves(self, tmp_path: Path) -> None: }, {"role": "designer", "gate": "optional", "handoffs": []}, ] - gen = AgentGenerator(workflow_stages=stages) + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") config = gen.load_artifact_config(tmpl_dir) assert "handoffs" in config assert config["handoffs"][0]["agent"] == "designer" @@ -336,7 +371,9 @@ def test_returns_empty_when_no_workflow_and_no_prompt(self) -> None: def test_fallback_handoff_without_workflow(self) -> None: """Returns a generic handoff (no agent:) when no workflow is configured but a prompt exists.""" - result = AgentGenerator()._resolve_handoffs("architect", "Do some work.") + result = AgentGenerator(workflow_mode="manual")._resolve_handoffs( + "architect", "Do some work." + ) assert len(result) == 1 assert result[0]["prompt"] == "Do some work." assert result[0]["label"] == "Continue to next stage" @@ -361,7 +398,7 @@ def test_with_workflow_finds_next_role(self) -> None: "handoffs": [{"prompt": "Design done.", "agent": "", "label": ""}], }, ] - gen = AgentGenerator(workflow_stages=stages) + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") result = gen._resolve_handoffs("architect", "Arch done.") assert len(result) == 1 assert result[0]["agent"] == "designer" @@ -377,7 +414,7 @@ def test_last_stage_returns_empty(self) -> None: }, {"role": "release", "gate": "required", "handoffs": []}, ] - gen = AgentGenerator(workflow_stages=stages) + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") assert gen._resolve_handoffs("release", "") == [] def test_unknown_role_returns_empty(self) -> None: @@ -389,7 +426,7 @@ def test_unknown_role_returns_empty(self) -> None: "handoffs": [{"prompt": "Done.", "agent": "", "label": ""}], } ] - gen = AgentGenerator(workflow_stages=stages) + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") assert gen._resolve_handoffs("unknown", "Some prompt.") == [] def test_workflow_prompt_used_as_fallback(self) -> None: @@ -402,7 +439,7 @@ def test_workflow_prompt_used_as_fallback(self) -> None: }, {"role": "tester", "gate": "required", "handoffs": []}, ] - gen = AgentGenerator(workflow_stages=stages) + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") result = gen._resolve_handoffs("engineer", "") assert len(result) == 1 assert "From workflow." in result[0]["prompt"] @@ -425,7 +462,7 @@ def test_multiple_handoffs_per_stage(self) -> None: {"role": "designer", "gate": "optional", "handoffs": []}, {"role": "engineer", "gate": "required", "handoffs": []}, ] - gen = AgentGenerator(workflow_stages=stages) + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") result = gen._resolve_handoffs("architect", "") assert len(result) == 2 assert result[0]["agent"] == "designer" @@ -442,7 +479,7 @@ def test_empty_workflow_prompt_returns_empty(self) -> None: }, {"role": "tester", "gate": "required", "handoffs": []}, ] - gen = AgentGenerator(workflow_stages=stages) + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") assert gen._resolve_handoffs("engineer", "") == [] def test_non_list_stage_handoffs_returns_empty(self) -> None: @@ -451,9 +488,22 @@ def test_non_list_stage_handoffs_returns_empty(self) -> None: {"role": "engineer", "gate": "required", "handoffs": "bad-value"}, {"role": "tester", "gate": "required", "handoffs": []}, ] - gen = AgentGenerator(workflow_stages=stages) + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") assert gen._resolve_handoffs("engineer", "") == [] + def test_empty_stage_handoffs_falls_back_to_agent_prompt(self) -> None: + """Falls back to agent default prompt when stage has no explicit handoffs.""" + stages: list[dict[str, Any]] = [ + {"role": "architect", "gate": "required", "handoffs": []}, + {"role": "designer", "gate": "required", "handoffs": []}, + ] + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") + result = gen._resolve_handoffs("architect", "Use agent default prompt.") + assert len(result) == 1 + assert result[0]["agent"] == "designer" + assert result[0]["prompt"] == "Use agent default prompt." + assert result[0]["label"] == "Go to next stage: Designer" + def test_non_dict_handoff_entry_is_skipped(self) -> None: """Non-dict handoff entries are skipped.""" stages: list[dict[str, Any]] = [ @@ -464,7 +514,7 @@ def test_non_dict_handoff_entry_is_skipped(self) -> None: }, {"role": "tester", "gate": "required", "handoffs": []}, ] - gen = AgentGenerator(workflow_stages=stages) + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") result = gen._resolve_handoffs("engineer", "") assert len(result) == 1 assert result[0]["agent"] == "tester" @@ -478,7 +528,9 @@ def test_returns_empty_string_when_no_prompt(self) -> None: def test_returns_handoff_without_agent_when_no_workflow(self) -> None: """Returns a handoff block without agent: key when no workflow is configured but prompt is set.""" - result = AgentGenerator()._build_handoffs("architect", "Work done.") + result = AgentGenerator(workflow_mode="manual")._build_handoffs( + "architect", "Work done." + ) assert "handoffs:" in result assert "Work done." in result assert "agent:" not in result @@ -493,7 +545,7 @@ def test_returns_handoff_string_with_agent(self) -> None: }, {"role": "designer", "gate": "optional", "handoffs": []}, ] - gen = AgentGenerator(workflow_stages=stages) + gen = AgentGenerator(workflow_stages=stages, workflow_mode="manual") result = gen._build_handoffs("architect", "Work done.") assert "handoffs:" in result assert "agent: designer" in result @@ -502,17 +554,17 @@ class TestBuildTable: """Tests for AgentGenerator._build_table.""" def test_single_column_when_no_notes(self) -> None: - """A single-column Artifact table is produced when no entry has notes.""" + """A single-column Item table is produced when no entry has notes.""" table = AgentGenerator()._build_table( [{"path": "docs/foo.md", "notes": "", "baseline": False}] ) lines = table.splitlines() - assert lines[0].startswith("| Artifact") + assert lines[0].startswith("| Item") assert lines[0].count("|") == 2 assert "`docs/foo.md`" in lines[2] def test_two_column_when_any_entry_has_notes(self) -> None: - """A two-column Artifact | Notes table is produced when any entry has notes.""" + """A two-column Item | Notes table is produced when any entry has notes.""" entries: list[dict[str, str | bool]] = [ {"path": "docs/foo.md", "notes": "", "baseline": False}, {"path": "docs/bar.md", "notes": "important", "baseline": False}, diff --git a/tests/vstack/agents/test_role_wiring.py b/tests/vstack/agents/test_role_wiring.py index eae808e..3113e7a 100644 --- a/tests/vstack/agents/test_role_wiring.py +++ b/tests/vstack/agents/test_role_wiring.py @@ -41,14 +41,14 @@ def test_engineer_template_references_explore_and_analyse() -> None: def test_all_role_templates_reference_concise() -> None: """Every role should expose concise runtime style controls.""" - roles = ["product", "architect", "designer", "engineer", "tester", "release"] + roles = ["product", "architect", "designer", "engineer", "tester", "release", "planner"] for role in roles: content = _read(f"{role}/template.md") assert "@#concise" in content def test_role_configs_follow_stage_handoff_policy() -> None: - """Non-release roles expose a handoff prompt under defaults.handoffs, release is terminal.""" + """Worker roles expose defaults.handoffs prompts; release and planner are terminal.""" roles_with_forward_handoff = ["product", "architect", "designer", "engineer", "tester"] for role in roles_with_forward_handoff: config = _read(f"{role}/config.yaml") @@ -59,13 +59,28 @@ def test_role_configs_follow_stage_handoff_policy() -> None: release_config = _read("release/config.yaml") assert "handoffs:" not in release_config + planner_config = _read("planner/config.yaml") + assert "handoffs:" not in planner_config + + +def test_planner_config_declares_coordinator_tools_and_worker_agents() -> None: + """Planner should be user-invocable and limited to orchestration tools + worker agents.""" + config = _read("planner/config.yaml") + for tool in ("read", "search", "todo", "agent"): + assert f"- {tool}" in config + + for worker in ("product", "architect", "designer", "engineer", "tester", "release"): + assert f"- {worker}" in config + + assert "user-invocable: true" in config + def test_all_role_handoff_targets_are_known_roles() -> None: """Handoff targets in generated agent files should reference known roles.""" from vstack.frontmatter import FrontmatterParser github_dir = Path(__file__).parents[3] / ".github" / "agents" - roles = ["product", "architect", "designer", "engineer", "tester", "release"] + roles = ["product", "architect", "designer", "engineer", "tester", "release", "planner"] valid_targets = set(roles) for role in roles: diff --git a/tests/vstack/agents/test_template_structure.py b/tests/vstack/agents/test_template_structure.py index d40ab49..46e1d99 100644 --- a/tests/vstack/agents/test_template_structure.py +++ b/tests/vstack/agents/test_template_structure.py @@ -5,7 +5,7 @@ from pathlib import Path TEMPLATES_ROOT = Path(__file__).resolve().parents[3] / "src" / "vstack" / "_templates" / "agents" -ROLES = ["product", "architect", "designer", "engineer", "tester", "release"] +ROLES = ["product", "architect", "designer", "engineer", "tester", "release", "planner"] REQUIRED_HEADINGS_IN_ORDER = [ "## identity and purpose", "## responsibilities", @@ -17,7 +17,7 @@ "## workflow and handoffs", "## success criteria", "## failure and escalation rules", - "## artifacts you use", + "## work items", "## completion checklist", "## skills you use", ] diff --git a/tests/vstack/cli/test_init.py b/tests/vstack/cli/test_init.py index 705ba48..9b52a4b 100644 --- a/tests/vstack/cli/test_init.py +++ b/tests/vstack/cli/test_init.py @@ -298,6 +298,250 @@ def _raise_oserror(self: Path, encoding: str = "utf-8") -> str: ) assert "skills" not in new_entries + # ------------------------------------------------------------------ + # _prune_planner_when_manual_mode + # ------------------------------------------------------------------ + + def test_prune_planner_when_manual_mode_removes_unchanged_tracked_file( + self, + tmp_path: Path, + ) -> None: + """Manual mode removes tracked planner file when checksum matches.""" + install_dir = tmp_path / ".github" + planner_file = install_dir / "agents" / "planner.agent.md" + planner_file.parent.mkdir(parents=True) + content = "planner\n" + planner_file.write_text(content, encoding="utf-8") + + entry = SimpleNamespace( + name="planner", + file="agents/planner.agent.md", + checksum=content_hash(content), + checksum_algorithm="sha256", + ) + gen = SimpleNamespace( + config=SimpleNamespace(type_name="agent", manifest_key="agents"), + workflow_mode="manual", + ) + new_entries: dict[str, list[Any]] = {} + + InitCommand._prune_planner_when_manual_mode( + install_dir=install_dir, + gen=gen, + existing_entries={"agent/planner": entry}, + new_entries=new_entries, + colors=SimpleNamespace(CYAN="", RESET="", DIM="", YELLOW="", GREEN="", BOLD=""), + prefix="", + dry_run=False, + ) + + assert not planner_file.exists() + assert "agents" not in new_entries + + def test_prune_planner_when_manual_mode_preserves_modified_file( + self, + tmp_path: Path, + ) -> None: + """Manual mode preserves locally modified planner file and keeps it tracked.""" + install_dir = tmp_path / ".github" + planner_file = install_dir / "agents" / "planner.agent.md" + planner_file.parent.mkdir(parents=True) + planner_file.write_text("planner modified\n", encoding="utf-8") + + entry = SimpleNamespace( + name="planner", + file="agents/planner.agent.md", + checksum=content_hash("planner original\n"), + checksum_algorithm="sha256", + ) + gen = SimpleNamespace( + config=SimpleNamespace(type_name="agent", manifest_key="agents"), + workflow_mode="manual", + ) + new_entries: dict[str, list[Any]] = {} + + InitCommand._prune_planner_when_manual_mode( + install_dir=install_dir, + gen=gen, + existing_entries={"agent/planner": entry}, + new_entries=new_entries, + colors=SimpleNamespace(CYAN="", RESET="", DIM="", YELLOW="", GREEN="", BOLD=""), + prefix="", + dry_run=False, + ) + + assert planner_file.exists() + assert "agents" in new_entries + assert len(new_entries["agents"]) == 1 + + @pytest.mark.parametrize( + ("type_name", "workflow_mode"), + [("skill", "manual"), ("agent", "agentic")], + ) + def test_prune_planner_noop_for_non_agent_or_non_manual_mode( + self, + tmp_path: Path, + type_name: str, + workflow_mode: str, + ) -> None: + """Prune helper should no-op unless generator is agent type in manual mode.""" + install_dir = tmp_path / ".github" + planner_file = install_dir / "agents" / "planner.agent.md" + planner_file.parent.mkdir(parents=True) + planner_file.write_text("planner\n", encoding="utf-8") + + entry = SimpleNamespace( + name="planner", + file="agents/planner.agent.md", + checksum=content_hash("planner\n"), + checksum_algorithm="sha256", + ) + gen = SimpleNamespace( + config=SimpleNamespace(type_name=type_name, manifest_key="agents"), + workflow_mode=workflow_mode, + ) + new_entries: dict[str, list[Any]] = {} + + InitCommand._prune_planner_when_manual_mode( + install_dir=install_dir, + gen=gen, + existing_entries={"agent/planner": entry}, + new_entries=new_entries, + colors=SimpleNamespace(CYAN="", RESET="", DIM="", YELLOW="", GREEN="", BOLD=""), + prefix="", + dry_run=False, + ) + + assert planner_file.exists() + assert new_entries == {} + + def test_prune_planner_noop_when_tracked_entry_file_is_missing(self, tmp_path: Path) -> None: + """Tracked planner entry with missing file should be ignored safely.""" + install_dir = tmp_path / ".github" + gen = SimpleNamespace( + config=SimpleNamespace(type_name="agent", manifest_key="agents"), + workflow_mode="manual", + ) + new_entries: dict[str, list[Any]] = {} + + entry = SimpleNamespace( + name="planner", + file="agents/planner.agent.md", + checksum=content_hash("planner\n"), + checksum_algorithm="sha256", + ) + + InitCommand._prune_planner_when_manual_mode( + install_dir=install_dir, + gen=gen, + existing_entries={"agent/planner": entry}, + new_entries=new_entries, + colors=SimpleNamespace(CYAN="", RESET="", DIM="", YELLOW="", GREEN="", BOLD=""), + prefix="", + dry_run=False, + ) + + assert new_entries == {} + + def test_prune_planner_noop_when_planner_entry_not_tracked(self, tmp_path: Path) -> None: + """Manual mode should no-op when manifest has no planner entry.""" + install_dir = tmp_path / ".github" + gen = SimpleNamespace( + config=SimpleNamespace(type_name="agent", manifest_key="agents"), + workflow_mode="manual", + ) + new_entries: dict[str, list[Any]] = {} + + InitCommand._prune_planner_when_manual_mode( + install_dir=install_dir, + gen=gen, + existing_entries={}, + new_entries=new_entries, + colors=SimpleNamespace(CYAN="", RESET="", DIM="", YELLOW="", GREEN="", BOLD=""), + prefix="", + dry_run=False, + ) + + assert new_entries == {} + + def test_prune_planner_preserves_when_checksum_algorithm_is_invalid( + self, + tmp_path: Path, + ) -> None: + """Invalid checksum algorithm should be treated as non-removable and preserved.""" + install_dir = tmp_path / ".github" + planner_file = install_dir / "agents" / "planner.agent.md" + planner_file.parent.mkdir(parents=True) + planner_file.write_text("planner\n", encoding="utf-8") + + entry = SimpleNamespace( + name="planner", + file="agents/planner.agent.md", + checksum="abc123", + checksum_algorithm="sha999", + ) + gen = SimpleNamespace( + config=SimpleNamespace(type_name="agent", manifest_key="agents"), + workflow_mode="manual", + ) + new_entries: dict[str, list[Any]] = {} + + InitCommand._prune_planner_when_manual_mode( + install_dir=install_dir, + gen=gen, + existing_entries={"agent/planner": entry}, + new_entries=new_entries, + colors=SimpleNamespace(CYAN="", RESET="", DIM="", YELLOW="", GREEN="", BOLD=""), + prefix="", + dry_run=False, + ) + + assert planner_file.exists() + assert "agents" in new_entries + assert len(new_entries["agents"]) == 1 + + def test_prune_planner_ignores_parent_rmdir_error( + self, + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + ) -> None: + """Parent directory cleanup failure should be swallowed after planner removal.""" + install_dir = tmp_path / ".github" + planner_file = install_dir / "agents" / "planner.agent.md" + planner_file.parent.mkdir(parents=True) + content = "planner\n" + planner_file.write_text(content, encoding="utf-8") + + entry = SimpleNamespace( + name="planner", + file="agents/planner.agent.md", + checksum=content_hash(content), + checksum_algorithm="sha256", + ) + gen = SimpleNamespace( + config=SimpleNamespace(type_name="agent", manifest_key="agents"), + workflow_mode="manual", + ) + new_entries: dict[str, list[Any]] = {} + + def _raise_rmdir(self: Path) -> None: + del self + raise OSError("rmdir blocked") + + monkeypatch.setattr(Path, "rmdir", _raise_rmdir) + + InitCommand._prune_planner_when_manual_mode( + install_dir=install_dir, + gen=gen, + existing_entries={"agent/planner": entry}, + new_entries=new_entries, + colors=SimpleNamespace(CYAN="", RESET="", DIM="", YELLOW="", GREEN="", BOLD=""), + prefix="", + dry_run=False, + ) + + assert not planner_file.exists() + # ------------------------------------------------------------------ # _print_summary # ------------------------------------------------------------------ diff --git a/tests/vstack/cli/test_interface.py b/tests/vstack/cli/test_interface.py index d5650bb..8a86421 100644 --- a/tests/vstack/cli/test_interface.py +++ b/tests/vstack/cli/test_interface.py @@ -60,13 +60,15 @@ def __init__( self, *, templates_root, - artifacts_root: str = "docs", + items_root: str = "docs", workflow_stages=None, + workflow_mode: str = "agentic", excluded_names=None, ) -> None: self.templates_root = templates_root - self.artifacts_root = artifacts_root + self.items_root = items_root self.workflow_stages = workflow_stages + self.workflow_mode = workflow_mode self.excluded_names = excluded_names @@ -164,46 +166,44 @@ def test_resolve_only_for_scope_accepts_allowed_global_types(self) -> None: assert CommandLineInterface.resolve_only_for_scope(args) == ["skill", "agent"] -class TestReadArtifactsRoot: - """Tests for CommandLineInterface._read_artifacts_root.""" +class TestReadItemsRoot: + """Tests for CommandLineInterface._read_items_root.""" def test_returns_default_when_install_dir_is_none(self) -> None: """Returns ARTIFACTS_DOCS_ROOT when no install dir is provided.""" from vstack.constants import ARTIFACTS_DOCS_ROOT - assert CommandLineInterface._read_artifacts_root(None) == ARTIFACTS_DOCS_ROOT + assert CommandLineInterface._read_items_root(None) == ARTIFACTS_DOCS_ROOT def test_returns_default_when_config_file_absent(self, tmp_path: Path) -> None: """Returns ARTIFACTS_DOCS_ROOT when .vstack/config.yaml does not exist.""" from vstack.constants import ARTIFACTS_DOCS_ROOT install_dir = tmp_path / ".github" - assert CommandLineInterface._read_artifacts_root(install_dir) == ARTIFACTS_DOCS_ROOT + assert CommandLineInterface._read_items_root(install_dir) == ARTIFACTS_DOCS_ROOT def test_returns_value_from_config(self, tmp_path: Path) -> None: - """Returns the artifacts.root value from .vstack/config.yaml.""" + """Returns the items.root value from .vstack/config.yaml.""" vstack_dir = tmp_path / ".vstack" vstack_dir.mkdir() - (vstack_dir / "config.yaml").write_text( - "artifacts:\n root: documentation\n", encoding="utf-8" - ) + (vstack_dir / "config.yaml").write_text("items:\n root: documentation\n", encoding="utf-8") install_dir = tmp_path / ".github" - assert CommandLineInterface._read_artifacts_root(install_dir) == "documentation" + assert CommandLineInterface._read_items_root(install_dir) == "documentation" def test_returns_default_when_value_is_blank(self, tmp_path: Path) -> None: - """Returns ARTIFACTS_DOCS_ROOT when artifacts.root is present but blank.""" + """Returns ARTIFACTS_DOCS_ROOT when items.root is present but blank.""" from vstack.constants import ARTIFACTS_DOCS_ROOT vstack_dir = tmp_path / ".vstack" vstack_dir.mkdir() - (vstack_dir / "config.yaml").write_text("artifacts:\n root:\n", encoding="utf-8") + (vstack_dir / "config.yaml").write_text("items:\n root:\n", encoding="utf-8") install_dir = tmp_path / ".github" - assert CommandLineInterface._read_artifacts_root(install_dir) == ARTIFACTS_DOCS_ROOT + assert CommandLineInterface._read_items_root(install_dir) == ARTIFACTS_DOCS_ROOT def test_returns_default_when_key_absent_in_config(self, tmp_path: Path) -> None: - """Returns ARTIFACTS_DOCS_ROOT when config.yaml exists but has no artifacts.root key.""" + """Returns ARTIFACTS_DOCS_ROOT when config.yaml exists but has no items.root key.""" from vstack.constants import ARTIFACTS_DOCS_ROOT vstack_dir = tmp_path / ".vstack" @@ -211,15 +211,15 @@ def test_returns_default_when_key_absent_in_config(self, tmp_path: Path) -> None (vstack_dir / "config.yaml").write_text("exclude:\n prompts: all\n", encoding="utf-8") install_dir = tmp_path / ".github" - assert CommandLineInterface._read_artifacts_root(install_dir) == ARTIFACTS_DOCS_ROOT + assert CommandLineInterface._read_items_root(install_dir) == ARTIFACTS_DOCS_ROOT - def test_run_passes_artifacts_root_from_config_to_service( + def test_run_passes_items_root_from_config_to_service( self, monkeypatch, tmp_path: Path ) -> None: - """run() reads artifacts.root from .vstack/config.yaml and passes it to the service.""" + """run() reads items.root from .vstack/config.yaml and passes it to the service.""" vstack_dir = tmp_path / ".vstack" vstack_dir.mkdir() - (vstack_dir / "config.yaml").write_text("artifacts:\n root: custom\n", encoding="utf-8") + (vstack_dir / "config.yaml").write_text("items:\n root: custom\n", encoding="utf-8") install_dir = tmp_path / ".github" args = argparse.Namespace(command="install", only=None, use_global=False) @@ -232,11 +232,13 @@ def __init__( self, *, templates_root, - artifacts_root: str = "docs", + items_root: str = "docs", workflow_stages=None, + workflow_mode: str = "agentic", excluded_names=None, ) -> None: - captured.append(artifacts_root) + del templates_root, workflow_stages, workflow_mode, excluded_names + captured.append(items_root) monkeypatch.setattr( CommandLineInterface, @@ -253,6 +255,22 @@ def __init__( assert captured == ["custom"] + def test_returns_legacy_artifacts_root_when_items_root_missing( + self, tmp_path: Path, capsys: pytest.CaptureFixture[str] + ) -> None: + """Legacy artifacts.root still works and emits a deprecation warning.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "artifacts:\n root: legacy-docs\n", encoding="utf-8" + ) + + install_dir = tmp_path / ".github" + CommandLineInterface._legacy_items_root_warned = False + assert CommandLineInterface._read_items_root(install_dir) == "legacy-docs" + captured = capsys.readouterr() + assert "legacy 'artifacts.root'" in captured.err + class TestReadWorkflowStages: """Tests for CommandLineInterface._read_workflow_stages.""" @@ -363,6 +381,81 @@ def test_returns_empty_when_stages_not_a_list(self, tmp_path: Path) -> None: assert result == [] +class TestReadWorkflowMode: + """Tests for CommandLineInterface._read_workflow_mode.""" + + def test_returns_manual_when_install_dir_is_none(self) -> None: + """Defaults to agentic mode when no install dir is available.""" + assert CommandLineInterface._read_workflow_mode(None) == "agentic" + + def test_returns_manual_when_config_absent(self, tmp_path: Path) -> None: + """Defaults to agentic mode when .vstack/config.yaml is missing.""" + assert CommandLineInterface._read_workflow_mode(tmp_path / ".github") == "agentic" + + def test_returns_mode_from_config_when_valid(self, tmp_path: Path) -> None: + """Returns parsed workflow.mode when value is supported.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text("workflow:\n mode: agentic\n", encoding="utf-8") + assert CommandLineInterface._read_workflow_mode(tmp_path / ".github") == "agentic" + + def test_returns_manual_when_mode_invalid(self, tmp_path: Path) -> None: + """Invalid mode values are normalized to agentic.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "workflow:\n mode: orchestration\n", encoding="utf-8" + ) + assert CommandLineInterface._read_workflow_mode(tmp_path / ".github") == "agentic" + + def test_returns_manual_when_mode_not_string(self, tmp_path: Path) -> None: + """Non-string workflow.mode values are normalized to agentic.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text("workflow:\n mode: 1\n", encoding="utf-8") + assert CommandLineInterface._read_workflow_mode(tmp_path / ".github") == "agentic" + + def test_run_passes_workflow_mode_to_service(self, monkeypatch, tmp_path: Path) -> None: + """run() reads workflow.mode and passes it into service construction.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text("workflow:\n mode: hybrid\n", encoding="utf-8") + + install_dir = tmp_path / ".github" + args = argparse.Namespace(command="install", only=None, use_global=False) + parser = _Parser(args=args, resolved_target=install_dir) + command = _Command(exit_code=0) + captured: list[str] = [] + + class _CapturingService: + def __init__( + self, + *, + templates_root, + items_root: str = "docs", + workflow_stages=None, + workflow_mode: str = "agentic", + excluded_names=None, + ) -> None: + del templates_root, items_root, workflow_stages, excluded_names + captured.append(workflow_mode) + + monkeypatch.setattr( + CommandLineInterface, + "_build_command_registry", + lambda self, service: {"install": command}, + ) + + interface = CommandLineInterface( + parser_cls=cast(Any, lambda: parser), + service_cls=cast(Any, _CapturingService), + templates_root=tmp_path, + ) + interface.run() + + assert captured == ["hybrid"] + + class TestParseStageHandoffs: """Tests for CommandLineInterface._parse_stage_handoffs.""" diff --git a/tests/vstack/cli/test_migrate.py b/tests/vstack/cli/test_migrate.py index 59b0e46..383565c 100644 --- a/tests/vstack/cli/test_migrate.py +++ b/tests/vstack/cli/test_migrate.py @@ -211,33 +211,45 @@ def test_leaves_path_unchanged_when_prefix_does_not_match(self) -> None: assert result == "other/architecture/overview.md" -class TestReadArtifactsRoot: - """_read_artifacts_root reads artifacts.root from config.yaml.""" +class TestReadItemsRoot: + """_read_items_root reads items.root from config.yaml.""" def test_returns_default_when_config_absent(self, tmp_path: Path) -> None: """Returns the default docs root when no config.yaml exists.""" - assert MigrateCommand._read_artifacts_root(tmp_path) == ARTIFACTS_DOCS_ROOT + assert MigrateCommand._read_items_root(tmp_path) == ARTIFACTS_DOCS_ROOT def test_returns_configured_root(self, tmp_path: Path) -> None: """Returns the custom root from config.yaml.""" vstack_dir = tmp_path / ".vstack" vstack_dir.mkdir() - (vstack_dir / "config.yaml").write_text("artifacts:\n root: content\n", encoding="utf-8") - assert MigrateCommand._read_artifacts_root(tmp_path) == "content" + (vstack_dir / "config.yaml").write_text("items:\n root: content\n", encoding="utf-8") + assert MigrateCommand._read_items_root(tmp_path) == "content" - def test_returns_default_when_artifacts_not_dict(self, tmp_path: Path) -> None: - """Returns default when artifacts key is not a mapping.""" + def test_returns_default_when_items_not_dict(self, tmp_path: Path) -> None: + """Returns default when items key is not a mapping.""" vstack_dir = tmp_path / ".vstack" vstack_dir.mkdir() - (vstack_dir / "config.yaml").write_text("artifacts: all\n", encoding="utf-8") - assert MigrateCommand._read_artifacts_root(tmp_path) == ARTIFACTS_DOCS_ROOT + (vstack_dir / "config.yaml").write_text("items: all\n", encoding="utf-8") + assert MigrateCommand._read_items_root(tmp_path) == ARTIFACTS_DOCS_ROOT + + def test_returns_legacy_artifacts_root_when_items_missing( + self, tmp_path: Path, capsys: pytest.CaptureFixture[str] + ) -> None: + """Legacy artifacts.root remains accepted and emits a deprecation warning.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text("artifacts:\n root: content\n", encoding="utf-8") + MigrateCommand._legacy_items_root_warned = False + assert MigrateCommand._read_items_root(tmp_path) == "content" + captured = capsys.readouterr() + assert "legacy 'artifacts.root'" in captured.err def test_returns_default_when_root_blank(self, tmp_path: Path) -> None: - """Returns default when artifacts.root is blank.""" + """Returns default when items.root is blank.""" vstack_dir = tmp_path / ".vstack" vstack_dir.mkdir() (vstack_dir / "config.yaml").write_text("artifacts:\n root: ''\n", encoding="utf-8") - assert MigrateCommand._read_artifacts_root(tmp_path) == ARTIFACTS_DOCS_ROOT + assert MigrateCommand._read_items_root(tmp_path) == ARTIFACTS_DOCS_ROOT class _FakeColors(Colors): @@ -251,6 +263,284 @@ class _FakeColors(Colors): BLUE = "" +class TestUpgradeLegacyKeys: + """Legacy key upgrades for items migration.""" + + def test_upgrades_project_config_root_key(self, tmp_path: Path) -> None: + """Adds items.root when only artifacts.root is configured.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + config_path = vstack_dir / "config.yaml" + config_path.write_text("artifacts:\n root: content\n", encoding="utf-8") + + changed = MigrateCommand._upgrade_project_items_root_key( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 1 + content = config_path.read_text(encoding="utf-8") + assert "items:" in content + assert "root: content" in content + + def test_project_config_upgrade_is_dry_run_only(self, tmp_path: Path) -> None: + """Dry-run reports project config upgrade without writing the file.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + config_path = vstack_dir / "config.yaml" + original = "artifacts:\n root: content\n" + config_path.write_text(original, encoding="utf-8") + + changed = MigrateCommand._upgrade_project_items_root_key( + project_root=tmp_path, + dry_run=True, + ) + + assert changed == 1 + assert config_path.read_text(encoding="utf-8") == original + + def test_project_config_upgrade_skips_when_items_root_already_set(self, tmp_path: Path) -> None: + """Does not change config when items.root is already present.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + config_path = vstack_dir / "config.yaml" + config_path.write_text("items:\n root: docs\n", encoding="utf-8") + + changed = MigrateCommand._upgrade_project_items_root_key( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 0 + + def test_project_config_upgrade_skips_when_artifacts_block_invalid( + self, tmp_path: Path + ) -> None: + """Does not change config when artifacts is not a mapping.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text("artifacts: all\n", encoding="utf-8") + + changed = MigrateCommand._upgrade_project_items_root_key( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 0 + + def test_project_config_upgrade_skips_when_artifacts_root_blank(self, tmp_path: Path) -> None: + """Does not change config when legacy artifacts.root is blank.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text("artifacts:\n root: ''\n", encoding="utf-8") + + changed = MigrateCommand._upgrade_project_items_root_key( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 0 + + def test_upgrades_agent_template_defaults_key(self, tmp_path: Path) -> None: + """Adds defaults.items from legacy defaults.artifacts in local templates.""" + config_path = tmp_path / ".vstack" / "templates" / "architect" / "config.yaml" + config_path.parent.mkdir(parents=True) + config_path.write_text( + "name: architect\ndefaults:\n artifacts:\n dir: architecture\n", + encoding="utf-8", + ) + + changed = MigrateCommand._upgrade_agent_template_items_keys( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 1 + parsed = yaml.safe_load(config_path.read_text(encoding="utf-8")) + assert parsed["defaults"]["items"]["dir"] == "architecture" + + def test_agent_template_upgrade_is_dry_run_only(self, tmp_path: Path) -> None: + """Dry-run reports agent template upgrade without writing files.""" + config_path = tmp_path / ".vstack" / "templates" / "architect" / "config.yaml" + config_path.parent.mkdir(parents=True) + original = "name: architect\ndefaults:\n artifacts:\n dir: architecture\n" + config_path.write_text(original, encoding="utf-8") + + changed = MigrateCommand._upgrade_agent_template_items_keys( + project_root=tmp_path, + dry_run=True, + ) + + assert changed == 1 + assert config_path.read_text(encoding="utf-8") == original + + def test_agent_template_upgrade_skips_non_mapping_yaml(self, tmp_path: Path) -> None: + """Skips template config files whose YAML root is not a mapping.""" + config_path = tmp_path / ".vstack" / "templates" / "architect" / "config.yaml" + config_path.parent.mkdir(parents=True) + config_path.write_text("- just\n- a\n- list\n", encoding="utf-8") + + changed = MigrateCommand._upgrade_agent_template_items_keys( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 0 + + def test_agent_template_upgrade_skips_when_defaults_invalid(self, tmp_path: Path) -> None: + """Skips template config files with non-mapping defaults.""" + config_path = tmp_path / ".vstack" / "templates" / "architect" / "config.yaml" + config_path.parent.mkdir(parents=True) + config_path.write_text("name: architect\ndefaults: all\n", encoding="utf-8") + + changed = MigrateCommand._upgrade_agent_template_items_keys( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 0 + + def test_agent_template_upgrade_skips_when_items_already_present(self, tmp_path: Path) -> None: + """Skips template config files that already define defaults.items.""" + config_path = tmp_path / ".vstack" / "templates" / "architect" / "config.yaml" + config_path.parent.mkdir(parents=True) + config_path.write_text( + "name: architect\ndefaults:\n items:\n dir: architecture\n", + encoding="utf-8", + ) + + changed = MigrateCommand._upgrade_agent_template_items_keys( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 0 + + def test_agent_template_upgrade_skips_when_artifacts_invalid(self, tmp_path: Path) -> None: + """Skips template config files where defaults.artifacts is not a mapping.""" + config_path = tmp_path / ".vstack" / "templates" / "architect" / "config.yaml" + config_path.parent.mkdir(parents=True) + config_path.write_text( + "name: architect\ndefaults:\n artifacts: all\n", + encoding="utf-8", + ) + + changed = MigrateCommand._upgrade_agent_template_items_keys( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 0 + + def test_cleanup_removes_legacy_commented_workflow_example(self, tmp_path: Path) -> None: + """Removes trailing commented ACTIVE CONFIGURATION example block.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + config_path = vstack_dir / "config.yaml" + config_path.write_text( + "workflow:\n" + " mode: agentic\n" + "# -----------------------------------------------------------------------------\n" + "# ACTIVE CONFIGURATION (UNCOMMENT AND EDIT)\n" + "# -----------------------------------------------------------------------------\n" + "# workflow:\n" + "# mode: agentic\n", + encoding="utf-8", + ) + + changed = MigrateCommand._cleanup_commented_workflow_example( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 1 + content = config_path.read_text(encoding="utf-8") + assert "ACTIVE CONFIGURATION (UNCOMMENT AND EDIT)" not in content + assert "workflow:\n mode: agentic" in content + + def test_cleanup_dry_run_does_not_write(self, tmp_path: Path) -> None: + """Dry-run reports cleanup without changing file content.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + config_path = vstack_dir / "config.yaml" + original = "# ACTIVE CONFIGURATION (UNCOMMENT AND EDIT)\n# workflow:\n# mode: agentic\n" + config_path.write_text(original, encoding="utf-8") + + changed = MigrateCommand._cleanup_commented_workflow_example( + project_root=tmp_path, + dry_run=True, + ) + + assert changed == 1 + assert config_path.read_text(encoding="utf-8") == original + + def test_cleanup_is_noop_when_marker_absent(self, tmp_path: Path) -> None: + """No changes are reported when the marker block is absent.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + config_path = vstack_dir / "config.yaml" + config_path.write_text("workflow:\n mode: agentic\n", encoding="utf-8") + + changed = MigrateCommand._cleanup_commented_workflow_example( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 0 + + def test_cleanup_preserves_non_comment_lines_after_marker(self, tmp_path: Path) -> None: + """Marker cleanup stops once active YAML resumes after commented lines.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + config_path = vstack_dir / "config.yaml" + config_path.write_text( + "# ACTIVE CONFIGURATION (UNCOMMENT AND EDIT)\n" + "# workflow:\n" + "workflow:\n" + " mode: agentic\n", + encoding="utf-8", + ) + + changed = MigrateCommand._cleanup_commented_workflow_example( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 1 + content = config_path.read_text(encoding="utf-8") + assert content == "workflow:\n mode: agentic\n" + + def test_cleanup_preserves_active_workflow_handoffs(self, tmp_path: Path) -> None: + """Cleanup never removes active workflow and handoff settings.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + config_path = vstack_dir / "config.yaml" + config_path.write_text( + "# ACTIVE CONFIGURATION (UNCOMMENT AND EDIT)\n" + "# workflow:\n" + "workflow:\n" + " mode: manual\n" + " stages:\n" + " - role: architect\n" + " gate: required\n" + " handoffs:\n" + " prompt: Keep this\n", + encoding="utf-8", + ) + + changed = MigrateCommand._cleanup_commented_workflow_example( + project_root=tmp_path, + dry_run=False, + ) + + assert changed == 1 + content = config_path.read_text(encoding="utf-8") + assert "workflow:" in content + assert "mode: manual" in content + assert "handoffs:" in content + assert "prompt: Keep this" in content + + class TestApplyMoves: """_apply_moves relocates files and reports results.""" @@ -264,7 +554,7 @@ def test_moves_file_to_new_path(self, tmp_path: Path) -> None: moved, skipped = MigrateCommand._apply_moves( moves=moves, project_root=tmp_path, - artifacts_root="docs", + items_root="docs", dry_run=False, colors=_FakeColors, ) @@ -279,7 +569,7 @@ def test_skips_absent_old_path(self, tmp_path: Path) -> None: moved, skipped = MigrateCommand._apply_moves( moves=moves, project_root=tmp_path, - artifacts_root="docs", + items_root="docs", dry_run=False, colors=_FakeColors, ) @@ -298,7 +588,7 @@ def test_skips_when_destination_exists(self, tmp_path: Path) -> None: moved, skipped = MigrateCommand._apply_moves( moves=moves, project_root=tmp_path, - artifacts_root="docs", + items_root="docs", dry_run=False, colors=_FakeColors, ) @@ -316,7 +606,7 @@ def test_dry_run_does_not_move_files(self, tmp_path: Path) -> None: moved, skipped = MigrateCommand._apply_moves( moves=moves, project_root=tmp_path, - artifacts_root="docs", + items_root="docs", dry_run=True, colors=_FakeColors, ) @@ -334,7 +624,7 @@ def test_skips_moves_with_empty_paths(self, tmp_path: Path) -> None: moved, skipped = MigrateCommand._apply_moves( moves=moves, project_root=tmp_path, - artifacts_root="docs", + items_root="docs", dry_run=False, colors=_FakeColors, ) @@ -351,14 +641,14 @@ def test_creates_parent_directories(self, tmp_path: Path) -> None: MigrateCommand._apply_moves( moves=moves, project_root=tmp_path, - artifacts_root="docs", + items_root="docs", dry_run=False, colors=_FakeColors, ) assert (tmp_path / "docs" / "deep" / "nested" / "b.md").exists() - def test_substitutes_custom_artifacts_root_in_new_path(self, tmp_path: Path) -> None: - """Applies the custom artifacts root when resolving new paths.""" + def test_substitutes_custom_items_root_in_new_path(self, tmp_path: Path) -> None: + """Applies the custom items.root when resolving new paths.""" old = tmp_path / "docs" / "a.md" old.parent.mkdir(parents=True) old.write_text("content", encoding="utf-8") @@ -367,7 +657,7 @@ def test_substitutes_custom_artifacts_root_in_new_path(self, tmp_path: Path) -> MigrateCommand._apply_moves( moves=moves, project_root=tmp_path, - artifacts_root="content", + items_root="content", dry_run=False, colors=_FakeColors, ) @@ -416,6 +706,22 @@ def test_returns_0_when_nothing_to_migrate( assert cmd.run(context=context) == 0 assert "Nothing to migrate" in capsys.readouterr().out + def test_prints_config_upgrade_message_when_from_gte_to(self, tmp_path: Path) -> None: + """Reports config upgrade when no path move is required.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text("artifacts:\n root: content\n", encoding="utf-8") + + cmd = MigrateCommand(_service()) + context = _context(Namespace(dry_run=False, target=str(tmp_path), from_major=3, to_major=3)) + + with patch("builtins.print") as mock_print: + result = cmd.run(context=context) + + assert result == 0 + printed = "\n".join(str(call.args[0]) for call in mock_print.call_args_list if call.args) + assert "Updated legacy config keys" in printed + def test_returns_1_when_major_not_detectable(self, tmp_path: Path) -> None: """Returns 1 and writes to stderr when from_major cannot be detected.""" cmd = MigrateCommand(_service()) diff --git a/tests/vstack/cli/test_service.py b/tests/vstack/cli/test_service.py index 79e1a93..b4e35c3 100644 --- a/tests/vstack/cli/test_service.py +++ b/tests/vstack/cli/test_service.py @@ -66,13 +66,13 @@ def test_cli_class_uses_known_types(self) -> None: names = {g.config.type_name for g in svc.generators} assert names == {"skill", "agent", "instruction", "prompt"} - def test_custom_artifacts_root_is_passed_to_agent_generator(self, tmp_path: Path) -> None: - """artifacts_root kwarg is forwarded to the AgentGenerator instance.""" + def test_custom_items_root_is_passed_to_agent_generator(self, tmp_path: Path) -> None: + """items_root kwarg is forwarded to the AgentGenerator instance.""" from vstack.agents.generator import AgentGenerator - svc = CommandService(templates_root=tmp_path, artifacts_root="documentation") + svc = CommandService(templates_root=tmp_path, items_root="documentation") agent_gen = next(g for g in svc.generators if isinstance(g, AgentGenerator)) - assert agent_gen.artifacts_root == "documentation" + assert agent_gen.items_root == "documentation" def test_gen_for_returns_none_for_unknown_type(self) -> None: """gen_for() returns None for an unrecognized type name.""" From 4d3419b106d7b7e56fc4741e6a1edc20d6383ad0 Mon Sep 17 00:00:00 2001 From: Erik Schaareman <erik@schaareman.org> Date: Sun, 10 May 2026 15:16:27 +0200 Subject: [PATCH 02/11] feat(hooks): add hooks ADR, design, and multi-template baseline --- .github/agents/architect.agent.md | 2 +- .github/agents/designer.agent.md | 2 +- .github/agents/engineer.agent.md | 2 +- .github/agents/planner.agent.md | 8 +- .github/agents/product.agent.md | 2 +- .github/agents/release.agent.md | 2 +- .github/agents/tester.agent.md | 2 +- .github/hooks/post-commit-security-scan.json | 14 ++ .github/hooks/post-edit-format.json | 8 + .github/hooks/pre-tool-safety-gate.json | 14 ++ .github/hooks/session-audit.json | 14 ++ .github/instructions/git.instructions.md | 2 +- .github/instructions/helm.instructions.md | 2 +- .github/instructions/java.instructions.md | 2 +- .github/instructions/k8s.instructions.md | 2 +- .github/instructions/markdown.instructions.md | 2 +- .github/instructions/python.instructions.md | 2 +- .github/instructions/rancher.instructions.md | 2 +- .github/instructions/security.instructions.md | 2 +- .../instructions/terraform.instructions.md | 2 +- .../instructions/terragrunt.instructions.md | 2 +- .github/instructions/testing.instructions.md | 2 +- .../instructions/typescript.instructions.md | 2 +- .github/prompts/api-design-review.prompt.md | 2 +- .github/prompts/architecture-risk.prompt.md | 2 +- .github/prompts/code-review.prompt.md | 2 +- .github/prompts/dependency-audit.prompt.md | 2 +- .github/prompts/incident-timeline.prompt.md | 2 +- .github/prompts/migration-safety.prompt.md | 2 +- .github/prompts/release-readiness.prompt.md | 2 +- .github/skills/adr/SKILL.md | 2 +- .github/skills/analyse/SKILL.md | 2 +- .github/skills/architecture/SKILL.md | 2 +- .github/skills/aws-cli/SKILL.md | 2 +- .github/skills/cicd/SKILL.md | 2 +- .github/skills/cloudformation/SKILL.md | 2 +- .github/skills/code-review/SKILL.md | 2 +- .github/skills/codeql/SKILL.md | 2 +- .github/skills/concise/SKILL.md | 2 +- .github/skills/consult/SKILL.md | 2 +- .github/skills/container/SKILL.md | 2 +- .github/skills/conventional-commit/SKILL.md | 2 +- .github/skills/debug/SKILL.md | 2 +- .github/skills/dependabot/SKILL.md | 2 +- .github/skills/dependency/SKILL.md | 2 +- .github/skills/design/SKILL.md | 2 +- .github/skills/docs/SKILL.md | 2 +- .github/skills/explore/SKILL.md | 2 +- .github/skills/gdpr/SKILL.md | 2 +- .github/skills/gh-issues/SKILL.md | 2 +- .github/skills/gh-release/SKILL.md | 2 +- .github/skills/guardrails/SKILL.md | 2 +- .github/skills/helm/SKILL.md | 2 +- .github/skills/incident/SKILL.md | 2 +- .github/skills/inspect/SKILL.md | 2 +- .github/skills/k8s/SKILL.md | 2 +- .github/skills/migrate/SKILL.md | 2 +- .github/skills/onboard/SKILL.md | 2 +- .github/skills/openapi/SKILL.md | 2 +- .github/skills/performance/SKILL.md | 2 +- .github/skills/postmortem/SKILL.md | 2 +- .github/skills/pr/SKILL.md | 2 +- .github/skills/rancher/SKILL.md | 2 +- .github/skills/rca/SKILL.md | 2 +- .github/skills/refactor/SKILL.md | 2 +- .github/skills/release-notes/SKILL.md | 2 +- .github/skills/requirements/SKILL.md | 2 +- .github/skills/secret-scan/SKILL.md | 2 +- .github/skills/security/SKILL.md | 2 +- .github/skills/terraform/SKILL.md | 2 +- .github/skills/terragrunt/SKILL.md | 2 +- .github/skills/threat-model/SKILL.md | 2 +- .github/skills/verify/SKILL.md | 2 +- .github/skills/vision/SKILL.md | 2 +- .vstack/vstack.json | 174 ++++++++++-------- .../adr/027-repository-hooks-artifact-type.md | 114 ++++++++++++ docs/design/hooks.md | 108 +++++++++++ docs/design/overview.md | 4 +- docs/product/requirements.md | 5 +- docs/product/roadmap.md | 29 ++- pyproject.toml | 9 + .../post-commit-security-scan/config.yaml | 1 + .../hooks/post-commit-security-scan/hook.json | 14 ++ .../hooks/post-edit-format/config.yaml | 1 + .../hooks/post-edit-format/hook.json | 8 + .../hooks/pre-tool-safety-gate/config.yaml | 1 + .../hooks/pre-tool-safety-gate/hook.json | 14 ++ .../hooks/session-audit/config.yaml | 1 + .../_templates/hooks/session-audit/hook.json | 14 ++ src/vstack/cli/constants.py | 10 +- src/vstack/cli/verify.py | 3 + src/vstack/hooks/__init__.py | 5 + src/vstack/hooks/config.py | 22 +++ src/vstack/hooks/constants.py | 10 + src/vstack/hooks/generator.py | 13 ++ tests/vstack/cli/test_interface.py | 2 +- tests/vstack/cli/test_service.py | 29 ++- tests/vstack/hooks/test___init__.py | 19 ++ tests/vstack/hooks/test_config.py | 33 ++++ tests/vstack/hooks/test_constants.py | 30 +++ tests/vstack/hooks/test_generator.py | 14 ++ 101 files changed, 723 insertions(+), 160 deletions(-) create mode 100644 .github/hooks/post-commit-security-scan.json create mode 100644 .github/hooks/post-edit-format.json create mode 100644 .github/hooks/pre-tool-safety-gate.json create mode 100644 .github/hooks/session-audit.json create mode 100644 docs/architecture/adr/027-repository-hooks-artifact-type.md create mode 100644 docs/design/hooks.md create mode 100644 src/vstack/_templates/hooks/post-commit-security-scan/config.yaml create mode 100644 src/vstack/_templates/hooks/post-commit-security-scan/hook.json create mode 100644 src/vstack/_templates/hooks/post-edit-format/config.yaml create mode 100644 src/vstack/_templates/hooks/post-edit-format/hook.json create mode 100644 src/vstack/_templates/hooks/pre-tool-safety-gate/config.yaml create mode 100644 src/vstack/_templates/hooks/pre-tool-safety-gate/hook.json create mode 100644 src/vstack/_templates/hooks/session-audit/config.yaml create mode 100644 src/vstack/_templates/hooks/session-audit/hook.json create mode 100644 src/vstack/hooks/__init__.py create mode 100644 src/vstack/hooks/config.py create mode 100644 src/vstack/hooks/constants.py create mode 100644 src/vstack/hooks/generator.py create mode 100644 tests/vstack/hooks/test___init__.py create mode 100644 tests/vstack/hooks/test_config.py create mode 100644 tests/vstack/hooks/test_constants.py create mode 100644 tests/vstack/hooks/test_generator.py diff --git a/.github/agents/architect.agent.md b/.github/agents/architect.agent.md index 6bb1d7d..0365edd 100644 --- a/.github/agents/architect.agent.md +++ b/.github/agents/architect.agent.md @@ -180,4 +180,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gdpr` — privacy by design and data processing architecture review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architect","artifact_type":"agent","artifact_version":"20260503022","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"architect","artifact_type":"agent","artifact_version":"20260503022","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/designer.agent.md b/.github/agents/designer.agent.md index fe28c22..f4c4041 100644 --- a/.github/agents/designer.agent.md +++ b/.github/agents/designer.agent.md @@ -193,4 +193,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#openapi` — OpenAPI 3.1 spec writing and review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"designer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"designer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/engineer.agent.md b/.github/agents/engineer.agent.md index 49171d1..ebf2eee 100644 --- a/.github/agents/engineer.agent.md +++ b/.github/agents/engineer.agent.md @@ -202,4 +202,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#rancher` — Rancher and Fleet multi-cluster operations and governance <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"engineer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"engineer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/planner.agent.md b/.github/agents/planner.agent.md index 69470b3..2482759 100644 --- a/.github/agents/planner.agent.md +++ b/.github/agents/planner.agent.md @@ -79,9 +79,9 @@ Execution model: 1. Load workflow stages from project config. 1. Read `workflow.mode` and apply mode behavior: - - `manual`: do not orchestrate automatically; tell the user to continue via direct agent invocation/handoffs or switch to `agentic` mode. - - `agentic`: orchestrate stages sequentially and treat planner as the progression controller. - - `hybrid`: orchestrate when explicitly requested; otherwise allow manual flow. + - `manual`: do not orchestrate automatically; tell the user to continue via direct agent invocation/handoffs or switch to `agentic` mode. + - `agentic`: orchestrate stages sequentially and treat planner as the progression controller. + - `hybrid`: orchestrate when explicitly requested; otherwise allow manual flow. 1. For each stage, invoke the mapped role agent as a subagent. 1. Capture stage result and evaluate gate policy. 1. Pause for user approval when required. @@ -135,4 +135,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#analyse` - assess stage impact, skip rationale, and trade-offs <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"planner","artifact_type":"agent","artifact_version":"20260510001","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"planner","artifact_type":"agent","artifact_version":"20260510001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/product.agent.md b/.github/agents/product.agent.md index 0792eb8..2aeff6a 100644 --- a/.github/agents/product.agent.md +++ b/.github/agents/product.agent.md @@ -164,4 +164,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gh-issues` — create and manage GitHub Issues for requirements, tasks, and user stories <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"product","artifact_type":"agent","artifact_version":"20260503021","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"product","artifact_type":"agent","artifact_version":"20260503021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/release.agent.md b/.github/agents/release.agent.md index da8e602..bf600b5 100644 --- a/.github/agents/release.agent.md +++ b/.github/agents/release.agent.md @@ -154,4 +154,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gh-issues` — create and manage GitHub Issues for tracking work and bug reports <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release","artifact_type":"agent","artifact_version":"20260503020","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"release","artifact_type":"agent","artifact_version":"20260503020","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/tester.agent.md b/.github/agents/tester.agent.md index 6f01d20..b77fcda 100644 --- a/.github/agents/tester.agent.md +++ b/.github/agents/tester.agent.md @@ -180,4 +180,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#rancher` — Rancher/Fleet configuration and multi-cluster governance review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"tester","artifact_type":"agent","artifact_version":"20260503026","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"tester","artifact_type":"agent","artifact_version":"20260503026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/hooks/post-commit-security-scan.json b/.github/hooks/post-commit-security-scan.json new file mode 100644 index 0000000..70431e3 --- /dev/null +++ b/.github/hooks/post-commit-security-scan.json @@ -0,0 +1,14 @@ +{ + "postToolUse": [ + { + "type": "command", + "command": "echo \"[vstack] security hint: run secret scan after git mutations\"" + } + ], + "sessionEnd": [ + { + "type": "command", + "command": "echo \"[vstack] security hint: run dependency and secret checks before push\"" + } + ] +} diff --git a/.github/hooks/post-edit-format.json b/.github/hooks/post-edit-format.json new file mode 100644 index 0000000..ab6d20e --- /dev/null +++ b/.github/hooks/post-edit-format.json @@ -0,0 +1,8 @@ +{ + "postToolUse": [ + { + "type": "command", + "command": "echo \"[vstack] format hint: run make format after file edits\"" + } + ] +} diff --git a/.github/hooks/pre-tool-safety-gate.json b/.github/hooks/pre-tool-safety-gate.json new file mode 100644 index 0000000..9c71c5c --- /dev/null +++ b/.github/hooks/pre-tool-safety-gate.json @@ -0,0 +1,14 @@ +{ + "preToolUse": [ + { + "type": "command", + "command": "echo \"[vstack] safety gate: review destructive operations before execution\"" + } + ], + "errorOccurred": [ + { + "type": "command", + "command": "echo \"[vstack] safety gate: execution error captured\"" + } + ] +} diff --git a/.github/hooks/session-audit.json b/.github/hooks/session-audit.json new file mode 100644 index 0000000..828c84d --- /dev/null +++ b/.github/hooks/session-audit.json @@ -0,0 +1,14 @@ +{ + "sessionStart": [ + { + "type": "command", + "command": "echo \"[vstack] session started\"" + } + ], + "sessionEnd": [ + { + "type": "command", + "command": "echo \"[vstack] session finished\"" + } + ] +} diff --git a/.github/instructions/git.instructions.md b/.github/instructions/git.instructions.md index ea3b781..ee0ecde 100644 --- a/.github/instructions/git.instructions.md +++ b/.github/instructions/git.instructions.md @@ -41,4 +41,4 @@ Use these Git and release hygiene conventions in this project. 1. Prefer local verification before pushing release-impacting changes. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"git","artifact_type":"instruction","artifact_version":"20260421001","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"git","artifact_type":"instruction","artifact_version":"20260421001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/helm.instructions.md b/.github/instructions/helm.instructions.md index a323785..1b37194 100644 --- a/.github/instructions/helm.instructions.md +++ b/.github/instructions/helm.instructions.md @@ -45,4 +45,4 @@ Use these Helm conventions in this project. - [Helm chart best practices](https://helm.sh/docs/chart_best_practices/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"instruction","artifact_version":"20260502040","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"instruction","artifact_version":"20260502040","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/java.instructions.md b/.github/instructions/java.instructions.md index 43b2ae4..98a498a 100644 --- a/.github/instructions/java.instructions.md +++ b/.github/instructions/java.instructions.md @@ -56,4 +56,4 @@ Use these Java conventions in this project. 1. Do not suppress static analysis warnings without a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"java","artifact_type":"instruction","artifact_version":"20260502001","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"java","artifact_type":"instruction","artifact_version":"20260502001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/k8s.instructions.md b/.github/instructions/k8s.instructions.md index 45925cb..5bcc17e 100644 --- a/.github/instructions/k8s.instructions.md +++ b/.github/instructions/k8s.instructions.md @@ -51,4 +51,4 @@ Use these Kubernetes conventions in this project. - [Kubernetes API reference](https://kubernetes.io/docs/reference/kubernetes-api/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"instruction","artifact_version":"20260502039","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"instruction","artifact_version":"20260502039","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/markdown.instructions.md b/.github/instructions/markdown.instructions.md index 4d9ac9a..64b29d5 100644 --- a/.github/instructions/markdown.instructions.md +++ b/.github/instructions/markdown.instructions.md @@ -51,4 +51,4 @@ Use these Markdown conventions in this project. 1. Keep examples accurate and runnable — a broken example is worse than no example. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"markdown","artifact_type":"instruction","artifact_version":"20260502002","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"markdown","artifact_type":"instruction","artifact_version":"20260502002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/python.instructions.md b/.github/instructions/python.instructions.md index 87e668c..a9a809c 100644 --- a/.github/instructions/python.instructions.md +++ b/.github/instructions/python.instructions.md @@ -42,4 +42,4 @@ Use these Python conventions in this project. 1. Do not silence lint/type errors unless there is a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"python","artifact_type":"instruction","artifact_version":"20260421002","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"python","artifact_type":"instruction","artifact_version":"20260421002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/rancher.instructions.md b/.github/instructions/rancher.instructions.md index cc5663c..3f52dd8 100644 --- a/.github/instructions/rancher.instructions.md +++ b/.github/instructions/rancher.instructions.md @@ -44,4 +44,4 @@ Use these Rancher conventions in this project. - [Fleet docs](https://fleet.rancher.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"instruction","artifact_version":"20260502041","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"instruction","artifact_version":"20260502041","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/security.instructions.md b/.github/instructions/security.instructions.md index cc4695b..dd02e2c 100644 --- a/.github/instructions/security.instructions.md +++ b/.github/instructions/security.instructions.md @@ -42,4 +42,4 @@ Apply these security policies in this project. 1. Isolate privileged logic; keep it minimal, auditable, and separate from business logic. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"instruction","artifact_version":"20260502003","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"instruction","artifact_version":"20260502003","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/terraform.instructions.md b/.github/instructions/terraform.instructions.md index 5a98069..da8fe19 100644 --- a/.github/instructions/terraform.instructions.md +++ b/.github/instructions/terraform.instructions.md @@ -60,4 +60,4 @@ Use these Terraform conventions in this project. - [tfsec](https://aquasecurity.github.io/tfsec/) · [checkov](https://www.checkov.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"instruction","artifact_version":"20260502034","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"instruction","artifact_version":"20260502034","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/terragrunt.instructions.md b/.github/instructions/terragrunt.instructions.md index 7f67a3a..9770cb6 100644 --- a/.github/instructions/terragrunt.instructions.md +++ b/.github/instructions/terragrunt.instructions.md @@ -57,4 +57,4 @@ Use these Terragrunt conventions in this project. - [Terragrunt CLI reference](https://terragrunt.gruntwork.io/docs/reference/cli-options/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"instruction","artifact_version":"20260502035","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"instruction","artifact_version":"20260502035","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index bae8c88..e673737 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -43,4 +43,4 @@ Use these testing conventions in this project. 1. Treat flaky tests as bugs; do not merge code with known test reliability issues. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"testing","artifact_type":"instruction","artifact_version":"20260502004","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"testing","artifact_type":"instruction","artifact_version":"20260502004","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/typescript.instructions.md b/.github/instructions/typescript.instructions.md index a718e94..adb935d 100644 --- a/.github/instructions/typescript.instructions.md +++ b/.github/instructions/typescript.instructions.md @@ -49,4 +49,4 @@ Use these TypeScript conventions in this project. 1. Do not suppress lint or type errors with inline disable comments unless there is a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"typescript","artifact_type":"instruction","artifact_version":"20260502005","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"typescript","artifact_type":"instruction","artifact_version":"20260502005","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/api-design-review.prompt.md b/.github/prompts/api-design-review.prompt.md index 321dc53..70e505c 100644 --- a/.github/prompts/api-design-review.prompt.md +++ b/.github/prompts/api-design-review.prompt.md @@ -55,4 +55,4 @@ List fields or objects that are missing required constraints, descriptions, or e - top priority fix in one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"api-design-review","artifact_type":"prompt","artifact_version":"20260502006","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"api-design-review","artifact_type":"prompt","artifact_version":"20260502006","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/architecture-risk.prompt.md b/.github/prompts/architecture-risk.prompt.md index 6e8c226..9d9f919 100644 --- a/.github/prompts/architecture-risk.prompt.md +++ b/.github/prompts/architecture-risk.prompt.md @@ -52,4 +52,4 @@ List security-specific risks not covered above: auth boundaries, sensitive data - one-sentence rationale <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architecture-risk","artifact_type":"prompt","artifact_version":"20260502007","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"architecture-risk","artifact_type":"prompt","artifact_version":"20260502007","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/code-review.prompt.md b/.github/prompts/code-review.prompt.md index 248c682..4f6287e 100644 --- a/.github/prompts/code-review.prompt.md +++ b/.github/prompts/code-review.prompt.md @@ -51,4 +51,4 @@ End with: - Biggest remaining risk: one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"prompt","artifact_version":"20260502008","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"prompt","artifact_version":"20260502008","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/dependency-audit.prompt.md b/.github/prompts/dependency-audit.prompt.md index a059ec3..d8744b5 100644 --- a/.github/prompts/dependency-audit.prompt.md +++ b/.github/prompts/dependency-audit.prompt.md @@ -60,4 +60,4 @@ List packages with unusual provenance concerns: abandoned maintainers, single-ma Ordered list of actions by priority (critical first). <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependency-audit","artifact_type":"prompt","artifact_version":"20260502009","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"dependency-audit","artifact_type":"prompt","artifact_version":"20260502009","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/incident-timeline.prompt.md b/.github/prompts/incident-timeline.prompt.md index dc955ac..ebe3980 100644 --- a/.github/prompts/incident-timeline.prompt.md +++ b/.github/prompts/incident-timeline.prompt.md @@ -57,4 +57,4 @@ For each action: List the minimum controls needed to reduce repeat probability. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"incident-timeline","artifact_type":"prompt","artifact_version":"20260502010","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"incident-timeline","artifact_type":"prompt","artifact_version":"20260502010","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/migration-safety.prompt.md b/.github/prompts/migration-safety.prompt.md index f57b5e4..e602ce8 100644 --- a/.github/prompts/migration-safety.prompt.md +++ b/.github/prompts/migration-safety.prompt.md @@ -52,4 +52,4 @@ List missing migration tests (forward, backward, data invariants, load-sensitive - biggest remaining risk in one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"migration-safety","artifact_type":"prompt","artifact_version":"20260502011","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"migration-safety","artifact_type":"prompt","artifact_version":"20260502011","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/release-readiness.prompt.md b/.github/prompts/release-readiness.prompt.md index 74efd78..8552f4f 100644 --- a/.github/prompts/release-readiness.prompt.md +++ b/.github/prompts/release-readiness.prompt.md @@ -46,4 +46,4 @@ For each expected artifact that is missing, flag it explicitly as: MISSING — [ One clear next step for the team. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release-readiness","artifact_type":"prompt","artifact_version":"20260502012","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"release-readiness","artifact_type":"prompt","artifact_version":"20260502012","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/adr/SKILL.md b/.github/skills/adr/SKILL.md index 93a9a76..7bd56f5 100644 --- a/.github/skills/adr/SKILL.md +++ b/.github/skills/adr/SKILL.md @@ -180,4 +180,4 @@ is a kebab-case title. After writing, state the file path and summary so the architect or product role can review. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"adr","artifact_type":"skill","artifact_version":"20260421003","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"adr","artifact_type":"skill","artifact_version":"20260421003","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/analyse/SKILL.md b/.github/skills/analyse/SKILL.md index 4efda59..3856249 100644 --- a/.github/skills/analyse/SKILL.md +++ b/.github/skills/analyse/SKILL.md @@ -214,4 +214,4 @@ State conclusions with confidence level: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"analyse","artifact_type":"skill","artifact_version":"20260421004","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"analyse","artifact_type":"skill","artifact_version":"20260421004","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/architecture/SKILL.md b/.github/skills/architecture/SKILL.md index c435ee2..e55a8d3 100644 --- a/.github/skills/architecture/SKILL.md +++ b/.github/skills/architecture/SKILL.md @@ -278,4 +278,4 @@ For each significant structural decision made during this review (technology cho - Update `docs/architecture/overview.md` to reflect the final decisions. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architecture","artifact_type":"skill","artifact_version":"20260421005","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"architecture","artifact_type":"skill","artifact_version":"20260421005","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/aws-cli/SKILL.md b/.github/skills/aws-cli/SKILL.md index 7211362..c28e4af 100644 --- a/.github/skills/aws-cli/SKILL.md +++ b/.github/skills/aws-cli/SKILL.md @@ -374,4 +374,4 @@ aws ce get-cost-and-usage \ - [AWS CLI named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"aws-cli","artifact_type":"skill","artifact_version":"20260502033","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"aws-cli","artifact_type":"skill","artifact_version":"20260502033","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/cicd/SKILL.md b/.github/skills/cicd/SKILL.md index 4a61e8a..421c19a 100644 --- a/.github/skills/cicd/SKILL.md +++ b/.github/skills/cicd/SKILL.md @@ -221,4 +221,4 @@ Configure these in GitHub → Settings → Branches. - [GitHub-hosted runners](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"cicd","artifact_type":"skill","artifact_version":"20260421006","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"cicd","artifact_type":"skill","artifact_version":"20260421006","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/cloudformation/SKILL.md b/.github/skills/cloudformation/SKILL.md index 6907f44..2ad5f01 100644 --- a/.github/skills/cloudformation/SKILL.md +++ b/.github/skills/cloudformation/SKILL.md @@ -344,4 +344,4 @@ AppSecurityGroup: - [AWS SAM documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"cloudformation","artifact_type":"skill","artifact_version":"20260502032","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"cloudformation","artifact_type":"skill","artifact_version":"20260502032","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/code-review/SKILL.md b/.github/skills/code-review/SKILL.md index 7a7b5e4..4d3a47b 100644 --- a/.github/skills/code-review/SKILL.md +++ b/.github/skills/code-review/SKILL.md @@ -218,4 +218,4 @@ Confidence: [HIGH/MEDIUM/LOW — explain if not HIGH] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"skill","artifact_version":"20260421007","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"skill","artifact_version":"20260421007","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/codeql/SKILL.md b/.github/skills/codeql/SKILL.md index 49ecf8f..855da54 100644 --- a/.github/skills/codeql/SKILL.md +++ b/.github/skills/codeql/SKILL.md @@ -251,4 +251,4 @@ GITHUB_TOKEN=<token> codeql github upload-results \ - [Supported languages and frameworks](https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/codeql-code-scanning-for-compiled-languages) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"codeql","artifact_type":"skill","artifact_version":"20260502026","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"codeql","artifact_type":"skill","artifact_version":"20260502026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/concise/SKILL.md b/.github/skills/concise/SKILL.md index dd46d43..dac193d 100644 --- a/.github/skills/concise/SKILL.md +++ b/.github/skills/concise/SKILL.md @@ -147,4 +147,4 @@ Current mode unchanged: <mode> - [ ] User confirmation/status returned in deterministic format <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"concise","artifact_type":"skill","artifact_version":"20260421008","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"concise","artifact_type":"skill","artifact_version":"20260421008","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/consult/SKILL.md b/.github/skills/consult/SKILL.md index bf7ba0e..2571f4e 100644 --- a/.github/skills/consult/SKILL.md +++ b/.github/skills/consult/SKILL.md @@ -218,4 +218,4 @@ reason: [one sentence] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"consult","artifact_type":"skill","artifact_version":"20260421009","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"consult","artifact_type":"skill","artifact_version":"20260421009","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/container/SKILL.md b/.github/skills/container/SKILL.md index 34adbf0..2d9c924 100644 --- a/.github/skills/container/SKILL.md +++ b/.github/skills/container/SKILL.md @@ -153,4 +153,4 @@ For production-like local testing, write a separate `docker-compose.prod.yml` wi - [Docker official images](https://hub.docker.com/search?image_filter=official) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"container","artifact_type":"skill","artifact_version":"20260421010","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"container","artifact_type":"skill","artifact_version":"20260421010","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/conventional-commit/SKILL.md b/.github/skills/conventional-commit/SKILL.md index d8c07d4..77a4b00 100644 --- a/.github/skills/conventional-commit/SKILL.md +++ b/.github/skills/conventional-commit/SKILL.md @@ -155,4 +155,4 @@ Remaining changes: If commit is blocked, report exact reason and proposed fix. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"conventional-commit","artifact_type":"skill","artifact_version":"20260502024","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"conventional-commit","artifact_type":"skill","artifact_version":"20260502024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/debug/SKILL.md b/.github/skills/debug/SKILL.md index 4740db2..76ec3b7 100644 --- a/.github/skills/debug/SKILL.md +++ b/.github/skills/debug/SKILL.md @@ -258,4 +258,4 @@ Prevention: [any follow-up items] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"debug","artifact_type":"skill","artifact_version":"20260421011","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"debug","artifact_type":"skill","artifact_version":"20260421011","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/dependabot/SKILL.md b/.github/skills/dependabot/SKILL.md index 945a621..ad9aeff 100644 --- a/.github/skills/dependabot/SKILL.md +++ b/.github/skills/dependabot/SKILL.md @@ -320,4 +320,4 @@ updates: - [Dependabot security updates](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependabot","artifact_type":"skill","artifact_version":"20260502027","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"dependabot","artifact_type":"skill","artifact_version":"20260502027","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/dependency/SKILL.md b/.github/skills/dependency/SKILL.md index ae4fd2b..3313f88 100644 --- a/.github/skills/dependency/SKILL.md +++ b/.github/skills/dependency/SKILL.md @@ -318,4 +318,4 @@ Action items (priority order): - [PyPI / npm / crates.io / Maven Central](https://pypi.org) (replace with the relevant registry) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependency","artifact_type":"skill","artifact_version":"20260421012","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"dependency","artifact_type":"skill","artifact_version":"20260421012","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/design/SKILL.md b/.github/skills/design/SKILL.md index c9ff1d5..e732ac8 100644 --- a/.github/skills/design/SKILL.md +++ b/.github/skills/design/SKILL.md @@ -243,4 +243,4 @@ Output a complete design document to `docs/design/overview.md` or `openapi.yaml` ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"design","artifact_type":"skill","artifact_version":"20260421013","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"design","artifact_type":"skill","artifact_version":"20260421013","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/docs/SKILL.md b/.github/skills/docs/SKILL.md index cfdefcc..be97300 100644 --- a/.github/skills/docs/SKILL.md +++ b/.github/skills/docs/SKILL.md @@ -151,4 +151,4 @@ Skipped (n/a): ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"docs","artifact_type":"skill","artifact_version":"20260421014","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"docs","artifact_type":"skill","artifact_version":"20260421014","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/explore/SKILL.md b/.github/skills/explore/SKILL.md index 4855992..aa6af6a 100644 --- a/.github/skills/explore/SKILL.md +++ b/.github/skills/explore/SKILL.md @@ -224,4 +224,4 @@ Stack: [language, framework, runtime versions] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"explore","artifact_type":"skill","artifact_version":"20260421015","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"explore","artifact_type":"skill","artifact_version":"20260421015","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/gdpr/SKILL.md b/.github/skills/gdpr/SKILL.md index 7ecb298..b6ff4fd 100644 --- a/.github/skills/gdpr/SKILL.md +++ b/.github/skills/gdpr/SKILL.md @@ -246,4 +246,4 @@ Use `@example.com` for all test email addresses. - [EDPB guidelines](https://www.edpb.europa.eu/our-work-tools/general-guidance/guidelines-recommendations-best-practices_en) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gdpr","artifact_type":"skill","artifact_version":"20260502029","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"gdpr","artifact_type":"skill","artifact_version":"20260502029","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/gh-issues/SKILL.md b/.github/skills/gh-issues/SKILL.md index 57cc1de..3b5a19a 100644 --- a/.github/skills/gh-issues/SKILL.md +++ b/.github/skills/gh-issues/SKILL.md @@ -230,4 +230,4 @@ https://github.com/<org>/<repo>/issues/<number> - [GitHub Issues documentation](https://docs.github.com/en/issues) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gh-issues","artifact_type":"skill","artifact_version":"20260502025","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"gh-issues","artifact_type":"skill","artifact_version":"20260502025","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/gh-release/SKILL.md b/.github/skills/gh-release/SKILL.md index 29b9d4d..e3b1c68 100644 --- a/.github/skills/gh-release/SKILL.md +++ b/.github/skills/gh-release/SKILL.md @@ -214,4 +214,4 @@ If blocked, report exact blocker and required user action. - [GitHub Releases documentation](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gh-release","artifact_type":"skill","artifact_version":"20260502023","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"gh-release","artifact_type":"skill","artifact_version":"20260502023","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/guardrails/SKILL.md b/.github/skills/guardrails/SKILL.md index c257719..820fe2e 100644 --- a/.github/skills/guardrails/SKILL.md +++ b/.github/skills/guardrails/SKILL.md @@ -72,4 +72,4 @@ Activate careful mode for this session. Two behaviors are now enabled. Explicitly ask to "disable guardrails". <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"guardrails","artifact_type":"skill","artifact_version":"20260421016","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"guardrails","artifact_type":"skill","artifact_version":"20260421016","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/helm/SKILL.md b/.github/skills/helm/SKILL.md index 804e65b..1746edd 100644 --- a/.github/skills/helm/SKILL.md +++ b/.github/skills/helm/SKILL.md @@ -140,4 +140,4 @@ Practices: - [Chart best practices](https://helm.sh/docs/chart_best_practices/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"skill","artifact_version":"20260502037","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"skill","artifact_version":"20260502037","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/incident/SKILL.md b/.github/skills/incident/SKILL.md index fc24744..784e4ac 100644 --- a/.github/skills/incident/SKILL.md +++ b/.github/skills/incident/SKILL.md @@ -251,4 +251,4 @@ Next: invoke @#rca and @#postmortem to produce written artifacts. ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"incident","artifact_type":"skill","artifact_version":"20260503002","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"incident","artifact_type":"skill","artifact_version":"20260503002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/inspect/SKILL.md b/.github/skills/inspect/SKILL.md index f7a4bfb..9a2346a 100644 --- a/.github/skills/inspect/SKILL.md +++ b/.github/skills/inspect/SKILL.md @@ -155,4 +155,4 @@ Confirm for changed paths: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"inspect","artifact_type":"skill","artifact_version":"20260421018","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"inspect","artifact_type":"skill","artifact_version":"20260421018","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/k8s/SKILL.md b/.github/skills/k8s/SKILL.md index eb83bce..42ca2a6 100644 --- a/.github/skills/k8s/SKILL.md +++ b/.github/skills/k8s/SKILL.md @@ -144,4 +144,4 @@ Common failure classes: - [Kubernetes API reference](https://kubernetes.io/docs/reference/kubernetes-api/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"skill","artifact_version":"20260502036","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"skill","artifact_version":"20260502036","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/migrate/SKILL.md b/.github/skills/migrate/SKILL.md index b9f18ac..07e1934 100644 --- a/.github/skills/migrate/SKILL.md +++ b/.github/skills/migrate/SKILL.md @@ -320,4 +320,4 @@ Pre-deploy checklist: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"migrate","artifact_type":"skill","artifact_version":"20260421019","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"migrate","artifact_type":"skill","artifact_version":"20260421019","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/onboard/SKILL.md b/.github/skills/onboard/SKILL.md index 9101cb3..da290f0 100644 --- a/.github/skills/onboard/SKILL.md +++ b/.github/skills/onboard/SKILL.md @@ -302,4 +302,4 @@ Gaps remaining (if any): ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"onboard","artifact_type":"skill","artifact_version":"20260421020","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"onboard","artifact_type":"skill","artifact_version":"20260421020","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/openapi/SKILL.md b/.github/skills/openapi/SKILL.md index b0107bc..77a8b91 100644 --- a/.github/skills/openapi/SKILL.md +++ b/.github/skills/openapi/SKILL.md @@ -403,4 +403,4 @@ Summary: [N critical, N warnings, N info] - [Redocly CLI (linting)](https://redocly.com/docs/cli/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"openapi","artifact_type":"skill","artifact_version":"20260421021","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"openapi","artifact_type":"skill","artifact_version":"20260421021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/performance/SKILL.md b/.github/skills/performance/SKILL.md index 2404a01..4bd99dc 100644 --- a/.github/skills/performance/SKILL.md +++ b/.github/skills/performance/SKILL.md @@ -242,4 +242,4 @@ For each bottleneck identified: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"performance","artifact_type":"skill","artifact_version":"20260421022","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"performance","artifact_type":"skill","artifact_version":"20260421022","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/postmortem/SKILL.md b/.github/skills/postmortem/SKILL.md index 4f47ea6..12508ec 100644 --- a/.github/skills/postmortem/SKILL.md +++ b/.github/skills/postmortem/SKILL.md @@ -184,4 +184,4 @@ Status: Draft — ready for team review ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"postmortem","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"postmortem","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/pr/SKILL.md b/.github/skills/pr/SKILL.md index 24f179f..bd8368a 100644 --- a/.github/skills/pr/SKILL.md +++ b/.github/skills/pr/SKILL.md @@ -151,4 +151,4 @@ Next steps depend on the repository CI/CD configuration: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"pr","artifact_type":"skill","artifact_version":"20260502013","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"pr","artifact_type":"skill","artifact_version":"20260502013","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/rancher/SKILL.md b/.github/skills/rancher/SKILL.md index 01a5624..3d58218 100644 --- a/.github/skills/rancher/SKILL.md +++ b/.github/skills/rancher/SKILL.md @@ -113,4 +113,4 @@ Checks: - [Fleet documentation](https://fleet.rancher.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"skill","artifact_version":"20260502038","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"skill","artifact_version":"20260502038","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/rca/SKILL.md b/.github/skills/rca/SKILL.md index 5cac050..0a302a5 100644 --- a/.github/skills/rca/SKILL.md +++ b/.github/skills/rca/SKILL.md @@ -207,4 +207,4 @@ Status: Draft — ready for review ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rca","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"rca","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/refactor/SKILL.md b/.github/skills/refactor/SKILL.md index c85f4e8..1110f6b 100644 --- a/.github/skills/refactor/SKILL.md +++ b/.github/skills/refactor/SKILL.md @@ -372,4 +372,4 @@ Behavior changed: No ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"refactor","artifact_type":"skill","artifact_version":"20260421023","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"refactor","artifact_type":"skill","artifact_version":"20260421023","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index 59a1fd8..39d4c98 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -147,4 +147,4 @@ Prepend a new entry at the top of `CHANGELOG.md`: Keep existing entries intact. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release-notes","artifact_type":"skill","artifact_version":"20260502014","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"release-notes","artifact_type":"skill","artifact_version":"20260502014","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/requirements/SKILL.md b/.github/skills/requirements/SKILL.md index 979ab3e..5d1e7d7 100644 --- a/.github/skills/requirements/SKILL.md +++ b/.github/skills/requirements/SKILL.md @@ -199,4 +199,4 @@ Write all findings to `docs/product/requirements.md`: After writing, summarize what was decided so the architect role can start. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"requirements","artifact_type":"skill","artifact_version":"20260421024","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"requirements","artifact_type":"skill","artifact_version":"20260421024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/secret-scan/SKILL.md b/.github/skills/secret-scan/SKILL.md index 664bb22..0c0674b 100644 --- a/.github/skills/secret-scan/SKILL.md +++ b/.github/skills/secret-scan/SKILL.md @@ -240,4 +240,4 @@ credential formats. - [Supported secret patterns](https://docs.github.com/en/code-security/secret-scanning/introduction/supported-secret-scanning-patterns) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"secret-scan","artifact_type":"skill","artifact_version":"20260502028","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"secret-scan","artifact_type":"skill","artifact_version":"20260502028","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/security/SKILL.md b/.github/skills/security/SKILL.md index c6c0f90..eb35c48 100644 --- a/.github/skills/security/SKILL.md +++ b/.github/skills/security/SKILL.md @@ -295,4 +295,4 @@ Scope: [full/diff/dependency/config] - [STRIDE threat modeling (Microsoft)](https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"skill","artifact_version":"20260421025","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"skill","artifact_version":"20260421025","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/terraform/SKILL.md b/.github/skills/terraform/SKILL.md index 8fbf89a..3ba5145 100644 --- a/.github/skills/terraform/SKILL.md +++ b/.github/skills/terraform/SKILL.md @@ -335,4 +335,4 @@ Run `terraform plan` after every state operation to verify the outcome. - [tfsec rules](https://aquasecurity.github.io/tfsec/latest/checks/aws/) · [checkov checks](https://www.checkov.io/5.Policy%20Index/terraform.html) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"skill","artifact_version":"20260502030","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"skill","artifact_version":"20260502030","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/terragrunt/SKILL.md b/.github/skills/terragrunt/SKILL.md index 8b499b9..77326de 100644 --- a/.github/skills/terragrunt/SKILL.md +++ b/.github/skills/terragrunt/SKILL.md @@ -307,4 +307,4 @@ Use `--terragrunt-non-interactive` in CI to prevent hanging on prompts. - [Gruntwork module registry](https://www.gruntwork.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"skill","artifact_version":"20260502031","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"skill","artifact_version":"20260502031","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/threat-model/SKILL.md b/.github/skills/threat-model/SKILL.md index a21b337..d2a81e5 100644 --- a/.github/skills/threat-model/SKILL.md +++ b/.github/skills/threat-model/SKILL.md @@ -245,4 +245,4 @@ For each high-priority threat include: - Final report is written to `docs/architecture/threat-model.md`. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"threat-model","artifact_type":"skill","artifact_version":"20260502021","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"threat-model","artifact_type":"skill","artifact_version":"20260502021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/verify/SKILL.md b/.github/skills/verify/SKILL.md index a9cfb65..585a7df 100644 --- a/.github/skills/verify/SKILL.md +++ b/.github/skills/verify/SKILL.md @@ -266,4 +266,4 @@ scope: [path/component/full] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"verify","artifact_type":"skill","artifact_version":"20260421026","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"verify","artifact_type":"skill","artifact_version":"20260421026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/vision/SKILL.md b/.github/skills/vision/SKILL.md index 33260a9..16dbb1b 100644 --- a/.github/skills/vision/SKILL.md +++ b/.github/skills/vision/SKILL.md @@ -206,4 +206,4 @@ For each finding: explain the tradeoff, give an opinionated recommendation, ask Present as: "Overall assessment: [READY/NEEDS REVISION/SCOPE CHANGE] because [1-2 sentence reason]." <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"vision","artifact_type":"skill","artifact_version":"20260421027","generator":"vstack","vstack_version":"3.1.1"} --> +<!-- VSTACK-META: {"artifact_name":"vision","artifact_type":"skill","artifact_version":"20260421027","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.vstack/vstack.json b/.vstack/vstack.json index 4cbf93d..efe13cf 100644 --- a/.vstack/vstack.json +++ b/.vstack/vstack.json @@ -1,316 +1,316 @@ { "manifest_version": 2, "hash_algorithm": "sha256", - "vstack_version": "3.1.1", - "installed_at": "2026-05-10T12:08:00.906817+00:00", + "vstack_version": "0.0.0.post3.dev0+df3fe6e", + "installed_at": "2026-05-10T13:12:42.249087+00:00", "artifacts": { "skills": [ { "name": "adr", "file": "skills/adr/SKILL.md", "version": "20260421003", - "checksum": "3e039f697174956e5f51a23783e2ee71192d79ec0bca6c94a02f727f7d207277", + "checksum": "871263fddee0b3ae8e2c2e2243aad4f2b3aa9e0dea0d2519c7d21c3e98430a83", "checksum_algorithm": "sha256" }, { "name": "analyse", "file": "skills/analyse/SKILL.md", "version": "20260421004", - "checksum": "f95d3e3d1bc243635bebf531f7feae04348da6f8a4c0985ad2ae84f2aaafa298", + "checksum": "79ced25692671a5286a26cbcd0e59ed4d641ac7493fd8e2b58b232f97e467224", "checksum_algorithm": "sha256" }, { "name": "architecture", "file": "skills/architecture/SKILL.md", "version": "20260421005", - "checksum": "b1c78e0c74be480a938ba0fc08791d032b9bfc2682ceca923b0a5a6664609f4c", + "checksum": "e2d0342184829c888a70ece6f82ad6a7450ad5d8c958ff6e028d7f245f1e5960", "checksum_algorithm": "sha256" }, { "name": "aws-cli", "file": "skills/aws-cli/SKILL.md", "version": "20260502033", - "checksum": "a75a1931fa01f416a80964d8119e1a099ac4eefb0f4969179a034e39c3b028eb", + "checksum": "4868a5adc17b6ac52839c85f3a09986991c2498501913ef48a8f85a6291e1b21", "checksum_algorithm": "sha256" }, { "name": "cicd", "file": "skills/cicd/SKILL.md", "version": "20260421006", - "checksum": "ac3911f5b9997ad3875c67e36c1dd23096ed70cd66f3130299a657c6d234c59d", + "checksum": "ba6c0cf0e644e24b1290c1b78969bad86dd7d5b39030edc7bd430d4d1832cfd7", "checksum_algorithm": "sha256" }, { "name": "cloudformation", "file": "skills/cloudformation/SKILL.md", "version": "20260502032", - "checksum": "ff0359c101814cb47cbce1fa41a80c001a59d8bbdb8eb16a645e5122976b806b", + "checksum": "0e61163cfc356105a309bbecf89ee26a5b10b9b5db58c331d76efa4a508e760e", "checksum_algorithm": "sha256" }, { "name": "code-review", "file": "skills/code-review/SKILL.md", "version": "20260421007", - "checksum": "c951124bd93bf7f3138e9dce304fb730b675f655bd9051f8a6bc5969ea4aca9c", + "checksum": "58b7caa9dd3a5da6bef8d8d4d1f7b6d513740d33ad16731a06b1b2a907d93a13", "checksum_algorithm": "sha256" }, { "name": "codeql", "file": "skills/codeql/SKILL.md", "version": "20260502026", - "checksum": "d3dfc82e43d3e4645bf180699ee2666a48aa26cf115add49b0258fbcbfe184c9", + "checksum": "95f29c975e5850307620c21a68df174b90ecaafd5b1e36659bb3c34d0027b5df", "checksum_algorithm": "sha256" }, { "name": "concise", "file": "skills/concise/SKILL.md", "version": "20260421008", - "checksum": "aca2ef118be257adc352ba0641f457faf6973ae033e3cb22960a23a7a8ee3261", + "checksum": "9555de7da6a925e76992b6f5e51ddccd6b4de3887ea31ed2ceb9113c9fe9bc7e", "checksum_algorithm": "sha256" }, { "name": "consult", "file": "skills/consult/SKILL.md", "version": "20260421009", - "checksum": "443616170149445f17e1b445a695b5b9b6a7bae25c2989c2dd9030a7b64f9771", + "checksum": "a340883900309261213ec0ea0d4e9e5b72ca83fb25c7cd6c3506e1f95f40cf76", "checksum_algorithm": "sha256" }, { "name": "container", "file": "skills/container/SKILL.md", "version": "20260421010", - "checksum": "47e0518a4ce5ac36309aacb59d6d81f16f62cefbf3ad2868ec9ba1b291b82468", + "checksum": "f0a4408a756195faca9a58dca76e56f950df227415d77af293bf55280191abd4", "checksum_algorithm": "sha256" }, { "name": "conventional-commit", "file": "skills/conventional-commit/SKILL.md", "version": "20260502024", - "checksum": "a630e8317e2dbffd6856a2ad820af81b6dad15e355ec060f09fdf59d900dccc2", + "checksum": "8f1ce569106b5210a8d4ffe5bc9123eb0dec6b24ff56cf680922eccdd9891012", "checksum_algorithm": "sha256" }, { "name": "debug", "file": "skills/debug/SKILL.md", "version": "20260421011", - "checksum": "d5835aaa59841c5c22d12562afece77ba0d539fbc17be6c9ba46f08c65752c7b", + "checksum": "b4c1c6de7142b7b74326d1c19a5cc27f3957d1faa48aa48bd6b1fc45832edf3b", "checksum_algorithm": "sha256" }, { "name": "dependabot", "file": "skills/dependabot/SKILL.md", "version": "20260502027", - "checksum": "3bda392b46bdee53a47e586cf1dc1ec59ed4b142c8e8afa2093333997c5f1a0f", + "checksum": "fa3f595740faa0b8093ffe4f32fb8e4b85f5834ea10817752ee9c94be8f38c2d", "checksum_algorithm": "sha256" }, { "name": "dependency", "file": "skills/dependency/SKILL.md", "version": "20260421012", - "checksum": "2d26955a7f97b2a880ff66c470f7c93336948817b7f2c816e14a9df2b33df18e", + "checksum": "2cbbecca8e6697b8523301325a889a5ca88c520f6d35a3b98807b6c3449239f5", "checksum_algorithm": "sha256" }, { "name": "design", "file": "skills/design/SKILL.md", "version": "20260421013", - "checksum": "367877b19c7fdc444bc24c833f025f3d11f43d0b3e625eecde5bdc283badc971", + "checksum": "2d56e34aca8d3cf5a3f327e3b69ab15707393ffc47607c91ec91dd5be86cd65b", "checksum_algorithm": "sha256" }, { "name": "docs", "file": "skills/docs/SKILL.md", "version": "20260421014", - "checksum": "8784d94cce824b53e82fdf22a66708fdee9cc03df1761b0d71c775db30b90872", + "checksum": "d6ebf7d270eac7911aea09a34cb7ec4006c70064b7e6f58dfa84a405e6620d18", "checksum_algorithm": "sha256" }, { "name": "explore", "file": "skills/explore/SKILL.md", "version": "20260421015", - "checksum": "99762b3f7c25adf214b89567e88e5d0f2e53544505bd1662440aea44d00be59f", + "checksum": "914323f796ac95a0f73d404f1b7eb38d94ba8950ed1a4acc5d80600ba03fae8f", "checksum_algorithm": "sha256" }, { "name": "gdpr", "file": "skills/gdpr/SKILL.md", "version": "20260502029", - "checksum": "34e2ba426beddb8efcc3e399b3536544344cbb160f164a13520e6d1e8892f678", + "checksum": "dfe60c4c59b7ae4dc66e98a885ec42b0e60eccc61fa1cb6fd269cd7eb3e0a94d", "checksum_algorithm": "sha256" }, { "name": "gh-issues", "file": "skills/gh-issues/SKILL.md", "version": "20260502025", - "checksum": "8e478886b9a2039fa4c7858da0d05a3cf6e8773dcb7c67367f907b7d0db5187c", + "checksum": "bc1f595283e7643de63c7519e994500b9434a0343154bfc404b842ce8e7028a8", "checksum_algorithm": "sha256" }, { "name": "gh-release", "file": "skills/gh-release/SKILL.md", "version": "20260502023", - "checksum": "30fa374af26d17ec5025398218eb319d18aa913d75fcf36c129ececfaafe25e3", + "checksum": "10abe52f681d261588608418a546580e7798e883179888061ed6d2285050d870", "checksum_algorithm": "sha256" }, { "name": "guardrails", "file": "skills/guardrails/SKILL.md", "version": "20260421016", - "checksum": "c1536c4c7b19b5f95ba0d06852698edf3c0f42cb3e485f61fdf8c04be09eae8c", + "checksum": "1c1a44c95461eb4eeef7ad64b5846a1efbb202cf1b75186421c5d0b8f9fd03f1", "checksum_algorithm": "sha256" }, { "name": "helm", "file": "skills/helm/SKILL.md", "version": "20260502037", - "checksum": "5bc0c8a900890d8faa65ea459645a19d026523b2da3d40258cc1406840e719b6", + "checksum": "6e6579ede2dadb6dbe895d07c362d208e62414338445bd035f47bd1404be501b", "checksum_algorithm": "sha256" }, { "name": "incident", "file": "skills/incident/SKILL.md", "version": "20260503002", - "checksum": "8e645512e59cf858587a93c3375a829c1350264af6e92d7179b1ad8e904f17c1", + "checksum": "546229fe91121dae2192aceb0b215096cc8cf5ba0cfb3d1300e30c73ff3f6276", "checksum_algorithm": "sha256" }, { "name": "inspect", "file": "skills/inspect/SKILL.md", "version": "20260421018", - "checksum": "9a2ec789958de371eadc9cdf68eb52c8b41904e18901990cb5972da7e31be43b", + "checksum": "3a4aee3620bbd669602001c041cf81788da5fac9dc055a9f1b0c155e50e88b46", "checksum_algorithm": "sha256" }, { "name": "k8s", "file": "skills/k8s/SKILL.md", "version": "20260502036", - "checksum": "10075706cbcc555bac8e2c6515bc35d919ad4acf53678d9abcdc8c218f2902e3", + "checksum": "2bfbe14c187809211a7296578644262e2e47c89b4a2fc28fc3a30a79fdaeb580", "checksum_algorithm": "sha256" }, { "name": "migrate", "file": "skills/migrate/SKILL.md", "version": "20260421019", - "checksum": "278b4b217ba16e56c1bd28ed2a6a8180bd3cb65e848c91c7dd2992bdcce72637", + "checksum": "eecc00aae2c58c573000a434fbf7ae891a3e85dcc99a42fec0e8bcadc3047b7d", "checksum_algorithm": "sha256" }, { "name": "onboard", "file": "skills/onboard/SKILL.md", "version": "20260421020", - "checksum": "dc37d9e77e6f35c32ce12e040afa6f90dbf0cc575bc0b1ead1dbfefda7fd2e5c", + "checksum": "f16f2d78002e3be8e7d1d9e2bf376c1b37eb181d821f3cd113e0d5aecf5236ae", "checksum_algorithm": "sha256" }, { "name": "openapi", "file": "skills/openapi/SKILL.md", "version": "20260421021", - "checksum": "12e9875af27880b84a31ecd1c7d7967955d207f05f04c8dbe7b753d2415ae179", + "checksum": "a3dbcded8e1a8b268a1976b9185f41de74ad0ad6e7aae98c399d6135de920529", "checksum_algorithm": "sha256" }, { "name": "performance", "file": "skills/performance/SKILL.md", "version": "20260421022", - "checksum": "6bc6da196ea01b3c2c1d4f27c6ab36acec0709ff7fb730bdcc2b5597ea5c0614", + "checksum": "fa4062c77381facacedac289b54a3ce7e6cf242dfa2b36f55401aebfe5fb8ae1", "checksum_algorithm": "sha256" }, { "name": "postmortem", "file": "skills/postmortem/SKILL.md", "version": "20260503001", - "checksum": "ba62c73a9b1e3c3c9e40b698e4b0cbcdfe9d8350a0a55ff7ff00216921665bc0", + "checksum": "fc70f26420e705e0979ff76476ea8176386258f2315fcb57f5ac9049a37f6c90", "checksum_algorithm": "sha256" }, { "name": "pr", "file": "skills/pr/SKILL.md", "version": "20260502013", - "checksum": "dd99ce60d20e3508ed88902d0dc2ad30bf96fab9d8a704548825bb7dbb4c6566", + "checksum": "fc7b2b08696982afd48033a3cc29b9fa4ac8b590fc61c828b84135585c88b345", "checksum_algorithm": "sha256" }, { "name": "rancher", "file": "skills/rancher/SKILL.md", "version": "20260502038", - "checksum": "8e9894d47f00fe08bb8ae44b847810226a27ecbe0c748287c9b0a83748dd0868", + "checksum": "3000f24c2ee2f99fdb363f30d6075993ed26533663d081089b7800098a28e648", "checksum_algorithm": "sha256" }, { "name": "rca", "file": "skills/rca/SKILL.md", "version": "20260503001", - "checksum": "68aa25538b134f91e09602aef428a504845744741fb52ac9cdfcfd2a31b2dcb5", + "checksum": "8bebb57c166c153d89e4589097ea419266b249df871a79e24c840112b5f8ad0c", "checksum_algorithm": "sha256" }, { "name": "refactor", "file": "skills/refactor/SKILL.md", "version": "20260421023", - "checksum": "81c4839028edce9022f895f9a471a0b2a6b539153161f2cdee3381379e45030a", + "checksum": "aa7fb995b6ac649b24b61066f15a6fe7fddab81bdcec8af27eba65092b957d68", "checksum_algorithm": "sha256" }, { "name": "release-notes", "file": "skills/release-notes/SKILL.md", "version": "20260502014", - "checksum": "7947a3e9f5f7fd911efcb257c990a8eec4954e28d6dee4690a724f2b273c2ef8", + "checksum": "589f001bcbf0e27c4da6ff9c530371a1f183b7936e3a7fc421879282bd7f9af3", "checksum_algorithm": "sha256" }, { "name": "requirements", "file": "skills/requirements/SKILL.md", "version": "20260421024", - "checksum": "8bf772becd69317c7337d5e83af58a77c88cd6912d16f77875d5b547639ca384", + "checksum": "7d3338f0b7c5d493ab714a90b7d1732de56467503a3fe4dacdd40031bfea356c", "checksum_algorithm": "sha256" }, { "name": "secret-scan", "file": "skills/secret-scan/SKILL.md", "version": "20260502028", - "checksum": "8706f7b53aa87ec62175748c6d819a6f7ffcffb986eeb2a04f67d71636c80750", + "checksum": "95df047af89a93a8fa3716899b8fe703b270d7f40b93fe65915f7d1bdb849370", "checksum_algorithm": "sha256" }, { "name": "security", "file": "skills/security/SKILL.md", "version": "20260421025", - "checksum": "9872abef1cb00cbf581a50fe82c10f9563ae4bb6e3810a9eacbffb4e5b527c7a", + "checksum": "e9bcd451ee25b1c752239e76e20ca67302061356b59214fae7b134a180022df8", "checksum_algorithm": "sha256" }, { "name": "terraform", "file": "skills/terraform/SKILL.md", "version": "20260502030", - "checksum": "669e6ffc1f294ffb7dd903448be1557aed9fd5be0233aa950040bf1892e54f1f", + "checksum": "2bb933512f72b61dae3a3b632a7afd43c62aad066de003ee0a565fc8f1895215", "checksum_algorithm": "sha256" }, { "name": "terragrunt", "file": "skills/terragrunt/SKILL.md", "version": "20260502031", - "checksum": "bc91cb7fb63dbb44c6eb92c85283871605b8f7317947213d9c70db5b3cbbb086", + "checksum": "e0d3dc0feefeafb9cd41d8d6885d8b4d53baed52ba70df7dc8d33bfc7410bae8", "checksum_algorithm": "sha256" }, { "name": "threat-model", "file": "skills/threat-model/SKILL.md", "version": "20260502021", - "checksum": "ec1fc2114ed6c86cf3d6c3790130ee81f5bca6e574b9ad3efb870fc488550663", + "checksum": "7fdf69113167e9bb8c866681a86f4b4d6416145bb9def0ebe18ceba690c8fbe9", "checksum_algorithm": "sha256" }, { "name": "verify", "file": "skills/verify/SKILL.md", "version": "20260421026", - "checksum": "a7bfc7214a07bac2650c84b34aa044b8aa4f573fff0af956c5f9fe8429ba81d0", + "checksum": "aa56d1a674ed827606af34b8e7c5bd9c2de61a6cb6f92f5fd9bdce28e46bcb30", "checksum_algorithm": "sha256" }, { "name": "vision", "file": "skills/vision/SKILL.md", "version": "20260421027", - "checksum": "f13be0922aeff7d9c8f59466fdf0bf491d6b4d3f8788ab254135f9bd8e94e96e", + "checksum": "c0058202130905d8495a3e565b5db546ad2d0ff6d654596358a0b12c484ce310", "checksum_algorithm": "sha256" } ], @@ -319,49 +319,79 @@ "name": "architect", "file": "agents/architect.agent.md", "version": "20260503022", - "checksum": "ec1a08761409dbc8a47562b3dfbe53957833d34c3f64b34f2ea6e877d17bd617", + "checksum": "99aa5d66f0d9039f7759c8011ab1a835b7262aebd6a4f7df591898d5102b8b94", "checksum_algorithm": "sha256" }, { "name": "designer", "file": "agents/designer.agent.md", "version": "20260503024", - "checksum": "b48cdca585f1760f3d581d61b9bdf43df9e4669cb78f4d7fad4f5199a270f6de", + "checksum": "394717ed714328b3c92278a4de33b2bd067ee9ed100529c557bf71f5d777e0fb", "checksum_algorithm": "sha256" }, { "name": "engineer", "file": "agents/engineer.agent.md", "version": "20260503024", - "checksum": "a8f8bcf22aebcc9dd6eabfa47ebbe57e6f58926bbac750b89d50175f25b2022a", + "checksum": "2e6de55f6bb292cb750c6403be74db2dbe913db0ac64936a4f066945f88f88d8", "checksum_algorithm": "sha256" }, { "name": "planner", "file": "agents/planner.agent.md", "version": "20260510001", - "checksum": "d46f0fdb8d4ee743f599c4d1243061063d783f3d194f8fb99925f37521bb8f5e", + "checksum": "73189a08e16d5e8f444471448bc4f26cf6dbbc290cb072d99fa6a768bfbc9bda", "checksum_algorithm": "sha256" }, { "name": "product", "file": "agents/product.agent.md", "version": "20260503021", - "checksum": "d859ceb31e29cb125d854b2b9dc1bf92dc81e331292533db9b30ffb987d20ff6", + "checksum": "94c9a9ceaaa626ff20351e1c7f268b4d67464c23d2aae2070ba9385209888c50", "checksum_algorithm": "sha256" }, { "name": "release", "file": "agents/release.agent.md", "version": "20260503020", - "checksum": "661b802eb7380cccc78165ba354c509e4d968df3eccdd80c134980563bfb4a7d", + "checksum": "ea226eb56b001e3906e07ed552867fded99d2555f4c8e4b269619ae27cbb0194", "checksum_algorithm": "sha256" }, { "name": "tester", "file": "agents/tester.agent.md", "version": "20260503026", - "checksum": "6a620dc7e37b8c0f31e927fe991d1dfdd4d520e86d640d7139bc8eeab4c96767", + "checksum": "27dd3baeeaad59b9a6a1a5cab0896282771e66278cf02d27820e3740b8793366", + "checksum_algorithm": "sha256" + } + ], + "hooks": [ + { + "name": "post-commit-security-scan", + "file": "hooks/post-commit-security-scan.json", + "version": "20260510001", + "checksum": "edfaaa09795a29667e8bd0d281888efb0c08062aae0915204e90aef8a0aae284", + "checksum_algorithm": "sha256" + }, + { + "name": "post-edit-format", + "file": "hooks/post-edit-format.json", + "version": "20260510001", + "checksum": "1b765a1edb135138f586c876d65c53258440ca01a9eea3ff098f6ceb715caf5a", + "checksum_algorithm": "sha256" + }, + { + "name": "pre-tool-safety-gate", + "file": "hooks/pre-tool-safety-gate.json", + "version": "20260510001", + "checksum": "1e8b862cccfa2a53251d6cae5e865bae8e08f1754a002ff9f8908b44f8ec3c6f", + "checksum_algorithm": "sha256" + }, + { + "name": "session-audit", + "file": "hooks/session-audit.json", + "version": "20260510001", + "checksum": "dd8191abe47de0285eac06dbb67386a5694e7af80ea1986d8c9b837283717a23", "checksum_algorithm": "sha256" } ], @@ -370,84 +400,84 @@ "name": "git", "file": "instructions/git.instructions.md", "version": "20260421001", - "checksum": "337b8e0bd880a694e6a334b6417d70b005984fbef10e8172efc899c623fa51f4", + "checksum": "ed6a191176e32631e2d572cb21278b555f6696a859839579821631156a8b35a5", "checksum_algorithm": "sha256" }, { "name": "helm", "file": "instructions/helm.instructions.md", "version": "20260502040", - "checksum": "e42683a6d6f4e579b5d4baf93e00541e6c4089aaa196512b38bff9ee0e16101f", + "checksum": "a60520853b79751517f089136ba7fb182feb1ea8e9c9495890e5c14ff32afd7b", "checksum_algorithm": "sha256" }, { "name": "java", "file": "instructions/java.instructions.md", "version": "20260502001", - "checksum": "634ac626ed19d4b912c463b80c9a0df93e1ece4ffcbc8ff2b8ba3f97bfba62dc", + "checksum": "69dc2bf3a5428ed03d77c2789871985ea7c5881af89c2b414548fca6e9488464", "checksum_algorithm": "sha256" }, { "name": "k8s", "file": "instructions/k8s.instructions.md", "version": "20260502039", - "checksum": "1300ccae1257f75f6406a54d00985e6243f8cf8d367d8c4dbb7ff00e67ef0521", + "checksum": "f19f1060bf2a1424950496290a43a3535f9749e4ed838cf7475fe35eb9487d67", "checksum_algorithm": "sha256" }, { "name": "markdown", "file": "instructions/markdown.instructions.md", "version": "20260502002", - "checksum": "ca88d33becf42fea73ef6863bdd9f9c7155f762c384a7047106af6395417f3af", + "checksum": "59c93c5b0e63360aff18ab3a7a207f4e798efd1f2c26b0a561a4ef4ea688c5c5", "checksum_algorithm": "sha256" }, { "name": "python", "file": "instructions/python.instructions.md", "version": "20260421002", - "checksum": "a6237f0642e18c35aaf44cd6e92927e6eb5d8bbcfccba0bb0e9a1c775e286d69", + "checksum": "ac40fffd3d3a3f9f8ca43e10ca603a187578c9d517533529a3d647b61cedf56c", "checksum_algorithm": "sha256" }, { "name": "rancher", "file": "instructions/rancher.instructions.md", "version": "20260502041", - "checksum": "322c68a0b56afa120cff6b26abe0ff882416aa9af8f14cc7c9f338c3c389417a", + "checksum": "4e643b1e078e9e8f127f1f2cf7707b36109e28b8697e208d8d3804848b867eec", "checksum_algorithm": "sha256" }, { "name": "security", "file": "instructions/security.instructions.md", "version": "20260502003", - "checksum": "ec403a2a6784dc4365202c2bb4886d84383dcdbd9e1eec19fb6a26d46583be52", + "checksum": "a943df637e44cb23c8e82f8b39bf70558bc502ece5af9a03a15d8492cffb51a4", "checksum_algorithm": "sha256" }, { "name": "terraform", "file": "instructions/terraform.instructions.md", "version": "20260502034", - "checksum": "5b0eb238a2c335cfad2309a453be4cbc23c0bdd0cd19741ab3e4c4a62f06d3c1", + "checksum": "1bada82da46a6359bb1814b6642074be6c0b7de9c2c6ca59e031a4d05ea4ea8c", "checksum_algorithm": "sha256" }, { "name": "terragrunt", "file": "instructions/terragrunt.instructions.md", "version": "20260502035", - "checksum": "d5b97886b042f4c2b73b3207ed9b0eb157e03a82e23af3609f9fe5e7b5b8a69a", + "checksum": "13c0059c366624ab482c2b3d24a353bcb0a2ffbb6bf735abd18796969a3e4020", "checksum_algorithm": "sha256" }, { "name": "testing", "file": "instructions/testing.instructions.md", "version": "20260502004", - "checksum": "d10d57bf7dae2edd74653080ea38727668e9e84a4d703a4f9747d351f8eb1ddf", + "checksum": "cbd1948f367c32c39032209e5ed9fcfff8ce6c46c6324a4aa9a8550365873ca3", "checksum_algorithm": "sha256" }, { "name": "typescript", "file": "instructions/typescript.instructions.md", "version": "20260502005", - "checksum": "3de62a3fbc5bcac74644d03de44e80f8bd32cf87b547c9676821408c0329a6a9", + "checksum": "fe412ba2e60baea66d0d07ae0c153fa2a9157bd57556fa7476f46ce466160ad3", "checksum_algorithm": "sha256" } ], @@ -456,49 +486,49 @@ "name": "api-design-review", "file": "prompts/api-design-review.prompt.md", "version": "20260502006", - "checksum": "84a94cb6a93fac1a67829b40a4c51c31f74deb271760bad3504937bab1eef72f", + "checksum": "1ba62a6f78b836256fe578c4ca312de24599c79b018119b81a100f6de4e0da11", "checksum_algorithm": "sha256" }, { "name": "architecture-risk", "file": "prompts/architecture-risk.prompt.md", "version": "20260502007", - "checksum": "cf4f36f1c2f2e4aa795ccf1ad75a9dcbc824f89655d26b60410b0b1c3cf3ffc3", + "checksum": "14fa36e36948309827c1c1296ff2c8b1f1306cfb19057ef190a5e60f6cc04a61", "checksum_algorithm": "sha256" }, { "name": "code-review", "file": "prompts/code-review.prompt.md", "version": "20260502008", - "checksum": "2c29ada8aca812513c2b6c2c78b61569ec0bba265b674af060e171c018ba3ba8", + "checksum": "b6499ca66706a08ced7b9bf69bd288c2fecb684e1c85e333ee91d36ba7e66c3f", "checksum_algorithm": "sha256" }, { "name": "dependency-audit", "file": "prompts/dependency-audit.prompt.md", "version": "20260502009", - "checksum": "b9775792c34a46dc3dd9e573675510109a7ef9da65a65860dd6af25d3a9fd4d0", + "checksum": "51cead168dbbe52b455813cc620a8426ec287ceb2994831aa6ae115b4e49d0b0", "checksum_algorithm": "sha256" }, { "name": "incident-timeline", "file": "prompts/incident-timeline.prompt.md", "version": "20260502010", - "checksum": "080ebbfb919d353f041e3a007fefa3b31c418590ce64dfbb490c54d96dbd084b", + "checksum": "28627dc7c362d510f312281fe0bda30e1927998bae440808af3a92d06c455393", "checksum_algorithm": "sha256" }, { "name": "migration-safety", "file": "prompts/migration-safety.prompt.md", "version": "20260502011", - "checksum": "7ed20c0929b56df847e6c34018c2a59c7eb6b2070789f6fcd8d4e0c7117e23d6", + "checksum": "814258882774ff98724b14caa0bc9ee35dc68f46e72e62b24884052e79ffacfa", "checksum_algorithm": "sha256" }, { "name": "release-readiness", "file": "prompts/release-readiness.prompt.md", "version": "20260502012", - "checksum": "d8d9f484bb977b01e3d8dcb034b5ba7cf33b163d6c2fb689bcdc1ae511656bbe", + "checksum": "49898a07169504acba1c2195abf730be77d1e17298cb4676f3ed926a27e31dc6", "checksum_algorithm": "sha256" } ] diff --git a/docs/architecture/adr/027-repository-hooks-artifact-type.md b/docs/architecture/adr/027-repository-hooks-artifact-type.md new file mode 100644 index 0000000..5619927 --- /dev/null +++ b/docs/architecture/adr/027-repository-hooks-artifact-type.md @@ -0,0 +1,114 @@ +# ADR-027: Repository Hooks as a First-Class Artifact Type + +> Maintained by: **architect** role + +**date:** 2026-05-10\ +**status:** accepted\ +**depends on:** ADR-014 (manifest schema versioning), ADR-019 (vstack project directory), ADR-022 (selective exclude filter), ADR-023 (workflow contract) + +## context + +vstack already manages four generated artifact families under `.github/`: +`skills`, `agents`, `instructions`, and `prompts`. + +Repository-level agent hooks in GitHub Copilot use `.github/hooks/<name>.json` and are +an operational control surface for safety and quality checks at session boundaries. +Until now, hooks were only represented as a raw per-agent frontmatter field (`hooks`) +and not as installable, manifest-tracked artifacts. + +That gap created three practical issues: + +1. No standard hook baseline shipped with `vstack install` +1. No checksum ownership or drift visibility for hook files +1. No consistent migration and exclusion behavior aligned with other artifact families + +## decision + +Hooks are promoted to a first-class artifact type in vstack. + +### 1) New artifact type + +A new singular type `hook` is added to the generator and CLI type registry. + +- Source templates: `src/vstack/_templates/hooks/<name>/hook.json` +- Source metadata: `src/vstack/_templates/hooks/<name>/config.yaml` +- Install output: `.github/hooks/<name>.json` + +### 2) Manifest tracking and verify behavior + +Hook artifacts are tracked in `.vstack/vstack.json` under the `hooks` manifest key, +including checksum metadata, identical to other managed artifact types. + +Because hook output is JSON, hooks do not emit markdown auto-generated footers. +`verify` must therefore: + +- validate file presence and expected names, +- validate checksum ownership/drift, +- skip VSTACK-META/footer checks for artifact types with `auto_gen_footer = false`. + +### 3) Baseline hook templates + +vstack ships multiple default hooks, not just one sample: + +- `session-audit` +- `pre-tool-safety-gate` +- `post-edit-format` +- `post-commit-security-scan` + +These templates are conservative defaults that provide immediate, low-risk guidance. +Teams can replace or extend them via normal template customization patterns. + +### 4) Separation from per-agent frontmatter hooks + +Per-agent frontmatter `hooks` remains supported and unchanged. +That field configures agent-scoped behavior. +Repository hook artifacts configure repository-scoped behavior. +Both are valid and intentionally separate concerns. + +### 5) Migration path + +No destructive migration is required. + +For existing repositories: + +1. upgrade to a vstack version that includes this ADR implementation, +1. run `vstack install` (or `vstack init` in CI), +1. optional: disable repository hooks using `.vstack/config.yaml` with `exclude.hook`. + +No existing agent configuration must be rewritten. + +## alternatives considered + +### A) Keep hooks only as per-agent raw frontmatter + +Rejected. This keeps hooks outside the managed artifact model and prevents +checksum governance, deterministic install output, and selective-type operations. + +### B) Treat hooks as prompts or instructions + +Rejected. Hook JSON has distinct schema/behavior and should not be forced into +markdown-oriented families with frontmatter/footer semantics. + +### C) Add hooks only in docs, no generator support + +Rejected. Documentation-only support does not provide enforceable defaults, +manifest state, or verifiable install behavior. + +## rationale + +Promoting hooks to a first-class type keeps vstack's artifact model coherent: +all generated repository artifacts are discoverable, installable, verifiable, +and checksum-tracked via one mechanism. + +It also enables consistent CLI ergonomics (`--only hook`, `exclude.hook`) and +reduces drift between declared policy and repository state. + +## impact on the Option B pipeline + +The planner-oriented orchestrated pipeline depends on predictable repository +controls during multi-stage execution. + +Repository hooks provide a stage-independent guardrail layer around session and +tool events. This improves baseline safety and quality enforcement regardless of +which role stage is active, while preserving stage-level workflow contracts from +ADR-023. diff --git a/docs/design/hooks.md b/docs/design/hooks.md new file mode 100644 index 0000000..f273d9e --- /dev/null +++ b/docs/design/hooks.md @@ -0,0 +1,108 @@ +# vstack - hooks design + +> Maintained by: **designer** role\ +> Last updated: 2026-05-10 + +## overview + +This document defines the repository-level hooks artifact family in vstack. +It complements: + +- `docs/design/overview.md` (global design baseline) +- `docs/design/workflow.md` (workflow progression model) +- `docs/architecture/adr/027-repository-hooks-artifact-type.md` (decision record) + +Repository hooks are generated JSON artifacts installed at: + +- `.github/hooks/<name>.json` + +They are managed by the same install/verify/status/uninstall manifest model used by +other artifact families. + +______________________________________________________________________ + +## 1. artifact model + +### 1.1 source and output mapping + +| Element | Path pattern | +| ---------------- | ------------------------------------------------ | +| source config | `src/vstack/_templates/hooks/<name>/config.yaml` | +| source body | `src/vstack/_templates/hooks/<name>/hook.json` | +| installed output | `.github/hooks/<name>.json` | +| manifest key | `hooks` | +| singular type | `hook` | + +### 1.2 type behavior + +Hook artifacts use JSON payloads, so they differ from markdown artifact families: + +| Property | Value | +| ------------------- | ----------- | +| `add_frontmatter` | `false` | +| `auto_gen_footer` | `false` | +| `artifact_is_dir` | `false` | +| `template_filename` | `hook.json` | + +Implication: manifest verification must use checksum ownership and skip markdown-footer metadata checks. + +______________________________________________________________________ + +## 2. baseline hook set + +vstack ships four default repository hooks: + +| Hook name | Primary events | Intent | +| --------------------------- | ----------------------------- | ----------------------------------------------- | +| `session-audit` | `sessionStart`, `sessionEnd` | Session boundary visibility | +| `pre-tool-safety-gate` | `preToolUse`, `errorOccurred` | Safety signaling around risky operations | +| `post-edit-format` | `postToolUse` | Formatting reminder after edit-heavy operations | +| `post-commit-security-scan` | `postToolUse`, `sessionEnd` | Security hygiene reminder around git mutations | + +These are conservative defaults and should remain safe to install in any repository. + +______________________________________________________________________ + +## 3. CLI contracts + +### 3.1 selection and exclusion + +- `--only hook` is supported in all type-aware commands. +- `.vstack/config.yaml` supports `exclude.hook` and per-name exclusions under `hook`. + +### 3.2 command behavior + +- `install` / `init`: generate and track `.github/hooks/*.json` +- `status`: show managed/modified/missing state for hook files +- `verify`: + - source checks: required hook names and source template presence + - output checks: installed file presence + checksum ownership/drift + - metadata checks: skipped for hook artifacts +- `uninstall`: remove tracked hook outputs according to checksum protection rules + +______________________________________________________________________ + +## 4. migration and compatibility + +Migration is additive. + +For existing repositories: + +1. run `vstack install` (or `vstack init` in CI), +1. review generated `.github/hooks/*.json`, +1. optionally disable specific defaults using `.vstack/config.yaml` `exclude.hook`. + +Per-agent frontmatter field `hooks` remains supported and is intentionally separate +from repository-level hooks artifacts. + +______________________________________________________________________ + +## 5. extension guidance + +When adding new built-in hooks: + +1. add template directory under `src/vstack/_templates/hooks/<name>/` +1. include `config.yaml` and `hook.json` +1. add name to expected canonical hook list in CLI constants +1. regenerate artifacts via `python -m vstack install` +1. update this design doc and roadmap status if behavior changes diff --git a/docs/design/overview.md b/docs/design/overview.md index 36b828c..8c51291 100644 --- a/docs/design/overview.md +++ b/docs/design/overview.md @@ -326,6 +326,7 @@ The tables below are the authoritative quick-reference for implementers. For ful guidance, field semantics, examples, and template structure, see the dedicated docs: - Agents → [agents.md](./agents.md) +- Hooks → [hooks.md](./hooks.md) - Skills → [skills.md](./skills.md) - Instructions → [instructions.md](./instructions.md) @@ -419,7 +420,8 @@ Every generated artifact ends with this block when `auto_gen_footer=True`: - Human-readable line: constant in `src/vstack/artifacts/constants.py` - Machine-readable `VSTACK-META` line: emitted by `GenericArtifactGenerator._build_footer` -- All 4 artifact types have `auto_gen_footer=True` +- Markdown artifact types (`skill`, `agent`, `instruction`, `prompt`) use + `auto_gen_footer=True`; JSON hooks use `auto_gen_footer=False` ______________________________________________________________________ diff --git a/docs/product/requirements.md b/docs/product/requirements.md index df06c39..085a973 100644 --- a/docs/product/requirements.md +++ b/docs/product/requirements.md @@ -1,7 +1,7 @@ # requirements > Maintained by: **product** role\ -> Last updated: 2026-04-26 +> Last updated: 2026-05-10 ______________________________________________________________________ @@ -23,7 +23,8 @@ ______________________________________________________________________ - Supported artifact types: `skill` (`.github/skills/<name>/SKILL.md`), `agent` (`.github/agents/<name>.agent.md`), `instruction` (`.github/instructions/<name>.instructions.md`), `prompt` - (`.github/prompts/<name>.prompt.md`). + (`.github/prompts/<name>.prompt.md`), `hook` + (`.github/hooks/<name>.json`). - At generation time the generator resolves `{{TOKEN}}` placeholders from shared partials and injects frontmatter validated against the artifact schema. - Unresolved tokens are flagged as errors by `validate` and `install`. diff --git a/docs/product/roadmap.md b/docs/product/roadmap.md index 2b0c3d8..d87d71a 100644 --- a/docs/product/roadmap.md +++ b/docs/product/roadmap.md @@ -26,7 +26,7 @@ ______________________________________________________________________ | install/init command semantics | v3.0.0 | shipped | `install` = first-run setup; `init` = idempotent CI regeneration (ADR-020, breaking change) | | manifest relocation | v3.0.0 | shipped | `vstack.json` moves from `.github/` to `.vstack/`; migration via `manifest upgrade` (ADR-014) | | selective install | v3.0.0 | shipped | Per-type and per-name exclusions via `exclude:` in `.vstack/config.yaml`; agents always installed (ADR-022) | -| agent hooks support | t.b.d. | candidate | Generate `.github/hooks/<name>.json` from vstack templates; enforce quality gates at session boundaries | +| agent hooks support | t.b.d. | shipped | First-class `hook` artifact type: generate `.github/hooks/<name>.json` from templates and track in manifest | | new skills (next batch) | t.b.d. | candidate | `spaces`: set up Copilot Spaces; `copilot-admin`: manage Copilot settings via `gh api` | | team customization layer | t.b.d. | candidate | Custompacks on top of vstack defaults; agents non-removable, skills fully overridable; overlay merge model | | workflow contract source-of-truth | t.b.d. | shipped | `workflow:` block in `.vstack/config.yaml`; `gate`, `hitl`, `handoffs` schema; `vstack migrate` command (ADR-023, ADR-026) | @@ -155,13 +155,13 @@ ______________________________________________________________________ ______________________________________________________________________ -### agent hooks support [candidate — t.b.d.] +### agent hooks support [shipped — t.b.d.] GitHub Copilot agents support a repository-level hooks mechanism: shell commands defined in `.github/hooks/<name>.json` that execute at key points during an agent session — `sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`, `postToolUse`, and `errorOccurred`. -vstack is well-positioned to provide curated, installable hook templates for common quality-gate patterns: +vstack now provides installable repository hook templates for quality-gate patterns: - **Pre-tool safety gate** (`preToolUse`) — block or log destructive operations before they run - **Session audit log** (`sessionStart` / `sessionEnd`) — record session boundaries with timestamp and working directory @@ -169,15 +169,28 @@ vstack is well-positioned to provide curated, installable hook templates for com - **Commit policy check** (`postToolUse`) — run `cchk` or commit-message lint after `git commit` tool calls - **Security scan on push** (`postToolUse`) — run `gitleaks` or `detect-secrets` after repository mutations -Planned direction: +Implemented: -- Add a `hooks` artifact type to the vstack generator, parallel to `skills` and `instructions` +- `hook` is a first-class artifact type in the generator and CLI type registry - Templates live in `src/vstack/_templates/hooks/<name>/hook.json` (source of truth) -- Generated output written to `.github/hooks/<name>.json` at install time -- Register hooks in `vstack.json` manifest and track them with checksums like other artifact types -- Note: the per-agent `hooks` frontmatter field (already supported) is separate — it scopes hooks to one +- Generated output is written to `.github/hooks/<name>.json` at install time +- Hooks are registered in `.vstack/vstack.json` and tracked with checksums like other artifact types +- The per-agent `hooks` frontmatter field remains separate and still scopes hooks to one agent; repository hooks apply to all agent sessions +Shipped default hook set: + +- `session-audit` +- `pre-tool-safety-gate` +- `post-edit-format` +- `post-commit-security-scan` + +Migration path: + +- Existing repositories do not require a manual migration step for this feature +- Run `vstack install` (or `vstack init` in CI) to materialize managed `.github/hooks/*.json` output +- Use `.vstack/config.yaml` `exclude.hook` if your project does not want repository-level hooks + Ref: [GitHub — Customize agent workflows with hooks](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/customize-cloud-agent/use-hooks) ### new skills (next batch) [candidate — t.b.d.] diff --git a/pyproject.toml b/pyproject.toml index f43aa58..41a528e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,6 +132,15 @@ warn_unused_ignores = true warn_return_any = false ignore_missing_imports = true +# --------------------------------------------------------------------------- +# pylint +# --------------------------------------------------------------------------- + +[tool.pylint.main] +# Ensure src-layout imports (e.g. vstack.hooks) resolve in editor-integrated pylint runs. +init-hook = "import sys; sys.path.insert(0, 'src')" +py-version = "3.11" + # --------------------------------------------------------------------------- # build system # --------------------------------------------------------------------------- diff --git a/src/vstack/_templates/hooks/post-commit-security-scan/config.yaml b/src/vstack/_templates/hooks/post-commit-security-scan/config.yaml new file mode 100644 index 0000000..b18ab19 --- /dev/null +++ b/src/vstack/_templates/hooks/post-commit-security-scan/config.yaml @@ -0,0 +1 @@ +version: 20260510001 diff --git a/src/vstack/_templates/hooks/post-commit-security-scan/hook.json b/src/vstack/_templates/hooks/post-commit-security-scan/hook.json new file mode 100644 index 0000000..70431e3 --- /dev/null +++ b/src/vstack/_templates/hooks/post-commit-security-scan/hook.json @@ -0,0 +1,14 @@ +{ + "postToolUse": [ + { + "type": "command", + "command": "echo \"[vstack] security hint: run secret scan after git mutations\"" + } + ], + "sessionEnd": [ + { + "type": "command", + "command": "echo \"[vstack] security hint: run dependency and secret checks before push\"" + } + ] +} diff --git a/src/vstack/_templates/hooks/post-edit-format/config.yaml b/src/vstack/_templates/hooks/post-edit-format/config.yaml new file mode 100644 index 0000000..b18ab19 --- /dev/null +++ b/src/vstack/_templates/hooks/post-edit-format/config.yaml @@ -0,0 +1 @@ +version: 20260510001 diff --git a/src/vstack/_templates/hooks/post-edit-format/hook.json b/src/vstack/_templates/hooks/post-edit-format/hook.json new file mode 100644 index 0000000..ab6d20e --- /dev/null +++ b/src/vstack/_templates/hooks/post-edit-format/hook.json @@ -0,0 +1,8 @@ +{ + "postToolUse": [ + { + "type": "command", + "command": "echo \"[vstack] format hint: run make format after file edits\"" + } + ] +} diff --git a/src/vstack/_templates/hooks/pre-tool-safety-gate/config.yaml b/src/vstack/_templates/hooks/pre-tool-safety-gate/config.yaml new file mode 100644 index 0000000..b18ab19 --- /dev/null +++ b/src/vstack/_templates/hooks/pre-tool-safety-gate/config.yaml @@ -0,0 +1 @@ +version: 20260510001 diff --git a/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.json b/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.json new file mode 100644 index 0000000..9c71c5c --- /dev/null +++ b/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.json @@ -0,0 +1,14 @@ +{ + "preToolUse": [ + { + "type": "command", + "command": "echo \"[vstack] safety gate: review destructive operations before execution\"" + } + ], + "errorOccurred": [ + { + "type": "command", + "command": "echo \"[vstack] safety gate: execution error captured\"" + } + ] +} diff --git a/src/vstack/_templates/hooks/session-audit/config.yaml b/src/vstack/_templates/hooks/session-audit/config.yaml new file mode 100644 index 0000000..b18ab19 --- /dev/null +++ b/src/vstack/_templates/hooks/session-audit/config.yaml @@ -0,0 +1 @@ +version: 20260510001 diff --git a/src/vstack/_templates/hooks/session-audit/hook.json b/src/vstack/_templates/hooks/session-audit/hook.json new file mode 100644 index 0000000..828c84d --- /dev/null +++ b/src/vstack/_templates/hooks/session-audit/hook.json @@ -0,0 +1,14 @@ +{ + "sessionStart": [ + { + "type": "command", + "command": "echo \"[vstack] session started\"" + } + ], + "sessionEnd": [ + { + "type": "command", + "command": "echo \"[vstack] session finished\"" + } + ] +} diff --git a/src/vstack/cli/constants.py b/src/vstack/cli/constants.py index c43e328..4e73a06 100644 --- a/src/vstack/cli/constants.py +++ b/src/vstack/cli/constants.py @@ -3,6 +3,7 @@ from __future__ import annotations from vstack.agents.config import AGENT_TYPE +from vstack.hooks.config import HOOK_TYPE from vstack.instructions.config import INSTRUCTION_TYPE from vstack.prompts.config import PROMPT_TYPE from vstack.skills.config import SKILL_TYPE @@ -68,10 +69,11 @@ class Colors: RESET = "\033[0m" -KNOWN_TYPES = [SKILL_TYPE, AGENT_TYPE, INSTRUCTION_TYPE, PROMPT_TYPE] +KNOWN_TYPES = [SKILL_TYPE, AGENT_TYPE, HOOK_TYPE, INSTRUCTION_TYPE, PROMPT_TYPE] KNOWN_TYPE_NAMES = [type_config.type_name for type_config in KNOWN_TYPES] GLOBAL_SUPPORTED_TYPE_NAMES = [ AGENT_TYPE.type_name, + HOOK_TYPE.type_name, INSTRUCTION_TYPE.type_name, PROMPT_TYPE.type_name, SKILL_TYPE.type_name, @@ -108,6 +110,12 @@ class ArtifactState: "testing", "typescript", ], + "hook": [ + "post-edit-format", + "post-commit-security-scan", + "pre-tool-safety-gate", + "session-audit", + ], "prompt": [ "api-design-review", "architecture-risk", diff --git a/src/vstack/cli/verify.py b/src/vstack/cli/verify.py index f13df0f..cd439c9 100644 --- a/src/vstack/cli/verify.py +++ b/src/vstack/cli/verify.py @@ -136,6 +136,9 @@ def _verify_manifest_metadata( service: CommandService, gen, manifest_data, install_dir: Path ) -> ValidationResult | None: """Verify footer metadata for all manifest-tracked artifacts of one type.""" + if not gen.config.auto_gen_footer: + return None + manifest_entries = manifest_data.entries_for(gen.config.manifest_key) if not manifest_entries: return None diff --git a/src/vstack/hooks/__init__.py b/src/vstack/hooks/__init__.py new file mode 100644 index 0000000..55ce35b --- /dev/null +++ b/src/vstack/hooks/__init__.py @@ -0,0 +1,5 @@ +"""Package initialization for vstack.hooks.""" + +from vstack.hooks.generator import HookGenerator + +__all__ = ["HookGenerator"] diff --git a/src/vstack/hooks/config.py b/src/vstack/hooks/config.py new file mode 100644 index 0000000..d0d96c1 --- /dev/null +++ b/src/vstack/hooks/config.py @@ -0,0 +1,22 @@ +"""Artifact type configuration for hook templates and output.""" + +from __future__ import annotations + +from vstack.artifacts.config import ArtifactTypeConfig +from vstack.hooks.constants import ( + HOOK_OUTPUT_SUBDIR, + HOOK_OUTPUT_SUFFIX, + HOOK_TEMPLATE_FILENAME, + HOOK_TEMPLATES_SUBDIR, +) + +HOOK_TYPE = ArtifactTypeConfig( + type_name="hook", + templates_dir=HOOK_TEMPLATES_SUBDIR, + output_subdir=HOOK_OUTPUT_SUBDIR, + output_pattern="{name}" + HOOK_OUTPUT_SUFFIX, + artifact_is_dir=False, + add_frontmatter=False, + auto_gen_footer=False, + template_filename=HOOK_TEMPLATE_FILENAME, +) diff --git a/src/vstack/hooks/constants.py b/src/vstack/hooks/constants.py new file mode 100644 index 0000000..7b2d0c4 --- /dev/null +++ b/src/vstack/hooks/constants.py @@ -0,0 +1,10 @@ +"""Constants for hook template and output locations.""" + +from vstack.constants import TEMPLATES_ROOT + +HOOK_OUTPUT_SUFFIX = ".json" +HOOK_TEMPLATES_SUBDIR = "hooks" +HOOK_OUTPUT_SUBDIR = "hooks" +HOOK_TEMPLATE_FILENAME = "hook.json" + +HOOK_TEMPLATES_DIR = TEMPLATES_ROOT / HOOK_TEMPLATES_SUBDIR diff --git a/src/vstack/hooks/generator.py b/src/vstack/hooks/generator.py new file mode 100644 index 0000000..b65a2ec --- /dev/null +++ b/src/vstack/hooks/generator.py @@ -0,0 +1,13 @@ +"""Thin hook generator wrapper over ``GenericArtifactGenerator``.""" + +from vstack.artifacts.generator import GenericArtifactGenerator +from vstack.constants import TEMPLATES_ROOT +from vstack.hooks.config import HOOK_TYPE + + +class HookGenerator(GenericArtifactGenerator): + """Generate hook artifacts using the built-in hook configuration.""" + + def __init__(self) -> None: + """Create a hook generator bound to the built-in template root.""" + super().__init__(HOOK_TYPE, TEMPLATES_ROOT) diff --git a/tests/vstack/cli/test_interface.py b/tests/vstack/cli/test_interface.py index 8a86421..2937d25 100644 --- a/tests/vstack/cli/test_interface.py +++ b/tests/vstack/cli/test_interface.py @@ -121,7 +121,7 @@ def test_run_dispatches_install_with_global_default_types(self, monkeypatch, tmp assert len(command.calls) == 1 assert command.calls[0].args is args assert command.calls[0].install_dir == tmp_path / ".github" - assert command.calls[0].only == ["agent", "instruction", "prompt", "skill"] + assert command.calls[0].only == ["agent", "hook", "instruction", "prompt", "skill"] def test_resolve_only_for_scope_rejects_disallowed_global_types(self) -> None: """Global mode rejects unsupported type filters with a helpful message.""" diff --git a/tests/vstack/cli/test_service.py b/tests/vstack/cli/test_service.py index b4e35c3..ecc52af 100644 --- a/tests/vstack/cli/test_service.py +++ b/tests/vstack/cli/test_service.py @@ -61,10 +61,10 @@ def test_label_falls_back_to_absolute(self, tmp_path: Path) -> None: assert svc.label(other) == str(other) def test_cli_class_uses_known_types(self) -> None: - """Service generators cover the four known artifact families.""" + """Service generators cover all known artifact families.""" svc = CommandService(templates_root=TEMPLATES_ROOT) names = {g.config.type_name for g in svc.generators} - assert names == {"skill", "agent", "instruction", "prompt"} + assert names == {"skill", "agent", "hook", "instruction", "prompt"} def test_custom_items_root_is_passed_to_agent_generator(self, tmp_path: Path) -> None: """items_root kwarg is forwarded to the AgentGenerator instance.""" @@ -1336,3 +1336,28 @@ def test_verify_manifest_metadata_skips_missing_artifact_files(self, tmp_path: P svc, gen, manifest_data, tmp_path / ".github" ) assert result is None + + def test_verify_manifest_metadata_skips_types_without_footer(self, tmp_path: Path) -> None: + """VerifyCommand._verify_manifest_metadata skips artifacts that disable footers.""" + svc = CommandService(templates_root=TEMPLATES_ROOT) + gen = svc.gen_for("hook") + assert gen is not None + + manifest_data = Manifest( + vstack_version=VERSION, + installed_at="2026-01-01T00:00:00Z", + artifacts={ + "hooks": [ + ArtifactEntry( + name="session-audit", + file="hooks/session-audit.json", + version="20260510001", + ) + ] + }, + ) + + result = VerifyCommand._verify_manifest_metadata( + svc, gen, manifest_data, tmp_path / ".github" + ) + assert result is None diff --git a/tests/vstack/hooks/test___init__.py b/tests/vstack/hooks/test___init__.py new file mode 100644 index 0000000..3f55d4e --- /dev/null +++ b/tests/vstack/hooks/test___init__.py @@ -0,0 +1,19 @@ +"""Tests for vstack.hooks package public API.""" + +from __future__ import annotations + +from vstack.hooks import HookGenerator + + +class TestHooksInit: + """Test cases for vstack.hooks package exports.""" + + def test_exports_hook_generator(self) -> None: + """Package exports HookGenerator at the top level.""" + assert HookGenerator is not None + + def test_all_contains_hook_generator(self) -> None: + """__all__ declares HookGenerator.""" + import vstack.hooks as mod + + assert "HookGenerator" in mod.__all__ diff --git a/tests/vstack/hooks/test_config.py b/tests/vstack/hooks/test_config.py new file mode 100644 index 0000000..e95132d --- /dev/null +++ b/tests/vstack/hooks/test_config.py @@ -0,0 +1,33 @@ +"""Tests for hook artifact type configuration.""" + +from __future__ import annotations + +from vstack.hooks.config import HOOK_TYPE + + +class TestHookType: + """Test cases for HOOK_TYPE artifact configuration.""" + + def test_type_name_is_hook(self) -> None: + """type_name must be 'hook'.""" + assert HOOK_TYPE.type_name == "hook" + + def test_output_subdir_is_hooks(self) -> None: + """Output artifacts go under the hooks subdirectory.""" + assert HOOK_TYPE.output_subdir == "hooks" + + def test_artifact_is_not_dir(self) -> None: + """Hooks produce single-file artifacts, not directories.""" + assert HOOK_TYPE.artifact_is_dir is False + + def test_add_frontmatter_is_false(self) -> None: + """Hook artifacts must not include frontmatter.""" + assert HOOK_TYPE.add_frontmatter is False + + def test_auto_gen_footer_is_false(self) -> None: + """Hook artifacts must not include the auto-gen footer.""" + assert HOOK_TYPE.auto_gen_footer is False + + def test_template_filename_is_hook_json(self) -> None: + """Hook artifacts render from hook.json template files.""" + assert HOOK_TYPE.template_filename == "hook.json" diff --git a/tests/vstack/hooks/test_constants.py b/tests/vstack/hooks/test_constants.py new file mode 100644 index 0000000..87a04c4 --- /dev/null +++ b/tests/vstack/hooks/test_constants.py @@ -0,0 +1,30 @@ +"""Tests for hook module constants.""" + +from __future__ import annotations + +from vstack.hooks.constants import ( + HOOK_OUTPUT_SUBDIR, + HOOK_OUTPUT_SUFFIX, + HOOK_TEMPLATE_FILENAME, + HOOK_TEMPLATES_SUBDIR, +) + + +class TestHookConstants: + """Test cases for hook constant values.""" + + def test_output_suffix_is_json(self) -> None: + """Output suffix must end with .json.""" + assert HOOK_OUTPUT_SUFFIX == ".json" + + def test_templates_subdir_is_hooks(self) -> None: + """Templates subdir must be 'hooks'.""" + assert HOOK_TEMPLATES_SUBDIR == "hooks" + + def test_output_subdir_is_hooks(self) -> None: + """Output subdir must be 'hooks'.""" + assert HOOK_OUTPUT_SUBDIR == "hooks" + + def test_template_filename_is_hook_json(self) -> None: + """Template filename must be hook.json.""" + assert HOOK_TEMPLATE_FILENAME == "hook.json" diff --git a/tests/vstack/hooks/test_generator.py b/tests/vstack/hooks/test_generator.py new file mode 100644 index 0000000..93b819b --- /dev/null +++ b/tests/vstack/hooks/test_generator.py @@ -0,0 +1,14 @@ +"""Tests for hook generator behavior.""" + +from __future__ import annotations + +from vstack.hooks.generator import HookGenerator + + +class TestHookGenerator: + """Test cases for HookGenerator.""" + + def test_generator_uses_hook_type(self) -> None: + """Test that generator uses hook type.""" + gen = HookGenerator() + assert gen.config.type_name == "hook" From 9e06cc09d55aaa601112cbd825cff17f53baf9cc Mon Sep 17 00:00:00 2001 From: Erik Schaareman <erik@schaareman.org> Date: Sun, 10 May 2026 22:40:05 +0200 Subject: [PATCH 03/11] feat(hooks): add configurable baseline --- .../adr/027-repository-hooks-artifact-type.md | 44 ++- docs/design/hook-yaml-schema.md | 254 ++++++++++++++ docs/design/hooks.md | 146 ++++++-- docs/product/roadmap.md | 2 +- .../post-commit-security-scan/config.yaml | 1 - .../hooks/post-commit-security-scan/hook.json | 14 - .../hooks/post-commit-security-scan/hook.yaml | 79 +++++ .../hooks/post-edit-format/config.yaml | 1 - .../hooks/post-edit-format/hook.json | 8 - .../hooks/post-edit-format/hook.yaml | 61 ++++ .../post-edit-markdown-quality/hook.yaml | 64 ++++ .../hooks/pre-tool-safety-gate/config.yaml | 1 - .../hooks/pre-tool-safety-gate/hook.json | 14 - .../hooks/pre-tool-safety-gate/hook.yaml | 67 ++++ .../hooks/session-audit/config.yaml | 1 - .../_templates/hooks/session-audit/hook.json | 14 - .../_templates/hooks/session-audit/hook.yaml | 60 ++++ .../_templates/project/.vstack/config.yaml | 22 ++ src/vstack/cli/constants.py | 1 + src/vstack/cli/install.py | 7 +- src/vstack/cli/interface.py | 56 +++ src/vstack/cli/service.py | 12 + src/vstack/hooks/constants.py | 2 +- src/vstack/hooks/generator.py | 234 ++++++++++++- tests/vstack/cli/test_install.py | 1 + tests/vstack/cli/test_interface.py | 166 ++++++++- tests/vstack/hooks/test_config.py | 6 +- tests/vstack/hooks/test_constants.py | 6 +- tests/vstack/hooks/test_generator.py | 319 ++++++++++++++++++ 29 files changed, 1566 insertions(+), 97 deletions(-) create mode 100644 docs/design/hook-yaml-schema.md delete mode 100644 src/vstack/_templates/hooks/post-commit-security-scan/config.yaml delete mode 100644 src/vstack/_templates/hooks/post-commit-security-scan/hook.json create mode 100644 src/vstack/_templates/hooks/post-commit-security-scan/hook.yaml delete mode 100644 src/vstack/_templates/hooks/post-edit-format/config.yaml delete mode 100644 src/vstack/_templates/hooks/post-edit-format/hook.json create mode 100644 src/vstack/_templates/hooks/post-edit-format/hook.yaml create mode 100644 src/vstack/_templates/hooks/post-edit-markdown-quality/hook.yaml delete mode 100644 src/vstack/_templates/hooks/pre-tool-safety-gate/config.yaml delete mode 100644 src/vstack/_templates/hooks/pre-tool-safety-gate/hook.json create mode 100644 src/vstack/_templates/hooks/pre-tool-safety-gate/hook.yaml delete mode 100644 src/vstack/_templates/hooks/session-audit/config.yaml delete mode 100644 src/vstack/_templates/hooks/session-audit/hook.json create mode 100644 src/vstack/_templates/hooks/session-audit/hook.yaml diff --git a/docs/architecture/adr/027-repository-hooks-artifact-type.md b/docs/architecture/adr/027-repository-hooks-artifact-type.md index 5619927..56fd985 100644 --- a/docs/architecture/adr/027-repository-hooks-artifact-type.md +++ b/docs/architecture/adr/027-repository-hooks-artifact-type.md @@ -30,8 +30,7 @@ Hooks are promoted to a first-class artifact type in vstack. A new singular type `hook` is added to the generator and CLI type registry. -- Source templates: `src/vstack/_templates/hooks/<name>/hook.json` -- Source metadata: `src/vstack/_templates/hooks/<name>/config.yaml` +- Source template: `src/vstack/_templates/hooks/<name>/hook.yaml` - Install output: `.github/hooks/<name>.json` ### 2) Manifest tracking and verify behavior @@ -53,11 +52,16 @@ vstack ships multiple default hooks, not just one sample: - `session-audit` - `pre-tool-safety-gate` - `post-edit-format` +- `post-edit-markdown-quality` - `post-commit-security-scan` These templates are conservative defaults that provide immediate, low-risk guidance. Teams can replace or extend them via normal template customization patterns. +For vstack itself, the markdown-quality hook is part of the default baseline because +the repository's primary outputs include ADRs, docs, prompts, instructions, and other +Markdown-heavy work items. + ### 4) Separation from per-agent frontmatter hooks Per-agent frontmatter `hooks` remains supported and unchanged. @@ -73,10 +77,44 @@ For existing repositories: 1. upgrade to a vstack version that includes this ADR implementation, 1. run `vstack install` (or `vstack init` in CI), -1. optional: disable repository hooks using `.vstack/config.yaml` with `exclude.hook`. +1. optional: disable repository hooks using `.vstack/config.yaml` with `exclude.hook` + or the `hooks:` configuration block. No existing agent configuration must be rewritten. +### 6) Execution model and dependency policy + +Repository hooks execute in the active Copilot hook runtime context for the current +workspace session. + +- local session: hooks run on the local machine, +- remote/devcontainer/SSH session: hooks run in that remote runtime. + +Hook defaults must remain operational in `audit` mode without hard external-tool +dependencies. Optional integrations (for example `make` or `gitleaks`) may run only +when explicitly enabled, and implementations must degrade safely when those tools are +not available. + +### 7) Project-level hook controls and template metadata semantics + +Projects may control the default generated hook baseline from `.vstack/config.yaml`. + +Supported controls: + +- `hooks.enabled`: disable the baseline hook family for the project, +- `hooks.mode`: set the generated default runtime mode (`audit` or `enforce`), +- `hooks.hooks.<name>.enabled`: disable one named hook, +- `hooks.hooks.<name>.mode`: override mode for one named hook. + +Hook `hook.yaml` files include descriptive metadata beyond versioning. This metadata +improves maintainability and reviewability while preserving the runtime JSON contract. + +Examples include: + +- `description` and `purpose` to state intent, +- `security_level` and `mode_default` to communicate expected operating posture, +- dependency hints for optional enforce-mode tooling. + ## alternatives considered ### A) Keep hooks only as per-agent raw frontmatter diff --git a/docs/design/hook-yaml-schema.md b/docs/design/hook-yaml-schema.md new file mode 100644 index 0000000..5379606 --- /dev/null +++ b/docs/design/hook-yaml-schema.md @@ -0,0 +1,254 @@ +# Hook YAML Template Format + +> Specification for repository hook YAML source templates in vstack. + +## Overview + +Hook templates are authored in YAML for readability and multiline command support. +The YAML file (`hook.yaml`) contains both metadata and the GitHub Copilot hooks definition. + +At install time, vstack: + +1. Loads hook template YAML +1. Validates metadata and structure +1. Generates JSON output at `.github/hooks/<name>.json` + +This decouples human-friendly source (YAML with long commands) from machine-readable output (JSON). + +______________________________________________________________________ + +## Schema + +```yaml +version: <integer> + # Artifact template version, incremented on semantic changes. + # Example: 20260510003 + +metadata: + name: <string> + # Hook identifier, used in URLs, file names, and logging. + # Must be lowercase, alphanumeric + hyphen. + # Example: "session-audit" + + description: <string> + # Human-readable explanation of hook behavior and intent. + # Supports multi-line folded text. + # Example: "Records session start/end events to JSONL audit logs." + + purpose: <enum: audit | security | quality> + # Functional classification. + # - audit: event recording without enforcement + # - security: threat detection or access control + # - quality: code style and compliance + + security_level: <enum: low | high> + # Risk classification. + # - low: read-only or non-destructive operations + # - high: may block, modify, or enforce policy + + mode_default: <enum: audit | enforce> + # Default VSTACK_HOOKS_MODE when environment variable is unset. + # - audit: non-intrusive logging and detection + # - enforce: strict policy, optional tool execution + # Most hooks default to 'audit' for safety. + + execution_context: <enum: copilot-hook-runtime | ci | local> + # Expected environment where this hook runs. + # - copilot-hook-runtime: GitHub Copilot hook system (default) + # - ci: CI/CD pipeline environment + # - local: developer machine + # Hints for tool and permission assumptions. + + dependencies: + required: [<string>, ...] + # Tools/commands that MUST exist for hook to function. + # Hook fails or degrades if missing. + # Example: ["git", "make"] + + optional: [<string>, ...] + # Tools used conditionally, usually in 'enforce' mode. + # Hook logs when missing but continues normally. + # Example: ["gitleaks", "make"] + +# GitHub Copilot hooks envelope +hooks: + <event_name>: + # Event name: one of: + # - sessionStart, sessionEnd + # - userPromptSubmitted + # - preToolUse, postToolUse + # - errorOccurred + + - type: command + # Currently, only type "command" is supported. + + description: <string> + # Optional: brief explanation of this action's behavior. + # Supports multi-line text. + + bash: <string> + # Bash script to execute. Supports multi-line. + # Input arrives via stdin (JSON payload from Copilot). + # Output to stdout for responses (JSON for permission decisions). + # Variables: $VSTACK_HOOKS_MODE (audit|enforce) + + powershell: <string> + # PowerShell script to execute. Supports multi-line. + # Read stdin with: $inputText = [Console]::In.ReadToEnd() + # Use environment variable: $env:VSTACK_HOOKS_MODE + + cwd: <string> + # Working directory. "." = workspace root. + + timeoutSec: <integer> + # Maximum execution time in seconds. Default 30. +``` + +______________________________________________________________________ + +## Example: Complete Hook Template + +```yaml +version: 20260510003 + +metadata: + name: session-audit + description: | + Records session start and end events to structured JSONL logs + in .vstack/logs/ for audit trail and compliance verification. + purpose: audit + security_level: low + mode_default: audit + execution_context: copilot-hook-runtime + dependencies: + required: [] + optional: [] + +hooks: + sessionStart: + - type: command + description: | + Log session start event with timestamp and context. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-session-start.jsonl + powershell: | + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + $inputText = [Console]::In.ReadToEnd() + Add-Content -Path .vstack/logs/hook-session-start.jsonl -Value $inputText + cwd: "." + timeoutSec: 10 + + sessionEnd: + - type: command + description: | + Log session end event for correlation with start. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-session-end.jsonl + powershell: | + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + $inputText = [Console]::In.ReadToEnd() + Add-Content -Path .vstack/logs/hook-session-end.jsonl -Value $inputText + cwd: "." + timeoutSec: 10 +``` + +______________________________________________________________________ + +## Authoring Guidance + +### Multi-line Scripts + +Use YAML literal folded scalars (`|-` or `|`) for scripts: + +```yaml +bash: | + mkdir -p .vstack/logs + input="$(cat)" + if [ -z "$input" ]; then + exit 1 + fi + printf '%s\n' "$input" >> .vstack/logs/audit.jsonl +``` + +### Bash Best Practices + +- Read stdin early: `input="$(cat)"` +- Check tool availability: `command -v git >/dev/null 2>&1` +- Use early exits: `[ "$VSTACK_HOOKS_MODE" != "enforce" ] && exit 0` +- Log errors to `.vstack/logs/hook-*.log`, not stderr + +### PowerShell Best Practices + +- Read stdin: `$inputText = [Console]::In.ReadToEnd()` +- Check commands: `Get-Command gitleaks -ErrorAction SilentlyContinue` +- Suppress output: `command *> $null` +- Use `$env:VSTACK_HOOKS_MODE` for mode detection + +### Environment Variables + +- `VSTACK_HOOKS_MODE` (string: `audit` | `enforce`) + - Default: `audit` if unset + - User can override in session +- `.vstack/logs/` for structured JSONL audit output +- `.vstack/config.yaml` available for shared settings + +______________________________________________________________________ + +## Generation and Installation + +### Source Workflow + +1. User authors hook template: `src/vstack/_templates/hooks/<name>/hook.yaml` +1. HookGenerator loads and validates YAML +1. Generator constructs GitHub Copilot hooks JSON envelope +1. Output written to `.github/hooks/<name>.json` + +### Local Project Workflow + +1. User copies/modifies hook template to `.vstack/templates/hooks/<name>/hook.yaml` +1. User runs `vstack install` +1. HookGenerator regenerates `.github/hooks/<name>.json` from YAML +1. Manifest updated with checksum + +______________________________________________________________________ + +## vstack Integration + +### Seeding + +When `vstack init` or `vstack install` runs locally, hook templates are seeded to: + +``` +.vstack/templates/hooks/<name>/hook.yaml +``` + +Users can modify these templates directly; `vstack install` will regenerate `.github/hooks/` JSON from them. + +### Manifest Tracking + +Hooks are tracked in `.vstack/vstack.json`: + +```json +{ + "hooks": { + "session-audit": { + "source_path": "src/vstack/_templates/hooks/session-audit/hook.yaml", + "output_path": ".github/hooks/session-audit.json", + "checksum": "sha256:abc123..." + } + } +} +``` + +### Verification + +`vstack verify` checks: + +- Source YAML is valid and well-formed +- Metadata fields are correct +- Hook structure complies with envelope spec +- Generated JSON checksums match manifest diff --git a/docs/design/hooks.md b/docs/design/hooks.md index f273d9e..9d98257 100644 --- a/docs/design/hooks.md +++ b/docs/design/hooks.md @@ -25,50 +25,86 @@ ______________________________________________________________________ ### 1.1 source and output mapping -| Element | Path pattern | -| ---------------- | ------------------------------------------------ | -| source config | `src/vstack/_templates/hooks/<name>/config.yaml` | -| source body | `src/vstack/_templates/hooks/<name>/hook.json` | -| installed output | `.github/hooks/<name>.json` | -| manifest key | `hooks` | -| singular type | `hook` | +| Element | Path pattern | +| ---------------- | ---------------------------------------------- | +| source template | `src/vstack/_templates/hooks/<name>/hook.yaml` | +| installed output | `.github/hooks/<name>.json` | +| manifest key | `hooks` | +| singular type | `hook` | ### 1.2 type behavior -Hook artifacts use JSON payloads, so they differ from markdown artifact families: +Hook artifacts render from YAML source templates into JSON payloads, so they differ from markdown artifact families: | Property | Value | | ------------------- | ----------- | | `add_frontmatter` | `false` | | `auto_gen_footer` | `false` | | `artifact_is_dir` | `false` | -| `template_filename` | `hook.json` | +| `template_filename` | `hook.yaml` | Implication: manifest verification must use checksum ownership and skip markdown-footer metadata checks. +Hook templates follow the GitHub Copilot hooks envelope: + +```json +{ + "version": 1, + "hooks": { + "preToolUse": [ + { + "type": "command", + "bash": "...", + "powershell": "..." + } + ] + } +} +``` + ______________________________________________________________________ ## 2. baseline hook set -vstack ships four default repository hooks: +vstack ships five default repository hooks: -| Hook name | Primary events | Intent | -| --------------------------- | ----------------------------- | ----------------------------------------------- | -| `session-audit` | `sessionStart`, `sessionEnd` | Session boundary visibility | -| `pre-tool-safety-gate` | `preToolUse`, `errorOccurred` | Safety signaling around risky operations | -| `post-edit-format` | `postToolUse` | Formatting reminder after edit-heavy operations | -| `post-commit-security-scan` | `postToolUse`, `sessionEnd` | Security hygiene reminder around git mutations | +| Hook name | Primary events | Intent | +| ---------------------------- | --------------------------------------------------- | ----------------------------------------------------------- | +| `session-audit` | `userPromptSubmitted`, `sessionStart`, `sessionEnd` | Structured JSONL session and prompt audit trail | +| `pre-tool-safety-gate` | `preToolUse`, `errorOccurred` | Deny destructive shell patterns + error logging | +| `post-edit-format` | `postToolUse` | Edit-event logging + optional `make format` run | +| `post-edit-markdown-quality` | `postToolUse` | vstack markdown/work-item formatting for docs and templates | +| `post-commit-security-scan` | `postToolUse`, `sessionEnd` | Git-mutation checks + optional `gitleaks` run | -These are conservative defaults and should remain safe to install in any repository. +These defaults are safe in audit mode and can be upgraded to enforcement behavior. +For vstack specifically, the markdown-quality hook gives the baseline hook set a direct payoff on ADRs, +design docs, prompts, instructions, and other generated work items. ______________________________________________________________________ ## 3. CLI contracts -### 3.1 selection and exclusion +### 3.1 selection, exclusion, and runtime defaults - `--only hook` is supported in all type-aware commands. - `.vstack/config.yaml` supports `exclude.hook` and per-name exclusions under `hook`. +- `.vstack/config.yaml` also supports a `hooks:` block for project-level defaults: + +```yaml +hooks: + enabled: true + mode: audit + hooks: + pre-tool-safety-gate: + mode: enforce + post-edit-markdown-quality: + enabled: false +``` + +- `hooks.enabled: false` disables the generated baseline hook family. +- `hooks.mode` sets the generated default fallback for `VSTACK_HOOKS_MODE`. +- `hooks.hooks.<name>.enabled: false` disables one named built-in hook. +- `hooks.hooks.<name>.mode` overrides the default mode for one named hook. ### 3.2 command behavior @@ -82,7 +118,19 @@ ______________________________________________________________________ ______________________________________________________________________ -## 4. migration and compatibility +## 4. source format and compatibility + +Source of truth for built-in hooks is a single `hook.yaml` file per hook directory. +The generator validates YAML structure, then renders the GitHub Copilot JSON envelope. + +That gives maintainers: + +- readable multiline shell commands, +- descriptive metadata next to behavior, +- project-level mode overrides at generation time, +- the same installed `.github/hooks/*.json` contract expected by Copilot. + +## 5. migration and compatibility Migration is additive. @@ -95,14 +143,70 @@ For existing repositories: Per-agent frontmatter field `hooks` remains supported and is intentionally separate from repository-level hooks artifacts. +## 6. operational modes + +Hook templates support two operational modes through `VSTACK_HOOKS_MODE`: + +- `audit` (default): record events and alerts without enforcing extra tool runs +- `enforce`: run optional actions such as `make format` and `gitleaks` when conditions match + +This keeps first-run installs non-disruptive while allowing stricter policy in CI or hardened repositories. + +## 7. execution context and dependencies + +### 6.1 where hooks execute + +Repository hooks execute in the active Copilot hook runtime context. + +- local workspace session: local machine runtime +- remote SSH/devcontainer session: remote runtime + +All built-in hooks use `cwd: "."` and therefore run from the workspace root. + +### 6.2 dependency strategy + +Built-in hooks follow a fail-safe dependency model: + +1. `audit` mode must work without optional tools, +1. `enforce` mode may call optional tools, +1. hooks must check tool availability before invoking external commands, +1. missing optional tools should log a structured alert and continue. + +This prevents install-time or first-run failures while supporting stricter policy in controlled environments. + +### 6.3 security posture + +- Keep default behavior non-destructive (`audit` first). +- Restrict deny-response behavior to explicit enforce paths. +- Use short timeouts and deterministic command paths. +- Never log secrets; log only event metadata and detection markers. + +## 8. config metadata contract + +`src/vstack/_templates/hooks/<name>/hook.yaml` includes a `metadata:` section. +These fields are documentation-oriented, and selected values such as `mode_default` are also +used as generation-time defaults when the project does not override them. + +Recommended fields: + +| Field | Purpose | +| ----------------------- | ----------------------------------------------- | +| `description` | Human-readable explanation of hook behavior | +| `purpose` | Classification (`audit`, `security`, `quality`) | +| `security_level` | Expected risk posture (`low`, `high`) | +| `mode_default` | Declared default operation mode | +| `execution_context` | Runtime location expectation | +| `dependencies.required` | Must-exist tools for intended behavior | +| `dependencies.optional` | Optional tooling used in enforce mode | + ______________________________________________________________________ -## 5. extension guidance +## 9. extension guidance When adding new built-in hooks: 1. add template directory under `src/vstack/_templates/hooks/<name>/` -1. include `config.yaml` and `hook.json` +1. include `hook.yaml` 1. add name to expected canonical hook list in CLI constants 1. regenerate artifacts via `python -m vstack install` 1. update this design doc and roadmap status if behavior changes diff --git a/docs/product/roadmap.md b/docs/product/roadmap.md index d87d71a..b8e480b 100644 --- a/docs/product/roadmap.md +++ b/docs/product/roadmap.md @@ -172,7 +172,7 @@ vstack now provides installable repository hook templates for quality-gate patte Implemented: - `hook` is a first-class artifact type in the generator and CLI type registry -- Templates live in `src/vstack/_templates/hooks/<name>/hook.json` (source of truth) +- Templates live in `src/vstack/_templates/hooks/<name>/hook.yaml` (source of truth) - Generated output is written to `.github/hooks/<name>.json` at install time - Hooks are registered in `.vstack/vstack.json` and tracked with checksums like other artifact types - The per-agent `hooks` frontmatter field remains separate and still scopes hooks to one diff --git a/src/vstack/_templates/hooks/post-commit-security-scan/config.yaml b/src/vstack/_templates/hooks/post-commit-security-scan/config.yaml deleted file mode 100644 index b18ab19..0000000 --- a/src/vstack/_templates/hooks/post-commit-security-scan/config.yaml +++ /dev/null @@ -1 +0,0 @@ -version: 20260510001 diff --git a/src/vstack/_templates/hooks/post-commit-security-scan/hook.json b/src/vstack/_templates/hooks/post-commit-security-scan/hook.json deleted file mode 100644 index 70431e3..0000000 --- a/src/vstack/_templates/hooks/post-commit-security-scan/hook.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "postToolUse": [ - { - "type": "command", - "command": "echo \"[vstack] security hint: run secret scan after git mutations\"" - } - ], - "sessionEnd": [ - { - "type": "command", - "command": "echo \"[vstack] security hint: run dependency and secret checks before push\"" - } - ] -} diff --git a/src/vstack/_templates/hooks/post-commit-security-scan/hook.yaml b/src/vstack/_templates/hooks/post-commit-security-scan/hook.yaml new file mode 100644 index 0000000..f433376 --- /dev/null +++ b/src/vstack/_templates/hooks/post-commit-security-scan/hook.yaml @@ -0,0 +1,79 @@ +version: 20260510003 + +metadata: + name: post-commit-security-scan + description: | + Staged diff security scanner. Detects git mutations and pattern-matches staged secrets + (AWS keys, GitHub tokens, private keys). Runs optional gitleaks scan when + VSTACK_HOOKS_MODE=enforce for deeper compliance scanning. + purpose: security + security_level: high + mode_default: audit + execution_context: copilot-hook-runtime + dependencies: + required: + - git + optional: + - gitleaks + +hooks: + postToolUse: + - type: command + description: | + Check staged diffs for secrets and log alerts to audit trail. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-post-commit.jsonl + if ! printf '%s' "$input" | grep -Eiq 'git (commit|push|merge|rebase)'; then + exit 0 + fi + if git rev-parse --git-dir >/dev/null 2>&1 && \ + git diff --cached 2>/dev/null | grep -Eiq 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----'; then + printf '%s\n' 'possible-secrets-detected-in-staged-diff' >> .vstack/logs/hook-security-alerts.log + fi + if [ "${VSTACK_HOOKS_MODE:-audit}" = "enforce" ]; then + if command -v gitleaks >/dev/null 2>&1; then + gitleaks dir . --no-banner --redact >/dev/null 2>&1 || printf '%s\n' 'gitleaks-reported-findings' >> .vstack/logs/hook-security-alerts.log + else + printf '%s\n' 'gitleaks-not-installed' >> .vstack/logs/hook-security-alerts.log + fi + fi + powershell: | + $inputText = [Console]::In.ReadToEnd() + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + Add-Content -Path .vstack/logs/hook-post-commit.jsonl -Value $inputText + if ($inputText -notmatch 'git (commit|push|merge|rebase)') { + exit 0 + } + if ((git rev-parse --git-dir 2>$null) -and (git diff --cached | Select-String -Pattern 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----' -Quiet)) { + Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'possible-secrets-detected-in-staged-diff' + } + $mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' } + if ($mode -eq 'enforce') { + if (Get-Command gitleaks -ErrorAction SilentlyContinue) { + gitleaks dir . --no-banner --redact *> $null + if ($LASTEXITCODE -ne 0) { + Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'gitleaks-reported-findings' + } + } else { + Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'gitleaks-not-installed' + } + } + cwd: "." + timeoutSec: 60 + + sessionEnd: + - type: command + description: | + Log session end for security scanning completeness. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-session-end-security.jsonl + powershell: | + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + $inputText = [Console]::In.ReadToEnd() + Add-Content -Path .vstack/logs/hook-session-end-security.jsonl -Value $inputText + cwd: "." + timeoutSec: 10 diff --git a/src/vstack/_templates/hooks/post-edit-format/config.yaml b/src/vstack/_templates/hooks/post-edit-format/config.yaml deleted file mode 100644 index b18ab19..0000000 --- a/src/vstack/_templates/hooks/post-edit-format/config.yaml +++ /dev/null @@ -1 +0,0 @@ -version: 20260510001 diff --git a/src/vstack/_templates/hooks/post-edit-format/hook.json b/src/vstack/_templates/hooks/post-edit-format/hook.json deleted file mode 100644 index ab6d20e..0000000 --- a/src/vstack/_templates/hooks/post-edit-format/hook.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "postToolUse": [ - { - "type": "command", - "command": "echo \"[vstack] format hint: run make format after file edits\"" - } - ] -} diff --git a/src/vstack/_templates/hooks/post-edit-format/hook.yaml b/src/vstack/_templates/hooks/post-edit-format/hook.yaml new file mode 100644 index 0000000..0457a33 --- /dev/null +++ b/src/vstack/_templates/hooks/post-edit-format/hook.yaml @@ -0,0 +1,61 @@ +version: 20260510003 + +metadata: + name: post-edit-format + description: | + Code quality enforcement hook. Runs 'make format' when VSTACK_HOOKS_MODE=enforce + on edit-like tools. Logs all edit events to audit trail regardless of mode. + purpose: quality + security_level: low + mode_default: audit + execution_context: copilot-hook-runtime + dependencies: + required: [] + optional: + - make + +hooks: + postToolUse: + - type: command + description: | + Log edit events and optionally run format in enforce mode. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-post-edit.jsonl + mode="${VSTACK_HOOKS_MODE:-audit}" + if [ "$mode" != "enforce" ]; then + exit 0 + fi + if printf '%s' "$input" | grep -Eq '"toolName"[[:space:]]*:[[:space:]]*"(edit|create|write|replace)"'; then + if [ -f Makefile ] && command -v make >/dev/null 2>&1; then + make format >/dev/null 2>&1 || printf '%s\n' 'format-run-failed' >> .vstack/logs/hook-quality-alerts.log + else + printf '%s\n' 'format-skipped-missing-make-or-makefile' >> .vstack/logs/hook-quality-alerts.log + fi + fi + powershell: | + $inputText = [Console]::In.ReadToEnd() + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + Add-Content -Path .vstack/logs/hook-post-edit.jsonl -Value $inputText + $mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' } + if ($mode -ne 'enforce') { + exit 0 + } + try { + $payload = $inputText | ConvertFrom-Json -ErrorAction Stop + } catch { + exit 0 + } + if ($payload.toolName -match '^(edit|create|write|replace)$') { + if ((Test-Path Makefile) -and (Get-Command make -ErrorAction SilentlyContinue)) { + make format *> $null + if ($LASTEXITCODE -ne 0) { + Add-Content -Path .vstack/logs/hook-quality-alerts.log -Value 'format-run-failed' + } + } else { + Add-Content -Path .vstack/logs/hook-quality-alerts.log -Value 'format-skipped-missing-make-or-makefile' + } + } + cwd: "." + timeoutSec: 30 diff --git a/src/vstack/_templates/hooks/post-edit-markdown-quality/hook.yaml b/src/vstack/_templates/hooks/post-edit-markdown-quality/hook.yaml new file mode 100644 index 0000000..5a58718 --- /dev/null +++ b/src/vstack/_templates/hooks/post-edit-markdown-quality/hook.yaml @@ -0,0 +1,64 @@ +version: 20260510004 + +metadata: + name: post-edit-markdown-quality + description: | + vstack-oriented markdown/work-item quality hook. Logs markdown-related edits + and, in enforce mode, runs markdown formatting targets when docs, ADRs, + prompts, instructions, or template markdown content appear in the tool payload. + purpose: quality + security_level: low + mode_default: audit + execution_context: copilot-hook-runtime + dependencies: + required: [] + optional: + - make + +hooks: + postToolUse: + - type: command + description: | + Log markdown-oriented edit events and optionally run markdown formatting. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-markdown-quality.jsonl + mode="${VSTACK_HOOKS_MODE:-audit}" + if [ "$mode" != "enforce" ]; then + exit 0 + fi + if ! printf '%s' "$input" | grep -Eiq '\.md|docs/|\.github/|\.vstack/templates/|adr/|prompt\.md|instructions\.md|SKILL\.md'; then + exit 0 + fi + if [ -f Makefile ] && command -v make >/dev/null 2>&1; then + make markdown-format >/dev/null 2>&1 || \ + make format >/dev/null 2>&1 || \ + printf '%s\n' 'markdown-format-run-failed' >> .vstack/logs/hook-markdown-quality-alerts.log + else + printf '%s\n' 'markdown-format-skipped-missing-make-or-makefile' >> .vstack/logs/hook-markdown-quality-alerts.log + fi + powershell: | + $inputText = [Console]::In.ReadToEnd() + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + Add-Content -Path .vstack/logs/hook-markdown-quality.jsonl -Value $inputText + $mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' } + if ($mode -ne 'enforce') { + exit 0 + } + if ($inputText -notmatch '\.md|docs/|\.github/|\.vstack/templates/|adr/|prompt\.md|instructions\.md|SKILL\.md') { + exit 0 + } + if ((Test-Path Makefile) -and (Get-Command make -ErrorAction SilentlyContinue)) { + make markdown-format *> $null + if ($LASTEXITCODE -ne 0) { + make format *> $null + } + if ($LASTEXITCODE -ne 0) { + Add-Content -Path .vstack/logs/hook-markdown-quality-alerts.log -Value 'markdown-format-run-failed' + } + } else { + Add-Content -Path .vstack/logs/hook-markdown-quality-alerts.log -Value 'markdown-format-skipped-missing-make-or-makefile' + } + cwd: "." + timeoutSec: 30 diff --git a/src/vstack/_templates/hooks/pre-tool-safety-gate/config.yaml b/src/vstack/_templates/hooks/pre-tool-safety-gate/config.yaml deleted file mode 100644 index b18ab19..0000000 --- a/src/vstack/_templates/hooks/pre-tool-safety-gate/config.yaml +++ /dev/null @@ -1 +0,0 @@ -version: 20260510001 diff --git a/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.json b/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.json deleted file mode 100644 index 9c71c5c..0000000 --- a/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "preToolUse": [ - { - "type": "command", - "command": "echo \"[vstack] safety gate: review destructive operations before execution\"" - } - ], - "errorOccurred": [ - { - "type": "command", - "command": "echo \"[vstack] safety gate: execution error captured\"" - } - ] -} diff --git a/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.yaml b/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.yaml new file mode 100644 index 0000000..4746d18 --- /dev/null +++ b/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.yaml @@ -0,0 +1,67 @@ +version: 20260510003 + +metadata: + name: pre-tool-safety-gate + description: | + Destructive operation safety gate. Logs all tool invocations to audit trail. + In enforce mode, blocks dangerous commands (rm -rf /, DROP TABLE, git reset --hard) + with explicit deny response. + purpose: security + security_level: high + mode_default: audit + execution_context: copilot-hook-runtime + dependencies: + required: [] + optional: [] + +hooks: + preToolUse: + - type: command + description: | + Check for destructive bash patterns and block in enforce mode. + Always logs to audit trail. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-pre-tool.jsonl + mode="${VSTACK_HOOKS_MODE:-audit}" + if [ "$mode" != "enforce" ]; then + exit 0 + fi + if printf '%s' "$input" | grep -Eq '"toolName"[[:space:]]*:[[:space:]]*"bash"' && \ + printf '%s' "$input" | grep -Eiq 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx'; then + echo '{"permissionDecision":"deny","permissionDecisionReason":"Blocked potentially destructive shell command by vstack pre-tool safety policy"}' + fi + powershell: | + $inputText = [Console]::In.ReadToEnd() + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + Add-Content -Path .vstack/logs/hook-pre-tool.jsonl -Value $inputText + $mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' } + if ($mode -ne 'enforce') { + exit 0 + } + try { + $payload = $inputText | ConvertFrom-Json -ErrorAction Stop + } catch { + exit 0 + } + if ($payload.toolName -eq 'bash' -and $inputText -match 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx') { + @{ permissionDecision = 'deny'; permissionDecisionReason = 'Blocked potentially destructive shell command by vstack pre-tool safety policy' } | ConvertTo-Json -Compress + } + cwd: "." + timeoutSec: 15 + + errorOccurred: + - type: command + description: | + Log tool errors for incident investigation and debugging. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-errors.jsonl + powershell: | + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + $inputText = [Console]::In.ReadToEnd() + Add-Content -Path .vstack/logs/hook-errors.jsonl -Value $inputText + cwd: "." + timeoutSec: 10 diff --git a/src/vstack/_templates/hooks/session-audit/config.yaml b/src/vstack/_templates/hooks/session-audit/config.yaml deleted file mode 100644 index b18ab19..0000000 --- a/src/vstack/_templates/hooks/session-audit/config.yaml +++ /dev/null @@ -1 +0,0 @@ -version: 20260510001 diff --git a/src/vstack/_templates/hooks/session-audit/hook.json b/src/vstack/_templates/hooks/session-audit/hook.json deleted file mode 100644 index 828c84d..0000000 --- a/src/vstack/_templates/hooks/session-audit/hook.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "sessionStart": [ - { - "type": "command", - "command": "echo \"[vstack] session started\"" - } - ], - "sessionEnd": [ - { - "type": "command", - "command": "echo \"[vstack] session finished\"" - } - ] -} diff --git a/src/vstack/_templates/hooks/session-audit/hook.yaml b/src/vstack/_templates/hooks/session-audit/hook.yaml new file mode 100644 index 0000000..0f2bcfb --- /dev/null +++ b/src/vstack/_templates/hooks/session-audit/hook.yaml @@ -0,0 +1,60 @@ +version: 20260510003 + +metadata: + name: session-audit + description: | + Records session start and end events to structured JSONL logs + in .vstack/logs/ for audit trail and compliance verification. + purpose: audit + security_level: low + mode_default: audit + execution_context: copilot-hook-runtime + dependencies: + required: [] + optional: [] + +hooks: + userPromptSubmitted: + - type: command + description: | + Log prompt submission events for audit correlation. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-user-prompt.jsonl + powershell: | + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + $inputText = [Console]::In.ReadToEnd() + Add-Content -Path .vstack/logs/hook-user-prompt.jsonl -Value $inputText + cwd: "." + timeoutSec: 10 + + sessionStart: + - type: command + description: | + Log session start event with timestamp and context. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-session-start.jsonl + powershell: | + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + $inputText = [Console]::In.ReadToEnd() + Add-Content -Path .vstack/logs/hook-session-start.jsonl -Value $inputText + cwd: "." + timeoutSec: 10 + + sessionEnd: + - type: command + description: | + Log session end event for correlation with start. + bash: | + mkdir -p .vstack/logs + input="$(cat)" + printf '%s\n' "$input" >> .vstack/logs/hook-session-end.jsonl + powershell: | + New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null + $inputText = [Console]::In.ReadToEnd() + Add-Content -Path .vstack/logs/hook-session-end.jsonl -Value $inputText + cwd: "." + timeoutSec: 10 diff --git a/src/vstack/_templates/project/.vstack/config.yaml b/src/vstack/_templates/project/.vstack/config.yaml index 1edd881..6c817ce 100644 --- a/src/vstack/_templates/project/.vstack/config.yaml +++ b/src/vstack/_templates/project/.vstack/config.yaml @@ -22,9 +22,31 @@ # - terragrunt # - helm # - k8s +# hooks: +# - post-edit-markdown-quality # instructions: all # prompts: all +# ----------------------------------------------------------------------------- +# REPOSITORY HOOKS +# +# Hooks are installed under .github/hooks/*.json and can be tuned here. +# +# hooks.enabled: disable the entire generated hook baseline +# hooks.mode: default runtime posture for generated hooks (audit or enforce) +# hooks.hooks.<name>.enabled: disable one named hook without disabling the type +# hooks.hooks.<name>.mode: override the default mode for one named hook +# ----------------------------------------------------------------------------- + +# hooks: +# enabled: true +# mode: audit +# hooks: +# pre-tool-safety-gate: +# mode: enforce +# post-edit-markdown-quality: +# enabled: false + # ----------------------------------------------------------------------------- # ROOT DIRECTORY FOR GENERATED WORK ITEMS # diff --git a/src/vstack/cli/constants.py b/src/vstack/cli/constants.py index 4e73a06..910eec8 100644 --- a/src/vstack/cli/constants.py +++ b/src/vstack/cli/constants.py @@ -111,6 +111,7 @@ class ArtifactState: "typescript", ], "hook": [ + "post-edit-markdown-quality", "post-edit-format", "post-commit-security-scan", "pre-tool-safety-gate", diff --git a/src/vstack/cli/install.py b/src/vstack/cli/install.py index b0d8fc2..d60feaa 100644 --- a/src/vstack/cli/install.py +++ b/src/vstack/cli/install.py @@ -97,11 +97,12 @@ def _write_vstack_gitignore(*, project_root: Path, dry_run: bool) -> None: Unlike seed files, ``.gitignore`` is always regenerated on install so it stays current with changes to the ``.vstack/`` directory layout. The file - ignores scratch/temporary output under ``.vstack/tmp/`` while keeping all - other committed files — ``.gitignore`` itself, ``config.yaml``, + ignores scratch/temporary output under ``.vstack/tmp/`` and runtime audit + logs under ``.vstack/logs/`` while keeping all other committed files — + ``.gitignore`` itself, ``config.yaml``, ``vstack.json``, and the seeded ``templates/`` tree — tracked by Git. """ - content = "# Generated by vstack install — do not edit manually.\ntmp/\n" + content = "# Generated by vstack install — do not edit manually.\ntmp/\nlogs/\n" gitignore = project_root / ".vstack" / ".gitignore" if not dry_run: gitignore.parent.mkdir(parents=True, exist_ok=True) diff --git a/src/vstack/cli/interface.py b/src/vstack/cli/interface.py index 3098f6b..d831404 100644 --- a/src/vstack/cli/interface.py +++ b/src/vstack/cli/interface.py @@ -18,6 +18,7 @@ # Maps plural config.yaml keys to singular internal type names. _CONFIG_TYPE_ALIAS: dict[str, str] = { "agents": "agent", + "hooks": "hook", "instructions": "instruction", "prompts": "prompt", "skills": "skill", @@ -228,6 +229,48 @@ def _read_workflow_mode(install_dir: Path | None) -> str: return mode return "agentic" + @staticmethod + def _read_hook_settings( + install_dir: Path | None, + ) -> tuple[bool, str, list[str], dict[str, str]]: + """Read project-level hook enablement and mode defaults from config.""" + if install_dir is None: + return True, "audit", [], {} + config_path = install_dir.parent / ".vstack" / "config.yaml" + if not config_path.exists(): + return True, "audit", [], {} + + parsed = FrontmatterParser.parse_yaml(config_path.read_text(encoding="utf-8")) + hooks_config = parsed.get("hooks", "") + if not isinstance(hooks_config, dict): + return True, "audit", [], {} + + enabled = hooks_config.get("enabled", True) + global_enabled = enabled if isinstance(enabled, bool) else True + + mode_value = hooks_config.get("mode", "audit") + default_mode = ( + mode_value + if isinstance(mode_value, str) and mode_value in {"audit", "enforce"} + else "audit" + ) + + disabled_names: list[str] = [] + mode_overrides: dict[str, str] = {} + per_hook = hooks_config.get("hooks", "") + if isinstance(per_hook, dict): + for hook_name, hook_config in per_hook.items(): + if not isinstance(hook_name, str) or not isinstance(hook_config, dict): + continue + if hook_config.get("enabled") is False: + disabled_names.append(hook_name) + continue + hook_mode = hook_config.get("mode", "") + if isinstance(hook_mode, str) and hook_mode in {"audit", "enforce"}: + mode_overrides[hook_name] = hook_mode + + return global_enabled, default_mode, disabled_names, mode_overrides + @staticmethod def _parse_stage_handoffs(item: dict) -> list[dict[str, str]]: """Parse the ``handoffs`` entry from a workflow stage dict. @@ -297,11 +340,17 @@ def run(self) -> int: items_root = self._read_items_root(resolved_install_dir) workflow_stages = self._read_workflow_stages(resolved_install_dir) workflow_mode = self._read_workflow_mode(resolved_install_dir) + hooks_enabled, hook_default_mode, disabled_hook_names, hook_mode_overrides = ( + self._read_hook_settings(resolved_install_dir) + ) service = cast(Any, self._service_cls)( templates_root=self._templates_root, items_root=items_root, workflow_stages=workflow_stages, workflow_mode=workflow_mode, + hook_default_mode=hook_default_mode, + hook_mode_overrides=hook_mode_overrides, + disabled_hook_names=disabled_hook_names, ) commands = self._build_command_registry(service) effective_only = self._resolve_only_filter( @@ -309,6 +358,13 @@ def run(self) -> int: resolve_only_for_scope=command_config.resolve_only_for_scope, ) excluded_types, excluded_names = self._read_exclude(resolved_install_dir) + if not hooks_enabled: + excluded_types = frozenset(set(excluded_types) | {"hook"}) + elif disabled_hook_names: + merged = dict(excluded_names) + merged.setdefault("hook", []) + merged["hook"] = sorted(set(merged["hook"]) | set(disabled_hook_names)) + excluded_names = merged if excluded_types: base = effective_only if effective_only is not None else list(KNOWN_TYPE_NAMES) effective_only = [t for t in base if t not in excluded_types] diff --git a/src/vstack/cli/service.py b/src/vstack/cli/service.py index 6a09768..d986afa 100644 --- a/src/vstack/cli/service.py +++ b/src/vstack/cli/service.py @@ -24,6 +24,8 @@ from vstack.artifacts.generator import GenericArtifactGenerator from vstack.cli.constants import KNOWN_TYPES, ArtifactState from vstack.constants import ARTIFACTS_DOCS_ROOT, VSTACK_DIR_NAME +from vstack.hooks.config import HOOK_TYPE +from vstack.hooks.generator import HookGenerator from vstack.manifest import CURRENT_MANIFEST_VERSION, ManifestFile, hash_with_algorithm @@ -42,6 +44,9 @@ def __init__( artifacts_root: str | None = None, workflow_stages: list[dict[str, str]] | None = None, workflow_mode: str = "agentic", + hook_default_mode: str = "audit", + hook_mode_overrides: dict[str, str] | None = None, + disabled_hook_names: list[str] | None = None, ) -> None: """Create generators for all known artifact families. @@ -77,6 +82,13 @@ def __init__( workflow_mode=workflow_mode, ) if tc is AGENT_TYPE + else HookGenerator( + templates_root, + default_mode=hook_default_mode, + mode_overrides=hook_mode_overrides, + disabled_names=disabled_hook_names, + ) + if tc is HOOK_TYPE else GenericArtifactGenerator(tc, templates_root) for tc in KNOWN_TYPES ] diff --git a/src/vstack/hooks/constants.py b/src/vstack/hooks/constants.py index 7b2d0c4..2c5aaee 100644 --- a/src/vstack/hooks/constants.py +++ b/src/vstack/hooks/constants.py @@ -5,6 +5,6 @@ HOOK_OUTPUT_SUFFIX = ".json" HOOK_TEMPLATES_SUBDIR = "hooks" HOOK_OUTPUT_SUBDIR = "hooks" -HOOK_TEMPLATE_FILENAME = "hook.json" +HOOK_TEMPLATE_FILENAME = "hook.yaml" HOOK_TEMPLATES_DIR = TEMPLATES_ROOT / HOOK_TEMPLATES_SUBDIR diff --git a/src/vstack/hooks/generator.py b/src/vstack/hooks/generator.py index b65a2ec..e9fa16e 100644 --- a/src/vstack/hooks/generator.py +++ b/src/vstack/hooks/generator.py @@ -1,13 +1,237 @@ -"""Thin hook generator wrapper over ``GenericArtifactGenerator``.""" +"""Hook generator with YAML source support and project-level hook settings.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import yaml from vstack.artifacts.generator import GenericArtifactGenerator +from vstack.artifacts.models import RenderedArtifact from vstack.constants import TEMPLATES_ROOT from vstack.hooks.config import HOOK_TYPE +from vstack.models import CheckMessage, ValidationResult class HookGenerator(GenericArtifactGenerator): - """Generate hook artifacts using the built-in hook configuration.""" + """Generate hook artifacts using YAML templates and project hook settings.""" + + _ALLOWED_EVENTS = { + "sessionStart", + "sessionEnd", + "userPromptSubmitted", + "preToolUse", + "postToolUse", + "errorOccurred", + } + _ALLOWED_PURPOSES = {"audit", "security", "quality"} + _ALLOWED_SECURITY_LEVELS = {"low", "high"} + _ALLOWED_MODES = {"audit", "enforce"} + _ALLOWED_ACTION_TYPES = {"command"} + + def __init__( + self, + templates_root: Path | None = None, + *, + default_mode: str = "audit", + mode_overrides: dict[str, str] | None = None, + disabled_names: list[str] | None = None, + ) -> None: + """Create a hook generator bound to the built-in or provided template root.""" + super().__init__(HOOK_TYPE, templates_root or TEMPLATES_ROOT) + self.default_mode = default_mode if default_mode in self._ALLOWED_MODES else "audit" + self.mode_overrides = { + name: mode + for name, mode in (mode_overrides or {}).items() + if mode in self._ALLOWED_MODES + } + self.disabled_names = set(disabled_names or []) + + def find_templates(self) -> list[Path]: + """Return hook templates, excluding hooks disabled by project config.""" + return [tmpl for tmpl in super().find_templates() if tmpl.name not in self.disabled_names] + + def _load_yaml_template(self, yaml_path: Path) -> dict: + """Load and parse a YAML hook template file.""" + try: + content = yaml.safe_load(yaml_path.read_text(encoding="utf-8")) + except yaml.YAMLError as exc: + raise ValueError(f"YAML parsing error: {exc}") from exc + if not isinstance(content, dict): + raise ValueError("YAML must contain a top-level object") + return content + + def _mode_for_hook(self, hook_name: str) -> str: + """Resolve the configured default mode for one hook.""" + return self.mode_overrides.get(hook_name, self.default_mode) + + def _apply_mode_defaults(self, text: str, *, mode: str) -> str: + """Replace audit fallbacks inside shell snippets with the configured mode.""" + return ( + text.replace("${VSTACK_HOOKS_MODE:-audit}", f"${{VSTACK_HOOKS_MODE:-{mode}}}") + .replace("else { 'audit' }", f"else {{ '{mode}' }}") + .replace('else { "audit" }', f'else {{ "{mode}" }}') + ) + + def _generate_json_from_yaml(self, yaml_data: dict) -> str: + """Convert validated YAML data to the GitHub Copilot hooks envelope.""" + metadata = yaml_data.get("metadata", {}) + hook_name = str(metadata.get("name", "")) + mode = self._mode_for_hook(hook_name) + hooks_dict = yaml_data.get("hooks", {}) + + rendered_hooks: dict[str, list[dict]] = {} + for event, actions in hooks_dict.items(): + rendered_actions: list[dict] = [] + for action in actions: + rendered_action = dict(action) + for shell_name in ("bash", "powershell"): + command = rendered_action.get(shell_name) + if isinstance(command, str): + rendered_action[shell_name] = self._apply_mode_defaults(command, mode=mode) + rendered_actions.append(rendered_action) + rendered_hooks[event] = rendered_actions + + return json.dumps({"version": 1, "hooks": rendered_hooks}, indent=2) + + def find_extra_files(self, tmpl_dir: Path) -> list[Path]: + """Hook templates use a single YAML file and do not copy sidecar files.""" + return [] + + def render(self, tmpl_dir: Path) -> RenderedArtifact: + """Render one YAML hook template to a JSON hook artifact.""" + tmpl_file = tmpl_dir / self.config.template_filename + yaml_data = self._load_yaml_template(tmpl_file) + metadata = yaml_data.get("metadata", {}) + name = str(metadata.get("name", tmpl_dir.name)) + json_content = self._generate_json_from_yaml(yaml_data) + return RenderedArtifact( + name=name, + content=json_content, + source_path=tmpl_file, + frontmatter=None, + unresolved=[], + ) + + def verify_input(self, expected_names: list[str] | None = None) -> ValidationResult: + """Verify hook templates and enforce the hook YAML schema constraints.""" + result = super().verify_input(expected_names) + + for tmpl_dir in self.find_templates(): + hook_path = tmpl_dir / self.config.template_filename + rel_path = f"templates/{self.config.templates_dir}/{tmpl_dir.name}/{hook_path.name}" + + try: + payload = yaml.safe_load(hook_path.read_text(encoding="utf-8")) + except yaml.YAMLError as exc: + result.messages.append(CheckMessage("fail", f"{rel_path} invalid YAML ({exc})")) + continue + + if not isinstance(payload, dict): + result.messages.append( + CheckMessage("fail", f"{rel_path} must contain a top-level YAML object") + ) + continue + + version = payload.get("version") + if isinstance(version, int) and version > 0: + result.messages.append(CheckMessage("pass", f"{rel_path} has supported version")) + else: + result.messages.append( + CheckMessage("fail", f"{rel_path} must set a positive integer 'version'") + ) + + metadata = payload.get("metadata") + if not isinstance(metadata, dict): + result.messages.append( + CheckMessage("fail", f"{rel_path} must define 'metadata' section as object") + ) + continue + + if not isinstance(metadata.get("name"), str): + result.messages.append( + CheckMessage("fail", f"{rel_path} metadata.name must be string") + ) + if not isinstance(metadata.get("description"), str): + result.messages.append( + CheckMessage("fail", f"{rel_path} metadata.description must be string") + ) + + purpose = metadata.get("purpose") + if purpose not in self._ALLOWED_PURPOSES: + result.messages.append( + CheckMessage( + "fail", + f"{rel_path} metadata.purpose must be one of: {', '.join(sorted(self._ALLOWED_PURPOSES))}", + ) + ) + + security_level = metadata.get("security_level") + if security_level not in self._ALLOWED_SECURITY_LEVELS: + result.messages.append( + CheckMessage( + "fail", + f"{rel_path} metadata.security_level must be one of: {', '.join(sorted(self._ALLOWED_SECURITY_LEVELS))}", + ) + ) + + mode_default = metadata.get("mode_default") + if mode_default not in self._ALLOWED_MODES: + result.messages.append( + CheckMessage( + "fail", + f"{rel_path} metadata.mode_default must be one of: {', '.join(sorted(self._ALLOWED_MODES))}", + ) + ) + + hooks = payload.get("hooks") + if not isinstance(hooks, dict) or not hooks: + result.messages.append( + CheckMessage("fail", f"{rel_path} must define a non-empty 'hooks' object") + ) + continue + + unknown_events = [event for event in hooks if event not in self._ALLOWED_EVENTS] + if unknown_events: + quoted = ", ".join(sorted(unknown_events)) + result.messages.append( + CheckMessage("fail", f"{rel_path} contains unsupported hook events: {quoted}") + ) + + for event, actions in hooks.items(): + if not isinstance(actions, list) or not actions: + result.messages.append( + CheckMessage("fail", f"{rel_path} event '{event}' must be a non-empty list") + ) + continue + + for idx, action in enumerate(actions): + action_label = f"{rel_path} event '{event}' action[{idx}]" + if not isinstance(action, dict): + result.messages.append( + CheckMessage("fail", f"{action_label} must be an object") + ) + continue + + if action.get("type") not in self._ALLOWED_ACTION_TYPES: + result.messages.append( + CheckMessage( + "fail", + f"{action_label} type must be one of: {', '.join(sorted(self._ALLOWED_ACTION_TYPES))}", + ) + ) + continue + + has_shell_cmd = isinstance(action.get("bash"), str) or isinstance( + action.get("powershell"), str + ) + if not has_shell_cmd: + result.messages.append( + CheckMessage( + "fail", + f"{action_label} must define 'bash' and/or 'powershell' as string", + ) + ) - def __init__(self) -> None: - """Create a hook generator bound to the built-in template root.""" - super().__init__(HOOK_TYPE, TEMPLATES_ROOT) + return result diff --git a/tests/vstack/cli/test_install.py b/tests/vstack/cli/test_install.py index d25aae2..388fa77 100644 --- a/tests/vstack/cli/test_install.py +++ b/tests/vstack/cli/test_install.py @@ -352,6 +352,7 @@ def test_write_vstack_gitignore_creates_file(self, tmp_path: Path) -> None: assert gitignore.exists() content = gitignore.read_text(encoding="utf-8") assert "tmp/\n" in content + assert "logs/\n" in content assert "*\n" not in content def test_write_vstack_gitignore_overwrites_existing(self, tmp_path: Path) -> None: diff --git a/tests/vstack/cli/test_interface.py b/tests/vstack/cli/test_interface.py index 2937d25..f696595 100644 --- a/tests/vstack/cli/test_interface.py +++ b/tests/vstack/cli/test_interface.py @@ -8,7 +8,7 @@ import pytest -from vstack.cli.base import CommandContext +from vstack.cli.base import BaseCommand, CommandContext from vstack.cli.interface import CommandLineInterface @@ -40,6 +40,14 @@ def resolve_targets(self, _args, *, command_name: str = "command") -> object: return self._resolved_target +class _ExposedCommandLineInterface(CommandLineInterface): + """Test helper exposing selected internals through public wrappers.""" + + def build_command_registry_public(self, service: Any) -> dict[str, BaseCommand]: + """Expose registry construction for direct branch coverage.""" + return self._build_command_registry(cast(Any, service)) + + class _Command: """Command test double that records invocation context.""" @@ -63,18 +71,31 @@ def __init__( items_root: str = "docs", workflow_stages=None, workflow_mode: str = "agentic", + hook_default_mode: str = "audit", + hook_mode_overrides=None, + disabled_hook_names=None, excluded_names=None, ) -> None: self.templates_root = templates_root self.items_root = items_root self.workflow_stages = workflow_stages self.workflow_mode = workflow_mode + self.hook_default_mode = hook_default_mode + self.hook_mode_overrides = hook_mode_overrides + self.disabled_hook_names = disabled_hook_names self.excluded_names = excluded_names class TestCommandLineInterface: """Test cases for interface-level parser and command dispatch.""" + def test_build_command_registry_instantiates_catalog_commands(self, tmp_path: Path) -> None: + """_build_command_registry returns instantiated command handlers.""" + interface = _ExposedCommandLineInterface(templates_root=tmp_path) + registry = interface.build_command_registry_public(_Service(templates_root=tmp_path)) + assert "install" in registry + assert "validate" in registry + def test_run_dispatches_validate_without_resolving_target(self, monkeypatch, tmp_path) -> None: """Test that validate passes through only-filter and no install_dir.""" args = argparse.Namespace(command="validate", only=["skill"], use_global=False) @@ -235,9 +256,20 @@ def __init__( items_root: str = "docs", workflow_stages=None, workflow_mode: str = "agentic", + hook_default_mode: str = "audit", + hook_mode_overrides=None, + disabled_hook_names=None, excluded_names=None, ) -> None: - del templates_root, workflow_stages, workflow_mode, excluded_names + del ( + templates_root, + workflow_stages, + workflow_mode, + hook_default_mode, + hook_mode_overrides, + disabled_hook_names, + excluded_names, + ) captured.append(items_root) monkeypatch.setattr( @@ -435,9 +467,20 @@ def __init__( items_root: str = "docs", workflow_stages=None, workflow_mode: str = "agentic", + hook_default_mode: str = "audit", + hook_mode_overrides=None, + disabled_hook_names=None, excluded_names=None, ) -> None: - del templates_root, items_root, workflow_stages, excluded_names + del ( + templates_root, + items_root, + workflow_stages, + hook_default_mode, + hook_mode_overrides, + disabled_hook_names, + excluded_names, + ) captured.append(workflow_mode) monkeypatch.setattr( @@ -456,6 +499,123 @@ def __init__( assert captured == ["hybrid"] +class TestReadHookSettings: + """Tests for CommandLineInterface._read_hook_settings.""" + + def test_returns_defaults_when_install_dir_is_none(self) -> None: + """Missing install dir yields enabled audit defaults.""" + assert CommandLineInterface._read_hook_settings(None) == (True, "audit", [], {}) + + def test_reads_global_and_per_hook_settings(self, tmp_path: Path) -> None: + """Reads hooks.enabled, hooks.mode, and per-hook overrides from config.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n" + " enabled: true\n" + " mode: enforce\n" + " hooks:\n" + " session-audit:\n" + " enabled: false\n" + " pre-tool-safety-gate:\n" + " mode: audit\n", + encoding="utf-8", + ) + + enabled, mode, disabled_names, mode_overrides = CommandLineInterface._read_hook_settings( + tmp_path / ".github" + ) + + assert enabled is True + assert mode == "enforce" + assert disabled_names == ["session-audit"] + assert mode_overrides == {"pre-tool-safety-gate": "audit"} + + def test_run_applies_disabled_hooks_to_context_and_service( + self, monkeypatch, tmp_path: Path + ) -> None: + """run() forwards hook settings into service construction and exclusions.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n" + " enabled: true\n" + " mode: enforce\n" + " hooks:\n" + " session-audit:\n" + " enabled: false\n", + encoding="utf-8", + ) + install_dir = tmp_path / ".github" + args = argparse.Namespace(command="install", only=None, use_global=False) + parser = _Parser(args=args, resolved_target=install_dir) + command = _Command(exit_code=0) + + monkeypatch.setattr( + CommandLineInterface, + "_build_command_registry", + lambda self, service: {"install": command}, + ) + + interface = CommandLineInterface( + parser_cls=cast(Any, lambda: parser), + service_cls=cast(Any, _Service), + templates_root=tmp_path, + ) + interface.run() + + assert command.calls[0].excluded_names == {"hook": ["session-audit"]} + + def test_read_hook_settings_ignores_non_dict_hook_entries(self, tmp_path: Path) -> None: + """Non-dict per-hook config entries are ignored gracefully.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n hooks:\n session-audit: disabled\n", + encoding="utf-8", + ) + + enabled, mode, disabled_names, mode_overrides = CommandLineInterface._read_hook_settings( + tmp_path / ".github" + ) + + assert enabled is True + assert mode == "audit" + assert disabled_names == [] + assert mode_overrides == {} + + def test_run_disables_hook_type_when_hooks_enabled_is_false( + self, monkeypatch, tmp_path: Path + ) -> None: + """run() removes hook from effective_only when hooks.enabled is false.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n enabled: false\n", + encoding="utf-8", + ) + install_dir = tmp_path / ".github" + args = argparse.Namespace(command="install", only=None, use_global=False) + parser = _Parser(args=args, resolved_target=install_dir) + command = _Command(exit_code=0) + + monkeypatch.setattr( + CommandLineInterface, + "_build_command_registry", + lambda self, service: {"install": command}, + ) + + interface = CommandLineInterface( + parser_cls=cast(Any, lambda: parser), + service_cls=cast(Any, _Service), + templates_root=tmp_path, + ) + interface.run() + + assert command.calls[0].only is not None + assert "hook" not in command.calls[0].only + + class TestParseStageHandoffs: """Tests for CommandLineInterface._parse_stage_handoffs.""" diff --git a/tests/vstack/hooks/test_config.py b/tests/vstack/hooks/test_config.py index e95132d..3ebc05d 100644 --- a/tests/vstack/hooks/test_config.py +++ b/tests/vstack/hooks/test_config.py @@ -28,6 +28,6 @@ def test_auto_gen_footer_is_false(self) -> None: """Hook artifacts must not include the auto-gen footer.""" assert HOOK_TYPE.auto_gen_footer is False - def test_template_filename_is_hook_json(self) -> None: - """Hook artifacts render from hook.json template files.""" - assert HOOK_TYPE.template_filename == "hook.json" + def test_template_filename_is_hook_yaml(self) -> None: + """Hook artifacts render from hook.yaml template files.""" + assert HOOK_TYPE.template_filename == "hook.yaml" diff --git a/tests/vstack/hooks/test_constants.py b/tests/vstack/hooks/test_constants.py index 87a04c4..6695ce6 100644 --- a/tests/vstack/hooks/test_constants.py +++ b/tests/vstack/hooks/test_constants.py @@ -25,6 +25,6 @@ def test_output_subdir_is_hooks(self) -> None: """Output subdir must be 'hooks'.""" assert HOOK_OUTPUT_SUBDIR == "hooks" - def test_template_filename_is_hook_json(self) -> None: - """Template filename must be hook.json.""" - assert HOOK_TEMPLATE_FILENAME == "hook.json" + def test_template_filename_is_hook_yaml(self) -> None: + """Template filename must be hook.yaml.""" + assert HOOK_TEMPLATE_FILENAME == "hook.yaml" diff --git a/tests/vstack/hooks/test_generator.py b/tests/vstack/hooks/test_generator.py index 93b819b..52c5e69 100644 --- a/tests/vstack/hooks/test_generator.py +++ b/tests/vstack/hooks/test_generator.py @@ -2,9 +2,20 @@ from __future__ import annotations +import json +from pathlib import Path + from vstack.hooks.generator import HookGenerator +class _ExposedHookGenerator(HookGenerator): + """Test helper exposing selected internals through public wrappers.""" + + def load_yaml_template_public(self, yaml_path: Path) -> dict: + """Expose YAML loading for focused failure-path tests.""" + return self._load_yaml_template(yaml_path) + + class TestHookGenerator: """Test cases for HookGenerator.""" @@ -12,3 +23,311 @@ def test_generator_uses_hook_type(self) -> None: """Test that generator uses hook type.""" gen = HookGenerator() assert gen.config.type_name == "hook" + + def test_verify_input_accepts_current_hook_templates(self) -> None: + """Built-in hook templates satisfy hook YAML structure checks.""" + gen = HookGenerator() + result = gen.verify_input() + assert result.failures == 0 + + def test_verify_input_reports_invalid_yaml(self, tmp_path: Path) -> None: + """Invalid YAML hook files are reported as source verification failures.""" + tmpl_dir = tmp_path / "hooks" / "broken" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510001\ninvalid: [yaml: structure:\n", encoding="utf-8" + ) + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + result = gen.verify_input() + + assert result.failures == 1 + assert any("invalid YAML" in msg.message for msg in result.messages) + + def test_verify_input_requires_version_and_hooks_wrapper(self, tmp_path: Path) -> None: + """Hook templates must use the official version/hooks wrapper structure.""" + tmpl_dir = tmp_path / "hooks" / "legacy" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "preToolUse:\n - type: command\n bash: 'echo hi'\n", + encoding="utf-8", + ) + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + result = gen.verify_input() + + assert result.failures == 2 + assert any("version" in msg.message.lower() for msg in result.messages) + assert any("hooks" in msg.message.lower() for msg in result.messages) + + def test_verify_input_rejects_non_dict_top_level(self, tmp_path: Path) -> None: + """Hook YAML must be a top-level object, not an array or primitive.""" + tmpl_dir = tmp_path / "hooks" / "array-hook" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text("- item1\n- item2\n", encoding="utf-8") + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + result = gen.verify_input() + + assert result.failures == 1 + assert any("must contain a top-level YAML object" in msg.message for msg in result.messages) + + def test_verify_input_rejects_unsupported_hook_events(self, tmp_path: Path) -> None: + """Hook templates cannot use unsupported event names.""" + tmpl_dir = tmp_path / "hooks" / "bad-events" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: bad-events\n" + " description: 'test'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n unknownEvent:\n - type: command\n bash: 'echo hi'\n", + encoding="utf-8", + ) + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + result = gen.verify_input() + + assert result.failures == 1 + assert any("unsupported hook events:" in msg.message for msg in result.messages) + + def test_verify_input_rejects_empty_actions_list(self, tmp_path: Path) -> None: + """Hook events must have at least one action.""" + tmpl_dir = tmp_path / "hooks" / "empty-actions" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: empty-actions\n" + " description: 'test'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n preToolUse: []\n", + encoding="utf-8", + ) + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + result = gen.verify_input() + + assert result.failures == 1 + assert any("must be a non-empty list" in msg.message for msg in result.messages) + + def test_verify_input_rejects_non_dict_action(self, tmp_path: Path) -> None: + """Hook actions must be objects, not arrays or primitives.""" + tmpl_dir = tmp_path / "hooks" / "bad-action" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: bad-action\n" + " description: 'test'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n preToolUse:\n - 'not an object'\n", + encoding="utf-8", + ) + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + result = gen.verify_input() + + assert result.failures == 1 + assert any("must be an object" in msg.message for msg in result.messages) + + def test_verify_input_rejects_wrong_action_type(self, tmp_path: Path) -> None: + """Hook actions must set type='command'.""" + tmpl_dir = tmp_path / "hooks" / "wrong-type" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: wrong-type\n" + " description: 'test'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n preToolUse:\n - type: webhook\n bash: 'echo hi'\n", + encoding="utf-8", + ) + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + result = gen.verify_input() + + assert result.failures == 1 + assert any("type must be one of:" in msg.message for msg in result.messages) + + def test_verify_input_rejects_missing_shell_commands(self, tmp_path: Path) -> None: + """Hook actions must have at least bash or powershell field.""" + tmpl_dir = tmp_path / "hooks" / "no-shell" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: no-shell\n" + " description: 'test'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n preToolUse:\n - type: command\n description: 'no shell command'\n", + encoding="utf-8", + ) + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + result = gen.verify_input() + + assert result.failures == 1 + assert any( + "must define 'bash' and/or 'powershell'" in msg.message for msg in result.messages + ) + + def test_render_generates_valid_json_from_yaml(self, tmp_path: Path) -> None: + """render() converts valid YAML templates to GitHub Copilot JSON.""" + tmpl_dir = tmp_path / "hooks" / "valid" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: valid\n" + " description: 'test hook'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n sessionStart:\n - type: command\n bash: 'echo start'\n", + encoding="utf-8", + ) + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + artifacts = gen.render_all() + + assert len(artifacts) == 1 + artifact = artifacts[0] + assert artifact.name == "valid" + + content = json.loads(artifact.content) + assert content["version"] == 1 + assert "hooks" in content + assert "sessionStart" in content["hooks"] + + def test_render_uses_project_default_mode_for_shell_fallbacks(self, tmp_path: Path) -> None: + """Configured project hook mode is injected into generated shell fallbacks.""" + tmpl_dir = tmp_path / "hooks" / "mode-test" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: mode-test\n" + " description: 'test hook'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n postToolUse:\n - type: command\n" + " bash: 'mode=${VSTACK_HOOKS_MODE:-audit}'\n" + " powershell: '$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { ''audit'' }'\n", + encoding="utf-8", + ) + + gen = HookGenerator(default_mode="enforce") + gen.templates_dir = tmp_path / "hooks" + artifact = gen.render_all()[0] + + content = json.loads(artifact.content) + action = content["hooks"]["postToolUse"][0] + assert "${VSTACK_HOOKS_MODE:-enforce}" in action["bash"] + assert "else { 'enforce' }" in action["powershell"] + + def test_find_templates_skips_disabled_hook_names(self, tmp_path: Path) -> None: + """Disabled hook names are excluded from rendering and validation.""" + enabled_dir = tmp_path / "hooks" / "enabled-hook" + enabled_dir.mkdir(parents=True) + (enabled_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: enabled-hook\n" + " description: 'test hook'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n sessionStart:\n - type: command\n bash: 'echo hi'\n", + encoding="utf-8", + ) + disabled_dir = tmp_path / "hooks" / "disabled-hook" + disabled_dir.mkdir(parents=True) + (disabled_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: disabled-hook\n" + " description: 'test hook'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n sessionStart:\n - type: command\n bash: 'echo hi'\n", + encoding="utf-8", + ) + + gen = HookGenerator(disabled_names=["disabled-hook"]) + gen.templates_dir = tmp_path / "hooks" + + assert [template.name for template in gen.find_templates()] == ["enabled-hook"] + + def test_load_yaml_template_rejects_invalid_yaml(self, tmp_path: Path) -> None: + """_load_yaml_template raises ValueError for invalid YAML source.""" + hook_path = tmp_path / "hook.yaml" + hook_path.write_text("invalid: [yaml\n", encoding="utf-8") + + gen = _ExposedHookGenerator() + + try: + gen.load_yaml_template_public(hook_path) + except ValueError as exc: + assert "YAML parsing error" in str(exc) + else: + raise AssertionError("Expected ValueError for invalid YAML") + + def test_load_yaml_template_rejects_non_object_yaml(self, tmp_path: Path) -> None: + """_load_yaml_template raises ValueError for non-object YAML.""" + hook_path = tmp_path / "hook.yaml" + hook_path.write_text("- item\n", encoding="utf-8") + + gen = _ExposedHookGenerator() + + try: + gen.load_yaml_template_public(hook_path) + except ValueError as exc: + assert "top-level object" in str(exc) + else: + raise AssertionError("Expected ValueError for non-object YAML") + + def test_find_extra_files_returns_empty_list(self, tmp_path: Path) -> None: + """Hook templates do not copy sidecar files during generation.""" + gen = HookGenerator() + assert gen.find_extra_files(tmp_path) == [] + + def test_verify_input_rejects_missing_metadata_fields(self, tmp_path: Path) -> None: + """verify_input reports invalid hook metadata fields individually.""" + tmpl_dir = tmp_path / "hooks" / "bad-metadata" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: 1\n description: 2\n" + " purpose: unknown\n security_level: medium\n mode_default: strict\n" + "hooks:\n sessionStart:\n - type: command\n bash: 'echo hi'\n", + encoding="utf-8", + ) + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + result = gen.verify_input() + + assert result.failures == 5 + assert any("metadata.name" in msg.message for msg in result.messages) + assert any("metadata.description" in msg.message for msg in result.messages) + assert any("metadata.purpose" in msg.message for msg in result.messages) + assert any("metadata.security_level" in msg.message for msg in result.messages) + assert any("metadata.mode_default" in msg.message for msg in result.messages) + + def test_verify_input_requires_positive_integer_version_and_hooks_object( + self, tmp_path: Path + ) -> None: + """verify_input rejects invalid version values and missing hooks.""" + tmpl_dir = tmp_path / "hooks" / "missing-hooks" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: broken\nmetadata:\n name: missing-hooks\n" + " description: 'test'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n", + encoding="utf-8", + ) + + gen = HookGenerator() + gen.templates_dir = tmp_path / "hooks" + result = gen.verify_input() + + assert result.failures == 2 + assert any("positive integer 'version'" in msg.message for msg in result.messages) + assert any("non-empty 'hooks' object" in msg.message for msg in result.messages) From ac9a6aef6ae24387c5fb413871acaa698f25c245 Mon Sep 17 00:00:00 2001 From: Erik Schaareman <erik@schaareman.org> Date: Sun, 10 May 2026 22:42:30 +0200 Subject: [PATCH 04/11] chore: regenerate generated artifacts --- .github/agents/architect.agent.md | 2 +- .github/agents/designer.agent.md | 2 +- .github/agents/engineer.agent.md | 2 +- .github/agents/planner.agent.md | 2 +- .github/agents/product.agent.md | 2 +- .github/agents/release.agent.md | 2 +- .github/agents/tester.agent.md | 2 +- .github/hooks/post-commit-security-scan.json | 37 ++-- .github/hooks/post-edit-format.json | 21 ++- .github/hooks/post-edit-markdown-quality.json | 15 ++ .github/hooks/pre-tool-safety-gate.json | 37 ++-- .github/hooks/session-audit.json | 47 +++-- .github/instructions/git.instructions.md | 2 +- .github/instructions/helm.instructions.md | 2 +- .github/instructions/java.instructions.md | 2 +- .github/instructions/k8s.instructions.md | 2 +- .github/instructions/markdown.instructions.md | 2 +- .github/instructions/python.instructions.md | 2 +- .github/instructions/rancher.instructions.md | 2 +- .github/instructions/security.instructions.md | 2 +- .../instructions/terraform.instructions.md | 2 +- .../instructions/terragrunt.instructions.md | 2 +- .github/instructions/testing.instructions.md | 2 +- .../instructions/typescript.instructions.md | 2 +- .github/prompts/api-design-review.prompt.md | 2 +- .github/prompts/architecture-risk.prompt.md | 2 +- .github/prompts/code-review.prompt.md | 2 +- .github/prompts/dependency-audit.prompt.md | 2 +- .github/prompts/incident-timeline.prompt.md | 2 +- .github/prompts/migration-safety.prompt.md | 2 +- .github/prompts/release-readiness.prompt.md | 2 +- .github/skills/adr/SKILL.md | 2 +- .github/skills/analyse/SKILL.md | 2 +- .github/skills/architecture/SKILL.md | 2 +- .github/skills/aws-cli/SKILL.md | 2 +- .github/skills/cicd/SKILL.md | 2 +- .github/skills/cloudformation/SKILL.md | 2 +- .github/skills/code-review/SKILL.md | 2 +- .github/skills/codeql/SKILL.md | 2 +- .github/skills/concise/SKILL.md | 2 +- .github/skills/consult/SKILL.md | 2 +- .github/skills/container/SKILL.md | 2 +- .github/skills/conventional-commit/SKILL.md | 2 +- .github/skills/debug/SKILL.md | 2 +- .github/skills/dependabot/SKILL.md | 2 +- .github/skills/dependency/SKILL.md | 2 +- .github/skills/design/SKILL.md | 2 +- .github/skills/docs/SKILL.md | 2 +- .github/skills/explore/SKILL.md | 2 +- .github/skills/gdpr/SKILL.md | 2 +- .github/skills/gh-issues/SKILL.md | 2 +- .github/skills/gh-release/SKILL.md | 2 +- .github/skills/guardrails/SKILL.md | 2 +- .github/skills/helm/SKILL.md | 2 +- .github/skills/incident/SKILL.md | 2 +- .github/skills/inspect/SKILL.md | 2 +- .github/skills/k8s/SKILL.md | 2 +- .github/skills/migrate/SKILL.md | 2 +- .github/skills/onboard/SKILL.md | 2 +- .github/skills/openapi/SKILL.md | 2 +- .github/skills/performance/SKILL.md | 2 +- .github/skills/postmortem/SKILL.md | 2 +- .github/skills/pr/SKILL.md | 2 +- .github/skills/rancher/SKILL.md | 2 +- .github/skills/rca/SKILL.md | 2 +- .github/skills/refactor/SKILL.md | 2 +- .github/skills/release-notes/SKILL.md | 2 +- .github/skills/requirements/SKILL.md | 2 +- .github/skills/secret-scan/SKILL.md | 2 +- .github/skills/security/SKILL.md | 2 +- .github/skills/terraform/SKILL.md | 2 +- .github/skills/terragrunt/SKILL.md | 2 +- .github/skills/threat-model/SKILL.md | 2 +- .github/skills/verify/SKILL.md | 2 +- .github/skills/vision/SKILL.md | 2 +- .vstack/.gitignore | 1 + .vstack/vstack.json | 167 +++++++++--------- 77 files changed, 269 insertions(+), 196 deletions(-) create mode 100644 .github/hooks/post-edit-markdown-quality.json diff --git a/.github/agents/architect.agent.md b/.github/agents/architect.agent.md index 0365edd..a7de8fd 100644 --- a/.github/agents/architect.agent.md +++ b/.github/agents/architect.agent.md @@ -180,4 +180,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gdpr` — privacy by design and data processing architecture review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architect","artifact_type":"agent","artifact_version":"20260503022","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"architect","artifact_type":"agent","artifact_version":"20260503022","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/designer.agent.md b/.github/agents/designer.agent.md index f4c4041..d77a994 100644 --- a/.github/agents/designer.agent.md +++ b/.github/agents/designer.agent.md @@ -193,4 +193,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#openapi` — OpenAPI 3.1 spec writing and review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"designer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"designer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/engineer.agent.md b/.github/agents/engineer.agent.md index ebf2eee..b15ee02 100644 --- a/.github/agents/engineer.agent.md +++ b/.github/agents/engineer.agent.md @@ -202,4 +202,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#rancher` — Rancher and Fleet multi-cluster operations and governance <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"engineer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"engineer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/planner.agent.md b/.github/agents/planner.agent.md index 2482759..e3ee998 100644 --- a/.github/agents/planner.agent.md +++ b/.github/agents/planner.agent.md @@ -135,4 +135,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#analyse` - assess stage impact, skip rationale, and trade-offs <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"planner","artifact_type":"agent","artifact_version":"20260510001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"planner","artifact_type":"agent","artifact_version":"20260510001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/product.agent.md b/.github/agents/product.agent.md index 2aeff6a..9a713da 100644 --- a/.github/agents/product.agent.md +++ b/.github/agents/product.agent.md @@ -164,4 +164,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gh-issues` — create and manage GitHub Issues for requirements, tasks, and user stories <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"product","artifact_type":"agent","artifact_version":"20260503021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"product","artifact_type":"agent","artifact_version":"20260503021","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/release.agent.md b/.github/agents/release.agent.md index bf600b5..836403d 100644 --- a/.github/agents/release.agent.md +++ b/.github/agents/release.agent.md @@ -154,4 +154,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gh-issues` — create and manage GitHub Issues for tracking work and bug reports <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release","artifact_type":"agent","artifact_version":"20260503020","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"release","artifact_type":"agent","artifact_version":"20260503020","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/tester.agent.md b/.github/agents/tester.agent.md index b77fcda..aa3e888 100644 --- a/.github/agents/tester.agent.md +++ b/.github/agents/tester.agent.md @@ -180,4 +180,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#rancher` — Rancher/Fleet configuration and multi-cluster governance review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"tester","artifact_type":"agent","artifact_version":"20260503026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"tester","artifact_type":"agent","artifact_version":"20260503026","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/hooks/post-commit-security-scan.json b/.github/hooks/post-commit-security-scan.json index 70431e3..65abc89 100644 --- a/.github/hooks/post-commit-security-scan.json +++ b/.github/hooks/post-commit-security-scan.json @@ -1,14 +1,25 @@ { - "postToolUse": [ - { - "type": "command", - "command": "echo \"[vstack] security hint: run secret scan after git mutations\"" - } - ], - "sessionEnd": [ - { - "type": "command", - "command": "echo \"[vstack] security hint: run dependency and secret checks before push\"" - } - ] -} + "version": 1, + "hooks": { + "postToolUse": [ + { + "type": "command", + "description": "Check staged diffs for secrets and log alerts to audit trail.\n", + "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-post-commit.jsonl\nif ! printf '%s' \"$input\" | grep -Eiq 'git (commit|push|merge|rebase)'; then\n exit 0\nfi\nif git rev-parse --git-dir >/dev/null 2>&1 && \\\n git diff --cached 2>/dev/null | grep -Eiq 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----'; then\n printf '%s\\n' 'possible-secrets-detected-in-staged-diff' >> .vstack/logs/hook-security-alerts.log\nfi\nif [ \"${VSTACK_HOOKS_MODE:-audit}\" = \"enforce\" ]; then\n if command -v gitleaks >/dev/null 2>&1; then\n gitleaks dir . --no-banner --redact >/dev/null 2>&1 || printf '%s\\n' 'gitleaks-reported-findings' >> .vstack/logs/hook-security-alerts.log\n else\n printf '%s\\n' 'gitleaks-not-installed' >> .vstack/logs/hook-security-alerts.log\n fi\nfi\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\nNew-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\nAdd-Content -Path .vstack/logs/hook-post-commit.jsonl -Value $inputText\nif ($inputText -notmatch 'git (commit|push|merge|rebase)') {\n exit 0\n}\nif ((git rev-parse --git-dir 2>$null) -and (git diff --cached | Select-String -Pattern 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----' -Quiet)) {\n Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'possible-secrets-detected-in-staged-diff'\n}\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -eq 'enforce') {\n if (Get-Command gitleaks -ErrorAction SilentlyContinue) {\n gitleaks dir . --no-banner --redact *> $null\n if ($LASTEXITCODE -ne 0) {\n Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'gitleaks-reported-findings'\n }\n } else {\n Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'gitleaks-not-installed'\n }\n}\n", + "cwd": ".", + "timeoutSec": 60 + } + ], + "sessionEnd": [ + { + "type": "command", + "description": "Log session end for security scanning completeness.\n", + "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-session-end-security.jsonl\n", + "powershell": "New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\n$inputText = [Console]::In.ReadToEnd()\nAdd-Content -Path .vstack/logs/hook-session-end-security.jsonl -Value $inputText\n", + "cwd": ".", + "timeoutSec": 10 + } + ] + } +} \ No newline at end of file diff --git a/.github/hooks/post-edit-format.json b/.github/hooks/post-edit-format.json index ab6d20e..6dd4bbb 100644 --- a/.github/hooks/post-edit-format.json +++ b/.github/hooks/post-edit-format.json @@ -1,8 +1,15 @@ { - "postToolUse": [ - { - "type": "command", - "command": "echo \"[vstack] format hint: run make format after file edits\"" - } - ] -} + "version": 1, + "hooks": { + "postToolUse": [ + { + "type": "command", + "description": "Log edit events and optionally run format in enforce mode.\n", + "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-post-edit.jsonl\nmode=\"${VSTACK_HOOKS_MODE:-audit}\"\nif [ \"$mode\" != \"enforce\" ]; then\n exit 0\nfi\nif printf '%s' \"$input\" | grep -Eq '\"toolName\"[[:space:]]*:[[:space:]]*\"(edit|create|write|replace)\"'; then\n if [ -f Makefile ] && command -v make >/dev/null 2>&1; then\n make format >/dev/null 2>&1 || printf '%s\\n' 'format-run-failed' >> .vstack/logs/hook-quality-alerts.log\n else\n printf '%s\\n' 'format-skipped-missing-make-or-makefile' >> .vstack/logs/hook-quality-alerts.log\n fi\nfi\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\nNew-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\nAdd-Content -Path .vstack/logs/hook-post-edit.jsonl -Value $inputText\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -ne 'enforce') {\n exit 0\n}\ntry {\n $payload = $inputText | ConvertFrom-Json -ErrorAction Stop\n} catch {\n exit 0\n}\nif ($payload.toolName -match '^(edit|create|write|replace)$') {\n if ((Test-Path Makefile) -and (Get-Command make -ErrorAction SilentlyContinue)) {\n make format *> $null\n if ($LASTEXITCODE -ne 0) {\n Add-Content -Path .vstack/logs/hook-quality-alerts.log -Value 'format-run-failed'\n }\n } else {\n Add-Content -Path .vstack/logs/hook-quality-alerts.log -Value 'format-skipped-missing-make-or-makefile'\n }\n}\n", + "cwd": ".", + "timeoutSec": 30 + } + ] + } +} \ No newline at end of file diff --git a/.github/hooks/post-edit-markdown-quality.json b/.github/hooks/post-edit-markdown-quality.json new file mode 100644 index 0000000..75a1e2b --- /dev/null +++ b/.github/hooks/post-edit-markdown-quality.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "hooks": { + "postToolUse": [ + { + "type": "command", + "description": "Log markdown-oriented edit events and optionally run markdown formatting.\n", + "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-markdown-quality.jsonl\nmode=\"${VSTACK_HOOKS_MODE:-audit}\"\nif [ \"$mode\" != \"enforce\" ]; then\n exit 0\nfi\nif ! printf '%s' \"$input\" | grep -Eiq '\\.md|docs/|\\.github/|\\.vstack/templates/|adr/|prompt\\.md|instructions\\.md|SKILL\\.md'; then\n exit 0\nfi\nif [ -f Makefile ] && command -v make >/dev/null 2>&1; then\n make markdown-format >/dev/null 2>&1 || \\\n make format >/dev/null 2>&1 || \\\n printf '%s\\n' 'markdown-format-run-failed' >> .vstack/logs/hook-markdown-quality-alerts.log\nelse\n printf '%s\\n' 'markdown-format-skipped-missing-make-or-makefile' >> .vstack/logs/hook-markdown-quality-alerts.log\nfi\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\nNew-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\nAdd-Content -Path .vstack/logs/hook-markdown-quality.jsonl -Value $inputText\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -ne 'enforce') {\n exit 0\n}\nif ($inputText -notmatch '\\.md|docs/|\\.github/|\\.vstack/templates/|adr/|prompt\\.md|instructions\\.md|SKILL\\.md') {\n exit 0\n}\nif ((Test-Path Makefile) -and (Get-Command make -ErrorAction SilentlyContinue)) {\n make markdown-format *> $null\n if ($LASTEXITCODE -ne 0) {\n make format *> $null\n }\n if ($LASTEXITCODE -ne 0) {\n Add-Content -Path .vstack/logs/hook-markdown-quality-alerts.log -Value 'markdown-format-run-failed'\n }\n} else {\n Add-Content -Path .vstack/logs/hook-markdown-quality-alerts.log -Value 'markdown-format-skipped-missing-make-or-makefile'\n}\n", + "cwd": ".", + "timeoutSec": 30 + } + ] + } +} \ No newline at end of file diff --git a/.github/hooks/pre-tool-safety-gate.json b/.github/hooks/pre-tool-safety-gate.json index 9c71c5c..83d0e29 100644 --- a/.github/hooks/pre-tool-safety-gate.json +++ b/.github/hooks/pre-tool-safety-gate.json @@ -1,14 +1,25 @@ { - "preToolUse": [ - { - "type": "command", - "command": "echo \"[vstack] safety gate: review destructive operations before execution\"" - } - ], - "errorOccurred": [ - { - "type": "command", - "command": "echo \"[vstack] safety gate: execution error captured\"" - } - ] -} + "version": 1, + "hooks": { + "preToolUse": [ + { + "type": "command", + "description": "Check for destructive bash patterns and block in enforce mode.\nAlways logs to audit trail.\n", + "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-pre-tool.jsonl\nmode=\"${VSTACK_HOOKS_MODE:-audit}\"\nif [ \"$mode\" != \"enforce\" ]; then\n exit 0\nfi\nif printf '%s' \"$input\" | grep -Eq '\"toolName\"[[:space:]]*:[[:space:]]*\"bash\"' && \\\n printf '%s' \"$input\" | grep -Eiq 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx'; then\n echo '{\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Blocked potentially destructive shell command by vstack pre-tool safety policy\"}'\nfi\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\nNew-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\nAdd-Content -Path .vstack/logs/hook-pre-tool.jsonl -Value $inputText\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -ne 'enforce') {\n exit 0\n}\ntry {\n $payload = $inputText | ConvertFrom-Json -ErrorAction Stop\n} catch {\n exit 0\n}\nif ($payload.toolName -eq 'bash' -and $inputText -match 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx') {\n @{ permissionDecision = 'deny'; permissionDecisionReason = 'Blocked potentially destructive shell command by vstack pre-tool safety policy' } | ConvertTo-Json -Compress\n}\n", + "cwd": ".", + "timeoutSec": 15 + } + ], + "errorOccurred": [ + { + "type": "command", + "description": "Log tool errors for incident investigation and debugging.\n", + "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-errors.jsonl\n", + "powershell": "New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\n$inputText = [Console]::In.ReadToEnd()\nAdd-Content -Path .vstack/logs/hook-errors.jsonl -Value $inputText\n", + "cwd": ".", + "timeoutSec": 10 + } + ] + } +} \ No newline at end of file diff --git a/.github/hooks/session-audit.json b/.github/hooks/session-audit.json index 828c84d..787a21b 100644 --- a/.github/hooks/session-audit.json +++ b/.github/hooks/session-audit.json @@ -1,14 +1,35 @@ { - "sessionStart": [ - { - "type": "command", - "command": "echo \"[vstack] session started\"" - } - ], - "sessionEnd": [ - { - "type": "command", - "command": "echo \"[vstack] session finished\"" - } - ] -} + "version": 1, + "hooks": { + "userPromptSubmitted": [ + { + "type": "command", + "description": "Log prompt submission events for audit correlation.\n", + "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-user-prompt.jsonl\n", + "powershell": "New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\n$inputText = [Console]::In.ReadToEnd()\nAdd-Content -Path .vstack/logs/hook-user-prompt.jsonl -Value $inputText\n", + "cwd": ".", + "timeoutSec": 10 + } + ], + "sessionStart": [ + { + "type": "command", + "description": "Log session start event with timestamp and context.\n", + "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-session-start.jsonl\n", + "powershell": "New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\n$inputText = [Console]::In.ReadToEnd()\nAdd-Content -Path .vstack/logs/hook-session-start.jsonl -Value $inputText\n", + "cwd": ".", + "timeoutSec": 10 + } + ], + "sessionEnd": [ + { + "type": "command", + "description": "Log session end event for correlation with start.\n", + "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-session-end.jsonl\n", + "powershell": "New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\n$inputText = [Console]::In.ReadToEnd()\nAdd-Content -Path .vstack/logs/hook-session-end.jsonl -Value $inputText\n", + "cwd": ".", + "timeoutSec": 10 + } + ] + } +} \ No newline at end of file diff --git a/.github/instructions/git.instructions.md b/.github/instructions/git.instructions.md index ee0ecde..db6263a 100644 --- a/.github/instructions/git.instructions.md +++ b/.github/instructions/git.instructions.md @@ -41,4 +41,4 @@ Use these Git and release hygiene conventions in this project. 1. Prefer local verification before pushing release-impacting changes. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"git","artifact_type":"instruction","artifact_version":"20260421001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"git","artifact_type":"instruction","artifact_version":"20260421001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/helm.instructions.md b/.github/instructions/helm.instructions.md index 1b37194..f570c7d 100644 --- a/.github/instructions/helm.instructions.md +++ b/.github/instructions/helm.instructions.md @@ -45,4 +45,4 @@ Use these Helm conventions in this project. - [Helm chart best practices](https://helm.sh/docs/chart_best_practices/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"instruction","artifact_version":"20260502040","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"instruction","artifact_version":"20260502040","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/java.instructions.md b/.github/instructions/java.instructions.md index 98a498a..118782c 100644 --- a/.github/instructions/java.instructions.md +++ b/.github/instructions/java.instructions.md @@ -56,4 +56,4 @@ Use these Java conventions in this project. 1. Do not suppress static analysis warnings without a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"java","artifact_type":"instruction","artifact_version":"20260502001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"java","artifact_type":"instruction","artifact_version":"20260502001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/k8s.instructions.md b/.github/instructions/k8s.instructions.md index 5bcc17e..e69825b 100644 --- a/.github/instructions/k8s.instructions.md +++ b/.github/instructions/k8s.instructions.md @@ -51,4 +51,4 @@ Use these Kubernetes conventions in this project. - [Kubernetes API reference](https://kubernetes.io/docs/reference/kubernetes-api/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"instruction","artifact_version":"20260502039","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"instruction","artifact_version":"20260502039","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/markdown.instructions.md b/.github/instructions/markdown.instructions.md index 64b29d5..7cf32ad 100644 --- a/.github/instructions/markdown.instructions.md +++ b/.github/instructions/markdown.instructions.md @@ -51,4 +51,4 @@ Use these Markdown conventions in this project. 1. Keep examples accurate and runnable — a broken example is worse than no example. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"markdown","artifact_type":"instruction","artifact_version":"20260502002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"markdown","artifact_type":"instruction","artifact_version":"20260502002","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/python.instructions.md b/.github/instructions/python.instructions.md index a9a809c..48db4d4 100644 --- a/.github/instructions/python.instructions.md +++ b/.github/instructions/python.instructions.md @@ -42,4 +42,4 @@ Use these Python conventions in this project. 1. Do not silence lint/type errors unless there is a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"python","artifact_type":"instruction","artifact_version":"20260421002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"python","artifact_type":"instruction","artifact_version":"20260421002","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/rancher.instructions.md b/.github/instructions/rancher.instructions.md index 3f52dd8..0457e71 100644 --- a/.github/instructions/rancher.instructions.md +++ b/.github/instructions/rancher.instructions.md @@ -44,4 +44,4 @@ Use these Rancher conventions in this project. - [Fleet docs](https://fleet.rancher.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"instruction","artifact_version":"20260502041","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"instruction","artifact_version":"20260502041","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/security.instructions.md b/.github/instructions/security.instructions.md index dd02e2c..8d5f82c 100644 --- a/.github/instructions/security.instructions.md +++ b/.github/instructions/security.instructions.md @@ -42,4 +42,4 @@ Apply these security policies in this project. 1. Isolate privileged logic; keep it minimal, auditable, and separate from business logic. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"instruction","artifact_version":"20260502003","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"instruction","artifact_version":"20260502003","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/terraform.instructions.md b/.github/instructions/terraform.instructions.md index da8fe19..fb5e44f 100644 --- a/.github/instructions/terraform.instructions.md +++ b/.github/instructions/terraform.instructions.md @@ -60,4 +60,4 @@ Use these Terraform conventions in this project. - [tfsec](https://aquasecurity.github.io/tfsec/) · [checkov](https://www.checkov.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"instruction","artifact_version":"20260502034","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"instruction","artifact_version":"20260502034","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/terragrunt.instructions.md b/.github/instructions/terragrunt.instructions.md index 9770cb6..d184fc6 100644 --- a/.github/instructions/terragrunt.instructions.md +++ b/.github/instructions/terragrunt.instructions.md @@ -57,4 +57,4 @@ Use these Terragrunt conventions in this project. - [Terragrunt CLI reference](https://terragrunt.gruntwork.io/docs/reference/cli-options/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"instruction","artifact_version":"20260502035","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"instruction","artifact_version":"20260502035","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index e673737..c1ffbe2 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -43,4 +43,4 @@ Use these testing conventions in this project. 1. Treat flaky tests as bugs; do not merge code with known test reliability issues. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"testing","artifact_type":"instruction","artifact_version":"20260502004","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"testing","artifact_type":"instruction","artifact_version":"20260502004","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/typescript.instructions.md b/.github/instructions/typescript.instructions.md index adb935d..f1692d2 100644 --- a/.github/instructions/typescript.instructions.md +++ b/.github/instructions/typescript.instructions.md @@ -49,4 +49,4 @@ Use these TypeScript conventions in this project. 1. Do not suppress lint or type errors with inline disable comments unless there is a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"typescript","artifact_type":"instruction","artifact_version":"20260502005","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"typescript","artifact_type":"instruction","artifact_version":"20260502005","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/api-design-review.prompt.md b/.github/prompts/api-design-review.prompt.md index 70e505c..2222575 100644 --- a/.github/prompts/api-design-review.prompt.md +++ b/.github/prompts/api-design-review.prompt.md @@ -55,4 +55,4 @@ List fields or objects that are missing required constraints, descriptions, or e - top priority fix in one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"api-design-review","artifact_type":"prompt","artifact_version":"20260502006","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"api-design-review","artifact_type":"prompt","artifact_version":"20260502006","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/architecture-risk.prompt.md b/.github/prompts/architecture-risk.prompt.md index 9d9f919..838adbb 100644 --- a/.github/prompts/architecture-risk.prompt.md +++ b/.github/prompts/architecture-risk.prompt.md @@ -52,4 +52,4 @@ List security-specific risks not covered above: auth boundaries, sensitive data - one-sentence rationale <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architecture-risk","artifact_type":"prompt","artifact_version":"20260502007","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"architecture-risk","artifact_type":"prompt","artifact_version":"20260502007","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/code-review.prompt.md b/.github/prompts/code-review.prompt.md index 4f6287e..4c6ceec 100644 --- a/.github/prompts/code-review.prompt.md +++ b/.github/prompts/code-review.prompt.md @@ -51,4 +51,4 @@ End with: - Biggest remaining risk: one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"prompt","artifact_version":"20260502008","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"prompt","artifact_version":"20260502008","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/dependency-audit.prompt.md b/.github/prompts/dependency-audit.prompt.md index d8744b5..33f9fc3 100644 --- a/.github/prompts/dependency-audit.prompt.md +++ b/.github/prompts/dependency-audit.prompt.md @@ -60,4 +60,4 @@ List packages with unusual provenance concerns: abandoned maintainers, single-ma Ordered list of actions by priority (critical first). <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependency-audit","artifact_type":"prompt","artifact_version":"20260502009","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"dependency-audit","artifact_type":"prompt","artifact_version":"20260502009","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/incident-timeline.prompt.md b/.github/prompts/incident-timeline.prompt.md index ebe3980..d92f4ad 100644 --- a/.github/prompts/incident-timeline.prompt.md +++ b/.github/prompts/incident-timeline.prompt.md @@ -57,4 +57,4 @@ For each action: List the minimum controls needed to reduce repeat probability. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"incident-timeline","artifact_type":"prompt","artifact_version":"20260502010","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"incident-timeline","artifact_type":"prompt","artifact_version":"20260502010","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/migration-safety.prompt.md b/.github/prompts/migration-safety.prompt.md index e602ce8..514d14e 100644 --- a/.github/prompts/migration-safety.prompt.md +++ b/.github/prompts/migration-safety.prompt.md @@ -52,4 +52,4 @@ List missing migration tests (forward, backward, data invariants, load-sensitive - biggest remaining risk in one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"migration-safety","artifact_type":"prompt","artifact_version":"20260502011","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"migration-safety","artifact_type":"prompt","artifact_version":"20260502011","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/release-readiness.prompt.md b/.github/prompts/release-readiness.prompt.md index 8552f4f..f99b15a 100644 --- a/.github/prompts/release-readiness.prompt.md +++ b/.github/prompts/release-readiness.prompt.md @@ -46,4 +46,4 @@ For each expected artifact that is missing, flag it explicitly as: MISSING — [ One clear next step for the team. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release-readiness","artifact_type":"prompt","artifact_version":"20260502012","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"release-readiness","artifact_type":"prompt","artifact_version":"20260502012","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/adr/SKILL.md b/.github/skills/adr/SKILL.md index 7bd56f5..7c36e1b 100644 --- a/.github/skills/adr/SKILL.md +++ b/.github/skills/adr/SKILL.md @@ -180,4 +180,4 @@ is a kebab-case title. After writing, state the file path and summary so the architect or product role can review. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"adr","artifact_type":"skill","artifact_version":"20260421003","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"adr","artifact_type":"skill","artifact_version":"20260421003","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/analyse/SKILL.md b/.github/skills/analyse/SKILL.md index 3856249..69762c9 100644 --- a/.github/skills/analyse/SKILL.md +++ b/.github/skills/analyse/SKILL.md @@ -214,4 +214,4 @@ State conclusions with confidence level: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"analyse","artifact_type":"skill","artifact_version":"20260421004","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"analyse","artifact_type":"skill","artifact_version":"20260421004","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/architecture/SKILL.md b/.github/skills/architecture/SKILL.md index e55a8d3..58a1ead 100644 --- a/.github/skills/architecture/SKILL.md +++ b/.github/skills/architecture/SKILL.md @@ -278,4 +278,4 @@ For each significant structural decision made during this review (technology cho - Update `docs/architecture/overview.md` to reflect the final decisions. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architecture","artifact_type":"skill","artifact_version":"20260421005","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"architecture","artifact_type":"skill","artifact_version":"20260421005","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/aws-cli/SKILL.md b/.github/skills/aws-cli/SKILL.md index c28e4af..c25b92d 100644 --- a/.github/skills/aws-cli/SKILL.md +++ b/.github/skills/aws-cli/SKILL.md @@ -374,4 +374,4 @@ aws ce get-cost-and-usage \ - [AWS CLI named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"aws-cli","artifact_type":"skill","artifact_version":"20260502033","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"aws-cli","artifact_type":"skill","artifact_version":"20260502033","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/cicd/SKILL.md b/.github/skills/cicd/SKILL.md index 421c19a..81b957a 100644 --- a/.github/skills/cicd/SKILL.md +++ b/.github/skills/cicd/SKILL.md @@ -221,4 +221,4 @@ Configure these in GitHub → Settings → Branches. - [GitHub-hosted runners](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"cicd","artifact_type":"skill","artifact_version":"20260421006","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"cicd","artifact_type":"skill","artifact_version":"20260421006","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/cloudformation/SKILL.md b/.github/skills/cloudformation/SKILL.md index 2ad5f01..0b735c7 100644 --- a/.github/skills/cloudformation/SKILL.md +++ b/.github/skills/cloudformation/SKILL.md @@ -344,4 +344,4 @@ AppSecurityGroup: - [AWS SAM documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"cloudformation","artifact_type":"skill","artifact_version":"20260502032","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"cloudformation","artifact_type":"skill","artifact_version":"20260502032","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/code-review/SKILL.md b/.github/skills/code-review/SKILL.md index 4d3a47b..60c3a84 100644 --- a/.github/skills/code-review/SKILL.md +++ b/.github/skills/code-review/SKILL.md @@ -218,4 +218,4 @@ Confidence: [HIGH/MEDIUM/LOW — explain if not HIGH] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"skill","artifact_version":"20260421007","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"skill","artifact_version":"20260421007","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/codeql/SKILL.md b/.github/skills/codeql/SKILL.md index 855da54..ffd1863 100644 --- a/.github/skills/codeql/SKILL.md +++ b/.github/skills/codeql/SKILL.md @@ -251,4 +251,4 @@ GITHUB_TOKEN=<token> codeql github upload-results \ - [Supported languages and frameworks](https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/codeql-code-scanning-for-compiled-languages) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"codeql","artifact_type":"skill","artifact_version":"20260502026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"codeql","artifact_type":"skill","artifact_version":"20260502026","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/concise/SKILL.md b/.github/skills/concise/SKILL.md index dac193d..188e8b0 100644 --- a/.github/skills/concise/SKILL.md +++ b/.github/skills/concise/SKILL.md @@ -147,4 +147,4 @@ Current mode unchanged: <mode> - [ ] User confirmation/status returned in deterministic format <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"concise","artifact_type":"skill","artifact_version":"20260421008","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"concise","artifact_type":"skill","artifact_version":"20260421008","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/consult/SKILL.md b/.github/skills/consult/SKILL.md index 2571f4e..8a3a2a3 100644 --- a/.github/skills/consult/SKILL.md +++ b/.github/skills/consult/SKILL.md @@ -218,4 +218,4 @@ reason: [one sentence] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"consult","artifact_type":"skill","artifact_version":"20260421009","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"consult","artifact_type":"skill","artifact_version":"20260421009","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/container/SKILL.md b/.github/skills/container/SKILL.md index 2d9c924..ea38b36 100644 --- a/.github/skills/container/SKILL.md +++ b/.github/skills/container/SKILL.md @@ -153,4 +153,4 @@ For production-like local testing, write a separate `docker-compose.prod.yml` wi - [Docker official images](https://hub.docker.com/search?image_filter=official) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"container","artifact_type":"skill","artifact_version":"20260421010","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"container","artifact_type":"skill","artifact_version":"20260421010","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/conventional-commit/SKILL.md b/.github/skills/conventional-commit/SKILL.md index 77a4b00..d5dde6b 100644 --- a/.github/skills/conventional-commit/SKILL.md +++ b/.github/skills/conventional-commit/SKILL.md @@ -155,4 +155,4 @@ Remaining changes: If commit is blocked, report exact reason and proposed fix. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"conventional-commit","artifact_type":"skill","artifact_version":"20260502024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"conventional-commit","artifact_type":"skill","artifact_version":"20260502024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/debug/SKILL.md b/.github/skills/debug/SKILL.md index 76ec3b7..1f2d656 100644 --- a/.github/skills/debug/SKILL.md +++ b/.github/skills/debug/SKILL.md @@ -258,4 +258,4 @@ Prevention: [any follow-up items] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"debug","artifact_type":"skill","artifact_version":"20260421011","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"debug","artifact_type":"skill","artifact_version":"20260421011","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/dependabot/SKILL.md b/.github/skills/dependabot/SKILL.md index ad9aeff..a269f4d 100644 --- a/.github/skills/dependabot/SKILL.md +++ b/.github/skills/dependabot/SKILL.md @@ -320,4 +320,4 @@ updates: - [Dependabot security updates](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependabot","artifact_type":"skill","artifact_version":"20260502027","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"dependabot","artifact_type":"skill","artifact_version":"20260502027","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/dependency/SKILL.md b/.github/skills/dependency/SKILL.md index 3313f88..26681ca 100644 --- a/.github/skills/dependency/SKILL.md +++ b/.github/skills/dependency/SKILL.md @@ -318,4 +318,4 @@ Action items (priority order): - [PyPI / npm / crates.io / Maven Central](https://pypi.org) (replace with the relevant registry) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependency","artifact_type":"skill","artifact_version":"20260421012","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"dependency","artifact_type":"skill","artifact_version":"20260421012","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/design/SKILL.md b/.github/skills/design/SKILL.md index e732ac8..c62109d 100644 --- a/.github/skills/design/SKILL.md +++ b/.github/skills/design/SKILL.md @@ -243,4 +243,4 @@ Output a complete design document to `docs/design/overview.md` or `openapi.yaml` ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"design","artifact_type":"skill","artifact_version":"20260421013","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"design","artifact_type":"skill","artifact_version":"20260421013","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/docs/SKILL.md b/.github/skills/docs/SKILL.md index be97300..fa75166 100644 --- a/.github/skills/docs/SKILL.md +++ b/.github/skills/docs/SKILL.md @@ -151,4 +151,4 @@ Skipped (n/a): ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"docs","artifact_type":"skill","artifact_version":"20260421014","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"docs","artifact_type":"skill","artifact_version":"20260421014","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/explore/SKILL.md b/.github/skills/explore/SKILL.md index aa6af6a..b46d037 100644 --- a/.github/skills/explore/SKILL.md +++ b/.github/skills/explore/SKILL.md @@ -224,4 +224,4 @@ Stack: [language, framework, runtime versions] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"explore","artifact_type":"skill","artifact_version":"20260421015","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"explore","artifact_type":"skill","artifact_version":"20260421015","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/gdpr/SKILL.md b/.github/skills/gdpr/SKILL.md index b6ff4fd..d1ae2c9 100644 --- a/.github/skills/gdpr/SKILL.md +++ b/.github/skills/gdpr/SKILL.md @@ -246,4 +246,4 @@ Use `@example.com` for all test email addresses. - [EDPB guidelines](https://www.edpb.europa.eu/our-work-tools/general-guidance/guidelines-recommendations-best-practices_en) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gdpr","artifact_type":"skill","artifact_version":"20260502029","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"gdpr","artifact_type":"skill","artifact_version":"20260502029","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/gh-issues/SKILL.md b/.github/skills/gh-issues/SKILL.md index 3b5a19a..692ec90 100644 --- a/.github/skills/gh-issues/SKILL.md +++ b/.github/skills/gh-issues/SKILL.md @@ -230,4 +230,4 @@ https://github.com/<org>/<repo>/issues/<number> - [GitHub Issues documentation](https://docs.github.com/en/issues) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gh-issues","artifact_type":"skill","artifact_version":"20260502025","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"gh-issues","artifact_type":"skill","artifact_version":"20260502025","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/gh-release/SKILL.md b/.github/skills/gh-release/SKILL.md index e3b1c68..b8e7465 100644 --- a/.github/skills/gh-release/SKILL.md +++ b/.github/skills/gh-release/SKILL.md @@ -214,4 +214,4 @@ If blocked, report exact blocker and required user action. - [GitHub Releases documentation](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gh-release","artifact_type":"skill","artifact_version":"20260502023","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"gh-release","artifact_type":"skill","artifact_version":"20260502023","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/guardrails/SKILL.md b/.github/skills/guardrails/SKILL.md index 820fe2e..b5f1702 100644 --- a/.github/skills/guardrails/SKILL.md +++ b/.github/skills/guardrails/SKILL.md @@ -72,4 +72,4 @@ Activate careful mode for this session. Two behaviors are now enabled. Explicitly ask to "disable guardrails". <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"guardrails","artifact_type":"skill","artifact_version":"20260421016","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"guardrails","artifact_type":"skill","artifact_version":"20260421016","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/helm/SKILL.md b/.github/skills/helm/SKILL.md index 1746edd..74ad155 100644 --- a/.github/skills/helm/SKILL.md +++ b/.github/skills/helm/SKILL.md @@ -140,4 +140,4 @@ Practices: - [Chart best practices](https://helm.sh/docs/chart_best_practices/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"skill","artifact_version":"20260502037","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"skill","artifact_version":"20260502037","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/incident/SKILL.md b/.github/skills/incident/SKILL.md index 784e4ac..4d88c90 100644 --- a/.github/skills/incident/SKILL.md +++ b/.github/skills/incident/SKILL.md @@ -251,4 +251,4 @@ Next: invoke @#rca and @#postmortem to produce written artifacts. ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"incident","artifact_type":"skill","artifact_version":"20260503002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"incident","artifact_type":"skill","artifact_version":"20260503002","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/inspect/SKILL.md b/.github/skills/inspect/SKILL.md index 9a2346a..944d397 100644 --- a/.github/skills/inspect/SKILL.md +++ b/.github/skills/inspect/SKILL.md @@ -155,4 +155,4 @@ Confirm for changed paths: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"inspect","artifact_type":"skill","artifact_version":"20260421018","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"inspect","artifact_type":"skill","artifact_version":"20260421018","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/k8s/SKILL.md b/.github/skills/k8s/SKILL.md index 42ca2a6..780b5af 100644 --- a/.github/skills/k8s/SKILL.md +++ b/.github/skills/k8s/SKILL.md @@ -144,4 +144,4 @@ Common failure classes: - [Kubernetes API reference](https://kubernetes.io/docs/reference/kubernetes-api/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"skill","artifact_version":"20260502036","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"skill","artifact_version":"20260502036","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/migrate/SKILL.md b/.github/skills/migrate/SKILL.md index 07e1934..93474c9 100644 --- a/.github/skills/migrate/SKILL.md +++ b/.github/skills/migrate/SKILL.md @@ -320,4 +320,4 @@ Pre-deploy checklist: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"migrate","artifact_type":"skill","artifact_version":"20260421019","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"migrate","artifact_type":"skill","artifact_version":"20260421019","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/onboard/SKILL.md b/.github/skills/onboard/SKILL.md index da290f0..63dd1cd 100644 --- a/.github/skills/onboard/SKILL.md +++ b/.github/skills/onboard/SKILL.md @@ -302,4 +302,4 @@ Gaps remaining (if any): ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"onboard","artifact_type":"skill","artifact_version":"20260421020","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"onboard","artifact_type":"skill","artifact_version":"20260421020","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/openapi/SKILL.md b/.github/skills/openapi/SKILL.md index 77a8b91..5ab1574 100644 --- a/.github/skills/openapi/SKILL.md +++ b/.github/skills/openapi/SKILL.md @@ -403,4 +403,4 @@ Summary: [N critical, N warnings, N info] - [Redocly CLI (linting)](https://redocly.com/docs/cli/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"openapi","artifact_type":"skill","artifact_version":"20260421021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"openapi","artifact_type":"skill","artifact_version":"20260421021","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/performance/SKILL.md b/.github/skills/performance/SKILL.md index 4bd99dc..0211b1a 100644 --- a/.github/skills/performance/SKILL.md +++ b/.github/skills/performance/SKILL.md @@ -242,4 +242,4 @@ For each bottleneck identified: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"performance","artifact_type":"skill","artifact_version":"20260421022","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"performance","artifact_type":"skill","artifact_version":"20260421022","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/postmortem/SKILL.md b/.github/skills/postmortem/SKILL.md index 12508ec..2d90f74 100644 --- a/.github/skills/postmortem/SKILL.md +++ b/.github/skills/postmortem/SKILL.md @@ -184,4 +184,4 @@ Status: Draft — ready for team review ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"postmortem","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"postmortem","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/pr/SKILL.md b/.github/skills/pr/SKILL.md index bd8368a..24a1b8c 100644 --- a/.github/skills/pr/SKILL.md +++ b/.github/skills/pr/SKILL.md @@ -151,4 +151,4 @@ Next steps depend on the repository CI/CD configuration: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"pr","artifact_type":"skill","artifact_version":"20260502013","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"pr","artifact_type":"skill","artifact_version":"20260502013","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/rancher/SKILL.md b/.github/skills/rancher/SKILL.md index 3d58218..ed7f64a 100644 --- a/.github/skills/rancher/SKILL.md +++ b/.github/skills/rancher/SKILL.md @@ -113,4 +113,4 @@ Checks: - [Fleet documentation](https://fleet.rancher.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"skill","artifact_version":"20260502038","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"skill","artifact_version":"20260502038","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/rca/SKILL.md b/.github/skills/rca/SKILL.md index 0a302a5..8f91c15 100644 --- a/.github/skills/rca/SKILL.md +++ b/.github/skills/rca/SKILL.md @@ -207,4 +207,4 @@ Status: Draft — ready for review ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rca","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"rca","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/refactor/SKILL.md b/.github/skills/refactor/SKILL.md index 1110f6b..da59d2a 100644 --- a/.github/skills/refactor/SKILL.md +++ b/.github/skills/refactor/SKILL.md @@ -372,4 +372,4 @@ Behavior changed: No ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"refactor","artifact_type":"skill","artifact_version":"20260421023","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"refactor","artifact_type":"skill","artifact_version":"20260421023","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index 39d4c98..079defd 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -147,4 +147,4 @@ Prepend a new entry at the top of `CHANGELOG.md`: Keep existing entries intact. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release-notes","artifact_type":"skill","artifact_version":"20260502014","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"release-notes","artifact_type":"skill","artifact_version":"20260502014","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/requirements/SKILL.md b/.github/skills/requirements/SKILL.md index 5d1e7d7..a9f7856 100644 --- a/.github/skills/requirements/SKILL.md +++ b/.github/skills/requirements/SKILL.md @@ -199,4 +199,4 @@ Write all findings to `docs/product/requirements.md`: After writing, summarize what was decided so the architect role can start. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"requirements","artifact_type":"skill","artifact_version":"20260421024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"requirements","artifact_type":"skill","artifact_version":"20260421024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/secret-scan/SKILL.md b/.github/skills/secret-scan/SKILL.md index 0c0674b..00dc2f9 100644 --- a/.github/skills/secret-scan/SKILL.md +++ b/.github/skills/secret-scan/SKILL.md @@ -240,4 +240,4 @@ credential formats. - [Supported secret patterns](https://docs.github.com/en/code-security/secret-scanning/introduction/supported-secret-scanning-patterns) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"secret-scan","artifact_type":"skill","artifact_version":"20260502028","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"secret-scan","artifact_type":"skill","artifact_version":"20260502028","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/security/SKILL.md b/.github/skills/security/SKILL.md index eb35c48..f868915 100644 --- a/.github/skills/security/SKILL.md +++ b/.github/skills/security/SKILL.md @@ -295,4 +295,4 @@ Scope: [full/diff/dependency/config] - [STRIDE threat modeling (Microsoft)](https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"skill","artifact_version":"20260421025","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"skill","artifact_version":"20260421025","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/terraform/SKILL.md b/.github/skills/terraform/SKILL.md index 3ba5145..0ec9dc4 100644 --- a/.github/skills/terraform/SKILL.md +++ b/.github/skills/terraform/SKILL.md @@ -335,4 +335,4 @@ Run `terraform plan` after every state operation to verify the outcome. - [tfsec rules](https://aquasecurity.github.io/tfsec/latest/checks/aws/) · [checkov checks](https://www.checkov.io/5.Policy%20Index/terraform.html) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"skill","artifact_version":"20260502030","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"skill","artifact_version":"20260502030","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/terragrunt/SKILL.md b/.github/skills/terragrunt/SKILL.md index 77326de..26929fe 100644 --- a/.github/skills/terragrunt/SKILL.md +++ b/.github/skills/terragrunt/SKILL.md @@ -307,4 +307,4 @@ Use `--terragrunt-non-interactive` in CI to prevent hanging on prompts. - [Gruntwork module registry](https://www.gruntwork.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"skill","artifact_version":"20260502031","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"skill","artifact_version":"20260502031","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/threat-model/SKILL.md b/.github/skills/threat-model/SKILL.md index d2a81e5..33ff969 100644 --- a/.github/skills/threat-model/SKILL.md +++ b/.github/skills/threat-model/SKILL.md @@ -245,4 +245,4 @@ For each high-priority threat include: - Final report is written to `docs/architecture/threat-model.md`. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"threat-model","artifact_type":"skill","artifact_version":"20260502021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"threat-model","artifact_type":"skill","artifact_version":"20260502021","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/verify/SKILL.md b/.github/skills/verify/SKILL.md index 585a7df..eb4dfcd 100644 --- a/.github/skills/verify/SKILL.md +++ b/.github/skills/verify/SKILL.md @@ -266,4 +266,4 @@ scope: [path/component/full] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"verify","artifact_type":"skill","artifact_version":"20260421026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"verify","artifact_type":"skill","artifact_version":"20260421026","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/vision/SKILL.md b/.github/skills/vision/SKILL.md index 16dbb1b..bd3370d 100644 --- a/.github/skills/vision/SKILL.md +++ b/.github/skills/vision/SKILL.md @@ -206,4 +206,4 @@ For each finding: explain the tradeoff, give an opinionated recommendation, ask Present as: "Overall assessment: [READY/NEEDS REVISION/SCOPE CHANGE] because [1-2 sentence reason]." <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"vision","artifact_type":"skill","artifact_version":"20260421027","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"vision","artifact_type":"skill","artifact_version":"20260421027","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.vstack/.gitignore b/.vstack/.gitignore index 9f3c0e4..4ad22bf 100644 --- a/.vstack/.gitignore +++ b/.vstack/.gitignore @@ -1,2 +1,3 @@ # Generated by vstack install — do not edit manually. tmp/ +logs/ diff --git a/.vstack/vstack.json b/.vstack/vstack.json index efe13cf..05d1fe7 100644 --- a/.vstack/vstack.json +++ b/.vstack/vstack.json @@ -1,316 +1,316 @@ { "manifest_version": 2, "hash_algorithm": "sha256", - "vstack_version": "0.0.0.post3.dev0+df3fe6e", - "installed_at": "2026-05-10T13:12:42.249087+00:00", + "vstack_version": "3.1.1.post2.dev0+4d3419b", + "installed_at": "2026-05-10T20:07:07.839973+00:00", "artifacts": { "skills": [ { "name": "adr", "file": "skills/adr/SKILL.md", "version": "20260421003", - "checksum": "871263fddee0b3ae8e2c2e2243aad4f2b3aa9e0dea0d2519c7d21c3e98430a83", + "checksum": "1def4de23e0bb887392fdc79f5a354416ed6851b4d9584411d2cb04a280f2259", "checksum_algorithm": "sha256" }, { "name": "analyse", "file": "skills/analyse/SKILL.md", "version": "20260421004", - "checksum": "79ced25692671a5286a26cbcd0e59ed4d641ac7493fd8e2b58b232f97e467224", + "checksum": "abfc4634c6a0123f95bf7a397e7c0334615c3c86da4cb02f18cd904fd33df917", "checksum_algorithm": "sha256" }, { "name": "architecture", "file": "skills/architecture/SKILL.md", "version": "20260421005", - "checksum": "e2d0342184829c888a70ece6f82ad6a7450ad5d8c958ff6e028d7f245f1e5960", + "checksum": "381c9ba6e33f2334f96227336c3cadab0ccdc78cf256c43a579fe31fde2689a9", "checksum_algorithm": "sha256" }, { "name": "aws-cli", "file": "skills/aws-cli/SKILL.md", "version": "20260502033", - "checksum": "4868a5adc17b6ac52839c85f3a09986991c2498501913ef48a8f85a6291e1b21", + "checksum": "484f61c7e9686f8f45e6e0e470bec05e638269ce760e9f029e19aa65a282647e", "checksum_algorithm": "sha256" }, { "name": "cicd", "file": "skills/cicd/SKILL.md", "version": "20260421006", - "checksum": "ba6c0cf0e644e24b1290c1b78969bad86dd7d5b39030edc7bd430d4d1832cfd7", + "checksum": "c58754d85c160836e96820770be3d7c2d3959091032c3e0eb65fb9898886a515", "checksum_algorithm": "sha256" }, { "name": "cloudformation", "file": "skills/cloudformation/SKILL.md", "version": "20260502032", - "checksum": "0e61163cfc356105a309bbecf89ee26a5b10b9b5db58c331d76efa4a508e760e", + "checksum": "a7576e06214a0e549d216818417677feae79d29b26485ddd7124c40e0349c51b", "checksum_algorithm": "sha256" }, { "name": "code-review", "file": "skills/code-review/SKILL.md", "version": "20260421007", - "checksum": "58b7caa9dd3a5da6bef8d8d4d1f7b6d513740d33ad16731a06b1b2a907d93a13", + "checksum": "48d6d8f5100875cd24540a8df8a3a8a1eccfcf353f8d649e9437d94ffa8374c0", "checksum_algorithm": "sha256" }, { "name": "codeql", "file": "skills/codeql/SKILL.md", "version": "20260502026", - "checksum": "95f29c975e5850307620c21a68df174b90ecaafd5b1e36659bb3c34d0027b5df", + "checksum": "87594771b5e46fd7a43d1a3d50528511358bc4094f336027ca5f00cdd0e13059", "checksum_algorithm": "sha256" }, { "name": "concise", "file": "skills/concise/SKILL.md", "version": "20260421008", - "checksum": "9555de7da6a925e76992b6f5e51ddccd6b4de3887ea31ed2ceb9113c9fe9bc7e", + "checksum": "2f92b948f975a5d418d0ec8204a55faf20a5f2576dee426d7f96e954501aa7ff", "checksum_algorithm": "sha256" }, { "name": "consult", "file": "skills/consult/SKILL.md", "version": "20260421009", - "checksum": "a340883900309261213ec0ea0d4e9e5b72ca83fb25c7cd6c3506e1f95f40cf76", + "checksum": "b760c5df32f6922dfc96d9ed7ed73715ac10b58d1570879476f9cf75c2a2bbd7", "checksum_algorithm": "sha256" }, { "name": "container", "file": "skills/container/SKILL.md", "version": "20260421010", - "checksum": "f0a4408a756195faca9a58dca76e56f950df227415d77af293bf55280191abd4", + "checksum": "9493acae27f345fe49553db6aa91b24a4575c4c31525989a79e3e70bcbfd9dff", "checksum_algorithm": "sha256" }, { "name": "conventional-commit", "file": "skills/conventional-commit/SKILL.md", "version": "20260502024", - "checksum": "8f1ce569106b5210a8d4ffe5bc9123eb0dec6b24ff56cf680922eccdd9891012", + "checksum": "401ea58fe12834d7786ce25eb142886020516eea2af8bf29c2a1cabcacb8e805", "checksum_algorithm": "sha256" }, { "name": "debug", "file": "skills/debug/SKILL.md", "version": "20260421011", - "checksum": "b4c1c6de7142b7b74326d1c19a5cc27f3957d1faa48aa48bd6b1fc45832edf3b", + "checksum": "3b62b8faf8c7c6316c915cff3bac03d363c0347b8ab6fbfe07cb23d0b266d58f", "checksum_algorithm": "sha256" }, { "name": "dependabot", "file": "skills/dependabot/SKILL.md", "version": "20260502027", - "checksum": "fa3f595740faa0b8093ffe4f32fb8e4b85f5834ea10817752ee9c94be8f38c2d", + "checksum": "509d421bae968bd6ee4364108a5cdb38bc4db3e931007db9b8f9faac218d1877", "checksum_algorithm": "sha256" }, { "name": "dependency", "file": "skills/dependency/SKILL.md", "version": "20260421012", - "checksum": "2cbbecca8e6697b8523301325a889a5ca88c520f6d35a3b98807b6c3449239f5", + "checksum": "b70c7b3005052ae09d95763a17d1116f1353c5bdfbcef92f200822724123fc71", "checksum_algorithm": "sha256" }, { "name": "design", "file": "skills/design/SKILL.md", "version": "20260421013", - "checksum": "2d56e34aca8d3cf5a3f327e3b69ab15707393ffc47607c91ec91dd5be86cd65b", + "checksum": "26d0130b03dd7710462763d015559d4df898682236a7619df999a3e338a90efa", "checksum_algorithm": "sha256" }, { "name": "docs", "file": "skills/docs/SKILL.md", "version": "20260421014", - "checksum": "d6ebf7d270eac7911aea09a34cb7ec4006c70064b7e6f58dfa84a405e6620d18", + "checksum": "7c13ddd443a33a854ce7f02fba9e50d6bfb60ecdd09e58edea4580d821d64cc6", "checksum_algorithm": "sha256" }, { "name": "explore", "file": "skills/explore/SKILL.md", "version": "20260421015", - "checksum": "914323f796ac95a0f73d404f1b7eb38d94ba8950ed1a4acc5d80600ba03fae8f", + "checksum": "699c3a1db16163e887c3e377f9618de8341cf77997bae6df4ad95f260be229ba", "checksum_algorithm": "sha256" }, { "name": "gdpr", "file": "skills/gdpr/SKILL.md", "version": "20260502029", - "checksum": "dfe60c4c59b7ae4dc66e98a885ec42b0e60eccc61fa1cb6fd269cd7eb3e0a94d", + "checksum": "796687e2877fb9af481fe4935ef185e93a17b179aa435bc696924bf91a9082d2", "checksum_algorithm": "sha256" }, { "name": "gh-issues", "file": "skills/gh-issues/SKILL.md", "version": "20260502025", - "checksum": "bc1f595283e7643de63c7519e994500b9434a0343154bfc404b842ce8e7028a8", + "checksum": "69e9f37144f418819028387f9769581b270c8797992df8fc09cf32db272249f6", "checksum_algorithm": "sha256" }, { "name": "gh-release", "file": "skills/gh-release/SKILL.md", "version": "20260502023", - "checksum": "10abe52f681d261588608418a546580e7798e883179888061ed6d2285050d870", + "checksum": "2c47e28a986239626a252d6d435e7a13d8a1d39284f14f7965336adee1ad7f00", "checksum_algorithm": "sha256" }, { "name": "guardrails", "file": "skills/guardrails/SKILL.md", "version": "20260421016", - "checksum": "1c1a44c95461eb4eeef7ad64b5846a1efbb202cf1b75186421c5d0b8f9fd03f1", + "checksum": "49bda4a6d23dbd3e272e034baa85daea3adbac226782a3921f0716b05c98cf0e", "checksum_algorithm": "sha256" }, { "name": "helm", "file": "skills/helm/SKILL.md", "version": "20260502037", - "checksum": "6e6579ede2dadb6dbe895d07c362d208e62414338445bd035f47bd1404be501b", + "checksum": "bd9057d8af61b4f1dacc637bfff80475a1ab594080dd88535f6d3f2c4a8258b5", "checksum_algorithm": "sha256" }, { "name": "incident", "file": "skills/incident/SKILL.md", "version": "20260503002", - "checksum": "546229fe91121dae2192aceb0b215096cc8cf5ba0cfb3d1300e30c73ff3f6276", + "checksum": "891fb9afe19b86010cbb8a810b47bd148c0f0df531d302e2b6d1ba91ae67d747", "checksum_algorithm": "sha256" }, { "name": "inspect", "file": "skills/inspect/SKILL.md", "version": "20260421018", - "checksum": "3a4aee3620bbd669602001c041cf81788da5fac9dc055a9f1b0c155e50e88b46", + "checksum": "e4fd6f8cbc837ac052cffa487f9cce14d8c0990e3f6285c6913ce44244dca6a8", "checksum_algorithm": "sha256" }, { "name": "k8s", "file": "skills/k8s/SKILL.md", "version": "20260502036", - "checksum": "2bfbe14c187809211a7296578644262e2e47c89b4a2fc28fc3a30a79fdaeb580", + "checksum": "f5319450a41d97e90a85f7eabd4fcabacef8288664fb5d5c884addc4892676a9", "checksum_algorithm": "sha256" }, { "name": "migrate", "file": "skills/migrate/SKILL.md", "version": "20260421019", - "checksum": "eecc00aae2c58c573000a434fbf7ae891a3e85dcc99a42fec0e8bcadc3047b7d", + "checksum": "f29f99ec9a5d324bb2d1628b8add0fac57cda66471633a1c23c66a46fdee02c6", "checksum_algorithm": "sha256" }, { "name": "onboard", "file": "skills/onboard/SKILL.md", "version": "20260421020", - "checksum": "f16f2d78002e3be8e7d1d9e2bf376c1b37eb181d821f3cd113e0d5aecf5236ae", + "checksum": "b2c4ac2679a31515d0ea6a1161076aeea19ef5b41f058f93471e077d098e9412", "checksum_algorithm": "sha256" }, { "name": "openapi", "file": "skills/openapi/SKILL.md", "version": "20260421021", - "checksum": "a3dbcded8e1a8b268a1976b9185f41de74ad0ad6e7aae98c399d6135de920529", + "checksum": "e7fda43ee964e2e31efd7f82dd7b11a28679fbe9f20b9a8faf6975719e518a68", "checksum_algorithm": "sha256" }, { "name": "performance", "file": "skills/performance/SKILL.md", "version": "20260421022", - "checksum": "fa4062c77381facacedac289b54a3ce7e6cf242dfa2b36f55401aebfe5fb8ae1", + "checksum": "9c27e5eda062cbd881d2073f0de4dac41b971adb024f1e1e0f28dd44393e8784", "checksum_algorithm": "sha256" }, { "name": "postmortem", "file": "skills/postmortem/SKILL.md", "version": "20260503001", - "checksum": "fc70f26420e705e0979ff76476ea8176386258f2315fcb57f5ac9049a37f6c90", + "checksum": "9197bed5f5bebb64988c639e4feb5ed264c72738d176875a0ce5d063b1e1d3aa", "checksum_algorithm": "sha256" }, { "name": "pr", "file": "skills/pr/SKILL.md", "version": "20260502013", - "checksum": "fc7b2b08696982afd48033a3cc29b9fa4ac8b590fc61c828b84135585c88b345", + "checksum": "9bcb8180cc74e8fa956818f3b5d27112fd73c289d966e8c2bb0a53109b9274ed", "checksum_algorithm": "sha256" }, { "name": "rancher", "file": "skills/rancher/SKILL.md", "version": "20260502038", - "checksum": "3000f24c2ee2f99fdb363f30d6075993ed26533663d081089b7800098a28e648", + "checksum": "26c67ca9a32e778f4b574fab91b33d00b425bc9a7ceb7177fb1558ebe1222951", "checksum_algorithm": "sha256" }, { "name": "rca", "file": "skills/rca/SKILL.md", "version": "20260503001", - "checksum": "8bebb57c166c153d89e4589097ea419266b249df871a79e24c840112b5f8ad0c", + "checksum": "f7e6a0fc4950136bf0165ba1051685c5b297779ea97aa76f1ca0631f593401b1", "checksum_algorithm": "sha256" }, { "name": "refactor", "file": "skills/refactor/SKILL.md", "version": "20260421023", - "checksum": "aa7fb995b6ac649b24b61066f15a6fe7fddab81bdcec8af27eba65092b957d68", + "checksum": "69b28c4a57134f29c6c59366b06ea7cf3468cc0db219d120567657abb0765801", "checksum_algorithm": "sha256" }, { "name": "release-notes", "file": "skills/release-notes/SKILL.md", "version": "20260502014", - "checksum": "589f001bcbf0e27c4da6ff9c530371a1f183b7936e3a7fc421879282bd7f9af3", + "checksum": "9b11a8bfc973ce1faf139cd90190e187955ee2334f7694049e39922f2e5d71c7", "checksum_algorithm": "sha256" }, { "name": "requirements", "file": "skills/requirements/SKILL.md", "version": "20260421024", - "checksum": "7d3338f0b7c5d493ab714a90b7d1732de56467503a3fe4dacdd40031bfea356c", + "checksum": "0a741ec2a7a899affef84a613d7a97d501feaf14e44355f3633aa199375c7afe", "checksum_algorithm": "sha256" }, { "name": "secret-scan", "file": "skills/secret-scan/SKILL.md", "version": "20260502028", - "checksum": "95df047af89a93a8fa3716899b8fe703b270d7f40b93fe65915f7d1bdb849370", + "checksum": "7ee0aadca4d6dac2d8c301ab51105a11587275ef5ed2700d50b786380040d29f", "checksum_algorithm": "sha256" }, { "name": "security", "file": "skills/security/SKILL.md", "version": "20260421025", - "checksum": "e9bcd451ee25b1c752239e76e20ca67302061356b59214fae7b134a180022df8", + "checksum": "ea7fc8189f6187a2dfa99114d1463d31b0afe147c0853406d80f90e15da5fd3c", "checksum_algorithm": "sha256" }, { "name": "terraform", "file": "skills/terraform/SKILL.md", "version": "20260502030", - "checksum": "2bb933512f72b61dae3a3b632a7afd43c62aad066de003ee0a565fc8f1895215", + "checksum": "6666debeef5f59910a0374c811744f3d29c7695a59ee141935c832bcc020c069", "checksum_algorithm": "sha256" }, { "name": "terragrunt", "file": "skills/terragrunt/SKILL.md", "version": "20260502031", - "checksum": "e0d3dc0feefeafb9cd41d8d6885d8b4d53baed52ba70df7dc8d33bfc7410bae8", + "checksum": "518577f40465f9ef94b90244deb3886206179c32ea533548267fe0b9ebda7190", "checksum_algorithm": "sha256" }, { "name": "threat-model", "file": "skills/threat-model/SKILL.md", "version": "20260502021", - "checksum": "7fdf69113167e9bb8c866681a86f4b4d6416145bb9def0ebe18ceba690c8fbe9", + "checksum": "a5d4d0f78a020b259337c446ad2a4abed37da7e6cfd70f9bbf3733f9e06f40f5", "checksum_algorithm": "sha256" }, { "name": "verify", "file": "skills/verify/SKILL.md", "version": "20260421026", - "checksum": "aa56d1a674ed827606af34b8e7c5bd9c2de61a6cb6f92f5fd9bdce28e46bcb30", + "checksum": "c242872c3542a4aaa54d1ce062e0432df6edde0a8678ac953e4729b5a93fb144", "checksum_algorithm": "sha256" }, { "name": "vision", "file": "skills/vision/SKILL.md", "version": "20260421027", - "checksum": "c0058202130905d8495a3e565b5db546ad2d0ff6d654596358a0b12c484ce310", + "checksum": "9dce0d40a2d80e8b5f521b0f36761667acbaf9b9a8e53f68f00118d27f8f90a4", "checksum_algorithm": "sha256" } ], @@ -319,49 +319,49 @@ "name": "architect", "file": "agents/architect.agent.md", "version": "20260503022", - "checksum": "99aa5d66f0d9039f7759c8011ab1a835b7262aebd6a4f7df591898d5102b8b94", + "checksum": "e097b021cee7e3a7bab7d75512a372b5aee073fd3d01ad28c4a7c582b569eebc", "checksum_algorithm": "sha256" }, { "name": "designer", "file": "agents/designer.agent.md", "version": "20260503024", - "checksum": "394717ed714328b3c92278a4de33b2bd067ee9ed100529c557bf71f5d777e0fb", + "checksum": "b7240cc02e1da79f962384ed5ac02ac769211f85c8f5976b8e8251168eb9deb7", "checksum_algorithm": "sha256" }, { "name": "engineer", "file": "agents/engineer.agent.md", "version": "20260503024", - "checksum": "2e6de55f6bb292cb750c6403be74db2dbe913db0ac64936a4f066945f88f88d8", + "checksum": "91ce873180353b58f41873a8ec2e4a448cc9f08871b81bc785fd182e0e1d1cde", "checksum_algorithm": "sha256" }, { "name": "planner", "file": "agents/planner.agent.md", "version": "20260510001", - "checksum": "73189a08e16d5e8f444471448bc4f26cf6dbbc290cb072d99fa6a768bfbc9bda", + "checksum": "18402e3cd9653e38943f23cb391147ede02b659b76847856ad678668c89b9f39", "checksum_algorithm": "sha256" }, { "name": "product", "file": "agents/product.agent.md", "version": "20260503021", - "checksum": "94c9a9ceaaa626ff20351e1c7f268b4d67464c23d2aae2070ba9385209888c50", + "checksum": "1bf2088c0c1d2750ede7e9c87d6e10dc4016db7f2aacce217be37ec8f4bc58b9", "checksum_algorithm": "sha256" }, { "name": "release", "file": "agents/release.agent.md", "version": "20260503020", - "checksum": "ea226eb56b001e3906e07ed552867fded99d2555f4c8e4b269619ae27cbb0194", + "checksum": "823cdefe1ee2aa3b4700da629a83f1efee0c8503cf0d214926d489a5b85720ea", "checksum_algorithm": "sha256" }, { "name": "tester", "file": "agents/tester.agent.md", "version": "20260503026", - "checksum": "27dd3baeeaad59b9a6a1a5cab0896282771e66278cf02d27820e3740b8793366", + "checksum": "2ab7d38e1bc24d530aaa784e8852ddd9f2c68c6d163466d6d608711062403197", "checksum_algorithm": "sha256" } ], @@ -369,29 +369,36 @@ { "name": "post-commit-security-scan", "file": "hooks/post-commit-security-scan.json", - "version": "20260510001", - "checksum": "edfaaa09795a29667e8bd0d281888efb0c08062aae0915204e90aef8a0aae284", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "4360b68090aa9c8e7f1cdce31924d5bacbabdc3e0d70cc618894b4049b0c9c33", "checksum_algorithm": "sha256" }, { "name": "post-edit-format", "file": "hooks/post-edit-format.json", - "version": "20260510001", - "checksum": "1b765a1edb135138f586c876d65c53258440ca01a9eea3ff098f6ceb715caf5a", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "add9f0a051add1c43d6016929c53d0bbde990b381360942cc4584ff0edbf5a5c", + "checksum_algorithm": "sha256" + }, + { + "name": "post-edit-markdown-quality", + "file": "hooks/post-edit-markdown-quality.json", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "5d7dbe986445710e99318586f40ff71385f4fb0c6fe2cb6b86a6f605cd2d4969", "checksum_algorithm": "sha256" }, { "name": "pre-tool-safety-gate", "file": "hooks/pre-tool-safety-gate.json", - "version": "20260510001", - "checksum": "1e8b862cccfa2a53251d6cae5e865bae8e08f1754a002ff9f8908b44f8ec3c6f", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "b1d92cfcd73dde9a47404159f89d32b1d61944fb786f4455c614e278a09eec4a", "checksum_algorithm": "sha256" }, { "name": "session-audit", "file": "hooks/session-audit.json", - "version": "20260510001", - "checksum": "dd8191abe47de0285eac06dbb67386a5694e7af80ea1986d8c9b837283717a23", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "b6a4c2a2fd78eb8a2ef087f25b2603ddf1d6081cff1cd741e572e6c459748965", "checksum_algorithm": "sha256" } ], @@ -400,84 +407,84 @@ "name": "git", "file": "instructions/git.instructions.md", "version": "20260421001", - "checksum": "ed6a191176e32631e2d572cb21278b555f6696a859839579821631156a8b35a5", + "checksum": "b0b866596988dab9e3b48b6cf1cb8de82b46cddfdcfe57214ee85308c2cf0e13", "checksum_algorithm": "sha256" }, { "name": "helm", "file": "instructions/helm.instructions.md", "version": "20260502040", - "checksum": "a60520853b79751517f089136ba7fb182feb1ea8e9c9495890e5c14ff32afd7b", + "checksum": "b1b5d2e535cf6a30fa86121c3e58081e77149242a16dabdb153291e4349f3015", "checksum_algorithm": "sha256" }, { "name": "java", "file": "instructions/java.instructions.md", "version": "20260502001", - "checksum": "69dc2bf3a5428ed03d77c2789871985ea7c5881af89c2b414548fca6e9488464", + "checksum": "663847977d195426234282780c53660fad2d5b06767e2848ec1afbef379e699a", "checksum_algorithm": "sha256" }, { "name": "k8s", "file": "instructions/k8s.instructions.md", "version": "20260502039", - "checksum": "f19f1060bf2a1424950496290a43a3535f9749e4ed838cf7475fe35eb9487d67", + "checksum": "95a7d12d5518eab2d91a20f5fe84e6f66af52dfb21b8ecb256d6b050144d8f03", "checksum_algorithm": "sha256" }, { "name": "markdown", "file": "instructions/markdown.instructions.md", "version": "20260502002", - "checksum": "59c93c5b0e63360aff18ab3a7a207f4e798efd1f2c26b0a561a4ef4ea688c5c5", + "checksum": "901ba40d993be4477057626039a4f4c6c0c6a49561429af5cb4cac016ebe3be2", "checksum_algorithm": "sha256" }, { "name": "python", "file": "instructions/python.instructions.md", "version": "20260421002", - "checksum": "ac40fffd3d3a3f9f8ca43e10ca603a187578c9d517533529a3d647b61cedf56c", + "checksum": "346e468cbf8249a8be1ae8881e1ac849ca0646e07ce044dc8e0e5947357293b6", "checksum_algorithm": "sha256" }, { "name": "rancher", "file": "instructions/rancher.instructions.md", "version": "20260502041", - "checksum": "4e643b1e078e9e8f127f1f2cf7707b36109e28b8697e208d8d3804848b867eec", + "checksum": "b0ea1821e3deac543599a2b838cb50d73cc663cffe95aecf7455279721cab294", "checksum_algorithm": "sha256" }, { "name": "security", "file": "instructions/security.instructions.md", "version": "20260502003", - "checksum": "a943df637e44cb23c8e82f8b39bf70558bc502ece5af9a03a15d8492cffb51a4", + "checksum": "a247eb547961349f52032152ed4ff1b9d77d6c268db8251d803a87ab31dd0ca5", "checksum_algorithm": "sha256" }, { "name": "terraform", "file": "instructions/terraform.instructions.md", "version": "20260502034", - "checksum": "1bada82da46a6359bb1814b6642074be6c0b7de9c2c6ca59e031a4d05ea4ea8c", + "checksum": "f0ac9c36bba8053351b1993418b4ae16f2db49b2bddf69e62ee1638f2a649908", "checksum_algorithm": "sha256" }, { "name": "terragrunt", "file": "instructions/terragrunt.instructions.md", "version": "20260502035", - "checksum": "13c0059c366624ab482c2b3d24a353bcb0a2ffbb6bf735abd18796969a3e4020", + "checksum": "37bb85ec3f2ab1c1bcaedd05da7f4df15e28a0ee32f74307ed53ad4733c028a5", "checksum_algorithm": "sha256" }, { "name": "testing", "file": "instructions/testing.instructions.md", "version": "20260502004", - "checksum": "cbd1948f367c32c39032209e5ed9fcfff8ce6c46c6324a4aa9a8550365873ca3", + "checksum": "11dd196e151c37d59585b77e7e95dddd2c395cf38b210ed2a9b88c83ccd0d756", "checksum_algorithm": "sha256" }, { "name": "typescript", "file": "instructions/typescript.instructions.md", "version": "20260502005", - "checksum": "fe412ba2e60baea66d0d07ae0c153fa2a9157bd57556fa7476f46ce466160ad3", + "checksum": "8fa6c892d0556be68e324e005bbb681024b7d41dcd89ba26c70874998d1d78bd", "checksum_algorithm": "sha256" } ], @@ -486,49 +493,49 @@ "name": "api-design-review", "file": "prompts/api-design-review.prompt.md", "version": "20260502006", - "checksum": "1ba62a6f78b836256fe578c4ca312de24599c79b018119b81a100f6de4e0da11", + "checksum": "1cc8cc182c052dd7e5d5c253a9549609f243a184b71d74aa6c092b9270692488", "checksum_algorithm": "sha256" }, { "name": "architecture-risk", "file": "prompts/architecture-risk.prompt.md", "version": "20260502007", - "checksum": "14fa36e36948309827c1c1296ff2c8b1f1306cfb19057ef190a5e60f6cc04a61", + "checksum": "4bc2830e1914c2a13cf849a8d67bc3e18355b4a1c24f001feaf88876c7131ba4", "checksum_algorithm": "sha256" }, { "name": "code-review", "file": "prompts/code-review.prompt.md", "version": "20260502008", - "checksum": "b6499ca66706a08ced7b9bf69bd288c2fecb684e1c85e333ee91d36ba7e66c3f", + "checksum": "02d98a41849a1cbb92c642a917850d9d689e3a4713dbb8b4e4ae5b22fe0fa801", "checksum_algorithm": "sha256" }, { "name": "dependency-audit", "file": "prompts/dependency-audit.prompt.md", "version": "20260502009", - "checksum": "51cead168dbbe52b455813cc620a8426ec287ceb2994831aa6ae115b4e49d0b0", + "checksum": "22c5d3f7f8ead6d750bf89fe1aa61c4a458fa7de3e78f3956cf363222965c8ac", "checksum_algorithm": "sha256" }, { "name": "incident-timeline", "file": "prompts/incident-timeline.prompt.md", "version": "20260502010", - "checksum": "28627dc7c362d510f312281fe0bda30e1927998bae440808af3a92d06c455393", + "checksum": "45729a09df502e63d0542a03690e9557ba6eb1f88bb50d4898233e749c179b58", "checksum_algorithm": "sha256" }, { "name": "migration-safety", "file": "prompts/migration-safety.prompt.md", "version": "20260502011", - "checksum": "814258882774ff98724b14caa0bc9ee35dc68f46e72e62b24884052e79ffacfa", + "checksum": "71f9f7ec5f1d714cd5d80b0582544a7bcc84a26cf5e3325b0846c9879a33fe14", "checksum_algorithm": "sha256" }, { "name": "release-readiness", "file": "prompts/release-readiness.prompt.md", "version": "20260502012", - "checksum": "49898a07169504acba1c2195abf730be77d1e17298cb4676f3ed926a27e31dc6", + "checksum": "ee729edd2e79b1c44ced2ca085422c0e3b170201c3dae6a67d14a6b1c861f87a", "checksum_algorithm": "sha256" } ] From e20e4af551ae7ebd180eb8decaa5c639797d2dc9 Mon Sep 17 00:00:00 2001 From: Erik Schaareman <erik@schaareman.org> Date: Sun, 10 May 2026 22:57:08 +0200 Subject: [PATCH 05/11] chore(gitignore): ignore local tmp test home --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f0608bd..ae80d4b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ env/ .pytest_cache/ .hypothesis/ .nox/ +.tmp-test-home/ .coverage .coverage.* coverage.xml From 4de65648b0537595d0b6dc5c5ad7567a38ce5ca3 Mon Sep 17 00:00:00 2001 From: Erik Schaareman <erik@schaareman.org> Date: Sun, 10 May 2026 22:57:53 +0200 Subject: [PATCH 06/11] chore(artifacts): regenerate installed github artifacts --- .github/agents/architect.agent.md | 2 +- .github/agents/designer.agent.md | 2 +- .github/agents/engineer.agent.md | 2 +- .github/agents/planner.agent.md | 2 +- .github/agents/product.agent.md | 2 +- .github/agents/release.agent.md | 2 +- .github/agents/tester.agent.md | 2 +- .github/instructions/git.instructions.md | 2 +- .github/instructions/helm.instructions.md | 2 +- .github/instructions/java.instructions.md | 2 +- .github/instructions/k8s.instructions.md | 2 +- .github/instructions/markdown.instructions.md | 2 +- .github/instructions/python.instructions.md | 2 +- .github/instructions/rancher.instructions.md | 2 +- .github/instructions/security.instructions.md | 2 +- .../instructions/terraform.instructions.md | 2 +- .../instructions/terragrunt.instructions.md | 2 +- .github/instructions/testing.instructions.md | 2 +- .../instructions/typescript.instructions.md | 2 +- .github/prompts/api-design-review.prompt.md | 2 +- .github/prompts/architecture-risk.prompt.md | 2 +- .github/prompts/code-review.prompt.md | 2 +- .github/prompts/dependency-audit.prompt.md | 2 +- .github/prompts/incident-timeline.prompt.md | 2 +- .github/prompts/migration-safety.prompt.md | 2 +- .github/prompts/release-readiness.prompt.md | 2 +- .github/skills/adr/SKILL.md | 2 +- .github/skills/analyse/SKILL.md | 2 +- .github/skills/architecture/SKILL.md | 2 +- .github/skills/aws-cli/SKILL.md | 2 +- .github/skills/cicd/SKILL.md | 2 +- .github/skills/cloudformation/SKILL.md | 2 +- .github/skills/code-review/SKILL.md | 2 +- .github/skills/codeql/SKILL.md | 2 +- .github/skills/concise/SKILL.md | 2 +- .github/skills/consult/SKILL.md | 2 +- .github/skills/container/SKILL.md | 2 +- .github/skills/conventional-commit/SKILL.md | 2 +- .github/skills/debug/SKILL.md | 2 +- .github/skills/dependabot/SKILL.md | 2 +- .github/skills/dependency/SKILL.md | 2 +- .github/skills/design/SKILL.md | 2 +- .github/skills/docs/SKILL.md | 2 +- .github/skills/explore/SKILL.md | 2 +- .github/skills/gdpr/SKILL.md | 2 +- .github/skills/gh-issues/SKILL.md | 2 +- .github/skills/gh-release/SKILL.md | 2 +- .github/skills/guardrails/SKILL.md | 2 +- .github/skills/helm/SKILL.md | 2 +- .github/skills/incident/SKILL.md | 2 +- .github/skills/inspect/SKILL.md | 2 +- .github/skills/k8s/SKILL.md | 2 +- .github/skills/migrate/SKILL.md | 2 +- .github/skills/onboard/SKILL.md | 2 +- .github/skills/openapi/SKILL.md | 2 +- .github/skills/performance/SKILL.md | 2 +- .github/skills/postmortem/SKILL.md | 2 +- .github/skills/pr/SKILL.md | 2 +- .github/skills/rancher/SKILL.md | 2 +- .github/skills/rca/SKILL.md | 2 +- .github/skills/refactor/SKILL.md | 2 +- .github/skills/release-notes/SKILL.md | 2 +- .github/skills/requirements/SKILL.md | 2 +- .github/skills/secret-scan/SKILL.md | 2 +- .github/skills/security/SKILL.md | 2 +- .github/skills/terraform/SKILL.md | 2 +- .github/skills/terragrunt/SKILL.md | 2 +- .github/skills/threat-model/SKILL.md | 2 +- .github/skills/verify/SKILL.md | 2 +- .github/skills/vision/SKILL.md | 2 +- .vstack/vstack.json | 154 +++++++++--------- 71 files changed, 147 insertions(+), 147 deletions(-) diff --git a/.github/agents/architect.agent.md b/.github/agents/architect.agent.md index a7de8fd..0365edd 100644 --- a/.github/agents/architect.agent.md +++ b/.github/agents/architect.agent.md @@ -180,4 +180,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gdpr` — privacy by design and data processing architecture review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architect","artifact_type":"agent","artifact_version":"20260503022","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"architect","artifact_type":"agent","artifact_version":"20260503022","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/designer.agent.md b/.github/agents/designer.agent.md index d77a994..f4c4041 100644 --- a/.github/agents/designer.agent.md +++ b/.github/agents/designer.agent.md @@ -193,4 +193,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#openapi` — OpenAPI 3.1 spec writing and review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"designer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"designer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/engineer.agent.md b/.github/agents/engineer.agent.md index b15ee02..ebf2eee 100644 --- a/.github/agents/engineer.agent.md +++ b/.github/agents/engineer.agent.md @@ -202,4 +202,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#rancher` — Rancher and Fleet multi-cluster operations and governance <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"engineer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"engineer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/planner.agent.md b/.github/agents/planner.agent.md index e3ee998..2482759 100644 --- a/.github/agents/planner.agent.md +++ b/.github/agents/planner.agent.md @@ -135,4 +135,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#analyse` - assess stage impact, skip rationale, and trade-offs <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"planner","artifact_type":"agent","artifact_version":"20260510001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"planner","artifact_type":"agent","artifact_version":"20260510001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/product.agent.md b/.github/agents/product.agent.md index 9a713da..2aeff6a 100644 --- a/.github/agents/product.agent.md +++ b/.github/agents/product.agent.md @@ -164,4 +164,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gh-issues` — create and manage GitHub Issues for requirements, tasks, and user stories <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"product","artifact_type":"agent","artifact_version":"20260503021","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"product","artifact_type":"agent","artifact_version":"20260503021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/release.agent.md b/.github/agents/release.agent.md index 836403d..bf600b5 100644 --- a/.github/agents/release.agent.md +++ b/.github/agents/release.agent.md @@ -154,4 +154,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gh-issues` — create and manage GitHub Issues for tracking work and bug reports <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release","artifact_type":"agent","artifact_version":"20260503020","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"release","artifact_type":"agent","artifact_version":"20260503020","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/agents/tester.agent.md b/.github/agents/tester.agent.md index aa3e888..b77fcda 100644 --- a/.github/agents/tester.agent.md +++ b/.github/agents/tester.agent.md @@ -180,4 +180,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#rancher` — Rancher/Fleet configuration and multi-cluster governance review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"tester","artifact_type":"agent","artifact_version":"20260503026","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"tester","artifact_type":"agent","artifact_version":"20260503026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/git.instructions.md b/.github/instructions/git.instructions.md index db6263a..ee0ecde 100644 --- a/.github/instructions/git.instructions.md +++ b/.github/instructions/git.instructions.md @@ -41,4 +41,4 @@ Use these Git and release hygiene conventions in this project. 1. Prefer local verification before pushing release-impacting changes. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"git","artifact_type":"instruction","artifact_version":"20260421001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"git","artifact_type":"instruction","artifact_version":"20260421001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/helm.instructions.md b/.github/instructions/helm.instructions.md index f570c7d..1b37194 100644 --- a/.github/instructions/helm.instructions.md +++ b/.github/instructions/helm.instructions.md @@ -45,4 +45,4 @@ Use these Helm conventions in this project. - [Helm chart best practices](https://helm.sh/docs/chart_best_practices/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"instruction","artifact_version":"20260502040","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"instruction","artifact_version":"20260502040","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/java.instructions.md b/.github/instructions/java.instructions.md index 118782c..98a498a 100644 --- a/.github/instructions/java.instructions.md +++ b/.github/instructions/java.instructions.md @@ -56,4 +56,4 @@ Use these Java conventions in this project. 1. Do not suppress static analysis warnings without a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"java","artifact_type":"instruction","artifact_version":"20260502001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"java","artifact_type":"instruction","artifact_version":"20260502001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/k8s.instructions.md b/.github/instructions/k8s.instructions.md index e69825b..5bcc17e 100644 --- a/.github/instructions/k8s.instructions.md +++ b/.github/instructions/k8s.instructions.md @@ -51,4 +51,4 @@ Use these Kubernetes conventions in this project. - [Kubernetes API reference](https://kubernetes.io/docs/reference/kubernetes-api/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"instruction","artifact_version":"20260502039","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"instruction","artifact_version":"20260502039","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/markdown.instructions.md b/.github/instructions/markdown.instructions.md index 7cf32ad..64b29d5 100644 --- a/.github/instructions/markdown.instructions.md +++ b/.github/instructions/markdown.instructions.md @@ -51,4 +51,4 @@ Use these Markdown conventions in this project. 1. Keep examples accurate and runnable — a broken example is worse than no example. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"markdown","artifact_type":"instruction","artifact_version":"20260502002","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"markdown","artifact_type":"instruction","artifact_version":"20260502002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/python.instructions.md b/.github/instructions/python.instructions.md index 48db4d4..a9a809c 100644 --- a/.github/instructions/python.instructions.md +++ b/.github/instructions/python.instructions.md @@ -42,4 +42,4 @@ Use these Python conventions in this project. 1. Do not silence lint/type errors unless there is a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"python","artifact_type":"instruction","artifact_version":"20260421002","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"python","artifact_type":"instruction","artifact_version":"20260421002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/rancher.instructions.md b/.github/instructions/rancher.instructions.md index 0457e71..3f52dd8 100644 --- a/.github/instructions/rancher.instructions.md +++ b/.github/instructions/rancher.instructions.md @@ -44,4 +44,4 @@ Use these Rancher conventions in this project. - [Fleet docs](https://fleet.rancher.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"instruction","artifact_version":"20260502041","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"instruction","artifact_version":"20260502041","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/security.instructions.md b/.github/instructions/security.instructions.md index 8d5f82c..dd02e2c 100644 --- a/.github/instructions/security.instructions.md +++ b/.github/instructions/security.instructions.md @@ -42,4 +42,4 @@ Apply these security policies in this project. 1. Isolate privileged logic; keep it minimal, auditable, and separate from business logic. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"instruction","artifact_version":"20260502003","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"instruction","artifact_version":"20260502003","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/terraform.instructions.md b/.github/instructions/terraform.instructions.md index fb5e44f..da8fe19 100644 --- a/.github/instructions/terraform.instructions.md +++ b/.github/instructions/terraform.instructions.md @@ -60,4 +60,4 @@ Use these Terraform conventions in this project. - [tfsec](https://aquasecurity.github.io/tfsec/) · [checkov](https://www.checkov.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"instruction","artifact_version":"20260502034","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"instruction","artifact_version":"20260502034","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/terragrunt.instructions.md b/.github/instructions/terragrunt.instructions.md index d184fc6..9770cb6 100644 --- a/.github/instructions/terragrunt.instructions.md +++ b/.github/instructions/terragrunt.instructions.md @@ -57,4 +57,4 @@ Use these Terragrunt conventions in this project. - [Terragrunt CLI reference](https://terragrunt.gruntwork.io/docs/reference/cli-options/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"instruction","artifact_version":"20260502035","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"instruction","artifact_version":"20260502035","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index c1ffbe2..e673737 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -43,4 +43,4 @@ Use these testing conventions in this project. 1. Treat flaky tests as bugs; do not merge code with known test reliability issues. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"testing","artifact_type":"instruction","artifact_version":"20260502004","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"testing","artifact_type":"instruction","artifact_version":"20260502004","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/instructions/typescript.instructions.md b/.github/instructions/typescript.instructions.md index f1692d2..adb935d 100644 --- a/.github/instructions/typescript.instructions.md +++ b/.github/instructions/typescript.instructions.md @@ -49,4 +49,4 @@ Use these TypeScript conventions in this project. 1. Do not suppress lint or type errors with inline disable comments unless there is a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"typescript","artifact_type":"instruction","artifact_version":"20260502005","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"typescript","artifact_type":"instruction","artifact_version":"20260502005","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/api-design-review.prompt.md b/.github/prompts/api-design-review.prompt.md index 2222575..70e505c 100644 --- a/.github/prompts/api-design-review.prompt.md +++ b/.github/prompts/api-design-review.prompt.md @@ -55,4 +55,4 @@ List fields or objects that are missing required constraints, descriptions, or e - top priority fix in one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"api-design-review","artifact_type":"prompt","artifact_version":"20260502006","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"api-design-review","artifact_type":"prompt","artifact_version":"20260502006","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/architecture-risk.prompt.md b/.github/prompts/architecture-risk.prompt.md index 838adbb..9d9f919 100644 --- a/.github/prompts/architecture-risk.prompt.md +++ b/.github/prompts/architecture-risk.prompt.md @@ -52,4 +52,4 @@ List security-specific risks not covered above: auth boundaries, sensitive data - one-sentence rationale <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architecture-risk","artifact_type":"prompt","artifact_version":"20260502007","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"architecture-risk","artifact_type":"prompt","artifact_version":"20260502007","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/code-review.prompt.md b/.github/prompts/code-review.prompt.md index 4c6ceec..4f6287e 100644 --- a/.github/prompts/code-review.prompt.md +++ b/.github/prompts/code-review.prompt.md @@ -51,4 +51,4 @@ End with: - Biggest remaining risk: one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"prompt","artifact_version":"20260502008","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"prompt","artifact_version":"20260502008","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/dependency-audit.prompt.md b/.github/prompts/dependency-audit.prompt.md index 33f9fc3..d8744b5 100644 --- a/.github/prompts/dependency-audit.prompt.md +++ b/.github/prompts/dependency-audit.prompt.md @@ -60,4 +60,4 @@ List packages with unusual provenance concerns: abandoned maintainers, single-ma Ordered list of actions by priority (critical first). <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependency-audit","artifact_type":"prompt","artifact_version":"20260502009","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"dependency-audit","artifact_type":"prompt","artifact_version":"20260502009","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/incident-timeline.prompt.md b/.github/prompts/incident-timeline.prompt.md index d92f4ad..ebe3980 100644 --- a/.github/prompts/incident-timeline.prompt.md +++ b/.github/prompts/incident-timeline.prompt.md @@ -57,4 +57,4 @@ For each action: List the minimum controls needed to reduce repeat probability. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"incident-timeline","artifact_type":"prompt","artifact_version":"20260502010","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"incident-timeline","artifact_type":"prompt","artifact_version":"20260502010","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/migration-safety.prompt.md b/.github/prompts/migration-safety.prompt.md index 514d14e..e602ce8 100644 --- a/.github/prompts/migration-safety.prompt.md +++ b/.github/prompts/migration-safety.prompt.md @@ -52,4 +52,4 @@ List missing migration tests (forward, backward, data invariants, load-sensitive - biggest remaining risk in one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"migration-safety","artifact_type":"prompt","artifact_version":"20260502011","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"migration-safety","artifact_type":"prompt","artifact_version":"20260502011","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/prompts/release-readiness.prompt.md b/.github/prompts/release-readiness.prompt.md index f99b15a..8552f4f 100644 --- a/.github/prompts/release-readiness.prompt.md +++ b/.github/prompts/release-readiness.prompt.md @@ -46,4 +46,4 @@ For each expected artifact that is missing, flag it explicitly as: MISSING — [ One clear next step for the team. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release-readiness","artifact_type":"prompt","artifact_version":"20260502012","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"release-readiness","artifact_type":"prompt","artifact_version":"20260502012","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/adr/SKILL.md b/.github/skills/adr/SKILL.md index 7c36e1b..7bd56f5 100644 --- a/.github/skills/adr/SKILL.md +++ b/.github/skills/adr/SKILL.md @@ -180,4 +180,4 @@ is a kebab-case title. After writing, state the file path and summary so the architect or product role can review. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"adr","artifact_type":"skill","artifact_version":"20260421003","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"adr","artifact_type":"skill","artifact_version":"20260421003","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/analyse/SKILL.md b/.github/skills/analyse/SKILL.md index 69762c9..3856249 100644 --- a/.github/skills/analyse/SKILL.md +++ b/.github/skills/analyse/SKILL.md @@ -214,4 +214,4 @@ State conclusions with confidence level: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"analyse","artifact_type":"skill","artifact_version":"20260421004","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"analyse","artifact_type":"skill","artifact_version":"20260421004","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/architecture/SKILL.md b/.github/skills/architecture/SKILL.md index 58a1ead..e55a8d3 100644 --- a/.github/skills/architecture/SKILL.md +++ b/.github/skills/architecture/SKILL.md @@ -278,4 +278,4 @@ For each significant structural decision made during this review (technology cho - Update `docs/architecture/overview.md` to reflect the final decisions. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architecture","artifact_type":"skill","artifact_version":"20260421005","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"architecture","artifact_type":"skill","artifact_version":"20260421005","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/aws-cli/SKILL.md b/.github/skills/aws-cli/SKILL.md index c25b92d..c28e4af 100644 --- a/.github/skills/aws-cli/SKILL.md +++ b/.github/skills/aws-cli/SKILL.md @@ -374,4 +374,4 @@ aws ce get-cost-and-usage \ - [AWS CLI named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"aws-cli","artifact_type":"skill","artifact_version":"20260502033","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"aws-cli","artifact_type":"skill","artifact_version":"20260502033","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/cicd/SKILL.md b/.github/skills/cicd/SKILL.md index 81b957a..421c19a 100644 --- a/.github/skills/cicd/SKILL.md +++ b/.github/skills/cicd/SKILL.md @@ -221,4 +221,4 @@ Configure these in GitHub → Settings → Branches. - [GitHub-hosted runners](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"cicd","artifact_type":"skill","artifact_version":"20260421006","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"cicd","artifact_type":"skill","artifact_version":"20260421006","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/cloudformation/SKILL.md b/.github/skills/cloudformation/SKILL.md index 0b735c7..2ad5f01 100644 --- a/.github/skills/cloudformation/SKILL.md +++ b/.github/skills/cloudformation/SKILL.md @@ -344,4 +344,4 @@ AppSecurityGroup: - [AWS SAM documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"cloudformation","artifact_type":"skill","artifact_version":"20260502032","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"cloudformation","artifact_type":"skill","artifact_version":"20260502032","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/code-review/SKILL.md b/.github/skills/code-review/SKILL.md index 60c3a84..4d3a47b 100644 --- a/.github/skills/code-review/SKILL.md +++ b/.github/skills/code-review/SKILL.md @@ -218,4 +218,4 @@ Confidence: [HIGH/MEDIUM/LOW — explain if not HIGH] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"skill","artifact_version":"20260421007","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"skill","artifact_version":"20260421007","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/codeql/SKILL.md b/.github/skills/codeql/SKILL.md index ffd1863..855da54 100644 --- a/.github/skills/codeql/SKILL.md +++ b/.github/skills/codeql/SKILL.md @@ -251,4 +251,4 @@ GITHUB_TOKEN=<token> codeql github upload-results \ - [Supported languages and frameworks](https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/codeql-code-scanning-for-compiled-languages) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"codeql","artifact_type":"skill","artifact_version":"20260502026","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"codeql","artifact_type":"skill","artifact_version":"20260502026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/concise/SKILL.md b/.github/skills/concise/SKILL.md index 188e8b0..dac193d 100644 --- a/.github/skills/concise/SKILL.md +++ b/.github/skills/concise/SKILL.md @@ -147,4 +147,4 @@ Current mode unchanged: <mode> - [ ] User confirmation/status returned in deterministic format <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"concise","artifact_type":"skill","artifact_version":"20260421008","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"concise","artifact_type":"skill","artifact_version":"20260421008","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/consult/SKILL.md b/.github/skills/consult/SKILL.md index 8a3a2a3..2571f4e 100644 --- a/.github/skills/consult/SKILL.md +++ b/.github/skills/consult/SKILL.md @@ -218,4 +218,4 @@ reason: [one sentence] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"consult","artifact_type":"skill","artifact_version":"20260421009","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"consult","artifact_type":"skill","artifact_version":"20260421009","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/container/SKILL.md b/.github/skills/container/SKILL.md index ea38b36..2d9c924 100644 --- a/.github/skills/container/SKILL.md +++ b/.github/skills/container/SKILL.md @@ -153,4 +153,4 @@ For production-like local testing, write a separate `docker-compose.prod.yml` wi - [Docker official images](https://hub.docker.com/search?image_filter=official) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"container","artifact_type":"skill","artifact_version":"20260421010","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"container","artifact_type":"skill","artifact_version":"20260421010","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/conventional-commit/SKILL.md b/.github/skills/conventional-commit/SKILL.md index d5dde6b..77a4b00 100644 --- a/.github/skills/conventional-commit/SKILL.md +++ b/.github/skills/conventional-commit/SKILL.md @@ -155,4 +155,4 @@ Remaining changes: If commit is blocked, report exact reason and proposed fix. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"conventional-commit","artifact_type":"skill","artifact_version":"20260502024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"conventional-commit","artifact_type":"skill","artifact_version":"20260502024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/debug/SKILL.md b/.github/skills/debug/SKILL.md index 1f2d656..76ec3b7 100644 --- a/.github/skills/debug/SKILL.md +++ b/.github/skills/debug/SKILL.md @@ -258,4 +258,4 @@ Prevention: [any follow-up items] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"debug","artifact_type":"skill","artifact_version":"20260421011","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"debug","artifact_type":"skill","artifact_version":"20260421011","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/dependabot/SKILL.md b/.github/skills/dependabot/SKILL.md index a269f4d..ad9aeff 100644 --- a/.github/skills/dependabot/SKILL.md +++ b/.github/skills/dependabot/SKILL.md @@ -320,4 +320,4 @@ updates: - [Dependabot security updates](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependabot","artifact_type":"skill","artifact_version":"20260502027","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"dependabot","artifact_type":"skill","artifact_version":"20260502027","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/dependency/SKILL.md b/.github/skills/dependency/SKILL.md index 26681ca..3313f88 100644 --- a/.github/skills/dependency/SKILL.md +++ b/.github/skills/dependency/SKILL.md @@ -318,4 +318,4 @@ Action items (priority order): - [PyPI / npm / crates.io / Maven Central](https://pypi.org) (replace with the relevant registry) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependency","artifact_type":"skill","artifact_version":"20260421012","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"dependency","artifact_type":"skill","artifact_version":"20260421012","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/design/SKILL.md b/.github/skills/design/SKILL.md index c62109d..e732ac8 100644 --- a/.github/skills/design/SKILL.md +++ b/.github/skills/design/SKILL.md @@ -243,4 +243,4 @@ Output a complete design document to `docs/design/overview.md` or `openapi.yaml` ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"design","artifact_type":"skill","artifact_version":"20260421013","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"design","artifact_type":"skill","artifact_version":"20260421013","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/docs/SKILL.md b/.github/skills/docs/SKILL.md index fa75166..be97300 100644 --- a/.github/skills/docs/SKILL.md +++ b/.github/skills/docs/SKILL.md @@ -151,4 +151,4 @@ Skipped (n/a): ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"docs","artifact_type":"skill","artifact_version":"20260421014","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"docs","artifact_type":"skill","artifact_version":"20260421014","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/explore/SKILL.md b/.github/skills/explore/SKILL.md index b46d037..aa6af6a 100644 --- a/.github/skills/explore/SKILL.md +++ b/.github/skills/explore/SKILL.md @@ -224,4 +224,4 @@ Stack: [language, framework, runtime versions] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"explore","artifact_type":"skill","artifact_version":"20260421015","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"explore","artifact_type":"skill","artifact_version":"20260421015","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/gdpr/SKILL.md b/.github/skills/gdpr/SKILL.md index d1ae2c9..b6ff4fd 100644 --- a/.github/skills/gdpr/SKILL.md +++ b/.github/skills/gdpr/SKILL.md @@ -246,4 +246,4 @@ Use `@example.com` for all test email addresses. - [EDPB guidelines](https://www.edpb.europa.eu/our-work-tools/general-guidance/guidelines-recommendations-best-practices_en) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gdpr","artifact_type":"skill","artifact_version":"20260502029","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"gdpr","artifact_type":"skill","artifact_version":"20260502029","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/gh-issues/SKILL.md b/.github/skills/gh-issues/SKILL.md index 692ec90..3b5a19a 100644 --- a/.github/skills/gh-issues/SKILL.md +++ b/.github/skills/gh-issues/SKILL.md @@ -230,4 +230,4 @@ https://github.com/<org>/<repo>/issues/<number> - [GitHub Issues documentation](https://docs.github.com/en/issues) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gh-issues","artifact_type":"skill","artifact_version":"20260502025","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"gh-issues","artifact_type":"skill","artifact_version":"20260502025","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/gh-release/SKILL.md b/.github/skills/gh-release/SKILL.md index b8e7465..e3b1c68 100644 --- a/.github/skills/gh-release/SKILL.md +++ b/.github/skills/gh-release/SKILL.md @@ -214,4 +214,4 @@ If blocked, report exact blocker and required user action. - [GitHub Releases documentation](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gh-release","artifact_type":"skill","artifact_version":"20260502023","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"gh-release","artifact_type":"skill","artifact_version":"20260502023","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/guardrails/SKILL.md b/.github/skills/guardrails/SKILL.md index b5f1702..820fe2e 100644 --- a/.github/skills/guardrails/SKILL.md +++ b/.github/skills/guardrails/SKILL.md @@ -72,4 +72,4 @@ Activate careful mode for this session. Two behaviors are now enabled. Explicitly ask to "disable guardrails". <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"guardrails","artifact_type":"skill","artifact_version":"20260421016","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"guardrails","artifact_type":"skill","artifact_version":"20260421016","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/helm/SKILL.md b/.github/skills/helm/SKILL.md index 74ad155..1746edd 100644 --- a/.github/skills/helm/SKILL.md +++ b/.github/skills/helm/SKILL.md @@ -140,4 +140,4 @@ Practices: - [Chart best practices](https://helm.sh/docs/chart_best_practices/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"skill","artifact_version":"20260502037","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"skill","artifact_version":"20260502037","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/incident/SKILL.md b/.github/skills/incident/SKILL.md index 4d88c90..784e4ac 100644 --- a/.github/skills/incident/SKILL.md +++ b/.github/skills/incident/SKILL.md @@ -251,4 +251,4 @@ Next: invoke @#rca and @#postmortem to produce written artifacts. ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"incident","artifact_type":"skill","artifact_version":"20260503002","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"incident","artifact_type":"skill","artifact_version":"20260503002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/inspect/SKILL.md b/.github/skills/inspect/SKILL.md index 944d397..9a2346a 100644 --- a/.github/skills/inspect/SKILL.md +++ b/.github/skills/inspect/SKILL.md @@ -155,4 +155,4 @@ Confirm for changed paths: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"inspect","artifact_type":"skill","artifact_version":"20260421018","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"inspect","artifact_type":"skill","artifact_version":"20260421018","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/k8s/SKILL.md b/.github/skills/k8s/SKILL.md index 780b5af..42ca2a6 100644 --- a/.github/skills/k8s/SKILL.md +++ b/.github/skills/k8s/SKILL.md @@ -144,4 +144,4 @@ Common failure classes: - [Kubernetes API reference](https://kubernetes.io/docs/reference/kubernetes-api/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"skill","artifact_version":"20260502036","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"skill","artifact_version":"20260502036","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/migrate/SKILL.md b/.github/skills/migrate/SKILL.md index 93474c9..07e1934 100644 --- a/.github/skills/migrate/SKILL.md +++ b/.github/skills/migrate/SKILL.md @@ -320,4 +320,4 @@ Pre-deploy checklist: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"migrate","artifact_type":"skill","artifact_version":"20260421019","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"migrate","artifact_type":"skill","artifact_version":"20260421019","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/onboard/SKILL.md b/.github/skills/onboard/SKILL.md index 63dd1cd..da290f0 100644 --- a/.github/skills/onboard/SKILL.md +++ b/.github/skills/onboard/SKILL.md @@ -302,4 +302,4 @@ Gaps remaining (if any): ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"onboard","artifact_type":"skill","artifact_version":"20260421020","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"onboard","artifact_type":"skill","artifact_version":"20260421020","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/openapi/SKILL.md b/.github/skills/openapi/SKILL.md index 5ab1574..77a8b91 100644 --- a/.github/skills/openapi/SKILL.md +++ b/.github/skills/openapi/SKILL.md @@ -403,4 +403,4 @@ Summary: [N critical, N warnings, N info] - [Redocly CLI (linting)](https://redocly.com/docs/cli/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"openapi","artifact_type":"skill","artifact_version":"20260421021","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"openapi","artifact_type":"skill","artifact_version":"20260421021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/performance/SKILL.md b/.github/skills/performance/SKILL.md index 0211b1a..4bd99dc 100644 --- a/.github/skills/performance/SKILL.md +++ b/.github/skills/performance/SKILL.md @@ -242,4 +242,4 @@ For each bottleneck identified: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"performance","artifact_type":"skill","artifact_version":"20260421022","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"performance","artifact_type":"skill","artifact_version":"20260421022","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/postmortem/SKILL.md b/.github/skills/postmortem/SKILL.md index 2d90f74..12508ec 100644 --- a/.github/skills/postmortem/SKILL.md +++ b/.github/skills/postmortem/SKILL.md @@ -184,4 +184,4 @@ Status: Draft — ready for team review ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"postmortem","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"postmortem","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/pr/SKILL.md b/.github/skills/pr/SKILL.md index 24a1b8c..bd8368a 100644 --- a/.github/skills/pr/SKILL.md +++ b/.github/skills/pr/SKILL.md @@ -151,4 +151,4 @@ Next steps depend on the repository CI/CD configuration: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"pr","artifact_type":"skill","artifact_version":"20260502013","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"pr","artifact_type":"skill","artifact_version":"20260502013","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/rancher/SKILL.md b/.github/skills/rancher/SKILL.md index ed7f64a..3d58218 100644 --- a/.github/skills/rancher/SKILL.md +++ b/.github/skills/rancher/SKILL.md @@ -113,4 +113,4 @@ Checks: - [Fleet documentation](https://fleet.rancher.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"skill","artifact_version":"20260502038","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"skill","artifact_version":"20260502038","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/rca/SKILL.md b/.github/skills/rca/SKILL.md index 8f91c15..0a302a5 100644 --- a/.github/skills/rca/SKILL.md +++ b/.github/skills/rca/SKILL.md @@ -207,4 +207,4 @@ Status: Draft — ready for review ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rca","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"rca","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/refactor/SKILL.md b/.github/skills/refactor/SKILL.md index da59d2a..1110f6b 100644 --- a/.github/skills/refactor/SKILL.md +++ b/.github/skills/refactor/SKILL.md @@ -372,4 +372,4 @@ Behavior changed: No ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"refactor","artifact_type":"skill","artifact_version":"20260421023","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"refactor","artifact_type":"skill","artifact_version":"20260421023","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index 079defd..39d4c98 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -147,4 +147,4 @@ Prepend a new entry at the top of `CHANGELOG.md`: Keep existing entries intact. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release-notes","artifact_type":"skill","artifact_version":"20260502014","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"release-notes","artifact_type":"skill","artifact_version":"20260502014","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/requirements/SKILL.md b/.github/skills/requirements/SKILL.md index a9f7856..5d1e7d7 100644 --- a/.github/skills/requirements/SKILL.md +++ b/.github/skills/requirements/SKILL.md @@ -199,4 +199,4 @@ Write all findings to `docs/product/requirements.md`: After writing, summarize what was decided so the architect role can start. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"requirements","artifact_type":"skill","artifact_version":"20260421024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"requirements","artifact_type":"skill","artifact_version":"20260421024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/secret-scan/SKILL.md b/.github/skills/secret-scan/SKILL.md index 00dc2f9..0c0674b 100644 --- a/.github/skills/secret-scan/SKILL.md +++ b/.github/skills/secret-scan/SKILL.md @@ -240,4 +240,4 @@ credential formats. - [Supported secret patterns](https://docs.github.com/en/code-security/secret-scanning/introduction/supported-secret-scanning-patterns) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"secret-scan","artifact_type":"skill","artifact_version":"20260502028","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"secret-scan","artifact_type":"skill","artifact_version":"20260502028","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/security/SKILL.md b/.github/skills/security/SKILL.md index f868915..eb35c48 100644 --- a/.github/skills/security/SKILL.md +++ b/.github/skills/security/SKILL.md @@ -295,4 +295,4 @@ Scope: [full/diff/dependency/config] - [STRIDE threat modeling (Microsoft)](https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"skill","artifact_version":"20260421025","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"skill","artifact_version":"20260421025","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/terraform/SKILL.md b/.github/skills/terraform/SKILL.md index 0ec9dc4..3ba5145 100644 --- a/.github/skills/terraform/SKILL.md +++ b/.github/skills/terraform/SKILL.md @@ -335,4 +335,4 @@ Run `terraform plan` after every state operation to verify the outcome. - [tfsec rules](https://aquasecurity.github.io/tfsec/latest/checks/aws/) · [checkov checks](https://www.checkov.io/5.Policy%20Index/terraform.html) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"skill","artifact_version":"20260502030","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"skill","artifact_version":"20260502030","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/terragrunt/SKILL.md b/.github/skills/terragrunt/SKILL.md index 26929fe..77326de 100644 --- a/.github/skills/terragrunt/SKILL.md +++ b/.github/skills/terragrunt/SKILL.md @@ -307,4 +307,4 @@ Use `--terragrunt-non-interactive` in CI to prevent hanging on prompts. - [Gruntwork module registry](https://www.gruntwork.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"skill","artifact_version":"20260502031","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"skill","artifact_version":"20260502031","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/threat-model/SKILL.md b/.github/skills/threat-model/SKILL.md index 33ff969..d2a81e5 100644 --- a/.github/skills/threat-model/SKILL.md +++ b/.github/skills/threat-model/SKILL.md @@ -245,4 +245,4 @@ For each high-priority threat include: - Final report is written to `docs/architecture/threat-model.md`. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"threat-model","artifact_type":"skill","artifact_version":"20260502021","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"threat-model","artifact_type":"skill","artifact_version":"20260502021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/verify/SKILL.md b/.github/skills/verify/SKILL.md index eb4dfcd..585a7df 100644 --- a/.github/skills/verify/SKILL.md +++ b/.github/skills/verify/SKILL.md @@ -266,4 +266,4 @@ scope: [path/component/full] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"verify","artifact_type":"skill","artifact_version":"20260421026","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"verify","artifact_type":"skill","artifact_version":"20260421026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.github/skills/vision/SKILL.md b/.github/skills/vision/SKILL.md index bd3370d..16dbb1b 100644 --- a/.github/skills/vision/SKILL.md +++ b/.github/skills/vision/SKILL.md @@ -206,4 +206,4 @@ For each finding: explain the tradeoff, give an opinionated recommendation, ask Present as: "Overall assessment: [READY/NEEDS REVISION/SCOPE CHANGE] because [1-2 sentence reason]." <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"vision","artifact_type":"skill","artifact_version":"20260421027","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> +<!-- VSTACK-META: {"artifact_name":"vision","artifact_type":"skill","artifact_version":"20260421027","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> diff --git a/.vstack/vstack.json b/.vstack/vstack.json index 05d1fe7..d1b2186 100644 --- a/.vstack/vstack.json +++ b/.vstack/vstack.json @@ -1,316 +1,316 @@ { "manifest_version": 2, "hash_algorithm": "sha256", - "vstack_version": "3.1.1.post2.dev0+4d3419b", - "installed_at": "2026-05-10T20:07:07.839973+00:00", + "vstack_version": "0.0.0.post3.dev0+df3fe6e", + "installed_at": "2026-05-10T20:52:18.390186+00:00", "artifacts": { "skills": [ { "name": "adr", "file": "skills/adr/SKILL.md", "version": "20260421003", - "checksum": "1def4de23e0bb887392fdc79f5a354416ed6851b4d9584411d2cb04a280f2259", + "checksum": "871263fddee0b3ae8e2c2e2243aad4f2b3aa9e0dea0d2519c7d21c3e98430a83", "checksum_algorithm": "sha256" }, { "name": "analyse", "file": "skills/analyse/SKILL.md", "version": "20260421004", - "checksum": "abfc4634c6a0123f95bf7a397e7c0334615c3c86da4cb02f18cd904fd33df917", + "checksum": "79ced25692671a5286a26cbcd0e59ed4d641ac7493fd8e2b58b232f97e467224", "checksum_algorithm": "sha256" }, { "name": "architecture", "file": "skills/architecture/SKILL.md", "version": "20260421005", - "checksum": "381c9ba6e33f2334f96227336c3cadab0ccdc78cf256c43a579fe31fde2689a9", + "checksum": "e2d0342184829c888a70ece6f82ad6a7450ad5d8c958ff6e028d7f245f1e5960", "checksum_algorithm": "sha256" }, { "name": "aws-cli", "file": "skills/aws-cli/SKILL.md", "version": "20260502033", - "checksum": "484f61c7e9686f8f45e6e0e470bec05e638269ce760e9f029e19aa65a282647e", + "checksum": "4868a5adc17b6ac52839c85f3a09986991c2498501913ef48a8f85a6291e1b21", "checksum_algorithm": "sha256" }, { "name": "cicd", "file": "skills/cicd/SKILL.md", "version": "20260421006", - "checksum": "c58754d85c160836e96820770be3d7c2d3959091032c3e0eb65fb9898886a515", + "checksum": "ba6c0cf0e644e24b1290c1b78969bad86dd7d5b39030edc7bd430d4d1832cfd7", "checksum_algorithm": "sha256" }, { "name": "cloudformation", "file": "skills/cloudformation/SKILL.md", "version": "20260502032", - "checksum": "a7576e06214a0e549d216818417677feae79d29b26485ddd7124c40e0349c51b", + "checksum": "0e61163cfc356105a309bbecf89ee26a5b10b9b5db58c331d76efa4a508e760e", "checksum_algorithm": "sha256" }, { "name": "code-review", "file": "skills/code-review/SKILL.md", "version": "20260421007", - "checksum": "48d6d8f5100875cd24540a8df8a3a8a1eccfcf353f8d649e9437d94ffa8374c0", + "checksum": "58b7caa9dd3a5da6bef8d8d4d1f7b6d513740d33ad16731a06b1b2a907d93a13", "checksum_algorithm": "sha256" }, { "name": "codeql", "file": "skills/codeql/SKILL.md", "version": "20260502026", - "checksum": "87594771b5e46fd7a43d1a3d50528511358bc4094f336027ca5f00cdd0e13059", + "checksum": "95f29c975e5850307620c21a68df174b90ecaafd5b1e36659bb3c34d0027b5df", "checksum_algorithm": "sha256" }, { "name": "concise", "file": "skills/concise/SKILL.md", "version": "20260421008", - "checksum": "2f92b948f975a5d418d0ec8204a55faf20a5f2576dee426d7f96e954501aa7ff", + "checksum": "9555de7da6a925e76992b6f5e51ddccd6b4de3887ea31ed2ceb9113c9fe9bc7e", "checksum_algorithm": "sha256" }, { "name": "consult", "file": "skills/consult/SKILL.md", "version": "20260421009", - "checksum": "b760c5df32f6922dfc96d9ed7ed73715ac10b58d1570879476f9cf75c2a2bbd7", + "checksum": "a340883900309261213ec0ea0d4e9e5b72ca83fb25c7cd6c3506e1f95f40cf76", "checksum_algorithm": "sha256" }, { "name": "container", "file": "skills/container/SKILL.md", "version": "20260421010", - "checksum": "9493acae27f345fe49553db6aa91b24a4575c4c31525989a79e3e70bcbfd9dff", + "checksum": "f0a4408a756195faca9a58dca76e56f950df227415d77af293bf55280191abd4", "checksum_algorithm": "sha256" }, { "name": "conventional-commit", "file": "skills/conventional-commit/SKILL.md", "version": "20260502024", - "checksum": "401ea58fe12834d7786ce25eb142886020516eea2af8bf29c2a1cabcacb8e805", + "checksum": "8f1ce569106b5210a8d4ffe5bc9123eb0dec6b24ff56cf680922eccdd9891012", "checksum_algorithm": "sha256" }, { "name": "debug", "file": "skills/debug/SKILL.md", "version": "20260421011", - "checksum": "3b62b8faf8c7c6316c915cff3bac03d363c0347b8ab6fbfe07cb23d0b266d58f", + "checksum": "b4c1c6de7142b7b74326d1c19a5cc27f3957d1faa48aa48bd6b1fc45832edf3b", "checksum_algorithm": "sha256" }, { "name": "dependabot", "file": "skills/dependabot/SKILL.md", "version": "20260502027", - "checksum": "509d421bae968bd6ee4364108a5cdb38bc4db3e931007db9b8f9faac218d1877", + "checksum": "fa3f595740faa0b8093ffe4f32fb8e4b85f5834ea10817752ee9c94be8f38c2d", "checksum_algorithm": "sha256" }, { "name": "dependency", "file": "skills/dependency/SKILL.md", "version": "20260421012", - "checksum": "b70c7b3005052ae09d95763a17d1116f1353c5bdfbcef92f200822724123fc71", + "checksum": "2cbbecca8e6697b8523301325a889a5ca88c520f6d35a3b98807b6c3449239f5", "checksum_algorithm": "sha256" }, { "name": "design", "file": "skills/design/SKILL.md", "version": "20260421013", - "checksum": "26d0130b03dd7710462763d015559d4df898682236a7619df999a3e338a90efa", + "checksum": "2d56e34aca8d3cf5a3f327e3b69ab15707393ffc47607c91ec91dd5be86cd65b", "checksum_algorithm": "sha256" }, { "name": "docs", "file": "skills/docs/SKILL.md", "version": "20260421014", - "checksum": "7c13ddd443a33a854ce7f02fba9e50d6bfb60ecdd09e58edea4580d821d64cc6", + "checksum": "d6ebf7d270eac7911aea09a34cb7ec4006c70064b7e6f58dfa84a405e6620d18", "checksum_algorithm": "sha256" }, { "name": "explore", "file": "skills/explore/SKILL.md", "version": "20260421015", - "checksum": "699c3a1db16163e887c3e377f9618de8341cf77997bae6df4ad95f260be229ba", + "checksum": "914323f796ac95a0f73d404f1b7eb38d94ba8950ed1a4acc5d80600ba03fae8f", "checksum_algorithm": "sha256" }, { "name": "gdpr", "file": "skills/gdpr/SKILL.md", "version": "20260502029", - "checksum": "796687e2877fb9af481fe4935ef185e93a17b179aa435bc696924bf91a9082d2", + "checksum": "dfe60c4c59b7ae4dc66e98a885ec42b0e60eccc61fa1cb6fd269cd7eb3e0a94d", "checksum_algorithm": "sha256" }, { "name": "gh-issues", "file": "skills/gh-issues/SKILL.md", "version": "20260502025", - "checksum": "69e9f37144f418819028387f9769581b270c8797992df8fc09cf32db272249f6", + "checksum": "bc1f595283e7643de63c7519e994500b9434a0343154bfc404b842ce8e7028a8", "checksum_algorithm": "sha256" }, { "name": "gh-release", "file": "skills/gh-release/SKILL.md", "version": "20260502023", - "checksum": "2c47e28a986239626a252d6d435e7a13d8a1d39284f14f7965336adee1ad7f00", + "checksum": "10abe52f681d261588608418a546580e7798e883179888061ed6d2285050d870", "checksum_algorithm": "sha256" }, { "name": "guardrails", "file": "skills/guardrails/SKILL.md", "version": "20260421016", - "checksum": "49bda4a6d23dbd3e272e034baa85daea3adbac226782a3921f0716b05c98cf0e", + "checksum": "1c1a44c95461eb4eeef7ad64b5846a1efbb202cf1b75186421c5d0b8f9fd03f1", "checksum_algorithm": "sha256" }, { "name": "helm", "file": "skills/helm/SKILL.md", "version": "20260502037", - "checksum": "bd9057d8af61b4f1dacc637bfff80475a1ab594080dd88535f6d3f2c4a8258b5", + "checksum": "6e6579ede2dadb6dbe895d07c362d208e62414338445bd035f47bd1404be501b", "checksum_algorithm": "sha256" }, { "name": "incident", "file": "skills/incident/SKILL.md", "version": "20260503002", - "checksum": "891fb9afe19b86010cbb8a810b47bd148c0f0df531d302e2b6d1ba91ae67d747", + "checksum": "546229fe91121dae2192aceb0b215096cc8cf5ba0cfb3d1300e30c73ff3f6276", "checksum_algorithm": "sha256" }, { "name": "inspect", "file": "skills/inspect/SKILL.md", "version": "20260421018", - "checksum": "e4fd6f8cbc837ac052cffa487f9cce14d8c0990e3f6285c6913ce44244dca6a8", + "checksum": "3a4aee3620bbd669602001c041cf81788da5fac9dc055a9f1b0c155e50e88b46", "checksum_algorithm": "sha256" }, { "name": "k8s", "file": "skills/k8s/SKILL.md", "version": "20260502036", - "checksum": "f5319450a41d97e90a85f7eabd4fcabacef8288664fb5d5c884addc4892676a9", + "checksum": "2bfbe14c187809211a7296578644262e2e47c89b4a2fc28fc3a30a79fdaeb580", "checksum_algorithm": "sha256" }, { "name": "migrate", "file": "skills/migrate/SKILL.md", "version": "20260421019", - "checksum": "f29f99ec9a5d324bb2d1628b8add0fac57cda66471633a1c23c66a46fdee02c6", + "checksum": "eecc00aae2c58c573000a434fbf7ae891a3e85dcc99a42fec0e8bcadc3047b7d", "checksum_algorithm": "sha256" }, { "name": "onboard", "file": "skills/onboard/SKILL.md", "version": "20260421020", - "checksum": "b2c4ac2679a31515d0ea6a1161076aeea19ef5b41f058f93471e077d098e9412", + "checksum": "f16f2d78002e3be8e7d1d9e2bf376c1b37eb181d821f3cd113e0d5aecf5236ae", "checksum_algorithm": "sha256" }, { "name": "openapi", "file": "skills/openapi/SKILL.md", "version": "20260421021", - "checksum": "e7fda43ee964e2e31efd7f82dd7b11a28679fbe9f20b9a8faf6975719e518a68", + "checksum": "a3dbcded8e1a8b268a1976b9185f41de74ad0ad6e7aae98c399d6135de920529", "checksum_algorithm": "sha256" }, { "name": "performance", "file": "skills/performance/SKILL.md", "version": "20260421022", - "checksum": "9c27e5eda062cbd881d2073f0de4dac41b971adb024f1e1e0f28dd44393e8784", + "checksum": "fa4062c77381facacedac289b54a3ce7e6cf242dfa2b36f55401aebfe5fb8ae1", "checksum_algorithm": "sha256" }, { "name": "postmortem", "file": "skills/postmortem/SKILL.md", "version": "20260503001", - "checksum": "9197bed5f5bebb64988c639e4feb5ed264c72738d176875a0ce5d063b1e1d3aa", + "checksum": "fc70f26420e705e0979ff76476ea8176386258f2315fcb57f5ac9049a37f6c90", "checksum_algorithm": "sha256" }, { "name": "pr", "file": "skills/pr/SKILL.md", "version": "20260502013", - "checksum": "9bcb8180cc74e8fa956818f3b5d27112fd73c289d966e8c2bb0a53109b9274ed", + "checksum": "fc7b2b08696982afd48033a3cc29b9fa4ac8b590fc61c828b84135585c88b345", "checksum_algorithm": "sha256" }, { "name": "rancher", "file": "skills/rancher/SKILL.md", "version": "20260502038", - "checksum": "26c67ca9a32e778f4b574fab91b33d00b425bc9a7ceb7177fb1558ebe1222951", + "checksum": "3000f24c2ee2f99fdb363f30d6075993ed26533663d081089b7800098a28e648", "checksum_algorithm": "sha256" }, { "name": "rca", "file": "skills/rca/SKILL.md", "version": "20260503001", - "checksum": "f7e6a0fc4950136bf0165ba1051685c5b297779ea97aa76f1ca0631f593401b1", + "checksum": "8bebb57c166c153d89e4589097ea419266b249df871a79e24c840112b5f8ad0c", "checksum_algorithm": "sha256" }, { "name": "refactor", "file": "skills/refactor/SKILL.md", "version": "20260421023", - "checksum": "69b28c4a57134f29c6c59366b06ea7cf3468cc0db219d120567657abb0765801", + "checksum": "aa7fb995b6ac649b24b61066f15a6fe7fddab81bdcec8af27eba65092b957d68", "checksum_algorithm": "sha256" }, { "name": "release-notes", "file": "skills/release-notes/SKILL.md", "version": "20260502014", - "checksum": "9b11a8bfc973ce1faf139cd90190e187955ee2334f7694049e39922f2e5d71c7", + "checksum": "589f001bcbf0e27c4da6ff9c530371a1f183b7936e3a7fc421879282bd7f9af3", "checksum_algorithm": "sha256" }, { "name": "requirements", "file": "skills/requirements/SKILL.md", "version": "20260421024", - "checksum": "0a741ec2a7a899affef84a613d7a97d501feaf14e44355f3633aa199375c7afe", + "checksum": "7d3338f0b7c5d493ab714a90b7d1732de56467503a3fe4dacdd40031bfea356c", "checksum_algorithm": "sha256" }, { "name": "secret-scan", "file": "skills/secret-scan/SKILL.md", "version": "20260502028", - "checksum": "7ee0aadca4d6dac2d8c301ab51105a11587275ef5ed2700d50b786380040d29f", + "checksum": "95df047af89a93a8fa3716899b8fe703b270d7f40b93fe65915f7d1bdb849370", "checksum_algorithm": "sha256" }, { "name": "security", "file": "skills/security/SKILL.md", "version": "20260421025", - "checksum": "ea7fc8189f6187a2dfa99114d1463d31b0afe147c0853406d80f90e15da5fd3c", + "checksum": "e9bcd451ee25b1c752239e76e20ca67302061356b59214fae7b134a180022df8", "checksum_algorithm": "sha256" }, { "name": "terraform", "file": "skills/terraform/SKILL.md", "version": "20260502030", - "checksum": "6666debeef5f59910a0374c811744f3d29c7695a59ee141935c832bcc020c069", + "checksum": "2bb933512f72b61dae3a3b632a7afd43c62aad066de003ee0a565fc8f1895215", "checksum_algorithm": "sha256" }, { "name": "terragrunt", "file": "skills/terragrunt/SKILL.md", "version": "20260502031", - "checksum": "518577f40465f9ef94b90244deb3886206179c32ea533548267fe0b9ebda7190", + "checksum": "e0d3dc0feefeafb9cd41d8d6885d8b4d53baed52ba70df7dc8d33bfc7410bae8", "checksum_algorithm": "sha256" }, { "name": "threat-model", "file": "skills/threat-model/SKILL.md", "version": "20260502021", - "checksum": "a5d4d0f78a020b259337c446ad2a4abed37da7e6cfd70f9bbf3733f9e06f40f5", + "checksum": "7fdf69113167e9bb8c866681a86f4b4d6416145bb9def0ebe18ceba690c8fbe9", "checksum_algorithm": "sha256" }, { "name": "verify", "file": "skills/verify/SKILL.md", "version": "20260421026", - "checksum": "c242872c3542a4aaa54d1ce062e0432df6edde0a8678ac953e4729b5a93fb144", + "checksum": "aa56d1a674ed827606af34b8e7c5bd9c2de61a6cb6f92f5fd9bdce28e46bcb30", "checksum_algorithm": "sha256" }, { "name": "vision", "file": "skills/vision/SKILL.md", "version": "20260421027", - "checksum": "9dce0d40a2d80e8b5f521b0f36761667acbaf9b9a8e53f68f00118d27f8f90a4", + "checksum": "c0058202130905d8495a3e565b5db546ad2d0ff6d654596358a0b12c484ce310", "checksum_algorithm": "sha256" } ], @@ -319,49 +319,49 @@ "name": "architect", "file": "agents/architect.agent.md", "version": "20260503022", - "checksum": "e097b021cee7e3a7bab7d75512a372b5aee073fd3d01ad28c4a7c582b569eebc", + "checksum": "99aa5d66f0d9039f7759c8011ab1a835b7262aebd6a4f7df591898d5102b8b94", "checksum_algorithm": "sha256" }, { "name": "designer", "file": "agents/designer.agent.md", "version": "20260503024", - "checksum": "b7240cc02e1da79f962384ed5ac02ac769211f85c8f5976b8e8251168eb9deb7", + "checksum": "394717ed714328b3c92278a4de33b2bd067ee9ed100529c557bf71f5d777e0fb", "checksum_algorithm": "sha256" }, { "name": "engineer", "file": "agents/engineer.agent.md", "version": "20260503024", - "checksum": "91ce873180353b58f41873a8ec2e4a448cc9f08871b81bc785fd182e0e1d1cde", + "checksum": "2e6de55f6bb292cb750c6403be74db2dbe913db0ac64936a4f066945f88f88d8", "checksum_algorithm": "sha256" }, { "name": "planner", "file": "agents/planner.agent.md", "version": "20260510001", - "checksum": "18402e3cd9653e38943f23cb391147ede02b659b76847856ad678668c89b9f39", + "checksum": "73189a08e16d5e8f444471448bc4f26cf6dbbc290cb072d99fa6a768bfbc9bda", "checksum_algorithm": "sha256" }, { "name": "product", "file": "agents/product.agent.md", "version": "20260503021", - "checksum": "1bf2088c0c1d2750ede7e9c87d6e10dc4016db7f2aacce217be37ec8f4bc58b9", + "checksum": "94c9a9ceaaa626ff20351e1c7f268b4d67464c23d2aae2070ba9385209888c50", "checksum_algorithm": "sha256" }, { "name": "release", "file": "agents/release.agent.md", "version": "20260503020", - "checksum": "823cdefe1ee2aa3b4700da629a83f1efee0c8503cf0d214926d489a5b85720ea", + "checksum": "ea226eb56b001e3906e07ed552867fded99d2555f4c8e4b269619ae27cbb0194", "checksum_algorithm": "sha256" }, { "name": "tester", "file": "agents/tester.agent.md", "version": "20260503026", - "checksum": "2ab7d38e1bc24d530aaa784e8852ddd9f2c68c6d163466d6d608711062403197", + "checksum": "27dd3baeeaad59b9a6a1a5cab0896282771e66278cf02d27820e3740b8793366", "checksum_algorithm": "sha256" } ], @@ -369,35 +369,35 @@ { "name": "post-commit-security-scan", "file": "hooks/post-commit-security-scan.json", - "version": "3.1.1.post2.dev0+4d3419b", + "version": "0.0.0.post3.dev0+df3fe6e", "checksum": "4360b68090aa9c8e7f1cdce31924d5bacbabdc3e0d70cc618894b4049b0c9c33", "checksum_algorithm": "sha256" }, { "name": "post-edit-format", "file": "hooks/post-edit-format.json", - "version": "3.1.1.post2.dev0+4d3419b", + "version": "0.0.0.post3.dev0+df3fe6e", "checksum": "add9f0a051add1c43d6016929c53d0bbde990b381360942cc4584ff0edbf5a5c", "checksum_algorithm": "sha256" }, { "name": "post-edit-markdown-quality", "file": "hooks/post-edit-markdown-quality.json", - "version": "3.1.1.post2.dev0+4d3419b", + "version": "0.0.0.post3.dev0+df3fe6e", "checksum": "5d7dbe986445710e99318586f40ff71385f4fb0c6fe2cb6b86a6f605cd2d4969", "checksum_algorithm": "sha256" }, { "name": "pre-tool-safety-gate", "file": "hooks/pre-tool-safety-gate.json", - "version": "3.1.1.post2.dev0+4d3419b", + "version": "0.0.0.post3.dev0+df3fe6e", "checksum": "b1d92cfcd73dde9a47404159f89d32b1d61944fb786f4455c614e278a09eec4a", "checksum_algorithm": "sha256" }, { "name": "session-audit", "file": "hooks/session-audit.json", - "version": "3.1.1.post2.dev0+4d3419b", + "version": "0.0.0.post3.dev0+df3fe6e", "checksum": "b6a4c2a2fd78eb8a2ef087f25b2603ddf1d6081cff1cd741e572e6c459748965", "checksum_algorithm": "sha256" } @@ -407,84 +407,84 @@ "name": "git", "file": "instructions/git.instructions.md", "version": "20260421001", - "checksum": "b0b866596988dab9e3b48b6cf1cb8de82b46cddfdcfe57214ee85308c2cf0e13", + "checksum": "ed6a191176e32631e2d572cb21278b555f6696a859839579821631156a8b35a5", "checksum_algorithm": "sha256" }, { "name": "helm", "file": "instructions/helm.instructions.md", "version": "20260502040", - "checksum": "b1b5d2e535cf6a30fa86121c3e58081e77149242a16dabdb153291e4349f3015", + "checksum": "a60520853b79751517f089136ba7fb182feb1ea8e9c9495890e5c14ff32afd7b", "checksum_algorithm": "sha256" }, { "name": "java", "file": "instructions/java.instructions.md", "version": "20260502001", - "checksum": "663847977d195426234282780c53660fad2d5b06767e2848ec1afbef379e699a", + "checksum": "69dc2bf3a5428ed03d77c2789871985ea7c5881af89c2b414548fca6e9488464", "checksum_algorithm": "sha256" }, { "name": "k8s", "file": "instructions/k8s.instructions.md", "version": "20260502039", - "checksum": "95a7d12d5518eab2d91a20f5fe84e6f66af52dfb21b8ecb256d6b050144d8f03", + "checksum": "f19f1060bf2a1424950496290a43a3535f9749e4ed838cf7475fe35eb9487d67", "checksum_algorithm": "sha256" }, { "name": "markdown", "file": "instructions/markdown.instructions.md", "version": "20260502002", - "checksum": "901ba40d993be4477057626039a4f4c6c0c6a49561429af5cb4cac016ebe3be2", + "checksum": "59c93c5b0e63360aff18ab3a7a207f4e798efd1f2c26b0a561a4ef4ea688c5c5", "checksum_algorithm": "sha256" }, { "name": "python", "file": "instructions/python.instructions.md", "version": "20260421002", - "checksum": "346e468cbf8249a8be1ae8881e1ac849ca0646e07ce044dc8e0e5947357293b6", + "checksum": "ac40fffd3d3a3f9f8ca43e10ca603a187578c9d517533529a3d647b61cedf56c", "checksum_algorithm": "sha256" }, { "name": "rancher", "file": "instructions/rancher.instructions.md", "version": "20260502041", - "checksum": "b0ea1821e3deac543599a2b838cb50d73cc663cffe95aecf7455279721cab294", + "checksum": "4e643b1e078e9e8f127f1f2cf7707b36109e28b8697e208d8d3804848b867eec", "checksum_algorithm": "sha256" }, { "name": "security", "file": "instructions/security.instructions.md", "version": "20260502003", - "checksum": "a247eb547961349f52032152ed4ff1b9d77d6c268db8251d803a87ab31dd0ca5", + "checksum": "a943df637e44cb23c8e82f8b39bf70558bc502ece5af9a03a15d8492cffb51a4", "checksum_algorithm": "sha256" }, { "name": "terraform", "file": "instructions/terraform.instructions.md", "version": "20260502034", - "checksum": "f0ac9c36bba8053351b1993418b4ae16f2db49b2bddf69e62ee1638f2a649908", + "checksum": "1bada82da46a6359bb1814b6642074be6c0b7de9c2c6ca59e031a4d05ea4ea8c", "checksum_algorithm": "sha256" }, { "name": "terragrunt", "file": "instructions/terragrunt.instructions.md", "version": "20260502035", - "checksum": "37bb85ec3f2ab1c1bcaedd05da7f4df15e28a0ee32f74307ed53ad4733c028a5", + "checksum": "13c0059c366624ab482c2b3d24a353bcb0a2ffbb6bf735abd18796969a3e4020", "checksum_algorithm": "sha256" }, { "name": "testing", "file": "instructions/testing.instructions.md", "version": "20260502004", - "checksum": "11dd196e151c37d59585b77e7e95dddd2c395cf38b210ed2a9b88c83ccd0d756", + "checksum": "cbd1948f367c32c39032209e5ed9fcfff8ce6c46c6324a4aa9a8550365873ca3", "checksum_algorithm": "sha256" }, { "name": "typescript", "file": "instructions/typescript.instructions.md", "version": "20260502005", - "checksum": "8fa6c892d0556be68e324e005bbb681024b7d41dcd89ba26c70874998d1d78bd", + "checksum": "fe412ba2e60baea66d0d07ae0c153fa2a9157bd57556fa7476f46ce466160ad3", "checksum_algorithm": "sha256" } ], @@ -493,49 +493,49 @@ "name": "api-design-review", "file": "prompts/api-design-review.prompt.md", "version": "20260502006", - "checksum": "1cc8cc182c052dd7e5d5c253a9549609f243a184b71d74aa6c092b9270692488", + "checksum": "1ba62a6f78b836256fe578c4ca312de24599c79b018119b81a100f6de4e0da11", "checksum_algorithm": "sha256" }, { "name": "architecture-risk", "file": "prompts/architecture-risk.prompt.md", "version": "20260502007", - "checksum": "4bc2830e1914c2a13cf849a8d67bc3e18355b4a1c24f001feaf88876c7131ba4", + "checksum": "14fa36e36948309827c1c1296ff2c8b1f1306cfb19057ef190a5e60f6cc04a61", "checksum_algorithm": "sha256" }, { "name": "code-review", "file": "prompts/code-review.prompt.md", "version": "20260502008", - "checksum": "02d98a41849a1cbb92c642a917850d9d689e3a4713dbb8b4e4ae5b22fe0fa801", + "checksum": "b6499ca66706a08ced7b9bf69bd288c2fecb684e1c85e333ee91d36ba7e66c3f", "checksum_algorithm": "sha256" }, { "name": "dependency-audit", "file": "prompts/dependency-audit.prompt.md", "version": "20260502009", - "checksum": "22c5d3f7f8ead6d750bf89fe1aa61c4a458fa7de3e78f3956cf363222965c8ac", + "checksum": "51cead168dbbe52b455813cc620a8426ec287ceb2994831aa6ae115b4e49d0b0", "checksum_algorithm": "sha256" }, { "name": "incident-timeline", "file": "prompts/incident-timeline.prompt.md", "version": "20260502010", - "checksum": "45729a09df502e63d0542a03690e9557ba6eb1f88bb50d4898233e749c179b58", + "checksum": "28627dc7c362d510f312281fe0bda30e1927998bae440808af3a92d06c455393", "checksum_algorithm": "sha256" }, { "name": "migration-safety", "file": "prompts/migration-safety.prompt.md", "version": "20260502011", - "checksum": "71f9f7ec5f1d714cd5d80b0582544a7bcc84a26cf5e3325b0846c9879a33fe14", + "checksum": "814258882774ff98724b14caa0bc9ee35dc68f46e72e62b24884052e79ffacfa", "checksum_algorithm": "sha256" }, { "name": "release-readiness", "file": "prompts/release-readiness.prompt.md", "version": "20260502012", - "checksum": "ee729edd2e79b1c44ced2ca085422c0e3b170201c3dae6a67d14a6b1c861f87a", + "checksum": "49898a07169504acba1c2195abf730be77d1e17298cb4676f3ed926a27e31dc6", "checksum_algorithm": "sha256" } ] From e33099d3eb0aeaa9736e8cff8b96ac626d38d506 Mon Sep 17 00:00:00 2001 From: Erik Schaareman <erik@schaareman.org> Date: Mon, 11 May 2026 20:03:58 +0200 Subject: [PATCH 07/11] chore(gitignore): drop legacy tmp-test-home ignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index ae80d4b..f0608bd 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ env/ .pytest_cache/ .hypothesis/ .nox/ -.tmp-test-home/ .coverage .coverage.* coverage.xml From 67d5b17cfb6fe4e004b003812bad393fd637c965 Mon Sep 17 00:00:00 2001 From: Erik Schaareman <erik@schaareman.org> Date: Mon, 11 May 2026 23:27:24 +0200 Subject: [PATCH 08/11] fix(hooks): decouple retention and harden ci automation --- .github/agents/architect.agent.md | 2 +- .github/agents/designer.agent.md | 2 +- .github/agents/engineer.agent.md | 2 +- .github/agents/planner.agent.md | 2 +- .github/agents/product.agent.md | 2 +- .github/agents/release.agent.md | 2 +- .github/agents/tester.agent.md | 2 +- .github/hooks/log-retention-cleanup.json | 15 + .github/hooks/post-commit-security-scan.json | 18 +- .github/hooks/post-edit-format.json | 8 +- .github/hooks/post-edit-markdown-quality.json | 8 +- .github/hooks/pre-tool-safety-gate.json | 12 +- .github/hooks/session-audit.json | 40 +- .github/instructions/git.instructions.md | 2 +- .github/instructions/helm.instructions.md | 2 +- .github/instructions/java.instructions.md | 2 +- .github/instructions/k8s.instructions.md | 2 +- .github/instructions/markdown.instructions.md | 2 +- .github/instructions/python.instructions.md | 2 +- .github/instructions/rancher.instructions.md | 2 +- .github/instructions/security.instructions.md | 2 +- .../instructions/terraform.instructions.md | 2 +- .../instructions/terragrunt.instructions.md | 2 +- .github/instructions/testing.instructions.md | 2 +- .../instructions/typescript.instructions.md | 2 +- .github/prompts/api-design-review.prompt.md | 2 +- .github/prompts/architecture-risk.prompt.md | 2 +- .github/prompts/code-review.prompt.md | 2 +- .github/prompts/dependency-audit.prompt.md | 2 +- .github/prompts/incident-timeline.prompt.md | 2 +- .github/prompts/migration-safety.prompt.md | 2 +- .github/prompts/release-readiness.prompt.md | 2 +- .github/skills/adr/SKILL.md | 2 +- .github/skills/analyse/SKILL.md | 2 +- .github/skills/architecture/SKILL.md | 2 +- .github/skills/aws-cli/SKILL.md | 2 +- .github/skills/cicd/SKILL.md | 2 +- .github/skills/cloudformation/SKILL.md | 2 +- .github/skills/code-review/SKILL.md | 2 +- .github/skills/codeql/SKILL.md | 2 +- .github/skills/concise/SKILL.md | 2 +- .github/skills/consult/SKILL.md | 2 +- .github/skills/container/SKILL.md | 2 +- .github/skills/conventional-commit/SKILL.md | 2 +- .github/skills/debug/SKILL.md | 2 +- .github/skills/dependabot/SKILL.md | 2 +- .github/skills/dependency/SKILL.md | 2 +- .github/skills/design/SKILL.md | 2 +- .github/skills/docs/SKILL.md | 2 +- .github/skills/explore/SKILL.md | 2 +- .github/skills/gdpr/SKILL.md | 2 +- .github/skills/gh-issues/SKILL.md | 2 +- .github/skills/gh-release/SKILL.md | 2 +- .github/skills/guardrails/SKILL.md | 2 +- .github/skills/helm/SKILL.md | 2 +- .github/skills/incident/SKILL.md | 2 +- .github/skills/inspect/SKILL.md | 2 +- .github/skills/k8s/SKILL.md | 2 +- .github/skills/migrate/SKILL.md | 2 +- .github/skills/onboard/SKILL.md | 2 +- .github/skills/openapi/SKILL.md | 2 +- .github/skills/performance/SKILL.md | 2 +- .github/skills/postmortem/SKILL.md | 2 +- .github/skills/pr/SKILL.md | 2 +- .github/skills/rancher/SKILL.md | 2 +- .github/skills/rca/SKILL.md | 2 +- .github/skills/refactor/SKILL.md | 2 +- .github/skills/release-notes/SKILL.md | 2 +- .github/skills/requirements/SKILL.md | 2 +- .github/skills/secret-scan/SKILL.md | 2 +- .github/skills/security/SKILL.md | 2 +- .github/skills/terraform/SKILL.md | 2 +- .github/skills/terragrunt/SKILL.md | 2 +- .github/skills/threat-model/SKILL.md | 2 +- .github/skills/verify/SKILL.md | 2 +- .github/skills/vision/SKILL.md | 2 +- .github/workflows/README.md | 3 +- .github/workflows/automerge.yml | 29 +- .github/workflows/codeql.yml | 38 ++ .vstack/config.yaml | 9 + .vstack/vstack.json | 171 +++++---- README.md | 21 +- docs/design/cicd.md | 18 +- docs/design/hook-yaml-schema.md | 13 +- docs/design/hooks.md | 19 +- docs/design/workflow.md | 1 + docs/product/roadmap.md | 11 +- .../hooks/log-retention-cleanup/hook.yaml | 108 ++++++ .../hooks/post-commit-security-scan/hook.yaml | 46 +-- .../hooks/post-edit-format/hook.yaml | 29 +- .../post-edit-markdown-quality/hook.yaml | 29 +- .../hooks/pre-tool-safety-gate/hook.yaml | 56 ++- .../_templates/hooks/session-audit/hook.yaml | 224 ++++++++++- .../_templates/project/.vstack/config.yaml | 62 +++ src/vstack/cli/constants.py | 1 + src/vstack/cli/interface.py | 120 +++++- src/vstack/cli/service.py | 12 + src/vstack/hooks/generator.py | 115 +++++- tests/vstack/cli/test_constants.py | 12 + tests/vstack/cli/test_interface.py | 363 +++++++++++++++++- tests/vstack/hooks/test_generator.py | 105 +++++ 101 files changed, 1524 insertions(+), 332 deletions(-) create mode 100644 .github/hooks/log-retention-cleanup.json create mode 100644 .github/workflows/codeql.yml create mode 100644 src/vstack/_templates/hooks/log-retention-cleanup/hook.yaml diff --git a/.github/agents/architect.agent.md b/.github/agents/architect.agent.md index 0365edd..a7de8fd 100644 --- a/.github/agents/architect.agent.md +++ b/.github/agents/architect.agent.md @@ -180,4 +180,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gdpr` — privacy by design and data processing architecture review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architect","artifact_type":"agent","artifact_version":"20260503022","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"architect","artifact_type":"agent","artifact_version":"20260503022","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/designer.agent.md b/.github/agents/designer.agent.md index f4c4041..d77a994 100644 --- a/.github/agents/designer.agent.md +++ b/.github/agents/designer.agent.md @@ -193,4 +193,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#openapi` — OpenAPI 3.1 spec writing and review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"designer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"designer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/engineer.agent.md b/.github/agents/engineer.agent.md index ebf2eee..b15ee02 100644 --- a/.github/agents/engineer.agent.md +++ b/.github/agents/engineer.agent.md @@ -202,4 +202,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#rancher` — Rancher and Fleet multi-cluster operations and governance <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"engineer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"engineer","artifact_type":"agent","artifact_version":"20260503024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/planner.agent.md b/.github/agents/planner.agent.md index 2482759..e3ee998 100644 --- a/.github/agents/planner.agent.md +++ b/.github/agents/planner.agent.md @@ -135,4 +135,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#analyse` - assess stage impact, skip rationale, and trade-offs <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"planner","artifact_type":"agent","artifact_version":"20260510001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"planner","artifact_type":"agent","artifact_version":"20260510001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/product.agent.md b/.github/agents/product.agent.md index 2aeff6a..9a713da 100644 --- a/.github/agents/product.agent.md +++ b/.github/agents/product.agent.md @@ -164,4 +164,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gh-issues` — create and manage GitHub Issues for requirements, tasks, and user stories <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"product","artifact_type":"agent","artifact_version":"20260503021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"product","artifact_type":"agent","artifact_version":"20260503021","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/release.agent.md b/.github/agents/release.agent.md index bf600b5..836403d 100644 --- a/.github/agents/release.agent.md +++ b/.github/agents/release.agent.md @@ -154,4 +154,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#gh-issues` — create and manage GitHub Issues for tracking work and bug reports <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release","artifact_type":"agent","artifact_version":"20260503020","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"release","artifact_type":"agent","artifact_version":"20260503020","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/agents/tester.agent.md b/.github/agents/tester.agent.md index b77fcda..aa3e888 100644 --- a/.github/agents/tester.agent.md +++ b/.github/agents/tester.agent.md @@ -180,4 +180,4 @@ that requires changes to upstream items, flag it and trigger a reverse handoff. - `@#rancher` — Rancher/Fleet configuration and multi-cluster governance review <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"tester","artifact_type":"agent","artifact_version":"20260503026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"tester","artifact_type":"agent","artifact_version":"20260503026","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/hooks/log-retention-cleanup.json b/.github/hooks/log-retention-cleanup.json new file mode 100644 index 0000000..cceaea6 --- /dev/null +++ b/.github/hooks/log-retention-cleanup.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "hooks": { + "sessionStart": [ + { + "type": "command", + "description": "Prune dated log directories older than the configured retention window.\n", + "bash": "retention_days=\"${VSTACK_HOOKS_LOG_RETENTION_DAYS:-7}\"\ncleanup_debug=\"${VSTACK_HOOKS_CLEANUP_LOG:-0}\"\nlog_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nmkdir -p \"$log_root\"\ndeleted_count=0\noldest_deleted_date=\"\"\ncleanup_error=\"\"\nif [ \"$retention_days\" -gt 0 ] 2>/dev/null; then\n scan_file=\"${TMPDIR:-/tmp}/vstack-hook-retention-$$.list\"\n if find \"$log_root\" -mindepth 1 -maxdepth 1 -type d -mtime +\"$retention_days\" -print0 > \"$scan_file\" 2>/dev/null; then\n while IFS= read -r -d '' dir; do\n dir_name=\"$(basename \"$dir\")\"\n if [[ \"$dir_name\" =~ ^[0-9]{8}$ ]]; then\n if rm -rf \"$dir\"; then\n deleted_count=$((deleted_count + 1))\n if [ -z \"$oldest_deleted_date\" ] || [ \"$dir_name\" -lt \"$oldest_deleted_date\" ]; then\n oldest_deleted_date=\"$dir_name\"\n fi\n else\n cleanup_error=\"delete-failed\"\n fi\n fi\n done < \"$scan_file\"\n else\n cleanup_error=\"find-failed\"\n fi\n rm -f \"$scan_file\"\nelse\n cleanup_error=\"invalid-retention-days\"\nfi\n\nif [ \"$cleanup_debug\" = \"1\" ] || [ \"$deleted_count\" -gt 0 ] || [ -n \"$cleanup_error\" ]; then\n log_name=\"hook-retention-cleanup.log\"\n log_dir=\"$log_root/$(date -u +%Y%m%d)\"\n mkdir -p \"$log_dir\"\n log_path=\"$log_dir/$log_name\"\n ts=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n printf '%s\\n' \"{\\\"ts\\\":\\\"$ts\\\",\\\"event\\\":\\\"retention-cleanup\\\",\\\"retention_days\\\":$retention_days,\\\"deleted_count\\\":$deleted_count,\\\"oldest_deleted_date\\\":\\\"$oldest_deleted_date\\\",\\\"error\\\":\\\"$cleanup_error\\\"}\" >> \"$log_path\"\nfi\n", + "powershell": "$retentionDays = if ($env:VSTACK_HOOKS_LOG_RETENTION_DAYS) { [int]$env:VSTACK_HOOKS_LOG_RETENTION_DAYS } else { '7' }\n$cleanupDebug = if ($env:VSTACK_HOOKS_CLEANUP_LOG) { $env:VSTACK_HOOKS_CLEANUP_LOG } else { '0' }\n$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\nNew-Item -ItemType Directory -Force -Path $logRoot | Out-Null\n$deletedCount = 0\n$oldestDeletedDate = ''\n$cleanupError = ''\nif ($retentionDays -gt 0) {\n $threshold = (Get-Date).ToUniversalTime().Date.AddDays(-$retentionDays)\n Get-ChildItem $logRoot -Directory -ErrorAction SilentlyContinue |\n Where-Object {\n $_.Name -match '^\\d{8}$' -and\n ([datetime]::ParseExact($_.Name, 'yyyyMMdd', [System.Globalization.CultureInfo]::InvariantCulture).Date -lt $threshold)\n } |\n ForEach-Object {\n try {\n Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction Stop\n $deletedCount++\n if (-not $oldestDeletedDate -or $_.Name -lt $oldestDeletedDate) {\n $oldestDeletedDate = $_.Name\n }\n } catch {\n $cleanupError = 'delete-failed'\n }\n }\n} else {\n $cleanupError = 'invalid-retention-days'\n}\n\nif ($cleanupDebug -eq '1' -or $deletedCount -gt 0 -or $cleanupError) {\n $logName = 'hook-retention-cleanup.log'\n $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\n New-Item -ItemType Directory -Force -Path $logDir | Out-Null\n $logPath = Join-Path $logDir $logName\n $entry = @{\n ts = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')\n event = 'retention-cleanup'\n retention_days = $retentionDays\n deleted_count = $deletedCount\n oldest_deleted_date = $oldestDeletedDate\n error = $cleanupError\n } | ConvertTo-Json -Compress\n Add-Content -Path $logPath -Value $entry\n}\n", + "cwd": ".", + "timeoutSec": 10 + } + ] + } +} diff --git a/.github/hooks/post-commit-security-scan.json b/.github/hooks/post-commit-security-scan.json index 65abc89..2ad0678 100644 --- a/.github/hooks/post-commit-security-scan.json +++ b/.github/hooks/post-commit-security-scan.json @@ -4,22 +4,12 @@ "postToolUse": [ { "type": "command", - "description": "Check staged diffs for secrets and log alerts to audit trail.\n", - "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-post-commit.jsonl\nif ! printf '%s' \"$input\" | grep -Eiq 'git (commit|push|merge|rebase)'; then\n exit 0\nfi\nif git rev-parse --git-dir >/dev/null 2>&1 && \\\n git diff --cached 2>/dev/null | grep -Eiq 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----'; then\n printf '%s\\n' 'possible-secrets-detected-in-staged-diff' >> .vstack/logs/hook-security-alerts.log\nfi\nif [ \"${VSTACK_HOOKS_MODE:-audit}\" = \"enforce\" ]; then\n if command -v gitleaks >/dev/null 2>&1; then\n gitleaks dir . --no-banner --redact >/dev/null 2>&1 || printf '%s\\n' 'gitleaks-reported-findings' >> .vstack/logs/hook-security-alerts.log\n else\n printf '%s\\n' 'gitleaks-not-installed' >> .vstack/logs/hook-security-alerts.log\n fi\nfi\n", - "powershell": "$inputText = [Console]::In.ReadToEnd()\nNew-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\nAdd-Content -Path .vstack/logs/hook-post-commit.jsonl -Value $inputText\nif ($inputText -notmatch 'git (commit|push|merge|rebase)') {\n exit 0\n}\nif ((git rev-parse --git-dir 2>$null) -and (git diff --cached | Select-String -Pattern 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----' -Quiet)) {\n Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'possible-secrets-detected-in-staged-diff'\n}\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -eq 'enforce') {\n if (Get-Command gitleaks -ErrorAction SilentlyContinue) {\n gitleaks dir . --no-banner --redact *> $null\n if ($LASTEXITCODE -ne 0) {\n Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'gitleaks-reported-findings'\n }\n } else {\n Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'gitleaks-not-installed'\n }\n}\n", + "description": "Check staged diffs for secrets and log only security alerts.\n", + "bash": "input=\"$(cat)\"\nlog_name=\"${VSTACK_HOOK_LOG_NAME:-hook-security-alerts.log}\"\nlog_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nlog_dir=\"$log_root/$(date -u +%Y%m%d)\"\nmkdir -p \"$log_dir\"\nlog_path=\"$log_dir/$log_name\"\nif ! printf '%s' \"$input\" | grep -Eiq 'git (commit|push|merge|rebase)'; then\n exit 0\nfi\nif git rev-parse --git-dir >/dev/null 2>&1 && \\\n git diff --cached 2>/dev/null | grep -Eiq 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----'; then\n printf '%s\\n' 'possible-secrets-detected-in-staged-diff' >> \"$log_path\"\nfi\nif [ \"${VSTACK_HOOKS_MODE:-audit}\" = \"enforce\" ]; then\n if command -v gitleaks >/dev/null 2>&1; then\n gitleaks dir . --no-banner --redact >/dev/null 2>&1 || printf '%s\\n' 'gitleaks-reported-findings' >> \"$log_path\"\n else\n printf '%s\\n' 'gitleaks-not-installed' >> \"$log_path\"\n fi\nfi\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\n$logName = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { 'hook-security-alerts.log' }\n$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\n$logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\nNew-Item -ItemType Directory -Force -Path $logDir | Out-Null\n$logPath = Join-Path $logDir $logName\nif ($inputText -notmatch 'git (commit|push|merge|rebase)') {\n exit 0\n}\nif ((git rev-parse --git-dir 2>$null) -and (git diff --cached | Select-String -Pattern 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----' -Quiet)) {\n Add-Content -Path $logPath -Value 'possible-secrets-detected-in-staged-diff'\n}\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -eq 'enforce') {\n if (Get-Command gitleaks -ErrorAction SilentlyContinue) {\n gitleaks dir . --no-banner --redact *> $null\n if ($LASTEXITCODE -ne 0) {\n Add-Content -Path $logPath -Value 'gitleaks-reported-findings'\n }\n } else {\n Add-Content -Path $logPath -Value 'gitleaks-not-installed'\n }\n}\n", "cwd": ".", "timeoutSec": 60 } - ], - "sessionEnd": [ - { - "type": "command", - "description": "Log session end for security scanning completeness.\n", - "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-session-end-security.jsonl\n", - "powershell": "New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\n$inputText = [Console]::In.ReadToEnd()\nAdd-Content -Path .vstack/logs/hook-session-end-security.jsonl -Value $inputText\n", - "cwd": ".", - "timeoutSec": 10 - } ] } -} \ No newline at end of file +} diff --git a/.github/hooks/post-edit-format.json b/.github/hooks/post-edit-format.json index 6dd4bbb..fd9c394 100644 --- a/.github/hooks/post-edit-format.json +++ b/.github/hooks/post-edit-format.json @@ -4,12 +4,12 @@ "postToolUse": [ { "type": "command", - "description": "Log edit events and optionally run format in enforce mode.\n", - "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-post-edit.jsonl\nmode=\"${VSTACK_HOOKS_MODE:-audit}\"\nif [ \"$mode\" != \"enforce\" ]; then\n exit 0\nfi\nif printf '%s' \"$input\" | grep -Eq '\"toolName\"[[:space:]]*:[[:space:]]*\"(edit|create|write|replace)\"'; then\n if [ -f Makefile ] && command -v make >/dev/null 2>&1; then\n make format >/dev/null 2>&1 || printf '%s\\n' 'format-run-failed' >> .vstack/logs/hook-quality-alerts.log\n else\n printf '%s\\n' 'format-skipped-missing-make-or-makefile' >> .vstack/logs/hook-quality-alerts.log\n fi\nfi\n", - "powershell": "$inputText = [Console]::In.ReadToEnd()\nNew-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\nAdd-Content -Path .vstack/logs/hook-post-edit.jsonl -Value $inputText\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -ne 'enforce') {\n exit 0\n}\ntry {\n $payload = $inputText | ConvertFrom-Json -ErrorAction Stop\n} catch {\n exit 0\n}\nif ($payload.toolName -match '^(edit|create|write|replace)$') {\n if ((Test-Path Makefile) -and (Get-Command make -ErrorAction SilentlyContinue)) {\n make format *> $null\n if ($LASTEXITCODE -ne 0) {\n Add-Content -Path .vstack/logs/hook-quality-alerts.log -Value 'format-run-failed'\n }\n } else {\n Add-Content -Path .vstack/logs/hook-quality-alerts.log -Value 'format-skipped-missing-make-or-makefile'\n }\n}\n", + "description": "Optionally run format in enforce mode; write alerts only on failures.\n", + "bash": "input=\"$(cat)\"\nlog_name=\"${VSTACK_HOOK_LOG_NAME:-hook-quality-alerts.log}\"\nlog_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nlog_dir=\"$log_root/$(date -u +%Y%m%d)\"\nmkdir -p \"$log_dir\"\nlog_path=\"$log_dir/$log_name\"\nmode=\"${VSTACK_HOOKS_MODE:-audit}\"\nif [ \"$mode\" != \"enforce\" ]; then\n exit 0\nfi\nif printf '%s' \"$input\" | grep -Eq '\"toolName\"[[:space:]]*:[[:space:]]*\"(edit|create|write|replace)\"'; then\n if [ -f Makefile ] && command -v make >/dev/null 2>&1; then\n make format >/dev/null 2>&1 || printf '%s\\n' 'format-run-failed' >> \"$log_path\"\n else\n printf '%s\\n' 'format-skipped-missing-make-or-makefile' >> \"$log_path\"\n fi\nfi\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\n$logName = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { 'hook-quality-alerts.log' }\n$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\n$logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\nNew-Item -ItemType Directory -Force -Path $logDir | Out-Null\n$logPath = Join-Path $logDir $logName\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -ne 'enforce') {\n exit 0\n}\ntry {\n $payload = $inputText | ConvertFrom-Json -ErrorAction Stop\n} catch {\n exit 0\n}\nif ($payload.toolName -match '^(edit|create|write|replace)$') {\n if ((Test-Path Makefile) -and (Get-Command make -ErrorAction SilentlyContinue)) {\n make format *> $null\n if ($LASTEXITCODE -ne 0) {\n Add-Content -Path $logPath -Value 'format-run-failed'\n }\n } else {\n Add-Content -Path $logPath -Value 'format-skipped-missing-make-or-makefile'\n }\n}\n", "cwd": ".", "timeoutSec": 30 } ] } -} \ No newline at end of file +} diff --git a/.github/hooks/post-edit-markdown-quality.json b/.github/hooks/post-edit-markdown-quality.json index 75a1e2b..d20fba2 100644 --- a/.github/hooks/post-edit-markdown-quality.json +++ b/.github/hooks/post-edit-markdown-quality.json @@ -4,12 +4,12 @@ "postToolUse": [ { "type": "command", - "description": "Log markdown-oriented edit events and optionally run markdown formatting.\n", - "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-markdown-quality.jsonl\nmode=\"${VSTACK_HOOKS_MODE:-audit}\"\nif [ \"$mode\" != \"enforce\" ]; then\n exit 0\nfi\nif ! printf '%s' \"$input\" | grep -Eiq '\\.md|docs/|\\.github/|\\.vstack/templates/|adr/|prompt\\.md|instructions\\.md|SKILL\\.md'; then\n exit 0\nfi\nif [ -f Makefile ] && command -v make >/dev/null 2>&1; then\n make markdown-format >/dev/null 2>&1 || \\\n make format >/dev/null 2>&1 || \\\n printf '%s\\n' 'markdown-format-run-failed' >> .vstack/logs/hook-markdown-quality-alerts.log\nelse\n printf '%s\\n' 'markdown-format-skipped-missing-make-or-makefile' >> .vstack/logs/hook-markdown-quality-alerts.log\nfi\n", - "powershell": "$inputText = [Console]::In.ReadToEnd()\nNew-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\nAdd-Content -Path .vstack/logs/hook-markdown-quality.jsonl -Value $inputText\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -ne 'enforce') {\n exit 0\n}\nif ($inputText -notmatch '\\.md|docs/|\\.github/|\\.vstack/templates/|adr/|prompt\\.md|instructions\\.md|SKILL\\.md') {\n exit 0\n}\nif ((Test-Path Makefile) -and (Get-Command make -ErrorAction SilentlyContinue)) {\n make markdown-format *> $null\n if ($LASTEXITCODE -ne 0) {\n make format *> $null\n }\n if ($LASTEXITCODE -ne 0) {\n Add-Content -Path .vstack/logs/hook-markdown-quality-alerts.log -Value 'markdown-format-run-failed'\n }\n} else {\n Add-Content -Path .vstack/logs/hook-markdown-quality-alerts.log -Value 'markdown-format-skipped-missing-make-or-makefile'\n}\n", + "description": "Run markdown formatting in enforce mode and log only failures/skips.\n", + "bash": "input=\"$(cat)\"\nlog_name=\"${VSTACK_HOOK_LOG_NAME:-hook-markdown-quality-alerts.log}\"\nlog_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nlog_dir=\"$log_root/$(date -u +%Y%m%d)\"\nmkdir -p \"$log_dir\"\nlog_path=\"$log_dir/$log_name\"\nmode=\"${VSTACK_HOOKS_MODE:-audit}\"\nif [ \"$mode\" != \"enforce\" ]; then\n exit 0\nfi\nif ! printf '%s' \"$input\" | grep -Eiq '\\.md|docs/|\\.github/|\\.vstack/templates/|adr/|prompt\\.md|instructions\\.md|SKILL\\.md'; then\n exit 0\nfi\nif [ -f Makefile ] && command -v make >/dev/null 2>&1; then\n make markdown-format >/dev/null 2>&1 || \\\n make format >/dev/null 2>&1 || \\\n printf '%s\\n' 'markdown-format-run-failed' >> \"$log_path\"\nelse\n printf '%s\\n' 'markdown-format-skipped-missing-make-or-makefile' >> \"$log_path\"\nfi\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\n$logName = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { 'hook-markdown-quality-alerts.log' }\n$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\n$logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\nNew-Item -ItemType Directory -Force -Path $logDir | Out-Null\n$logPath = Join-Path $logDir $logName\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -ne 'enforce') {\n exit 0\n}\nif ($inputText -notmatch '\\.md|docs/|\\.github/|\\.vstack/templates/|adr/|prompt\\.md|instructions\\.md|SKILL\\.md') {\n exit 0\n}\nif ((Test-Path Makefile) -and (Get-Command make -ErrorAction SilentlyContinue)) {\n make markdown-format *> $null\n if ($LASTEXITCODE -ne 0) {\n make format *> $null\n }\n if ($LASTEXITCODE -ne 0) {\n Add-Content -Path $logPath -Value 'markdown-format-run-failed'\n }\n} else {\n Add-Content -Path $logPath -Value 'markdown-format-skipped-missing-make-or-makefile'\n}\n", "cwd": ".", "timeoutSec": 30 } ] } -} \ No newline at end of file +} diff --git a/.github/hooks/pre-tool-safety-gate.json b/.github/hooks/pre-tool-safety-gate.json index 83d0e29..3e910fa 100644 --- a/.github/hooks/pre-tool-safety-gate.json +++ b/.github/hooks/pre-tool-safety-gate.json @@ -4,9 +4,9 @@ "preToolUse": [ { "type": "command", - "description": "Check for destructive bash patterns and block in enforce mode.\nAlways logs to audit trail.\n", - "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-pre-tool.jsonl\nmode=\"${VSTACK_HOOKS_MODE:-audit}\"\nif [ \"$mode\" != \"enforce\" ]; then\n exit 0\nfi\nif printf '%s' \"$input\" | grep -Eq '\"toolName\"[[:space:]]*:[[:space:]]*\"bash\"' && \\\n printf '%s' \"$input\" | grep -Eiq 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx'; then\n echo '{\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Blocked potentially destructive shell command by vstack pre-tool safety policy\"}'\nfi\n", - "powershell": "$inputText = [Console]::In.ReadToEnd()\nNew-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\nAdd-Content -Path .vstack/logs/hook-pre-tool.jsonl -Value $inputText\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -ne 'enforce') {\n exit 0\n}\ntry {\n $payload = $inputText | ConvertFrom-Json -ErrorAction Stop\n} catch {\n exit 0\n}\nif ($payload.toolName -eq 'bash' -and $inputText -match 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx') {\n @{ permissionDecision = 'deny'; permissionDecisionReason = 'Blocked potentially destructive shell command by vstack pre-tool safety policy' } | ConvertTo-Json -Compress\n}\n", + "description": "Check for destructive bash patterns and block in enforce mode.\nLogs only policy decisions and errors in this hook.\n", + "bash": "input=\"$(cat)\"\nlog_name=\"${VSTACK_HOOK_LOG_NAME:-hook-security-alerts.log}\"\nlog_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nlog_dir=\"$log_root/$(date -u +%Y%m%d)\"\nmkdir -p \"$log_dir\"\nlog_path=\"$log_dir/$log_name\"\nmode=\"${VSTACK_HOOKS_MODE:-audit}\"\nif [ \"$mode\" != \"enforce\" ]; then\n exit 0\nfi\ntool_name=\"$(printf '%s' \"$input\" | sed -n 's/.*\"toolName\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' | head -n1)\"\nif [ -z \"$tool_name\" ]; then\n printf '%s\\n' 'payload-parse-failed' >> \"$log_path\"\n echo '{\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Blocked request because payload could not be validated in enforce mode\"}'\n exit 0\nfi\nif [ \"$tool_name\" = \"bash\" ] && \\\n printf '%s' \"$input\" | grep -Eiq 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx|shutdown -h|shutdown now|reboot|poweroff|halt'; then\n printf '%s\\n' 'destructive-command-blocked' >> \"$log_path\"\n echo '{\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Blocked potentially destructive shell command by vstack pre-tool safety policy\"}'\nfi\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\n$logName = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { 'hook-security-alerts.log' }\n$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\n$logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\nNew-Item -ItemType Directory -Force -Path $logDir | Out-Null\n$logPath = Join-Path $logDir $logName\n$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' }\nif ($mode -ne 'enforce') {\n exit 0\n}\ntry {\n $payload = $inputText | ConvertFrom-Json -ErrorAction Stop\n} catch {\n Add-Content -Path $logPath -Value 'payload-parse-failed'\n @{ permissionDecision = 'deny'; permissionDecisionReason = 'Blocked request because payload could not be validated in enforce mode' } | ConvertTo-Json -Compress\n exit 0\n}\nif (-not $payload.toolName) {\n Add-Content -Path $logPath -Value 'payload-parse-failed'\n @{ permissionDecision = 'deny'; permissionDecisionReason = 'Blocked request because payload could not be validated in enforce mode' } | ConvertTo-Json -Compress\n exit 0\n}\nif ($payload.toolName -eq 'bash' -and $inputText -match 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx|shutdown -h|shutdown now|reboot|poweroff|halt') {\n Add-Content -Path $logPath -Value 'destructive-command-blocked'\n @{ permissionDecision = 'deny'; permissionDecisionReason = 'Blocked potentially destructive shell command by vstack pre-tool safety policy' } | ConvertTo-Json -Compress\n}\n", "cwd": ".", "timeoutSec": 15 } @@ -15,11 +15,11 @@ { "type": "command", "description": "Log tool errors for incident investigation and debugging.\n", - "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-errors.jsonl\n", - "powershell": "New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\n$inputText = [Console]::In.ReadToEnd()\nAdd-Content -Path .vstack/logs/hook-errors.jsonl -Value $inputText\n", + "bash": "log_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nlog_dir=\"$log_root/$(date -u +%Y%m%d)\"\nmkdir -p \"$log_dir\"\nprintf '%s\\n' \"hook-error-occurred\" >> \"$log_dir/hook-errors.log\"\n", + "powershell": "$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\n$logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\nNew-Item -ItemType Directory -Force -Path $logDir | Out-Null\nAdd-Content -Path (Join-Path $logDir 'hook-errors.log') -Value 'hook-error-occurred'\n", "cwd": ".", "timeoutSec": 10 } ] } -} \ No newline at end of file +} diff --git a/.github/hooks/session-audit.json b/.github/hooks/session-audit.json index 787a21b..65e6045 100644 --- a/.github/hooks/session-audit.json +++ b/.github/hooks/session-audit.json @@ -1,12 +1,32 @@ { "version": 1, "hooks": { + "preToolUse": [ + { + "type": "command", + "description": "Centrally log tool call requests with size and estimated tokens.\n", + "bash": "input=\"$(cat)\"\nlog_level=\"${VSTACK_HOOKS_LOG_LEVEL:-minimal}\"\nlog_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nlog_dir=\"$log_root/$(date -u +%Y%m%d)\"\nmkdir -p \"$log_dir\"\nif [ \"$log_level\" = \"off\" ]; then\n exit 0\nfi\nif [ \"$log_level\" = \"verbose\" ]; then\n printf '%s\\n' \"$input\" >> \"$log_dir/hook-tool-use.log\"\n exit 0\nfi\nts=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nbytes=\"$(printf '%s' \"$input\" | wc -c | tr -d ' ')\"\nest_tokens=\"$(( (bytes + 3) / 4 ))\"\ntool_name=\"$(printf '%s' \"$input\" | sed -n 's/.*\"toolName\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' | head -n1)\"\nif [ -z \"$tool_name\" ]; then\n tool_name=\"unknown\"\nfi\nprintf '{\"timestamp\":\"%s\",\"event\":\"preToolUse\",\"tool_name\":\"%s\",\"size_bytes\":%s,\"estimated_tokens\":%s}\\n' \"$ts\" \"$tool_name\" \"$bytes\" \"$est_tokens\" >> \"$log_dir/hook-tool-use.log\"\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\n$logLevel = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { 'minimal' }\n$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\n$logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\nNew-Item -ItemType Directory -Force -Path $logDir | Out-Null\nif ($logLevel -eq 'off') {\n exit 0\n}\nif ($logLevel -eq 'verbose') {\n Add-Content -Path (Join-Path $logDir 'hook-tool-use.log') -Value $inputText\n exit 0\n}\n$toolName = 'unknown'\ntry {\n $payload = $inputText | ConvertFrom-Json -ErrorAction Stop\n if ($payload.toolName) {\n $toolName = [string]$payload.toolName\n }\n} catch {\n}\n$record = @{\n timestamp = (Get-Date).ToUniversalTime().ToString('o')\n event = 'preToolUse'\n tool_name = $toolName\n size_bytes = $inputText.Length\n estimated_tokens = [int][math]::Ceiling($inputText.Length / 4.0)\n} | ConvertTo-Json -Compress\nAdd-Content -Path (Join-Path $logDir 'hook-tool-use.log') -Value $record\n", + "cwd": ".", + "timeoutSec": 10 + } + ], + "postToolUse": [ + { + "type": "command", + "description": "Centrally log tool responses with size and estimated tokens.\n", + "bash": "input=\"$(cat)\"\nlog_level=\"${VSTACK_HOOKS_LOG_LEVEL:-minimal}\"\nlog_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nlog_dir=\"$log_root/$(date -u +%Y%m%d)\"\nmkdir -p \"$log_dir\"\nif [ \"$log_level\" = \"off\" ]; then\n exit 0\nfi\nif [ \"$log_level\" = \"verbose\" ]; then\n printf '%s\\n' \"$input\" >> \"$log_dir/hook-tool-use.log\"\n exit 0\nfi\nts=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nbytes=\"$(printf '%s' \"$input\" | wc -c | tr -d ' ')\"\nest_tokens=\"$(( (bytes + 3) / 4 ))\"\ntool_name=\"$(printf '%s' \"$input\" | sed -n 's/.*\"toolName\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' | head -n1)\"\nif [ -z \"$tool_name\" ]; then\n tool_name=\"unknown\"\nfi\nprintf '{\"timestamp\":\"%s\",\"event\":\"postToolUse\",\"tool_name\":\"%s\",\"size_bytes\":%s,\"estimated_tokens\":%s}\\n' \"$ts\" \"$tool_name\" \"$bytes\" \"$est_tokens\" >> \"$log_dir/hook-tool-use.log\"\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\n$logLevel = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { 'minimal' }\n$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\n$logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\nNew-Item -ItemType Directory -Force -Path $logDir | Out-Null\nif ($logLevel -eq 'off') {\n exit 0\n}\nif ($logLevel -eq 'verbose') {\n Add-Content -Path (Join-Path $logDir 'hook-tool-use.log') -Value $inputText\n exit 0\n}\n$toolName = 'unknown'\ntry {\n $payload = $inputText | ConvertFrom-Json -ErrorAction Stop\n if ($payload.toolName) {\n $toolName = [string]$payload.toolName\n }\n} catch {\n}\n$record = @{\n timestamp = (Get-Date).ToUniversalTime().ToString('o')\n event = 'postToolUse'\n tool_name = $toolName\n size_bytes = $inputText.Length\n estimated_tokens = [int][math]::Ceiling($inputText.Length / 4.0)\n} | ConvertTo-Json -Compress\nAdd-Content -Path (Join-Path $logDir 'hook-tool-use.log') -Value $record\n", + "cwd": ".", + "timeoutSec": 10 + } + ], "userPromptSubmitted": [ { "type": "command", - "description": "Log prompt submission events for audit correlation.\n", - "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-user-prompt.jsonl\n", - "powershell": "New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\n$inputText = [Console]::In.ReadToEnd()\nAdd-Content -Path .vstack/logs/hook-user-prompt.jsonl -Value $inputText\n", + "description": "Centrally log prompt events with configurable verbosity.\n", + "bash": "input=\"$(cat)\"\nlog_level=\"${VSTACK_HOOKS_LOG_LEVEL:-minimal}\"\nlog_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nlog_dir=\"$log_root/$(date -u +%Y%m%d)\"\nmkdir -p \"$log_dir\"\nif [ \"$log_level\" = \"off\" ]; then\n exit 0\nfi\nif [ \"$log_level\" = \"verbose\" ]; then\n printf '%s\\n' \"$input\" >> \"$log_dir/hook-user-prompt.log\"\n exit 0\nfi\nts=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nbytes=\"$(printf '%s' \"$input\" | wc -c | tr -d ' ')\"\nest_tokens=\"$(( (bytes + 3) / 4 ))\"\nprintf '{\"timestamp\":\"%s\",\"event\":\"userPromptSubmitted\",\"size_bytes\":%s,\"estimated_tokens\":%s}\\n' \"$ts\" \"$bytes\" \"$est_tokens\" >> \"$log_dir/hook-user-prompt.log\"\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\n$logLevel = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { 'minimal' }\n$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\n$logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\nNew-Item -ItemType Directory -Force -Path $logDir | Out-Null\nif ($logLevel -eq 'off') {\n exit 0\n}\nif ($logLevel -eq 'verbose') {\n Add-Content -Path (Join-Path $logDir 'hook-user-prompt.log') -Value $inputText\n exit 0\n}\n$record = @{ timestamp = (Get-Date).ToUniversalTime().ToString('o'); event = 'userPromptSubmitted'; size_bytes = $inputText.Length; estimated_tokens = [int][math]::Ceiling($inputText.Length / 4.0) } | ConvertTo-Json -Compress\nAdd-Content -Path (Join-Path $logDir 'hook-user-prompt.log') -Value $record\n", "cwd": ".", "timeoutSec": 10 } @@ -14,9 +34,9 @@ "sessionStart": [ { "type": "command", - "description": "Log session start event with timestamp and context.\n", - "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-session-start.jsonl\n", - "powershell": "New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\n$inputText = [Console]::In.ReadToEnd()\nAdd-Content -Path .vstack/logs/hook-session-start.jsonl -Value $inputText\n", + "description": "Centrally log session start with configurable verbosity.\n", + "bash": "input=\"$(cat)\"\nlog_level=\"${VSTACK_HOOKS_LOG_LEVEL:-minimal}\"\nlog_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nlog_dir=\"$log_root/$(date -u +%Y%m%d)\"\nmkdir -p \"$log_dir\"\nif [ \"$log_level\" = \"off\" ]; then\n exit 0\nfi\nif [ \"$log_level\" = \"verbose\" ]; then\n printf '%s\\n' \"$input\" >> \"$log_dir/hook-session-start.log\"\n exit 0\nfi\nts=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nbytes=\"$(printf '%s' \"$input\" | wc -c | tr -d ' ')\"\nest_tokens=\"$(( (bytes + 3) / 4 ))\"\nprintf '{\"timestamp\":\"%s\",\"event\":\"sessionStart\",\"size_bytes\":%s,\"estimated_tokens\":%s}\\n' \"$ts\" \"$bytes\" \"$est_tokens\" >> \"$log_dir/hook-session-start.log\"\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\n$logLevel = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { 'minimal' }\n$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\n$logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\nNew-Item -ItemType Directory -Force -Path $logDir | Out-Null\nif ($logLevel -eq 'off') {\n exit 0\n}\nif ($logLevel -eq 'verbose') {\n Add-Content -Path (Join-Path $logDir 'hook-session-start.log') -Value $inputText\n exit 0\n}\n$record = @{ timestamp = (Get-Date).ToUniversalTime().ToString('o'); event = 'sessionStart'; size_bytes = $inputText.Length; estimated_tokens = [int][math]::Ceiling($inputText.Length / 4.0) } | ConvertTo-Json -Compress\nAdd-Content -Path (Join-Path $logDir 'hook-session-start.log') -Value $record\n", "cwd": ".", "timeoutSec": 10 } @@ -24,12 +44,12 @@ "sessionEnd": [ { "type": "command", - "description": "Log session end event for correlation with start.\n", - "bash": "mkdir -p .vstack/logs\ninput=\"$(cat)\"\nprintf '%s\\n' \"$input\" >> .vstack/logs/hook-session-end.jsonl\n", - "powershell": "New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null\n$inputText = [Console]::In.ReadToEnd()\nAdd-Content -Path .vstack/logs/hook-session-end.jsonl -Value $inputText\n", + "description": "Centrally log session end for correlation with start.\n", + "bash": "input=\"$(cat)\"\nlog_level=\"${VSTACK_HOOKS_LOG_LEVEL:-minimal}\"\nlog_root=\"${VSTACK_HOOK_LOG_DIR:-.vstack/logs}\"\nlog_dir=\"$log_root/$(date -u +%Y%m%d)\"\nmkdir -p \"$log_dir\"\nif [ \"$log_level\" = \"off\" ]; then\n exit 0\nfi\nif [ \"$log_level\" = \"verbose\" ]; then\n printf '%s\\n' \"$input\" >> \"$log_dir/hook-session-end.log\"\n exit 0\nfi\nts=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nbytes=\"$(printf '%s' \"$input\" | wc -c | tr -d ' ')\"\nest_tokens=\"$(( (bytes + 3) / 4 ))\"\nprintf '{\"timestamp\":\"%s\",\"event\":\"sessionEnd\",\"size_bytes\":%s,\"estimated_tokens\":%s}\\n' \"$ts\" \"$bytes\" \"$est_tokens\" >> \"$log_dir/hook-session-end.log\"\n", + "powershell": "$inputText = [Console]::In.ReadToEnd()\n$logLevel = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { 'minimal' }\n$logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' }\n$logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd')\nNew-Item -ItemType Directory -Force -Path $logDir | Out-Null\nif ($logLevel -eq 'off') {\n exit 0\n}\nif ($logLevel -eq 'verbose') {\n Add-Content -Path (Join-Path $logDir 'hook-session-end.log') -Value $inputText\n exit 0\n}\n$record = @{ timestamp = (Get-Date).ToUniversalTime().ToString('o'); event = 'sessionEnd'; size_bytes = $inputText.Length; estimated_tokens = [int][math]::Ceiling($inputText.Length / 4.0) } | ConvertTo-Json -Compress\nAdd-Content -Path (Join-Path $logDir 'hook-session-end.log') -Value $record\n", "cwd": ".", "timeoutSec": 10 } ] } -} \ No newline at end of file +} diff --git a/.github/instructions/git.instructions.md b/.github/instructions/git.instructions.md index ee0ecde..db6263a 100644 --- a/.github/instructions/git.instructions.md +++ b/.github/instructions/git.instructions.md @@ -41,4 +41,4 @@ Use these Git and release hygiene conventions in this project. 1. Prefer local verification before pushing release-impacting changes. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"git","artifact_type":"instruction","artifact_version":"20260421001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"git","artifact_type":"instruction","artifact_version":"20260421001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/helm.instructions.md b/.github/instructions/helm.instructions.md index 1b37194..f570c7d 100644 --- a/.github/instructions/helm.instructions.md +++ b/.github/instructions/helm.instructions.md @@ -45,4 +45,4 @@ Use these Helm conventions in this project. - [Helm chart best practices](https://helm.sh/docs/chart_best_practices/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"instruction","artifact_version":"20260502040","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"instruction","artifact_version":"20260502040","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/java.instructions.md b/.github/instructions/java.instructions.md index 98a498a..118782c 100644 --- a/.github/instructions/java.instructions.md +++ b/.github/instructions/java.instructions.md @@ -56,4 +56,4 @@ Use these Java conventions in this project. 1. Do not suppress static analysis warnings without a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"java","artifact_type":"instruction","artifact_version":"20260502001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"java","artifact_type":"instruction","artifact_version":"20260502001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/k8s.instructions.md b/.github/instructions/k8s.instructions.md index 5bcc17e..e69825b 100644 --- a/.github/instructions/k8s.instructions.md +++ b/.github/instructions/k8s.instructions.md @@ -51,4 +51,4 @@ Use these Kubernetes conventions in this project. - [Kubernetes API reference](https://kubernetes.io/docs/reference/kubernetes-api/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"instruction","artifact_version":"20260502039","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"instruction","artifact_version":"20260502039","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/markdown.instructions.md b/.github/instructions/markdown.instructions.md index 64b29d5..7cf32ad 100644 --- a/.github/instructions/markdown.instructions.md +++ b/.github/instructions/markdown.instructions.md @@ -51,4 +51,4 @@ Use these Markdown conventions in this project. 1. Keep examples accurate and runnable — a broken example is worse than no example. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"markdown","artifact_type":"instruction","artifact_version":"20260502002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"markdown","artifact_type":"instruction","artifact_version":"20260502002","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/python.instructions.md b/.github/instructions/python.instructions.md index a9a809c..48db4d4 100644 --- a/.github/instructions/python.instructions.md +++ b/.github/instructions/python.instructions.md @@ -42,4 +42,4 @@ Use these Python conventions in this project. 1. Do not silence lint/type errors unless there is a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"python","artifact_type":"instruction","artifact_version":"20260421002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"python","artifact_type":"instruction","artifact_version":"20260421002","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/rancher.instructions.md b/.github/instructions/rancher.instructions.md index 3f52dd8..0457e71 100644 --- a/.github/instructions/rancher.instructions.md +++ b/.github/instructions/rancher.instructions.md @@ -44,4 +44,4 @@ Use these Rancher conventions in this project. - [Fleet docs](https://fleet.rancher.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"instruction","artifact_version":"20260502041","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"instruction","artifact_version":"20260502041","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/security.instructions.md b/.github/instructions/security.instructions.md index dd02e2c..8d5f82c 100644 --- a/.github/instructions/security.instructions.md +++ b/.github/instructions/security.instructions.md @@ -42,4 +42,4 @@ Apply these security policies in this project. 1. Isolate privileged logic; keep it minimal, auditable, and separate from business logic. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"instruction","artifact_version":"20260502003","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"instruction","artifact_version":"20260502003","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/terraform.instructions.md b/.github/instructions/terraform.instructions.md index da8fe19..fb5e44f 100644 --- a/.github/instructions/terraform.instructions.md +++ b/.github/instructions/terraform.instructions.md @@ -60,4 +60,4 @@ Use these Terraform conventions in this project. - [tfsec](https://aquasecurity.github.io/tfsec/) · [checkov](https://www.checkov.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"instruction","artifact_version":"20260502034","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"instruction","artifact_version":"20260502034","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/terragrunt.instructions.md b/.github/instructions/terragrunt.instructions.md index 9770cb6..d184fc6 100644 --- a/.github/instructions/terragrunt.instructions.md +++ b/.github/instructions/terragrunt.instructions.md @@ -57,4 +57,4 @@ Use these Terragrunt conventions in this project. - [Terragrunt CLI reference](https://terragrunt.gruntwork.io/docs/reference/cli-options/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"instruction","artifact_version":"20260502035","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"instruction","artifact_version":"20260502035","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index e673737..c1ffbe2 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -43,4 +43,4 @@ Use these testing conventions in this project. 1. Treat flaky tests as bugs; do not merge code with known test reliability issues. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"testing","artifact_type":"instruction","artifact_version":"20260502004","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"testing","artifact_type":"instruction","artifact_version":"20260502004","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/instructions/typescript.instructions.md b/.github/instructions/typescript.instructions.md index adb935d..f1692d2 100644 --- a/.github/instructions/typescript.instructions.md +++ b/.github/instructions/typescript.instructions.md @@ -49,4 +49,4 @@ Use these TypeScript conventions in this project. 1. Do not suppress lint or type errors with inline disable comments unless there is a documented, task-specific reason. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"typescript","artifact_type":"instruction","artifact_version":"20260502005","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"typescript","artifact_type":"instruction","artifact_version":"20260502005","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/api-design-review.prompt.md b/.github/prompts/api-design-review.prompt.md index 70e505c..2222575 100644 --- a/.github/prompts/api-design-review.prompt.md +++ b/.github/prompts/api-design-review.prompt.md @@ -55,4 +55,4 @@ List fields or objects that are missing required constraints, descriptions, or e - top priority fix in one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"api-design-review","artifact_type":"prompt","artifact_version":"20260502006","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"api-design-review","artifact_type":"prompt","artifact_version":"20260502006","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/architecture-risk.prompt.md b/.github/prompts/architecture-risk.prompt.md index 9d9f919..838adbb 100644 --- a/.github/prompts/architecture-risk.prompt.md +++ b/.github/prompts/architecture-risk.prompt.md @@ -52,4 +52,4 @@ List security-specific risks not covered above: auth boundaries, sensitive data - one-sentence rationale <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architecture-risk","artifact_type":"prompt","artifact_version":"20260502007","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"architecture-risk","artifact_type":"prompt","artifact_version":"20260502007","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/code-review.prompt.md b/.github/prompts/code-review.prompt.md index 4f6287e..4c6ceec 100644 --- a/.github/prompts/code-review.prompt.md +++ b/.github/prompts/code-review.prompt.md @@ -51,4 +51,4 @@ End with: - Biggest remaining risk: one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"prompt","artifact_version":"20260502008","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"prompt","artifact_version":"20260502008","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/dependency-audit.prompt.md b/.github/prompts/dependency-audit.prompt.md index d8744b5..33f9fc3 100644 --- a/.github/prompts/dependency-audit.prompt.md +++ b/.github/prompts/dependency-audit.prompt.md @@ -60,4 +60,4 @@ List packages with unusual provenance concerns: abandoned maintainers, single-ma Ordered list of actions by priority (critical first). <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependency-audit","artifact_type":"prompt","artifact_version":"20260502009","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"dependency-audit","artifact_type":"prompt","artifact_version":"20260502009","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/incident-timeline.prompt.md b/.github/prompts/incident-timeline.prompt.md index ebe3980..d92f4ad 100644 --- a/.github/prompts/incident-timeline.prompt.md +++ b/.github/prompts/incident-timeline.prompt.md @@ -57,4 +57,4 @@ For each action: List the minimum controls needed to reduce repeat probability. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"incident-timeline","artifact_type":"prompt","artifact_version":"20260502010","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"incident-timeline","artifact_type":"prompt","artifact_version":"20260502010","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/migration-safety.prompt.md b/.github/prompts/migration-safety.prompt.md index e602ce8..514d14e 100644 --- a/.github/prompts/migration-safety.prompt.md +++ b/.github/prompts/migration-safety.prompt.md @@ -52,4 +52,4 @@ List missing migration tests (forward, backward, data invariants, load-sensitive - biggest remaining risk in one sentence <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"migration-safety","artifact_type":"prompt","artifact_version":"20260502011","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"migration-safety","artifact_type":"prompt","artifact_version":"20260502011","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/prompts/release-readiness.prompt.md b/.github/prompts/release-readiness.prompt.md index 8552f4f..f99b15a 100644 --- a/.github/prompts/release-readiness.prompt.md +++ b/.github/prompts/release-readiness.prompt.md @@ -46,4 +46,4 @@ For each expected artifact that is missing, flag it explicitly as: MISSING — [ One clear next step for the team. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release-readiness","artifact_type":"prompt","artifact_version":"20260502012","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"release-readiness","artifact_type":"prompt","artifact_version":"20260502012","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/adr/SKILL.md b/.github/skills/adr/SKILL.md index 7bd56f5..7c36e1b 100644 --- a/.github/skills/adr/SKILL.md +++ b/.github/skills/adr/SKILL.md @@ -180,4 +180,4 @@ is a kebab-case title. After writing, state the file path and summary so the architect or product role can review. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"adr","artifact_type":"skill","artifact_version":"20260421003","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"adr","artifact_type":"skill","artifact_version":"20260421003","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/analyse/SKILL.md b/.github/skills/analyse/SKILL.md index 3856249..69762c9 100644 --- a/.github/skills/analyse/SKILL.md +++ b/.github/skills/analyse/SKILL.md @@ -214,4 +214,4 @@ State conclusions with confidence level: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"analyse","artifact_type":"skill","artifact_version":"20260421004","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"analyse","artifact_type":"skill","artifact_version":"20260421004","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/architecture/SKILL.md b/.github/skills/architecture/SKILL.md index e55a8d3..58a1ead 100644 --- a/.github/skills/architecture/SKILL.md +++ b/.github/skills/architecture/SKILL.md @@ -278,4 +278,4 @@ For each significant structural decision made during this review (technology cho - Update `docs/architecture/overview.md` to reflect the final decisions. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"architecture","artifact_type":"skill","artifact_version":"20260421005","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"architecture","artifact_type":"skill","artifact_version":"20260421005","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/aws-cli/SKILL.md b/.github/skills/aws-cli/SKILL.md index c28e4af..c25b92d 100644 --- a/.github/skills/aws-cli/SKILL.md +++ b/.github/skills/aws-cli/SKILL.md @@ -374,4 +374,4 @@ aws ce get-cost-and-usage \ - [AWS CLI named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"aws-cli","artifact_type":"skill","artifact_version":"20260502033","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"aws-cli","artifact_type":"skill","artifact_version":"20260502033","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/cicd/SKILL.md b/.github/skills/cicd/SKILL.md index 421c19a..81b957a 100644 --- a/.github/skills/cicd/SKILL.md +++ b/.github/skills/cicd/SKILL.md @@ -221,4 +221,4 @@ Configure these in GitHub → Settings → Branches. - [GitHub-hosted runners](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"cicd","artifact_type":"skill","artifact_version":"20260421006","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"cicd","artifact_type":"skill","artifact_version":"20260421006","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/cloudformation/SKILL.md b/.github/skills/cloudformation/SKILL.md index 2ad5f01..0b735c7 100644 --- a/.github/skills/cloudformation/SKILL.md +++ b/.github/skills/cloudformation/SKILL.md @@ -344,4 +344,4 @@ AppSecurityGroup: - [AWS SAM documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"cloudformation","artifact_type":"skill","artifact_version":"20260502032","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"cloudformation","artifact_type":"skill","artifact_version":"20260502032","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/code-review/SKILL.md b/.github/skills/code-review/SKILL.md index 4d3a47b..60c3a84 100644 --- a/.github/skills/code-review/SKILL.md +++ b/.github/skills/code-review/SKILL.md @@ -218,4 +218,4 @@ Confidence: [HIGH/MEDIUM/LOW — explain if not HIGH] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"skill","artifact_version":"20260421007","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"code-review","artifact_type":"skill","artifact_version":"20260421007","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/codeql/SKILL.md b/.github/skills/codeql/SKILL.md index 855da54..ffd1863 100644 --- a/.github/skills/codeql/SKILL.md +++ b/.github/skills/codeql/SKILL.md @@ -251,4 +251,4 @@ GITHUB_TOKEN=<token> codeql github upload-results \ - [Supported languages and frameworks](https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/codeql-code-scanning-for-compiled-languages) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"codeql","artifact_type":"skill","artifact_version":"20260502026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"codeql","artifact_type":"skill","artifact_version":"20260502026","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/concise/SKILL.md b/.github/skills/concise/SKILL.md index dac193d..188e8b0 100644 --- a/.github/skills/concise/SKILL.md +++ b/.github/skills/concise/SKILL.md @@ -147,4 +147,4 @@ Current mode unchanged: <mode> - [ ] User confirmation/status returned in deterministic format <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"concise","artifact_type":"skill","artifact_version":"20260421008","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"concise","artifact_type":"skill","artifact_version":"20260421008","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/consult/SKILL.md b/.github/skills/consult/SKILL.md index 2571f4e..8a3a2a3 100644 --- a/.github/skills/consult/SKILL.md +++ b/.github/skills/consult/SKILL.md @@ -218,4 +218,4 @@ reason: [one sentence] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"consult","artifact_type":"skill","artifact_version":"20260421009","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"consult","artifact_type":"skill","artifact_version":"20260421009","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/container/SKILL.md b/.github/skills/container/SKILL.md index 2d9c924..ea38b36 100644 --- a/.github/skills/container/SKILL.md +++ b/.github/skills/container/SKILL.md @@ -153,4 +153,4 @@ For production-like local testing, write a separate `docker-compose.prod.yml` wi - [Docker official images](https://hub.docker.com/search?image_filter=official) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"container","artifact_type":"skill","artifact_version":"20260421010","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"container","artifact_type":"skill","artifact_version":"20260421010","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/conventional-commit/SKILL.md b/.github/skills/conventional-commit/SKILL.md index 77a4b00..d5dde6b 100644 --- a/.github/skills/conventional-commit/SKILL.md +++ b/.github/skills/conventional-commit/SKILL.md @@ -155,4 +155,4 @@ Remaining changes: If commit is blocked, report exact reason and proposed fix. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"conventional-commit","artifact_type":"skill","artifact_version":"20260502024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"conventional-commit","artifact_type":"skill","artifact_version":"20260502024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/debug/SKILL.md b/.github/skills/debug/SKILL.md index 76ec3b7..1f2d656 100644 --- a/.github/skills/debug/SKILL.md +++ b/.github/skills/debug/SKILL.md @@ -258,4 +258,4 @@ Prevention: [any follow-up items] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"debug","artifact_type":"skill","artifact_version":"20260421011","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"debug","artifact_type":"skill","artifact_version":"20260421011","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/dependabot/SKILL.md b/.github/skills/dependabot/SKILL.md index ad9aeff..a269f4d 100644 --- a/.github/skills/dependabot/SKILL.md +++ b/.github/skills/dependabot/SKILL.md @@ -320,4 +320,4 @@ updates: - [Dependabot security updates](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependabot","artifact_type":"skill","artifact_version":"20260502027","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"dependabot","artifact_type":"skill","artifact_version":"20260502027","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/dependency/SKILL.md b/.github/skills/dependency/SKILL.md index 3313f88..26681ca 100644 --- a/.github/skills/dependency/SKILL.md +++ b/.github/skills/dependency/SKILL.md @@ -318,4 +318,4 @@ Action items (priority order): - [PyPI / npm / crates.io / Maven Central](https://pypi.org) (replace with the relevant registry) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"dependency","artifact_type":"skill","artifact_version":"20260421012","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"dependency","artifact_type":"skill","artifact_version":"20260421012","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/design/SKILL.md b/.github/skills/design/SKILL.md index e732ac8..c62109d 100644 --- a/.github/skills/design/SKILL.md +++ b/.github/skills/design/SKILL.md @@ -243,4 +243,4 @@ Output a complete design document to `docs/design/overview.md` or `openapi.yaml` ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"design","artifact_type":"skill","artifact_version":"20260421013","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"design","artifact_type":"skill","artifact_version":"20260421013","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/docs/SKILL.md b/.github/skills/docs/SKILL.md index be97300..fa75166 100644 --- a/.github/skills/docs/SKILL.md +++ b/.github/skills/docs/SKILL.md @@ -151,4 +151,4 @@ Skipped (n/a): ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"docs","artifact_type":"skill","artifact_version":"20260421014","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"docs","artifact_type":"skill","artifact_version":"20260421014","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/explore/SKILL.md b/.github/skills/explore/SKILL.md index aa6af6a..b46d037 100644 --- a/.github/skills/explore/SKILL.md +++ b/.github/skills/explore/SKILL.md @@ -224,4 +224,4 @@ Stack: [language, framework, runtime versions] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"explore","artifact_type":"skill","artifact_version":"20260421015","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"explore","artifact_type":"skill","artifact_version":"20260421015","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/gdpr/SKILL.md b/.github/skills/gdpr/SKILL.md index b6ff4fd..d1ae2c9 100644 --- a/.github/skills/gdpr/SKILL.md +++ b/.github/skills/gdpr/SKILL.md @@ -246,4 +246,4 @@ Use `@example.com` for all test email addresses. - [EDPB guidelines](https://www.edpb.europa.eu/our-work-tools/general-guidance/guidelines-recommendations-best-practices_en) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gdpr","artifact_type":"skill","artifact_version":"20260502029","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"gdpr","artifact_type":"skill","artifact_version":"20260502029","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/gh-issues/SKILL.md b/.github/skills/gh-issues/SKILL.md index 3b5a19a..692ec90 100644 --- a/.github/skills/gh-issues/SKILL.md +++ b/.github/skills/gh-issues/SKILL.md @@ -230,4 +230,4 @@ https://github.com/<org>/<repo>/issues/<number> - [GitHub Issues documentation](https://docs.github.com/en/issues) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gh-issues","artifact_type":"skill","artifact_version":"20260502025","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"gh-issues","artifact_type":"skill","artifact_version":"20260502025","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/gh-release/SKILL.md b/.github/skills/gh-release/SKILL.md index e3b1c68..b8e7465 100644 --- a/.github/skills/gh-release/SKILL.md +++ b/.github/skills/gh-release/SKILL.md @@ -214,4 +214,4 @@ If blocked, report exact blocker and required user action. - [GitHub Releases documentation](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"gh-release","artifact_type":"skill","artifact_version":"20260502023","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"gh-release","artifact_type":"skill","artifact_version":"20260502023","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/guardrails/SKILL.md b/.github/skills/guardrails/SKILL.md index 820fe2e..b5f1702 100644 --- a/.github/skills/guardrails/SKILL.md +++ b/.github/skills/guardrails/SKILL.md @@ -72,4 +72,4 @@ Activate careful mode for this session. Two behaviors are now enabled. Explicitly ask to "disable guardrails". <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"guardrails","artifact_type":"skill","artifact_version":"20260421016","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"guardrails","artifact_type":"skill","artifact_version":"20260421016","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/helm/SKILL.md b/.github/skills/helm/SKILL.md index 1746edd..74ad155 100644 --- a/.github/skills/helm/SKILL.md +++ b/.github/skills/helm/SKILL.md @@ -140,4 +140,4 @@ Practices: - [Chart best practices](https://helm.sh/docs/chart_best_practices/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"skill","artifact_version":"20260502037","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"helm","artifact_type":"skill","artifact_version":"20260502037","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/incident/SKILL.md b/.github/skills/incident/SKILL.md index 784e4ac..4d88c90 100644 --- a/.github/skills/incident/SKILL.md +++ b/.github/skills/incident/SKILL.md @@ -251,4 +251,4 @@ Next: invoke @#rca and @#postmortem to produce written artifacts. ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"incident","artifact_type":"skill","artifact_version":"20260503002","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"incident","artifact_type":"skill","artifact_version":"20260503002","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/inspect/SKILL.md b/.github/skills/inspect/SKILL.md index 9a2346a..944d397 100644 --- a/.github/skills/inspect/SKILL.md +++ b/.github/skills/inspect/SKILL.md @@ -155,4 +155,4 @@ Confirm for changed paths: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"inspect","artifact_type":"skill","artifact_version":"20260421018","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"inspect","artifact_type":"skill","artifact_version":"20260421018","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/k8s/SKILL.md b/.github/skills/k8s/SKILL.md index 42ca2a6..780b5af 100644 --- a/.github/skills/k8s/SKILL.md +++ b/.github/skills/k8s/SKILL.md @@ -144,4 +144,4 @@ Common failure classes: - [Kubernetes API reference](https://kubernetes.io/docs/reference/kubernetes-api/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"skill","artifact_version":"20260502036","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"k8s","artifact_type":"skill","artifact_version":"20260502036","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/migrate/SKILL.md b/.github/skills/migrate/SKILL.md index 07e1934..93474c9 100644 --- a/.github/skills/migrate/SKILL.md +++ b/.github/skills/migrate/SKILL.md @@ -320,4 +320,4 @@ Pre-deploy checklist: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"migrate","artifact_type":"skill","artifact_version":"20260421019","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"migrate","artifact_type":"skill","artifact_version":"20260421019","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/onboard/SKILL.md b/.github/skills/onboard/SKILL.md index da290f0..63dd1cd 100644 --- a/.github/skills/onboard/SKILL.md +++ b/.github/skills/onboard/SKILL.md @@ -302,4 +302,4 @@ Gaps remaining (if any): ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"onboard","artifact_type":"skill","artifact_version":"20260421020","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"onboard","artifact_type":"skill","artifact_version":"20260421020","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/openapi/SKILL.md b/.github/skills/openapi/SKILL.md index 77a8b91..5ab1574 100644 --- a/.github/skills/openapi/SKILL.md +++ b/.github/skills/openapi/SKILL.md @@ -403,4 +403,4 @@ Summary: [N critical, N warnings, N info] - [Redocly CLI (linting)](https://redocly.com/docs/cli/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"openapi","artifact_type":"skill","artifact_version":"20260421021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"openapi","artifact_type":"skill","artifact_version":"20260421021","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/performance/SKILL.md b/.github/skills/performance/SKILL.md index 4bd99dc..0211b1a 100644 --- a/.github/skills/performance/SKILL.md +++ b/.github/skills/performance/SKILL.md @@ -242,4 +242,4 @@ For each bottleneck identified: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"performance","artifact_type":"skill","artifact_version":"20260421022","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"performance","artifact_type":"skill","artifact_version":"20260421022","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/postmortem/SKILL.md b/.github/skills/postmortem/SKILL.md index 12508ec..2d90f74 100644 --- a/.github/skills/postmortem/SKILL.md +++ b/.github/skills/postmortem/SKILL.md @@ -184,4 +184,4 @@ Status: Draft — ready for team review ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"postmortem","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"postmortem","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/pr/SKILL.md b/.github/skills/pr/SKILL.md index bd8368a..24a1b8c 100644 --- a/.github/skills/pr/SKILL.md +++ b/.github/skills/pr/SKILL.md @@ -151,4 +151,4 @@ Next steps depend on the repository CI/CD configuration: ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"pr","artifact_type":"skill","artifact_version":"20260502013","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"pr","artifact_type":"skill","artifact_version":"20260502013","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/rancher/SKILL.md b/.github/skills/rancher/SKILL.md index 3d58218..ed7f64a 100644 --- a/.github/skills/rancher/SKILL.md +++ b/.github/skills/rancher/SKILL.md @@ -113,4 +113,4 @@ Checks: - [Fleet documentation](https://fleet.rancher.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"skill","artifact_version":"20260502038","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"rancher","artifact_type":"skill","artifact_version":"20260502038","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/rca/SKILL.md b/.github/skills/rca/SKILL.md index 0a302a5..8f91c15 100644 --- a/.github/skills/rca/SKILL.md +++ b/.github/skills/rca/SKILL.md @@ -207,4 +207,4 @@ Status: Draft — ready for review ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"rca","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"rca","artifact_type":"skill","artifact_version":"20260503001","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/refactor/SKILL.md b/.github/skills/refactor/SKILL.md index 1110f6b..da59d2a 100644 --- a/.github/skills/refactor/SKILL.md +++ b/.github/skills/refactor/SKILL.md @@ -372,4 +372,4 @@ Behavior changed: No ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"refactor","artifact_type":"skill","artifact_version":"20260421023","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"refactor","artifact_type":"skill","artifact_version":"20260421023","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index 39d4c98..079defd 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -147,4 +147,4 @@ Prepend a new entry at the top of `CHANGELOG.md`: Keep existing entries intact. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"release-notes","artifact_type":"skill","artifact_version":"20260502014","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"release-notes","artifact_type":"skill","artifact_version":"20260502014","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/requirements/SKILL.md b/.github/skills/requirements/SKILL.md index 5d1e7d7..a9f7856 100644 --- a/.github/skills/requirements/SKILL.md +++ b/.github/skills/requirements/SKILL.md @@ -199,4 +199,4 @@ Write all findings to `docs/product/requirements.md`: After writing, summarize what was decided so the architect role can start. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"requirements","artifact_type":"skill","artifact_version":"20260421024","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"requirements","artifact_type":"skill","artifact_version":"20260421024","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/secret-scan/SKILL.md b/.github/skills/secret-scan/SKILL.md index 0c0674b..00dc2f9 100644 --- a/.github/skills/secret-scan/SKILL.md +++ b/.github/skills/secret-scan/SKILL.md @@ -240,4 +240,4 @@ credential formats. - [Supported secret patterns](https://docs.github.com/en/code-security/secret-scanning/introduction/supported-secret-scanning-patterns) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"secret-scan","artifact_type":"skill","artifact_version":"20260502028","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"secret-scan","artifact_type":"skill","artifact_version":"20260502028","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/security/SKILL.md b/.github/skills/security/SKILL.md index eb35c48..f868915 100644 --- a/.github/skills/security/SKILL.md +++ b/.github/skills/security/SKILL.md @@ -295,4 +295,4 @@ Scope: [full/diff/dependency/config] - [STRIDE threat modeling (Microsoft)](https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"skill","artifact_version":"20260421025","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"security","artifact_type":"skill","artifact_version":"20260421025","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/terraform/SKILL.md b/.github/skills/terraform/SKILL.md index 3ba5145..0ec9dc4 100644 --- a/.github/skills/terraform/SKILL.md +++ b/.github/skills/terraform/SKILL.md @@ -335,4 +335,4 @@ Run `terraform plan` after every state operation to verify the outcome. - [tfsec rules](https://aquasecurity.github.io/tfsec/latest/checks/aws/) · [checkov checks](https://www.checkov.io/5.Policy%20Index/terraform.html) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"skill","artifact_version":"20260502030","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"terraform","artifact_type":"skill","artifact_version":"20260502030","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/terragrunt/SKILL.md b/.github/skills/terragrunt/SKILL.md index 77326de..26929fe 100644 --- a/.github/skills/terragrunt/SKILL.md +++ b/.github/skills/terragrunt/SKILL.md @@ -307,4 +307,4 @@ Use `--terragrunt-non-interactive` in CI to prevent hanging on prompts. - [Gruntwork module registry](https://www.gruntwork.io/) <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"skill","artifact_version":"20260502031","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"terragrunt","artifact_type":"skill","artifact_version":"20260502031","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/threat-model/SKILL.md b/.github/skills/threat-model/SKILL.md index d2a81e5..33ff969 100644 --- a/.github/skills/threat-model/SKILL.md +++ b/.github/skills/threat-model/SKILL.md @@ -245,4 +245,4 @@ For each high-priority threat include: - Final report is written to `docs/architecture/threat-model.md`. <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"threat-model","artifact_type":"skill","artifact_version":"20260502021","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"threat-model","artifact_type":"skill","artifact_version":"20260502021","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/verify/SKILL.md b/.github/skills/verify/SKILL.md index 585a7df..eb4dfcd 100644 --- a/.github/skills/verify/SKILL.md +++ b/.github/skills/verify/SKILL.md @@ -266,4 +266,4 @@ scope: [path/component/full] ``` <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"verify","artifact_type":"skill","artifact_version":"20260421026","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"verify","artifact_type":"skill","artifact_version":"20260421026","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/skills/vision/SKILL.md b/.github/skills/vision/SKILL.md index 16dbb1b..bd3370d 100644 --- a/.github/skills/vision/SKILL.md +++ b/.github/skills/vision/SKILL.md @@ -206,4 +206,4 @@ For each finding: explain the tradeoff, give an opinionated recommendation, ask Present as: "Overall assessment: [READY/NEEDS REVISION/SCOPE CHANGE] because [1-2 sentence reason]." <!-- AUTO-GENERATED — maintained by vstack, do not edit directly --> -<!-- VSTACK-META: {"artifact_name":"vision","artifact_type":"skill","artifact_version":"20260421027","generator":"vstack","vstack_version":"0.0.0.post3.dev0+df3fe6e"} --> +<!-- VSTACK-META: {"artifact_name":"vision","artifact_type":"skill","artifact_version":"20260421027","generator":"vstack","vstack_version":"3.1.1.post2.dev0+4d3419b"} --> diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 78f15df..4d0033e 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -12,6 +12,7 @@ For the full CI/CD design and release model, see `docs/design/cicd.md`. | `check.yml` | push to non-main branches and PRs to `main` | Single-version unit test feedback (py3.11) | | `verify.yml` | pull_request to `main` | Required verification checks: cross-version test matrix + artifact verify | | `security.yml` | pull_request to `main` | Required security checks | +| `codeql.yml` | push/pull_request to `main` + weekly schedule | Code scanning for Actions and Python with CodeQL | | `automerge.yml` | pull_request_target to `main` | Dependabot safe auto-merge policy | | `release.yml` | push to `main` | Release Please orchestration | | `publish.yml` | release published | Build and publish to PyPI | @@ -20,7 +21,7 @@ For the full CI/CD design and release model, see `docs/design/cicd.md`. 1. `commit.yml` handles commit policy and lint/typecheck on branch pushes and PRs. 2. `check.yml` provides single-version test feedback on branch pushes and PRs. -3. `verify.yml` and `security.yml` are the required PR gates. +3. `verify.yml`, `security.yml`, and `codeql.yml` are the recommended PR security gates. 4. `release.yml` is the only release orchestrator. 5. `publish.yml` is publish-only and never computes versions. 6. Ruleset on `main` should require `Commit`, `Check`, `Verify` (all jobs), and `Security` before merge. diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 712a65d..5d7f0ed 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -58,13 +58,28 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - await github.rest.pulls.createReview({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.pull_request.number, - event: "APPROVE", - body: "Auto-approved for safe Dependabot update policy." - }) + try { + await github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + event: "APPROVE", + body: "Auto-approved for safe Dependabot update policy." + }) + } catch (error) { + const msg = String(error.message || "") + if ( + error.status === 422 && + msg.includes("GitHub Actions is not permitted to approve pull requests") + ) { + core.warning( + "Skipping auto-approve: repository setting blocks workflow approvals. " + + "Enable 'Allow GitHub Actions to create and approve pull requests' to restore this step." + ) + } else { + core.setFailed("Could not auto-approve eligible Dependabot PR. " + msg) + } + } - name: Update branch for eligible PR if: steps.decision.outputs.should_automerge == 'true' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..3fddf4d --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,38 @@ +name: CodeQL + +"on": + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "17 3 * * 1" + +permissions: + actions: read + contents: read + security-events: write + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + language: ["actions", "python"] + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v4 + + - name: Analyze + uses: github/codeql-action/analyze@v4 diff --git a/.vstack/config.yaml b/.vstack/config.yaml index 521566f..483fa9e 100644 --- a/.vstack/config.yaml +++ b/.vstack/config.yaml @@ -96,6 +96,15 @@ # # ----------------------------------------------------------------------------- +hooks: + enabled: true + mode: audit + log_level: minimal + log_retention_days: 7 + hooks: + session-audit: + log_level: minimal + workflow: mode: agentic version: 1 diff --git a/.vstack/vstack.json b/.vstack/vstack.json index d1b2186..674647f 100644 --- a/.vstack/vstack.json +++ b/.vstack/vstack.json @@ -1,316 +1,316 @@ { "manifest_version": 2, "hash_algorithm": "sha256", - "vstack_version": "0.0.0.post3.dev0+df3fe6e", - "installed_at": "2026-05-10T20:52:18.390186+00:00", + "vstack_version": "3.1.1.post2.dev0+4d3419b", + "installed_at": "2026-05-11T20:59:07.400274+00:00", "artifacts": { "skills": [ { "name": "adr", "file": "skills/adr/SKILL.md", "version": "20260421003", - "checksum": "871263fddee0b3ae8e2c2e2243aad4f2b3aa9e0dea0d2519c7d21c3e98430a83", + "checksum": "1def4de23e0bb887392fdc79f5a354416ed6851b4d9584411d2cb04a280f2259", "checksum_algorithm": "sha256" }, { "name": "analyse", "file": "skills/analyse/SKILL.md", "version": "20260421004", - "checksum": "79ced25692671a5286a26cbcd0e59ed4d641ac7493fd8e2b58b232f97e467224", + "checksum": "abfc4634c6a0123f95bf7a397e7c0334615c3c86da4cb02f18cd904fd33df917", "checksum_algorithm": "sha256" }, { "name": "architecture", "file": "skills/architecture/SKILL.md", "version": "20260421005", - "checksum": "e2d0342184829c888a70ece6f82ad6a7450ad5d8c958ff6e028d7f245f1e5960", + "checksum": "381c9ba6e33f2334f96227336c3cadab0ccdc78cf256c43a579fe31fde2689a9", "checksum_algorithm": "sha256" }, { "name": "aws-cli", "file": "skills/aws-cli/SKILL.md", "version": "20260502033", - "checksum": "4868a5adc17b6ac52839c85f3a09986991c2498501913ef48a8f85a6291e1b21", + "checksum": "484f61c7e9686f8f45e6e0e470bec05e638269ce760e9f029e19aa65a282647e", "checksum_algorithm": "sha256" }, { "name": "cicd", "file": "skills/cicd/SKILL.md", "version": "20260421006", - "checksum": "ba6c0cf0e644e24b1290c1b78969bad86dd7d5b39030edc7bd430d4d1832cfd7", + "checksum": "c58754d85c160836e96820770be3d7c2d3959091032c3e0eb65fb9898886a515", "checksum_algorithm": "sha256" }, { "name": "cloudformation", "file": "skills/cloudformation/SKILL.md", "version": "20260502032", - "checksum": "0e61163cfc356105a309bbecf89ee26a5b10b9b5db58c331d76efa4a508e760e", + "checksum": "a7576e06214a0e549d216818417677feae79d29b26485ddd7124c40e0349c51b", "checksum_algorithm": "sha256" }, { "name": "code-review", "file": "skills/code-review/SKILL.md", "version": "20260421007", - "checksum": "58b7caa9dd3a5da6bef8d8d4d1f7b6d513740d33ad16731a06b1b2a907d93a13", + "checksum": "48d6d8f5100875cd24540a8df8a3a8a1eccfcf353f8d649e9437d94ffa8374c0", "checksum_algorithm": "sha256" }, { "name": "codeql", "file": "skills/codeql/SKILL.md", "version": "20260502026", - "checksum": "95f29c975e5850307620c21a68df174b90ecaafd5b1e36659bb3c34d0027b5df", + "checksum": "87594771b5e46fd7a43d1a3d50528511358bc4094f336027ca5f00cdd0e13059", "checksum_algorithm": "sha256" }, { "name": "concise", "file": "skills/concise/SKILL.md", "version": "20260421008", - "checksum": "9555de7da6a925e76992b6f5e51ddccd6b4de3887ea31ed2ceb9113c9fe9bc7e", + "checksum": "2f92b948f975a5d418d0ec8204a55faf20a5f2576dee426d7f96e954501aa7ff", "checksum_algorithm": "sha256" }, { "name": "consult", "file": "skills/consult/SKILL.md", "version": "20260421009", - "checksum": "a340883900309261213ec0ea0d4e9e5b72ca83fb25c7cd6c3506e1f95f40cf76", + "checksum": "b760c5df32f6922dfc96d9ed7ed73715ac10b58d1570879476f9cf75c2a2bbd7", "checksum_algorithm": "sha256" }, { "name": "container", "file": "skills/container/SKILL.md", "version": "20260421010", - "checksum": "f0a4408a756195faca9a58dca76e56f950df227415d77af293bf55280191abd4", + "checksum": "9493acae27f345fe49553db6aa91b24a4575c4c31525989a79e3e70bcbfd9dff", "checksum_algorithm": "sha256" }, { "name": "conventional-commit", "file": "skills/conventional-commit/SKILL.md", "version": "20260502024", - "checksum": "8f1ce569106b5210a8d4ffe5bc9123eb0dec6b24ff56cf680922eccdd9891012", + "checksum": "401ea58fe12834d7786ce25eb142886020516eea2af8bf29c2a1cabcacb8e805", "checksum_algorithm": "sha256" }, { "name": "debug", "file": "skills/debug/SKILL.md", "version": "20260421011", - "checksum": "b4c1c6de7142b7b74326d1c19a5cc27f3957d1faa48aa48bd6b1fc45832edf3b", + "checksum": "3b62b8faf8c7c6316c915cff3bac03d363c0347b8ab6fbfe07cb23d0b266d58f", "checksum_algorithm": "sha256" }, { "name": "dependabot", "file": "skills/dependabot/SKILL.md", "version": "20260502027", - "checksum": "fa3f595740faa0b8093ffe4f32fb8e4b85f5834ea10817752ee9c94be8f38c2d", + "checksum": "509d421bae968bd6ee4364108a5cdb38bc4db3e931007db9b8f9faac218d1877", "checksum_algorithm": "sha256" }, { "name": "dependency", "file": "skills/dependency/SKILL.md", "version": "20260421012", - "checksum": "2cbbecca8e6697b8523301325a889a5ca88c520f6d35a3b98807b6c3449239f5", + "checksum": "b70c7b3005052ae09d95763a17d1116f1353c5bdfbcef92f200822724123fc71", "checksum_algorithm": "sha256" }, { "name": "design", "file": "skills/design/SKILL.md", "version": "20260421013", - "checksum": "2d56e34aca8d3cf5a3f327e3b69ab15707393ffc47607c91ec91dd5be86cd65b", + "checksum": "26d0130b03dd7710462763d015559d4df898682236a7619df999a3e338a90efa", "checksum_algorithm": "sha256" }, { "name": "docs", "file": "skills/docs/SKILL.md", "version": "20260421014", - "checksum": "d6ebf7d270eac7911aea09a34cb7ec4006c70064b7e6f58dfa84a405e6620d18", + "checksum": "7c13ddd443a33a854ce7f02fba9e50d6bfb60ecdd09e58edea4580d821d64cc6", "checksum_algorithm": "sha256" }, { "name": "explore", "file": "skills/explore/SKILL.md", "version": "20260421015", - "checksum": "914323f796ac95a0f73d404f1b7eb38d94ba8950ed1a4acc5d80600ba03fae8f", + "checksum": "699c3a1db16163e887c3e377f9618de8341cf77997bae6df4ad95f260be229ba", "checksum_algorithm": "sha256" }, { "name": "gdpr", "file": "skills/gdpr/SKILL.md", "version": "20260502029", - "checksum": "dfe60c4c59b7ae4dc66e98a885ec42b0e60eccc61fa1cb6fd269cd7eb3e0a94d", + "checksum": "796687e2877fb9af481fe4935ef185e93a17b179aa435bc696924bf91a9082d2", "checksum_algorithm": "sha256" }, { "name": "gh-issues", "file": "skills/gh-issues/SKILL.md", "version": "20260502025", - "checksum": "bc1f595283e7643de63c7519e994500b9434a0343154bfc404b842ce8e7028a8", + "checksum": "69e9f37144f418819028387f9769581b270c8797992df8fc09cf32db272249f6", "checksum_algorithm": "sha256" }, { "name": "gh-release", "file": "skills/gh-release/SKILL.md", "version": "20260502023", - "checksum": "10abe52f681d261588608418a546580e7798e883179888061ed6d2285050d870", + "checksum": "2c47e28a986239626a252d6d435e7a13d8a1d39284f14f7965336adee1ad7f00", "checksum_algorithm": "sha256" }, { "name": "guardrails", "file": "skills/guardrails/SKILL.md", "version": "20260421016", - "checksum": "1c1a44c95461eb4eeef7ad64b5846a1efbb202cf1b75186421c5d0b8f9fd03f1", + "checksum": "49bda4a6d23dbd3e272e034baa85daea3adbac226782a3921f0716b05c98cf0e", "checksum_algorithm": "sha256" }, { "name": "helm", "file": "skills/helm/SKILL.md", "version": "20260502037", - "checksum": "6e6579ede2dadb6dbe895d07c362d208e62414338445bd035f47bd1404be501b", + "checksum": "bd9057d8af61b4f1dacc637bfff80475a1ab594080dd88535f6d3f2c4a8258b5", "checksum_algorithm": "sha256" }, { "name": "incident", "file": "skills/incident/SKILL.md", "version": "20260503002", - "checksum": "546229fe91121dae2192aceb0b215096cc8cf5ba0cfb3d1300e30c73ff3f6276", + "checksum": "891fb9afe19b86010cbb8a810b47bd148c0f0df531d302e2b6d1ba91ae67d747", "checksum_algorithm": "sha256" }, { "name": "inspect", "file": "skills/inspect/SKILL.md", "version": "20260421018", - "checksum": "3a4aee3620bbd669602001c041cf81788da5fac9dc055a9f1b0c155e50e88b46", + "checksum": "e4fd6f8cbc837ac052cffa487f9cce14d8c0990e3f6285c6913ce44244dca6a8", "checksum_algorithm": "sha256" }, { "name": "k8s", "file": "skills/k8s/SKILL.md", "version": "20260502036", - "checksum": "2bfbe14c187809211a7296578644262e2e47c89b4a2fc28fc3a30a79fdaeb580", + "checksum": "f5319450a41d97e90a85f7eabd4fcabacef8288664fb5d5c884addc4892676a9", "checksum_algorithm": "sha256" }, { "name": "migrate", "file": "skills/migrate/SKILL.md", "version": "20260421019", - "checksum": "eecc00aae2c58c573000a434fbf7ae891a3e85dcc99a42fec0e8bcadc3047b7d", + "checksum": "f29f99ec9a5d324bb2d1628b8add0fac57cda66471633a1c23c66a46fdee02c6", "checksum_algorithm": "sha256" }, { "name": "onboard", "file": "skills/onboard/SKILL.md", "version": "20260421020", - "checksum": "f16f2d78002e3be8e7d1d9e2bf376c1b37eb181d821f3cd113e0d5aecf5236ae", + "checksum": "b2c4ac2679a31515d0ea6a1161076aeea19ef5b41f058f93471e077d098e9412", "checksum_algorithm": "sha256" }, { "name": "openapi", "file": "skills/openapi/SKILL.md", "version": "20260421021", - "checksum": "a3dbcded8e1a8b268a1976b9185f41de74ad0ad6e7aae98c399d6135de920529", + "checksum": "e7fda43ee964e2e31efd7f82dd7b11a28679fbe9f20b9a8faf6975719e518a68", "checksum_algorithm": "sha256" }, { "name": "performance", "file": "skills/performance/SKILL.md", "version": "20260421022", - "checksum": "fa4062c77381facacedac289b54a3ce7e6cf242dfa2b36f55401aebfe5fb8ae1", + "checksum": "9c27e5eda062cbd881d2073f0de4dac41b971adb024f1e1e0f28dd44393e8784", "checksum_algorithm": "sha256" }, { "name": "postmortem", "file": "skills/postmortem/SKILL.md", "version": "20260503001", - "checksum": "fc70f26420e705e0979ff76476ea8176386258f2315fcb57f5ac9049a37f6c90", + "checksum": "9197bed5f5bebb64988c639e4feb5ed264c72738d176875a0ce5d063b1e1d3aa", "checksum_algorithm": "sha256" }, { "name": "pr", "file": "skills/pr/SKILL.md", "version": "20260502013", - "checksum": "fc7b2b08696982afd48033a3cc29b9fa4ac8b590fc61c828b84135585c88b345", + "checksum": "9bcb8180cc74e8fa956818f3b5d27112fd73c289d966e8c2bb0a53109b9274ed", "checksum_algorithm": "sha256" }, { "name": "rancher", "file": "skills/rancher/SKILL.md", "version": "20260502038", - "checksum": "3000f24c2ee2f99fdb363f30d6075993ed26533663d081089b7800098a28e648", + "checksum": "26c67ca9a32e778f4b574fab91b33d00b425bc9a7ceb7177fb1558ebe1222951", "checksum_algorithm": "sha256" }, { "name": "rca", "file": "skills/rca/SKILL.md", "version": "20260503001", - "checksum": "8bebb57c166c153d89e4589097ea419266b249df871a79e24c840112b5f8ad0c", + "checksum": "f7e6a0fc4950136bf0165ba1051685c5b297779ea97aa76f1ca0631f593401b1", "checksum_algorithm": "sha256" }, { "name": "refactor", "file": "skills/refactor/SKILL.md", "version": "20260421023", - "checksum": "aa7fb995b6ac649b24b61066f15a6fe7fddab81bdcec8af27eba65092b957d68", + "checksum": "69b28c4a57134f29c6c59366b06ea7cf3468cc0db219d120567657abb0765801", "checksum_algorithm": "sha256" }, { "name": "release-notes", "file": "skills/release-notes/SKILL.md", "version": "20260502014", - "checksum": "589f001bcbf0e27c4da6ff9c530371a1f183b7936e3a7fc421879282bd7f9af3", + "checksum": "9b11a8bfc973ce1faf139cd90190e187955ee2334f7694049e39922f2e5d71c7", "checksum_algorithm": "sha256" }, { "name": "requirements", "file": "skills/requirements/SKILL.md", "version": "20260421024", - "checksum": "7d3338f0b7c5d493ab714a90b7d1732de56467503a3fe4dacdd40031bfea356c", + "checksum": "0a741ec2a7a899affef84a613d7a97d501feaf14e44355f3633aa199375c7afe", "checksum_algorithm": "sha256" }, { "name": "secret-scan", "file": "skills/secret-scan/SKILL.md", "version": "20260502028", - "checksum": "95df047af89a93a8fa3716899b8fe703b270d7f40b93fe65915f7d1bdb849370", + "checksum": "7ee0aadca4d6dac2d8c301ab51105a11587275ef5ed2700d50b786380040d29f", "checksum_algorithm": "sha256" }, { "name": "security", "file": "skills/security/SKILL.md", "version": "20260421025", - "checksum": "e9bcd451ee25b1c752239e76e20ca67302061356b59214fae7b134a180022df8", + "checksum": "ea7fc8189f6187a2dfa99114d1463d31b0afe147c0853406d80f90e15da5fd3c", "checksum_algorithm": "sha256" }, { "name": "terraform", "file": "skills/terraform/SKILL.md", "version": "20260502030", - "checksum": "2bb933512f72b61dae3a3b632a7afd43c62aad066de003ee0a565fc8f1895215", + "checksum": "6666debeef5f59910a0374c811744f3d29c7695a59ee141935c832bcc020c069", "checksum_algorithm": "sha256" }, { "name": "terragrunt", "file": "skills/terragrunt/SKILL.md", "version": "20260502031", - "checksum": "e0d3dc0feefeafb9cd41d8d6885d8b4d53baed52ba70df7dc8d33bfc7410bae8", + "checksum": "518577f40465f9ef94b90244deb3886206179c32ea533548267fe0b9ebda7190", "checksum_algorithm": "sha256" }, { "name": "threat-model", "file": "skills/threat-model/SKILL.md", "version": "20260502021", - "checksum": "7fdf69113167e9bb8c866681a86f4b4d6416145bb9def0ebe18ceba690c8fbe9", + "checksum": "a5d4d0f78a020b259337c446ad2a4abed37da7e6cfd70f9bbf3733f9e06f40f5", "checksum_algorithm": "sha256" }, { "name": "verify", "file": "skills/verify/SKILL.md", "version": "20260421026", - "checksum": "aa56d1a674ed827606af34b8e7c5bd9c2de61a6cb6f92f5fd9bdce28e46bcb30", + "checksum": "c242872c3542a4aaa54d1ce062e0432df6edde0a8678ac953e4729b5a93fb144", "checksum_algorithm": "sha256" }, { "name": "vision", "file": "skills/vision/SKILL.md", "version": "20260421027", - "checksum": "c0058202130905d8495a3e565b5db546ad2d0ff6d654596358a0b12c484ce310", + "checksum": "9dce0d40a2d80e8b5f521b0f36761667acbaf9b9a8e53f68f00118d27f8f90a4", "checksum_algorithm": "sha256" } ], @@ -319,86 +319,93 @@ "name": "architect", "file": "agents/architect.agent.md", "version": "20260503022", - "checksum": "99aa5d66f0d9039f7759c8011ab1a835b7262aebd6a4f7df591898d5102b8b94", + "checksum": "e097b021cee7e3a7bab7d75512a372b5aee073fd3d01ad28c4a7c582b569eebc", "checksum_algorithm": "sha256" }, { "name": "designer", "file": "agents/designer.agent.md", "version": "20260503024", - "checksum": "394717ed714328b3c92278a4de33b2bd067ee9ed100529c557bf71f5d777e0fb", + "checksum": "b7240cc02e1da79f962384ed5ac02ac769211f85c8f5976b8e8251168eb9deb7", "checksum_algorithm": "sha256" }, { "name": "engineer", "file": "agents/engineer.agent.md", "version": "20260503024", - "checksum": "2e6de55f6bb292cb750c6403be74db2dbe913db0ac64936a4f066945f88f88d8", + "checksum": "91ce873180353b58f41873a8ec2e4a448cc9f08871b81bc785fd182e0e1d1cde", "checksum_algorithm": "sha256" }, { "name": "planner", "file": "agents/planner.agent.md", "version": "20260510001", - "checksum": "73189a08e16d5e8f444471448bc4f26cf6dbbc290cb072d99fa6a768bfbc9bda", + "checksum": "18402e3cd9653e38943f23cb391147ede02b659b76847856ad678668c89b9f39", "checksum_algorithm": "sha256" }, { "name": "product", "file": "agents/product.agent.md", "version": "20260503021", - "checksum": "94c9a9ceaaa626ff20351e1c7f268b4d67464c23d2aae2070ba9385209888c50", + "checksum": "1bf2088c0c1d2750ede7e9c87d6e10dc4016db7f2aacce217be37ec8f4bc58b9", "checksum_algorithm": "sha256" }, { "name": "release", "file": "agents/release.agent.md", "version": "20260503020", - "checksum": "ea226eb56b001e3906e07ed552867fded99d2555f4c8e4b269619ae27cbb0194", + "checksum": "823cdefe1ee2aa3b4700da629a83f1efee0c8503cf0d214926d489a5b85720ea", "checksum_algorithm": "sha256" }, { "name": "tester", "file": "agents/tester.agent.md", "version": "20260503026", - "checksum": "27dd3baeeaad59b9a6a1a5cab0896282771e66278cf02d27820e3740b8793366", + "checksum": "2ab7d38e1bc24d530aaa784e8852ddd9f2c68c6d163466d6d608711062403197", "checksum_algorithm": "sha256" } ], "hooks": [ + { + "name": "log-retention-cleanup", + "file": "hooks/log-retention-cleanup.json", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "76f2ecb32c1ed259ee155a2e3f3332a06dffdb3d25514b62b3e81e9c2a1b9cad", + "checksum_algorithm": "sha256" + }, { "name": "post-commit-security-scan", "file": "hooks/post-commit-security-scan.json", - "version": "0.0.0.post3.dev0+df3fe6e", - "checksum": "4360b68090aa9c8e7f1cdce31924d5bacbabdc3e0d70cc618894b4049b0c9c33", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "4f83d6421d5b46b8f8ac180d241433f9f52177b00911668ab7275f0b5f22cb94", "checksum_algorithm": "sha256" }, { "name": "post-edit-format", "file": "hooks/post-edit-format.json", - "version": "0.0.0.post3.dev0+df3fe6e", - "checksum": "add9f0a051add1c43d6016929c53d0bbde990b381360942cc4584ff0edbf5a5c", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "65adaf057b03d92c4b80a52e66c281e4af814392ff7a1bf1ce3ad2d446e9da81", "checksum_algorithm": "sha256" }, { "name": "post-edit-markdown-quality", "file": "hooks/post-edit-markdown-quality.json", - "version": "0.0.0.post3.dev0+df3fe6e", - "checksum": "5d7dbe986445710e99318586f40ff71385f4fb0c6fe2cb6b86a6f605cd2d4969", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "fab9f3326adfdcab5e98ab32a9d351108f422f027b6bd310c3d7eef4b084362b", "checksum_algorithm": "sha256" }, { "name": "pre-tool-safety-gate", "file": "hooks/pre-tool-safety-gate.json", - "version": "0.0.0.post3.dev0+df3fe6e", - "checksum": "b1d92cfcd73dde9a47404159f89d32b1d61944fb786f4455c614e278a09eec4a", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "1ebc5b9044c7e4553f3eed9df344092f8961725424ed45d5411c298c952fd321", "checksum_algorithm": "sha256" }, { "name": "session-audit", "file": "hooks/session-audit.json", - "version": "0.0.0.post3.dev0+df3fe6e", - "checksum": "b6a4c2a2fd78eb8a2ef087f25b2603ddf1d6081cff1cd741e572e6c459748965", + "version": "3.1.1.post2.dev0+4d3419b", + "checksum": "4f529b63b6dc323f3aceb7d8a8515e95c61273356c650057c9d2d60ec17dbcec", "checksum_algorithm": "sha256" } ], @@ -407,84 +414,84 @@ "name": "git", "file": "instructions/git.instructions.md", "version": "20260421001", - "checksum": "ed6a191176e32631e2d572cb21278b555f6696a859839579821631156a8b35a5", + "checksum": "b0b866596988dab9e3b48b6cf1cb8de82b46cddfdcfe57214ee85308c2cf0e13", "checksum_algorithm": "sha256" }, { "name": "helm", "file": "instructions/helm.instructions.md", "version": "20260502040", - "checksum": "a60520853b79751517f089136ba7fb182feb1ea8e9c9495890e5c14ff32afd7b", + "checksum": "b1b5d2e535cf6a30fa86121c3e58081e77149242a16dabdb153291e4349f3015", "checksum_algorithm": "sha256" }, { "name": "java", "file": "instructions/java.instructions.md", "version": "20260502001", - "checksum": "69dc2bf3a5428ed03d77c2789871985ea7c5881af89c2b414548fca6e9488464", + "checksum": "663847977d195426234282780c53660fad2d5b06767e2848ec1afbef379e699a", "checksum_algorithm": "sha256" }, { "name": "k8s", "file": "instructions/k8s.instructions.md", "version": "20260502039", - "checksum": "f19f1060bf2a1424950496290a43a3535f9749e4ed838cf7475fe35eb9487d67", + "checksum": "95a7d12d5518eab2d91a20f5fe84e6f66af52dfb21b8ecb256d6b050144d8f03", "checksum_algorithm": "sha256" }, { "name": "markdown", "file": "instructions/markdown.instructions.md", "version": "20260502002", - "checksum": "59c93c5b0e63360aff18ab3a7a207f4e798efd1f2c26b0a561a4ef4ea688c5c5", + "checksum": "901ba40d993be4477057626039a4f4c6c0c6a49561429af5cb4cac016ebe3be2", "checksum_algorithm": "sha256" }, { "name": "python", "file": "instructions/python.instructions.md", "version": "20260421002", - "checksum": "ac40fffd3d3a3f9f8ca43e10ca603a187578c9d517533529a3d647b61cedf56c", + "checksum": "346e468cbf8249a8be1ae8881e1ac849ca0646e07ce044dc8e0e5947357293b6", "checksum_algorithm": "sha256" }, { "name": "rancher", "file": "instructions/rancher.instructions.md", "version": "20260502041", - "checksum": "4e643b1e078e9e8f127f1f2cf7707b36109e28b8697e208d8d3804848b867eec", + "checksum": "b0ea1821e3deac543599a2b838cb50d73cc663cffe95aecf7455279721cab294", "checksum_algorithm": "sha256" }, { "name": "security", "file": "instructions/security.instructions.md", "version": "20260502003", - "checksum": "a943df637e44cb23c8e82f8b39bf70558bc502ece5af9a03a15d8492cffb51a4", + "checksum": "a247eb547961349f52032152ed4ff1b9d77d6c268db8251d803a87ab31dd0ca5", "checksum_algorithm": "sha256" }, { "name": "terraform", "file": "instructions/terraform.instructions.md", "version": "20260502034", - "checksum": "1bada82da46a6359bb1814b6642074be6c0b7de9c2c6ca59e031a4d05ea4ea8c", + "checksum": "f0ac9c36bba8053351b1993418b4ae16f2db49b2bddf69e62ee1638f2a649908", "checksum_algorithm": "sha256" }, { "name": "terragrunt", "file": "instructions/terragrunt.instructions.md", "version": "20260502035", - "checksum": "13c0059c366624ab482c2b3d24a353bcb0a2ffbb6bf735abd18796969a3e4020", + "checksum": "37bb85ec3f2ab1c1bcaedd05da7f4df15e28a0ee32f74307ed53ad4733c028a5", "checksum_algorithm": "sha256" }, { "name": "testing", "file": "instructions/testing.instructions.md", "version": "20260502004", - "checksum": "cbd1948f367c32c39032209e5ed9fcfff8ce6c46c6324a4aa9a8550365873ca3", + "checksum": "11dd196e151c37d59585b77e7e95dddd2c395cf38b210ed2a9b88c83ccd0d756", "checksum_algorithm": "sha256" }, { "name": "typescript", "file": "instructions/typescript.instructions.md", "version": "20260502005", - "checksum": "fe412ba2e60baea66d0d07ae0c153fa2a9157bd57556fa7476f46ce466160ad3", + "checksum": "8fa6c892d0556be68e324e005bbb681024b7d41dcd89ba26c70874998d1d78bd", "checksum_algorithm": "sha256" } ], @@ -493,49 +500,49 @@ "name": "api-design-review", "file": "prompts/api-design-review.prompt.md", "version": "20260502006", - "checksum": "1ba62a6f78b836256fe578c4ca312de24599c79b018119b81a100f6de4e0da11", + "checksum": "1cc8cc182c052dd7e5d5c253a9549609f243a184b71d74aa6c092b9270692488", "checksum_algorithm": "sha256" }, { "name": "architecture-risk", "file": "prompts/architecture-risk.prompt.md", "version": "20260502007", - "checksum": "14fa36e36948309827c1c1296ff2c8b1f1306cfb19057ef190a5e60f6cc04a61", + "checksum": "4bc2830e1914c2a13cf849a8d67bc3e18355b4a1c24f001feaf88876c7131ba4", "checksum_algorithm": "sha256" }, { "name": "code-review", "file": "prompts/code-review.prompt.md", "version": "20260502008", - "checksum": "b6499ca66706a08ced7b9bf69bd288c2fecb684e1c85e333ee91d36ba7e66c3f", + "checksum": "02d98a41849a1cbb92c642a917850d9d689e3a4713dbb8b4e4ae5b22fe0fa801", "checksum_algorithm": "sha256" }, { "name": "dependency-audit", "file": "prompts/dependency-audit.prompt.md", "version": "20260502009", - "checksum": "51cead168dbbe52b455813cc620a8426ec287ceb2994831aa6ae115b4e49d0b0", + "checksum": "22c5d3f7f8ead6d750bf89fe1aa61c4a458fa7de3e78f3956cf363222965c8ac", "checksum_algorithm": "sha256" }, { "name": "incident-timeline", "file": "prompts/incident-timeline.prompt.md", "version": "20260502010", - "checksum": "28627dc7c362d510f312281fe0bda30e1927998bae440808af3a92d06c455393", + "checksum": "45729a09df502e63d0542a03690e9557ba6eb1f88bb50d4898233e749c179b58", "checksum_algorithm": "sha256" }, { "name": "migration-safety", "file": "prompts/migration-safety.prompt.md", "version": "20260502011", - "checksum": "814258882774ff98724b14caa0bc9ee35dc68f46e72e62b24884052e79ffacfa", + "checksum": "71f9f7ec5f1d714cd5d80b0582544a7bcc84a26cf5e3325b0846c9879a33fe14", "checksum_algorithm": "sha256" }, { "name": "release-readiness", "file": "prompts/release-readiness.prompt.md", "version": "20260502012", - "checksum": "49898a07169504acba1c2195abf730be77d1e17298cb4676f3ed926a27e31dc6", + "checksum": "ee729edd2e79b1c44ced2ca085422c0e3b170201c3dae6a67d14a6b1c861f87a", "checksum_algorithm": "sha256" } ] diff --git a/README.md b/README.md index 9d16e9e..0551f90 100644 --- a/README.md +++ b/README.md @@ -1038,15 +1038,16 @@ ______________________________________________________________________ ## 🚦 CI and Release Automation -| Workflow | Trigger | Purpose | -| --------------- | ------------------------------------------ | ------------------------------------------------------------------- | -| `commit.yml` | Push to non-main branches and PR to `main` | commit/branch policy and lint/typecheck gate | -| `check.yml` | Push to non-main branches and PR to `main` | single-version unit tests (py3.11) | -| `verify.yml` | Pull request to `main` | cross-version test matrix (py3.11–3.14) and artifact install/verify | -| `security.yml` | Pull request to `main` | dependency audit and secret scanning | -| `automerge.yml` | Pull request target to `main` | safe Dependabot auto-merge policy | -| `release.yml` | Push to `main` | Release Please orchestration (release PR, changelog, tags) | -| `publish.yml` | GitHub release `published` | build from release tag and publish to PyPI | +| Workflow | Trigger | Purpose | +| --------------- | --------------------------------------------- | ------------------------------------------------------------------- | +| `commit.yml` | Push to non-main branches and PR to `main` | commit/branch policy and lint/typecheck gate | +| `check.yml` | Push to non-main branches and PR to `main` | single-version unit tests (py3.11) | +| `verify.yml` | Pull request to `main` | cross-version test matrix (py3.11–3.14) and artifact install/verify | +| `security.yml` | Pull request to `main` | dependency audit and secret scanning | +| `codeql.yml` | Push/pull request to `main` + weekly schedule | code scanning for GitHub Actions and Python | +| `automerge.yml` | Pull request target to `main` | safe Dependabot auto-merge policy | +| `release.yml` | Push to `main` | Release Please orchestration (release PR, changelog, tags) | +| `publish.yml` | GitHub release `published` | build from release tag and publish to PyPI | Commit policy specifics: @@ -1058,7 +1059,7 @@ Commit policy specifics: Recommended branch protection for `main`: - Require PR before merge. -- Require status checks from `commit.yml`, `check.yml`, `verify.yml`, and `security.yml`. +- Require status checks from `commit.yml`, `check.yml`, `verify.yml`, `security.yml`, and `codeql.yml`. - Disallow force pushes and branch deletion. Full pipeline documentation: [docs/design/cicd.md](docs/design/cicd.md) diff --git a/docs/design/cicd.md b/docs/design/cicd.md index 3e400e7..e2a035d 100644 --- a/docs/design/cicd.md +++ b/docs/design/cicd.md @@ -25,6 +25,7 @@ Their jobs are merge-blocking when configured as required status checks in the ` | `.github/workflows/check.yml` | push to non-main branches, pull_request to `main` | Single-version unit tests (py3.11) | | `.github/workflows/verify.yml` | pull_request to `main` | Cross-version test matrix (py3.11–3.14) and artifact install verify | | `.github/workflows/security.yml` | pull_request to `main` | Dependency vulnerability scan and secret scan | +| `.github/workflows/codeql.yml` | push/pull_request to `main` + weekly schedule | Code scanning for GitHub Actions and Python | | `.github/workflows/automerge.yml` | pull_request_target to `main` | Dependabot auto-approve/auto-merge policy gate | | `.github/workflows/release.yml` | push to `main`, workflow_dispatch | Release Please orchestration: release PR lifecycle, changelog, tag, GitHub release | | `.github/workflows/publish.yml` | release `published` | Build artifacts from release tag and publish to PyPI | @@ -186,13 +187,15 @@ Configure via **Settings → Rules → Rulesets** on GitHub. - **Require a pull request before merging**: enabled. - **Required approvals**: at least 1. -- **Require status checks to pass**: add the following checks: - - `Commit / Validate Commit Messages` - - `Commit / Format Lint Typecheck` - - `Check / Unit Tests` - - `Verify / Tests (py3.11)`, `Verify / Tests (py3.12)`, `Verify / Tests (py3.13)`, `Verify / Tests (py3.14)` - - `Verify / Artifact Install Verify` - - `Security / Dependency and Secret Scan` +- **Require status checks to pass**. +- Required checks: +- `Commit / Validate Commit Messages` +- `Commit / Format Lint Typecheck` +- `Check / Unit Tests` +- `Verify / Tests (py3.11)`, `Verify / Tests (py3.12)`, `Verify / Tests (py3.13)`, `Verify / Tests (py3.14)` +- `Verify / Artifact Install Verify` +- `Security / Dependency and Secret Scan` +- `CodeQL / Analyze (actions)`, `CodeQL / Analyze (python)` - Release PRs created by the GitHub App token (`vstack-release-bot[bot]`) trigger `pull_request` events normally — these checks run on release PRs the same as on any other PR. - **Allowed merge methods**: must include **Squash** — required for `automerge.yml` to enable auto-merge for Dependabot PRs. @@ -247,6 +250,7 @@ commit. No manual tracking is needed. - `.github/workflows/check.yml` - `.github/workflows/verify.yml` - `.github/workflows/security.yml` +- `.github/workflows/codeql.yml` - `.github/workflows/automerge.yml` - `.github/workflows/release.yml` - `.github/workflows/publish.yml` diff --git a/docs/design/hook-yaml-schema.md b/docs/design/hook-yaml-schema.md index 5379606..89195f7 100644 --- a/docs/design/hook-yaml-schema.md +++ b/docs/design/hook-yaml-schema.md @@ -108,6 +108,9 @@ ______________________________________________________________________ ## Example: Complete Hook Template +This is a minimal example to show the schema shape. Production hooks in vstack also +use a dated log directory pattern and dedicated retention cleanup logic. + ```yaml version: 20260510003 @@ -193,7 +196,15 @@ bash: | - `VSTACK_HOOKS_MODE` (string: `audit` | `enforce`) - Default: `audit` if unset - User can override in session -- `.vstack/logs/` for structured JSONL audit output +- `VSTACK_HOOK_LOG_DIR` (string path) + - Default: `.vstack/logs` + - Hooks write under dated directories: `<log_root>/YYYYMMDD/` +- `VSTACK_HOOKS_LOG_RETENTION_DAYS` (positive integer) + - Default: `7` + - Used by `log-retention-cleanup` to prune older dated directories +- `VSTACK_HOOKS_CLEANUP_LOG` (`0` | `1`) + - Default: `0` + - When set to `1`, retention hook emits cleanup summary logs even when no deletions occur - `.vstack/config.yaml` available for shared settings ______________________________________________________________________ diff --git a/docs/design/hooks.md b/docs/design/hooks.md index 9d98257..0341d7e 100644 --- a/docs/design/hooks.md +++ b/docs/design/hooks.md @@ -1,7 +1,7 @@ # vstack - hooks design > Maintained by: **designer** role\ -> Last updated: 2026-05-10 +> Last updated: 2026-05-11 ## overview @@ -66,15 +66,16 @@ ______________________________________________________________________ ## 2. baseline hook set -vstack ships five default repository hooks: +vstack ships six default repository hooks: -| Hook name | Primary events | Intent | -| ---------------------------- | --------------------------------------------------- | ----------------------------------------------------------- | -| `session-audit` | `userPromptSubmitted`, `sessionStart`, `sessionEnd` | Structured JSONL session and prompt audit trail | -| `pre-tool-safety-gate` | `preToolUse`, `errorOccurred` | Deny destructive shell patterns + error logging | -| `post-edit-format` | `postToolUse` | Edit-event logging + optional `make format` run | -| `post-edit-markdown-quality` | `postToolUse` | vstack markdown/work-item formatting for docs and templates | -| `post-commit-security-scan` | `postToolUse`, `sessionEnd` | Git-mutation checks + optional `gitleaks` run | +| Hook name | Primary events | Intent | +| ---------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `session-audit` | `userPromptSubmitted`, `sessionStart`, `sessionEnd`, `preToolUse`, `postToolUse` | Structured JSONL session, prompt, and tool telemetry audit trail | +| `log-retention-cleanup` | `sessionStart` | Prune dated log directories by retention policy | +| `pre-tool-safety-gate` | `preToolUse`, `errorOccurred` | Deny destructive shell patterns + error logging | +| `post-edit-format` | `postToolUse` | Edit-event logging + optional `make format` run | +| `post-edit-markdown-quality` | `postToolUse` | vstack markdown/work-item formatting for docs and templates | +| `post-commit-security-scan` | `postToolUse`, `sessionEnd` | Git-mutation checks + optional `gitleaks` run | These defaults are safe in audit mode and can be upgraded to enforcement behavior. For vstack specifically, the markdown-quality hook gives the baseline hook set a direct payoff on ADRs, diff --git a/docs/design/workflow.md b/docs/design/workflow.md index 6b22e1b..dd88bf4 100644 --- a/docs/design/workflow.md +++ b/docs/design/workflow.md @@ -81,6 +81,7 @@ and easy to reason about. | `.github/workflows/check.yml` | Push to non-main branches and pull requests to `main` | Single-version unit tests (py3.11) for fast feedback. | | `.github/workflows/verify.yml` | Pull request to `main` | Cross-version test matrix (py3.11–3.14) and artifact install/verify flow. | | `.github/workflows/security.yml` | Pull request to `main` | Dependency vulnerability audit and secret scan. | +| `.github/workflows/codeql.yml` | Push/pull request to `main` + weekly schedule | Code scanning for GitHub Actions and Python. | | `.github/workflows/automerge.yml` | Pull request target to `main` | Dependabot safe auto-merge policy for eligible updates. | | `.github/workflows/release.yml` | Push to `main` | Run release-please to maintain release PRs and create tags/releases when merged. | | `.github/workflows/publish.yml` | GitHub release published | Build package artifacts from the release tag and publish to PyPI. | diff --git a/docs/product/roadmap.md b/docs/product/roadmap.md index b8e480b..81aef9f 100644 --- a/docs/product/roadmap.md +++ b/docs/product/roadmap.md @@ -164,10 +164,11 @@ GitHub Copilot agents support a repository-level hooks mechanism: shell commands vstack now provides installable repository hook templates for quality-gate patterns: - **Pre-tool safety gate** (`preToolUse`) — block or log destructive operations before they run -- **Session audit log** (`sessionStart` / `sessionEnd`) — record session boundaries with timestamp and working directory -- **Auto-format on edit** (`postToolUse`) — trigger `ruff format`, `terraform fmt`, `mdformat` after file edits -- **Commit policy check** (`postToolUse`) — run `cchk` or commit-message lint after `git commit` tool calls -- **Security scan on push** (`postToolUse`) — run `gitleaks` or `detect-secrets` after repository mutations +- **Session audit log** (`sessionStart` / `sessionEnd` / `userPromptSubmitted` / `preToolUse` / `postToolUse`) — record session boundaries and lightweight prompt/tool telemetry +- **Log retention cleanup** (`sessionStart`) — prune old dated log directories based on retention settings +- **Auto-format on edit** (`postToolUse`) — optionally run `make format` in enforce mode +- **Markdown quality check** (`postToolUse`) — run markdown/work-item formatting checks for docs and templates +- **Post-commit security scan** (`postToolUse` / `sessionEnd`) — run staged secret checks with optional `gitleaks` in enforce mode Implemented: @@ -181,8 +182,10 @@ Implemented: Shipped default hook set: - `session-audit` +- `log-retention-cleanup` - `pre-tool-safety-gate` - `post-edit-format` +- `post-edit-markdown-quality` - `post-commit-security-scan` Migration path: diff --git a/src/vstack/_templates/hooks/log-retention-cleanup/hook.yaml b/src/vstack/_templates/hooks/log-retention-cleanup/hook.yaml new file mode 100644 index 0000000..a2a5350 --- /dev/null +++ b/src/vstack/_templates/hooks/log-retention-cleanup/hook.yaml @@ -0,0 +1,108 @@ +version: 20260511003 + +metadata: + name: log-retention-cleanup + description: | + Dedicated housekeeping hook that prunes dated log directories based on + VSTACK_HOOKS_LOG_RETENTION_DAYS. This stays active even if session-audit + is disabled. Set VSTACK_HOOKS_CLEANUP_LOG=1 to emit cleanup summary logs. + purpose: audit + security_level: low + mode_default: audit + execution_context: copilot-hook-runtime + dependencies: + required: [] + optional: [] + +hooks: + sessionStart: + - type: command + description: | + Prune dated log directories older than the configured retention window. + bash: | + retention_days="${VSTACK_HOOKS_LOG_RETENTION_DAYS:-7}" + cleanup_debug="${VSTACK_HOOKS_CLEANUP_LOG:-0}" + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + mkdir -p "$log_root" + deleted_count=0 + oldest_deleted_date="" + cleanup_error="" + if [ "$retention_days" -gt 0 ] 2>/dev/null; then + scan_file="${TMPDIR:-/tmp}/vstack-hook-retention-$$.list" + if find "$log_root" -mindepth 1 -maxdepth 1 -type d -mtime +"$retention_days" -print0 > "$scan_file" 2>/dev/null; then + while IFS= read -r -d '' dir; do + dir_name="$(basename "$dir")" + if [[ "$dir_name" =~ ^[0-9]{8}$ ]]; then + if rm -rf "$dir"; then + deleted_count=$((deleted_count + 1)) + if [ -z "$oldest_deleted_date" ] || [ "$dir_name" -lt "$oldest_deleted_date" ]; then + oldest_deleted_date="$dir_name" + fi + else + cleanup_error="delete-failed" + fi + fi + done < "$scan_file" + else + cleanup_error="find-failed" + fi + rm -f "$scan_file" + else + cleanup_error="invalid-retention-days" + fi + + if [ "$cleanup_debug" = "1" ] || [ "$deleted_count" -gt 0 ] || [ -n "$cleanup_error" ]; then + log_name="hook-retention-cleanup.log" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + log_path="$log_dir/$log_name" + ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf '%s\n' "{\"ts\":\"$ts\",\"event\":\"retention-cleanup\",\"retention_days\":$retention_days,\"deleted_count\":$deleted_count,\"oldest_deleted_date\":\"$oldest_deleted_date\",\"error\":\"$cleanup_error\"}" >> "$log_path" + fi + powershell: | + $retentionDays = if ($env:VSTACK_HOOKS_LOG_RETENTION_DAYS) { [int]$env:VSTACK_HOOKS_LOG_RETENTION_DAYS } else { '7' } + $cleanupDebug = if ($env:VSTACK_HOOKS_CLEANUP_LOG) { $env:VSTACK_HOOKS_CLEANUP_LOG } else { '0' } + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + New-Item -ItemType Directory -Force -Path $logRoot | Out-Null + $deletedCount = 0 + $oldestDeletedDate = '' + $cleanupError = '' + if ($retentionDays -gt 0) { + $threshold = (Get-Date).ToUniversalTime().Date.AddDays(-$retentionDays) + Get-ChildItem $logRoot -Directory -ErrorAction SilentlyContinue | + Where-Object { + $_.Name -match '^\d{8}$' -and + ([datetime]::ParseExact($_.Name, 'yyyyMMdd', [System.Globalization.CultureInfo]::InvariantCulture).Date -lt $threshold) + } | + ForEach-Object { + try { + Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction Stop + $deletedCount++ + if (-not $oldestDeletedDate -or $_.Name -lt $oldestDeletedDate) { + $oldestDeletedDate = $_.Name + } + } catch { + $cleanupError = 'delete-failed' + } + } + } else { + $cleanupError = 'invalid-retention-days' + } + + if ($cleanupDebug -eq '1' -or $deletedCount -gt 0 -or $cleanupError) { + $logName = 'hook-retention-cleanup.log' + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + $logPath = Join-Path $logDir $logName + $entry = @{ + ts = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + event = 'retention-cleanup' + retention_days = $retentionDays + deleted_count = $deletedCount + oldest_deleted_date = $oldestDeletedDate + error = $cleanupError + } | ConvertTo-Json -Compress + Add-Content -Path $logPath -Value $entry + } + cwd: "." + timeoutSec: 10 diff --git a/src/vstack/_templates/hooks/post-commit-security-scan/hook.yaml b/src/vstack/_templates/hooks/post-commit-security-scan/hook.yaml index f433376..44b455c 100644 --- a/src/vstack/_templates/hooks/post-commit-security-scan/hook.yaml +++ b/src/vstack/_templates/hooks/post-commit-security-scan/hook.yaml @@ -6,6 +6,9 @@ metadata: Staged diff security scanner. Detects git mutations and pattern-matches staged secrets (AWS keys, GitHub tokens, private keys). Runs optional gitleaks scan when VSTACK_HOOKS_MODE=enforce for deeper compliance scanning. + log: + name: hook-security-alerts.log + retention_days: 30 purpose: security security_level: high mode_default: audit @@ -20,60 +23,51 @@ hooks: postToolUse: - type: command description: | - Check staged diffs for secrets and log alerts to audit trail. + Check staged diffs for secrets and log only security alerts. bash: | - mkdir -p .vstack/logs input="$(cat)" - printf '%s\n' "$input" >> .vstack/logs/hook-post-commit.jsonl + log_name="${VSTACK_HOOK_LOG_NAME:-hook-security-alerts.log}" + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + log_path="$log_dir/$log_name" if ! printf '%s' "$input" | grep -Eiq 'git (commit|push|merge|rebase)'; then exit 0 fi if git rev-parse --git-dir >/dev/null 2>&1 && \ git diff --cached 2>/dev/null | grep -Eiq 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----'; then - printf '%s\n' 'possible-secrets-detected-in-staged-diff' >> .vstack/logs/hook-security-alerts.log + printf '%s\n' 'possible-secrets-detected-in-staged-diff' >> "$log_path" fi if [ "${VSTACK_HOOKS_MODE:-audit}" = "enforce" ]; then if command -v gitleaks >/dev/null 2>&1; then - gitleaks dir . --no-banner --redact >/dev/null 2>&1 || printf '%s\n' 'gitleaks-reported-findings' >> .vstack/logs/hook-security-alerts.log + gitleaks dir . --no-banner --redact >/dev/null 2>&1 || printf '%s\n' 'gitleaks-reported-findings' >> "$log_path" else - printf '%s\n' 'gitleaks-not-installed' >> .vstack/logs/hook-security-alerts.log + printf '%s\n' 'gitleaks-not-installed' >> "$log_path" fi fi powershell: | $inputText = [Console]::In.ReadToEnd() - New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null - Add-Content -Path .vstack/logs/hook-post-commit.jsonl -Value $inputText + $logName = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { 'hook-security-alerts.log' } + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + $logPath = Join-Path $logDir $logName if ($inputText -notmatch 'git (commit|push|merge|rebase)') { exit 0 } if ((git rev-parse --git-dir 2>$null) -and (git diff --cached | Select-String -Pattern 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----' -Quiet)) { - Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'possible-secrets-detected-in-staged-diff' + Add-Content -Path $logPath -Value 'possible-secrets-detected-in-staged-diff' } $mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' } if ($mode -eq 'enforce') { if (Get-Command gitleaks -ErrorAction SilentlyContinue) { gitleaks dir . --no-banner --redact *> $null if ($LASTEXITCODE -ne 0) { - Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'gitleaks-reported-findings' + Add-Content -Path $logPath -Value 'gitleaks-reported-findings' } } else { - Add-Content -Path .vstack/logs/hook-security-alerts.log -Value 'gitleaks-not-installed' + Add-Content -Path $logPath -Value 'gitleaks-not-installed' } } cwd: "." timeoutSec: 60 - - sessionEnd: - - type: command - description: | - Log session end for security scanning completeness. - bash: | - mkdir -p .vstack/logs - input="$(cat)" - printf '%s\n' "$input" >> .vstack/logs/hook-session-end-security.jsonl - powershell: | - New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null - $inputText = [Console]::In.ReadToEnd() - Add-Content -Path .vstack/logs/hook-session-end-security.jsonl -Value $inputText - cwd: "." - timeoutSec: 10 diff --git a/src/vstack/_templates/hooks/post-edit-format/hook.yaml b/src/vstack/_templates/hooks/post-edit-format/hook.yaml index 0457a33..1f655bf 100644 --- a/src/vstack/_templates/hooks/post-edit-format/hook.yaml +++ b/src/vstack/_templates/hooks/post-edit-format/hook.yaml @@ -4,7 +4,10 @@ metadata: name: post-edit-format description: | Code quality enforcement hook. Runs 'make format' when VSTACK_HOOKS_MODE=enforce - on edit-like tools. Logs all edit events to audit trail regardless of mode. + on edit-like tools. Central audit logging is handled by session-audit. + log: + name: hook-quality-alerts.log + retention_days: 7 purpose: quality security_level: low mode_default: audit @@ -18,26 +21,32 @@ hooks: postToolUse: - type: command description: | - Log edit events and optionally run format in enforce mode. + Optionally run format in enforce mode; write alerts only on failures. bash: | - mkdir -p .vstack/logs input="$(cat)" - printf '%s\n' "$input" >> .vstack/logs/hook-post-edit.jsonl + log_name="${VSTACK_HOOK_LOG_NAME:-hook-quality-alerts.log}" + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + log_path="$log_dir/$log_name" mode="${VSTACK_HOOKS_MODE:-audit}" if [ "$mode" != "enforce" ]; then exit 0 fi if printf '%s' "$input" | grep -Eq '"toolName"[[:space:]]*:[[:space:]]*"(edit|create|write|replace)"'; then if [ -f Makefile ] && command -v make >/dev/null 2>&1; then - make format >/dev/null 2>&1 || printf '%s\n' 'format-run-failed' >> .vstack/logs/hook-quality-alerts.log + make format >/dev/null 2>&1 || printf '%s\n' 'format-run-failed' >> "$log_path" else - printf '%s\n' 'format-skipped-missing-make-or-makefile' >> .vstack/logs/hook-quality-alerts.log + printf '%s\n' 'format-skipped-missing-make-or-makefile' >> "$log_path" fi fi powershell: | $inputText = [Console]::In.ReadToEnd() - New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null - Add-Content -Path .vstack/logs/hook-post-edit.jsonl -Value $inputText + $logName = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { 'hook-quality-alerts.log' } + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + $logPath = Join-Path $logDir $logName $mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' } if ($mode -ne 'enforce') { exit 0 @@ -51,10 +60,10 @@ hooks: if ((Test-Path Makefile) -and (Get-Command make -ErrorAction SilentlyContinue)) { make format *> $null if ($LASTEXITCODE -ne 0) { - Add-Content -Path .vstack/logs/hook-quality-alerts.log -Value 'format-run-failed' + Add-Content -Path $logPath -Value 'format-run-failed' } } else { - Add-Content -Path .vstack/logs/hook-quality-alerts.log -Value 'format-skipped-missing-make-or-makefile' + Add-Content -Path $logPath -Value 'format-skipped-missing-make-or-makefile' } } cwd: "." diff --git a/src/vstack/_templates/hooks/post-edit-markdown-quality/hook.yaml b/src/vstack/_templates/hooks/post-edit-markdown-quality/hook.yaml index 5a58718..82ca4ac 100644 --- a/src/vstack/_templates/hooks/post-edit-markdown-quality/hook.yaml +++ b/src/vstack/_templates/hooks/post-edit-markdown-quality/hook.yaml @@ -3,9 +3,12 @@ version: 20260510004 metadata: name: post-edit-markdown-quality description: | - vstack-oriented markdown/work-item quality hook. Logs markdown-related edits + vstack-oriented markdown/work-item quality hook. and, in enforce mode, runs markdown formatting targets when docs, ADRs, prompts, instructions, or template markdown content appear in the tool payload. + log: + name: hook-markdown-quality-alerts.log + retention_days: 7 purpose: quality security_level: low mode_default: audit @@ -19,11 +22,14 @@ hooks: postToolUse: - type: command description: | - Log markdown-oriented edit events and optionally run markdown formatting. + Run markdown formatting in enforce mode and log only failures/skips. bash: | - mkdir -p .vstack/logs input="$(cat)" - printf '%s\n' "$input" >> .vstack/logs/hook-markdown-quality.jsonl + log_name="${VSTACK_HOOK_LOG_NAME:-hook-markdown-quality-alerts.log}" + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + log_path="$log_dir/$log_name" mode="${VSTACK_HOOKS_MODE:-audit}" if [ "$mode" != "enforce" ]; then exit 0 @@ -34,14 +40,17 @@ hooks: if [ -f Makefile ] && command -v make >/dev/null 2>&1; then make markdown-format >/dev/null 2>&1 || \ make format >/dev/null 2>&1 || \ - printf '%s\n' 'markdown-format-run-failed' >> .vstack/logs/hook-markdown-quality-alerts.log + printf '%s\n' 'markdown-format-run-failed' >> "$log_path" else - printf '%s\n' 'markdown-format-skipped-missing-make-or-makefile' >> .vstack/logs/hook-markdown-quality-alerts.log + printf '%s\n' 'markdown-format-skipped-missing-make-or-makefile' >> "$log_path" fi powershell: | $inputText = [Console]::In.ReadToEnd() - New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null - Add-Content -Path .vstack/logs/hook-markdown-quality.jsonl -Value $inputText + $logName = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { 'hook-markdown-quality-alerts.log' } + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + $logPath = Join-Path $logDir $logName $mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' } if ($mode -ne 'enforce') { exit 0 @@ -55,10 +64,10 @@ hooks: make format *> $null } if ($LASTEXITCODE -ne 0) { - Add-Content -Path .vstack/logs/hook-markdown-quality-alerts.log -Value 'markdown-format-run-failed' + Add-Content -Path $logPath -Value 'markdown-format-run-failed' } } else { - Add-Content -Path .vstack/logs/hook-markdown-quality-alerts.log -Value 'markdown-format-skipped-missing-make-or-makefile' + Add-Content -Path $logPath -Value 'markdown-format-skipped-missing-make-or-makefile' } cwd: "." timeoutSec: 30 diff --git a/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.yaml b/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.yaml index 4746d18..59b731d 100644 --- a/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.yaml +++ b/src/vstack/_templates/hooks/pre-tool-safety-gate/hook.yaml @@ -3,9 +3,12 @@ version: 20260510003 metadata: name: pre-tool-safety-gate description: | - Destructive operation safety gate. Logs all tool invocations to audit trail. + Destructive operation safety gate. Central audit logging is handled by session-audit. In enforce mode, blocks dangerous commands (rm -rf /, DROP TABLE, git reset --hard) with explicit deny response. + log: + name: hook-security-alerts.log + retention_days: 30 purpose: security security_level: high mode_default: audit @@ -19,23 +22,36 @@ hooks: - type: command description: | Check for destructive bash patterns and block in enforce mode. - Always logs to audit trail. + Logs only policy decisions and errors in this hook. bash: | - mkdir -p .vstack/logs input="$(cat)" - printf '%s\n' "$input" >> .vstack/logs/hook-pre-tool.jsonl + log_name="${VSTACK_HOOK_LOG_NAME:-hook-security-alerts.log}" + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + log_path="$log_dir/$log_name" mode="${VSTACK_HOOKS_MODE:-audit}" if [ "$mode" != "enforce" ]; then exit 0 fi - if printf '%s' "$input" | grep -Eq '"toolName"[[:space:]]*:[[:space:]]*"bash"' && \ - printf '%s' "$input" | grep -Eiq 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx'; then + tool_name="$(printf '%s' "$input" | sed -n 's/.*"toolName"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)" + if [ -z "$tool_name" ]; then + printf '%s\n' 'payload-parse-failed' >> "$log_path" + echo '{"permissionDecision":"deny","permissionDecisionReason":"Blocked request because payload could not be validated in enforce mode"}' + exit 0 + fi + if [ "$tool_name" = "bash" ] && \ + printf '%s' "$input" | grep -Eiq 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx|shutdown -h|shutdown now|reboot|poweroff|halt'; then + printf '%s\n' 'destructive-command-blocked' >> "$log_path" echo '{"permissionDecision":"deny","permissionDecisionReason":"Blocked potentially destructive shell command by vstack pre-tool safety policy"}' fi powershell: | $inputText = [Console]::In.ReadToEnd() - New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null - Add-Content -Path .vstack/logs/hook-pre-tool.jsonl -Value $inputText + $logName = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { 'hook-security-alerts.log' } + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + $logPath = Join-Path $logDir $logName $mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { 'audit' } if ($mode -ne 'enforce') { exit 0 @@ -43,9 +59,17 @@ hooks: try { $payload = $inputText | ConvertFrom-Json -ErrorAction Stop } catch { + Add-Content -Path $logPath -Value 'payload-parse-failed' + @{ permissionDecision = 'deny'; permissionDecisionReason = 'Blocked request because payload could not be validated in enforce mode' } | ConvertTo-Json -Compress + exit 0 + } + if (-not $payload.toolName) { + Add-Content -Path $logPath -Value 'payload-parse-failed' + @{ permissionDecision = 'deny'; permissionDecisionReason = 'Blocked request because payload could not be validated in enforce mode' } | ConvertTo-Json -Compress exit 0 } - if ($payload.toolName -eq 'bash' -and $inputText -match 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx') { + if ($payload.toolName -eq 'bash' -and $inputText -match 'rm -rf /|mkfs|dd if=|DROP TABLE|TRUNCATE TABLE|git reset --hard|git clean -fdx|shutdown -h|shutdown now|reboot|poweroff|halt') { + Add-Content -Path $logPath -Value 'destructive-command-blocked' @{ permissionDecision = 'deny'; permissionDecisionReason = 'Blocked potentially destructive shell command by vstack pre-tool safety policy' } | ConvertTo-Json -Compress } cwd: "." @@ -56,12 +80,14 @@ hooks: description: | Log tool errors for incident investigation and debugging. bash: | - mkdir -p .vstack/logs - input="$(cat)" - printf '%s\n' "$input" >> .vstack/logs/hook-errors.jsonl + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + printf '%s\n' "hook-error-occurred" >> "$log_dir/hook-errors.log" powershell: | - New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null - $inputText = [Console]::In.ReadToEnd() - Add-Content -Path .vstack/logs/hook-errors.jsonl -Value $inputText + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + Add-Content -Path (Join-Path $logDir 'hook-errors.log') -Value 'hook-error-occurred' cwd: "." timeoutSec: 10 diff --git a/src/vstack/_templates/hooks/session-audit/hook.yaml b/src/vstack/_templates/hooks/session-audit/hook.yaml index 0f2bcfb..8fa8631 100644 --- a/src/vstack/_templates/hooks/session-audit/hook.yaml +++ b/src/vstack/_templates/hooks/session-audit/hook.yaml @@ -1,10 +1,12 @@ -version: 20260510003 +version: 20260511004 metadata: name: session-audit description: | - Records session start and end events to structured JSONL logs - in .vstack/logs/ for audit trail and compliance verification. + Records session boundaries plus prompt and tool telemetry to + structured JSONL logs in .vstack/logs/ for audit and diagnostics. + log: + retention_days: 7 purpose: audit security_level: low mode_default: audit @@ -14,47 +16,233 @@ metadata: optional: [] hooks: + preToolUse: + - type: command + description: | + Centrally log tool call requests with size and estimated tokens. + bash: | + input="$(cat)" + log_level="${VSTACK_HOOKS_LOG_LEVEL:-minimal}" + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + if [ "$log_level" = "off" ]; then + exit 0 + fi + if [ "$log_level" = "verbose" ]; then + printf '%s\n' "$input" >> "$log_dir/hook-tool-use.log" + exit 0 + fi + ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + bytes="$(printf '%s' "$input" | wc -c | tr -d ' ')" + est_tokens="$(( (bytes + 3) / 4 ))" + tool_name="$(printf '%s' "$input" | sed -n 's/.*"toolName"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)" + if [ -z "$tool_name" ]; then + tool_name="unknown" + fi + printf '{"timestamp":"%s","event":"preToolUse","tool_name":"%s","size_bytes":%s,"estimated_tokens":%s}\n' "$ts" "$tool_name" "$bytes" "$est_tokens" >> "$log_dir/hook-tool-use.log" + powershell: | + $inputText = [Console]::In.ReadToEnd() + $logLevel = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { 'minimal' } + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + if ($logLevel -eq 'off') { + exit 0 + } + if ($logLevel -eq 'verbose') { + Add-Content -Path (Join-Path $logDir 'hook-tool-use.log') -Value $inputText + exit 0 + } + $toolName = 'unknown' + try { + $payload = $inputText | ConvertFrom-Json -ErrorAction Stop + if ($payload.toolName) { + $toolName = [string]$payload.toolName + } + } catch { + } + $record = @{ + timestamp = (Get-Date).ToUniversalTime().ToString('o') + event = 'preToolUse' + tool_name = $toolName + size_bytes = $inputText.Length + estimated_tokens = [int][math]::Ceiling($inputText.Length / 4.0) + } | ConvertTo-Json -Compress + Add-Content -Path (Join-Path $logDir 'hook-tool-use.log') -Value $record + cwd: "." + timeoutSec: 10 + + postToolUse: + - type: command + description: | + Centrally log tool responses with size and estimated tokens. + bash: | + input="$(cat)" + log_level="${VSTACK_HOOKS_LOG_LEVEL:-minimal}" + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + if [ "$log_level" = "off" ]; then + exit 0 + fi + if [ "$log_level" = "verbose" ]; then + printf '%s\n' "$input" >> "$log_dir/hook-tool-use.log" + exit 0 + fi + ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + bytes="$(printf '%s' "$input" | wc -c | tr -d ' ')" + est_tokens="$(( (bytes + 3) / 4 ))" + tool_name="$(printf '%s' "$input" | sed -n 's/.*"toolName"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)" + if [ -z "$tool_name" ]; then + tool_name="unknown" + fi + printf '{"timestamp":"%s","event":"postToolUse","tool_name":"%s","size_bytes":%s,"estimated_tokens":%s}\n' "$ts" "$tool_name" "$bytes" "$est_tokens" >> "$log_dir/hook-tool-use.log" + powershell: | + $inputText = [Console]::In.ReadToEnd() + $logLevel = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { 'minimal' } + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + if ($logLevel -eq 'off') { + exit 0 + } + if ($logLevel -eq 'verbose') { + Add-Content -Path (Join-Path $logDir 'hook-tool-use.log') -Value $inputText + exit 0 + } + $toolName = 'unknown' + try { + $payload = $inputText | ConvertFrom-Json -ErrorAction Stop + if ($payload.toolName) { + $toolName = [string]$payload.toolName + } + } catch { + } + $record = @{ + timestamp = (Get-Date).ToUniversalTime().ToString('o') + event = 'postToolUse' + tool_name = $toolName + size_bytes = $inputText.Length + estimated_tokens = [int][math]::Ceiling($inputText.Length / 4.0) + } | ConvertTo-Json -Compress + Add-Content -Path (Join-Path $logDir 'hook-tool-use.log') -Value $record + cwd: "." + timeoutSec: 10 + userPromptSubmitted: - type: command description: | - Log prompt submission events for audit correlation. + Centrally log prompt events with configurable verbosity. bash: | - mkdir -p .vstack/logs input="$(cat)" - printf '%s\n' "$input" >> .vstack/logs/hook-user-prompt.jsonl + log_level="${VSTACK_HOOKS_LOG_LEVEL:-minimal}" + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + if [ "$log_level" = "off" ]; then + exit 0 + fi + if [ "$log_level" = "verbose" ]; then + printf '%s\n' "$input" >> "$log_dir/hook-user-prompt.log" + exit 0 + fi + ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + bytes="$(printf '%s' "$input" | wc -c | tr -d ' ')" + est_tokens="$(( (bytes + 3) / 4 ))" + printf '{"timestamp":"%s","event":"userPromptSubmitted","size_bytes":%s,"estimated_tokens":%s}\n' "$ts" "$bytes" "$est_tokens" >> "$log_dir/hook-user-prompt.log" powershell: | - New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null $inputText = [Console]::In.ReadToEnd() - Add-Content -Path .vstack/logs/hook-user-prompt.jsonl -Value $inputText + $logLevel = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { 'minimal' } + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + if ($logLevel -eq 'off') { + exit 0 + } + if ($logLevel -eq 'verbose') { + Add-Content -Path (Join-Path $logDir 'hook-user-prompt.log') -Value $inputText + exit 0 + } + $record = @{ timestamp = (Get-Date).ToUniversalTime().ToString('o'); event = 'userPromptSubmitted'; size_bytes = $inputText.Length; estimated_tokens = [int][math]::Ceiling($inputText.Length / 4.0) } | ConvertTo-Json -Compress + Add-Content -Path (Join-Path $logDir 'hook-user-prompt.log') -Value $record cwd: "." timeoutSec: 10 sessionStart: - type: command description: | - Log session start event with timestamp and context. + Centrally log session start with configurable verbosity. bash: | - mkdir -p .vstack/logs input="$(cat)" - printf '%s\n' "$input" >> .vstack/logs/hook-session-start.jsonl + log_level="${VSTACK_HOOKS_LOG_LEVEL:-minimal}" + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + if [ "$log_level" = "off" ]; then + exit 0 + fi + if [ "$log_level" = "verbose" ]; then + printf '%s\n' "$input" >> "$log_dir/hook-session-start.log" + exit 0 + fi + ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + bytes="$(printf '%s' "$input" | wc -c | tr -d ' ')" + est_tokens="$(( (bytes + 3) / 4 ))" + printf '{"timestamp":"%s","event":"sessionStart","size_bytes":%s,"estimated_tokens":%s}\n' "$ts" "$bytes" "$est_tokens" >> "$log_dir/hook-session-start.log" powershell: | - New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null $inputText = [Console]::In.ReadToEnd() - Add-Content -Path .vstack/logs/hook-session-start.jsonl -Value $inputText + $logLevel = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { 'minimal' } + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + if ($logLevel -eq 'off') { + exit 0 + } + if ($logLevel -eq 'verbose') { + Add-Content -Path (Join-Path $logDir 'hook-session-start.log') -Value $inputText + exit 0 + } + $record = @{ timestamp = (Get-Date).ToUniversalTime().ToString('o'); event = 'sessionStart'; size_bytes = $inputText.Length; estimated_tokens = [int][math]::Ceiling($inputText.Length / 4.0) } | ConvertTo-Json -Compress + Add-Content -Path (Join-Path $logDir 'hook-session-start.log') -Value $record cwd: "." timeoutSec: 10 sessionEnd: - type: command description: | - Log session end event for correlation with start. + Centrally log session end for correlation with start. bash: | - mkdir -p .vstack/logs input="$(cat)" - printf '%s\n' "$input" >> .vstack/logs/hook-session-end.jsonl + log_level="${VSTACK_HOOKS_LOG_LEVEL:-minimal}" + log_root="${VSTACK_HOOK_LOG_DIR:-.vstack/logs}" + log_dir="$log_root/$(date -u +%Y%m%d)" + mkdir -p "$log_dir" + if [ "$log_level" = "off" ]; then + exit 0 + fi + if [ "$log_level" = "verbose" ]; then + printf '%s\n' "$input" >> "$log_dir/hook-session-end.log" + exit 0 + fi + ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + bytes="$(printf '%s' "$input" | wc -c | tr -d ' ')" + est_tokens="$(( (bytes + 3) / 4 ))" + printf '{"timestamp":"%s","event":"sessionEnd","size_bytes":%s,"estimated_tokens":%s}\n' "$ts" "$bytes" "$est_tokens" >> "$log_dir/hook-session-end.log" powershell: | - New-Item -ItemType Directory -Force -Path .vstack/logs | Out-Null $inputText = [Console]::In.ReadToEnd() - Add-Content -Path .vstack/logs/hook-session-end.jsonl -Value $inputText + $logLevel = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { 'minimal' } + $logRoot = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { '.vstack/logs' } + $logDir = Join-Path $logRoot (Get-Date).ToUniversalTime().ToString('yyyyMMdd') + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + if ($logLevel -eq 'off') { + exit 0 + } + if ($logLevel -eq 'verbose') { + Add-Content -Path (Join-Path $logDir 'hook-session-end.log') -Value $inputText + exit 0 + } + $record = @{ timestamp = (Get-Date).ToUniversalTime().ToString('o'); event = 'sessionEnd'; size_bytes = $inputText.Length; estimated_tokens = [int][math]::Ceiling($inputText.Length / 4.0) } | ConvertTo-Json -Compress + Add-Content -Path (Join-Path $logDir 'hook-session-end.log') -Value $record cwd: "." timeoutSec: 10 diff --git a/src/vstack/_templates/project/.vstack/config.yaml b/src/vstack/_templates/project/.vstack/config.yaml index 6c817ce..77b0110 100644 --- a/src/vstack/_templates/project/.vstack/config.yaml +++ b/src/vstack/_templates/project/.vstack/config.yaml @@ -34,16 +34,78 @@ # # hooks.enabled: disable the entire generated hook baseline # hooks.mode: default runtime posture for generated hooks (audit or enforce) +# hooks.log_level: default audit verbosity for central logger (off|minimal|verbose) +# hooks.log_retention_days: retain dated log folders for this many days (session-audit) +# hooks.log_dir: root directory for dated hook logs (YYYYMMDD buckets) # hooks.hooks.<name>.enabled: disable one named hook without disabling the type # hooks.hooks.<name>.mode: override the default mode for one named hook +# hooks.hooks.<name>.log.level: override central audit verbosity per hook +# hooks.hooks.<name>.log.name: override the log filename for one hook (basename only) +# Effective path is always .vstack/logs/YYYYMMDD/<name> +# hooks.hooks.<name>.log.retention_days: override retention days for one hook (e.g., security alerts: 30) +# +# Example 1: quiet mode (logs off) +# hooks: +# enabled: true +# mode: audit +# log_level: off +# +# Example 2: security team setup (verbose, longer retention, custom paths) +# hooks: +# enabled: true +# log_level: verbose +# log_retention_days: 7 +# hooks: +# pre-tool-safety-gate: +# mode: enforce +# log: +# name: security-gate.log +# retention_days: 30 +# post-commit-security-scan: +# log: +# name: security-scan.log +# retention_days: 30 +# session-audit: +# log: +# level: verbose +# +# Example 3: minimal quality checks, verbose security logging +# hooks: +# enabled: true +# log_level: minimal +# hooks: +# post-edit-format: +# log: +# level: off +# post-edit-markdown-quality: +# log: +# level: off +# post-commit-security-scan: +# log: +# level: verbose +# retention_days: 60 +# +# Defaults: enabled=true, mode=audit, log_level=minimal, log_retention_days=7 # ----------------------------------------------------------------------------- # hooks: # enabled: true # mode: audit +# log_level: minimal +# log_retention_days: 7 +# log_dir: .vstack/logs # hooks: # pre-tool-safety-gate: # mode: enforce +# session-audit: +# log: +# level: verbose +# post-edit-format: +# log: +# name: custom-quality.log +# post-commit-security-scan: +# log: +# retention_days: 30 # post-edit-markdown-quality: # enabled: false diff --git a/src/vstack/cli/constants.py b/src/vstack/cli/constants.py index 910eec8..57949d2 100644 --- a/src/vstack/cli/constants.py +++ b/src/vstack/cli/constants.py @@ -111,6 +111,7 @@ class ArtifactState: "typescript", ], "hook": [ + "log-retention-cleanup", "post-edit-markdown-quality", "post-edit-format", "post-commit-security-scan", diff --git a/src/vstack/cli/interface.py b/src/vstack/cli/interface.py index d831404..afcf244 100644 --- a/src/vstack/cli/interface.py +++ b/src/vstack/cli/interface.py @@ -83,6 +83,23 @@ def _resolve_only_filter( return self.resolve_only_for_scope(args) return getattr(args, "only", None) + @staticmethod + def _is_valid_hook_log_name(name: str) -> bool: + """Return whether *name* is an allowed per-hook log filename. + + Allowed names are plain filenames (no path separators), ending in ``.log`` + and containing only alphanumeric characters, ``.``, ``_`` and ``-``. + """ + candidate = name.strip() + if not candidate: + return False + if "/" in candidate or "\\" in candidate: + return False + if not candidate.endswith(".log"): + return False + allowed = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-") + return all(char in allowed for char in candidate) + @staticmethod def _read_exclude( install_dir: Path | None, @@ -232,31 +249,64 @@ def _read_workflow_mode(install_dir: Path | None) -> str: @staticmethod def _read_hook_settings( install_dir: Path | None, - ) -> tuple[bool, str, list[str], dict[str, str]]: - """Read project-level hook enablement and mode defaults from config.""" + ) -> tuple[ + bool, + str, + str, + int, + str, + list[str], + dict[str, str], + dict[str, str], + dict[str, str], + dict[str, int], + ]: + """Read project-level hook enablement, mode, and log-level settings.""" + allowed_modes = {"audit", "enforce"} + allowed_log_levels = {"off", "minimal", "verbose"} if install_dir is None: - return True, "audit", [], {} + return True, "audit", "minimal", 7, ".vstack/logs", [], {}, {}, {}, {} config_path = install_dir.parent / ".vstack" / "config.yaml" if not config_path.exists(): - return True, "audit", [], {} + return True, "audit", "minimal", 7, ".vstack/logs", [], {}, {}, {}, {} parsed = FrontmatterParser.parse_yaml(config_path.read_text(encoding="utf-8")) hooks_config = parsed.get("hooks", "") if not isinstance(hooks_config, dict): - return True, "audit", [], {} + return True, "audit", "minimal", 7, ".vstack/logs", [], {}, {}, {}, {} enabled = hooks_config.get("enabled", True) global_enabled = enabled if isinstance(enabled, bool) else True mode_value = hooks_config.get("mode", "audit") default_mode = ( - mode_value - if isinstance(mode_value, str) and mode_value in {"audit", "enforce"} - else "audit" + mode_value if isinstance(mode_value, str) and mode_value in allowed_modes else "audit" + ) + + log_level_value = hooks_config.get("log_level", "minimal") + default_log_level = ( + log_level_value + if isinstance(log_level_value, str) and log_level_value in allowed_log_levels + else "minimal" ) + retention_value = hooks_config.get("log_retention_days", 7) + if isinstance(retention_value, int) and retention_value >= 0: + default_retention_days = retention_value + else: + default_retention_days = 7 + + log_dir_value = hooks_config.get("log_dir", ".vstack/logs") + if isinstance(log_dir_value, str) and log_dir_value.strip(): + default_log_dir = log_dir_value.strip() + else: + default_log_dir = ".vstack/logs" + disabled_names: list[str] = [] mode_overrides: dict[str, str] = {} + log_level_overrides: dict[str, str] = {} + log_name_overrides: dict[str, str] = {} + log_retention_days_overrides: dict[str, int] = {} per_hook = hooks_config.get("hooks", "") if isinstance(per_hook, dict): for hook_name, hook_config in per_hook.items(): @@ -265,11 +315,40 @@ def _read_hook_settings( if hook_config.get("enabled") is False: disabled_names.append(hook_name) continue + + nested_log = hook_config.get("log", "") + nested_log_config = nested_log if isinstance(nested_log, dict) else {} + hook_mode = hook_config.get("mode", "") - if isinstance(hook_mode, str) and hook_mode in {"audit", "enforce"}: + if isinstance(hook_mode, str) and hook_mode in allowed_modes: mode_overrides[hook_name] = hook_mode - return global_enabled, default_mode, disabled_names, mode_overrides + hook_log_level = nested_log_config.get("level", "") + if isinstance(hook_log_level, str) and hook_log_level in allowed_log_levels: + log_level_overrides[hook_name] = hook_log_level + + hook_log_name = nested_log_config.get("name", "") + if isinstance(hook_log_name, str): + normalized_log_name = hook_log_name.strip() + if CommandLineInterface._is_valid_hook_log_name(normalized_log_name): + log_name_overrides[hook_name] = normalized_log_name + + hook_retention_days = nested_log_config.get("retention_days", None) + if isinstance(hook_retention_days, int) and hook_retention_days >= 0: + log_retention_days_overrides[hook_name] = hook_retention_days + + return ( + global_enabled, + default_mode, + default_log_level, + default_retention_days, + default_log_dir, + disabled_names, + mode_overrides, + log_level_overrides, + log_name_overrides, + log_retention_days_overrides, + ) @staticmethod def _parse_stage_handoffs(item: dict) -> list[dict[str, str]]: @@ -340,16 +419,31 @@ def run(self) -> int: items_root = self._read_items_root(resolved_install_dir) workflow_stages = self._read_workflow_stages(resolved_install_dir) workflow_mode = self._read_workflow_mode(resolved_install_dir) - hooks_enabled, hook_default_mode, disabled_hook_names, hook_mode_overrides = ( - self._read_hook_settings(resolved_install_dir) - ) + ( + hooks_enabled, + hook_default_mode, + hook_default_log_level, + hook_log_retention_days, + hook_log_dir, + disabled_hook_names, + hook_mode_overrides, + hook_log_level_overrides, + hook_log_name_overrides, + hook_log_retention_days_overrides, + ) = self._read_hook_settings(resolved_install_dir) service = cast(Any, self._service_cls)( templates_root=self._templates_root, items_root=items_root, workflow_stages=workflow_stages, workflow_mode=workflow_mode, hook_default_mode=hook_default_mode, + hook_default_log_level=hook_default_log_level, + hook_log_retention_days=hook_log_retention_days, + hook_log_dir=hook_log_dir, hook_mode_overrides=hook_mode_overrides, + hook_log_level_overrides=hook_log_level_overrides, + hook_log_name_overrides=hook_log_name_overrides, + hook_log_retention_days_overrides=hook_log_retention_days_overrides, disabled_hook_names=disabled_hook_names, ) commands = self._build_command_registry(service) diff --git a/src/vstack/cli/service.py b/src/vstack/cli/service.py index d986afa..394042f 100644 --- a/src/vstack/cli/service.py +++ b/src/vstack/cli/service.py @@ -45,7 +45,13 @@ def __init__( workflow_stages: list[dict[str, str]] | None = None, workflow_mode: str = "agentic", hook_default_mode: str = "audit", + hook_default_log_level: str = "minimal", + hook_log_retention_days: int = 7, + hook_log_dir: str = ".vstack/logs", hook_mode_overrides: dict[str, str] | None = None, + hook_log_level_overrides: dict[str, str] | None = None, + hook_log_name_overrides: dict[str, str] | None = None, + hook_log_retention_days_overrides: dict[str, int] | None = None, disabled_hook_names: list[str] | None = None, ) -> None: """Create generators for all known artifact families. @@ -85,7 +91,13 @@ def __init__( else HookGenerator( templates_root, default_mode=hook_default_mode, + default_log_level=hook_default_log_level, + default_log_retention_days=hook_log_retention_days, + default_log_dir=hook_log_dir, mode_overrides=hook_mode_overrides, + log_level_overrides=hook_log_level_overrides, + log_name_overrides=hook_log_name_overrides, + log_retention_days_overrides=hook_log_retention_days_overrides, disabled_names=disabled_hook_names, ) if tc is HOOK_TYPE diff --git a/src/vstack/hooks/generator.py b/src/vstack/hooks/generator.py index e9fa16e..2289ebd 100644 --- a/src/vstack/hooks/generator.py +++ b/src/vstack/hooks/generator.py @@ -28,6 +28,7 @@ class HookGenerator(GenericArtifactGenerator): _ALLOWED_PURPOSES = {"audit", "security", "quality"} _ALLOWED_SECURITY_LEVELS = {"low", "high"} _ALLOWED_MODES = {"audit", "enforce"} + _ALLOWED_LOG_LEVELS = {"off", "minimal", "verbose"} _ALLOWED_ACTION_TYPES = {"command"} def __init__( @@ -35,17 +36,47 @@ def __init__( templates_root: Path | None = None, *, default_mode: str = "audit", + default_log_level: str = "minimal", + default_log_retention_days: int = 7, + default_log_dir: str = ".vstack/logs", mode_overrides: dict[str, str] | None = None, + log_level_overrides: dict[str, str] | None = None, + log_name_overrides: dict[str, str] | None = None, + log_retention_days_overrides: dict[str, int] | None = None, disabled_names: list[str] | None = None, ) -> None: """Create a hook generator bound to the built-in or provided template root.""" super().__init__(HOOK_TYPE, templates_root or TEMPLATES_ROOT) self.default_mode = default_mode if default_mode in self._ALLOWED_MODES else "audit" + self.default_log_level = ( + default_log_level if default_log_level in self._ALLOWED_LOG_LEVELS else "minimal" + ) + self.default_log_retention_days = ( + default_log_retention_days if default_log_retention_days >= 0 else 7 + ) + self.default_log_dir = ( + default_log_dir.strip() if default_log_dir.strip() else ".vstack/logs" + ) self.mode_overrides = { name: mode for name, mode in (mode_overrides or {}).items() if mode in self._ALLOWED_MODES } + self.log_level_overrides = { + name: level + for name, level in (log_level_overrides or {}).items() + if level in self._ALLOWED_LOG_LEVELS + } + self.log_name_overrides = { + name: log_name + for name, log_name in (log_name_overrides or {}).items() + if isinstance(log_name, str) and log_name + } + self.log_retention_days_overrides = { + name: days + for name, days in (log_retention_days_overrides or {}).items() + if isinstance(days, int) and days >= 0 + } self.disabled_names = set(disabled_names or []) def find_templates(self) -> list[Path]: @@ -66,19 +97,87 @@ def _mode_for_hook(self, hook_name: str) -> str: """Resolve the configured default mode for one hook.""" return self.mode_overrides.get(hook_name, self.default_mode) - def _apply_mode_defaults(self, text: str, *, mode: str) -> str: - """Replace audit fallbacks inside shell snippets with the configured mode.""" - return ( + def _log_level_for_hook(self, hook_name: str) -> str: + """Resolve the configured default log level for one hook.""" + return self.log_level_overrides.get(hook_name, self.default_log_level) + + def _retention_days_for_hook(self, hook_name: str) -> int: + """Resolve the configured retention days for one hook.""" + return self.log_retention_days_overrides.get(hook_name, self.default_log_retention_days) + + def _log_name_for_hook(self, hook_name: str) -> str | None: + """Resolve the configured log filename override for one hook.""" + return self.log_name_overrides.get(hook_name) + + def _apply_runtime_defaults( + self, + text: str, + *, + mode: str, + log_level: str, + retention_days: int, + log_dir: str, + log_name: str | None, + default_log_name: str | None, + ) -> str: + """Inject configured mode and log level fallbacks into shell snippets.""" + rendered = ( text.replace("${VSTACK_HOOKS_MODE:-audit}", f"${{VSTACK_HOOKS_MODE:-{mode}}}") + .replace( + "${VSTACK_HOOKS_LOG_LEVEL:-minimal}", + f"${{VSTACK_HOOKS_LOG_LEVEL:-{log_level}}}", + ) + .replace( + "${VSTACK_HOOKS_LOG_RETENTION_DAYS:-7}", + f"${{VSTACK_HOOKS_LOG_RETENTION_DAYS:-{retention_days}}}", + ) + .replace( + "${VSTACK_HOOK_LOG_DIR:-.vstack/logs}", + f"${{VSTACK_HOOK_LOG_DIR:-{log_dir}}}", + ) .replace("else { 'audit' }", f"else {{ '{mode}' }}") + .replace("else { 'minimal' }", f"else {{ '{log_level}' }}") + .replace("else { '7' }", f"else {{ '{retention_days}' }}") + .replace("else { '.vstack/logs' }", f"else {{ '{log_dir}' }}") .replace('else { "audit" }', f'else {{ "{mode}" }}') + .replace('else { "minimal" }', f'else {{ "{log_level}" }}') + .replace('else { "7" }', f'else {{ "{retention_days}" }}') + .replace('else { ".vstack/logs" }', f'else {{ "{log_dir}" }}') ) + if ( + isinstance(log_name, str) + and log_name + and isinstance(default_log_name, str) + and default_log_name + ): + rendered = ( + rendered.replace( + f"${{VSTACK_HOOK_LOG_NAME:-{default_log_name}}}", + f"${{VSTACK_HOOK_LOG_NAME:-{log_name}}}", + ) + .replace( + f"else {{ '{default_log_name}' }}", + f"else {{ '{log_name}' }}", + ) + .replace( + f'else {{ "{default_log_name}" }}', + f'else {{ "{log_name}" }}', + ) + ) + return rendered def _generate_json_from_yaml(self, yaml_data: dict) -> str: """Convert validated YAML data to the GitHub Copilot hooks envelope.""" metadata = yaml_data.get("metadata", {}) hook_name = str(metadata.get("name", "")) mode = self._mode_for_hook(hook_name) + log_level = self._log_level_for_hook(hook_name) + retention_days = self._retention_days_for_hook(hook_name) + log_name = self._log_name_for_hook(hook_name) + log_metadata = metadata.get("log", "") + default_log_name = "" + if isinstance(log_metadata, dict): + default_log_name = str(log_metadata.get("name", "")) hooks_dict = yaml_data.get("hooks", {}) rendered_hooks: dict[str, list[dict]] = {} @@ -89,7 +188,15 @@ def _generate_json_from_yaml(self, yaml_data: dict) -> str: for shell_name in ("bash", "powershell"): command = rendered_action.get(shell_name) if isinstance(command, str): - rendered_action[shell_name] = self._apply_mode_defaults(command, mode=mode) + rendered_action[shell_name] = self._apply_runtime_defaults( + command, + mode=mode, + log_level=log_level, + retention_days=retention_days, + log_dir=self.default_log_dir, + log_name=log_name, + default_log_name=str(default_log_name), + ) rendered_actions.append(rendered_action) rendered_hooks[event] = rendered_actions diff --git a/tests/vstack/cli/test_constants.py b/tests/vstack/cli/test_constants.py index a3d07f3..7522058 100644 --- a/tests/vstack/cli/test_constants.py +++ b/tests/vstack/cli/test_constants.py @@ -3,6 +3,7 @@ from __future__ import annotations from vstack.cli.constants import EXPECTED_CANONICAL_NAMES as CLI_NAMES +from vstack.cli.constants import EXPECTED_INPUT_NAMES EXPECTED_CLI_NAMES = [ "vision", @@ -62,3 +63,14 @@ def test_expected_canonical_names_content(self) -> None: f"Expected: {EXPECTED_CLI_NAMES}\n" f"Found: {CLI_NAMES}" ) + + def test_expected_hook_input_names_content(self) -> None: + """Verify source checks require the complete built-in hook set.""" + assert EXPECTED_INPUT_NAMES["hook"] == [ + "log-retention-cleanup", + "post-edit-markdown-quality", + "post-edit-format", + "post-commit-security-scan", + "pre-tool-safety-gate", + "session-audit", + ] diff --git a/tests/vstack/cli/test_interface.py b/tests/vstack/cli/test_interface.py index f696595..c9df9f4 100644 --- a/tests/vstack/cli/test_interface.py +++ b/tests/vstack/cli/test_interface.py @@ -72,7 +72,13 @@ def __init__( workflow_stages=None, workflow_mode: str = "agentic", hook_default_mode: str = "audit", + hook_default_log_level: str = "minimal", + hook_log_retention_days: int = 7, + hook_log_dir: str = ".vstack/logs", hook_mode_overrides=None, + hook_log_level_overrides=None, + hook_log_name_overrides=None, + hook_log_retention_days_overrides=None, disabled_hook_names=None, excluded_names=None, ) -> None: @@ -81,7 +87,13 @@ def __init__( self.workflow_stages = workflow_stages self.workflow_mode = workflow_mode self.hook_default_mode = hook_default_mode + self.hook_default_log_level = hook_default_log_level + self.hook_log_retention_days = hook_log_retention_days + self.hook_log_dir = hook_log_dir self.hook_mode_overrides = hook_mode_overrides + self.hook_log_level_overrides = hook_log_level_overrides + self.hook_log_name_overrides = hook_log_name_overrides + self.hook_log_retention_days_overrides = hook_log_retention_days_overrides self.disabled_hook_names = disabled_hook_names self.excluded_names = excluded_names @@ -257,7 +269,13 @@ def __init__( workflow_stages=None, workflow_mode: str = "agentic", hook_default_mode: str = "audit", + hook_default_log_level: str = "minimal", + hook_log_retention_days: int = 7, + hook_log_dir: str = ".vstack/logs", hook_mode_overrides=None, + hook_log_level_overrides=None, + hook_log_name_overrides=None, + hook_log_retention_days_overrides=None, disabled_hook_names=None, excluded_names=None, ) -> None: @@ -266,7 +284,13 @@ def __init__( workflow_stages, workflow_mode, hook_default_mode, + hook_default_log_level, + hook_log_retention_days, + hook_log_dir, hook_mode_overrides, + hook_log_level_overrides, + hook_log_name_overrides, + hook_log_retention_days_overrides, disabled_hook_names, excluded_names, ) @@ -468,7 +492,13 @@ def __init__( workflow_stages=None, workflow_mode: str = "agentic", hook_default_mode: str = "audit", + hook_default_log_level: str = "minimal", + hook_log_retention_days: int = 7, + hook_log_dir: str = ".vstack/logs", hook_mode_overrides=None, + hook_log_level_overrides=None, + hook_log_name_overrides=None, + hook_log_retention_days_overrides=None, disabled_hook_names=None, excluded_names=None, ) -> None: @@ -477,7 +507,13 @@ def __init__( items_root, workflow_stages, hook_default_mode, + hook_default_log_level, + hook_log_retention_days, + hook_log_dir, hook_mode_overrides, + hook_log_level_overrides, + hook_log_name_overrides, + hook_log_retention_days_overrides, disabled_hook_names, excluded_names, ) @@ -504,7 +540,18 @@ class TestReadHookSettings: def test_returns_defaults_when_install_dir_is_none(self) -> None: """Missing install dir yields enabled audit defaults.""" - assert CommandLineInterface._read_hook_settings(None) == (True, "audit", [], {}) + assert CommandLineInterface._read_hook_settings(None) == ( + True, + "audit", + "minimal", + 7, + ".vstack/logs", + [], + {}, + {}, + {}, + {}, + ) def test_reads_global_and_per_hook_settings(self, tmp_path: Path) -> None: """Reads hooks.enabled, hooks.mode, and per-hook overrides from config.""" @@ -514,22 +561,42 @@ def test_reads_global_and_per_hook_settings(self, tmp_path: Path) -> None: "hooks:\n" " enabled: true\n" " mode: enforce\n" + " log_level: verbose\n" + " log_retention_days: 14\n" " hooks:\n" " session-audit:\n" " enabled: false\n" " pre-tool-safety-gate:\n" - " mode: audit\n", + " mode: audit\n" + " log:\n" + " level: minimal\n" + " name: custom-security.log\n", encoding="utf-8", ) - enabled, mode, disabled_names, mode_overrides = CommandLineInterface._read_hook_settings( - tmp_path / ".github" - ) + ( + enabled, + mode, + log_level, + retention_days, + log_dir, + disabled_names, + mode_overrides, + log_level_overrides, + log_name_overrides, + retention_days_overrides, + ) = CommandLineInterface._read_hook_settings(tmp_path / ".github") assert enabled is True assert mode == "enforce" + assert log_level == "verbose" + assert retention_days == 14 + assert log_dir == ".vstack/logs" assert disabled_names == ["session-audit"] assert mode_overrides == {"pre-tool-safety-gate": "audit"} + assert log_level_overrides == {"pre-tool-safety-gate": "minimal"} + assert log_name_overrides == {"pre-tool-safety-gate": "custom-security.log"} + assert retention_days_overrides == {} def test_run_applies_disabled_hooks_to_context_and_service( self, monkeypatch, tmp_path: Path @@ -575,14 +642,62 @@ def test_read_hook_settings_ignores_non_dict_hook_entries(self, tmp_path: Path) encoding="utf-8", ) - enabled, mode, disabled_names, mode_overrides = CommandLineInterface._read_hook_settings( - tmp_path / ".github" + ( + enabled, + mode, + log_level, + retention_days, + log_dir, + disabled_names, + mode_overrides, + log_level_overrides, + log_name_overrides, + retention_days_overrides, + ) = CommandLineInterface._read_hook_settings(tmp_path / ".github") + + assert enabled is True + assert mode == "audit" + assert log_level == "minimal" + assert retention_days == 7 + assert log_dir == ".vstack/logs" + assert disabled_names == [] + assert mode_overrides == {} + assert log_level_overrides == {} + assert log_name_overrides == {} + assert retention_days_overrides == {} + + def test_invalid_log_retention_days_falls_back_to_default(self, tmp_path: Path) -> None: + """Invalid hooks.log_retention_days values fall back to seven days.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n log_retention_days: not-a-number\n", + encoding="utf-8", ) + ( + enabled, + mode, + log_level, + retention_days, + log_dir, + disabled_names, + mode_overrides, + log_level_overrides, + log_name_overrides, + retention_days_overrides, + ) = CommandLineInterface._read_hook_settings(tmp_path / ".github") + assert enabled is True assert mode == "audit" + assert log_level == "minimal" + assert retention_days == 7 + assert log_dir == ".vstack/logs" assert disabled_names == [] assert mode_overrides == {} + assert log_level_overrides == {} + assert log_name_overrides == {} + assert retention_days_overrides == {} def test_run_disables_hook_type_when_hooks_enabled_is_false( self, monkeypatch, tmp_path: Path @@ -615,6 +730,240 @@ def test_run_disables_hook_type_when_hooks_enabled_is_false( assert command.calls[0].only is not None assert "hook" not in command.calls[0].only + def test_per_hook_log_retention_days_override(self, tmp_path: Path) -> None: + """Per-hook log.retention_days overrides are parsed correctly.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n" + " log_retention_days: 7\n" + " hooks:\n" + " post-commit-security-scan:\n" + " log:\n" + " retention_days: 30\n" + " session-audit:\n" + " log:\n" + " retention_days: 14\n", + encoding="utf-8", + ) + + ( + enabled, + mode, + log_level, + retention_days, + _log_dir, + disabled_names, + mode_overrides, + log_level_overrides, + log_name_overrides, + retention_days_overrides, + ) = CommandLineInterface._read_hook_settings(tmp_path / ".github") + + assert enabled is True + assert retention_days == 7 + assert retention_days_overrides == { + "post-commit-security-scan": 30, + "session-audit": 14, + } + + def test_nested_per_hook_log_block_parses_name_level_and_retention( + self, tmp_path: Path + ) -> None: + """Nested hooks.hooks.<name>.log.* keys are parsed for overrides.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n" + " hooks:\n" + " post-commit-security-scan:\n" + " log:\n" + " name: nested-security.log\n" + " level: verbose\n" + " retention_days: 45\n", + encoding="utf-8", + ) + + ( + enabled, + mode, + log_level, + retention_days, + log_dir, + disabled_names, + mode_overrides, + log_level_overrides, + log_name_overrides, + retention_days_overrides, + ) = CommandLineInterface._read_hook_settings(tmp_path / ".github") + + assert enabled is True + assert mode == "audit" + assert log_level == "minimal" + assert retention_days == 7 + assert log_dir == ".vstack/logs" + assert disabled_names == [] + assert mode_overrides == {} + assert log_level_overrides == {"post-commit-security-scan": "verbose"} + assert log_name_overrides == {"post-commit-security-scan": "nested-security.log"} + assert retention_days_overrides == {"post-commit-security-scan": 45} + + def test_flat_per_hook_log_keys_are_ignored(self, tmp_path: Path) -> None: + """Flat per-hook log keys are ignored; only hooks.hooks.<name>.log.* applies.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n" + " hooks:\n" + " pre-tool-safety-gate:\n" + " log_level: minimal\n" + " log_name: flat.log\n" + " log_retention_days: 10\n", + encoding="utf-8", + ) + + ( + enabled, + mode, + log_level, + retention_days, + log_dir, + disabled_names, + mode_overrides, + log_level_overrides, + log_name_overrides, + retention_days_overrides, + ) = CommandLineInterface._read_hook_settings(tmp_path / ".github") + + assert enabled is True + assert mode == "audit" + assert log_level == "minimal" + assert retention_days == 7 + assert log_dir == ".vstack/logs" + assert disabled_names == [] + assert mode_overrides == {} + assert log_level_overrides == {} + assert log_name_overrides == {} + assert retention_days_overrides == {} + + def test_per_hook_log_name_validation_accepts_only_safe_log_filenames( + self, tmp_path: Path + ) -> None: + """Only safe basename log names ending with .log are accepted.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n" + " hooks:\n" + " post-edit-format:\n" + " log:\n" + " name: quality-custom.log\n" + " pre-tool-safety-gate:\n" + " log:\n" + " name: ../escape.log\n" + " post-commit-security-scan:\n" + " log:\n" + " name: security/alerts.log\n" + " session-audit:\n" + " log:\n" + " name: absolute.log.tmp\n" + " post-edit-markdown-quality:\n" + " log:\n" + " name: bad*chars.log\n", + encoding="utf-8", + ) + + ( + _enabled, + _mode, + _log_level, + _retention_days, + _log_dir, + _disabled_names, + _mode_overrides, + _log_level_overrides, + log_name_overrides, + _retention_days_overrides, + ) = CommandLineInterface._read_hook_settings(tmp_path / ".github") + + assert log_name_overrides == {"post-edit-format": "quality-custom.log"} + + def test_per_hook_log_name_strips_outer_whitespace(self, tmp_path: Path) -> None: + """Per-hook log.name values are trimmed before validation and storage.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n" + " hooks:\n" + " post-edit-format:\n" + " log:\n" + " name: ' windows-style.log '\n", + encoding="utf-8", + ) + + ( + _enabled, + _mode, + _log_level, + _retention_days, + _log_dir, + _disabled_names, + _mode_overrides, + _log_level_overrides, + log_name_overrides, + _retention_days_overrides, + ) = CommandLineInterface._read_hook_settings(tmp_path / ".github") + + assert log_name_overrides == {"post-edit-format": "windows-style.log"} + + def test_global_hook_log_dir_override_is_parsed(self, tmp_path: Path) -> None: + """hooks.log_dir overrides the default log root for generated hook scripts.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n log_dir: .vstack/custom-logs\n", + encoding="utf-8", + ) + + ( + _enabled, + _mode, + _log_level, + _retention_days, + log_dir, + _disabled_names, + _mode_overrides, + _log_level_overrides, + _log_name_overrides, + _retention_days_overrides, + ) = CommandLineInterface._read_hook_settings(tmp_path / ".github") + + assert log_dir == ".vstack/custom-logs" + + def test_global_hook_log_dir_invalid_value_falls_back_to_default(self, tmp_path: Path) -> None: + """Invalid hooks.log_dir values fall back to the default generated log root.""" + vstack_dir = tmp_path / ".vstack" + vstack_dir.mkdir() + (vstack_dir / "config.yaml").write_text( + "hooks:\n log_dir: []\n", + encoding="utf-8", + ) + + ( + _enabled, + _mode, + _log_level, + _retention_days, + log_dir, + _disabled_names, + _mode_overrides, + _log_level_overrides, + _log_name_overrides, + _retention_days_overrides, + ) = CommandLineInterface._read_hook_settings(tmp_path / ".github") + + assert log_dir == ".vstack/logs" + class TestParseStageHandoffs: """Tests for CommandLineInterface._parse_stage_handoffs.""" diff --git a/tests/vstack/hooks/test_generator.py b/tests/vstack/hooks/test_generator.py index 52c5e69..ad0ac02 100644 --- a/tests/vstack/hooks/test_generator.py +++ b/tests/vstack/hooks/test_generator.py @@ -227,6 +227,111 @@ def test_render_uses_project_default_mode_for_shell_fallbacks(self, tmp_path: Pa assert "${VSTACK_HOOKS_MODE:-enforce}" in action["bash"] assert "else { 'enforce' }" in action["powershell"] + def test_render_uses_project_default_log_level_for_shell_fallbacks( + self, tmp_path: Path + ) -> None: + """Configured project hook log level is injected into generated shell fallbacks.""" + tmpl_dir = tmp_path / "hooks" / "log-level-test" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: log-level-test\n" + " description: 'test hook'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n sessionStart:\n - type: command\n" + " bash: 'level=${VSTACK_HOOKS_LOG_LEVEL:-minimal}'\n" + " powershell: '$level = if ($env:VSTACK_HOOKS_LOG_LEVEL) { $env:VSTACK_HOOKS_LOG_LEVEL } else { ''minimal'' }'\n", + encoding="utf-8", + ) + + gen = HookGenerator(default_log_level="verbose") + gen.templates_dir = tmp_path / "hooks" + artifact = gen.render_all()[0] + + content = json.loads(artifact.content) + action = content["hooks"]["sessionStart"][0] + assert "${VSTACK_HOOKS_LOG_LEVEL:-verbose}" in action["bash"] + assert "else { 'verbose' }" in action["powershell"] + + def test_render_uses_project_default_log_name_for_shell_targets(self, tmp_path: Path) -> None: + """Configured project hook log name is injected into generated shell fallbacks.""" + tmpl_dir = tmp_path / "hooks" / "path-test" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: path-test\n" + " description: 'test hook'\n purpose: quality\n security_level: low\n" + " log:\n name: hook-quality-alerts.log\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n postToolUse:\n - type: command\n" + " bash: 'log_name=${VSTACK_HOOK_LOG_NAME:-hook-quality-alerts.log}'\n" + " powershell: '$name = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { ''hook-quality-alerts.log'' }'\n", + encoding="utf-8", + ) + + gen = HookGenerator(log_name_overrides={"path-test": "custom-quality.log"}) + gen.templates_dir = tmp_path / "hooks" + artifact = gen.render_all()[0] + + content = json.loads(artifact.content) + action = content["hooks"]["postToolUse"][0] + assert "${VSTACK_HOOK_LOG_NAME:-custom-quality.log}" in action["bash"] + assert "else { 'custom-quality.log' }" in action["powershell"] + + def test_render_uses_per_hook_retention_days_override(self, tmp_path: Path) -> None: + """Per-hook retention_days overrides are applied to shell fallbacks.""" + tmpl_dir = tmp_path / "hooks" / "retention-test" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: retention-test\n" + " description: 'test hook'\n purpose: security\n security_level: high\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n postToolUse:\n - type: command\n" + " bash: 'retention_days=\"${VSTACK_HOOKS_LOG_RETENTION_DAYS:-7}\"'\n" + " powershell: '$retention = if ($env:VSTACK_HOOKS_LOG_RETENTION_DAYS) { $env:VSTACK_HOOKS_LOG_RETENTION_DAYS } else { \"7\" }'\n", + encoding="utf-8", + ) + + gen = HookGenerator( + default_log_retention_days=7, + log_retention_days_overrides={"retention-test": 30}, + ) + gen.templates_dir = tmp_path / "hooks" + artifact = gen.render_all()[0] + + content = json.loads(artifact.content) + action = content["hooks"]["postToolUse"][0] + # Verify retention_days override was applied to bash fallback + assert "${VSTACK_HOOKS_LOG_RETENTION_DAYS:-30}" in action["bash"] + # Verify retention_days override was applied to powershell fallback + assert 'else { "30" }' in action["powershell"] + + def test_render_uses_project_default_log_dir_for_shell_fallbacks(self, tmp_path: Path) -> None: + """Configured project hook log directory is injected into shell fallbacks.""" + tmpl_dir = tmp_path / "hooks" / "log-dir-test" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: log-dir-test\n" + " description: 'test hook'\n purpose: quality\n security_level: low\n" + " log:\n name: log-dir-test.log\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n postToolUse:\n - type: command\n" + " bash: 'log_root=${VSTACK_HOOK_LOG_DIR:-.vstack/logs}'\n" + " powershell: '$root = if ($env:VSTACK_HOOK_LOG_DIR) { $env:VSTACK_HOOK_LOG_DIR } else { ''.vstack/logs'' }'\n", + encoding="utf-8", + ) + + gen = HookGenerator(default_log_dir=".vstack/custom-logs") + gen.templates_dir = tmp_path / "hooks" + artifact = gen.render_all()[0] + + content = json.loads(artifact.content) + action = content["hooks"]["postToolUse"][0] + assert "${VSTACK_HOOK_LOG_DIR:-.vstack/custom-logs}" in action["bash"] + assert "else { '.vstack/custom-logs' }" in action["powershell"] + def test_find_templates_skips_disabled_hook_names(self, tmp_path: Path) -> None: """Disabled hook names are excluded from rendering and validation.""" enabled_dir = tmp_path / "hooks" / "enabled-hook" From f2acbfd5f48b9bcdb8c8514d259d7ddcf771a887 Mon Sep 17 00:00:00 2001 From: Erik Schaareman <erik@schaareman.org> Date: Mon, 11 May 2026 23:37:18 +0200 Subject: [PATCH 09/11] chore(deps): bump mypy and types-pyyaml --- poetry.lock | 323 +++++++++++++++++++++++++++++----------------------- 1 file changed, 182 insertions(+), 141 deletions(-) diff --git a/poetry.lock b/poetry.lock index bb286f2..907f0b0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,46 @@ # This file is automatically @generated by Poetry 2.3.4 and should not be changed by hand. +[[package]] +name = "ast-serialize" +version = "0.3.0" +description = "Python bindings for mypy AST serialization" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ast_serialize-0.3.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3a867927df59f76a18dc1d874a0b2c079b42c58972dca637905576deb0912e14"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a6fb063bf040abf8321e7b8113a0554eda445ffc508aa51287f8808886a5ae22"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5075cd8482573d743586779e5f9b652a015e37d4e95132d7e5a9bc5c8f483d8f"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:41560b27794f4553b0f77811e9fb325b77db4a2b39018d437e09932275306e66"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b967c01ca74909c5d90e0fe4393401e2cc5da5ebd9a6262a19e45ffd3757dec8"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:424ebb8f46cd993f7cec4009d119312d8433dd90e6b0df0499cd2c91bdcc5af9"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14b1d566b56e2ee70b11fec1de7e0b94ec7cd83717ec7d189967841a361190e"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ba30b18735f047ec11103d1ab92f4789cf1fea1e0dc89b04a2f5a0632fd79de"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e6ea0754cb7b0f682ebb005ffb0d18f8d17993490d9c289863cd69cacc4ab8df"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:a0c5aa1073a5ba7b2abaa4b54abe8b8d75c4d1e2d54a2ff70b0ca6222fea5728"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4e52650d834c1ea7791969a361de2c54c13b2fb4c519ec79445fa8b9021a147d"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15bd6af3f136c61dae27805eb6b8f3269e85a545c4c27ffe9e530ead78d2b36d"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-win32.whl", hash = "sha256:d188bfe37b674b49708497683051d4b571366a668799c9b8e8a94513694969d9"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5832c2fdf8f8a6cf682b4cfcf677f5eaf39b4ddbc490f5480cfccdd1e7ce8fa1"}, + {file = "ast_serialize-0.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:670f177188d128fb7f9f15b5ad0e1b553d22c34e3f584dcb83eb8077600437f0"}, + {file = "ast_serialize-0.3.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ec2fafa5e4313cc8feed96e436ebe19ac7bc6fa41fbc2827e826c48b9e4c3a9"}, + {file = "ast_serialize-0.3.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ef6d3c08b7b4cd29b48410338e134764a00e76d25841eb02c1084e868c888ecc"}, + {file = "ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d841424f41b886e98044abc80769c14a956e6e5ccd5fb5b0d9f5ead72be18a4"}, + {file = "ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d21453734ad39367ede5d37efe4f59f830ce1c09f432fc72a90e368f77a4a3e7"}, + {file = "ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5e110cdce2a347e1dd987529c88ef54d26f67848dce3eba1b3b2cc2cf085c94"}, + {file = "ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6e23a98e57560a055f5c4b68700a0fd5ce483d2814c23140b3638c7f5d1e61"}, + {file = "ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c9e763d70293d65ce1e1ea8c943140c68d0953f0268c7ee0998f2e07f77dd0"}, + {file = "ast_serialize-0.3.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4388a1796c228f1ce5c391426f7d21a0003ad3b47f677dbeded9bd1a85c7209f"}, + {file = "ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5283cdcc0c64c3d8b9b688dc6aaa012d9c0cf1380a7f774a6bae6a1c01b3205a"}, + {file = "ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f5ef88cc5842a5d7a6ac09dc0d5fc2c98f5d276c1f076f866d55047ce886785b"}, + {file = "ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cc14bf402bdc0978594ecce783793de2c7470cd4f5cd7eb286ca97ed8ff7cba9"}, + {file = "ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11eae0cf1b7b3e0678133cc2daa974ea972caf02eb4b3aa062af6fa9acd52c57"}, + {file = "ast_serialize-0.3.0-cp39-abi3-win32.whl", hash = "sha256:2db3dd99de5e6a5a11d7dda73de8750eb6e5baaf25245adf7bdcfe64b6108ae2"}, + {file = "ast_serialize-0.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:a2cd125adccf7969470621905d302750cd25951f22ea430d9a25b7be031e5549"}, + {file = "ast_serialize-0.3.0-cp39-abi3-win_arm64.whl", hash = "sha256:0dd00da29985f15f50dc35728b7e1e7c84507bccfea1d9914738530f1c72238a"}, + {file = "ast_serialize-0.3.0.tar.gz", hash = "sha256:1bc3ca09a63a021376527c4e938deedd11d11d675ce850e6f9c7487f5889992b"}, +] + [[package]] name = "cachetools" version = "7.1.0" @@ -208,161 +249,162 @@ files = [ [[package]] name = "librt" -version = "0.9.0" +version = "0.11.0" description = "Mypyc runtime library" optional = false python-versions = ">=3.9" groups = ["dev"] markers = "platform_python_implementation != \"PyPy\"" files = [ - {file = "librt-0.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f8e12706dcb8ff6b3ed57514a19e45c49ad00bcd423e87b2b2e4b5f64578443"}, - {file = "librt-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e3dda8345307fd7306db0ed0cb109a63a2c85ba780eb9dc2d09b2049a931f9c"}, - {file = "librt-0.9.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:de7dac64e3eb832ffc7b840eb8f52f76420cde1b845be51b2a0f6b870890645e"}, - {file = "librt-0.9.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22a904cbdb678f7cb348c90d543d3c52f581663d687992fee47fd566dcbf5285"}, - {file = "librt-0.9.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:224b9727eb8bc188bc3bcf29d969dba0cd61b01d9bac80c41575520cc4baabb2"}, - {file = "librt-0.9.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e94cbc6ad9a6aeea46d775cbb11f361022f778a9cc8cc90af653d3a594b057ce"}, - {file = "librt-0.9.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7bc30ad339f4e1a01d4917d645e522a0bc0030644d8973f6346397c93ba1503f"}, - {file = "librt-0.9.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:56d65b583cf43b8cf4c8fbe1e1da20fa3076cc32a1149a141507af1062718236"}, - {file = "librt-0.9.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0a1be03168b2691ba61927e299b352a6315189199ca18a57b733f86cb3cc8d38"}, - {file = "librt-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:63c12efcd160e1d14da11af0c46c0217473e1e0d2ae1acbccc83f561ea4c2a7b"}, - {file = "librt-0.9.0-cp310-cp310-win32.whl", hash = "sha256:e9002e98dcb1c0a66723592520decd86238ddcef168b37ff6cfb559200b4b774"}, - {file = "librt-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9fcb461fbf70654a52a7cc670e606f04449e2374c199b1825f754e16dacfedd8"}, - {file = "librt-0.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90904fac73c478f4b83f4ed96c99c8208b75e6f9a8a1910548f69a00f1eaa671"}, - {file = "librt-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:789fff71757facc0738e8d89e3b84e4f0251c1c975e85e81b152cdaca927cc2d"}, - {file = "librt-0.9.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1bf465d1e5b0a27713862441f6467b5ab76385f4ecf8f1f3a44f8aa3c695b4b6"}, - {file = "librt-0.9.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f819e0c6413e259a17a7c0d49f97f405abadd3c2a316a3b46c6440b7dbbedbb1"}, - {file = "librt-0.9.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0785c2fb4a81e1aece366aa3e2e039f4a4d7d21aaaded5227d7f3c703427882"}, - {file = "librt-0.9.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80b25c7b570a86c03b5da69e665809deb39265476e8e21d96a9328f9762f9990"}, - {file = "librt-0.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d4d16b608a1c43d7e33142099a75cd93af482dadce0bf82421e91cad077157f4"}, - {file = "librt-0.9.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:194fc1a32e1e21fe809d38b5faea66cc65eaa00217c8901fbdb99866938adbdb"}, - {file = "librt-0.9.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8c6bc1384d9738781cfd41d09ad7f6e8af13cfea2c75ece6bd6d2566cdea2076"}, - {file = "librt-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cb151e52a044f06e54ac7f7b47adbfc89b5c8e2b63e1175a9d587c43e8942a"}, - {file = "librt-0.9.0-cp311-cp311-win32.whl", hash = "sha256:f100bfe2acf8a3689af9d0cc660d89f17286c9c795f9f18f7b62dd1a6b247ae6"}, - {file = "librt-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:0b73e4266307e51c95e09c0750b7ec383c561d2e97d58e473f6f6a209952fbb8"}, - {file = "librt-0.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc5518873822d2faa8ebdd2c1a4d7c8ef47b01a058495ab7924cb65bdbf5fc9a"}, - {file = "librt-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b3e3bc363f71bda1639a4ee593cb78f7fbfeacc73411ec0d4c92f00730010a4"}, - {file = "librt-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a09c2f5869649101738653a9b7ab70cf045a1105ac66cbb8f4055e61df78f2d"}, - {file = "librt-0.9.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ca8e133d799c948db2ab1afc081c333a825b5540475164726dcbf73537e5c2f"}, - {file = "librt-0.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:603138ee838ee1583f1b960b62d5d0007845c5c423feb68e44648b1359014e27"}, - {file = "librt-0.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4003f70c56a5addd6aa0897f200dd59afd3bf7bcd5b3cce46dd21f925743bc2"}, - {file = "librt-0.9.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78042f6facfd98ecb25e9829c7e37cce23363d9d7c83bc5f72702c5059eb082b"}, - {file = "librt-0.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a361c9434a64d70a7dbb771d1de302c0cc9f13c0bffe1cf7e642152814b35265"}, - {file = "librt-0.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dd2c7e082b0b92e1baa4da28163a808672485617bc855cc22a2fd06978fa9084"}, - {file = "librt-0.9.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7e6274fd33fc5b2a14d41c9119629d3ff395849d8bcbc80cf637d9e8d2034da8"}, - {file = "librt-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5093043afb226ecfa1400120d1ebd4442b4f99977783e4f4f7248879009b227f"}, - {file = "librt-0.9.0-cp312-cp312-win32.whl", hash = "sha256:9edcc35d1cae9fd5320171b1a838c7da8a5c968af31e82ecc3dff30b4be0957f"}, - {file = "librt-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc2917258e131ae5f958a4d872e07555b51cb7466a43433218061c74ef33745"}, - {file = "librt-0.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:90e6d5420fc8a300518d4d2288154ff45005e920425c22cbbfe8330f3f754bd9"}, - {file = "librt-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29b68cd9714531672db62cc54f6e8ff981900f824d13fa0e00749189e13778e"}, - {file = "librt-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d5c8a5929ac325729f6119802070b561f4db793dffc45e9ac750992a4ed4d22"}, - {file = "librt-0.9.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:756775d25ec8345b837ab52effee3ad2f3b2dfd6bbee3e3f029c517bd5d8f05a"}, - {file = "librt-0.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8f5d00b49818f4e2b1667db994488b045835e0ac16fe2f924f3871bd2b8ac5"}, - {file = "librt-0.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c81aef782380f0f13ead670aae01825eb653b44b046aa0e5ebbb79f76ed4aa11"}, - {file = "librt-0.9.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66b58fed90a545328e80d575467244de3741e088c1af928f0b489ebec3ef3858"}, - {file = "librt-0.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e78fb7419e07d98c2af4b8567b72b3eaf8cb05caad642e9963465569c8b2d87e"}, - {file = "librt-0.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c3786f0f4490a5cd87f1ed6cefae833ad6b1060d52044ce0434a2e85893afd0"}, - {file = "librt-0.9.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8494cfc61e03542f2d381e71804990b3931175a29b9278fdb4a5459948778dc2"}, - {file = "librt-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:07cf11f769831186eeac424376e6189f20ace4f7263e2134bdb9757340d84d4d"}, - {file = "librt-0.9.0-cp313-cp313-win32.whl", hash = "sha256:850d6d03177e52700af605fd60db7f37dcb89782049a149674d1a9649c2138fd"}, - {file = "librt-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:a5af136bfba820d592f86c67affcef9b3ff4d4360ac3255e341e964489b48519"}, - {file = "librt-0.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:4c4d0440a3a8e31d962340c3e1cc3fc9ee7febd34c8d8f770d06adb947779ea5"}, - {file = "librt-0.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:3f05d145df35dca5056a8bc3838e940efebd893a54b3e19b2dda39ceaa299bcb"}, - {file = "librt-0.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1c587494461ebd42229d0f1739f3aa34237dd9980623ecf1be8d3bcba79f4499"}, - {file = "librt-0.9.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0a2040f801406b93657a70b72fa12311063a319fee72ce98e1524da7200171f"}, - {file = "librt-0.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f38bc489037eca88d6ebefc9c4d41a4e07c8e8b4de5188a9e6d290273ad7ebb1"}, - {file = "librt-0.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3fd278f5e6bf7c75ccd6d12344eb686cc020712683363b66f46ac79d37c799f"}, - {file = "librt-0.9.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fcbdf2a9ca24e87bbebb47f1fe34e531ef06f104f98c9ccfc953a3f3344c567a"}, - {file = "librt-0.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e306d956cfa027fe041585f02a1602c32bfa6bb8ebea4899d373383295a6c62f"}, - {file = "librt-0.9.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:465814ab157986acb9dfa5ccd7df944be5eefc0d08d31ec6e8d88bc71251d845"}, - {file = "librt-0.9.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:703f4ae36d6240bfe24f542bac784c7e4194ec49c3ba5a994d02891649e2d85b"}, - {file = "librt-0.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3be322a15ee5e70b93b7a59cfd074614f22cc8c9ff18bd27f474e79137ea8d3b"}, - {file = "librt-0.9.0-cp314-cp314-win32.whl", hash = "sha256:b8da9f8035bb417770b1e1610526d87ad4fc58a2804dc4d79c53f6d2cf5a6eb9"}, - {file = "librt-0.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:b8bd70d5d816566a580d193326912f4a76ec2d28a97dc4cd4cc831c0af8e330e"}, - {file = "librt-0.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:fc5758e2b7a56532dc33e3c544d78cbaa9ecf0a0f2a2da2df882c1d6b99a317f"}, - {file = "librt-0.9.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f24b90b0e0c8cc9491fb1693ae91fe17cb7963153a1946395acdbdd5818429a4"}, - {file = "librt-0.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fe56e80badb66fdcde06bef81bbaa5bfcf6fbd7aefb86222d9e369c38c6b228"}, - {file = "librt-0.9.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:527b5b820b47a09e09829051452bb0d1dd2122261254e2a6f674d12f1d793d54"}, - {file = "librt-0.9.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d429bdd4ac0ab17c8e4a8af0ed2a7440b16eba474909ab357131018fe8c7e71"}, - {file = "librt-0.9.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7202bdcac47d3a708271c4304a474a8605a4a9a4a709e954bf2d3241140aa938"}, - {file = "librt-0.9.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0d620e74897f8c2613b3c4e2e9c1e422eb46d2ddd07df540784d44117836af3"}, - {file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d69fc39e627908f4c03297d5a88d9284b73f4d90b424461e32e8c2485e21c283"}, - {file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c2640e23d2b7c98796f123ffd95cf2022c7777aa8a4a3b98b36c570d37e85eee"}, - {file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:451daa98463b7695b0a30aa56bf637831ea559e7b8101ac2ef6382e8eb15e29c"}, - {file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:928bd06eca2c2bbf4349e5b817f837509b0604342e65a502de1d50a7570afd15"}, - {file = "librt-0.9.0-cp314-cp314t-win32.whl", hash = "sha256:a9c63e04d003bc0fb6a03b348018b9a3002f98268200e22cc80f146beac5dc40"}, - {file = "librt-0.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f162af66a2ed3f7d1d161a82ca584efd15acd9c1cff190a373458c32f7d42118"}, - {file = "librt-0.9.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a4b25c6c25cac5d0d9d6d6da855195b254e0021e513e0249f0e3b444dc6e0e61"}, - {file = "librt-0.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5112c2fb7c2eefefaeaf5c97fec81343ef44ee86a30dcfaa8223822fba6467b4"}, - {file = "librt-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a81eea9b999b985e4bacc650c4312805ea7008fd5e45e1bf221310176a7bcb3a"}, - {file = "librt-0.9.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eea1b54943475f51698f85fa230c65ccac769f1e603b981be060ac5763d90927"}, - {file = "librt-0.9.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81107843ed1836874b46b310f9b1816abcb89912af627868522461c3b7333c0f"}, - {file = "librt-0.9.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa95738a68cedd3a6f5492feddc513e2e166b50602958139e47bbdd82da0f5a7"}, - {file = "librt-0.9.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6788207daa0c19955d2b668f3294a368d19f67d9b5f274553fd073c1260cbb9f"}, - {file = "librt-0.9.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f48c963a76d71b9d7927eb817b543d0dccd52ab6648b99d37bd54f4cd475d856"}, - {file = "librt-0.9.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:42ff8a962554c350d4a83cf47d9b7b78b0e6ff7943e87df7cdfc97c07f3c016f"}, - {file = "librt-0.9.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:657f8ba7b9eaaa82759a104137aed2a3ef7bc46ccfd43e0d89b04005b3e0a4cc"}, - {file = "librt-0.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d03fa4fd277a7974c1978c92c374c57f44edeee163d147b477b143446ad1bf6"}, - {file = "librt-0.9.0-cp39-cp39-win32.whl", hash = "sha256:d9da80e5b04acce03ced8ba6479a71c2a2edf535c2acc0d09c80d2f80f3bad15"}, - {file = "librt-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:54d412e47c21b85865676ed0724e37a89e9593c2eee1e7367adf85bfad56ffb1"}, - {file = "librt-0.9.0.tar.gz", hash = "sha256:a0951822531e7aee6e0dfb556b30d5ee36bbe234faf60c20a16c01be3530869d"}, + {file = "librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f"}, + {file = "librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45"}, + {file = "librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c"}, + {file = "librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33"}, + {file = "librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884"}, + {file = "librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0"}, + {file = "librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89"}, + {file = "librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4"}, + {file = "librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29"}, + {file = "librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9"}, + {file = "librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5"}, + {file = "librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b"}, + {file = "librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89"}, + {file = "librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412"}, + {file = "librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d"}, + {file = "librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73"}, + {file = "librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c"}, + {file = "librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46"}, + {file = "librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3"}, + {file = "librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67"}, + {file = "librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a"}, + {file = "librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a"}, + {file = "librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8"}, + {file = "librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a"}, + {file = "librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9"}, + {file = "librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c"}, + {file = "librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894"}, + {file = "librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c"}, + {file = "librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea"}, + {file = "librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230"}, + {file = "librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2"}, + {file = "librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e"}, + {file = "librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e"}, + {file = "librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47"}, + {file = "librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44"}, + {file = "librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd"}, + {file = "librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4"}, + {file = "librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8"}, + {file = "librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b"}, + {file = "librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175"}, + {file = "librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe"}, + {file = "librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f"}, + {file = "librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7"}, + {file = "librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1"}, + {file = "librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72"}, + {file = "librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd"}, + {file = "librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8"}, + {file = "librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c"}, + {file = "librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253"}, + {file = "librt-0.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6bd72d903911d995ab666dbd1871f8b1e80925a699af8063fbf50053329fb05f"}, + {file = "librt-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ef69ac715f3cd8e5cd252cb2aebfa72c015492aacc339d5d7bf8fef3c62c677"}, + {file = "librt-0.11.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:624a40c4a4ad7773315c287276cd024509b2c66ff5904f504bfc08d2c70293ab"}, + {file = "librt-0.11.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:41dc19fe150b69716c8ece4f76773a9e8813fe3e35e032a58b4d46423fb8d7c0"}, + {file = "librt-0.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e8bd98ea9c47ae90b319a087ab28dac493f1ffbc1ecd1f28fcdbf3b7e1108d1"}, + {file = "librt-0.11.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84308fc49423ce6475d1c5d1985cd69a8ca9f0325fc7d5f81bb690a3f3625d4e"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ff0fbaf5f44a21beeb0110f2ab64f45135a9536a834b79c0d1ef018f2786bbfa"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9c028a9442a18e266955d364ce42259136e79a7ba14d773e0d778d5f70cd56f1"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9f1692105a02bcf853f355032a5fdc5494358ef83d8fd22d16de375c85cec3f5"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7a80a71e1fda83cc752a9141e87aae7fef279538597564d670e9ce513f286192"}, + {file = "librt-0.11.0-cp39-cp39-win32.whl", hash = "sha256:140695816ddf3c86eb972981a26f35efd871c44b0c3aed44c8cd01749386617f"}, + {file = "librt-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:92f7ff819c197fc30473190a12c2856f325ac90aabfccbeb2072d28cc2e234e3"}, + {file = "librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1"}, ] [[package]] name = "mypy" -version = "1.20.2" +version = "2.1.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4"}, - {file = "mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997"}, - {file = "mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14"}, - {file = "mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99"}, - {file = "mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c"}, - {file = "mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd"}, - {file = "mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2"}, - {file = "mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c"}, - {file = "mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3"}, - {file = "mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254"}, - {file = "mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98"}, - {file = "mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac"}, - {file = "mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67"}, - {file = "mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100"}, - {file = "mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b"}, - {file = "mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4"}, - {file = "mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6"}, - {file = "mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066"}, - {file = "mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102"}, - {file = "mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9"}, - {file = "mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58"}, - {file = "mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026"}, - {file = "mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943"}, - {file = "mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517"}, - {file = "mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15"}, - {file = "mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee"}, - {file = "mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f"}, - {file = "mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330"}, - {file = "mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30"}, - {file = "mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924"}, - {file = "mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb"}, - {file = "mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc"}, - {file = "mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558"}, - {file = "mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8"}, - {file = "mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3"}, - {file = "mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609"}, - {file = "mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2"}, - {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c"}, - {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744"}, - {file = "mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6"}, - {file = "mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec"}, - {file = "mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382"}, - {file = "mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563"}, - {file = "mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665"}, + {file = "mypy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc"}, + {file = "mypy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849"}, + {file = "mypy-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd"}, + {file = "mypy-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166"}, + {file = "mypy-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8"}, + {file = "mypy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8"}, + {file = "mypy-2.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e"}, + {file = "mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41"}, + {file = "mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca"}, + {file = "mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538"}, + {file = "mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398"}, + {file = "mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563"}, + {file = "mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389"}, + {file = "mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666"}, + {file = "mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af"}, + {file = "mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6"}, + {file = "mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211"}, + {file = "mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b"}, + {file = "mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22"}, + {file = "mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b"}, + {file = "mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8"}, + {file = "mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5"}, + {file = "mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e"}, + {file = "mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e"}, + {file = "mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285"}, + {file = "mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5"}, + {file = "mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65"}, + {file = "mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d"}, + {file = "mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2"}, + {file = "mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f"}, + {file = "mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4"}, + {file = "mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef"}, + {file = "mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135"}, + {file = "mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21"}, + {file = "mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57"}, + {file = "mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e"}, + {file = "mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780"}, + {file = "mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd"}, + {file = "mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08"}, + {file = "mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081"}, + {file = "mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7"}, + {file = "mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6"}, + {file = "mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289"}, + {file = "mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633"}, ] [package.dependencies] -librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""} +ast-serialize = ">=0.3.0,<1.0.0" +librt = {version = ">=0.11.0", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" pathspec = ">=1.0.0" typing_extensions = {version = ">=4.6.0", markers = "python_version < \"3.15\""} @@ -372,7 +414,6 @@ dmypy = ["psutil (>=4.0)"] faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] -native-parser = ["ast-serialize (>=0.1.1,<1.0.0)"] reports = ["lxml"] [[package]] @@ -723,14 +764,14 @@ completion = ["argcomplete (>=3.6.3)"] [[package]] name = "types-pyyaml" -version = "6.0.12.20260508" +version = "6.0.12.20260510" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "types_pyyaml-6.0.12.20260508-py3-none-any.whl", hash = "sha256:edc094ed3a918b0c6232f71a5b67fdf38e76e17517b7d87bfbb9fc27d442fb51"}, - {file = "types_pyyaml-6.0.12.20260508.tar.gz", hash = "sha256:5ae42149c3ebf7aaaf6c65ee49af590c80f0ba52e9e3f75a75c5564b33556fa6"}, + {file = "types_pyyaml-6.0.12.20260510-py3-none-any.whl", hash = "sha256:3492eb9ba4d9d833473214c4d5736cccf5f37d93f5854059721e1c84f785309d"}, + {file = "types_pyyaml-6.0.12.20260510.tar.gz", hash = "sha256:09c1f1cb65a6eebea1e2e51ccf4918b8288e152909609a35cdb0d805efd125ad"}, ] [[package]] From 6801b78605a1e69c42f4887472129c778977cc07 Mon Sep 17 00:00:00 2001 From: Erik Schaareman <erik@schaareman.org> Date: Tue, 12 May 2026 21:19:02 +0200 Subject: [PATCH 10/11] chore(deps): refresh dependency and action baselines --- .github/dependabot.yml | 2 +- .github/workflows/automerge.yml | 8 +-- .github/workflows/check.yml | 6 +- .github/workflows/codeql.yml | 8 +-- .github/workflows/commit.yml | 12 ++-- .github/workflows/publish.yml | 12 ++-- .github/workflows/release.yml | 6 +- .github/workflows/security.yml | 10 +-- .github/workflows/verify.yml | 10 +-- docs/architecture/overview.md | 8 ++- docs/design/overview.md | 3 +- pyproject.toml | 28 ++++---- src/vstack/artifacts/protocol.py | 9 +-- src/vstack/cli/constants.py | 2 +- src/vstack/hooks/generator.py | 95 +++++++++++++--------------- tests/vstack/cli/test_constants.py | 12 ++++ tests/vstack/hooks/test_generator.py | 78 ++++++++++++++++++++++- 17 files changed, 198 insertions(+), 111 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 66bd500..2fd1a14 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,7 +23,7 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" assignees: - "eschaar" open-pull-requests-limit: 5 diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 5d7f0ed..ba458db 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Fetch Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v3 + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -54,7 +54,7 @@ jobs: - name: Approve eligible PR if: steps.decision.outputs.should_automerge == 'true' - uses: actions/github-script@v9 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -83,7 +83,7 @@ jobs: - name: Update branch for eligible PR if: steps.decision.outputs.should_automerge == 'true' - uses: actions/github-script@v9 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -111,7 +111,7 @@ jobs: - name: Enable auto-merge for eligible PR if: steps.decision.outputs.should_automerge == 'true' - uses: actions/github-script@v9 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e82876c..4b2495c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -25,7 +25,7 @@ permissions: env: PYTHON_VERSION: "3.11" - POETRY_VERSION: "2.3.4" + POETRY_VERSION: "2.4.1" POETRY_VIRTUALENVS_IN_PROJECT: "true" FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" @@ -37,13 +37,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install Poetry run: pipx install "poetry==${POETRY_VERSION}" - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: ${{ env.PYTHON_VERSION }} cache: poetry diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3fddf4d..62bc181 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,15 +24,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@5e316336eb4f107009e477d4bfbfff13d7250fae with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@v4 + uses: github/codeql-action/autobuild@5e316336eb4f107009e477d4bfbfff13d7250fae - name: Analyze - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@5e316336eb4f107009e477d4bfbfff13d7250fae diff --git a/.github/workflows/commit.yml b/.github/workflows/commit.yml index db1c6f7..24f5e30 100644 --- a/.github/workflows/commit.yml +++ b/.github/workflows/commit.yml @@ -26,7 +26,7 @@ permissions: env: PYTHON_VERSION: "3.11" - POETRY_VERSION: "2.3.4" + POETRY_VERSION: "2.4.1" POETRY_VIRTUALENVS_IN_PROJECT: "true" FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" @@ -37,14 +37,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: # Full history is required to inspect the full pushed commit range. fetch-depth: 0 - name: Validate commits with commit-check if: github.event_name == 'push' - uses: commit-check/commit-check-action@v2 + uses: commit-check/commit-check-action@9b531e7dc071c3a7d92fd1bea4dec91119ca4890 with: # Commit and branch policy is read from cchk.toml in repo root. message: true @@ -56,7 +56,7 @@ jobs: - name: Validate commit messages with commit-check if: github.event_name == 'pull_request' - uses: commit-check/commit-check-action@v2 + uses: commit-check/commit-check-action@9b531e7dc071c3a7d92fd1bea4dec91119ca4890 with: # On PR events, validate commit messages only (branch refs are pull/*). message: true @@ -131,13 +131,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install Poetry run: pipx install "poetry==${POETRY_VERSION}" - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: ${{ env.PYTHON_VERSION }} cache: poetry diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6cdfeda..719a525 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,7 @@ permissions: env: PYTHON_VERSION: "3.11" - POETRY_VERSION: "2.3.4" + POETRY_VERSION: "2.4.1" FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" TRUSTED_RELEASE_ACTORS: "vstack-release-bot[bot],eschaar" @@ -73,7 +73,7 @@ jobs: echo "release_actor='$ACTOR' is trusted." - name: Checkout release tag - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: ref: refs/tags/${{ github.event.release.tag_name }} fetch-depth: 0 @@ -82,10 +82,10 @@ jobs: run: pipx install "poetry==${POETRY_VERSION}" - name: Install poetry-dynamic-versioning plugin - run: pipx inject poetry "poetry-dynamic-versioning[plugin]>=1.0.0,<2.0.0" + run: pipx inject poetry "poetry-dynamic-versioning[plugin]>=1.10.0,<2.0.0" - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: ${{ env.PYTHON_VERSION }} @@ -124,11 +124,11 @@ jobs: - name: Publish to PyPI (trusted publishing) id: publish_trusted continue-on-error: true - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b - name: Publish to PyPI (API token fallback) if: steps.publish_trusted.outcome == 'failure' && env.PYPI_API_TOKEN != '' - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b with: user: __token__ password: ${{ env.PYPI_API_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 16db063..dc313a6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,14 +29,14 @@ jobs: steps: - name: Generate app token id: app-token - uses: actions/create-github-app-token@v3 + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 with: # v3 requires the GitHub App client ID. client-id: ${{ secrets.APP_CLIENT_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Checkout repository metadata - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} @@ -70,7 +70,7 @@ jobs: fi - name: Run release-please - uses: googleapis/release-please-action@v5 + uses: googleapis/release-please-action@0dfd8538845b8e92600d271a895a5372865d4062 with: token: ${{ steps.app-token.outputs.token }} config-file: .release-please-config.json diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index f8dd294..be6bb04 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -21,11 +21,11 @@ env: # Keep scanner runtime consistent across runs. PYTHON_VERSION: "3.11" # Pin Poetry CLI version for deterministic CI behavior. - POETRY_VERSION: "2.3.4" + POETRY_VERSION: "2.4.1" # Keep Poetry virtual environments inside the workspace for deterministic paths. POETRY_VIRTUALENVS_IN_PROJECT: "true" # Pin pip-audit to avoid non-deterministic CI failures from tool updates. - PIP_AUDIT_VERSION: "2.9.0" + PIP_AUDIT_VERSION: "2.10.0" FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" jobs: @@ -36,13 +36,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install Poetry run: pipx install "poetry==${POETRY_VERSION}" - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: ${{ env.PYTHON_VERSION }} cache: poetry @@ -60,7 +60,7 @@ jobs: - name: Secret scan # Scan only PR diff range to reduce noise and speed up checks. - uses: trufflesecurity/trufflehog@v3.95.2 + uses: trufflesecurity/trufflehog@17456f8c7d042d8c82c9a8ca9e937231f9f42e26 with: path: ./ base: ${{ github.event.pull_request.base.sha }} diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index b8b3ef4..88daab1 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -21,7 +21,7 @@ env: # Shared interpreter version for reproducible CI behavior. PYTHON_VERSION: "3.11" # Pin Poetry CLI version for deterministic CI behavior. - POETRY_VERSION: "2.3.4" + POETRY_VERSION: "2.4.1" # Keep Poetry virtual environments inside the workspace for deterministic paths. POETRY_VIRTUALENVS_IN_PROJECT: "true" FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" @@ -39,13 +39,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install Poetry run: pipx install "poetry==${POETRY_VERSION}" - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: ${{ matrix.python-version }} cache: poetry @@ -64,13 +64,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install Poetry run: pipx install "poetry==${POETRY_VERSION}" - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: ${{ env.PYTHON_VERSION }} cache: poetry diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index a44cc19..792863c 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -6,7 +6,7 @@ ## overview vstack is a VS Code–native AI engineering workflow system. It provides template-driven -skills, agents, instructions, and prompts for planning, reviewing, verifying, and +skills, agents, hooks, instructions, and prompts for planning, reviewing, verifying, and releasing software via GitHub Copilot Agent Mode. **System style:** `platform` — a standalone CLI tool and SDK. vstack installs @@ -26,10 +26,11 @@ vstack/ │ ├── agents/ ← AGENT_SCHEMA, AGENT_TYPE │ ├── instructions/ ← instruction config and wrappers │ ├── prompts/ ← prompt config and wrappers +│ ├── hooks/ ← hook config and YAML-to-JSON generator │ ├── manifest/ ← Manifest, ManifestFile, ArtifactEntry, checksums │ ├── cli/ ← interface, registry, service, per-command handlers, helpers │ └── _templates/ ← source templates for all artifact types -│ ├── skills/, agents/, instructions/, prompts/ +│ ├── skills/, agents/, hooks/, instructions/, prompts/ │ ├── docs/ ← baseline doc stubs (seeded by vstack install) │ └── project/ ← .vstack/ config and artifact starter templates ├── docs/ @@ -45,6 +46,7 @@ vstack/ ├── .github/ ← generated Copilot artifacts (never edit directly) │ ├── skills/<name>/SKILL.md │ ├── agents/<name>.agent.md +│ ├── hooks/<name>.json │ ├── instructions/<name>.instructions.md │ └── prompts/<name>.prompt.md └── README.md @@ -105,7 +107,7 @@ have defined skill access and artifact ownership. See ### 5. manifest (`.vstack/vstack.json`) Generated at install time at `.vstack/vstack.json`. Tracks every `.github/` artifact -installed by `vstack init` (skills, agents, instructions, and prompts), including a +installed by `vstack init` (skills, agents, hooks, instructions, and prompts), including a per-file SHA-256 checksum, version, and algorithm so that: - `vstack uninstall` removes exactly the files it installed. diff --git a/docs/design/overview.md b/docs/design/overview.md index 8c51291..f76a14a 100644 --- a/docs/design/overview.md +++ b/docs/design/overview.md @@ -20,7 +20,7 @@ ______________________________________________________________________ ### 1.1 artifact lifecycle states -An artifact (skill, agent, instruction, prompt) exists in one of these states relative +An artifact (skill, agent, instruction, prompt, hook) exists in one of these states relative to the target `.github/` directory: ```text @@ -77,6 +77,7 @@ stateDiagram-v2 } ], "agents": [], + "hooks": [], "instructions": [], "prompts": [] } diff --git a/pyproject.toml b/pyproject.toml index 41a528e..a3d72ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,16 +74,16 @@ include = [ version = "0.0.0" [tool.poetry.requires-plugins] -poetry-dynamic-versioning = { version = ">=1.0.0,<2.0.0", extras = ["plugin"] } +poetry-dynamic-versioning = { version = ">=1.10.0,<2.0.0", extras = ["plugin"] } [tool.poetry.group.dev.dependencies] -pytest = ">=9.0" -pytest-cov = ">=6.0" +pytest = ">=9.0.3" +pytest-cov = ">=7.1.0" ruff = ">=0.15.12" -mypy = ">=1.20.2" +mypy = ">=2.1.0" pre-commit = ">=4.6.0" -tox = ">=4.15" -types-PyYAML = ">=6.0" +tox = ">=4.54.0" +types-PyYAML = ">=6.0.12.20260510" # --------------------------------------------------------------------------- @@ -146,7 +146,7 @@ py-version = "3.11" # --------------------------------------------------------------------------- [build-system] -requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +requires = ["poetry-core>=2.4.0", "poetry-dynamic-versioning>=1.10.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" # --------------------------------------------------------------------------- @@ -177,7 +177,7 @@ extraPaths = ["scripts"] [tool.tox] legacy_tox_ini = """ [tox] -min_version = 4.15 +min_version = 4.54.0 env_list = py311, py312, py313, py314, lint, type skip_missing_interpreters = true @@ -185,9 +185,9 @@ skip_missing_interpreters = true description = Run pytest for {env_name} package = skip deps = - pytest>=9.0 - pytest-cov>=6.0 - pyyaml>=6.0 + pytest>=9.0.3 + pytest-cov>=7.1.0 + pyyaml>=6.0.3 setenv = PYTHONPATH = {tox_root}/src commands = @@ -197,7 +197,7 @@ commands = description = Run ruff lint checks package = skip deps = - ruff>=0.15 + ruff>=0.15.12 commands = ruff check src tests @@ -205,8 +205,8 @@ commands = description = Run mypy type checks package = skip deps = - mypy>=1.20 - types-PyYAML>=6.0 + mypy>=2.1.0 + types-PyYAML>=6.0.12.20260510 commands = mypy src tests """ diff --git a/src/vstack/artifacts/protocol.py b/src/vstack/artifacts/protocol.py index 1185540..ed248fb 100644 --- a/src/vstack/artifacts/protocol.py +++ b/src/vstack/artifacts/protocol.py @@ -17,10 +17,11 @@ class ArtifactGenerator(Protocol): """Structural protocol satisfied by all artifact generators. - Implemented by :class:`~vstack.skills.generator.SkillGenerator`, - :class:`~vstack.agents.generator.AgentGenerator`, - :class:`~vstack.instructions.generator.InstructionGenerator`, and - :class:`~vstack.prompts.generator.PromptGenerator`. + In practice this protocol is satisfied by + :class:`~vstack.artifacts.generator.GenericArtifactGenerator` and + specialized subclasses such as + :class:`~vstack.agents.generator.AgentGenerator` and + :class:`~vstack.hooks.generator.HookGenerator`. """ def generate(self, output_dir: Path) -> ArtifactResult: diff --git a/src/vstack/cli/constants.py b/src/vstack/cli/constants.py index 57949d2..26b35ee 100644 --- a/src/vstack/cli/constants.py +++ b/src/vstack/cli/constants.py @@ -95,7 +95,7 @@ class ArtifactState: # Names that must exist for each artifact type (used in verify --source). EXPECTED_INPUT_NAMES: dict[str, list[str]] = { "skill": EXPECTED_CANONICAL_NAMES, - "agent": ["architect", "designer", "engineer", "product", "release", "tester"], + "agent": ["architect", "designer", "engineer", "planner", "product", "release", "tester"], "instruction": [ "git", "java", diff --git a/src/vstack/hooks/generator.py b/src/vstack/hooks/generator.py index 2289ebd..17d2be7 100644 --- a/src/vstack/hooks/generator.py +++ b/src/vstack/hooks/generator.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import re from pathlib import Path import yaml @@ -109,6 +110,30 @@ def _log_name_for_hook(self, hook_name: str) -> str | None: """Resolve the configured log filename override for one hook.""" return self.log_name_overrides.get(hook_name) + @staticmethod + def _sanitize_log_name(log_name: str) -> str | None: + """Normalize configured log filename overrides to basename-only values.""" + normalized = log_name.strip().replace("\\", "/") + basename = normalized.rsplit("/", 1)[-1] + if basename in {"", ".", ".."}: + return None + return basename + + @staticmethod + def _replace_bash_fallback(text: str, env_name: str, value: str) -> str: + """Replace ${ENV:-default} fallback values for one shell variable.""" + pattern = re.compile(r"\$\{" + re.escape(env_name) + r":-[^}]*\}") + return pattern.sub(f"${{{env_name}:-{value}}}", text) + + @staticmethod + def _replace_powershell_fallback(text: str, env_name: str, value: str) -> str: + """Replace PowerShell fallback values for one environment variable.""" + escaped_value = value.replace("'", "''") + pattern = re.compile( + rf"(if\s*\(\$env:{re.escape(env_name)}\)\s*\{{\s*\$env:{re.escape(env_name)}\s*\}}\s*else\s*\{{\s*)(['\"]).*?\2(\s*\}})", + ) + return pattern.sub(rf"\1'{escaped_value}'\3", text) + def _apply_runtime_defaults( self, text: str, @@ -118,52 +143,22 @@ def _apply_runtime_defaults( retention_days: int, log_dir: str, log_name: str | None, - default_log_name: str | None, ) -> str: """Inject configured mode and log level fallbacks into shell snippets.""" - rendered = ( - text.replace("${VSTACK_HOOKS_MODE:-audit}", f"${{VSTACK_HOOKS_MODE:-{mode}}}") - .replace( - "${VSTACK_HOOKS_LOG_LEVEL:-minimal}", - f"${{VSTACK_HOOKS_LOG_LEVEL:-{log_level}}}", - ) - .replace( - "${VSTACK_HOOKS_LOG_RETENTION_DAYS:-7}", - f"${{VSTACK_HOOKS_LOG_RETENTION_DAYS:-{retention_days}}}", - ) - .replace( - "${VSTACK_HOOK_LOG_DIR:-.vstack/logs}", - f"${{VSTACK_HOOK_LOG_DIR:-{log_dir}}}", - ) - .replace("else { 'audit' }", f"else {{ '{mode}' }}") - .replace("else { 'minimal' }", f"else {{ '{log_level}' }}") - .replace("else { '7' }", f"else {{ '{retention_days}' }}") - .replace("else { '.vstack/logs' }", f"else {{ '{log_dir}' }}") - .replace('else { "audit" }', f'else {{ "{mode}" }}') - .replace('else { "minimal" }', f'else {{ "{log_level}" }}') - .replace('else { "7" }', f'else {{ "{retention_days}" }}') - .replace('else { ".vstack/logs" }', f'else {{ "{log_dir}" }}') - ) - if ( - isinstance(log_name, str) - and log_name - and isinstance(default_log_name, str) - and default_log_name - ): - rendered = ( - rendered.replace( - f"${{VSTACK_HOOK_LOG_NAME:-{default_log_name}}}", - f"${{VSTACK_HOOK_LOG_NAME:-{log_name}}}", - ) - .replace( - f"else {{ '{default_log_name}' }}", - f"else {{ '{log_name}' }}", - ) - .replace( - f'else {{ "{default_log_name}" }}', - f'else {{ "{log_name}" }}', - ) - ) + rendered = text + runtime_defaults = { + "VSTACK_HOOKS_MODE": mode, + "VSTACK_HOOKS_LOG_LEVEL": log_level, + "VSTACK_HOOKS_LOG_RETENTION_DAYS": str(retention_days), + "VSTACK_HOOK_LOG_DIR": log_dir, + } + if isinstance(log_name, str) and log_name: + runtime_defaults["VSTACK_HOOK_LOG_NAME"] = log_name + + for env_name, value in runtime_defaults.items(): + rendered = self._replace_bash_fallback(rendered, env_name, value) + rendered = self._replace_powershell_fallback(rendered, env_name, value) + return rendered def _generate_json_from_yaml(self, yaml_data: dict) -> str: @@ -173,11 +168,12 @@ def _generate_json_from_yaml(self, yaml_data: dict) -> str: mode = self._mode_for_hook(hook_name) log_level = self._log_level_for_hook(hook_name) retention_days = self._retention_days_for_hook(hook_name) - log_name = self._log_name_for_hook(hook_name) - log_metadata = metadata.get("log", "") - default_log_name = "" - if isinstance(log_metadata, dict): - default_log_name = str(log_metadata.get("name", "")) + configured_log_name = self._log_name_for_hook(hook_name) + log_name = ( + self._sanitize_log_name(configured_log_name) + if isinstance(configured_log_name, str) + else None + ) hooks_dict = yaml_data.get("hooks", {}) rendered_hooks: dict[str, list[dict]] = {} @@ -195,7 +191,6 @@ def _generate_json_from_yaml(self, yaml_data: dict) -> str: retention_days=retention_days, log_dir=self.default_log_dir, log_name=log_name, - default_log_name=str(default_log_name), ) rendered_actions.append(rendered_action) rendered_hooks[event] = rendered_actions diff --git a/tests/vstack/cli/test_constants.py b/tests/vstack/cli/test_constants.py index 7522058..8f38b61 100644 --- a/tests/vstack/cli/test_constants.py +++ b/tests/vstack/cli/test_constants.py @@ -74,3 +74,15 @@ def test_expected_hook_input_names_content(self) -> None: "pre-tool-safety-gate", "session-audit", ] + + def test_expected_agent_input_names_content(self) -> None: + """Verify source checks require all built-in agents including planner.""" + assert EXPECTED_INPUT_NAMES["agent"] == [ + "architect", + "designer", + "engineer", + "planner", + "product", + "release", + "tester", + ] diff --git a/tests/vstack/hooks/test_generator.py b/tests/vstack/hooks/test_generator.py index ad0ac02..809e706 100644 --- a/tests/vstack/hooks/test_generator.py +++ b/tests/vstack/hooks/test_generator.py @@ -227,6 +227,32 @@ def test_render_uses_project_default_mode_for_shell_fallbacks(self, tmp_path: Pa assert "${VSTACK_HOOKS_MODE:-enforce}" in action["bash"] assert "else { 'enforce' }" in action["powershell"] + def test_render_replaces_mode_fallback_even_with_nonstandard_template_default( + self, tmp_path: Path + ) -> None: + """Mode replacement should not depend on one hard-coded source default value.""" + tmpl_dir = tmp_path / "hooks" / "mode-default-variant" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: mode-default-variant\n" + " description: 'test hook'\n purpose: audit\n security_level: low\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n postToolUse:\n - type: command\n" + " bash: 'mode=${VSTACK_HOOKS_MODE:-legacy-default}'\n" + " powershell: '$mode = if ($env:VSTACK_HOOKS_MODE) { $env:VSTACK_HOOKS_MODE } else { ''legacy-default'' }'\n", + encoding="utf-8", + ) + + gen = HookGenerator(default_mode="enforce") + gen.templates_dir = tmp_path / "hooks" + artifact = gen.render_all()[0] + + content = json.loads(artifact.content) + action = content["hooks"]["postToolUse"][0] + assert "${VSTACK_HOOKS_MODE:-enforce}" in action["bash"] + assert "else { 'enforce' }" in action["powershell"] + def test_render_uses_project_default_log_level_for_shell_fallbacks( self, tmp_path: Path ) -> None: @@ -278,6 +304,56 @@ def test_render_uses_project_default_log_name_for_shell_targets(self, tmp_path: assert "${VSTACK_HOOK_LOG_NAME:-custom-quality.log}" in action["bash"] assert "else { 'custom-quality.log' }" in action["powershell"] + def test_render_sanitizes_log_name_override_to_basename(self, tmp_path: Path) -> None: + """Configured log filename overrides should drop path traversal segments.""" + tmpl_dir = tmp_path / "hooks" / "sanitize-log-name" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: sanitize-log-name\n" + " description: 'test hook'\n purpose: quality\n security_level: low\n" + " log:\n name: hook-quality-alerts.log\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n postToolUse:\n - type: command\n" + " bash: 'log_name=${VSTACK_HOOK_LOG_NAME:-hook-quality-alerts.log}'\n" + " powershell: '$name = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { ''hook-quality-alerts.log'' }'\n", + encoding="utf-8", + ) + + gen = HookGenerator(log_name_overrides={"sanitize-log-name": "../unsafe.log"}) + gen.templates_dir = tmp_path / "hooks" + artifact = gen.render_all()[0] + + content = json.loads(artifact.content) + action = content["hooks"]["postToolUse"][0] + assert "${VSTACK_HOOK_LOG_NAME:-unsafe.log}" in action["bash"] + assert "else { 'unsafe.log' }" in action["powershell"] + + def test_render_ignores_invalid_log_name_override(self, tmp_path: Path) -> None: + """Invalid basename results should be ignored and keep template defaults.""" + tmpl_dir = tmp_path / "hooks" / "ignore-invalid-log-name" + tmpl_dir.mkdir(parents=True) + (tmpl_dir / "hook.yaml").write_text( + "version: 20260510003\nmetadata:\n name: ignore-invalid-log-name\n" + " description: 'test hook'\n purpose: quality\n security_level: low\n" + " log:\n name: hook-quality-alerts.log\n" + " mode_default: audit\n execution_context: copilot-hook-runtime\n" + " dependencies:\n required: []\n optional: []\n" + "hooks:\n postToolUse:\n - type: command\n" + " bash: 'log_name=${VSTACK_HOOK_LOG_NAME:-hook-quality-alerts.log}'\n" + " powershell: '$name = if ($env:VSTACK_HOOK_LOG_NAME) { $env:VSTACK_HOOK_LOG_NAME } else { ''hook-quality-alerts.log'' }'\n", + encoding="utf-8", + ) + + gen = HookGenerator(log_name_overrides={"ignore-invalid-log-name": "../"}) + gen.templates_dir = tmp_path / "hooks" + artifact = gen.render_all()[0] + + content = json.loads(artifact.content) + action = content["hooks"]["postToolUse"][0] + assert "${VSTACK_HOOK_LOG_NAME:-hook-quality-alerts.log}" in action["bash"] + assert "else { 'hook-quality-alerts.log' }" in action["powershell"] + def test_render_uses_per_hook_retention_days_override(self, tmp_path: Path) -> None: """Per-hook retention_days overrides are applied to shell fallbacks.""" tmpl_dir = tmp_path / "hooks" / "retention-test" @@ -305,7 +381,7 @@ def test_render_uses_per_hook_retention_days_override(self, tmp_path: Path) -> N # Verify retention_days override was applied to bash fallback assert "${VSTACK_HOOKS_LOG_RETENTION_DAYS:-30}" in action["bash"] # Verify retention_days override was applied to powershell fallback - assert 'else { "30" }' in action["powershell"] + assert "else { '30' }" in action["powershell"] def test_render_uses_project_default_log_dir_for_shell_fallbacks(self, tmp_path: Path) -> None: """Configured project hook log directory is injected into shell fallbacks.""" From b479d68c7f3563797d5b13c252db31fc10dcf065 Mon Sep 17 00:00:00 2001 From: Erik Schaareman <erik@schaareman.org> Date: Tue, 12 May 2026 21:23:36 +0200 Subject: [PATCH 11/11] chore(deps): refresh poetry lockfile --- poetry.lock | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 907f0b0..b81e2b1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -737,14 +737,14 @@ files = [ [[package]] name = "tox" -version = "4.53.1" +version = "4.54.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "tox-4.53.1-py3-none-any.whl", hash = "sha256:4a9948607e976a337c22d64a1b4fafd486125e82f00ab6ce32fa6cacc23f48b1"}, - {file = "tox-4.53.1.tar.gz", hash = "sha256:7be9805ed4a34242510c7acc9a7e3a01a35942e08f31f8bd69067c3a37130afc"}, + {file = "tox-4.54.0-py3-none-any.whl", hash = "sha256:a2d7c1177242ae9c3d9e404039e9f945ce16a3e5dfc66972c643e27d7e764f4b"}, + {file = "tox-4.54.0.tar.gz", hash = "sha256:21e36fd8256590379620848d0b03b52f4d541b65b749de1a17c3e616978dad58"}, ] [package.dependencies] @@ -761,6 +761,7 @@ virtualenv = ">=21.1" [package.extras] completion = ["argcomplete (>=3.6.3)"] +testing = ["devpi-process (>=1.1.1)", "pytest (>=9.0.2)", "pytest-mock (>=3.15.1)"] [[package]] name = "types-pyyaml" @@ -807,4 +808,4 @@ python-discovery = ">=1.2.2" [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.15" -content-hash = "b3b6750041a168225c31625cd64b77b78229506f5b66edc8bebbe468bc7ba586" +content-hash = "08d019a46cc765de96b54d9acd7a699fdd0ce8ad3c137c4f05a5fca9886d1717"