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
84 changes: 84 additions & 0 deletions .agents/checks/ci-gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# The one quality gate. `just ci` and the `quality` job in
# `.github/workflows/ci.yml` both run this file, so a local run and a remote run
# cannot drift; this is the check that remote branch protection requires.
#
# It sequences checks only. The caller supplies the locked toolchain: direnv
# locally, `nix develop` in CI. Every command is read-only over the tracked tree,
# and the last step proves it.
#
# It deliberately never runs `nix fmt`. That formatter applies Rust 2024
# formatting to this Rust 2021 crate and reflows the retained `docs/audits/`
# evidence, so `cargo fmt` is the accepted formatting check here.
set -euo pipefail

cd "$(git rev-parse --show-toplevel)"

step() {
printf '\n== %s ==\n' "$1"
}

# The tracked tree as the gate found it. On a CI checkout this is empty, so the
# final comparison is literally "the tracked tree is clean"; locally it lets the
# gate run over uncommitted work while still proving the gate changed none of it.
tracked_before=$(git status --porcelain --untracked-files=no)

step 'cargo fmt'
cargo fmt --all -- --check

step 'clippy'
cargo clippy --locked --all-targets -- -D warnings

step 'tests'
cargo test --locked

step 'repository checks'
cargo run --locked -- checks

step 'work state'
cargo run --locked -- validate

step 'actionlint'
actionlint

