Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .abcd/development/brief/04-surfaces/16-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ error — so `abcd audit` gates a repo's CI as well as backing onboarding.
| `decision-durability` | warn | a committed `.abcd/work/DECISIONS.md`; decisions not living only in the gitignored layer |
| `docs-currency` | warn | reuses the docs-lint engine where `docs/` exists |
| `privacy-hygiene` | error | no absolute local paths in committed files, honouring an `abcd-audit:allow` line waiver |
| `identity-positioning` | warn | every registered surface still carries the canonical identity block's tagline (and pitch, where required); Where-gated on a committed `.abcd/positioning.json`, and per-repo upgradeable to `error` — see [`19-identity.md`](19-identity.md) |

## How it is built

Expand Down
85 changes: 85 additions & 0 deletions .abcd/development/brief/04-surfaces/19-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# `/abcd:identity` — Repo Positioning

`/abcd:identity` holds every rendered surface of a repository to one canonical
identity block. The bare and `render` forms are **strictly read-only**; `init` is
the single write path, and it runs once, at onboarding.

It answers a different question from `/abcd:audit`: `audit` reports whether the
repo conforms to the working conventions as a whole, and runs the positioning
check as one rule among them; `identity` is where a maintainer looks at the canon
itself and at what a fix would read like.

## The identity block

The canonical home is a markdown block in the repo's own record — markdown stays
the single source of truth, and the committed configuration records only where
the block lives:

```markdown
## Identity (canonical)

- **Title:** abcd — Agent-Based Configuration for Development
- **Tagline:** A host-agnostic configuration layer for intent-driven development.
- **Pitch:** A single Go binary that carries the why from idea to shipped
reality, usable as a plugin in compatible agent harnesses.
```

Title and tagline are required; the pitch is optional at onboarding and may wrap
across lines. abcd's own block is the "Identity (canonical)" section of
[`01-product/README.md`](../01-product/README.md).

## The registry

`.abcd/positioning.json` records the block's location, the family severity, and
the registered surfaces. It sits beside the repo's other per-concern
configuration (`docs-lint.json`, `record-lint.json`, `rules.json`) rather than
under `.abcd/config/`, where `identity.json` already means the git
commit-author pin.

| field | meaning |
|---|---|
| `block` | `{file, heading}` — where the canonical block lives |
| `severity` | `warn` (default: highlight, never gate) or `blocker` |
| `surfaces[]` | `{id, files, kind, patterns \| field, requires, template}` |

A surface names candidate `files` (the first that exists is checked, so one entry
covers several manifest formats), a locator (`kind: "regexp"` with capture-group
patterns, or `kind: "json_field"` with a top-level key), the block fields it
`requires`, and the `template` a proposal renders from. An empty `surfaces` list
means the three defaults; a non-empty one replaces them, so nothing is ever
registered silently.

## Behaviour

```bash
abcd identity --json # the block and every surface's verdict; exit 0
abcd identity render # a unified diff per drifted surface; writes nothing
abcd identity init … # record the block and the pointer to it
```

Comparison is by normalised containment: markup, dashes, line wrapping, and case
are folded away, so a tagline bolded mid-sentence or wrapped across two lines is
not drift, while a reworded one is. A drifted surface reports the file, the line,
the exact text it says, and the canonical line it should carry.

**Autonomous rewriting is permanently out of scope.** `render` proposes; the
maintainer adopts. A deliberate change of positioning is an edit to the block,
after which the same proposal flow chases the surfaces.

`init` never re-interviews a repo that already has a block — it adopts it —
and refuses to repoint a registry that is already committed.

## The check

The drift check runs as the `identity-positioning` rule on every `abcd audit`,
Where-gated on a committed registry so an un-adopted repo is skipped rather than
failed. Its acceptance corpus is [`iss-143`](../../../work/issues/resolved/iss-143-tagline-three-variant-drift.md),
the recorded three-variant tagline drift this check exists to catch.

## References

