diff --git a/CHANGELOG.md b/CHANGELOG.md index 211c311..04ce951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `integrations/graphify_import.py` (#80, PR #81): draft `status: inferred` + contracts from a [graphify](https://github.com/Graphify-Labs/graphify) + knowledge graph — the brownfield entry point. Signatures come from the + Python source via `ast`; deps only from EXTRACTED edges between selected + units. Repo-only tooling: no new MCP tools or CLI commands. The repo's own + extracted graph is committed at `graphify-out/graph.json`. + ## [0.4.0] - 2026-07-09 **Renamed: heddle → hashloom.** Two unrelated "heddle" MCP servers already diff --git a/CLAUDE.md b/CLAUDE.md index 6d540dc..cc5e9af 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,8 @@ documents "the entire surface"; if a change would add to it, stop and confirm. The repo is itself a hashloom project: `contracts/` holds contracts for the stable seams (the five `api.py` functions, the `contract.py` hashing trio, -`impl_hash`, `verification_key`, `HashloomError`, the `Store` Protocol). The +`impl_hash`, `verification_key`, `HashloomError`, the `Store` Protocol, and +the `LanguageAdapter` seam behind the per-extension adapters). The workflow from [docs/getting-started.md](docs/getting-started.md) applies here: - **The hashloom MCP server is project-configured** in [.mcp.json](.mcp.json) @@ -47,9 +48,10 @@ workflow from [docs/getting-started.md](docs/getting-started.md) applies here: `status: inferred`; the user flips it to `confirmed` on review. `hashloom status` lists the review queue. - **Do not contract churning interiors.** `remote.py`, `cache_server.py`, and - `shared.py` are deliberately uncontracted while the v0.3 hosted-store work - reshapes them; helpers (`tokens.py`, `project.py`) are weft. Pinning - interiors is the failure mode the README warns about. + `shared.py` are deliberately uncontracted while the hosted-store work (the + v0.5 theme, renumbered from v0.4 in the 0.4.0 rename) reshapes them; helpers + (`tokens.py`, `project.py`) are weft. Pinning interiors is the failure mode + the README warns about. - **The hashloom gate layers on the DoD — it never replaces it.** Full `uv run pytest` and the benchmark below remain the definition of done; hashloom's cached verify must not be the only thing vouching for hashloom. diff --git a/ISSUES.md b/ISSUES.md index f214f40..4ad00d6 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -3,8 +3,8 @@ Per the v0.1 spec: if a task isn't on a milestone, it's an issue. The repo now has a remote, so new follow-ups are filed on the GitHub tracker (the verification-model sharpening lives there as -[#18](https://github.com/davet47/heddle/issues/18) through -[#20](https://github.com/davet47/heddle/issues/20), prioritised in +[#18](https://github.com/davet47/hashloom/issues/18) through +[#20](https://github.com/davet47/hashloom/issues/20), prioritised in [ROADMAP.md](ROADMAP.md)). This file keeps the original spec non-goals and the launch follow-ups, with status as of the 0.1.0 release (shipped 2026-06-23). @@ -19,7 +19,7 @@ launch follow-ups, with status as of the 0.1.0 release (shipped 2026-06-23). ## Known limitations / follow-ups -7. **PyPI release**: ✓ **Resolved in 0.1.0.** Published as `heddle-mcp` (the bare `heddle` name is held by a third-party placeholder) via GitHub Actions Trusted Publishing. `pip install heddle-mcp`; the import name and CLI stay `heddle`. See [RELEASING.md](RELEASING.md). +7. **PyPI release**: ✓ **Resolved in 0.1.0.** Published as `heddle-mcp` (the bare `heddle` name was held by a third-party placeholder) via GitHub Actions Trusted Publishing; the import name and CLI stayed `heddle` through 0.3.3. Since the 0.4.0 rename the distribution, import name, and CLI are all `hashloom` — see item 10. See [RELEASING.md](RELEASING.md). 8. **README gif**: ✓ **Resolved in 0.1.0.** Recorded and embedded under the CI badge; storyboard in [docs/demo.md](docs/demo.md). 9. **Pre-existing stale bytecode**: partially mitigated. The verification runner passes `-B` / `PYTHONDONTWRITEBYTECODE` so its own runs never cache bytecode, but with the default `pycache_trust: true` a stale user-written `__pycache__` (same size, same mtime second) could still be loaded. Mitigation shipped: set `pycache_trust: false` (or `--no-pycache-trust`) to clear `__pycache__` before each verify run. Making that the default remains a possible future change. 10. **Name finalization**: ✓ **Resolved**, then revised. "heddle" was the locked project name through 0.3.3 (PyPI: `heddle-mcp`); renamed to **hashloom** in 0.4.0 after two unrelated "heddle" MCP servers surfaced — see the CHANGELOG's 0.4.0 entry. The bare `hashloom` PyPI name is ours. diff --git a/README.md b/README.md index 0079025..fb36b1a 100644 --- a/README.md +++ b/README.md @@ -185,21 +185,24 @@ cd examples/sales hashloom init && hashloom index && hashloom serve # then point your agent at it ``` -20 contracts, 25 tests, three dependency layers deep. Every example directory -ships its own README with per-language run instructions. - -There are counterpart examples in Go at `examples/go-ledger` (8 contracts over a small -double-entry ledger, same loop: `hashloom init && hashloom index`, then -`hashloom verify --radius Entry` gates the blast radius with `go test` under the -hood — needs a Go toolchain), in TypeScript at `examples/ts-cart` -(8 contracts over a shopping cart; `npm install` first for its `typescript`, -then the same loop — verification runs on Node's built-in `node:test`, Node ->= 22.6), and in Java at `examples/java-payroll` (11 contracts over a weekly -payroll run, three layers deep — records, `Class.method` quals, a parameterized -bracket table, and a `@Nested` test class with dotted node ids; the shape of a -Spring service layer with zero framework dependencies. Same loop — -`hashloom verify --radius TimeSheet` runs Maven under the hood; needs a JDK >= 17 -and Maven). +20 contracts, 25 tests, dependency chains up to six contracts deep. Every +example directory ships its own README with per-language run instructions. + +There are counterpart examples in three other languages, each using the same +`init && index` loop: + +- **Go** — `examples/go-ledger`: 8 contracts over a small double-entry ledger. + `hashloom verify --radius Entry` gates the blast radius with `go test` under + the hood. Needs a Go toolchain. +- **TypeScript** — `examples/ts-cart`: 8 contracts over a shopping cart. Run + `npm install` first for its `typescript`; verification runs on Node's + built-in `node:test`. Needs Node >= 22.6. +- **Java** — `examples/java-payroll`: 11 contracts over a weekly payroll run, + three layers deep — records, `Class.method` quals, a parameterized bracket + table, and a `@Nested` test class with dotted node ids; the shape of a + Spring service layer with zero framework dependencies. + `hashloom verify --radius TimeSheet` runs Maven under the hood. Needs a + JDK >= 17 and Maven. ## Development diff --git a/ROADMAP.md b/ROADMAP.md index 999b036..efd2836 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,8 @@ **Shipped so far** (full detail in the [CHANGELOG](CHANGELOG.md)): -- **v0.1** (0.1.0 → PyPI as `hashloom`, 2026-06-23) — the engine: +- **v0.1** (0.1.0 → PyPI as `heddle-mcp`, 2026-06-23; the project renamed to + `hashloom` in 0.4.0) — the engine: content-addressed contracts, a hash-keyed verification cache, and a blast-radius query, over MCP. Single-process and Python-only by design. - **v0.2** (0.2.0 → PyPI, 2026-07-04) — solo → team: a shared verification diff --git a/docs/benchmarks.md b/docs/benchmarks.md index bdb9e17..ea512b7 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -26,9 +26,11 @@ representative regeneration tasks, one per dependency layer. The **full sweeps** barely benefit — so a sweep always averages lower than the gate. That is not a regression; it is the honest cost of counting everything. -Raw-side counts reproduce exactly; hashloom-side totals jitter by ~1 token per -unit between runs (verify statuses flip `pass` → `cached-pass`), so per-unit -last decimals move while the ratios hold. +Hashloom-side counts reproduce exactly (both scripts warm the cache before +measuring, so every measured verify is a `cached-pass`); raw-side totals can +drift by ~1 token per unit across environments, because the raw count includes +live test-runner output and its incidental formatting. Per-unit last decimals +move while the ratios hold. ## What one number hides: the distribution @@ -44,7 +46,7 @@ the more a ~300-token packet replaces: mode prints nothing on a green suite, so the raw baseline gets *zero* suite-output tokens here — the Java row is the most conservative of the four.) -The examples are deliberately small (8–20 contracts, 2–3 layers), so their +The examples are deliberately small (8–20 contracts, chains two to six deep), so their sweeps sit in the 3–4× range. Deeper projects score higher, not lower: every additional dependency layer widens the gap between reading a closure and reading a packet. The DoD gate's 5.4× on three mid-to-deep units shows the @@ -53,7 +55,7 @@ same effect inside one project. ## Cache economics (from a real store, not a benchmark) Verification caching is the other half of the payoff. This repo dogfoods -hashloom (12 contracts over its own stable seams — see `contracts/`), and its +hashloom (13 contracts over its own stable seams — see `contracts/`), and its store counters after day one: | store | verify requests | served from cache | test runs avoided | hit rate | diff --git a/docs/demo.md b/docs/demo.md index b0a119f..ff63dce 100644 --- a/docs/demo.md +++ b/docs/demo.md @@ -27,7 +27,7 @@ rm -rf .hashloom && hashloom init && hashloom index | 2 | benchmark table | `uv run python bench/benchmark.py` | 5×+ fewer tokens per regeneration | 5 | | 3 | the ask | Claude Code: "re-implement `revenue_by_region`" | the agent asks hashloom, not the filesystem | 3 | | 4 | `get_contract` | MCP `get_contract("revenue_by_region")` → packet | one ~300-token packet: spec + dep signatures + callers | 5 | -| 5 | the weave | agent edits `src/revenue.py::revenue_by_region` | it weaves the weft | 4 | +| 5 | the weave | agent edits `src/metrics.py::revenue_by_region` | it weaves the weft | 4 | | 6 | `verify` (miss) | MCP `verify(["revenue_by_region"])` → `pass` | hash-keyed verification — pytest only on a miss | 5 | | 7 | `verify` (hit) | `verify` again → `cached-pass` | second run: cached-pass, no pytest | 3 | | 8 | `status` | MCP `status()` → token counters | the whole loop, in a few hundred tokens | 4 | @@ -54,7 +54,7 @@ reports `tokens: 0`: the token counters are incremented only by the MCP server ## Recording tips - Scene 2's benchmark rebuilds and warms `examples/sales/.hashloom`, so don't wipe - the store after it. Scene 5's edit to `src/revenue.py::revenue_by_region` busts + the store after it. Scene 5's edit to `src/metrics.py::revenue_by_region` busts only that unit's impl hash, so scene 6 verifies it cold (a real pytest `pass`), scene 7 reuses the cache, and scene 8's `status` stays clean (`dirty: 0`). Re-indexing the whole store instead leaves every other contract `dirty` and diff --git a/docs/hosted-store.md b/docs/hosted-store.md index 18f99bb..6dbc9ff 100644 --- a/docs/hosted-store.md +++ b/docs/hosted-store.md @@ -29,8 +29,11 @@ Behind the Protocol it can later be a remote backend with no change to callers. `tests/test_shared_store.py` shows client A's green served to client B without B running pytest, and confirms failures are not published. -What the MVP does NOT do yet: it is not wired into the CLI/MCP (no surface -change), and the shared store is local-file only. Wiring is below. +When the MVP first shipped it was not yet wired into the CLI/MCP and the +shared store was local-file only; both have since shipped — `build_store()` +wires the layered store into `index`/`status`/`verify` and the MCP server, +and `RemoteStore` makes the shared side an HTTP backend (item 1 below and +"Running the cache server"). ## From MVP to hosted (the hard parts) @@ -62,8 +65,10 @@ change), and the shared store is local-file only. Wiring is below. 5. **Cross-graph invalidation.** When a shared contract changes, dependents' shared verdicts must be invalidated for the whole team, not just locally. `mark_stale` needs a shared analogue keyed off the dependency graph. -6. **Clocks.** `ran_at` is client-generated today; a shared store should stamp - server-side to order writes without trusting client clocks. +6. ✓ **Clocks (shipped for the shared path).** The cache server's own + `SqliteStore` stamps `ran_at` server-side — the client's publish request + carries no timestamp — so write ordering never trusts client clocks. + Local stores still stamp locally, where ordering is single-writer anyway. ## Running the cache server diff --git a/examples/sales/README.md b/examples/sales/README.md index 12bf217..8427fb7 100644 --- a/examples/sales/README.md +++ b/examples/sales/README.md @@ -39,7 +39,7 @@ how incomplete sales are excluded, say — then: hashloom verify --radius included_sales ``` -Exactly one unit re-runs; its twelve dependents stay `cached-pass`, because +Exactly one unit re-runs; its fourteen dependents stay `cached-pass`, because they lean on `included_sales`'s *contract*, which didn't change. Now edit a contract instead — add a field to [contracts/Sale.yaml](contracts/Sale.yaml)'s signature and `hashloom index` — and `verify --radius Sale` re-verifies