diff --git a/.abcd/config/reading-presets.json b/.abcd/config/reading-presets.json index 68c1ee402..497bc928e 100644 --- a/.abcd/config/reading-presets.json +++ b/.abcd/config/reading-presets.json @@ -60,10 +60,10 @@ "test" ], "window": { - "tokens_est": 1280000, - "measured_tokens_est": 1265015, - "measured_bytes": 4870311, - "measured_at": "b198acca0dd490d7e48fb0d11c54382fa826c21a" + "tokens_est": 1300000, + "measured_tokens_est": 1277632, + "measured_bytes": 4918884, + "measured_at": "00a250663e9d9f4631f7e7e0e2485f67b75f961a" } }, "entailment": { @@ -133,9 +133,9 @@ ], "window": { "tokens_est": 390000, - "measured_tokens_est": 377255, - "measured_bytes": 1452432, - "measured_at": "b198acca0dd490d7e48fb0d11c54382fa826c21a" + "measured_tokens_est": 379456, + "measured_bytes": 1460909, + "measured_at": "00a250663e9d9f4631f7e7e0e2485f67b75f961a" } }, "comparative": { @@ -216,10 +216,10 @@ "test" ], "window": { - "tokens_est": 1290000, - "measured_tokens_est": 1274051, - "measured_bytes": 4905099, - "measured_at": "b198acca0dd490d7e48fb0d11c54382fa826c21a" + "tokens_est": 1300000, + "measured_tokens_est": 1286668, + "measured_bytes": 4953672, + "measured_at": "00a250663e9d9f4631f7e7e0e2485f67b75f961a" } } } diff --git a/.abcd/development/brief/04-surfaces/01-ahoy.md b/.abcd/development/brief/04-surfaces/01-ahoy.md index edf47b521..aea9d2826 100644 --- a/.abcd/development/brief/04-surfaces/01-ahoy.md +++ b/.abcd/development/brief/04-surfaces/01-ahoy.md @@ -382,7 +382,7 @@ about, one question per category present, never one per item. | `safe-autocreate` | the repo skeleton, history-store directories, the name-guard artefacts | applied once the category is approved, no per-item prompt; create-if-absent, never overwriting | | `config-change` | visibility, oracle adapter, the `PATH` entry, the git-identity pin, the artefact kind | transparent confirm; skip-if-set with a "current value" notice | | `plugin-owned` | the marker block (itd-3); hook-manifest verification | silent overwrite on marker drift; a non-resolvable diagnostic for a malformed or missing manifest, and for a conventions file whose block would land inside a fence or HTML comment nothing closes (`marker.unplaceable`) | -| `dependency` | the opt-in scanners | one category-level approval covering them; abcd never auto-executes a package manager, and the user runs the commands | +| `dependency` | a tool a capability uses and cannot find: gitleaks, optional over the native secret scanner and required where the repository armed it in `.abcd/config/gitleaks.json` | the category approval reaches the step; each tool is then explained from the tool registry (what it is, optional or required here, what works without it, the exact install step, what the install does) and its install step runs only on a per-tool yes — typed at a terminal, or relayed by a host as a flag naming the tool — never under the approve-everything flag, a piped answer or CI; a no is reported as what the capability continues on | | `status-line` | the offer of abcd's status line in the host harness | an advisory offer asked after its own question, written only on an answered consent; never under the approve-everything flag, and reported as optional work it skipped | | `oracle-routing` | the offer of abcd's proposed model-tier routing table (itd-2609170822093401): the machine's `~/.abcd/oracle-routing.json`, then, as a separate question, the repository's `.abcd/config/oracle-routing.json` | the proposal rendered as a table (agent, tier, fan-out) and each file written only on its own answered consent, the machine one owner-only; never under the approve-everything flag, and reported as optional work it skipped; a decline records nothing, so the next install offers again; uninstall leaves both files | | `user-state` | the registry entry, re-founding, stale or duplicate entries | guided; never auto-edit user-scope state, report extras read-only | @@ -448,6 +448,25 @@ prompt wait rather than decline, which is the contract every prompting CLI has. A run that must neither block nor prompt closes stdin and pre-answers with flags. +**Installing a tool is the one question a piped answer never answers.** It runs +a program on the machine, so it is asked only of a person at a terminal, after +the tool registry's explanation is shown, and its default is no. Off a terminal +the answer is a flag naming the tool, which is how a host relays the answer its +own question tool got; the approve-everything flag never installs a tool, and a +CI runner never installs one and is not asked: the canonical CI detector +(`internal/cienv`, `GITHUB_ACTIONS=true` or a truthy `CI`) refuses, and so does +`CI` set to any value, `false` and `0` included. What runs is the +registry's fixed argv for the platform, never a shell string and never a command +composed from input, and only when the package manager resolves on `PATH` +outside the repository (`internal/core/tools`). The step runs in a process group +of its own, bounded at 15 minutes (its verify at 30 seconds), and a timeout kills +that group through the handle abcd holds. The kill has one limit: a process the +step moves into another group or session (`setsid`, `setpgid`) is out of its +reach and can outlive the run. abcd stops reading output 10 seconds after the +step exits or is killed, so such a process holding the output open cannot hold +the run past its bound, and a step that exits cleanly while leaving one behind +is reported as failed, saying so. + The non-interactive flags pre-answer the prompts: approve every resolvable category, decide the adoption question either way, set the marker target, the oracle backend, the deep-scan toggle and the repo visibility, select track-latest @@ -591,9 +610,12 @@ byte-identical to a fresh install save for the setup date. - **Given** a repo with the install run at an older setup version, **when** the install runs, **then** the version is updated, the marker block refreshed, and existing config keys preserved. -- **Given** an opt-in scanner is not on `PATH`, **when** the dependency category - is approved, **then** the user is shown the install commands under one - category-level approval; abcd never auto-executes a package manager. +- **Given** a tool a capability uses is not on `PATH`, **when** the dependency + category is approved, **then** the person is shown the tool registry's + explanation and asked per tool; the registry's fixed install step runs only + on a yes typed at a terminal or relayed by a host naming the tool, the result + reports what ran and whether its verify passed, and a no reports what the + capability continues on (itd-63). - **Given** no oracle adapter is wired, **when** detection resolves the oracle, **then** it stays host-delegated: abcd needs no API keys or model config, because it emits prompts the host runs (adr-25), and an adapter can be @@ -665,6 +687,7 @@ Sub-verbs: none. | `--bin-dir` | string | | `--dev` | bool | | `--docs-target` | string | +| `--install-tool` | stringSlice | | `--oracle-backend` | string | | `--refuse-adopt` | bool | | `--scan-deep` | string | diff --git a/.abcd/development/brief/04-surfaces/05-intent.md b/.abcd/development/brief/04-surfaces/05-intent.md index aa7ad64a7..26fb0e80c 100644 --- a/.abcd/development/brief/04-surfaces/05-intent.md +++ b/.abcd/development/brief/04-surfaces/05-intent.md @@ -302,7 +302,7 @@ Later phase — intent-auditor (shape-classification role) scans the corpus | Subcommand | Purpose | File movement | |---|---|---| | `/abcd:intent` (no args) | Read-only status: bucket counts (drafts / planned / shipped / disciplines / superseded) with the count of owed fidelity reviews beside them (the owed listing's total, from the same reader), open/closed spec counts, the itd↔spc links, and in the machine-readable form a per-intent listing (id, title, bucket, `ac_state` `real` or `seeded`, and the filing date a timestamp id encodes, null for an ordinal id; iss-242), a ledger-routing hint (`abcd capture "…"` for an observation, `abcd intent "…"` for a user-facing change), and an ideate-routing line (a big, unproven idea? `abcd ideate` runs the optional admission gauntlet and records the verdict either way) | — | -| `/abcd:intent ""` | **Canonical create** (spc-30 (predecessor store)/itd-46): a leading quoted seed is the canonical create entry. Seeds a draft skeleton whose `## Press Release` is the quoted text as prose, under an H1 derived from the text's first sentence (cut on a word boundary at the slug cap) or given as a title — one line, non-empty, redacted like the text — with Why This Matters and Acceptance Criteria seeded as prompts for the human to fill; assigns `itd-N` and derives the slug from the text; writes `suggested_kind: null`. An optional impact (additive, breaking or fix) stamps the draft's product impact at create time, and an optional production mode (hand-written, dictated-and-formatted or scribe-transcribed) stamps how its text was produced (itd-178); the draft's `origin` carries no flag and is derived from the verb that ran. A leading quote always creates — never falls through to bare render | writes to `drafts/itd-N-.md` (no spec created) | +| `/abcd:intent ""` | **Canonical create** (spc-30 (predecessor store)/itd-46): a leading quoted seed is the canonical create entry. Seeds a draft skeleton whose `## Press Release` is the quoted text as prose, under an H1 derived from the text's first sentence (cut on a word boundary at the slug cap) or given as a title — one line, non-empty, redacted like the text — with Why This Matters and Acceptance Criteria seeded as prompts for the human to fill; assigns `itd-N` and derives the slug from the text; writes `suggested_kind: null`. An optional impact (additive, breaking or fix) stamps the draft's product impact at create time, and an optional production mode (hand-written, dictated-and-formatted or scribe-transcribed) stamps how its text was produced (itd-178); the draft's `origin` carries no flag and is derived from the verb that ran. Before the draft is written, under the store's mint lock, its title and press release are matched against every open and resolved issue and every intent by the term-overlap heuristic `/abcd:capture` uses (itd-2609212137116617): a record at or above `match.threshold` is written onto the draft as `duplicates:` or `refines:`, at most three links, and the output lists the near misses below it with their scores; the match never refuses the create. A leading quote always creates — never falls through to bare render | writes to `drafts/itd-N-.md` (no spec created) | | The grill step, on one intent id | Socratic adversarial interview that stress-tests an intent for vagueness, missing acceptance, hidden assumptions before planning. Glossary-aware once `terminology/` exists. A brief-section mode would stress-test a brief section instead. (per itd-27, `intents/planned/` — a later phase; no grill sub-verb ships yet) | (stays in current state) | | Plan (one intent id) | Plans a draft: mints its native spec, injects the bidirectional link (intent `spec_id` ↔ spec `intent`), stamps an identity onto every unmarked scope condition, and moves the file `drafts/` → `planned/`. An impact given at planning stamps the INTENT's product-impact judgement, because the planning interview is where that judgement is made: validated at the create path's bar (never `internal`), written as the bare scalar the create path writes, refused before anything moves when it disagrees with a judgement the record already carries, and a no-op when it agrees; without one the field is left as found and the judgement stays owed to the close (iss-2609170726457256). A production mode given at planning stamps the MINTED SPEC's disclosure pair; the intent's own stamp was written at create time and is never rewritten. On an intent already in `planned/` it does the identity step alone (no spec, no move), takes an impact under the same rules, and refuses when nothing is unmarked and no judgement is added — except where the planned record's `spec_id` is null, when it mints (or reuses the spec already naming the intent) and links the spec in place on the draft's Acceptance Criteria bar, still with no move, and the readiness gate's remedy for the missing spec names this call (iss-2609211738504433). | `drafts/` → `planned/` (stamp step: no move) | | Plan a bundle (several intent ids, a bundle name) | **The bundle command** (itd-34): plans two or more drafts as ONE shared spec. The name is the human's (the plugin page asks for it; the CLI refuses several ids without one, and a name with one id), kebab-case, and carried by no other record. It refuses a member naming another in `blocked_by`, naming the edge, and any member that is not a plannable draft — held, without criteria, already specced or naming another bundle — before the mint. It mints one spec whose frontmatter lists every member (`intents:` beside `intent:`, and `bundle:`), stamps `kind: bundle-member`, `bundle: `, the scope-condition identities and an impact given at planning onto each member, links each `spec_id` to the shared spec, and moves all of them together; a failure after the mint puts every member back and takes the spec back. The shared spec's close ships every member together. | every member `drafts/` → `planned/` | @@ -359,6 +359,12 @@ production_mode: hand-written # how the text was produced: hand-written | dictat # draft, which graduated from nothing. The shipped # record_provenance rule holds it against origin: # extracted-from-record / contributed-by-reading +# duplicates: [iss-N | itd-N, ...] — written by the filing-time match (itd-2609212137116617) +# refines: [iss-N | itd-N, ...] when a record clears match.threshold: a near-identical +# double, or a broader record this draft is the narrower case of. +# A person confirms a link by leaving it and removes it by +# deleting the line; record_schema resolves both like every +# other cross-reference # Added later, not part of the seed skeleton: # held: "" — the hold the intent verb writes and its unhold removes, on a # drafts/ or planned/ record only: one non-empty line, redacted before the diff --git a/.abcd/development/brief/04-surfaces/06-capture.md b/.abcd/development/brief/04-surfaces/06-capture.md index 9b0383f1a..904c41ed6 100644 --- a/.abcd/development/brief/04-surfaces/06-capture.md +++ b/.abcd/development/brief/04-surfaces/06-capture.md @@ -49,7 +49,10 @@ refuses is counted in none of the three totals, so the board counts it beside them and names, for each one, the reader layer that refused it: the filename, the guarded read, the frontmatter parse, the schema or the folder and filename invariants. The layer is what tells a reader whether the record or the reader is -the side to fix (iss-2609120452071388). The board also counts the records git +the side to fix (iss-2609120452071388). A status directory that exists and +cannot be listed is not counted as empty and is not an entry: the board faults +naming the directory, as the list, the transitions and the mint do +(iss-2609261241121312, iss-2609261631120364). The board also counts the records git reports as untracked or changed and marks each such row: folder membership is a status only once the file is committed, so an uncommitted record is in no state to any other branch, worktree or gate (iss-2609100508570527). @@ -81,6 +84,24 @@ record names no location in this checkout, so nothing ties it to the repository it is filed into: that is a nudge, not a gate, and it is the shape every misfiled record behind iss-2609120511058115 had (iss-2609231156260287). +Before the record is written, the fast path matches its text against the +record (itd-2609212137116617): every open and resolved issue's body and every +intent's title and press release, under the ledger lock, through the +term-overlap primitive in `internal/core/record/match`. The score is the share +of the new text's terms a candidate already holds, each term weighted by how +rare it is across the candidates, and it is declared a lexical heuristic on +every output. A candidate at or above `match.threshold` is written onto the +new record as `duplicates:` (the two hold each other's terms) or `refines:` +(the candidate holds this text's terms and more, so this record is the +narrower), at most three links; the output lists the rest, and the best five +below the threshold as near misses with their scores. The match never refuses +and never drops a capture: a text with fewer than eight distinct terms, a record +set that cannot be read and a configuration the reader refuses each file the +record unlinked, and the output says which. A person confirms a link by leaving +it and removes it by deleting its line, which leaves an ordinary record. The +match proposes no `reverses` and no `supersedes`: the itd-84 discipline keeps a +reversal advisory and human. + One flag belongs to one category: the lapse-instant flag carries the RFC 3339 instant a recorded discipline gave way, for the `lapse` category, and it has no default. @@ -339,6 +360,8 @@ related_specs: [spc-N, ...] related_issues: [iss-N, ...] synthesis_clusters: [