step 'reset tripwires'
# The process families RESET.md deleted. Forbidden whether tracked or not: a
# tracked one is what branch protection has to block, and an untracked one would
# otherwise slip past the tracked-tree check below.
returned=()
for artefact in \
docs/plans \
docs/metrics/workflow.jsonl \
.agents/LEDGER.template.md \
.agents/workflow.toml \
.agents/reviews; do
if [[ -e ${artefact} || -n $(git ls-files -- "${artefact}") ]]; then
returned+=("${artefact}")
fi
done
if ((${#returned[@]} > 0)); then
printf 'error: deleted reset artefact returned: %s\n' "${returned[*]}" >&2
exit 1
fi

step 'workflow pins'
# actionlint has no rule for this, and an unpinned action is a mutable
# dependency of every merge the gate guards.
if unpinned=$(grep -rEn '^[[:space:]]*-?[[:space:]]*uses:' .github/workflows |
grep -Ev 'uses: [A-Za-z0-9._/-]+@[0-9a-f]{40}([[:space:]]|$)'); then
printf 'error: workflow action is not pinned to a commit SHA:\n%s\n' "${unpinned}" >&2
exit 1
fi

step 'tracked tree'
tracked_after=$(git status --porcelain --untracked-files=no)
if [[ ${tracked_after} != "${tracked_before}" ]]; then
printf 'error: the gate changed the tracked tree.\nbefore:\n%s\nafter:\n%s\n' \
"${tracked_before}" "${tracked_after}" >&2
exit 1
fi
if [[ -n ${tracked_after} ]]; then
printf 'note: tracked files were already modified before this run:\n%s\n' "${tracked_after}"
fi

printf '\nci: every check passed\n'
89 changes: 46 additions & 43 deletions .agents/work.toml
Original file line number Diff line number Diff line change
@@ -1,62 +1,65 @@
version = 1
selected_action = "support-safe-work-paragraphs"

[[step]]
id = "make-next-small-and-truthful"
id = "enforce-reset-guardrails"
status = "complete"
blocked_by = []
user_problem = "The next command emits about 764 KB, copies stale ledger prose, hides concurrent work, and selects the wrong loop."
change = "Read this bounded work file, project every active unit once, select one action deterministically, and remove ledger and free-form resume output."
user_problem = "Nothing outside this repository stops a change that breaks the build or revives a deleted reset artefact."
change = "Add one locked-toolchain GitHub CI workflow plus one shared local gate, then have the human protect main with a required pull request and that check."
acceptance = [
"Human output is at most 8192 bytes on this repository.",
"JSON output is at most 8192 bytes on this repository.",
"No output contains verbatim ledger text.",
"Every active unit appears exactly once.",
"Exactly one correct selected action appears.",
"Two runs of each format are byte-identical.",
"One read-only job runs on pushes to main and on pull requests.",
"It runs the accepted cargo formatting check, strict Clippy, locked tests, repository checks, real work validation, reset tripwires and actionlint in the locked Nix environment.",
"It never runs nix fmt.",
"Actions are pinned to immutable commit SHAs.",
"Local and CI runs share one gate source.",
"The tracked tree stays clean after a run.",
"Remote protection requires a pull request and this check, with zero approvals.",
]
why_next = "This removes the largest immediate user cost and gives later reset work a bounded control surface."
why_next = "Every later action is safer once a mechanical gate, not a promise, holds the reset boundary."

[[step]]
id = "make-default-scaffold-minimal"
status = "complete"
blocked_by = ["make-next-small-and-truthful"]
user_problem = "New projects inherit the workflow that produced the deleted planning tree."
change = "Simplify the default pack to one small work file, one implementation branch, compact guidance, and no ledger, JSONL log, plan-review loop, or review directory."
id = "support-safe-work-paragraphs"
status = "active"
blocked_by = ["enforce-reset-guardrails"]
user_problem = "Every work field must fit one line, so a step cannot explain its problem without cramming it."
change = "Let prose fields carry paragraphs while ids and structural fields stay single-line, and keep both projections safe."
acceptance = [
"A fresh scaffold creates no ledger, JSONL log, docs/plans process tree, or review directory.",
"Generated workflow guidance stays within 65536 bytes.",
"No individual prompt exceeds 4096 bytes.",
"A second scaffold is byte-idempotent.",
"The full product test suite passes.",
"IDs, statuses, blockers and the selected action stay single-line.",
"Prose fields accept paragraphs.",
"Unsafe control characters are still rejected.",
"Human output renders paragraphs without heading injection.",
"JSON preserves paragraph structure.",
]
why_next = "Fixing only this repository would leave the product teaching adopters the failed process."
why_next = "Bounded state is only worth trusting if a step can state its problem honestly inside the size limits."

[[step]]
id = "validate-real-work-state"
status = "complete"
blocked_by = ["make-default-scaffold-minimal"]
user_problem = "The current validator can certify self-authored review claims without proving useful work occurred."
change = "Validate the bounded work file and useful state invariants without depending on deleted plans, ledgers, or round logs."
id = "explore-general-review-surface"
status = "pending"
blocked_by = ["support-safe-work-paragraphs"]
user_problem = "The scaffold supports delivery review, but not a standalone review of a current tree or a diff between two states."
change = "Compare a compact prompt, a dedicated CLI command and no product surface, then recommend one."
acceptance = [
"A clean minimal scaffold validates.",
"Validation rejects a sixth step, duplicate selection, unknown status, unresolved blocker, and oversized state.",
"Appending a claimed review record cannot make validation pass.",
"Human and JSON status outputs are deterministic and at most 16384 bytes.",
"Representative current-tree and two-ref review tasks drive the comparison.",
"Every option is judged against the same tasks.",
"The result is a capped scratch decision brief.",
"No product code, pack asset or shipped documentation changes.",
]
why_next = "The minimal workflow needs a mechanical boundary that checks real state without recreating evidence theatre."
why_next = "Deciding the surface first avoids shipping a command the workflow turns out not to need."

[[step]]
id = "establish-release-baseline"
status = "complete"
blocked_by = ["validate-real-work-state"]
user_problem = "Process churn obscures whether the retained product still works end to end."
change = "Add the all-complete terminal work state, align user documentation with minimal behaviour, normalise the Rust formatting baseline, remove only proven unreachable product code, and prepare a local release candidate."
id = "investigate-workflow-failure-causes"
status = "pending"
blocked_by = ["explore-general-review-surface"]
user_problem = "The August audit dated the workflow failure and proved immediate mechanisms, but did not identify what caused them to start or worsen."
change = "Trace initiating causes and test delivery-value signals across code, agent-facing text and generated output."
acceptance = [
"An all-complete work file with no selected action validates, and validate, status, and next project its terminal state.",
"Formatting checks, Clippy with warnings denied, and all tests pass.",
"A local install succeeds.",
"An empty-directory scaffold is byte-idempotent across two runs.",
"Validation and both next formats pass from a clean tree.",
"No command requires a deleted process file.",
"Hypotheses and evidence grades are fixed before measurement.",
"The dated change points are traced through human messages, prompts and Git history.",
"Matched work before and after the transition tests each candidate cause.",
"Results separate demonstrated causes, supported causes and unknowns.",
"Each supported cause maps to a bounded preventive control.",
"Self-certified process metrics are excluded.",
"The result is a scratch decision brief of at most 20000 bytes.",
]
why_next = "This turns the reset into a tested product baseline while keeping push and release authority with the human."
why_next = "Causal evidence can prevent another failure without turning activity counts into another target."
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: ci

on:
pull_request:
push:
branches: [main]

# Read-only: the gate inspects the tree and runs no release, publish, or write step.
permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
# `quality` is the stable check name remote branch protection requires. Renaming
# the job silently disables that protection, so keep the id and the name together.
quality:
name: quality
runs-on: ubuntu-latest
steps:
# Pinned to immutable commit SHAs, not tags: a tag can be moved onto other code.
- name: Check out the commit under test
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
# The committed flake supplies the locked toolchain; `.agents/checks/ci-gate.sh`
# is the same gate `just ci` runs locally. Already inside `nix develop`, so the
# recipe must not wrap the script in direnv a second time.
- name: Run the shared gate
env:
SKIP_DIRENV: "1"
run: nix develop --command just ci
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,13 @@ just build # cargo build
just test # cargo test
just clippy # cargo clippy --all-targets
just fmt # format all files through the Nix formatter
just ci # the full quality gate, exactly what GitHub CI runs
just run -- --help
```

The verification convention before each commit is `cargo clippy --all-targets -- -D warnings`, `nix fmt`, and keeping all text ASCII-clean.
Run `just ci` before each commit, and keep all text ASCII-clean. It runs `.agents/checks/ci-gate.sh`, the one gate the `quality` job in `.github/workflows/ci.yml` also runs through the locked flake: `cargo fmt --all -- --check`, Clippy with warnings denied, the locked tests, `agent-flow checks`, `agent-flow validate`, `actionlint`, tripwires for the process artefacts `RESET.md` deleted, and a check that the run left the tracked tree unchanged.

The gate deliberately does not run `nix fmt`. That formatter applies Rust 2024 formatting to this Rust 2021 crate and reflows the retained `docs/audits/` records, so `cargo fmt` is the accepted formatting check.

## License

Expand Down
3 changes: 3 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@
pkgs.just
pkgs.git
pkgs.gh

# CI gate (`.agents/checks/ci-gate.sh`)
pkgs.actionlint
];

env = {
Expand Down
6 changes: 6 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ clippy:
fmt:
{{ direnv_prefix }} nix fmt

# Run the full quality gate: exactly what the `quality` job runs in GitHub CI.
# Invoked through `bash` rather than the script's shebang so the gate runs under the
# bash the flake pins, on any host, including one with no `/usr/bin/env`.
ci:
{{ direnv_prefix }} bash .agents/checks/ci-gate.sh

# Regenerate the project's own reference guidance and role prompts from the built-in
# pack. The scaffold run leaves working files alone, notably this repository's live
# `.agents/work.toml` and optional `.agents/checks.toml`; copying the refreshed guidance
Expand Down