- Plugin command: [`commands/abcd/identity.md`](../../../../commands/abcd/identity.md)
- Spec: [`spc-19`](../../specs/open/spc-19-your-repo-says-the-same-thing-about-itself-everywhere-becaus.md)
- Intent: [`itd-102`](../../intents/planned/itd-102-your-repo-says-the-same-thing-about-itself-everywhere-becaus.md)
- Onboarding consumer: [`15-prepare-this-repo.md`](15-prepare-this-repo.md)
- Conformance surface: [`16-audit.md`](16-audit.md)
3 changes: 2 additions & 1 deletion .abcd/development/brief/04-surfaces/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ The brief's user-facing command surface is the set enumerated below (not all are
| 13 | `/abcd:consult` | shipped | Consult the local sources corpus (`~/.abcd/sources`) and record source→decision provenance — host-delegated command, no Go verb | [`13-consult.md`](13-consult.md) |
| 14 | `/abcd:ingest` | shipped | Register a URL or document into the local sources corpus — host-delegated command, no Go verb | [`14-ingest.md`](14-ingest.md) |
| 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) |
| 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, identity positioning) — 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) |
| 19 | `/abcd:identity` | shipped | Repo positioning: the canonical identity block (title, tagline, pitch) and every rendered surface held to it. Bare renders the block and each surface's verdict; `render` prints the proposed correction as a unified diff and writes nothing (autonomous rewriting is permanently out of scope); `init` records the block and the `.abcd/positioning.json` pointer at onboarding, adopting an existing block rather than re-interviewing. The drift check itself runs as the `identity-positioning` rule on every audit, warn-tier and per-repo upgradeable to blocker (itd-102, spc-19) | [`19-identity.md`](19-identity.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-102
slug: your-repo-says-the-same-thing-about-itself-everywhere-becaus
spec_id: null
kind: null
spec_id: spc-19
kind: standalone
suggested_kind: null
reclassification_history: []
builds_on: []
Expand Down
51 changes: 51 additions & 0 deletions .abcd/development/release/surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,57 @@
}
]
},
{
"path": "abcd identity",
"hidden": false,
"flags": []
},
{
"path": "abcd identity init",
"hidden": false,
"flags": [
{
"name": "file",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
},
{
"name": "heading",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
},
{
"name": "pitch",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
},
{
"name": "tagline",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
},
{
"name": "title",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
}
]
},
{
"path": "abcd identity render",
"hidden": false,
"flags": []
},
{
"path": "abcd intent",
"hidden": false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
id: spc-19
slug: your-repo-says-the-same-thing-about-itself-everywhere-becaus
intent: itd-102
---
# your-repo-says-the-same-thing-about-itself-everywhere-becaus

## Summary

spc-19 delivers itd-102's repo-identity system: one canonical, parseable
markdown identity block (title, tagline, pitch), an onboarding interview that
writes it, and a deterministic positioning check that holds every registered
surface to it — highlighting the exact drifted line, warn-tier by default,
and never rewriting anything autonomously. 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)

- **The identity home is a parseable markdown block, not a structured file.**
Markdown stays the single source of truth; the repo's committed abcd
configuration records only where the block lives.
- **The check is warn-tier by default** — highlight, never gate — and
per-repo upgradeable to blocker. **Autonomous rewriting is permanently
out**: re-rendering from the block is always a proposed diff a human
adopts.

## Mechanism

### Identity block

The block is three fixed bullets under a recognisable heading, exactly the
shape the abcd-cli brief already carries (`.abcd/development/brief/01-product/README.md`, "Identity (canonical)"):

- `- **Title:** …` (required)
- `- **Tagline:** …` (required)
- `- **Pitch:** …` (optional at onboarding; title and tagline are required)

The parser accepts a multi-line pitch bullet. The block's location — file
path plus heading — is recorded in the repo's committed abcd configuration
alongside the other check configuration; abcd-cli's own configuration points
at the existing brief section unchanged (AC 4).

### Onboarding interview

The prepare flow asks once: title, tagline, pitch (pitch may be skipped).
The host conducts the interview (the prepare skill carries the wording); the
binary scaffolds the block into the recorded location and the pointer into
the configuration. A repo that already has a block is never re-interviewed —
onboarding detects and registers it instead.

### Positioning check

A deterministic check, run by the audit verb, compares each registered
surface against the block:

- **Default registered surfaces (three):** the README strapline, the
plugin/package manifest description field, and the conventions file's
(AGENTS.md) opening line. Further surfaces are registrable per-repo in the
same configuration; none are registered silently.
- A diverging surface yields a warn-tier finding naming the file and the
exact drifted line, and quoting the canonical line it should carry.
Per-repo configuration may upgrade the family to blocker.
- **Acceptance corpus:** iss-143's three-variant tagline drift — the
recorded real instance this check exists to catch. The check's fixtures
reproduce that drift shape and must all be caught.

### Re-render as a proposal

`abcd identity render` (verb name final at implementation; the behaviour is
the contract) prints the proposed corrected surface content as a diff for
the human to adopt. It writes nothing. A deliberate identity change is an
edit to the block itself, after which the same proposal flow chases the
surfaces.

### Wiring

Both planes at delivery: the audit verb's check and the identity verbs reach
the CLI and the plugin markdown surface; the prepare skill gains the
interview step. abcd-cli's own audit runs the check against its brief block
and passes clean (or its findings are real drift, fixed by re-render or
recorded change — never by weakening the check).

## Acceptance-criteria mapping

