Skip to content
Merged
98 changes: 98 additions & 0 deletions .abcd/development/brief/04-surfaces/18-ideate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# `/abcd:ideate` — Gate an Idea Before It Becomes a Record Entry

`/abcd:ideate` puts a big, unproven idea through a three-leg admission gauntlet
and records the verdict — whether the idea survives or dies. Only a survivor
graduates to a draft intent, and ideate mints no intent itself.

It is **optional and never a gate**. No other verb requires it, nothing warns
when it is skipped, and capture friction stays at one line. The routing help in
the `intent` and `capture` surfaces names it for big, unproven ideas; that is a
pointer, not a precondition.

## Division of labour

Ideate follows the host-delegated pattern the `disembark` synthesis family
proved. The **host** runs the three judgement legs as agents; the **binary** is
the deterministic frame that validates their output, proves every citation, and
writes the record. The binary never fetches a source, never calls a model, and
never decides whether an idea is any good.

## The three legs, in order

| # | Leg | What it produces |
|---|---|---|
| 1 | Primary-source research | A claims table: each load-bearing claim, the **primary** source it was checked against, and the finding (`verified` / `falsified` / `unverifiable`) |
| 2 | Record grill | Hits on the existing record (brief, intents in every bucket, ADRs, principles), each cited by record id with a relation (`covered` / `contradicted` / `superseded`) |
| 3 | Adversarial review | Kill attempts, each with an outcome (`survived` / `partial` / `fatal`) |

The order is validated, not assumed: the legs travel as an ordered array and a
payload whose legs are missing, reordered, or duplicated is refused. Each leg
changes what the next one is looking at, so running them out of order produces a
different — and weaker — result.

Leg 3 is defined by its **conduct**: fresh-context, off-policy, unknown
authorship. The evaluator did not conduct the research and receives the idea as
an artefact whose provenance it does not know. That is the
evaluator-outside-the-loop principle applied to ideas, and it is the one measured
debiasing effect the protocol rests on. The binary cannot observe how an agent
was run, so this obligation lives in the command page's orchestration script,
which instructs the host to strip authorship and prior-leg framing before the
hand-off.

## Behaviour

```bash
abcd ideate record <idea-slug> --verdict-json <file|-> --json
```

`--verdict-json` is **required**. Unlike the `disembark` synthesis verbs there is
no deterministic fallback mode: those can fall back because a packed lifeboat's
own files carry evidence, and there is no evidence-only verdict an idea could
have. A binary that invented one would be doing the judging.

The verb writes two things:

- `.abcd/development/research/YYYY-MM-DD-ideate-<idea-slug>.md` — the verdict
record: the idea as captured, the three legs, the verdict, and the rejected
alternatives, rendered for a human.
- one dated pointer line in `.abcd/work/DECISIONS.md`.

No new record family: a killed idea is a research outcome, and the research
directory is where a session looks before re-proposing one.

Exit codes are the release-cut shape without the middle state: `0` recorded, `2`
refused with nothing written. There is no exit 1, because a verdict is either
recordable or it is not.

## What the binary refuses

| Refusal | Why |
|---|---|
| A cited record id that does not resolve in the repository | A grill hit on a record that does not exist is a hit on nothing. The refusal names every offending id |
| A cited value that is not a record id at all | Bounded before it is matched or echoed |
| Legs missing, reordered, or duplicated; a leg carrying another leg's evidence | The gauntlet is exactly three legs, in order |
| An out-of-enum verdict, claim status, grill relation, or kill outcome | Each set is closed; an unregistered value is a whole-document refusal, never a coercion |
| A claim naming no primary source | A claim checked against nothing is an assertion |
| An empty `rejected_alternatives` list with no explicit `no_rejected_alternatives` marker | Silence and "nothing was weighed" are indistinguishable to a later reader, and the record exists to stop the idea being re-litigated |
| A slug that is not lower-case kebab-case | The slug becomes a filename; the grammar is the lexical half of the write containment, `os.Root` the other |
| A verdict record that already exists for this slug and date | Overwriting would erase a recorded reason, which is the one thing the verb exists to preserve |
| A repository with no `.abcd/work/DECISIONS.md` | A record nothing points at is a record nobody finds — refused before anything is written |
| A symlinked component anywhere in `.abcd/development/research/` | The write goes through one `os.Root` opened at the repository root, which refuses symlink traversal at every level, not just the leaf |

