Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4889533
Slice 1: the ts-morph extractor and the one graph (plan 06)
darko-mijic Jun 10, 2026
ee65802
Post-review hardening (Codex adversarial pass): bin entry, default ro…
darko-mijic Jun 10, 2026
27c0eed
Slice 2: generic anchors and the anchored layer (plan 07)
darko-mijic Jun 10, 2026
049f79f
Post-review hardening (Codex adversarial pass): loud authored shape, …
darko-mijic Jun 10, 2026
aed2f96
Slice 3: the graph-validator gate (plan 08)
darko-mijic Jun 10, 2026
9e7cca1
Post-review hardening (Codex adversarial pass): kind-typed relation e…
darko-mijic Jun 10, 2026
129e7ce
Slice 4: the agent surface (the reader) and the Design Review (plan 09)
darko-mijic Jun 10, 2026
81ca7db
Post-review hardening (Codex adversarial pass): the shared resolving-…
darko-mijic Jun 11, 2026
59bf926
Slice 5: polish — the CLI surface resolved, one diagnostic rule, the …
darko-mijic Jun 11, 2026
35a9831
Post-review hardening (Slice-5 review pass): name the bar where it is…
darko-mijic Jun 11, 2026
7432154
Post-review hardening (extractor adversarial pass): parse-broken file…
darko-mijic Jun 11, 2026
067ca94
Fail-closed example-verifier decode: the declared-claim contract row …
darko-mijic Jun 11, 2026
35cba59
CLI failure paths pinned: the --check-clean divergence branches and t…
darko-mijic Jun 11, 2026
ccb98bd
Post-review hardening (test-coverage pass): mutation-proof evidence a…
darko-mijic Jun 11, 2026
66076a0
Doc-fidelity repair: the concept docs match the landed surface — bran…
darko-mijic Jun 11, 2026
e0ca78c
JTBD drift repair against the language base: readiness is stated, nev…
darko-mijic Jun 11, 2026
22a4d24
Post-review hardening (integration pass): the anchor flavor is a seco…
darko-mijic Jun 11, 2026
61b54f9
Post-review hardening (verification pass): the shared enabled-example…
darko-mijic Jun 11, 2026
7ace8cd
Post-review hardening (fix-verification pass): the adoption-arc gloss…
darko-mijic Jun 11, 2026
97ac31c
CI repair: recovery-path artifact removal never throws on Node 20 — f…
darko-mijic Jun 11, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ jobs:

- name: Build
run: npm run build

