Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions .github/workflows/sstack-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
permissions:
contents: read

concurrency:
group: sstack-quality-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
quality:
name: Stack lint and tests
Expand All @@ -22,16 +26,16 @@ jobs:
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
cache: pip
cache-dependency-path: tools/sstack/requirements-dev.txt
- run: python -m pip install -r tools/sstack/requirements-dev.txt
- run: python -m ruff check --config tools/sstack/ruff.toml install.py
- run: python -m ruff format --check --config tools/sstack/ruff.toml install.py
- run: python tools/sstack/lint.py
- run: python tools/sstack/control.py verify stack
- name: Require project coverage for changed paths
- name: Verify affected projects
env:
BASE_REVISION: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }}
BASE_REVISION: ${{ github.event.pull_request.base.sha || github.event.before }}
run: |
if [[ "$BASE_REVISION" =~ ^0+$ ]]; then
BASE_REVISION="$GITHUB_SHA"
if [[ -z "$BASE_REVISION" || "$BASE_REVISION" =~ ^0+$ ]]; then
python tools/sstack/control.py verify stack --jobs 2
python tools/sstack/control.py coverage --base "$GITHUB_SHA"
else
python tools/sstack/control.py verify changed --base "$BASE_REVISION" --jobs 2
fi
python tools/sstack/control.py coverage --base "$BASE_REVISION"
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ Answer in chat by default. Create artifacts only when requested or necessary for
implementation, verification or handoff; update existing records instead of
duplicating them. Keep secrets and private customer data out of shared evidence.

Run python tools/sstack/lint.py and python tools/sstack/control.py verify stack
with the pinned development dependencies before delivering tooling changes.
Run python tools/sstack/control.py verify changed --base <actual-base> --jobs 2
with pinned development dependencies. This selects docs checks or shared lint/tests
and affected project suites; do not duplicate the selected commands. Runtime and
skill changes require the full stack gate. Use one independent verifier/reviewer
by default and overlap it with safe checks on a stable commit. See the delivery
contract for risk escalation and exact-input evidence reuse. Use verify stack for
an explicit full regression or when no comparison base exists.
Report actual evidence and operating limits. The local delivery store coordinates
one clone and its worktrees on one host, not independent clones or computers.
66 changes: 63 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,17 +225,77 @@ small evaluation sets only where acceptance needs them, before implementation.
See the [tool and delivery protocol](tools/sstack/README.md) for schemas, evidence,
claims, recovery and external verification.

## Faster delivery without weaker acceptance

The normal gate is one command, run from your feature branch against its actual
base (use the same Python environment throughout):

```sh
python tools/sstack/control.py verify changed --base origin/main --jobs 2
```

It selects affected checks from the project registry, runs checks declared safe
to overlap, and collects coverage, logs and timings in one report. The built-in
stack suite includes lint, so a separate lint invocation is unnecessary. Use
`--jobs 1` when serial execution is needed, or `--plan` to inspect selection.

- **README-only changes:** documentation contracts and links. Run any changed
executable examples separately; Markdown is never blindly executed.
- **Skills, agent instructions, runtime or configuration:** full stack lint/tests.
- **Application work:** registered affected suites and dependencies, plus the
ticket's acceptance criteria. Unknown paths and missing required live proof block.

The shipped lightweight route covers only this repo's README.md and
`tools/sstack/README.md`. Consumer projects must register their own documentation
checks; a generated site or Markdown used as application input still needs its
runtime tests. Optional `exclude_paths` separates documentation ownership from a
broad project prefix without treating unowned paths as covered.

One independent agent normally verifies acceptance **and** reviews the diff. It
can start on a stable head while CI runs, then inspect the completed results before
approval. Fixes return to the same engineer; the reviewer checks the affected
changes and refreshed evidence. PM keeps tickets to one observable outcome and
reuses the existing ticket/report/PR instead of generating extra handoff files.

Custom check definitions may opt into `parallel_safe: true` only when their
fixtures and outputs cannot conflict. They otherwise run serially. The default
worker limit is two; `--jobs` supports one through eight.

Successful offline evidence can be reused explicitly:

```sh
python tools/sstack/control.py verify changed --base origin/main \
--environment-key immutable-test-environment-v1
python tools/sstack/control.py verify changed --base origin/main \
--environment-key immutable-test-environment-v1 \
--reuse "artifacts/sstack/<previous-run>/report.json"
```

