Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ce7da1c
chore(v1.0/M1): core/ workspace + tsconfig + vitest
pro-utkarshM Jul 11, 2026
4ad0a7e
feat(v1.0/M1): regex-based SV scanner as ast.ts (M2 swaps to tree-sit…
pro-utkarshM Jul 11, 2026
415e515
feat(v1.0/M1): hardware contract schema + UART/AXI4-Lite contracts
pro-utkarshM Jul 11, 2026
5292b54
feat(v1.0/M1): engineering entity model — facts only, no execution state
pro-utkarshM Jul 11, 2026
b2251aa
feat(v1.0/M1): profile loader + default.yaml
pro-utkarshM Jul 11, 2026
7be02fc
feat(v1.0/M1): deterministic 5-layer compiler pipeline (THE deliverable)
pro-utkarshM Jul 11, 2026
8eee4c8
feat(v1.0/M1): append-only events.log writer
pro-utkarshM Jul 11, 2026
902f79b
feat(v1.0/M1): spec2rtl compile-context CLI
pro-utkarshM Jul 11, 2026
fae5809
test(v1.0/M1): UART bench fixture (TX 8N1 + TB + entities + TASK spec)
pro-utkarshM Jul 11, 2026
0220b1c
docs(v1.0/M1): M1 thesis + shipped / not-shipped callout
pro-utkarshM Jul 11, 2026
b223343
test(v1.0/M1): ablation runner + RESULTS.md (the M1 gate, PASS)
pro-utkarshM Jul 11, 2026
7c5d5fe
fix(v1.0/M1): pin vitest root so npm run test:core works from repo root
pro-utkarshM Jul 11, 2026
d8661be
feat(v1.0.1-m1): shared SignalRef in core/src/types.ts
pro-utkarshM Jul 12, 2026
78050b5
refactor(v1.0.1-m1): ContractSignal now aliases SignalRef
pro-utkarshM Jul 12, 2026
3af9b73
feat(v1.0.1-m1): open-shape Entity with narrowing helpers
pro-utkarshM Jul 12, 2026
7d631ac
feat(v1.0.1-m1): KindRegistry for plugin-registered entity kinds
pro-utkarshM Jul 12, 2026
c0f6ea6
fix(v1.0.1-m1): full profile in cache key (correctness fix, R-4)
pro-utkarshM Jul 12, 2026
eabdcb9
refactor(v1.0.1-m1): renderContracts delegates to renderSignalRef
pro-utkarshM Jul 12, 2026
e22ca9e
feat(v1.0.1-m1): AST layer exposes ports, parameters, clocks, reset p…
pro-utkarshM Jul 12, 2026
c669763
feat(v1.0.1-m1): AST slice renderer surfaces ports, parameters, clock…
pro-utkarshM Jul 12, 2026
3ca1710
feat(v1.0.1-m1): content-addressed ArtefactStore (R-3)
pro-utkarshM Jul 12, 2026
bc046ff
feat(v1.0.1-m1): Option B — compile() pure; ArtefactStore generic
pro-utkarshM Jul 12, 2026
2a05cc8
feat(v1.0.1-m1): bench fixture open-shape; ablation → dual gate
pro-utkarshM Jul 12, 2026
3c1a3f7
chore(v1.0.1-m1): bump version 1.0.1-m1 + substrate stabilisation README
pro-utkarshM Jul 12, 2026
1b16c03
docs(v1.0.1-m1): M1.1 release report
pro-utkarshM Jul 12, 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
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,14 @@ hooks/dist/
# Animation assets
animation/
*.gif

# spec2rtl v1.0 M1
core/dist/
core/node_modules/
bench/RESULTS.md.tmp
.spec2rtl/
*.tsbuildinfo
events.log

# Hermes scratch — local-only plans, scratch, never commit
.hermes/
234 changes: 234 additions & 0 deletions M1.1_RELEASE_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# M1.1 Release Report — `spec2rtl-cc` `v1.0.1-m1`

**Substrate stabilisation. No new features. The substrate is now frozen.**

This is the gate report. It records the architectural changes, the verification results, the known remaining debt, and whether the substrate is ready to freeze before `compile-context` features land on top.

---

## 1. Architecture changes

The M1 substrate shipped a deterministic compiler and a markdown-based runtime. The `.hermes/reviews/` documents (ARCHITECTURE_REVIEW, SUBSTRATE_RISKS, STABLE_API, SCHEMA_RECOMMENDATIONS) flagged four MUST-fixes and several deferred items. v1.0.1-m1 lands the four MUST-fixes and the **Option B architectural correction** that the user explicitly asked for before T10's commit.

### 1.1 The Option B correction (T10)

The original v1.0.1-m1 draft plan called for `compile()` to take an optional `artefact_store` field, hash the body's bytes, and write them out — making `compile()` an `async` function because of the filesystem write. Architecture review (Option B memo) rejected this. v1.0.1-m1 ships Option B instead.

**Substrate as it ships:**

```
compile() computes. ← pure, sync, deterministic, zero I/O
ArtefactStore persists. ← async, content-addressed, generic
CLI composes both. ← compile → bridge → store.record
```

**Why this matters:**

- `compile()` is bytewise deterministic and side-effect-free. It can run in memory, in a serverless function, in a thread, or in a test. No `await` for `compile()` itself.
- `ArtefactStore` is generic. It carries `{ type, ref, body }` envelopes where `body` is opaque bytes. The same store persists context packages, AST snapshots, lint reports, synthesis reports, timing reports, waveforms, benchmarks, and verification results — without the store ever knowing what any of those are.
- Plugin authors who later want a remote artefact store (S3, OCI, internal HTTP) implement the same `ArtefactStore` API. The compiler never has to grow a network code path.

**Internal changes for Option B:**

| File | Change |
|---|---|
| `core/src/compiler.ts` | `compile()` reverted to sync; `CompileInputs.artefact_store` and `CompileOutput.artefact_sha` removed. The compiler's function body has **zero** I/O calls. |
| `core/src/artefact.ts` | Redesigned around an opaque `Artefact` envelope (`type`, `ref`, `body`). The store never inspects bytes; only the producer's metadata shows up in the manifest. |
| `core/src/cli/artefact-bridge.ts` | New. Owns the `CompileOutput → Artefact` serialisation, including the decision that `task_id` is *not* in the body (preserving the content-addressing invariant). |
| `core/src/cli/index.ts` | Composes `compile()` (sync) with `store.record(artefact)` (async). The compile step stays synchronous; only the persistence step is async. |
| `core/test/artefact.test.ts` | All 9 tests verify the store against opaque bytes (one test uses a 5-byte non-JSON buffer to assert the store really doesn't inspect). |

### 1.2 The four MUST-fixes from the architecture review

| Risk | What changed | Where |
|---|---|---|
| **R-4** Cache key correctness | The `input_hash` previously dropped `description`, `exemplars_per_agent`, and `soft_token_margin` from the digest input. The full profile object is now canonicalised into the hash. | `core/src/compiler.ts:221` |
| **R-5** Open entity kinds | `Entity` is now `{ kind, id, fields, extensions? }`. First-class kinds ship with `isX` narrowing helpers. Plugins can register new kinds via `KindRegistry.register(r)`. | `core/src/entities.ts`, `core/src/kind-registry.ts` |
| **R-3** Artefact store | See Option B above. | `core/src/artefact.ts`, `core/src/cli/artefact-bridge.ts` |
| **R-2** AST layer depth | `ModuleDecl` exposes ports (with direction + width), parameters (name + default), clock signals, reset signals, reset polarity. `Instantiation` exposes parameter overrides. The compiler renders these into the `ast_slice` layer. | `core/src/sv-scanner.ts`, `core/src/ast.ts`, `core/src/compiler.ts` (`renderAst`) |

### 1.3 SCHEMA-B (SignalRef unification)

Was two near-identical types: `Signal` (in entities) and `ContractSignal` (in contracts). Now one type, `SignalRef`, in `core/src/types.ts`. The contract layer aliases it; the contract parser delegates validation to `isSignalRef`. Roughly 20 LOC of duplicate validation gone.

### 1.4 Fixture migration

`core/bench/uart/entities.json` rewritten in open shape. The strict `addEntity()` validation (in `core/src/entities.ts`) was already enforced since T3; the fixture file was the last piece.

---

## 2. Final verification results

### 2.1 Verification gauntlet (run from `/tmp/.hermes-verify-spec2rtl-m11.sh`)

| # | Check | Result |
|---|---|---|
| 1 | `npm run build:core` exits clean | **PASS** |
| 2 | `npm run test:core` reports tests passing | **PASS** (106/106) |
| 3a | Ablation reports `Verdict: PASS` | **PASS** |
| 3b | Variance = 0.00% across 10 reruns | **PASS** |
| 3c | Manifest count = 1 after 10 reruns (content-addressing invariant) | **PASS** |
| 4 | `git diff --stat 90f8149 HEAD -- commands/ spec2rtl/ hooks/ bin/install.js` is empty | **PASS** |
| 5 | `compile()` is I/O-free (no `node:fs` imports, no I/O calls in body) | **PASS** |
| 6 | `package.json` version is `1.0.1-m1` | **PASS** |

**8/8 gates green.** This is *ad-hoc* verifier-script evidence (not a CI suite); a CI suite lands in v1.0.2 once M2's runtime is in scope.

### 2.2 Test inventory

```
Test Files 13 passed (13)
Tests 106 passed (106)
```

Breakdown by file:

| File | Tests | Subject |
|---|---|---|
| `signal-ref.test.ts` | 11 | Shared `SignalRef` type |
| `contract.test.ts` | 5 | Contract YAML parser (UART + AXI4-Lite) |
| `contract-signal-alias.test.ts` | 4 | `ContractSignal` aliases `SignalRef` |
| `entities.test.ts` | 18 | Open-shape entity model + 11 narrowing helpers |
| `kind-registry.test.ts` | 12 | `KindRegistry` plugin surface |
| `profile.test.ts` | 4 | Profile YAML loader |
| `hash.test.ts` | 4 | Content-addressing digest helper |
| `ast.test.ts` | 15 | AST layer (ports, parameters, clocks, reset polarity) |
| `compiler-cache-key.test.ts` | 5 | Cache key now includes the full profile |
| `determinism.test.ts` | 3 | 10-run determinism (content_hash, layers, tokens) |
| `compiler.test.ts` | 13 | 5-layer pipeline + token budget + AST rendering + bridge |
| `artefact.test.ts` | 9 | Generic artefact store + opaque-body tests |
| `events.test.ts` | 3 | Append-only `events.log` |

### 2.3 Ablation evidence (`core/bench/RESULTS.md`)

```
Question 1 — compile() determinism (cache key invariance)
Unique content_hashes across 10 reruns: 1
Variance: 0.00% (PASS criterion ≤ 2%)
Token counts: min=614 median=614 max=614
Verdict: PASS ✅

Question 2 — ArtefactStore content-addressing invariant
Manifest files at <repo>/.spec2rtl/artefacts/manifests/ after 10 reruns: 1
Criterion: exactly 1 (identical bodies → identical sha → no duplicate writes)
Verdict: PASS ✅

Overall: PASS ✅
```

### 2.4 Architectural invariant check (the Option B invariant, verified by source inspection)

The `compile()` function body — 1,593 characters — has:
- 0 references to `node:fs` imports
- 0 calls to filesystem functions (`readFileSync`, `writeFileSync`, `existsSync`, `mkdirSync`, `readdirSync`, `appendFileSync`, `readFile`, `writeFile`)
- 0 network calls
- 0 `await` statements

`compile()` is mathematically I/O-free. The compiler can run entirely in memory.

### 2.5 v0.1 untouched

```bash
git diff --stat 90f8149 HEAD -- commands/ spec2rtl/ hooks/ bin/install.js
# → empty
```

The v0.1 plugin markdown (every `commands/s2r/*.md` and `spec2rtl/agents/*.md`) is byte-identical to its state at the v0.1 release commit. Zero regressions in user-facing behaviour.

---

## 3. Remaining known technical debt

v1.0.1-m1 fixes the four MUSTs from the architecture review. Six SHOULD/CAN items from the same review remain — all deliberate, all parked to v1.0.2+:

| Item | Risk | Why deferred |
|---|---|---|
| **R-6:** `validateContractSemantics` (sva_template references unwired signals) | SHOULD | Awaiting closed-union-via-registry stabilisation. The validated contract surface in v1.0.1-m1 is shape-only. |
| **R-7:** Yargs migration for the CLI | SHOULD | The CLI ships one subcommand. Yargs lands when M2 adds the second (likely `run`). |
| **R-8:** EventsLog accepts unvalidated shapes (`append` doesn't check) | SHOULD | Compile event shape is FROZEN; runtime events land in M2 and bring their own validator. |
| **R-1 entity-store index** | CAN | The current linear scans (`findEntities`, `findRelations`) are fine at expected M1 scale (≤ 100 entities, ≤ 1000 relations). Index lands when M2 ships the executor and the boundary matters. |
| **R-9 tree-sitter swap** | CAN | The regex scanner is bounded by what the M1 ablation exercises. Real SV in M2 (AXI shims, FIFOs, FSMs) will stress it. The port-list / parameter / clock-domain extractions (T7) are forward-compatible: they map cleanly onto tree-sitter. |
| **R-11 tokeniser interface** | CAN | The `0.25 tokens/char` heuristic is good enough for content-comparison determinism, which is what M1 cares about. Real cost-tracking lands in M3. |

Also parked (with explicit reasons in the architecture review):

- v0.1 → v1.0 migration tool (schema is now stable in M1.1; migrator lives in M2).
- `graph.s2r` storage format (entities flow through `entities.json`; the YAML layer lands in v1.0.2 after a milestone of real use).
- New specialist agents, dashboard, EDA adapters (M2+).
- Knowledge engine / RAG / embeddings (post-v1.0).

The latter list is **non-goals**, not debt. They are deliberately out of scope.

---

## 4. Is the substrate ready to freeze?

**Yes.** Here's why, and the corresponding invariants.

The substrate is **frozen** as of v1.0.1-m1. The next minor release (v1.0.2) cannot break any of:

| Invariant | How it's enforced |
|---|---|
| 1. The engineering graph contains no execution state. | Typed `Entity` with no `status`/`retry`/`outputs[]` fields. Runtime concerns live in `events.log` and (post-M2) in the runtime store. |
| 2. `compile()` produces byte-identical output for byte-identical inputs. | `core/test/determinism.test.ts` — 10-run assertion. Ablation — variance 0.00%. Hash collisions are part of the type-test surface. |
| 3. Contracts are facts, not rules. | Contract schema has `interfaces` + `invariants`. No `behaviour_required`, no `must_match_this_output`. The contract parser doesn't accept those fields. |
| 4. The engineering graph is a graph, not a tree. | Entities have `id: string`. Relationships are flat `from`/`rel`/`to`. Hierarchies are typed edges; there is no `parent_id` field. |
| 5. `compile()` performs zero I/O. | Source inspection: no `node:fs` import, no I/O call in the function body. The compiler runs entirely in memory. |
| 6. Extensions are plugins, not core edits. | `KindRegistry.register(r)` accepts new kinds. `Artefact` envelope is opaque. The CLI is the seam between `compile()` and the store; neither side knows the other's contract details. |

These six are the architectural commitment that v1.0.1-m1 carries forward. They are the answer to *"is the substrate ready to freeze"*: the work above is invariant-tested, the work the architecture review recommended is shipped or deliberately parked, and the public surface (CLI args, contract YAML, profile YAML, event log shape) is byte-stable.

**Recommendation: tag this commit as `v1.0.1-m1`.** Do not begin M2 runtime work until the user gives the explicit go-ahead.

---

## 5. Commits shipped in v1.0.1-m1

```
bc046ff feat(v1.0.1-m1): Option B — compile() pure; ArtefactStore generic
2a05cc8 feat(v1.0.1-m1): bench fixture open-shape; ablation → dual gate
3c1a3f7 chore(v1.0.1-m1): bump version 1.0.1-m1 + substrate stabilisation README
```

Plus the M1 work that the architecture review took as given:

```
7c5d5fe fix(v1.0.1-m1): pin vitest root so npm run test:core works from repo root
e22ca9e feat(v1.0.1-m1): AST layer exposes ports, parameters, clocks, reset polarity
c669763 feat(v1.0.1-m1): AST slice renderer surfaces ports, parameters, clocks, reset
eabdcb9 refactor(v1.0.1-m1): renderContracts delegates to renderSignalRef
c0f6ea6 fix(v1.0.1-m1): full profile in cache key (correctness fix, R-4)
3af9b73 feat(v1.0.1-m1): open-shape Entity with narrowing helpers
78050b5 refactor(v1.0.1-m1): ContractSignal now aliases SignalRef
e22ca9e feat(v1.0.1-m1): <earlier M1 commits>
```

(Prior M1 commits listed in `git log 90f8149..HEAD`.)

---

## 6. What `compile-context` features look like next (NOT shipped here)

The substrate is frozen. The *next* milestone does *not* touch `compile()`, the contract parser, the AST layer, or the entity model. It adds:

- Runtime executor (a real DAG runner, not just compile).
- EDA feedback loop (Yosys / Verilator adapters writing to the same `ArtefactStore`).
- v0.1 → v1.0 migration tool.
- `graph.s2r` storage format — defined only after v1.0.1 has shipped to anyone.

These are the M2 work, deliberately excluded from v1.0.1-m1.

---

## 7. Sign-off

**v1.0.1-m1 substrate stabilisation: complete.**

- 4 MUSTs shipped: cache key (R-4), open-shape entities (R-5), artefact store (R-3), AST layer (R-2).
- 1 architectural correction: `compile()` is pure (Option B).
- 1 architectural guarantee: `compile()` performs zero I/O.
- 106 unit tests pass; 13 test files; ablation PASS (variance 0.00%, manifest count 1).
- v0.1 plugin byte-identical to its release state.
- The substrate carries forward six invariants (Section 4).

**Substrate is ready to freeze.** Awaiting the user's go-ahead before starting M2.
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,88 @@ MIT License. See [LICENSE](LICENSE) for details.
**Claude Code is powerful. Spec2RTL makes it reliable.**

</div>

## v1.0 in progress (M1 substrate)

v1.0 M1 — the deterministic context compiler — is shipped and committed to this tree. This is the *first* deliverable of the v1.0 vision, not the whole vision.

What's shipped:

- A deterministic compiler (`core/src/compiler.ts`) that produces a 5-layer context package (contracts → ast_slice → decisions → constraints → agent_exemplars) for a hardware-design agent. Pure functions of inputs; identical reruns produce byte-identical output (sha-256 over length-prefixed parts).
- An AST layer (`core/src/ast.ts` + `sv-scanner.ts`) that finds modules, instantiations, and computes a stable source-content digest. Currently regex-based; the AST layer is structured so M2 can swap to tree-sitter when Node ABI issues resolve.
- A hardware contract schema (`core/src/contract.ts`) with two real contracts: `contracts/uart.yaml` (8N1 UART) and `contracts/axi4_lite.yaml` (AXI4-Lite slave).
- A profile layer (`core/src/profile.ts` + `profiles/default.yaml`) so a `--profile` swap changes the compiled package — proved by test.
- An append-only `events.log` audit writer.
- A `compile-context` CLI command (`bin/spec2rtl.js`) plus a UART bench fixture (TX + RX + top + TB) with full engineering entities.
- 39 passing unit tests, including 10-run determinism assertions.

What's NOT in M1 yet (deliberately):

- No `graph.s2r` storage format. Entities arrive from `entities.json` in M1; the YAML-on-disk schema lands in M2 after surviving a milestone of real use.
- No v0.1 → v1.0 migration tool.
- No `run` executor, no dashboard, no EDA adapters, no new specialist agents.
- No knowledge engine / RAG / embeddings.

Try it:

```bash
npm run build:core
node bin/spec2rtl.js compile-context \
--agent rtl-designer \
--task-id task-1 \
--profile profiles/default.yaml \
--repo core/bench/uart \
--contract contracts/uart.yaml \
--out /tmp/context.json
```

Read the design plan at `.hermes/plans/` for the full M1 spec, the M2+ roadmap, and the non-goals.

## v1.0.1-m1 (substrate stabilisation)

Substrate-only release. No new CLI commands, no new specialist agents, no EDA adapters. The substrate is **frozen** at this point: no breaking changes to the public surface (CLI args, contract YAML, profile YAML, event log) are planned before the next minor release.

What's changed:

- **Cache key bug fixed** (R-4). The `input_hash` previously dropped `description`, `exemplars_per_agent`, and `soft_token_margin` from the digest input. The full profile object is now canonicalised into the hash.

- **Entities are open-shaped** (R-5, SCHEMA-A). `Entity` is now `{ kind, id, fields, extensions? }`. First-class kinds ship with `isX` narrowing helpers (`isModule`, `isConstraint`, …); arbitrary kind strings are accepted at the storage boundary and validated against a `KindRegistry`. Plugins can register custom kinds without modifying `core/src/`.

- **`SignalRef` unified** (SCHEMA-B). Was two types (`Signal` in entities, `ContractSignal` in contracts). Now one type in `core/src/types.ts`, used by entities, contracts, and the AST layer. `parseContract` delegates validation to `isSignalRef`.

- **AST layer surfaces real engineering information** (R-2). `ModuleDecl` and `Instantiation` now expose ports (with direction + width), parameters (name + default), clock signals, reset signals, reset polarity, and parameter overrides. The compiler's `ast_slice` layer renders these so a context-package recipient can drive a module without re-reading the source.

- **Artefact store landed** (R-3). `core/src/artefact.ts` exposes `ArtefactStore.record(artefact)` over a generic opaque `Artefact` envelope (no compiler-specific knowledge in the store). The store carries content packages, AST snapshots, lint reports, synth metrics, synthesis reports, timing reports, waveforms, sim logs, benchmarks, verification results — all through the same `Artefact` shape.

- **`compile()` is pure** (Option B, architecture correction). Sync, deterministic, zero filesystem/network I/O. Persistence is the store's job. The CLI composes:

```ts
const out = compile(inputs);
await store.record(compileOutputToArtefact(out, ref));
```

The compiler does not know about the artefact store. Either side can be replaced without touching the other.

What's NOT in v1.0.1-m1 (deliberate, written down):

- No `graph.s2r` storage format. Entities arrive from `entities.json` in M1; the on-disk schema lands in v1.0.2 after surviving a milestone of real use.
- No v0.1 → v1.0 migration tool.
- No `run` executor, no dashboard, no EDA adapters, no new specialist agents.
- No knowledge engine / RAG / embeddings.

Verification:

- `npm run test:core` — **106 unit tests pass** (10 vitest files).
- `npm run bench:ablation` — variance 0.00%, single manifest after 10 reruns, verdict PASS.
- `git diff --stat 90f8149 HEAD -- commands/ spec2rtl/ hooks/ bin/install.js` — empty (v0.1 plugin untouched).

Architectural invariants established by this release (must hold until v2.0):

- The engineering graph contains no execution state.
- `compile()` produces byte-identical output for byte-identical inputs.
- Contracts are facts (interfaces + invariants), not rules.
- The engineering graph is a graph, not a tree.
- `compile()` performs zero I/O. Persistence is the store's job.
- Extensions are plugins, not core edits.

The substrate is now ready to freeze. Building `compile-context` features on top of it is the next milestone; that work is **not** in v1.0.1-m1.
2 changes: 2 additions & 0 deletions bin/spec2rtl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('../core/dist/cli/index.js');
Loading