diff --git a/.abcd/development/brief/05-internals/01-agents.md b/.abcd/development/brief/05-internals/01-agents.md index 21ade440f..57ecd72f5 100644 --- a/.abcd/development/brief/05-internals/01-agents.md +++ b/.abcd/development/brief/05-internals/01-agents.md @@ -51,10 +51,13 @@ record carries, and two hand-kept copies drift the moment one side gains a field So the cold-reading item contract does not live with cold reading; the reading package imports it from there. -`agents/` also holds two plain docs, its README and its changelog, which carry no -agent frontmatter. Because the plugin manifest declares no agents key, the loader -globs the flat `agents/*.md` set and registers both as harness agents alongside -the real prompt files; iss-110 tracks the mis-registration. +`agents/` holds the prompt files and their per-agent fixture directories and +nothing else. Because the plugin manifest declares no agents key, the loader +globs the flat `agents/*.md` set and registers every file in it as a harness +agent, so the roster's README and changelog, plain docs with no agent +frontmatter, live at [`.abcd/development/agents/`](../../agents/README.md) +instead (iss-110), and `TestPluginAgentSurfaceRegistersOnlyAgents` holds the +directory to prompts alone. ## The design roster still to be built @@ -153,11 +156,19 @@ does the deterministic work and hands a prompt to the host's subagent dispatch, which owns model choice, credentials and execution, and abcd consumes the structured result. -Concrete backends are **opt-in adapters** behind the same seam, selected when an -operator wants abcd to reach a model directly: a local model, a model CLI run as a -subprocess, a provider API, or a model over MCP. The `oracle.backend` config key -records the choice, defaulting to host-delegated; an unreachable adapter degrades -to that default rather than blocking. Per +Concrete backends are **opt-in adapters** behind the same seam, for an operator +who wants abcd to reach a model directly. Two ship. A provider API, hosted or a +local OpenAI-compatible server, is reached through the OpenAI-compatible adapter +by pointing an agent's `oracle.roles` entry at `/` on a +connection `abcd ahoy connect` configured +([`03-configuration.md` § The provider adapter's keys](03-configuration.md#the-provider-adapters-keys)); +a provider that cannot be reached leaves the step to the host rather than +blocking. A model CLI run as a subprocess is a runner that +`roles..runner` names (`internal/core/runner`); an absent or failing +runner hands the role back to the host. A model over MCP is a **design +target**. The `oracle.backend` config key records a choice, defaulting to +host-delegated, and routes nothing: host-delegated is the only value with an +adapter behind it, so install records it without asking. Per [`04-universal-patterns.md § 7`](04-universal-patterns.md#7-vendor-agnostic-adapters-with-environment-branching) the seam is one interface with a native default and opt-in shapes, never a fixed cascade the core imposes. diff --git a/.abcd/development/brief/05-internals/03-configuration.md b/.abcd/development/brief/05-internals/03-configuration.md index b81616029..158ec0464 100644 --- a/.abcd/development/brief/05-internals/03-configuration.md +++ b/.abcd/development/brief/05-internals/03-configuration.md @@ -1,9 +1,13 @@ # Configuration Model Almost nothing here is a decision anyone has to make. Installing abcd asks four -questions, records the answers, and gets on with it; four further keys can be -hand-set and are read but never written. That is the whole of the configuration -surface the shipped binary consults. Everything else on this page is either a +questions, records the answers, and gets on with it; `ahoy install +--attribution` writes one more key, and four further keys can be hand-set and +are read but never written. Beyond those nine, the binary reads only keys a +person opts into: the provider adapter's `oracle` keys and the interview's list +style, both below, the implement loop's `pace` +(`internal/core/implement/loop/pace.go`), and the runner routes under `roles` +and `runner` (`internal/core/runner/config.go`). Everything else on this page is either a store abcd lays out for itself, a policy that follows from one of those four answers, or an axis the design commits to and has not built. @@ -290,10 +294,10 @@ belong in a drainer layer that does not exist. Schema versioning and cross-version migration come in a later phase: a configuration record carries `schema_version: 1`, and migrators are added if a later phase changes the shape (itd-9). The stamp is a convention rather than a -held rule, and the tree is not uniform — eight of the thirteen committed records -carry it, and five do not, the record-lint and docs-lint configuration among +held rule, and the tree is not uniform — eight of the fourteen committed records +carry it, and six do not, the record-lint and docs-lint configuration among them. Nothing refuses an unstamped record, so a migration that arrives before the -stamps do has no version to read on those five. +stamps do has no version to read on those six. ## The history store @@ -978,7 +982,7 @@ not tied to a spec into `.abcd/work/reviews/`, where the reviews charter already governs the shape any sweep would have to meet; the local tier's notes into a curated notes target; and an external tool's workspace state into its own namespace (itd-7). Only the memory package exists today, and its shipped front -door takes a URL rather than a harvested directory. +door takes a local file or an https URL rather than a harvested directory. Two triggers are drawn: an implicit one at disembark's first phase, and a manual CLI refresh. Per-source enable flags would default to on for a private repo and off @@ -1033,13 +1037,17 @@ abcd/ │ ├── core/ # transport-agnostic core, one package per capability │ │ # (adr-23); each returns structured results │ ├── adapter/ # the seam packages (adr-22): interface + native default -│ │ ├── scanner/ # + optional plug-in. The scanner is the one seam with a -│ │ └── gitleaks/ # package today; 02-adapters.md is the catalogue -│ └── surface/cli/ # the only front door that ships (an mcp/ door is adr-23's third) +│ │ ├── scanner/ # + optional plug-in. The scanner is the one capability +│ │ ├── gitleaks/ # seam with a package today; gitleaks is its plug-in, and +│ │ ├── hosting/ # hosting, openaiapi and tailscale are adapters outside +│ │ ├── openaiapi/ # the capability seams; 02-adapters.md is the catalogue +│ │ └── tailscale/ +│ └── surface/ # front doors: cli/, and dashboard/, the one network +│ # listener (an mcp/ door is adr-23's third) ├── commands/.md # markdown command surfaces, flat; the gated list lives in │ # ../04-surfaces/README.md (abcd.md is the bare /abcd board) -├── agents/.md # host-delegated agent prompts, plus per-agent fixtures/, -│ # README.md and CHANGELOG.md — see 01-agents.md +├── agents/.md # host-delegated agent prompts, plus per-agent fixtures/ +│ # and nothing else — see 01-agents.md └── hooks/ # host event hooks; every event command runs through a ├── bootstrap.sh # resolving shim. bootstrap.sh PROVISIONS the plugin-root ├── .sh # binary and never builds one. Each event's shim is its own diff --git a/.abcd/development/brief/05-internals/05-prompt-quality.md b/.abcd/development/brief/05-internals/05-prompt-quality.md index 489be1262..b87895e23 100644 --- a/.abcd/development/brief/05-internals/05-prompt-quality.md +++ b/.abcd/development/brief/05-internals/05-prompt-quality.md @@ -70,9 +70,14 @@ The design target: each agent ships two or three fixture inputs with an expected output structure, validated by schema and judged by an oracle for whether the output is good enough, run in CI by a generic harness. -Neither half exists. There is no `internal/core/prompttest` package, and every -shipped agent's `fixtures/` directory holds exactly one file, the injection canary, -which is layer C's presence check rather than a golden test. The harness lands with +Neither half exists as a generic layer. There is no `internal/core/prompttest` +package, and sixteen of the eighteen shipped agents' `fixtures/` directories hold +exactly one file, the injection canary, which is layer C's presence check rather +than a golden test. The other two add fixtures for a second output: the intent +auditor a canary for its consistency role, and the release changelog composer a +canary for its release page and a no-forecast fixture. The composer's three carry +expected payloads that `internal/core/release/fixtures_test.go` runs through the +real ingest, with no model in the loop. The harness lands with the first Pass-A agent's spec (the lifeboat pipeline, whose original plan the retired [Phase 6](../../roadmap/phases/phase-6-lifeboat.md) document holds), the point at which a second agent exists to generalise the runner over. diff --git a/.abcd/development/brief/06-delivery/01-build-sequence.md b/.abcd/development/brief/06-delivery/01-build-sequence.md index 9b30070fe..2a89dfbce 100644 --- a/.abcd/development/brief/06-delivery/01-build-sequence.md +++ b/.abcd/development/brief/06-delivery/01-build-sequence.md @@ -30,9 +30,14 @@ load cleanly. A seam is stood up by the milestone that first consumes it, never on day one: "wired or it isn't done" forbids the stub-interface scaffold this milestone was -first drawn around. The scanner is the one seam standing — the native secret and -PII scan, with an external backend as its config-selected plug-in. The oracle, -history, spec and run seams, and the wiring packages beside them, are planned +first drawn around. The scanner is the one capability seam standing — the native +secret and PII scan, with an external backend as its config-selected plug-in. +Three further adapter packages stand outside the capability seams: `openaiapi`, +the opt-in OpenAI-compatible provider an agent's oracle role can be pointed at; +`hosting`, the provider seam `abcd site setup` routes a rendered site through; +and `tailscale`, the dashboard's view of this computer's own addresses. The +oracle seam's own package, the history, spec and run seams, and the wiring +packages beside them, are planned rather than present, and the claim is gated rather than trusted: the `index_drift` record-lint rule holds every path in the planned-seams region of [`internal/README.md`](../../../../internal/README.md) to being absent from the @@ -51,9 +56,11 @@ core and the packaging boundary holds. the conventions marker block and the rules loader (itd-3), and bootstrapping the user-scope history store. There is no `abcd init` and no config get/set pair: install is the write path a person reaches for, and the bare invocation, - its `--dry-run`, `--identity` and `--remote` modes, and `doctor` are the - read-only halves. Two further forms write: `uninstall` takes abcd back out again, and - `remote apply` turns on the forge's own secret scanning. The full surface is + its `--dry-run`, `--identity`, `--remote` and `--providers` modes, and `doctor` + are the read-only halves. Four further forms write: `uninstall` takes abcd back + out again, `remote apply` turns on the forge's own secret scanning, `connect` + configures a provider on this machine after one verification call, and + `credential --home` stores a verified key. The full surface is the machine-checked table in [`04-surfaces/01-ahoy.md`](../04-surfaces/01-ahoy.md); this milestone is what install has to do, not the whole verb. @@ -82,20 +89,23 @@ core and the packaging boundary holds. ## 3. Intent, brief, and review through the host-delegated oracle -- **intent**: `/abcd:intent` (itd-1, itd-27, itd-34), with press-release +- **intent**: `/abcd:intent` (itd-1, itd-34, itd-94), with press-release composition. Creation is bare quoted text; the shipped sub-verbs are the machine-checked table in [`../04-surfaces/05-intent.md`](../04-surfaces/05-intent.md). Shipping runs the other way round: an intent moves to `shipped/` as the close-hook of `abcd spec close` — on the close after which no open spec names it, since an intent owns - one or more specs — so there is no `intent ship`. `grill` is a design target (itd-27); the - admission gauntlet that ships is `/abcd:ideate`. + one or more specs — so there is no `intent ship`. There is no `grill` sub-verb + either: the grilling itd-27 asked for is the planning interview, `abcd intent + interview`, under itd-94, which supersedes it; the admission gauntlet that + ships is `/abcd:ideate`. - **review**: the oracle seam, **host-delegated by default** ([adr-25](../../decisions/adrs/0025-host-delegated-llm-default.md)): abcd emits a prompt, the host's subagent dispatch runs it, abcd consumes the structured result. Native, CLI, API and MCP adapters are opt-in for an operator who wants abcd to reach a model directly; the default install needs no API keys. - **MCP front door** *(design target)*: a second thin door over the unchanged - core (adr-23). `internal/surface/` holds one door, `cli`; the second is added + core (adr-23). `internal/surface/` holds `cli`, the one door onto the core, and + `dashboard`, the network listener behind `abcd dashboard`; the second door is added once a surface is worth exposing, and the core needs no rework for it because it is transport-agnostic already. @@ -126,9 +136,10 @@ and a companion agent loop as opt-in adapter loops behind the same contract. The receipt-gated, report-not-block iteration boundary is the seam contract every adapter loop inherits. -Nothing of this ships: the binary registers no `run` verb, the run adapter is on -the planned-seams list, and the operator surface over it is itd-29, in -`intents/planned/`. +The seam does not ship: the binary registers no `run` verb, and the run adapter +is on the planned-seams list. The operator surface itd-29 drew over it is +superseded by itd-2609201916151817, in `intents/planned/`, whose loop ships on +the intent key without the seam, through `abcd build`. ## 6. Lifeboat round-trip diff --git a/.abcd/development/brief/06-delivery/02-verification-matrix.md b/.abcd/development/brief/06-delivery/02-verification-matrix.md index d32fcde8d..17172dfc0 100644 --- a/.abcd/development/brief/06-delivery/02-verification-matrix.md +++ b/.abcd/development/brief/06-delivery/02-verification-matrix.md @@ -22,7 +22,7 @@ table is a gap in the table, never evidence that the capability is ungated.** | Test | What to check | |---|---| -| Front-door wiring (CLI↔core↔plugin) | A `commands/*.md` surface shells to the binary → the CLI front door parses input → the core returns a **structured result** the door renders. Core behaviour with no front door calling it is dead scaffolding and fails the wiring check. **(staged)** the second half, that the same core call is reachable from the MCP door with no logic re-expressed (adr-23), waits on that door: `internal/surface/` holds `cli` alone | +| Front-door wiring (CLI↔core↔plugin) | A `commands/*.md` surface shells to the binary → the CLI front door parses input → the core returns a **structured result** the door renders. Core behaviour with no front door calling it is dead scaffolding and fails the wiring check. **(staged)** the second half, that the same core call is reachable from the MCP door with no logic re-expressed (adr-23), waits on that door: `internal/surface/` holds `cli` and the dashboard's listener, and no MCP door | | ahoy idempotency | Run twice at the same version → no marker duplication, "already up to date", transparent re-confirm of visibility | | ahoy upgrade | Bump the plugin version → the config's `meta` block updated, marker content refreshed, no duplication | | ahoy visibility flip | Switch private→public → the gitignore updated correctly, user shown the resulting tracked-versus-ignored list | @@ -32,7 +32,7 @@ table is a gap in the table, never evidence that the capability is ungated.** | Native transcript capture | A session transcript is written to the root-SHA-keyed native store, **redacted at write time before anything lands on disk** (adr-29); the store sits in the user's own home by default, outside every checkout, so no transcript is a candidate for a commit in the first place, and the one place it can sit beside a working tree is the gitignored local tier a user opts a checkout into by name; an optional import merges by timestamp and content hash over the same store | | Native spec store | Directory-as-truth (adr-26): a spec's location encodes its status, and `abcd spec close` moves it and, when no open spec is left naming the linked intent, ships that intent in the same act; `--remainder ` mints the follow-on spec for a partial delivery, and the intent stays planned until it too closes (adr-2609151513118583). Readiness is per-intent, through `abcd intent ready`. **(staged)** the dependency graph over specs and tasks, the sequencing that resolves ready work from it, and a companion backend round-tripping the same on-disk shape (adr-24): the spec core carries no graph, and `spec` registers one sub-verb | | Host-delegated oracle (default) | An oracle call with **no adapter wired** → abcd emits the prompt, the host's subagent dispatch runs it, the structured result returns to the caller; never blocks, needs no API keys (adr-25). Two consecutive calls are independent dispatches, so the audit-fix loop re-primes context every iteration | -| Oracle adapter degrade | **(staged)** A wired adapter that is unreachable degrades to the host-delegated default rather than failing the run. No oracle adapter package exists, so there is nothing to make unreachable; `ahoy install --oracle-backend` records the choice ahead of the seam | +| Oracle adapter degrade | A step routed to a provider connection that cannot be reached, nothing having been sent, runs through the harness rather than failing the run: the receipt names the connection tried and the harness used, and the front door prints one fallback line; any other provider error refuses the step (`TestAnUnreachableProviderFallsBackToTheHarness`, `TestAnUnreachableProviderLeavesTheStepToTheHost`) | | Oracle re-review | After a fix, the audit-fix loop re-dispatches the review prompt through the seam; the verdict can downgrade and is honoured | | Review quality | A lifeboat review carries specific findings, not vague approval | | Disembark read-only (adr-35) | A pack never writes to the source: a test hashes the source tree before and after the run and asserts it is byte-identical. Pointing disembark at a repository abcd has never touched succeeds | @@ -48,9 +48,9 @@ table is a gap in the table, never evidence that the capability is ungated.** | Launch manifest | Correct include and exclude, resolved from the bundler's own rules. **(staged)** a per-repo allow-file overriding them: the launch core reads no such file | | Launch branch mode | **(staged)** a branch-scoped cut leaving the default branch untouched. No mode selector ships | | Launch version stamp | The bump tier is derived from shipped intents' `impact` with the surface-diff guardrail (adr-31), and the version is stamped into the **release artefact** at cut time, leaving the working tree unversioned | -| Launch documentation audit | **(staged)** The docs tree is inspected, and a warn-level failure surfaces a user prompt before proceeding. The gate appears in every launch report and reports itself unimplemented: nothing reads the docs tree at cut time and nothing prompts | +| Launch documentation audit | The gate appears in every launch report: with `.abcd/docs-lint.json` present it runs the docs-lint engine over the configured doc roots and reports the findings at the warn tier, and without it the row reads `not_armed` (`TestDocAuditRowSaysWhenItIsNotArmed`). **(staged)** a warn-level finding surfacing a user prompt before proceeding: nothing prompts | | Asset discovery | **(staged)** The prompt fires when assets are found; user-supplied paths merge into the curator input; the provenance file records origin. Nothing on the pack path looks for an asset, no sub-verb offers a path to add one, and the provenance file such a pack would write carries no origin field — the same gap the *Embark asset curation* row records from the other end | -| Intent capture | Bare quoted text runs the interview and writes a draft with press-release content, a persona quote and acceptance criteria (itd-1, spc-30/itd-46) | +| Intent capture | Bare quoted text runs no interview: it writes a draft whose press release is the text, with Why This Matters, Mechanism, Scope Conditions and Acceptance Criteria seeded as prompts to be replaced, the criteria through the planning interview, before the draft can be planned (itd-1, spc-30/itd-46) | | Intent plan | `abcd intent plan ` lints the acceptance criteria, scaffolds the bidirectionally-linked spec in the native store, injects both sides of the link, and moves the intent to `planned/`. A draft missing its acceptance-criteria section is a hard block naming the missing section | | Intent ship | Shipping is spec-driven: `abcd spec close` moves the spec and, as its close-hook, the linked intent — on the close after which no OPEN spec names it, because an intent owns one or more specs (adr-2609151513118583). The intent must end up carrying a product impact, because that is what decides the release's version: a record that already declares one keeps it, one that declares none takes the impact on the close that ships, and a close that can name neither is refused before anything moves; `--impact` at an earlier close, which ships nothing, is refused too. There is no `intent ship`, and nothing runs the close for you, so an intent whose code is on the default branch with a spec still open ships with no changelog line | | Intent lifecycle hook | Closing the LAST open spec is what moves the linked intent; an earlier close moves nothing and names the specs that still hold it. **(staged)** the review queue that transition was to feed: nothing is enqueued or backfilled, and auto-running the auditor is deferred (spc-6 disowned it). The audit that ships is manual — `abcd intent audit` re-emits the request and `abcd intent audit ingest` writes the returned verdict into the intent's own Audit Notes | @@ -68,7 +68,7 @@ table is a gap in the table, never evidence that the capability is ungated.** | Capture promote | `abcd capture promote --grounds …` mints an intent draft from the issue and stamps the pair of back-edges natively; naming an existing intent is link mode, which writes both halves (`related_issues` on the intent, `related_intents` on the record), and an existing back-edge is kept first and reported rather than clobbered. The same verb promotes a dispositioned reading item | | Capture back-link migration | `abcd capture migrate` reports, and `--apply` rewrites, every retired promote back-link into `related_intents` / `related_issues`, completing a join written from one end only; a second run changes nothing | | Intent issue drift | `abcd intent audit --issue-drift` reports every promote join that does not read the same from both ends, warning on stderr and exiting 0; `--strict` exits 1 on any finding, and the strict form runs in the pre-push gate and in CI | -| Capture migration | **(staged)** promoting a pre-ledger flat issue list into the structured ledger with provenance naming the source file. The binary registers no migration verb | +| Capture migration | **(staged)** promoting a pre-ledger flat issue list into the structured ledger with provenance naming the source file. The binary registers no verb that reads a flat list: `capture migrate` rewrites retired promote back-links inside the ledger | | Rules-loader keyword recall | A prompt containing a domain's keywords injects that domain's rules; an unrelated prompt injects nothing | | Rules-loader dedup | The same prompt three times injects once: within a session a domain that stays in force is never re-injected unchanged, and one that leaves the active set and returns is injected again ([The prompt router's output](../05-internals/03-configuration.md#the-prompt-routers-output)). The refresh is event-driven rather than turn-counted — `SessionStart` and `PreCompact` are the two events that clear the per-session dedup ledger. Nothing counts prompts | | Rules-loader star-command | A leading `*DOMAIN` injects that domain's rules regardless of keyword match | @@ -94,7 +94,7 @@ table is a gap in the table, never evidence that the capability is ungated.** | Modification grammar (itd-37) | **(staged)** in full. Every spec would carry a `## Modification Grammar` section with its three sub-headings and the ripple axis, checked by concreteness lints; the auditor would fire a boilerplate check on text that could equally describe a different spec; and at spec completion the distiller would write the per-spec and per-domain pages. No committed spec carries the section, `abcd spec` scaffolds none, the codes exist in no lint, and nothing writes the pages | | Vocabulary registration (itd-37) | Every term introduced in a spec's vocabulary delta appears in the same spec in the registry that fits it — the canonical glossary or the reserved-vocabulary register. `VR001` is reserved for the check and unimplemented, so registration is enforced by review | | Recovery humility (idea-2) | The disembark and embark surface docs carry the recovery-humility paragraph, and embark's closing imperative names hunting the originating session before trusting the lifeboat blindly | -| Surface discipline (sweep) | No ` show`, ` stats`, ` view` or plain unfiltered ` list` sub-verbs ship, and bare invocation renders state. `SD001` is reserved for the check and unimplemented, so the discipline is held by review — and the shipped tree does not satisfy it: `history show` and a plain `history list` both ship, and unfiltered `banlist list` renders identically to bare `banlist`, which is the redundancy the rule exists to forbid. The earned exception is a filter flag immediately adjacent to `list`: `capture list` with no filter exits 2 and asks for one, so the flag is the surface rather than a convenience, while `banlist list` does not, which is why it is a gap rather than an exception. Where bare invocation does not render is enumerated once, in [`../04-surfaces/README.md`](../04-surfaces/README.md#bare-invocation) | +| Surface discipline (sweep) | No ` show`, ` stats`, ` view` or plain unfiltered ` list` sub-verbs ship, and bare invocation renders state. `SD001` is reserved for the check and unimplemented, so the discipline is held by review — and the shipped tree does not satisfy it: `history show`, `inbox show` and a plain `history list` all ship, and unfiltered `banlist list` renders identically to bare `banlist`, which is the redundancy the rule exists to forbid. The earned exception is a filter flag immediately adjacent to `list`: `capture list` with no filter exits 2 and asks for one, so the flag is the surface rather than a convenience, while `banlist list` does not, which is why it is a gap rather than an exception. Where bare invocation does not render is enumerated once, in [`../04-surfaces/README.md`](../04-surfaces/README.md#bare-invocation) | > **Open question (adr-35):** where a mid-run interrupt checkpoint lands is > undecided, which is why the *Disembark checkpoint* row is staged. adr-35 makes