- name: Validate and view the example (the conformance + honesty gate + the derived Design Review)
run: npm run check:example
9 changes: 9 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@ plans/**

# Archived review artifacts - verbatim historical records, not format-policed
reviews/

# The golden graph oracle carries the serializer's exact bytes (serialize.ts owns all output
# bytes); reformatting it would break the byte-compare against extractor output
test/fixtures/checkout-v1/expected-graph.json

# The golden Design Review carries the renderer's exact bytes (design-review.ts owns them);
# same rule for the generated artifacts themselves — derived output is never format-policed
test/fixtures/checkout-v1/expected-design-review/
generated/
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ Progressive disclosure — start at the top, follow the pointers down.

The MVP proves the founding principle on **one** bounded context — Order Management, `pack:checkout-v1`, ~8–12
specs (`spec:orders.create-order` + a few child scenarios/rules + 1 NFR + the parent `spec:orders.order-management`
behavior + the pack); **not** the whole checkout flow. It is built as thin **end-to-end slices on the Phase 0
behavior + the pack); **not** the whole checkout flow. The worked example lives at `examples/checkout-v1`
(documented walkthrough in its README). It is built as thin **end-to-end slices on the Phase 0
foundation**. `docs/concept/07` is the slice roadmap; **`plans/` holds the live, canonical per-session plan** —
read it before writing code.

Expand All @@ -67,7 +68,7 @@ read it before writing code.
| **2** | Generic anchors + implementation binding + spec↔test linkage → `verifies` edges (`anchored` claim). |
| **3** | Core conformance + honesty checks (referential integrity · duplicate IDs · honest readiness against the floor · orphans · `verifies` linkage · authoring-shape honesty) + the CI gate. |
| **4** | The agent surface (the `reader` — entry adapters + impact) + the Design Review / one generated read-only view — both fully derived. |
| **5** | Polish: the `sdp` CLI (`build` · `validate` · maybe `explain`/`search`), error messages, the documented example, a clean-repo determinism test. |
| **5** | Polish: the CLI surface resolved (`build` · `validate` · `view`; `explain`/`search` below the second-caller bar), one diagnostic rendering rule, the documented example walkthrough, the clean-repo determinism test. |

> **Tracer-bullet discipline.** Author the example specs and anchored code *first*, so the DSL and extractor are
> forced to be usable before they are finished. If the example doesn't typecheck, fix the DSL — not the example.
Expand Down
4 changes: 2 additions & 2 deletions docs/concept/00-vision-scope-and-mvp-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The MVP proves the founding principle — *one regenerable graph derived from th

- **Typed `Spec` DSL** in TypeScript: `spec()`, `pack()`, sections, relations, the three descriptors (`kind` · `altitude` · `readiness`), enrichment-in-place, refinement into child specs. One primitive, stable IDs, no artifact migration.
- **Generic source anchors** that bind any code location (class, function, route, module) to a spec ID + optional component. Framework-neutral — *how* the runtime is wired is an extractor detail, not a job.
- **The `ts-morph` one-graph extractor**: one canonical, regenerable graph from `/specs/**/*.sdp.ts` + anchors + basic test discovery, with honest edge `claim`s.
- **The `ts-morph` one-graph extractor**: one canonical, regenerable graph from every `*.sdp.ts` under the extraction root (conventionally `/specs/`) + anchors + basic test discovery, with honest edge `claim`s.
- **Core conformance + honesty checks**: referential integrity (no dangling ID references), duplicate-ID detection, honest readiness (against the readiness floor), orphan detection, `verifies` linkage from tests to specs, and authoring-shape honesty (no hand-authored derived edges or delivery facts). CI fails on errors.
- **One generated read-only view**: a derived, regenerable human-readable projection (spec tree + per-spec detail with readiness, relations, impact list, source links).
- **Bidirectional spec↔test trace**: query "what verifies this spec?" and "what does this test cover?" from the graph.
Expand Down Expand Up @@ -79,7 +79,7 @@ Everything below is real, designed-for, and out of the first slice. Each is cut
| **MCP surface** (designed-in, deferred build) | The integration surface for *user-facing apps* — one more projection of the one read model, **distinct from the agent surface** (agents *script*; apps *integrate*). Designed-in, but its build is deferred and its shape is a fresh design — not an afterthought, and not carried over from any prior implementation. |
| **Architecture enforcement** (forbidden-dependency tiers, ts-arch tests, custom rules) | A whole validation competency. MVP keeps only core graph invariants. |
| **Incremental builds / caching / sharding** | Full rebuild is fine at MVP scale. |
| **Full CLI** (evidence, migrate, ai subcommands) | MVP CLI is `sdp build` and `sdp validate` (plus maybe a simple `explain`/`search`). |
| **Full CLI** (evidence, migrate, ai subcommands) | MVP CLI is `sdp build` · `sdp validate` · `sdp view`. `explain`/`search` stay below the second-caller bar (`06` §3): the agent scripts the reader, the human reads the Design Review — a terminal verb over the same joins would render the same information a third time. Revisit on measured pain (`07` §5). |

Three stances shape the deferrals above: the graph is exposed to agents from day one as the **agent surface** (a typed graph the agent scripts, not a verb wall); there is no patch loop (edits route intent → agent → git); and verification is structural — test run verdicts are CI's, never ingested.

Expand Down
6 changes: 3 additions & 3 deletions docs/concept/01-founding-principles-and-invariants.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ If a successor kept only the essentials, the design stands or falls on these. Ea
### P4 — One enrichable primitive
**Principle · CORE.** A single object shape — the `Spec` — carries every durable delivery artifact; the familiar delivery nouns (Use Case, NFR, Decision Record; Epic, Feature, Story) are **named coordinates** on it, not separate artifact types. Realization is a **derived delivery fact** (`implemented` / `has-verifier` / `observed`), never an authored artifact. Refinement is *enrichment of the same identified object*, never migration into a different artifact type (which loses information). This is the central reframing of the whole design. *(The concrete field set is Representation; the "one primitive, enrich not convert" thesis is the Principle.)* See [`02-core-model.md`](./02-core-model.md).

**Corollary — significance governs detail, not a tier.** Because refinement is enrichment of one object — not promotion through fixed readiness stages — a spec carries detail only where the work is *architecturally significant or genuinely novel*. The Nth instance of an established shape (a CRUD endpoint, a standard codec, a barrel) earns a **reference, not a re-derivation**; padding a spec to "fill a level" destroys signal and is pure cleanup debt. Readiness (P8) is **stated by the author and checked against a floor — never a quota of artifacts to produce.** Fixed tiers pressure an author to fill the tier rather than the need — AI authors especially, which replicate the prevailing detail level even where it does not fit. *Enrich what matters; reference what is standard; pad nothing.*
**Corollary — significance governs detail, not a tier.** Because refinement is enrichment of one object — not advancement through fixed readiness stages — a spec carries detail only where the work is *architecturally significant or genuinely novel*. The Nth instance of an established shape (a CRUD endpoint, a standard codec, a barrel) earns a **reference, not a re-derivation**; padding a spec to "fill a level" destroys signal and is pure cleanup debt. Readiness (P8) is **stated by the author and checked against a floor — never a quota of artifacts to produce.** Fixed tiers pressure an author to fill the tier rather than the need — AI authors especially, which replicate the prevailing detail level even where it does not fit. *Enrich what matters; reference what is standard; pad nothing.*

### P5 — Statically-extractable authoring
**Principle · CORE.** Spec and anchor source is restricted to static, side-effect-free literals — no loops, conditionals, computed IDs, interpolated IDs, IO, async, or imports of product code — so a static analyzer reifies it deterministically. Treat a spec file as "a JSON file that TypeScript happens to validate." This is the precondition for P3 on the authoring side. *(The specific allowed grammar and the choice of `ts-morph` are Representation; the requirement to be statically extractable is the Principle.)*
Expand Down Expand Up @@ -96,7 +96,7 @@ These named invariants sharpen the design.
**Principle · CORE.** One bad input never poisons the whole build. A non-static expression drops that one property and keeps the rest of the spec; an unresolved reference still serializes (with a sentinel) and surfaces as a validation error, rather than aborting the extractor. The pipeline degrades *locally*, not globally.

### L8 — Generated output is disposable
**Principle · CORE.** Everything under `generated/` is gitignored and regenerable; any consumer may delete it and rebuild. A single carve-out — a generated `spec-ids` union type consumed by `tsc` for early referential-integrity checks — is itself a known fragility (it must regenerate before `tsc`, or `tsc` lies), and is *optional*: the graph validator catches the same broken references at build time regardless. Treat the union as a convenience, not a load-bearing gate.
**Principle · CORE.** Everything under `generated/` is gitignored and regenerable; any consumer may delete it and rebuild. A single designed-for carve-out — a generated `spec-ids` union type consumed by `tsc` for early referential-integrity checks — would itself be a known fragility (it must regenerate before `tsc`, or `tsc` lies), and stays *optional and deferred* (the MVP ships without it): the graph validator catches the same broken references at build time regardless. Treat the union as a convenience, never a load-bearing gate.

### L9 — The `Spec` envelope is a stability contract; sections are the extension surface
**Principle · CORE.** The outer `Spec` shape is intentionally minimal and changes almost never. New capability arrives by **adding sections or enum members**, not by changing the top-level envelope. The envelope is the stability contract; sections are where the system grows.
Expand All @@ -114,7 +114,7 @@ These appear in the design and matter, but none is a Principle. Swapping any of
| Specific anchor syntaxes (decorator / JSDoc / anchor const) | P9/P10 (declared vs anchored) | CORE |
| Flat node/edge arrays (hierarchy as edges, not nesting) | P2 (one queryable read model) | CORE |
| The enum member sets on each descriptor | P8 (three descriptors) | CORE — reconciled in `02` |
| Generated `spec-ids` union for compile-time ref checks | early referential integrity | CORE (optional, L8) |
| Generated `spec-ids` union for compile-time ref checks | early referential integrity | ASPIRATIONAL (optional convenience, L8) |
| The runtime-composition adapter (Effect / Awilix / manual wiring) | "read architecture from live wiring" | ASPIRATIONAL |
| HTML / Web Components for the rich view | "rich, shareable read surface from the graph" | ASPIRATIONAL |
| Impact graph (derived import/symbol graph) | P10 corollary (impact + curation-assist) | ASPIRATIONAL (thin version may be MVP — `07`) |
Expand Down
24 changes: 12 additions & 12 deletions docs/concept/02-core-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Sections carry the detail. They are the **extension surface**: the system grows
| `verification` | mode (manual / reviewed / contract / executable) + criteria | A verifying test *existing and enabled* is the derived `has-verifier` delivery fact (§2), not an authored field here. Pass/fail is **not** in the graph — it is CI's, operational. |
| `ui` | references to component stories, design-tool nodes, visual baselines, accessibility status | **Aspirational.** Always links, never owns or renders. |

The `decision` section carries **no `status` field** (rejected by the typing law, MD-11): a decision's adoption arc is the envelope's `readiness` (raised → explored → written → ratified), checked against the floor like any spec; replacement is the `supersedes` relation; a *rejected* path is not a truth-spec at all — it lives in the chosen decision's `alternatives` / `consequences`. One concept, one place.
The `decision` section carries **no `status` field** (rejected by the typing law, MD-11): a decision's adoption arc is the envelope's `readiness` (`idea` raised → `scoped` explored → `defined` written → `ready` ratified), checked against the floor like any spec; replacement is the `supersedes` relation; a *rejected* path is not a truth-spec at all — it lives in the chosen decision's `alternatives` / `consequences`. One concept, one place.

### The typing law — which sections have typed shapes (MD-11)

Expand All @@ -161,7 +161,7 @@ Two laws make the duality safe (content-only sections — DECISIONS MD-10):
```ts
// idea
export const CreateOrder = spec({
id: "spec:orders.create-order",
id: specId("spec:orders.create-order"),
title: "Customer creates an order",
kind: "behavior",
altitude: "feature",
Expand All @@ -171,7 +171,7 @@ export const CreateOrder = spec({
outcome: "turn a valid cart into an order",
value: "customers can complete purchases",
},
relations: [refines("spec:orders.order-management")],
relations: [refines(specId("spec:orders.order-management"))],
});
```

Expand All @@ -180,7 +180,7 @@ Later, the *same* spec — same ID — gains sections and climbs readiness:
```ts
// ready — same object, enriched (no artifact migration)
export const CreateOrder = spec({
id: "spec:orders.create-order",
id: specId("spec:orders.create-order"),
title: "Customer creates an order",
kind: "behavior",
altitude: "feature",
Expand All @@ -192,8 +192,8 @@ export const CreateOrder = spec({
{ flavor: "performance", statement: "order creation is fast enough for checkout", target: "p95 < 300ms" },
],
relations: [
refines("spec:orders.order-management"),
decidedBy("spec:decisions.order-lifecycle"),
refines(specId("spec:orders.order-management")),
decidedBy(specId("spec:decisions.order-lifecycle")),
],
});
```
Expand All @@ -204,7 +204,7 @@ A low-altitude example becomes executable the same way — it is not a separate

```ts
export const ValidCartCreatesOrder = spec({
id: "spec:orders.create-order.valid-cart",
id: specId("spec:orders.create-order.valid-cart"),
title: "Valid cart creates an order",
kind: "example",
altitude: "story",
Expand All @@ -217,7 +217,7 @@ export const ValidCartCreatesOrder = spec({
}],
},
verification: { mode: "executable", criteria: ["an order row exists", "an OrderCreated event is published"] },
relations: [refines("spec:orders.create-order"), verifies("spec:orders.create-order")],
relations: [refines(specId("spec:orders.create-order")), verifies(specId("spec:orders.create-order"))],
});
```

Expand All @@ -238,10 +238,10 @@ A `Pack` clusters related specs (a feature initiative, a bounded slice) so a tea

```ts
export const CheckoutV1 = pack({
id: "pack:checkout-v1",
id: packId("pack:checkout-v1"),
title: "Checkout v1",
framing: "let customers complete purchases reliably — lifts conversion, cuts failed orders",
modelRefs: ["spec:checkout.glossary"], // → standalone kind:"model" specs; shared vocabulary is never inlined twice
modelRefs: [ref("spec:checkout.glossary")], // → standalone kind:"model" specs; shared vocabulary is never inlined twice
specs: [
ref("spec:orders.create-order"),
ref("spec:payments.authorize-payment"),
Expand Down Expand Up @@ -277,7 +277,7 @@ Namespaces in the MVP: `spec`, `pack`, `impl`, `api`, `test`, `component`, `doc`

`doc:` is reserved for a genuinely *external* document linked from a decision spec (e.g. `doc:adr-order-lifecycle`) — never for an in-system decision, which is a `spec:decisions.*` spec.

Code and specs link by these IDs *in strings*, never by import edges (P6) — which is the only thing that lets either side survive heavy refactoring. The cost of string IDs is typos; that is what referential-integrity checks exist to catch (`05`). The optional generated `spec-ids` union type pushes some of those checks to `tsc`, but it is a convenience, not a load-bearing gate (L8).
Code and specs link by these IDs *in strings*, never by import edges (P6) — which is the only thing that lets either side survive heavy refactoring. The cost of string IDs is typos; that is what referential-integrity checks exist to catch (`05`). An optional generated `spec-ids` union type could push some of those checks to `tsc` — a deferred convenience, never a load-bearing gate (L8).

> **IDs carry no history.** IDs are stable by convention and survive refactors; renaming one is a repo edit that git records. The graph does not bookkeep ID history (see `01`, git is the event log).

Expand All @@ -302,7 +302,7 @@ MVP relation vocabulary (a Representation; extensible):

Two notes carried from the language ratification: the verb forms align with UML where a standard stereotype exists (`refines` ≈ «refine», `dependsOn` ≈ UML *Dependency*, `decidedBy` ≈ «trace», `verifies` ≈ «verify») — adopted nouns, per the governing rubric. And `constrainedBy` / `decidedBy` are deliberately kept **distinct** from a generic `dependsOn`: "bounded by an NFR" and "shaped by a decision" are high-value, separately-queryable intents that a generic dependency edge would flatten.

> **`doc:` targets are a named deferral (MD-16).** The DSL's relation builders and `ref()` accept only `spec:` targets today, so `decidedBy` → an external `doc:` ADR is designed-for but not yet representable; until the need arrives, an external ADR is referenced from the decision spec's body, not by a typed edge. (The glossary's flagged-ambiguities entry and the notes in `src/ids.ts` / `src/model/relations.ts` carry the same flag.)
> **`doc:` targets are a named deferral (MD-16).** The DSL's relation builders and `ref()` accept only `spec:` targets, so `decidedBy` → an external `doc:` ADR is designed-for but not yet representable; until the need arrives, an external ADR is referenced from the decision spec's body, not by a typed edge. (The glossary's flagged-ambiguities entry and the notes in `src/ids.ts` / `src/model/relations.ts` carry the same flag.)

**Derived edges — never authored:**

Expand Down
Loading
Loading