The research directory itself is **created when absent**: nothing else in abcd
establishes it and no convention check requires it, so refusing would fail the
first run in every repository — after the three host legs have already been paid
for, and with the verdict unrecoverable when it arrived on stdin. The exclusive
create is the no-overwrite guarantee, and the decision log's read-modify-write
runs under the same advisory lock the ledger allocators use.

Refusals are **whole-document**, never cite-or-be-dropped. A verdict record with
a quietly-dropped falsified claim or grill hit is worse than no record, because a
later session trusts it.

## References

- Plugin command: [`commands/abcd/ideate.md`](../../../../commands/abcd/ideate.md)
- Intent: [`itd-104`](../../intents/planned/itd-104-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md)
- Spec: [`spc-18`](../../specs/open/spc-18-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md)
- Routing-help neighbours: [`05-intent.md`](05-intent.md), [`06-capture.md`](06-capture.md)
1 change: 1 addition & 0 deletions .abcd/development/brief/04-surfaces/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The brief's user-facing command surface is the set enumerated below (not all are
| 15 | `/abcd:prepare-this-repo` | shipped | Bring an owned repo up to abcd's conventions (interim bridge) — host-delegated command, no Go verb | [`15-prepare-this-repo.md`](15-prepare-this-repo.md) |
| 16 | `/abcd:audit` | shipped | Check whether a repo conforms to the working conventions (three-tier layout, AGENTS.md router, durable decisions, docs currency, privacy hygiene) — read-only, tri-state exit; backs `prepare-this-repo` and gates CI (itd-85) | [`16-audit.md`](16-audit.md) |
| 17 | `/abcd:guard` | shipped | Shell-hazard guard (`check`) — decide one candidate command line against the bundled hazard registry merged with the repo's committed `.abcd/guard.json`, and answer allow / warn / block with the plain-language why and the safe successor. Read-only. The `hook` sub-verb is the host pre-tool-use adapter, live-wired from `hooks/hooks.json` behind a fail-open-loud shim; guard health (hook installed, binary reachable, registry loadable) is reported by `abcd ahoy` (itd-103, spc-16) | [`17-guard.md`](17-guard.md) |
| 18 | `/abcd:ideate` | shipped | Idea-admission gauntlet (itd-104, spc-18): three host-run legs in a validated order — primary-source research, a record grill whose every hit is cited by an id the binary proves resolves, and a fresh-context/off-policy/unknown-authorship adversarial review — then `abcd ideate record <idea-slug> --verdict-json` writes the dated verdict record under `.abcd/development/research/` and one pointer line in `.abcd/work/DECISIONS.md`. Recorded whether the idea survives or dies, with rejected alternatives that an explicit marker is required to leave empty. Optional and never a gate: the `intent` and `capture` routing help names it, nothing requires it | [`18-ideate.md`](18-ideate.md) |