- AC 1 (interview lands a parseable fixed-shape block; config records the
location; markdown is the source of truth) → Identity block + Onboarding
interview.
- AC 2 (divergence yields a warn finding naming the exact line; per-repo
upgrade to blocker) → Positioning check.
- AC 3 (never rewrites autonomously; re-render is a proposed diff) →
Re-render as a proposal.
- AC 4 (abcd-cli points at the existing brief Identity section unchanged;
iss-143 is the acceptance corpus) → Identity block + Positioning check.

## Out of scope

- Registering surfaces beyond the canonical three by default (per-repo
addition exists; defaults stay at three).
- Any autonomous write to a rendered surface, under any flag.
- Interview wording beyond the prepare skill's text (host-side, adjustable
without a binary change).
34 changes: 34 additions & 0 deletions .abcd/positioning.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"schema_version": 1,
"block": {
"file": ".abcd/development/brief/01-product/README.md",
"heading": "Identity (canonical)"
},
"severity": "warn",
"surfaces": [
{
"id": "readme-strapline",
"files": ["README.md"],
"kind": "regexp",
"patterns": ["(?s)<p>(.*?)</p>"],
"requires": ["tagline"],
"template": "{tagline}"
},
{
"id": "manifest-description",
"files": [".claude-plugin/plugin.json"],
"kind": "json_field",
"field": "description",
"requires": ["tagline", "pitch"],
"template": "{tagline} {pitch}"
},
{
"id": "conventions-opening",
"files": ["AGENTS.md"],
"kind": "regexp",
"patterns": ["(?s)<!-- END ABCD -->\\n+(.*?)(?:\\n\\n|\\z)"],
"requires": ["tagline"],
"template": "{tagline}"
}
]
}
4 changes: 4 additions & 0 deletions .abcd/work/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -867,3 +867,7 @@ parallel-agent merge contention bites.
- 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.
- 2026-07-28: itd-102 spec minted as spc-16 (fourth live iss-80 instance this run); renumbered to spc-19 by hand.
- 2026-07-28 — itd-102 implementation: repo positioning lives in a NEW `internal/core/positioning` package, not `internal/core/identity` — that package is the git commit-author gate (`.abcd/config/identity.json` pin, pre-commit hook) and shares nothing with repo self-description but the English word; folding them would put two unrelated concerns behind one name. The user-facing verb stays `abcd identity` (spc-19). For the same collision the registry is `.abcd/positioning.json` at the `.abcd/` top level (beside docs-lint.json / record-lint.json / rules.json), NOT `.abcd/config/identity.json`. Rejected: extending the commit-identity package; naming the verb `abcd positioning` (spc-19 fixes the verb).
- 2026-07-28 — itd-102: surface comparison is normalised CONTAINMENT of each required block field (markup, dashes, wrapping, case folded; trailing sentence punctuation trimmed from the needle), not line equality. Equality would flag abcd's own three conforming surfaces, each of which carries the tagline in a different rendering (inside `<p>`, concatenated with the pitch in the manifest, bolded mid-sentence and line-wrapped in AGENTS.md); containment catches all three iss-143 variants while accepting all three current ones. Consequence: `identity render` fires only on drift, and its template render is a proposal to adopt by hand, not a byte-exact reproduction of conforming prose.
- 2026-07-28 — itd-102: no scaffold entry point existed in ahoy/prepare for a repo record block, so the verb family gained a minimal `identity init` (write path: block + pointer, atomic, adopts an existing block, refuses to repoint an adopted registry). `abcd launch scaffold` is release-machinery-specific and `ahoy install` writes only abcd's own plumbing; extending either would have widened its remit.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-149"
slug: "the-positioning-check-bounds-each-surface-read-to-1-mib-but"
severity: "minor"
category: "bug"
source: "agent-finding"
found_during: "itd-102-security-review"
found_at: "internal/core/positioning/config.go"
---

The positioning check bounds each surface read to 1 MiB but nothing bounds the number of surfaces a committed registry may declare, so one abcd audit run over a hostile repo can be made to hold an unbounded multiple of that cap. Detector: a Config.Validate test that refuses a registry declaring more than a fixed surface count.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-150"
slug: "a-committed-positioning-registry-may-name-a-surface-inside-g"
severity: "minor"
category: "bug"
source: "agent-finding"
found_during: "itd-102-security-review"
found_at: "internal/core/positioning/config.go"
---

A committed positioning registry may name a surface inside .git (ValidRelPath accepts .git/config and it is inside the containment root), so abcd identity --json quotes .git/config — including a credential-bearing remote URL — into SurfaceResult.Found. Pre-existing; not an escape, and the output reaches only the operator's own stdout. Detector: a Config.Validate test refusing surface candidates under .git/. Found during the itd-102 containment review.
Loading