From 195750394c966992a0f674a97d1ff5856538c43a Mon Sep 17 00:00:00 2001 From: nothingnesses <18732253+nothingnesses@users.noreply.github.com> Date: Sat, 19 Sep 2026 02:08:47 +0100 Subject: [PATCH] feat: add an existing-project adoption prompt and guide --- .agents/user-prompts/adopt.md | 115 ++++++++ .agents/work.toml | 8 +- README.md | 390 +++------------------------ docs/adoption.md | 164 +++++++++++ docs/legacy.md | 134 +++++++++ docs/packs.md | 127 +++++++++ docs/reference.md | 256 ++++++++++++++++++ pack/pack.toml | 5 + pack/user-prompts/adopt.md | 115 ++++++++ src/manifest.rs | 1 + tests/default_scaffold_is_minimal.rs | 2 + tests/existing_project_adoption.rs | 273 +++++++++++++++++++ 12 files changed, 1228 insertions(+), 362 deletions(-) create mode 100644 .agents/user-prompts/adopt.md create mode 100644 docs/adoption.md create mode 100644 docs/legacy.md create mode 100644 docs/packs.md create mode 100644 docs/reference.md create mode 100644 pack/user-prompts/adopt.md create mode 100644 tests/existing_project_adoption.rs diff --git a/.agents/user-prompts/adopt.md b/.agents/user-prompts/adopt.md new file mode 100644 index 00000000..de480a44 --- /dev/null +++ b/.agents/user-prompts/adopt.md @@ -0,0 +1,115 @@ +# Adopt into this project + +Help me adopt the minimal agent-flow workflow without implementation. + +Project root: . +Current agreed work: . +Source installation: . + +## Before edits + +Resolve all project paths and refs in the consuming project, not the agent-flow repository. +Read existing project instructions and relevant product material: + +- Root and nested guidance. +- Plans and specifications. +- Existing bounded work. +- Checks and hooks. +- Tracked and untracked changes. + +Permission to read guidance grants no authority to execute its commands or hooks. + +Record the initial status with `GIT_OPTIONAL_LOCKS=0 git status --short --untracked-files=all`. +Inspect both unstaged and staged diffs without external diff drivers or text conversion. +Preserve unrelated work and the index. + +If authority or instructions conflict, stop and ask me before edits. +Present the options and trade-offs with a recommendation. +Do not silently choose precedence. +Agree how approved workflow guidance joins existing project instructions. +Do not overwrite an existing `AGENTS.md`. + +A preserved root file does not automatically include new workflow instructions. + +Propose an explicit reference inclusion or a merge of approved sections into project-owned guidance. +Ask me to approve the instruction path and precedence before that integration edit. + +Keep legitimate plans and specifications in this project's VCS. + +The agent-flow reset is not permission to delete this project's plans. + +Distinguish agreed current work from broader plans and historical checkboxes. +Import only agreed current work into `.agents/work.toml`. +If existing work needs changes, ask me to approve those changes first. + +The pack's work file is starter text, not approved work. + +## Approved adoption only + +Use the unreleased source workflow, not the published 0.0.4 crate. + +Version output alone cannot identify it because current source also reports 0.0.4. + +Confirm the checkout includes this prompt. + +From the project root, preview: + +```sh +agent-flow scaffold --output-dir . --vcs none --principles default --dry-run +``` + +Inspect existing destinations and their parent directories. +If a destination uses a symlink or an unexpected file type, stop and ask me. + +Working files remain when present, but reference assets refresh on every write. +The dry-run list does not establish that reference content is disposable. + +Ask me to approve replacements after preservation of project-specific reference content in project-owned files. +After approval, apply: + +```sh +agent-flow scaffold --output-dir . --vcs none --principles default --write +``` + +Apply only the agreed instruction integration and work-file edits. +Keep the work file within 4,096 bytes and five total steps. +Use only these statuses: + +- `active`. +- `pending`. +- `complete`. + +Do not use `--force`. +Do not install hooks or run project checks. +Do not automatically: + +- Start implementation. +- Stage files. +- Commit. +- Publish. + +Do not convert legacy plans or create workflow records. + +## Validate and return + +Run these read-only commands: + +```sh +agent-flow validate --source .agents/work.toml +agent-flow status --source .agents/work.toml +agent-flow status --source .agents/work.toml --json +agent-flow next --source .agents/work.toml +agent-flow next --source .agents/work.toml --json +``` + +Compare the projected action with my agreement. + +Structural validation does not prove correct interpretation of intent or independent review. + +If validation fails or intent differs, stop and report it. +Inspect the final status and both diffs. +Open all new files, including untracked or ignored scaffold output absent from ordinary diffs. +Confirm unrelated work and the index remain unchanged. + +Return changed paths with exact commands and results. +Request my review of the complete diff and new files, then stop. diff --git a/.agents/work.toml b/.agents/work.toml index 0d4bd88b..deb93d93 100644 --- a/.agents/work.toml +++ b/.agents/work.toml @@ -1,5 +1,5 @@ version = 1 -selected_action = "accept-github-owner-name" +selected_action = "document-existing-project-adoption" [[step]] id = "preserve-post-reset-context" @@ -14,7 +14,7 @@ why_next = "The human marked this delivery complete after the merge. This compac [[step]] id = "accept-github-owner-name" -status = "active" +status = "complete" blocked_by = ["preserve-post-reset-context"] user_problem = "GitHub used the owner's public display name for a squash commit. The exact-name check rejects that identity and blocks main CI." change = "Accept the two approved owner names with the existing exact email. Add regression tests without other attribution policy changes." @@ -30,7 +30,7 @@ why_next = "The human selected this CI repair before adoption and approved at mo [[step]] id = "document-existing-project-adoption" -status = "pending" +status = "active" blocked_by = ["accept-github-owner-name"] user_problem = "Existing projects lack a tested adoption walkthrough. The README mixes onboarding with lengthy reference material. Agents lack a bounded adoption prompt." change = "Add an adoption guide and shipped user prompt. Shorten the README and relocate useful reference material. Keep runtime behaviour unchanged." @@ -43,4 +43,4 @@ acceptance = [ "Focused tests and a disposable-repository rehearsal exercise adoption commands and preservation of existing project files.", "Runtime commands, existing delivery-role contracts, audit reports and released history remain unchanged. Host just ci and independent review cover the candidate.", ] -why_next = "The human approved this onboarding change with at most five worker dispatches, including failures. Use one author, independent review and conditional triage, one fix and focused verification. Stop before publication or completion." +why_next = "The human approved this onboarding change with at most six worker dispatches, including failures. Use one author, independent review and conditional triage, one fix and focused verification. Stop before publication or completion." diff --git a/README.md b/README.md index 510f7834..a7518ae6 100644 --- a/README.md +++ b/README.md @@ -2,386 +2,60 @@ [![crates.io](https://img.shields.io/crates/v/agent-flow.svg)](https://crates.io/crates/agent-flow) [![GitHub License](https://img.shields.io/github/license/nothingnesses/agent-flow?color=blue)](https://github.com/nothingnesses/agent-flow/blob/main/LICENSE) -A small command-line tool that scaffolds a bounded agent delivery workflow into a project. The built-in pack creates one compact work file, one implementation-branch workflow, and role prompts for implementation, independent product review, conditional triage, one scoped fix, and one focused verification. +agent-flow scaffolds a bounded agent delivery workflow into a project. It provides one compact work file and role prompts for a single implementation branch. -Those files are a written contract and the state that goes with it. The tool writes them and projects what they say. It starts no agent and runs no delivery pass for you. See [Roles are contracts, not isolation](#roles-are-contracts-not-isolation) for where execution, and the isolation around it, come from. +The delivery sequence contains at most five passes: -## Decision and research context - -- [Historical decision revalidation](docs/audits/2026-09-09-historical-decision-revalidation.md) preserves the retained direction without approving implementation. -- [Tooling research and Rust library trial](docs/audits/2026-09-16-tooling-research-and-trial.md) records later evidence and its limits. - -## Motivations - -- Starting a task should require one bounded source of state, not a generated process tree. -- Scaffolding into an existing project is safe: tool-owned references refresh, while working files are create-if-absent unless `--force` is passed. -- The default is minimal. Product-development checks and hooks are opt-in. -- Guidance is harness-agnostic. `AGENTS.md` is canonical; harness-specific files should point to it rather than duplicate it. +1. Implementation. +2. Independent product review. +3. Separate triage, only for review findings. +4. One scoped fix, only for valid findings within scope. +5. Focused verification, only after a fix. -## What it scaffolds - -The built-in pack writes this default layout: - -``` -AGENTS.md compact canonical guidance (working file) -.agents/ - work.toml bounded delivery state (working file) - AGENTS.reference.md pristine guidance copy - principles.toml compact selectable principles - prompts/ - implementer.md make the bounded product change - reviewer.md independently review the product diff - triager.md adjudicate findings when any exist - fixer.md make the one allowed scoped fix - verifier.md verify that fix once - user-prompts/ - kickoff.md start the selected action - review.md ask for one standalone read-only review -``` - -The two user prompts answer different questions. Copy `kickoff.md` to start the selected action and run the bounded delivery around it. Copy `review.md` when you only want code that already exists reviewed: a whole tree at one ref, or one diff between two refs, judged against criteria you supply. It is a human-invoked reference asset, not workflow state, so it starts no delivery, changes no file in the reviewed repository and persists no review state anywhere, confines any reproduction to a scratch directory you authorise outside that repository, and returns its review as the agent's direct response. - -The default creates no ledger, JSON Lines round log, `docs/plans/` process tree, review directory, plan-review loop, or convergence-round state. `.agents/work.toml` contains at most five ordered delivery steps. While work remains, `selected_action` names one active step and several steps may be active at once; after every step is complete, the field is omitted. `agent-flow validate`, `status`, and `next` use that state by default. - -`AGENTS.md` is rendered from the selected principles. The root guidance and `.agents/work.toml` are working files, created only when absent unless `--force` is used. Tool-owned references under `.agents/` refresh on each run. - -Select `--module checks` to add `.agents/checks.toml`, seeded ast-grep assets, a checks-reviewer prompt, and an inert pre-commit hook. These are optional product-development tools, not task state. Pair it with `--with-precommit-hook` to install a create-if-absent delegate without overwriting an existing hook. - -## Bounded workflow - -The human selects one action in `.agents/work.toml` and starts it with `.agents/user-prompts/kickoff.md`. Delivery stays on one `impl/` branch and uses no parallel implementation worktrees. - -```mermaid -flowchart LR - start["Selected action"] --> implement["Implementation pass"] - implement --> review["Independent product review"] - review -->|clean| human["Return result to human"] - review -->|findings| triage["Separate triage"] - triage -->|none valid| human - triage -->|valid, in scope| fix["One scoped fix"] - fix --> verify["One focused verification"] - verify -->|pass| human - verify -->|fail| unresolved["Return unresolved work to human"] -``` +The prompts are contracts, not agent launchers. agent-flow does not enforce role isolation or independent review. The harness or external runner supplies those properties. -There is no plan review or convergence loop. Review findings cannot broaden the selected action's acceptance criteria. A missing independent reviewer, an out-of-scope finding, an unsafe fix, or a failed focused verification stops the workflow and returns the decision to the human. +The default creates no process plan tree or review records. Checks and hooks stay opt-in. -### Roles are contracts, not isolation +## Install the current workflow -The five passes above are logical roles: prose contracts in `.agents/prompts/`, read against the state in `.agents/work.toml`. agent-flow scaffolds those contracts and that state, and its read-only commands project them. It does that and nothing more. +The minimal workflow is **unreleased**. The published `agent-flow` 0.0.4 crate predates it, although current source still reports version 0.0.4. Version output alone cannot identify this workflow. -It does not launch an agent, spawn a process, or create a git worktree to run a role in, and it enforces no separation of processes, filesystems, networks, credentials, or tool access between roles. Two roles the diagram draws apart may well execute in one process, over one working tree, with one set of credentials. Nothing in the tool prevents that, and nothing in the tool detects it. - -The agent harness or external runner you drive the roles with is what supplies that isolation, and it is also what makes an independent product review independent: the tool cannot tell an independent reviewer from the implementer wearing a second hat. Choose a harness whose separation you trust, and treat the role prompts as the contract it executes against. - -One command does use a worktree, and it is unrelated to roles: `agent-flow checks` runs the configured lint and format commands inside a throwaway git worktree, so an in-place formatter cannot mutate the live tree. - -## Installation - -agent-flow is a standalone Rust binary that runs without Nix. Install the latest release from crates.io: - -```sh -cargo install agent-flow -``` - -Up to 0.0.2 the crate and the binary were called `agent-scaffold`. The 0.0.3 entry in [CHANGELOG.md](CHANGELOG.md) is the durable record of that rename, including what to run to upgrade from 0.0.2. Every published `agent-scaffold` version stays installable and un-yanked, and the `agent-scaffold` crate name is free for whoever wants to reclaim it. To ask for it, open an issue at , this project's issue tracker. - -Or build from source with a recent Rust toolchain (Rust 1.88 or newer): +Use Rust 1.88 or newer to install from source: ```sh git clone https://github.com/nothingnesses/agent-flow cd agent-flow - -# Install the `agent-flow` binary into ~/.cargo/bin: -cargo install --path . - -# ...or just build it and use the produced binary: -cargo build --release -# ./target/release/agent-flow -``` - -If you use Nix, a development shell with the pinned toolchain and helpers is provided by the flake: - -```sh -nix develop # or: direnv allow, if you use direnv -``` - -## Usage - -Every action is a subcommand. Bare `agent-flow` (with no subcommand) prints the list of subcommands and exits; scaffolding runs under the `scaffold` verb. - -Writes are off unless confirmed, and a scaffold run always prints a plan of what it would do (one line per asset: `create`, `refresh`, `skip (exists)`, or `overwrite`). - -On an interactive terminal, running `agent-flow scaffold` with no flags opens the two-pane selector; choosing Save in its confirmation modal writes the scaffold (Cancel or quit writes nothing). For non-interactive use: - -- `--write` applies the changes directly (using `--principles`), skipping the selector. Off a terminal, this is the only way writes happen. -- `--dry-run` prints the plan and exits without writing and without opening the selector. -- With no flag and no terminal (a pipe or CI), it prints the plan and writes nothing. - -Open the selector for the current directory: - -```sh -agent-flow scaffold -``` - -Apply directly, without the selector (into a specific directory): - -```sh -agent-flow scaffold --output-dir path/to/project --write -``` - -Re-running is safe and idempotent: reference assets are refreshed and existing working files are left untouched. Pass `--force` to overwrite working files too (`--force` decides overwrite-versus-skip; `--write` decides whether to write at all, so the two combine). - -By default it also initialises an empty git repository in the output directory (like `cargo new`); pass `--vcs none` to skip that. It shows up in the plan and runs only on write; if the directory is already inside a git repository it is skipped (so scaffolding into a subdirectory of an existing repo does not nest a new one), and the repository is left empty (committing the scaffolded files is up to you). - -### Choosing principles - -`--principles` takes a comma-separated list of tokens: - -- `default`: the sensible default subset. -- `all`: every principle in the pack. -- `none`: no principles. -- `tag:`: every principle carrying that tag (for example `tag:fp`). -- a bare id: that one principle. - -Tokens combine and are de-duplicated by first occurrence, so a bare id list keeps its order. - -```sh -# List the default principles and exit, without scaffolding: -agent-flow scaffold --list-principles - -# List every principle: -agent-flow scaffold --principles all --list-principles - -# Scaffold a specific, ordered selection: -agent-flow scaffold --principles kiss,verify-dont-trust,tag:fp -``` - -`--principle-detail` controls how much of each principle is rendered: `name`, `summary` (the default), or `full` (name, rationale, and references). - -### Interactive selection - -On a terminal, `agent-flow scaffold` opens the two-pane selector by default (seeded from `--principles`); pass `--write` or `--dry-run` to skip it: - -- Left pane lists available principles; right pane lists the included ones in order. -- `i` / `a` move the highlighted principle to the other pane, inserting it before (`i`) or after (`a`) the cursor. -- `Tab` / `h` / `l` / arrow keys switch focus; `j` / `k` or the arrows move the cursor; `K` / `J` reorder within the included pane. -- `u` / `U` undo and redo; `/` filters the available pane by name, id, or tag. -- `Enter` opens a save-confirmation modal (defaulting to Cancel so nothing is written by accident); `q` aborts. - -On save it prints a ready-to-paste `--principles ` line so the exact selection and order can be replayed non-interactively. - -### Legacy plan rendering - -For an existing project that uses the legacy plan flow, a plan is a structured `.plan.toml` skeleton (its Roadmap `[[step]]` entries, `[[question]]` queue, and `[[principle]]` list) plus opaque Markdown prose sidecars (the step and question bodies and the front/tail matter). `render` generates the committed `.md` view from them, splicing each sidecar verbatim: the TOML and the sidecars are the source, and the generated `.md` is a projection that is never hand-edited. - -```sh -# Generate .md from the skeleton and its sidecars: -agent-flow render docs/plans/my-task.plan.toml - -# Re-render in memory and compare against the committed .md (warn on drift): -agent-flow render --check docs/plans/my-task.plan.toml - -# Fail (exit non-zero) on drift, for CI or a pre-commit hook: -agent-flow render --check --strict docs/plans/my-task.plan.toml +git rev-parse HEAD +cargo install --locked --path . --root "$HOME/.local/agent-flow-source" +export PATH="$HOME/.local/agent-flow-source/bin:$PATH" ``` -`render` is strict: a schema violation, an unresolved cross-reference, or a missing sidecar exits non-zero and writes nothing, so a broken source never yields a partial plan. `render --check` catches both a hand-edit of the generated file and a stale render after a source edit; it warns locally (so a forgotten re-render never blocks an in-flight step) and, with `--strict`, fails hard. The built-in minimal pack does not emit a plan template; this command remains for existing projects and custom packs. - -### Validating and projecting workflow state - -`validate`, `status`, and `next` are read-only: they inspect workflow state and never write anything. A fourth command, `audit` (below), is advisory and read-mostly: it writes only its own report. +Record the source commit from `git rev-parse HEAD`. Check that the checkout contains [`pack/user-prompts/adopt.md`](pack/user-prompts/adopt.md) before adoption. -When `.agents/work.toml` exists, bounded work mode is the default. The same source can be selected explicitly with `--source .agents/work.toml`. The version-1 file is at most 4,096 bytes, contains at most five total ordered `[[step]]` entries, and uses only `active`, `pending`, and `complete`. While any step remains active or pending, `selected_action` is required and names an active step; it may be absent only when every step is complete. Several steps may be active, but every blocker id must exist and an active step's blockers must already be complete; pending steps may depend on active or pending predecessors. +The separate installation root leaves other installed binaries unchanged. The binary runs without Nix. -Fields split into prose and structure, and only prose may span lines. The four prose fields (`user_problem`, `change`, each `acceptance` item, and `why_next`) accept TOML multi-line strings, so a step can state its problem in paragraphs instead of cramming it onto one line. The structural values stay one line each: `selected_action`, every step `id`, every `blocked_by` id, and every status. Everything else stays unsafe in both kinds of field, prose included: tabs, carriage returns, any other control character, and the Unicode line (U+2028) and paragraph (U+2029) separators are rejected with the offending field named. Only the line-feed paragraph break is prose-only. +For published versions and the former `agent-scaffold` name, see [release history](docs/reference.md#releases-and-the-rename). -`validate` checks those invariants and exits nonzero with source-prefixed diagnostics on a violation. For an all-complete file it reports valid completion. It does not read plans, ledgers, workflow specs, review directories, or metrics logs in work mode, so a self-authored review record cannot change the result. +## Adopt into an existing project -`status` projects every ordered step, its status, and dependency ids with their current statuses, plus the selected action or no action after completion. Human and `--json` forms are deterministic and fail rather than truncate above 16,384 bytes. `next` lists every active unit in file order and one selected action with its user problem, change, acceptance criteria, and why-next rationale; after completion it lists no active units, no selected action, and an explicit completed result (`selected_action` is `null` in JSON). Pending-step prose and all legacy process files are not read by this path. Both `next` formats retain their 8,192-byte fail-rather-than-truncate limit. +Follow the [adoption guide](docs/adoption.md) before any scaffold write. It covers project authority and preservation of existing material. -Multiline prose keeps its paragraphs in both formats. The human brief prints the first line beside its label and indents every continuation line behind a ` |` gutter, blank paragraph lines included; since no top-level line of that output begins with a space, prose reading `SELECTED ACTION`, `acceptance:`, `- forged` or `why next:` arrives as the indented continuation it is and cannot forge a heading, an active-unit row, or an acceptance item. `--json` needs no gutter and preserves each accepted string exactly, line feeds and blank lines included. +For agent-assisted adoption, copy the [canonical adoption prompt](pack/user-prompts/adopt.md) into your harness. The default pack installs the same prompt at `.agents/user-prompts/adopt.md`. -```sh -# Bounded human brief from .agents/work.toml: -agent-flow next - -# The same projection as deterministic JSON: -agent-flow next --source .agents/work.toml --json - -# Validate and project the bounded work state: -agent-flow validate -agent-flow status -agent-flow status --json -``` +Scaffold preserves existing working files unless forced, but always refreshes reference assets. A preserved `AGENTS.md` does not automatically include the new workflow instructions. -For `validate` and `status`, explicitly naming a legacy plan, metrics log, workflow check, or resume input selects compatibility mode. All three commands also keep their legacy fallback when no `.agents/work.toml` exists; `next` selects it when an old plan is named explicitly. Legacy mode retains the plan and metrics projections, but `next` no longer emits free-form ledger or resume text. - -The default scaffold does not create or require a metrics log. For existing instrumented or legacy-plan projects, an explicit legacy invocation of `validate` checks the workflow's metrics log against its record schema. With `--plan` it also checks a Markdown plan's structured regions (the Roadmap table and the Open Questions queue) against the plan schema, and with `--source` it checks a `.plan.toml` structured source (its schema and internal cross-references). With `--workflow` it cross-references the plan status against the round log: every Roadmap step marked `complete` must have converging round records in the log (or a recorded waiver), so a step marked done without its review loop (or that never reached the clean-round streak its risk class requires) is caught. The workflow check reads the plan from a TOML `--source` when that source declares `[meta].primary = "toml"` (a TOML-only project needs no `--plan`), else from the Markdown `--plan`. It reports every malformed record, region, or cross-reference and every workflow disagreement, and exits non-zero if any exist, so it can gate a commit or run in CI. A `--workflow` run that cannot see a round log is itself one of those failures rather than a skip: with no log at the resolved path the check cannot run, so it reports that and exits non-zero instead of reporting success for a project it never checked. That is the boundary between the two legacy enforcement tiers, and `--workflow` fails on a project with no round log; plain legacy validation without `--workflow` still notes an absent log on stderr at exit 0. It also REFUSES a pairing it cannot vouch for: when the round log it is about to read does not live under the project root of the plan it is about to check, `--workflow` reports that and exits non-zero rather than joining the two, since a green over one project's plan and another project's evidence is worse than no answer at all: - -```sh -# Explicitly validate the legacy default metrics path (docs/metrics/workflow.jsonl): -agent-flow validate --metrics docs/metrics/workflow.jsonl - -# Validate a TOML plan skeleton (its schema and internal cross-references): -agent-flow validate --source docs/plans/my-task.plan.toml - -# Cross-reference a TOML-primary plan's status against the round log (no --plan needed): -agent-flow validate --source docs/plans/my-task.plan.toml --workflow - -# The Markdown path still works when a project keeps a Markdown plan: -agent-flow validate --plan docs/plans/my-task.md --workflow - -# Pointing --workflow at a log outside the plan's own project is refused (exit 1): -agent-flow validate --source /elsewhere/docs/plans/their-task.plan.toml \ - --metrics docs/metrics/workflow.jsonl --workflow -# --workflow would join /elsewhere/docs/plans/their-task.plan.toml against -# docs/metrics/workflow.jsonl, which is not under the plan's project root /elsewhere; -# pass a `--metrics` under that root, run against the plan's own log, or correct the -# `--source` and `--plan` pair -``` +## Reference -The round log is resolved FROM THE PLAN, not from the directory you happen to be standing in. With no `--metrics`, the log is `docs/metrics/workflow.jsonl` under the project root derived from the plan source: the nearest `/docs/plans/` ancestor of `--source` (else of `--plan`), or the source's own directory when it has no such ancestor, so a plan at a project root with no `docs/plans` still reads that root's log. So `agent-flow validate --source /elsewhere/docs/plans/their-task.plan.toml --workflow` checks THEIR plan against THEIR log, rather than joining their plan to yours. `status`, `status --resume`, and legacy plan-mode `next` resolve the same way, and the ledger those legacy readers use is `.ledger.md` beside the plan source. An explicit `--metrics` (or `--ledger-fragment`) is used verbatim, and a run with neither `--source` nor `--plan` has nothing to anchor to, so it keeps the current-directory-relative `docs/metrics/workflow.jsonl`. The rule is textual: it never consults `.git`, so it works the same in a nested repository, outside a repository, and in an unpacked tarball. One consequence to know about: a bare filename run from inside `docs/plans` (`cd docs/plans && agent-flow validate --source my-task.plan.toml --workflow`) has no parent directories to derive a root from, so it looks for `docs/metrics/workflow.jsonl` beneath `docs/plans` and fails, naming the log it looked for; run it from the project root instead. +- [Workflow and command reference](docs/reference.md). +- [Custom packs and optional modules](docs/packs.md). +- [Legacy compatibility and code-value audit](docs/legacy.md). +- [Changelog](CHANGELOG.md). -Anchoring changes where the DEFAULT log resolves; it does nothing about a log you name explicitly, so a second rule sits on top of it. Every one of these commands that reads a legacy plan checks that the log (and, for the ledger readers, the ledger) it is about to read lives under the project root of THAT plan, resolving both through their real on-disk locations so a symlink cannot disguise one as the other. Where no plan is read, which is always so for `status --resume` and is so for `status` and `next` whenever neither a TOML-primary `--source` nor a readable `--plan` resolves, those three take their roots from the anchors instead: every `--source` or `--plan` you gave THAT IS ON DISK yields one and the artifact must be under all of them, so a `--source` and a `--plan` naming two different projects reject each other's artifacts. An anchor that is not on disk yields a root only when NO anchor you gave is on disk, derived from the path itself resolved as far as the filesystem allows, and a `note:` on stderr tells you the anchor is not there; what the anchor's own directory owns is still read, so a plan file you have not written yet still reads its own project's log. Beside an anchor that IS on disk it yields nothing and the one on disk decides, so naming a plan file you have not written does not withhold the other anchor's own log and ledger. ON DISK means the existence check answered yes: an anchor the check cannot answer for at all (a directory above it this process cannot traverse, a symlink loop, a name the kernel rejects) is grouped with the anchors that are not on disk rather than with the ones that are, so a path the tool could not check never becomes the one that decides, and its `note:` says the check failed rather than that the path is missing. With NEITHER anchor there is nothing to pair against, so no root is derived, no containment check fires, and the current-directory-relative defaults described above stand. `validate --workflow` has no such fallback and needs none: with no plan resolved there is nothing for it to check, so it refuses on that ground (`--workflow requested but no plan source resolved`) without ever reaching containment. Where a checked artifact IS outside the root, `validate --workflow` refuses as above, while `status` and `next` LEAVE THAT PART OUT with a reason in its place and still exit 0 (see the `status` paragraph below). Two consequences are worth knowing. A layout where `docs/plans` or `docs/metrics` is a symlink pointing somewhere the other one is not under will now be refused by `validate --workflow` and left out by the projections, even though it worked before; the trade taken is that a loud refusal beats silently reading the wrong file. And a setup that deliberately points one project's `--metrics` at a log outside its own root now exits non-zero under `--workflow`. The rule is CONTAINMENT, not identity: it can tell that a log outside the plan's tree is not the plan's, but a foreign log copied INSIDE that tree still looks like the plan's own, because the round records carry no project of their own to check. - -In explicit legacy mode, `status` prints a best-effort projection of the plan's Roadmap steps grouped by status and its Open Questions count, plus a metrics-record count. It reads the plan from a `.plan.toml` `--source` when that source is TOML-primary, else from the Markdown `--plan`. Unlike work mode, this compatibility projection does not fail on a missing or malformed file (a missing part is simply left out), and `--json` emits the projection as JSON for another tool to consume. A round log that cannot be paired with the plan is one of the parts that gets left out: `status` prints `metrics: unavailable, ` in place of the record count, legacy plan-mode `next` leaves out the count AND the whole `ACTIVE LOOP` block (an instruction derived from evidence the tool cannot vouch for is exactly what must not be emitted), and `status --resume` prints a note naming the rejected ledger instead of the `## RESUME STATE` block. All three still EXIT 0. The same release adds a refusal to `validate --workflow` for the same condition; the projections deliberately do not refuse, because leaving out what they do not have is their documented contract. - -In legacy plan mode, `--json` says which part is missing and why, so a machine consumer can tell the causes apart rather than reading one bare `null` for all of them. `status`'s projection carries `metrics_absent_reason` (`log-absent`, or `log-not-this-project`) beside `metrics`, and legacy `next` carries the same field plus `resume_state_absent_reason` (`ledger-absent`, `no-resume-section`, or `ledger-not-this-project`) and `no_active_loop_reason` (`no-plan-steps`, `all-steps-terminal`, or `metrics-not-this-project`) beside `active_loop`. It has no free-form `resume_state` field. Each is `null` when its part is present, and the shared `not-this-project` spelling is deliberate: an unpairable log reports `log-not-this-project` and `metrics-not-this-project` together, so the two can be joined without a lookup table: - -```sh -# Human-readable summary (from a TOML-primary plan skeleton): -agent-flow status --source docs/plans/my-task.plan.toml - -# Or from a Markdown plan: -agent-flow status --plan docs/plans/my-task.md - -# Machine-readable projection: -agent-flow status --source docs/plans/my-task.plan.toml --json - -# A log that cannot be paired with the plan is left out, with a reason, at exit 0: -agent-flow status --source /elsewhere/docs/plans/their-task.plan.toml \ - --metrics docs/metrics/workflow.jsonl --json -# { -# "plan": { ... }, -# "metrics": null, -# "metrics_absent_reason": "log-not-this-project" -# } -``` - -### Auditing code value - -`audit` builds an advisory, static report of code that may not be earning its keep: author-declared suppression reasons (an `#[allow(dead_code)]` with its stated rationale) that are shown as fences rather than proposed for removal. It is read-mostly: it writes only its own report, `docs/plans/.code-value-report.md` (or `--out`), and never edits `src/`, `Cargo.toml`, the plan, or the metrics log, and never deletes anything. A human reads the report and decides each candidate; nothing is removed automatically. - -Every report leads with a mandatory caveat: a passing audit is necessary but not sufficient and is only relative to the named signal set, so "nothing flagged" is never proof the codebase has no dead code. The report is projected from a typed intermediate, which `--json` prints to stdout (writing no file) for another tool to consume: - -```sh -# Write the Markdown report to docs/plans/my-task.code-value-report.md: -agent-flow audit --source docs/plans/my-task.plan.toml - -# Print the machine intermediate instead of writing a file: -agent-flow audit --source docs/plans/my-task.plan.toml --json - -# Audit a crate elsewhere and write the report to a chosen path: -agent-flow audit --dir path/to/crate --out reports/code-value.md -``` - -## Bring your own pack - -By default the tool uses its built-in pack. Point `--template` at a directory to scaffold from your own pack instead: - -```sh -agent-flow scaffold --template path/to/my-pack --var project=my-service -``` - -A pack is a directory with a `pack.toml` manifest that declares its assets and any variables: - -```toml -# Each asset: where its source file lands, whether it is a tool-owned reference -# asset or a user working file, and whether it is rendered or copied verbatim. -# One source may map to several assets. -[[asset]] -source = "AGENTS.md" -dest = "AGENTS.md" -ownership = "working" # "working" (create-if-absent) or "reference" (refreshed) -render = true # substitute {{variables}}; omit or false to copy verbatim - -[[asset]] -source = "principles.toml" -dest = ".agents/principles.toml" -ownership = "reference" - -[[asset]] -source = "hooks/pre-commit" -dest = ".agents/hooks/pre-commit" -ownership = "reference" -executable = true # drop with the executable bit set (e.g. a hook script); omit or false otherwise - -# Variables the pack's rendered assets can reference as {{name}}. -[[var]] -name = "project" -default = "my-project" # optional; omit `default` to make the variable required - -[[var]] -name = "author" # required: must be supplied with --var author=... -``` - -Every file a pack reads must live inside the pack directory, and that is enforced rather than assumed: an `[[asset]]`'s `source`, a `[[module]]`'s `guidance`, and the `pack.toml`, `principles.toml` and `instrument.md` the tool reads by name are each refused if the path is absolute, carries a `..` component, or lands outside the pack once symbolic links are followed. The refusal is loud, names the file, and writes nothing. This is the same trade the metrics and ledger boundary above takes: a loud refusal beats silently reading a file the pack did not ship. A link INSIDE the pack is fine, and so is pointing `--template` at a link to the pack directory itself; what is refused is a link whose target is outside. If your pack is assembled by a tool that links each file to somewhere else, such as GNU stow, home-manager or a nix profile, point `--template` at the real directory when the files all resolve into one, and otherwise materialise the pack into real files (`cp -rL`, or a clone rather than a link). - -Rendering does minimal `{{name}}` substitution (there is no template engine). `{{principles}}`, `{{instrument}}`, and `{{modules}}` are built-in variables the tool computes itself; all three are reserved, so a pack may neither declare them nor set them with `--var`. `{{principles}}` is computed from the selection. `{{instrument}}` is filled from the pack's optional `instrument.md` render fragment when `--instrument` is set (empty otherwise); like `principles.toml`, that fragment is read directly and inlined, not dropped as its own asset. `{{modules}}` is the concatenated guidance of the enabled modules (see Optional modules below), empty when none is enabled. Setting a variable the pack does not declare, or leaving a required variable unset, is an error and nothing is written. - -### Optional modules - -A pack can group opt-in extras into named modules. Declare each module in a `[[module]]` section, then tag the `[[asset]]` and `[[var]]` entries that belong to it with `module = ""`: - -```toml -# Each module names itself and describes what it adds. This section is the -# authoritative list of known module names. -[[module]] -name = "diagrams" -description = "Adds a diagram template and the variable it renders." -guidance = "diagrams-guidance.md" # optional: a pack fragment concatenated into {{modules}} when this module is enabled -requires = ["checks"] # optional: modules this one auto-enables (transitively) when selected - -# An asset tagged with a module is dropped only when that module is selected. -[[asset]] -source = "diagram.md" -dest = "docs/diagram.md" -ownership = "working" -render = true -module = "diagrams" - -# A variable tagged with a module is only in play when that module is selected. -[[var]] -name = "diagram_title" -module = "diagrams" # required here, but only demanded when `diagrams` is selected -``` - -An entry with no `module` tag is core: it is always applied. A tagged entry is applied only when you select its module with the repeatable `--module ` flag (`agent-flow scaffold --module diagrams`). With no module selected, every tagged asset is dropped and every tagged variable is skipped entirely: its default does not apply, it is not required, and a `--var` naming it is rejected as undeclared, exactly as if the pack never declared it. A selected module's variables behave like core ones (a default applies, or the variable is required if it has none). Because core output does not depend on any module, scaffolding with no `--module` is byte-identical to a pack that declares no modules at all. - -A module may declare an optional `guidance` fragment: when the module is enabled, that fragment (read from the pack like `instrument.md`, not dropped as its own asset) is concatenated, in `[[module]]` declaration order, into the reserved `{{modules}}` render slot. A module may also declare `requires`, the modules it auto-enables when selected: selecting a module enables everything it requires, transitively, so a module can depend on another without you naming both. A `requires` cycle is tolerated (the expansion is a fixed point), so it neither loops nor errors. - -Every module a tag or a `requires` references, and every `--module` you pass, must be declared in a `[[module]]` section, and each module name must be declared only once. An unknown `--module`, a tag naming a module no `[[module]]` declares, a `requires` naming a module no `[[module]]` declares, or a duplicated `[[module]]` name is an error, and nothing is written. - -Principles are a property of the pack: if your pack ships its own `principles.toml`, `--template` selects and renders from that set rather than the built-in one. A pack that ships no `principles.toml` simply has no principles to select. - -## Development - -The repository uses Nix, direnv, and just. Common tasks: - -```sh -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 -``` - -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, the scratch-repository tests for the attribution check, the attribution check itself over every commit reachable from `HEAD`, or from the branch commit named by `ATTRIBUTION_TARGET` when GitHub CI checks out a pull request's merge result, and a check that the run left the tracked tree unchanged. +## Decision and research context -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. +- [Historical decision revalidation](docs/audits/2026-09-09-historical-decision-revalidation.md) preserves the retained direction without implementation authority. +- [Tooling research and Rust library trial](docs/audits/2026-09-16-tooling-research-and-trial.md) records later evidence and its limits. -## License +## Licence -This project is licensed under the [Blue Oak Model License 1.0.0](LICENSE). +This project uses the [Blue Oak Model License 1.0.0](LICENSE). diff --git a/docs/adoption.md b/docs/adoption.md new file mode 100644 index 00000000..2be4c80a --- /dev/null +++ b/docs/adoption.md @@ -0,0 +1,164 @@ +# Adopt agent-flow into an existing project + +This guide covers the unreleased minimal workflow, not the published 0.0.4 crate. + +The rehearsal uses runtime source at `d486e747981776eb36c8d13c8217a4d5263dcda4` with this delivery's candidate directory pack. That base predates the adoption prompt. No released version or future commit identifies the complete candidate yet. + +Use the [source installation instructions](../README.md#install-the-current-workflow). Record the exact checkout commit and any local changes. + +The source still reports 0.0.4, so version output alone is insufficient. + +## 1. Establish authority before edits + +All project paths and refs below belong to the consuming project, not the agent-flow source checkout. + +Read the project's existing instructions and relevant product material: + +- Root and nested agent guidance. +- Plans and specifications. +- Current work and its acceptance criteria. +- Check configuration and hooks. +- Local changes and untracked files. + +Permission to read guidance grants no authority to execute its commands or hooks. + +Ask the human to resolve instruction conflicts before edits. Ask who owns uncertain decisions. Agree how the new workflow joins the existing instructions. Do not silently choose precedence. + +Keep legitimate project plans and specifications in that project's VCS. + +The agent-flow reset authorises no deletion of another project's plans. Historical checkboxes and broader plans do not establish current agreed work. + +Identify only the current work that the human approves for `.agents/work.toml`. If scope exceeds five steps or 4,096 bytes, stop for a smaller agreed scope. + +## 2. Inspect the project and proposed assets + +Change to the consuming project root: + +```sh +cd /path/to/your-project +``` + +Record the initial state without index refresh: + +```sh +GIT_OPTIONAL_LOCKS=0 git status --short --untracked-files=all +git diff --no-ext-diff --no-textconv +git diff --cached --no-ext-diff --no-textconv +``` + +Preserve unrelated changes and the index. Do not stage or stash work as part of adoption. + +Preview the default pack: + +```sh +agent-flow scaffold --output-dir . --vcs none --principles default --dry-run +``` + +The preview labels each destination with its proposed action. It does not establish that existing reference content is disposable. + +Inspect every existing destination and its parent directories. If a destination uses a symlink or an unexpected file type, stop for human direction. + +The default pack has two ownership classes: + +- Working files, `AGENTS.md` and `.agents/work.toml`, remain unchanged when present. +- Reference assets under `.agents/` refresh on every write, including locally edited copies. + +Compare reference content before replacement. If local references contain project requirements, ask the human how to preserve them in project-owned files first. Obtain approval for each reference replacement. Do not use `--force`. + +Checks and hooks stay outside this default write. + +Do not select `--module checks` or `--with-precommit-hook` during adoption. + +## 3. Apply the approved scaffold + +After the human approves the destinations and instruction integration, apply the scaffold: + +```sh +agent-flow scaffold --output-dir . --vcs none --principles default --write +``` + +`--vcs none` prevents repository initialisation. This command neither stages nor commits the output. + +Do not overwrite an existing `AGENTS.md`. Apply only the human-approved integration edit to that file, or to the project's actual instruction entry point. + +Two integration approaches are available: + +- Add an explicit instruction to read `.agents/AGENTS.reference.md`, after approval of its full content and precedence. +- Merge the approved workflow sections into project-owned guidance, with the existing project rules intact. + +A reference link makes future reference refreshes instruction changes too. A manual merge requires later human reconciliation when the workflow changes. + +A preserved root file alone does not activate the workflow. + +Ask the human to approve the applicable instruction path and any precedence rule. If neither approach preserves project intent, stop. + +## 4. Set only agreed current work + +The generated work file contains starter text, not approved project work. + +If `.agents/work.toml` already exists, preserve it unless the human explicitly approves specific changes. If it is new, replace the starter only with human-approved current work. + +Keep broader plans and specifications at their existing project paths. Refer to them from the work prose when necessary. Do not convert legacy plans or copy historical checkboxes into active steps. + +This example assumes the human approved only a help-text correction. It grants no authority in your project. + +```toml +version = 1 +selected_action = "clarify-help" + +[[step]] +id = "clarify-help" +status = "active" +blocked_by = [] +user_problem = "The help text omits the output directory default." +change = "Document the output directory default in the help text." +acceptance = [ + "The help text names the default output directory.", + "The existing CLI tests pass.", +] +why_next = "The human selected this correction before further CLI changes." +``` + +The [state reference](reference.md#bounded-work-state) describes the closed schema and its limits. + +## 5. Validate and review + +Run the read-only state commands from the consuming project root: + +```sh +agent-flow validate --source .agents/work.toml +agent-flow status --source .agents/work.toml +agent-flow status --source .agents/work.toml --json +agent-flow next --source .agents/work.toml +agent-flow next --source .agents/work.toml --json +``` + +These commands parse and project state. They do not execute the project's checks or hooks. Structural validation proves neither correct interpretation of project intent nor independent review. + +Compare the projected action with the human's agreement. If validation fails or the interpretation differs, stop for correction within the approved scope. + +Inspect the complete result: + +```sh +GIT_OPTIONAL_LOCKS=0 git status --short --untracked-files=all +git diff --no-ext-diff --no-textconv +git diff --cached --no-ext-diff --no-textconv +git ls-files --others --exclude-standard +``` + +Open every new file listed above, since ordinary `git diff` excludes untracked files. Inspect any new ignored files at the scaffold destinations too. Compare the index and unrelated changes with the initial state. + +Request human review of the diff and all new files. Report the source revision alongside commands and results. Report unresolved decisions separately. + +Stop here. Do not automatically: + +- Start implementation or invoke `kickoff.md`. +- Install or execute hooks. +- Run project check commands. +- Stage or commit files. +- Publish changes. +- Convert or delete legacy plans. + +For later refreshes, repeat the destination review before the scaffold write. + +Unchanged references produce identical bytes, but edited reference copies are replaced. Working-file preservation and reference refresh are separate behaviours. diff --git a/docs/legacy.md b/docs/legacy.md new file mode 100644 index 00000000..9b40c720 --- /dev/null +++ b/docs/legacy.md @@ -0,0 +1,134 @@ +# Legacy compatibility and audit + +This reference supports projects that already use legacy interfaces. It is not an adoption or conversion procedure. For the minimal workflow, use the [adoption guide](adoption.md). + +Existing project plans remain legitimate project material. Adoption does not authorise their conversion or deletion. + +## Mode selection + +An existing `.agents/work.toml` selects bounded mode by default. Explicit legacy inputs select compatibility mode for these commands: + +- `validate`, with a legacy plan or metrics input. A workflow check also selects compatibility mode. +- `status`, with a legacy plan or metrics input. Resume flags also select compatibility mode. +- `next`, with an explicit legacy plan source. + +Without `.agents/work.toml`, all three retain legacy fallback behaviour. Legacy `next` no longer emits free-form ledger or resume text. + +## Structured plans and render + +A legacy plan contains a `.plan.toml` skeleton and Markdown sidecars. The TOML describes these structured regions: + +- Roadmap steps. +- The question queue. +- Principles. + +Sidecars hold the remaining prose. `render` inserts sidecars verbatim into the generated `.md` projection. The source consists of TOML and sidecars, not the generated Markdown. + +```sh +agent-flow render docs/plans/my-task.plan.toml +agent-flow render --check docs/plans/my-task.plan.toml +agent-flow render --check --strict docs/plans/my-task.plan.toml +``` + +A schema violation or missing sidecar prevents output. Unresolved cross-references also fail. + +`--check` compares an in-memory render with the committed view without writes. Drift produces a warning at exit 0, while `--strict` makes drift nonzero. Invalid source always fails. + +The minimal pack emits no legacy plan template. Custom packs can still supply one. Scaffold preserves an existing generated view whose bytes differ, with a `keep (edited)` message. + +## Legacy validation + +Explicit legacy validation checks metrics records against their schema. `--plan` checks the Markdown plan's structured regions. A legacy TOML `--source` checks its schema and internal references. + +```sh +agent-flow validate --metrics docs/metrics/workflow.jsonl +agent-flow validate --source docs/plans/my-task.plan.toml +agent-flow validate --source docs/plans/my-task.plan.toml --workflow +agent-flow validate --plan docs/plans/my-task.md --workflow +``` + +`--workflow` cross-references plan completion with round records and applicable waivers. A TOML source with `[meta].primary = "toml"` supplies the plan without `--plan`. Otherwise, the check uses Markdown. + +Malformed records and workflow disagreements produce nonzero exits. Without `--workflow`, an absent legacy log produces a note at exit 0. + +With `--workflow`, an absent log or unresolved plan fails the check. A log outside the plan's project root also fails. Containment cannot identify foreign records copied inside the correct project tree. + +`--workflow-spec` selects a control-constants specification and requires `--workflow`. A malformed specification fails. + +## Legacy path resolution + +Without explicit `--metrics`, the log resolves from the plan source rather than the current directory. + +The project root comes from the nearest `/docs/plans/` ancestor of `--source`, or otherwise `--plan`. Without such an ancestor, it comes from the source's directory. + +The default log is `docs/metrics/workflow.jsonl` under that root. Legacy ledger readers use `.ledger.md` beside the plan source. + +Explicit `--metrics` and `--ledger-fragment` paths remain verbatim. With neither source nor plan, defaults remain relative to the current directory. + +Root derivation is textual and does not inspect `.git`. A bare filename from inside `docs/plans` lacks the ancestors needed for the intended project root. Run legacy commands from the project root with project-relative paths. + +Containment compares real on-disk locations, so symbolic links cannot disguise an external log or ledger. Symlink layouts that separate plans from their evidence can fail containment. + +When no plan is read, projections derive roots from every supplied anchor that exists on disk. The artefact must remain under all those roots. + +If no supplied anchor exists, roots derive from partially resolved paths. A stderr note identifies absent or inaccessible anchors. An inaccessible anchor does not overrule an existing one. + +With no anchors, projections perform no containment check. `validate --workflow` instead refuses when no plan resolves. + +## Legacy status and next + +`status` projects these legacy fields: + +- Roadmap steps grouped by status. +- The question count. +- The metrics count. + +It prefers TOML-primary source data, otherwise Markdown. + +Unlike bounded mode, legacy projections are best-effort. Missing or malformed parts do not necessarily fail the command. `--json` emits the available projection. + +```sh +agent-flow status --source docs/plans/my-task.plan.toml +agent-flow status --plan docs/plans/my-task.md +agent-flow status --source docs/plans/my-task.plan.toml --json +agent-flow status --source docs/plans/my-task.plan.toml --resume +``` + +A rejected log makes `status` print `metrics: unavailable` with a reason. Legacy `next` omits the metrics count and `ACTIVE LOOP` block for that condition. `status --resume` reports a rejected ledger instead of its `## RESUME STATE` section. + +These projections still exit 0. This differs from the nonzero refusal in `validate --workflow`. + +Legacy JSON carries reason fields: + +`metrics_absent_reason` accepts `log-absent` or `log-not-this-project`. + +`resume_state_absent_reason` belongs to legacy `next` and accepts: + +- `ledger-absent`. +- `no-resume-section`. +- `ledger-not-this-project`. + +`no_active_loop_reason` belongs to legacy `next` and accepts: + +- `no-plan-steps`. +- `all-steps-terminal`. +- `metrics-not-this-project`. +A present component has a null absence reason. Legacy `next` has no free-form `resume_state` field. + +## Code-value audit + +`audit` creates an advisory static report. Its current signal covers author-declared suppression reasons, such as `#[allow(dead_code)]`, as fences rather than removal proposals. + +```sh +agent-flow audit --source docs/plans/my-task.plan.toml +agent-flow audit --source docs/plans/my-task.plan.toml --json +agent-flow audit --dir path/to/crate --out reports/code-value.md +``` + +The default report path is `docs/plans/.code-value-report.md`. `--out` changes that path. `--json` emits the typed intermediate to stdout without a report file. + +The command edits no product source or plan and deletes nothing. A human decides any subsequent change. + +The report's caveat limits results to the named signal set. An empty report does not prove that the codebase lacks dead code. + +The command is unrelated to the historical `audit.md` user prompt. The current minimal pack does not ship that prompt. diff --git a/docs/packs.md b/docs/packs.md new file mode 100644 index 00000000..e5f83084 --- /dev/null +++ b/docs/packs.md @@ -0,0 +1,127 @@ +# Custom packs + +For normal adoption, use the [default-pack guide](adoption.md). A custom pack can produce a different layout and workflow. + +## Manifest and variables + +`--template` selects a directory pack instead of the embedded pack: + +```sh +agent-flow scaffold --template path/to/my-pack --var project=my-service --var author=maintainer --dry-run +``` + +The directory contains `pack.toml`: + +```toml +[[asset]] +source = "AGENTS.md" +dest = "AGENTS.md" +ownership = "working" +render = true + +[[asset]] +source = "principles.toml" +dest = ".agents/principles.toml" +ownership = "reference" + +[[asset]] +source = "hooks/pre-commit" +dest = ".agents/hooks/pre-commit" +ownership = "reference" +executable = true + +[[var]] +name = "project" +default = "my-project" + +[[var]] +name = "author" +``` + +Each asset maps a source to a relative destination. One source can supply several destinations. + +`working` assets are create-if-absent unless forced. `reference` assets refresh on every write. `executable = true` sets Unix mode `0o755`, with no mode change on non-Unix systems. + +`render = true` enables minimal `{{name}}` substitution, not a template language. Without it, the file is copied verbatim. Rendered content ends with one newline. Unknown placeholders remain unchanged. + +Variables without a default require `--var name=value`. An undeclared variable or absent required value causes an error before writes. + +The tool reserves these variables: + +- `principles`. +- `instrument`. +- `modules`. +- `workflow_control`. +- `isolation_policy`. +- `recommendation_rule`. +- `findings_naming`. + +Packs cannot declare reserved variables or override them with `--var`. + +`principles` contains the selected principles. A directory pack uses its own `principles.toml`. A pack without that file has no principles. + +`instrument` contains the optional `instrument.md` fragment when `--instrument` is present. Otherwise it is empty. The minimal built-in pack has no instrumentation slot and creates no round log. + +`modules` contains enabled module guidance in declaration order. The remaining reserved fragments support compatibility guidance. + +## Containment + +Every pack source must remain inside the pack directory. This includes: + +- Asset sources. +- Module guidance. +- `pack.toml`. +- `principles.toml`. +- `instrument.md`. + +The loader rejects absolute paths and `..` components. It also rejects files that resolve outside the pack through symbolic links. A refusal names the file and prevents writes. + +Links within the pack work. A `--template` link to the pack directory also works. + +If all linked files resolve into one real directory, select that directory. Otherwise, materialise the pack into real files, for example with `cp -rL`, or use a clone. + +Destination paths must be relative without `..` components. This lexical check does not protect against symlinked destination directories. Inspect the consuming project's destinations before any write. + +## Optional modules + +Modules group opt-in assets and variables: + +```toml +[[module]] +name = "diagrams" +description = "Adds a diagram template." +guidance = "diagrams-guidance.md" +requires = ["checks"] + +[[module]] +name = "checks" +description = "Adds project checks." + +[[asset]] +source = "diagram.md" +dest = "docs/diagram.md" +ownership = "working" +render = true +module = "diagrams" + +[[var]] +name = "diagram_title" +module = "diagrams" +``` + +`--module ` is repeatable. Untagged entries always apply. Tagged entries apply only when their module is enabled. + +Disabled variables contribute no defaults and require no values. A `--var` for a disabled variable is undeclared and fails. + +An optional `guidance` file contributes to `{{modules}}` rather than a separate output asset. Enabled guidance files must exist. + +`requires` enables dependencies transitively. Cycles terminate through fixed-point expansion. + +These errors prevent writes: + +- Unknown selected modules. +- Unknown module tags. +- Unknown dependencies. +- Duplicate module declarations. + +With no selected modules, tagged entries do not affect the core output. diff --git a/docs/reference.md b/docs/reference.md new file mode 100644 index 00000000..cedc6236 --- /dev/null +++ b/docs/reference.md @@ -0,0 +1,256 @@ +# Workflow and command reference + +For first adoption, use the [existing-project guide](adoption.md). [Custom packs](packs.md) and [legacy compatibility](legacy.md) have separate references. + +## Default files + +```text +AGENTS.md project-owned guidance +.agents/ + work.toml project-owned bounded state + AGENTS.reference.md refreshed guidance reference + principles.toml refreshed principles catalogue + prompts/ + implementer.md + reviewer.md + triager.md + fixer.md + verifier.md + user-prompts/ + adopt.md + kickoff.md + review.md +``` + +Guidance is harness-agnostic. Harness-specific instruction files can point to the project's agreed root guidance rather than duplicate it. + +The default creates none of these process artefacts: + +- Ledger templates. +- JSON Lines round logs. +- A `docs/plans/` process tree. +- Review directories. +- Plan-review loops or convergence-round state. + +The user prompts serve separate human requests: + +- `adopt.md` prepares an existing project for human review, without implementation. +- `kickoff.md` starts the selected action and bounded delivery. +- `review.md` requests a standalone read-only review, either a whole tree at one ref or one diff between refs. + +The standalone review prompt returns the review directly. It creates no review state and permits reproduction only in human-authorised scratch space outside the reviewed repository. + +## Scaffold options + +Bare `agent-flow` prints subcommand help. `agent-flow scaffold` prints the proposed asset actions before any write: + +- `create` for absent assets. +- `refresh` for existing reference assets. +- `skip (exists)` for existing working files. +- `overwrite` for forced working-file replacement. + +On a terminal, `scaffold` opens the principle selector. Save confirms writes, while Cancel or quit writes nothing. + +Outside a terminal, writes require `--write`. Without it, the command only prints the plan. `--dry-run` always skips the selector and writes nothing. + +```sh +agent-flow scaffold +agent-flow scaffold --output-dir path/to/project --dry-run +agent-flow scaffold --output-dir path/to/project --write +``` + +Reference assets refresh on each write. Working files remain unchanged unless `--force` accompanies the write. `--force` controls replacement, not write permission. + +The default VCS option initialises an empty Git repository only on write. A target already inside a repository does not receive a nested repository. `--vcs none` disables initialisation. Scaffold never commits files. + +### Principles + +`--principles` accepts comma-separated tokens: + +- `default` selects the default subset. +- `all` selects every principle. +- `none` selects no principles. +- `tag:` selects principles with that tag. +- A bare id selects one principle. + +The selection removes duplicates and preserves first occurrence order. `--principle-detail` accepts these values: + +- `name` for names only. +- `summary` for the default summaries. +- `full` for names with rationale and references. + +```sh +agent-flow scaffold --list-principles +agent-flow scaffold --principles all --list-principles +agent-flow scaffold --principles kiss,verify-dont-trust,tag:fp --dry-run +``` + +The selector starts from `--principles`. Its left pane shows available principles, and its right pane shows the ordered selection. + +| Keys | Action | +| --- | --- | +| `i` / `a` | Move the highlighted principle before or after the destination cursor. | +| `Tab` | Switch panes. | +| `h` / `l` | Switch panes. | +| Horizontal arrows | Switch panes. | +| `j` / `k` | Move the cursor. | +| Vertical arrows | Move the cursor. | +| `K` / `J` | Reorder the selection. | +| `u` / `U` | Undo or redo. | +| `/` | Filter available principles. | +| `Enter` | Open confirmation, with Cancel selected. | +| `q` | Abort. | + +The filter matches these principle fields: + +- Name. +- Id. +- Tag. + +Save prints a `--principles ` argument for later reuse. + +### Optional checks and hooks + +`--module checks` adds product-development tooling: + +- A project-owned `.agents/checks.toml`. +- Project-owned ast-grep configuration and an example rule. +- A reference checks-reviewer prompt. +- A reference `.agents/hooks/pre-commit` script. + +The script stays inert unless separately installed. `--with-precommit-hook` requires `--module checks` and installs a create-if-absent delegate. It does not overwrite existing hooks or install outside the target project. + +`agent-flow checks` runs configured lint and format commands in a temporary Git worktree. `--staged` selects index content rather than tracked working-tree content. The runner does not provide a security sandbox for trusted commands that use absolute paths or mutate Git metadata. + +These opt-in tools are neither task state nor proof of review. Permission to read a check configuration does not authorise its execution. + +## Bounded work state + +`.agents/work.toml` is the only workflow task-state file. Version 1 permits at most five total ordered steps and 4,096 source bytes. + +Each step has these fields: + +- `id`. +- `status`. +- `blocked_by`. +- `user_problem`. +- `change`. +- `acceptance`, an array of criteria. +- `why_next`. + +Statuses use a closed vocabulary: + +- `active`. +- `pending`. +- `complete`. + +While unfinished work remains, `selected_action` must name exactly one active step. Several steps can be active. An all-complete file omits selection and projects an explicit completed result. + +Every blocker must name a step. Active-step blockers must be complete. Pending-step blockers must precede that step and remain active or pending. A pending step cannot block itself. + +Prose fields accept TOML multiline strings: + +- `user_problem`. +- `change`. +- Each `acceptance` item. +- `why_next`. + +Structural values stay on one line. Prose permits line feeds, but all fields reject other control characters and Unicode line or paragraph separators. This includes tabs and carriage returns. + +```sh +agent-flow validate --source .agents/work.toml +agent-flow status --source .agents/work.toml +agent-flow status --source .agents/work.toml --json +agent-flow next --source .agents/work.toml +agent-flow next --source .agents/work.toml --json +``` + +These commands are read-only. With no explicit legacy inputs, an existing `.agents/work.toml` selects bounded mode automatically. + +`validate` checks structural invariants and reports source-prefixed errors with a nonzero exit. Review records cannot change its result. Valid syntax does not prove correct intent or independent review. + +`status` projects every ordered step with its dependency statuses and the selected action. Human and JSON output fail rather than truncate above 16,384 bytes. + +`next` lists every active unit in file order and the selected action's brief. It excludes pending-step prose. Both output formats fail rather than truncate above 8,192 bytes. + +All-complete work produces no active units and no selected action. JSON represents that action as `null`. Identical input produces identical output. + +Human multiline prose uses an indented ` |` continuation gutter, so embedded prose cannot forge top-level headings. JSON preserves accepted strings exactly, including paragraph breaks. + +## Roles are contracts, not isolation + +The human selects the action and invokes `kickoff.md`. Delivery uses one `impl/` branch, without parallel implementation worktrees. + +```mermaid +flowchart LR + start["Selected action"] --> implement["Implementation"] + implement --> review["Independent product review"] + review -->|clean| human["Return to human"] + review -->|findings| triage["Separate triage"] + triage -->|none valid| human + triage -->|valid and in scope| fix["One scoped fix"] + fix --> verify["Focused verification"] + verify -->|pass| human + verify -->|fail| unresolved["Return unresolved work"] +``` + +Review findings cannot expand acceptance criteria. The workflow stops for these conditions: + +- No independent reviewer. +- A finding outside the accepted scope. +- An unsafe fix. +- Failed focused verification. + +The role prompts define contracts. agent-flow launches no role agent or process and creates no role worktree. It neither enforces nor detects separation between roles across these boundaries: + +- Processes. +- Filesystems. +- Networks. +- Credentials. +- Tool access. + +The harness or external runner supplies isolation and independent review. The checks runner's temporary worktree is unrelated to delivery roles. + +## Releases and the rename + +The minimal workflow remains under [Unreleased](../CHANGELOG.md#unreleased). Published 0.0.4 predates it. Use the [source installation path](../README.md#install-the-current-workflow) for this guide. + +`cargo install agent-flow` installs the latest published crate, not necessarily the workflow described here. + +The crate and binary used the name `agent-scaffold` through 0.0.2. The [0.0.3 changelog](../CHANGELOG.md#003---2026-08-15) retains the historical upgrade instructions. The 0.0.4 entry corrects its scaffold-layout claim. + +Published `agent-scaffold` versions remain installable and un-yanked. The crate name is available for reuse. The contact route is the [project issue tracker](https://github.com/nothingnesses/agent-flow/issues). + +Historical force-refresh instructions are not existing-project adoption instructions. + +## Development + +The repository provides a Nix development shell with its pinned toolchain. `nix develop` enters it, or `direnv allow` enables the reviewed environment. + +Common Just recipes are: + +```sh +just build +just test +just clippy +just ci +just run -- --help +``` + +`just ci` runs the same `.agents/checks/ci-gate.sh` as GitHub CI through the locked environment. It includes: + +- Rust formatting checks. +- Clippy with warnings denied. +- Locked tests. +- Product checks and bounded-state validation. +- `actionlint`. +- Tripwires for removed process artefacts. +- Scratch tests for attribution rules. +- Attribution checks across complete reachable history. +- A tracked-tree preservation check. + +`ATTRIBUTION_TARGET` selects the branch commit when CI checks a pull request merge result. + +Use Rust formatting only for changed Rust files. Do not use `just fmt` or `nix fmt` for scoped Rust changes. + +That formatter applies Rust 2024 formatting and reflows retained audit records. The accepted Rust check uses `cargo fmt`. diff --git a/pack/pack.toml b/pack/pack.toml index e80888a0..247738ff 100644 --- a/pack/pack.toml +++ b/pack/pack.toml @@ -63,6 +63,11 @@ source = "user-prompts/review.md" dest = ".agents/user-prompts/review.md" ownership = "reference" +[[asset]] +source = "user-prompts/adopt.md" +dest = ".agents/user-prompts/adopt.md" +ownership = "reference" + # Optional product-development checks. No asset in this module is task state. [[asset]] source = "checks.toml" diff --git a/pack/user-prompts/adopt.md b/pack/user-prompts/adopt.md new file mode 100644 index 00000000..de480a44 --- /dev/null +++ b/pack/user-prompts/adopt.md @@ -0,0 +1,115 @@ +# Adopt into this project + +Help me adopt the minimal agent-flow workflow without implementation. + +Project root: . +Current agreed work: . +Source installation: . + +## Before edits + +Resolve all project paths and refs in the consuming project, not the agent-flow repository. +Read existing project instructions and relevant product material: + +- Root and nested guidance. +- Plans and specifications. +- Existing bounded work. +- Checks and hooks. +- Tracked and untracked changes. + +Permission to read guidance grants no authority to execute its commands or hooks. + +Record the initial status with `GIT_OPTIONAL_LOCKS=0 git status --short --untracked-files=all`. +Inspect both unstaged and staged diffs without external diff drivers or text conversion. +Preserve unrelated work and the index. + +If authority or instructions conflict, stop and ask me before edits. +Present the options and trade-offs with a recommendation. +Do not silently choose precedence. +Agree how approved workflow guidance joins existing project instructions. +Do not overwrite an existing `AGENTS.md`. + +A preserved root file does not automatically include new workflow instructions. + +Propose an explicit reference inclusion or a merge of approved sections into project-owned guidance. +Ask me to approve the instruction path and precedence before that integration edit. + +Keep legitimate plans and specifications in this project's VCS. + +The agent-flow reset is not permission to delete this project's plans. + +Distinguish agreed current work from broader plans and historical checkboxes. +Import only agreed current work into `.agents/work.toml`. +If existing work needs changes, ask me to approve those changes first. + +The pack's work file is starter text, not approved work. + +## Approved adoption only + +Use the unreleased source workflow, not the published 0.0.4 crate. + +Version output alone cannot identify it because current source also reports 0.0.4. + +Confirm the checkout includes this prompt. + +From the project root, preview: + +```sh +agent-flow scaffold --output-dir . --vcs none --principles default --dry-run +``` + +Inspect existing destinations and their parent directories. +If a destination uses a symlink or an unexpected file type, stop and ask me. + +Working files remain when present, but reference assets refresh on every write. +The dry-run list does not establish that reference content is disposable. + +Ask me to approve replacements after preservation of project-specific reference content in project-owned files. +After approval, apply: + +```sh +agent-flow scaffold --output-dir . --vcs none --principles default --write +``` + +Apply only the agreed instruction integration and work-file edits. +Keep the work file within 4,096 bytes and five total steps. +Use only these statuses: + +- `active`. +- `pending`. +- `complete`. + +Do not use `--force`. +Do not install hooks or run project checks. +Do not automatically: + +- Start implementation. +- Stage files. +- Commit. +- Publish. + +Do not convert legacy plans or create workflow records. + +## Validate and return + +Run these read-only commands: + +```sh +agent-flow validate --source .agents/work.toml +agent-flow status --source .agents/work.toml +agent-flow status --source .agents/work.toml --json +agent-flow next --source .agents/work.toml +agent-flow next --source .agents/work.toml --json +``` + +Compare the projected action with my agreement. + +Structural validation does not prove correct interpretation of intent or independent review. + +If validation fails or intent differs, stop and report it. +Inspect the final status and both diffs. +Open all new files, including untracked or ignored scaffold output absent from ordinary diffs. +Confirm unrelated work and the index remain unchanged. + +Return changed paths with exact commands and results. +Request my review of the complete diff and new files, then stop. diff --git a/src/manifest.rs b/src/manifest.rs index a767abd6..99a065d2 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -864,6 +864,7 @@ mod tests { ".agents/principles.toml", ".agents/user-prompts/kickoff.md", ".agents/user-prompts/review.md", + ".agents/user-prompts/adopt.md", ] ); } diff --git a/tests/default_scaffold_is_minimal.rs b/tests/default_scaffold_is_minimal.rs index 2ae9bb02..9fb532c5 100644 --- a/tests/default_scaffold_is_minimal.rs +++ b/tests/default_scaffold_is_minimal.rs @@ -146,6 +146,7 @@ fn default_scaffold_is_bounded_parseable_and_byte_idempotent() { ".agents/prompts/reviewer.md", ".agents/prompts/triager.md", ".agents/prompts/verifier.md", + ".agents/user-prompts/adopt.md", ".agents/user-prompts/kickoff.md", ".agents/user-prompts/review.md", ".agents/work.toml", @@ -156,6 +157,7 @@ fn default_scaffold_is_bounded_parseable_and_byte_idempotent() { expected, "the module-free scaffold should create only the minimal core assets" ); + assert!(!root.join(".git/hooks/pre-commit").exists()); assert!(!root.join(".agents/LEDGER.template.md").exists()); assert!(!root.join(".agents/workflow.toml").exists()); assert!(!root.join("docs/plans").exists()); diff --git a/tests/existing_project_adoption.rs b/tests/existing_project_adoption.rs new file mode 100644 index 00000000..d40a1cf9 --- /dev/null +++ b/tests/existing_project_adoption.rs @@ -0,0 +1,273 @@ +use std::{ + collections::BTreeMap, + fs, + path::{ + Path, + PathBuf, + }, + process::{ + Command, + Output, + }, +}; + +const GUIDE: &str = include_str!("../docs/adoption.md"); +const PROMPT: &str = include_str!("../pack/user-prompts/adopt.md"); + +fn block(language: &str) -> &str { + GUIDE.split_once(&format!("```{language}\n")).unwrap().1.split_once("```").unwrap().0 +} + +fn guide_commands() -> Vec> { + GUIDE + .lines() + .filter_map(|line| line.strip_prefix("agent-flow ")) + .map(|line| line.split_whitespace().collect()) + .collect() +} + +fn success(output: Output) -> String { + assert!( + output.status.success(), + "stdout: {}\nstderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + String::from_utf8(output.stdout).unwrap() +} + +fn git( + root: &Path, + args: &[&str], +) -> String { + success( + Command::new("git") + .current_dir(root) + .env("GIT_OPTIONAL_LOCKS", "0") + .args(args) + .output() + .unwrap(), + ) +} + +fn cli( + root: &Path, + args: &[&str], + directory_pack: bool, +) -> String { + let mut command = Command::new(env!("CARGO_BIN_EXE_agent-flow")); + command.current_dir(root).args(args); + if directory_pack && args[0] == "scaffold" { + command.arg("--template").arg(Path::new(env!("CARGO_MANIFEST_DIR")).join("pack")); + } + success(command.output().unwrap()) +} + +fn put( + root: &Path, + path: &str, + contents: &str, +) { + let path = root.join(path); + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, contents).unwrap(); +} + +fn snapshot(root: &Path) -> BTreeMap> { + fn collect( + root: &Path, + dir: &Path, + files: &mut BTreeMap>, + ) { + for entry in fs::read_dir(dir).unwrap() { + let path = entry.unwrap().path(); + if path.is_dir() { + collect(root, &path, files); + } else { + files.insert(path.strip_prefix(root).unwrap().to_owned(), fs::read(path).unwrap()); + } + } + } + let mut files = BTreeMap::new(); + collect(root, root, &mut files); + files +} + +fn fixture( + name: &str, + existing_work: bool, +) -> PathBuf { + let root = + std::env::temp_dir().join(format!("agent-flow-adoption-{}-{name}", std::process::id())); + if root.exists() { + fs::remove_dir_all(&root).unwrap(); + } + fs::create_dir_all(&root).unwrap(); + git(&root, &["init", "-q"]); + for (path, text) in [ + ("AGENTS.md", "# Project instructions\n\nPreserve the public API.\n"), + ( + "docs/plans/product.md", + "# Product plan\n\n- [x] Historical launch.\n- [ ] Broader redesign, not approved.\n", + ), + ("docs/specification.md", "# Specification\n\nThe public API remains stable.\n"), + (".agents/checks.toml", "# Project checks, do not execute during adoption.\n"), + (".agents/checks/local.sh", "#!/bin/sh\nexit 97\n"), + (".agents/hooks/pre-commit", "#!/bin/sh\nexit 98\n"), + ("src/product.txt", "original\n"), + ] { + put(&root, path, text); + } + if existing_work { + put(&root, ".agents/work.toml", &block("toml").replace("clarify-help", "existing-action")); + } + git(&root, &["add", "."]); + git( + &root, + &[ + "-c", + "user.name=Fixture", + "-c", + "user.email=fixture@example.invalid", + "-c", + "core.hooksPath=/dev/null", + "-c", + "commit.gpgsign=false", + "commit", + "-qm", + "Initial fixture", + ], + ); + put(&root, ".git/hooks/pre-commit", "#!/bin/sh\nexit 99\n"); + put(&root, "src/product.txt", "staged unrelated change\n"); + git(&root, &["add", "src/product.txt"]); + put(&root, "src/product.txt", "unstaged unrelated change\n"); + put(&root, "notes.txt", "Untracked project notes.\n"); + put(&root, ".agents/AGENTS.reference.md", "Old tool reference, approved for replacement.\n"); + root +} + +#[test] +fn displayed_adoption_commands_preserve_existing_projects() { + let commands = guide_commands(); + assert_eq!(commands.len(), 7); + assert_eq!( + commands[0], + ["scaffold", "--output-dir", ".", "--vcs", "none", "--principles", "default", "--dry-run"] + ); + assert_eq!( + commands[1], + ["scaffold", "--output-dir", ".", "--vcs", "none", "--principles", "default", "--write"] + ); + for directory_pack in [false, true] { + for existing_work in [false, true] { + let root = fixture(&format!("{directory_pack}-{existing_work}"), existing_work); + let before = snapshot(&root); + let preview = cli(&root, &commands[0], directory_pack); + assert!(preview.contains(".agents/user-prompts/adopt.md")); + assert!(preview.contains("skip (exists) AGENTS.md")); + assert!(preview.contains("refresh .agents/AGENTS.reference.md")); + assert_eq!(snapshot(&root), before, "dry-run changed the project"); + + cli(&root, &commands[1], directory_pack); + let installed = snapshot(&root); + for (path, contents) in &before { + if path != Path::new(".agents/AGENTS.reference.md") { + assert_eq!(installed.get(path), Some(contents), "changed {}", path.display()); + } + } + assert_ne!( + installed[Path::new(".agents/AGENTS.reference.md")], + before[Path::new(".agents/AGENTS.reference.md")] + ); + assert_eq!(installed[Path::new(".agents/user-prompts/adopt.md")], PROMPT.as_bytes()); + if !existing_work { + assert_eq!( + installed[Path::new(".agents/work.toml")], + include_bytes!("../pack/work.toml").as_slice() + ); + put(&root, ".agents/work.toml", block("toml")); + } + let instructions = fs::read_to_string(root.join("AGENTS.md")).unwrap(); + put(&root, "AGENTS.md", &format!("{instructions}\nRead `.agents/AGENTS.reference.md` for the approved delivery workflow.\n")); + let adopted = snapshot(&root); + let expected_action = if existing_work { "existing-action" } else { "clarify-help" }; + for args in &commands[2 ..] { + let output = cli(&root, args, directory_pack); + if args.contains(&"--json") { + let value: serde_json::Value = serde_json::from_str(&output).unwrap(); + if args[0] == "next" { + assert_eq!(value["selected_action"]["id"], expected_action); + } else { + assert_eq!(value["selected_action"], expected_action); + } + } else if args[0] != "validate" { + assert!(output.contains(expected_action)); + } + assert_eq!(snapshot(&root), adopted, "read-only command changed the project"); + } + + cli(&root, &commands[1], directory_pack); + assert_eq!(snapshot(&root), adopted, "repeat scaffold changed approved work"); + put( + &root, + ".agents/user-prompts/adopt.md", + "Disposable reference edit, approved for replacement.\n", + ); + let edited_reference = snapshot(&root); + cli(&root, &commands[0], directory_pack); + assert_eq!(snapshot(&root), edited_reference); + cli(&root, &commands[1], directory_pack); + assert_eq!( + snapshot(&root), + adopted, + "reference refresh differs from working-file preservation" + ); + assert!(!root.join(".agents/prompts/checks-reviewer.md").exists()); + assert!(!root.join("docs/plans/product.plan.toml").exists()); + assert!(!root.join("docs/metrics").exists()); + assert!(!root.join("docs/plans/product.reviews").exists()); + let untracked = git(&root, &["ls-files", "--others", "--exclude-standard"]); + assert!(untracked.contains(".agents/user-prompts/adopt.md")); + assert!(untracked.contains("notes.txt")); + assert_eq!(snapshot(&root), adopted); + fs::remove_dir_all(root).unwrap(); + } + } +} + +#[test] +fn adoption_prompt_is_discoverable_canonical_and_bounded() { + let readme = include_str!("../README.md"); + assert!(readme.contains("[canonical adoption prompt](pack/user-prompts/adopt.md)")); + assert!(readme.contains("[adoption guide](docs/adoption.md)")); + assert_eq!(PROMPT, include_str!("../.agents/user-prompts/adopt.md")); + assert!(PROMPT.is_ascii()); + assert!(PROMPT.len() <= 4_096); + for clause in [ + "If authority or instructions conflict, stop and ask me before edits.", + "Do not silently choose precedence.", + "Do not overwrite an existing `AGENTS.md`.", + "A preserved root file does not automatically include new workflow instructions.", + "Permission to read guidance grants no authority to execute its commands or hooks.", + "Keep legitimate plans and specifications in this project's VCS.", + "Distinguish agreed current work from broader plans and historical checkboxes.", + "Import only agreed current work into `.agents/work.toml`.", + "The dry-run list does not establish that reference content is disposable.", + "Do not use `--force`.", + "Do not install hooks or run project checks.", + "- Start implementation.", + "- Stage files.", + "- Commit.", + "- Publish.", + "Do not convert legacy plans or create workflow records.", + "Structural validation does not prove correct interpretation of intent or independent review.", + "Confirm unrelated work and the index remain unchanged.", + "Request my review of the complete diff and new files, then stop.", + ] { + assert!(PROMPT.contains(clause), "missing adoption clause: {clause}"); + } + for args in guide_commands() { + assert!(PROMPT.contains(&format!("agent-flow {}", args.join(" ")))); + } +}