The **Status** column is machine-checked: the `surface_coverage` record-lint rule asserts every `shipped` row has a backing surface (`commands/abcd/<name>.md` or `skills/<name>/`) and every `staged` row (a design target) has none — and, in reverse, that every real surface has a row here. The bare `/abcd` top-level is binary-backed (its command file is `commands/abcd.md`, not a `commands/abcd/<name>.md` entry) and is exempt from the file check. Keeping this column honest is how the brief's surface set stays reconciled with the shipped binary; the semantic half — whether each row's *prose* matches binary behaviour — stays a release-gate agent check.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
id: itd-104
slug: abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea
spec_id: null
kind: null
spec_id: spc-18
kind: standalone
suggested_kind: null
reclassification_history: []
builds_on: []
Expand Down
18 changes: 18 additions & 0 deletions .abcd/development/release/surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,24 @@
"hidden": false,
"flags": []
},
{
"path": "abcd ideate",
"hidden": false,
"flags": []
},
{
"path": "abcd ideate record",
"hidden": false,
"flags": [
{
"name": "verdict-json",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
}
]
},
{
"path": "abcd intent",
"hidden": false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
id: spc-18
slug: abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea
intent: itd-104
---
# abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea

## Summary

spc-18 delivers itd-104's idea-admission protocol: `/abcd:ideate`, an
optional three-leg gauntlet — primary-source research, a record grill, and an
independent adversarial review — whose outcome is a registered verdict with
rejected alternatives, recorded whether the idea survives or dies. Only a
survivor graduates to a draft intent. The design decisions below were settled
by the 2026-07-27 grill; this spec records them together with the mechanism —
it does not reopen them.

## Settled constraints (from the grill)

- **Optional verb, never a gate.** Capture friction stays at one line: no
ideate step is required or suggested as blocking by the intent or capture
verbs. The routing help carries the nudge — ideate is named for big,
unproven ideas.
- **The adversarial leg codifies fresh-context, off-policy, unknown
authorship** — the evaluator did not do the research and receives the idea
as an artefact of unknown authorship. This is the only measured debiasing
effect, per the salvaged 2026-07-14 research record, and it is the
evaluator-outside-the-loop principle applied to ideas.
- **Two load-bearing steps survive automation** (from the proven manual run):
check the record first, and open the primary source.

## Mechanism

### Division of labour

Ideate follows the host-delegated pattern the disembark family proved: the
host runs the judgement legs as agents; the abcd binary is the deterministic
frame that scaffolds the run, validates the legs' outputs, and writes the
record. The binary never does LLM work; the host never writes the record
directly.

### The three legs, in order

1. **Primary-source research.** A host agent researches the idea's
load-bearing claims, each checked against its primary source — a claim
carries its primary URL or document reference, never only a secondary
citation. Output: a claims table (claim, primary source, verified /
falsified / unverifiable).
2. **Record grill.** A host agent reads the existing record — brief, intents
(all buckets), ADRs, principles — and answers: does an entry cover,
contradict, or supersede this idea? Every hit is cited by record id. The
binary validates at ingest that every cited id resolves in the repository
— a verdict citing a non-existent record is refused.
3. **Adversarial review.** A fresh-context agent, off-policy, receiving the
idea and the two earlier legs' outputs as artefacts of unknown authorship,
tries to kill the idea. It did not conduct the research (evaluator outside
the loop).

### Verdict record (spec-time decision)

The verdict is a dated research record:
`.abcd/development/research/YYYY-MM-DD-ideate-<idea-slug>.md`, written by the
binary from a structured verdict the host supplies
(`abcd ideate record <idea-slug> --verdict-json <file>`). It contains the
idea as captured, the three legs' findings, the verdict
(survives / killed / reframed), the rejected alternatives, and — for record
grill hits — the cited record ids. A one-line dated entry in
`.abcd/work/DECISIONS.md` points at it. No new record family: killed ideas
are research outcomes, and the research directory is where a future session
looks before re-proposing one. A surviving idea graduates through the
existing quoted-text intent create — ideate mints no intents itself.

### Wiring

Both planes at delivery: the `abcd ideate` verb family (CLI) and the
`/abcd:ideate` skill (`commands/abcd/ideate.md`) that orchestrates the three
legs and feeds the verdict to the binary. The routing help in the intent and
capture surfaces names ideate as the optional path for big, unproven ideas —
a pointer, never a precondition.

## Acceptance-criteria mapping

- AC 1 (never blocking; routing help names it) → Settled constraints +
Wiring.
- AC 2 (three legs in order; adversarial leg fresh-context, off-policy,
unknown authorship) → The three legs.
- AC 3 (decision recorded with rejected alternatives either way; only
survivors graduate) → Verdict record.
- AC 4 (record-grill hits cited by id, validated) → The three legs + Verdict
record.

## Out of scope

- Consuming a lifeboat or external document as the idea source (open
question retained; the idea arrives as quoted text in this spec).
- Any change to capture or intent-create flow beyond the routing-help text.
- Automation of the legs inside the binary — the legs are host agents by
boundary; the binary validates and records.
1 change: 1 addition & 0 deletions .abcd/work/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -866,3 +866,4 @@ parallel-agent merge contention bites.
- 2026-07-27: itd-103 guard wiring — `abcd guard hook` is a sub-verb of the USER-facing `guard` verb, not a fifth entrypoint under the hidden `hook` subtree. Reason: guard health has to be legible (AC 1), and a hidden adapter documents nothing; the other `hook` entrypoints are injection transport with no user-facing counterpart, this one is the same decision the user can also ask for by hand. Consequence: it appears in the generated CLI reference and must stay host-agnostic in its prose.
- 2026-07-27: itd-103 guard wiring — the two front doors disagree on ONE case by design. A guard that cannot be evaluated (unparsable command, registry that will not load) is a FAULT for `guard check` (exit 2: a script must never read silence as clearance) and fail-OPEN for `guard hook` (exit 0 + loud stderr: a broken guard must never stop a session). Rejected: one shared behaviour, which would either brick sessions or teach scripts that silence means safe.
- 2026-07-27: itd-103 guard wiring — the fail-open-loud shim is a shell wrapper in the plugin's `hooks/hooks.json` PreToolUse entry (pass through only the binary's own 0/2; everything else warns UNGUARDED and allows), not Go code. The failure it guards against is the Go binary not running at all, so it cannot live in Go. Tested by executing the committed manifest string under /bin/sh against a fake plugin root.
- 2026-07-28: itd-104 spec minted as spc-16 (third live iss-80 instance this run: 16 collided with #156, 17 with the itd-101 branch); renumbered to spc-18 by hand.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ go.work.sum

# capture ledger allocator lock (runtime artefact)
.abcd/work/issues/.iss-alloc.lock

# decision-log append lock (runtime artefact)
.abcd/work/.decisions.lock
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ called out in a **Breaking** section.

### Added

- **`/abcd:ideate` — the idea-admission gauntlet** (itd-104, spc-18). A big,
unproven idea can be put through three legs before it becomes a record entry:
primary-source research (each load-bearing claim checked against its **primary**
source, never a secondary citation), a grill against the existing record, and an
adversarial review that is fresh-context, off-policy, and receives the idea as an
artefact of unknown authorship. The legs are host work; `abcd ideate record
<idea-slug> --verdict-json <file|->` is the deterministic frame that validates
them and writes the durable verdict — the dated record under
`.abcd/development/research/` plus one pointer line in `.abcd/work/DECISIONS.md`.
The verdict is recorded **whether the idea survives or dies**, and its rejected
alternatives may be empty only behind an explicit marker, because silence and
"nothing was weighed" read the same to a session tempted to re-propose the idea.
Every record-grill hit is **cited by id and proved to resolve** in the
repository: an id naming no record refuses the whole verdict and names the id.
The legs travel as an ordered array so "three legs, in order" is checked rather
than assumed, and refusals are whole-document — nothing is written unless
everything validates. Ideate is **optional and never a gate**: the `intent` and
`capture` routing help names it for big unproven ideas, and nothing requires it
or warns when it is skipped.

- **`abcd guard` — the shell-hazard guard, wired into a live session** (itd-103,
spc-16). `abcd guard check --command "<line>"` evaluates one candidate command
against the hazard registry and reports allow, warn, or block: a blocker exits
Expand Down Expand Up @@ -108,6 +128,20 @@ called out in a **Breaking** section.

### Fixed

- **Untrusted prose can no longer open raw HTML in a record abcd writes.** The
shared cleaner every host-delegated ingest routes through — the release
changelog ingest, the lifeboat synthesis writers, and the ideate verdict —
neutralised newlines and HTML comment markers but left a bare tag intact. In
CommonMark a `<` followed by a letter, `/`, `!`, or `?` opens an HTML block, and
several of those block types run to the end of the document: a single
`<script>` in one model-supplied field made every later section of the record
render as inert text inside an unclosed element, while a forged section above it
rendered normally — so an artefact whose whole value is that a later session
trusts it could hide its own evidence. Every tag opener is now broken apart in
the one canonical primitive, so the fix lands at all three boundaries at once.
The neutralisation runs **after** terminal sanitisation, which closes a second
hole: sanitising substitutes `?` for a masked rune, so `<` before an escape byte
previously became `<?` — a processing instruction — after the old ordering.
- **The disembark probe's recursive file walk is bounded per directory, opens
each child in O(1), and skips the common ecosystems' dependency trees**
(iss-112, iss-114, iss-116). The walk now reads every directory with a bounded
Expand Down
4 changes: 3 additions & 1 deletion commands/abcd/capture.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ Summarise the JSON for the user: `open_count` / `resolved_count` /

**Which ledger?** A half-formed observation, question, or nitpick goes to
`/abcd:capture "…"`; a user-facing change you want to ship goes to
`/abcd:intent "…"`.
`/abcd:intent "…"`. For a big, unproven idea there is an optional third route:
`/abcd:ideate` runs the admission gauntlet and records the verdict either way.
It is a pointer, never a precondition — capture friction stays at one line.

## Capture an issue

Expand Down
Loading