Reuse requires the same head/base, source inputs, check commands, runtime identity
and intact logs; otherwise checks rerun. Custom checks also need `reuse_safe: true`.
The environment key is your attestation of external dependencies and fixtures,
not a label to reuse indefinitely: change it whenever those inputs change. If
those inputs cannot be pinned, run fresh. Live/UI proof, independent review and
merge readiness are never satisfied by this cache. See the
[delivery rules](tools/sstack/skills/sstack/references/delivery.md#choose-the-shortest-sufficient-path)
for risk-based verification and exact reuse limits.

CI uses the same selection, caches Python dependencies and cancels superseded
runs for the same PR. A stable check name remains available for branch protection.
Reports expose check and elapsed durations so actual savings can be measured;
parallel checks do not guarantee a proportional improvement in total feature time.

## Check the stack

```sh
python3 -m venv .venv
.venv/bin/python -m pip install -r tools/sstack/requirements-dev.txt
.venv/bin/python tools/sstack/lint.py
.venv/bin/python tools/sstack/control.py verify stack
```

The included GitHub workflow checks this repository's lint, contracts, tests and
changed-path coverage. Product acceptance still requires the affected project's
The included GitHub workflow selects documentation checks or stack lint/tests
from changed-path coverage, retaining the full gate for skills and runtime edits.
Product acceptance still requires the affected project's
checks and any declared live/UI proof. Repository protection and required checks
must be configured separately.

Expand Down
27 changes: 23 additions & 4 deletions tools/sstack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ without changing saved configuration. No board is supplied with this package.
```sh
python3 -m venv /tmp/sstack-dev
/tmp/sstack-dev/bin/python -m pip install -r tools/sstack/requirements-dev.txt
/tmp/sstack-dev/bin/python tools/sstack/lint.py
/tmp/sstack-dev/bin/python tools/sstack/control.py verify stack
```

Expand All @@ -47,6 +46,24 @@ and `python -m ruff format tools/sstack`, then inspect and recheck. The shared
[engineering principles](skills/sstack-mode/references/engineering.md) guide
implementation; lint alone cannot establish good design or business correctness.

For routine delivery, use `control.py verify changed --base <actual-base> --jobs 2`
instead of a full regression. `verify stack` includes lint and tests. Exact README
paths use the built-in `docs` suite; skills/runtime changes remain full stack.
Custom suites accept optional `parallel_safe` and `reuse_safe` booleans, both false
by default. Parallel execution is bounded (1–8 workers, default 2); serial checks
act as barriers and never overlap other checks. Per-check `duration_seconds` and
report `elapsed_seconds` expose cost without another reporting artifact.

Explicit reuse uses `--reuse <report.json> --environment-key <immutable-runtime-id>`;
the original run must supply the same environment key. Reuse verifies the full
head/base/input/command/runtime binding and successful check log digests, and
returns the original report reference. Mismatches rerun. Caller attestation of
external environment state is still required; the runtime fingerprint is not a
content hash of every excluded dependency file. Never enable reuse for live,
time-dependent or shared-state checks. See the
[delivery rules](skills/sstack/references/delivery.md#choose-the-shortest-sufficient-path)
for required risk escalation and independent review.

## Project coverage and evidence

`projects.json` registers owned paths, offline suites, external acceptance recipes
Expand All @@ -55,11 +72,11 @@ a project. Unknown changed paths block coverage. Shared skill edits run stack
checks; shared runtime files can also map to their actual product consumers.

```sh
python tools/sstack/control.py coverage --base origin/main
python tools/sstack/control.py verify changed --base origin/main --plan
python tools/sstack/control.py verify changed --base origin/main
python tools/sstack/control.py verify changed --base origin/main --jobs 2
```

The report already embeds coverage. Use standalone `coverage --base <actual-base>`
or add `--plan` only when an inspection is useful; they are not additional gates.
Coverage success means the changed paths were mapped, not that acceptance passed.
`verify changed` executes registered offline commands and keeps outstanding external
proof blocked. The controller can satisfy those obligations with the declared
Expand Down Expand Up @@ -128,6 +145,8 @@ second report tree. Packets by current state:
to helper reports, and `evaluation` reference to independent premerge results.
Coverage is recomputed; required commands, base/head, input and log digests must
match. Required failed/duplicate checks cannot hide behind passing checks.
`coverage` can reference the same verification report containing embedded
coverage; no second coverage file is needed. The gate recomputes and compares it.
- `reviewing`: `review` reference with `artifact`, current `plan_digest`, independent
`reviewer`, `decision: "approved"`, and `blocking_findings: []`. Live GitHub state
must show the expected head/base, clean merge state and successful named checks.
Expand Down
14 changes: 11 additions & 3 deletions tools/sstack/check_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from __future__ import annotations

import argparse
import re
from dataclasses import dataclass
from pathlib import Path
Expand Down Expand Up @@ -137,11 +138,13 @@ def check_markdown(root: Path, relative: Path, content: str) -> list[Finding]:
return findings


def check(root: Path = ROOT) -> list[Finding]:
def check(root: Path = ROOT, *, include_readme: bool = False) -> list[Finding]:
"""Check only stack docs, entry instructions, and skill registrations."""
root = Path(root)
canonical = root / STACK / "skills"
documents = {Path("AGENTS.md"), Path("CLAUDE.md"), STACK / "README.md"}
if include_readme and (root / "README.md").exists():
documents.add(Path("README.md"))
documents.update(STACK / "skills" / name / "SKILL.md" for name in SKILLS)
documents.update(
path.relative_to(root)
Expand Down Expand Up @@ -181,8 +184,13 @@ def check(root: Path = ROOT) -> list[Finding]:
return findings


def main() -> int:
findings = check()
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--include-readme", action="store_true", help="Check root README if present."
)
args = parser.parse_args(argv)
findings = check(include_readme=args.include_readme)
for finding in findings:
print(finding)
if not findings:
Expand Down
Loading
Loading