Skip to content
Open
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
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,34 @@ Versioning: [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- **Gate runs the test suite once, not twice** (`F-97abf5db`) — pre-push ran BOTH the
unit stage (`vitest run`) and the coverage stage (`vitest run --coverage`), executing
the full suite **twice** (~9.5s + ~10.7s). Since the coverage run already runs every
test, the unit run was redundant. A gate-scoped memo (mirroring the spec cache F-cd0415)
now shares ONE coverage run across both stages: the unit stage triggers it and, on a
GREEN run, reuses it instead of re-running. **Sound attribution:** a non-green coverage
run sends the unit stage to a tests-only fallback, so a *coverage-threshold* miss fails
coverage but not unit, and a *real test failure* still fails both (verified — a failing
test reds both stages). Measured on cladding's own repo: `clad check --tier=pre-push`
**~40.4s → ~30.1s (−~10s)**. Pass-through when unprimed (standalone stage / MCP), cleared
in a `finally`. Note: test *selection* (changed-files) stays out — a gate must run the
whole suite; this only removes the duplicate full run.

- **Incremental TS gate — tsc `--incremental` + eslint `--cache`** (`F-bfe14aac`) — the
TypeScript type and lint gates re-ran from scratch every time. They now reuse a build
cache: `tsc --noEmit --incremental` (build-info file) and `eslint --cache`, both written
under `.cladding/cache/` (already gitignored, so the managed project's tree stays clean).
On an unchanged re-run — the local pre-commit/pre-push loop — measured on cladding's own
repo: **tsc 2.7s → 1.1s, eslint 2.5s → 0.6s (~3.4s saved)**. **Sound, not a shortcut:** a
newly-introduced type error is still caught with a stale build-info present (verified —
`tsc --incremental` rebuilds the affected program slice; `eslint --cache` keys on
file+config hash). Cold runs (fresh CI checkout) just rebuild the cache — no regression.
Test execution is deliberately **not** scoped — a gate must run the whole suite, so
changed-files/test-selection (unsound for a gate) was intentionally avoided.


## [0.7.0] — 2026-07-01 — Knowledge Graph

### Knowledge graph (spec↔code↔doc)
Expand Down
590 changes: 295 additions & 295 deletions plugins/claude-code/dist/clad.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ project:

# Auto-maintained by `clad sync` (F-5b9f9f). Do not edit by hand.
inventory:
features: 199
features: 201
scenarios: 2
capabilities: 6
test_files: 170
last_synced: "2026-07-01"
test_files: 172
last_synced: "2026-07-02"
98 changes: 50 additions & 48 deletions spec/attestation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
# --tier=pre-push --strict` GREEN refreshes. Content-anchored: survives
# fresh clones and squash/rebase (suggested .gitattributes: merge=union).
attested:
F-001: 1eb56cee6b065fbe
F-001: 830ae324d89c3fce
F-002: c116a9d32f862ee9
F-003: c9578f9a6e70dcdf
F-004: 791125e674b98fb5
F-004: b6e68a1ab2d8fe81
F-005: 70c3b7166f297bff
F-006: 488cb6f2d452a286
F-007: d1c159c46d5a454f
Expand All @@ -26,11 +26,11 @@ attested:
F-018: a4c997fd2e8a8a22
F-019: 722e8bf2c8e0b41c
F-020: 2c5f4a94e3e57e9b
F-021: 8a1a82a59a1c45c7
F-021: 3038583c9be2dac3
F-022: 8f596a1c737f6d42
F-02343cd1: 77875ee09a7ea3bf
F-023: 0c14948e5a91bb0f
F-024: f67a86816b06f8ee
F-02343cd1: 4898c10c8112933b
F-023: c347678282dafd98
F-024: 8e94295e3bdf989f
F-025: 187339b684896b8e
F-026: bb35faf43ba582cb
F-027: 7f4b1a055f141bb7
Expand All @@ -46,17 +46,17 @@ attested:
F-037: 7f811c5c8bc0e8e3
F-038: 1338100beadb15a6
F-039: 2e60f3d899b72d7f
F-040: f678671ad6613fd8
F-040: f657b518e8c95b9f
F-041: cddb50fc41e49066
F-042: d1f661281bb9fb6e
F-043: 2c5f4a94e3e57e9b
F-044: 2f5ee0e3cee5f762
F-044: b66f25667a92e6cb
F-045: b494c9a80442ac20
F-046: 4b563fce74b6bb4b
F-047: baf5a2dbb9bb5a4b
F-048: 30dcf0786717f873
F-048: 0908b3efde759da9
F-049: 444a75986c1c3430
F-051: 1c6cf231cc139640
F-051: 15166184e6321e6a
F-052: 61e043371b9f7c71
F-053: 13a90cd08aec07ae
F-054: 5f5d30500bd8cd7f
Expand All @@ -69,7 +69,7 @@ attested:
F-061: c16123610e8fe7fc
F-062: 0ab83282a7f7b1ef
F-063: 76a719993cc71fa8
F-064: e176668e53f61c6e
F-064: ae2257aa0a9a82f5
F-065: e6ed3ef916201947
F-066: 512896be3294c6e4
F-067: 6e6dbd05bf314b56
Expand All @@ -79,121 +79,123 @@ attested:
F-070: e50bb5d3addc7720
F-071: 3183a483a8015d4c
F-072: 44e1d39139c816cc
F-073: 47be139281e7940a
F-073: ca0128a8f26b448e
F-074: a4cc2b9bf66c77ac
F-075: f8242e8d7fdc0488
F-075: 4eb010b3597b6980
F-076: 0061ab2d4b4991a8
F-077: 7aac44757146b695
F-078: c6af3d128b98f4e6
F-079: eaa08d9aeeb2a263
F-079: 21f71d692ea02449
F-080: 1c19da74d32894e6
F-081: 248f9660cfb1b02b
F-098d3b: 42d61bf806ce462b
F-09d68b: dca7938bcf6fa47e
F-09d68b: 5786f607fd02cd58
F-0ed2db: f94e2f45a16ff99c
F-0f2984d0: b852882d00177ef9
F-0f2984d0: c91aa249ed795b43
F-12d740: 84ad71574d306c81
F-15999130: 894b484b7a93690c
F-16138071: c8b73555d47cfdd7
F-16138071: 66d6c10dfabf57f8
F-16746b: 2f98a1261b9b1fc2
F-17df0a: 915d13b33258d3fc
F-18e951: d907c170a230e052
F-1c9166: c72ece4c25cc4748
F-1d23a6: 8edadd4a7f136330
F-1d23a6: 4c61a2e3f46e7c0f
F-1edb38: 64283112a3ab96ce
F-24062d: 7d0f890c1b7c68d0
F-245bd5: a8372aeb83acc411
F-2be3e3bb: b852882d00177ef9
F-2be3e3bb: c91aa249ed795b43
F-2de65d: 84ad71574d306c81
F-315fd7: c3b042c80fa7c187
F-31eeb8: d88a9880d29ae411
F-32b1e0: 9b29e21313d121b1
F-32b1e0: 628a367abff37a63
F-3788c2: af9778dea8687b29
F-37b4a8: e067655bad681488
F-3a5339: b2b2ea8775f99267
F-3a5339: 524214cca460245d
F-3b3690: 6a36aad282d36f3a
F-40327b: 8295358f7b813c8a
F-417ff0: 0cc5eeefc5e08377
F-417ff0: f58a04fcfb16a6d5
F-42af48: 7702447a407758a1
F-43d8e3: bbea25941e2b675d
F-4747ef: c255a18b6849d002
F-4db939: b2c386ca4e18c117
F-50ff43: fac674314685a912
F-551a1c: 305488fada044107
F-569f4b37: 6ad74b9c3113817a
F-56abaa: a8863eff906b4d73
F-569f4b37: 458a8c15b5c85801
F-56abaa: f7fee17336e46647
F-570a3f: 3f60012b22c9b715
F-59f093: 26735424fba6308c
F-5b188856: 92b72281c248eba3
F-5b9f9f: 0b972209be8b642f
F-5b9f9f: 5ab050d64334fb33
F-5d3ed2: 9452eac28760fb99
F-5f6b45: 15323c4f5b619de7
F-64a5c159: adedb516a257c7ec
F-64a5c159: 4d3e31d490fe3f69
F-65814a: 2136c8b8c94ef535
F-67d2e9: 03dc30dc995da728
F-67d2e9: e8ea664fe48a10d8
F-67e33f: c8f84d2f7b3971aa
F-6d943d: e02e71ed007aded2
F-6f80e7: 0c0e5b71ae22cc26
F-7076f7: 7e819e0d440f3ffe
F-7794a6bc: 4d075a1a19bf4ba5
F-7794a6bc: 8b3c5cc9ecca721a
F-77f7ead0: 3f8cd4feeece1abd
F-78b50d: 688e6afe2352a034
F-7afbd4: 18ce48352bee0fce
F-7ce18e: 42fe8057bc8e250b
F-7ce18e: ba2b90adc41ec0ec
F-7fa4a7: 19b7709a0b2202e3
F-80d19d: 6e03a098269ce4c3
F-80d19d: 648a6e72fbd5c4ae
F-836a90: a0d57b88aafbe6e8
F-8f419e: f3473746f4e252bc
F-904495a5: 56b5bb4bf3c35ddf
F-9064ff: b586e3aa6bc24aac
F-904495a5: e92154cd232d9967
F-9064ff: 529abc9fab019e21
F-94dda4: 8dfb0267c45534f9
F-95a096: 4c7b844669411617
F-96250595: 846ab496e40cdd48
F-96700032: 5ae97b4c82c14acc
F-97abf5db: d07f7000f8fab9d7
F-99c6e5: 8a8724852f1d6059
F-9a3b61: 786eeefef2d25138
F-9b643e: 1c68e95890f2671e
F-9b643e: efbde82cde97020d
F-9d168287: 6a3dc163c77b9e15
F-a04cd9: e65d87671306d305
F-a4b512: d136f2a1bbc4383a
F-a5228c: 4a9f641497c37b67
F-a5228c: bfea6820a138828c
F-aa7197: 7f561e4f3c902716
F-ae61c1: 7c3c8622e5375754
F-aee1da: a6d7525a6c547877
F-aee61f: e009b9eb07addd30
F-af45042a: 9c61ca116a28cbb6
F-af96b1: e75ca2cb3412a7a5
F-b2094740: f379bf4feef6771a
F-b2094740: 2ea89b77e7c8e740
F-b43066: 9402b630adcf1eae
F-b61449: 7095ce00662e987d
F-b84c38: 61a41c3f765e8a92
F-b99577: b3de7411ed1f21aa
F-b84c38: 7c1b0ae30a9a5331
F-b99577: 2c07a912992e8cf7
F-ba2e05: 158e77c8af32514a
F-ba4b7a: c282e0e915ed547c
F-bb15e6: 9b629bd8910007fa
F-bb15e6: 1778a14f423295cb
F-bd07d7: 4bf7e1baddf5d754
F-bdcd90: 826853f7885a5a08
F-c037ae: 6a58cdcfd0474e5f
F-c2c996: 5c73fa010b1502fa
F-c48eb2: 6a581a63e255c279
F-bdcd90: 74978de6d70b5954
F-bfe14aac: 2ea89b77e7c8e740
F-c037ae: a37a0f9651e182b0
F-c2c996: c4f65193958987e4
F-c48eb2: a707eb6db729a91a
F-c4c5ae: 18200d79542ae22e
F-c8aef8: 02e07f929a1d0ded
F-cd0415: 9cf6ce40e2a8b381
F-cfba0c: 077c03b8a96f562b
F-d12edf: c19aa5d1007e0b8f
F-d2c806: b3d8668905855a6c
F-d2c806: e7b19d6e2f6ec583
F-d3bde4: 915d13b33258d3fc
F-d49585: 11e3ac2dce796fc6
F-d6b93648: f755a47c66e07635
F-d49585: e09f480b54fed368
F-d6b93648: 8606146965a4ade3
F-d7312b: 000237d094145b6a
F-d8223c: 0501e9564231899b
F-d980359c: 8f1559276afc5c03
F-dd51b42c: 496eeffa2641169d
F-dd51b42c: 30b7e2a656892648
F-dddb89: f5625354e55eba9b
F-e0f6c7: fe68521cda464f23
F-e0f6c7: 7b2a2d98b2486c8a
F-eb732f: d8abb536ff850a7a
F-ee47fc2b: adb87c97b8ccf6e1
F-ee5f643e: 7c61f35852f093ca
F-ee5f643e: 70b5f822272e14e7
F-ef2fd9: b3a1dcd1e750a714
F-f334fa: 5207f35968a0c9b2
F-f44d1b: 62e0779d9c0ef11f
Expand Down
33 changes: 33 additions & 0 deletions spec/features/incremental-ts-gate-bfe14aac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
id: F-bfe14aac
slug: incremental-ts-gate
title: "Incremental TS gate — tsc --incremental + eslint --cache (sound; ~3.4s faster on warm re-runs)"
status: done
modules:
- src/stages/toolchain/detect.ts
acceptance_criteria:
- id: AC-41107118
ears: ubiquitous
action: "run the TypeScript type gate as tsc --noEmit --incremental with a tsBuildInfoFile under .cladding/cache, so an unchanged re-check reuses the build cache"
response: "detectToolchain's typescript gates.type carries --incremental + --tsBuildInfoFile .cladding/cache/tsc.tsbuildinfo"
text: "The system shall run the TS type gate with tsc --incremental and a .cladding/cache build-info file."
test_refs: ["tests/stages/incremental-gate.test.ts"]
- id: AC-1f2116fa
ears: ubiquitous
action: "run the TypeScript lint gate as eslint --cache with a cache-location under .cladding/cache, so unchanged files reuse cached lint results"
response: "detectToolchain's typescript gates.lint carries --cache + --cache-location .cladding/cache/eslint"
text: "The system shall run the TS lint gate with eslint --cache and a .cladding/cache cache-location."
test_refs: ["tests/stages/incremental-gate.test.ts"]
- id: AC-f253b9fa
ears: state
condition: "while a build cache from a prior run exists and the tree is unchanged"
action: "still catch a newly-introduced type/lint error (incremental is sound, not a correctness shortcut)"
response: "tsc --incremental rebuilds the affected program slice and eslint --cache keys on file+config hash, so a real error is never masked — the cache only skips proven-unchanged work"
text: "While a cache exists, the system shall still catch any real type/lint error (sound incrementality)."
test_refs: ["tests/stages/incremental-gate.test.ts"]
- id: AC-dd80d585
ears: unwanted
condition: "if the gate writes its incremental caches"
action: "keep them under the .cladding/ namespace so they never pollute the managed project's git status"
response: "both cache paths live under .cladding/cache (already gitignored), not the project root"
text: "If the gate caches, then it shall write under .cladding/ so the project tree stays clean."
test_refs: ["tests/stages/incremental-gate.test.ts"]
38 changes: 38 additions & 0 deletions spec/features/test-run-dedup-97abf5db.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
id: F-97abf5db
slug: test-run-dedup
title: "Test-run dedup — run the suite ONCE per gate (coverage run shared with the unit stage), sound attribution on failure"
status: done
modules:
- src/stages/test-run-cache.ts
- src/stages/unit.ts
- src/stages/cov.ts
- src/cli/clad.ts
acceptance_criteria:
- id: AC-ccb4e6dc
ears: event
condition: "when both the unit (2.1) and coverage (2.2) stages run in a primed gate pass"
action: "execute the test suite only ONCE via a shared memoized coverage run, reused across both stages"
response: "test-run-cache.ts memoizes the coverage run by cwd; the unit stage triggers it and the coverage stage gets a cache hit, so vitest runs once instead of twice"
text: "When both test stages run in a primed gate, the suite shall execute once."
test_refs: ["tests/stages/test-run-dedup.test.ts"]
- id: AC-1cd3923c
ears: state
condition: "while the shared coverage run is GREEN (exit 0)"
action: "report the unit stage as pass without re-running the suite"
response: "a green coverage run proves all tests passed, so the unit stage reuses it (the common, speed-critical green path saves the duplicate ~9.5s run)"
text: "While the shared coverage run is green, the unit stage shall pass without re-running."
test_refs: ["tests/stages/test-run-dedup.test.ts"]
- id: AC-81e95cc4
ears: unwanted
condition: "if the shared coverage run is non-green"
action: "run the tests-only command for the unit stage so failure is attributed precisely"
response: "a real test failure fails BOTH stages; a coverage-threshold-only miss (tests pass) fails coverage but the unit stage's tests-only fallback passes — correct attribution, never a false unit pass on a real test failure"
text: "If the shared coverage run is non-green, then the unit stage shall re-run tests-only for precise attribution."
test_refs: ["tests/stages/test-run-dedup.test.ts"]
- id: AC-32ddd2a8
ears: state
condition: "while no cache is primed (standalone stage call / MCP) or the project has no coverage runner"
action: "run each stage's own command exactly as before"
response: "the dedup is a primed-gate optimization; unprimed or coverage-less projects keep byte-for-byte the prior per-stage behavior"
text: "While unprimed or coverage-less, each stage shall run its own command unchanged."
test_refs: ["tests/stages/test-run-dedup.test.ts"]
2 changes: 2 additions & 0 deletions spec/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ features:
F-95a096: {slug: ops-visibility-polish, status: done, modules: 3}
F-96250595: {slug: iterative-impact-slice, status: done, modules: 2}
F-96700032: {slug: unverified-ac-junit, status: done, modules: 4}
F-97abf5db: {slug: test-run-dedup, status: done, modules: 4}
F-99c6e5: {slug: cladding-self-fixes, status: done, modules: 13}
F-9a3b61: {slug: ab-ext-uncommit-demos, status: done, modules: 3}
F-9b643e: {slug: scan-conventions, status: done, modules: 5}
Expand All @@ -174,6 +175,7 @@ features:
F-bb15e6: {slug: clad-doctor, status: done, modules: 3}
F-bd07d7: {slug: greenfield-seeds, status: done, modules: 5}
F-bdcd90: {slug: oracle-policy-risk-weighted, status: done, modules: 8}
F-bfe14aac: {slug: incremental-ts-gate, status: done, modules: 1}
F-c037ae: {slug: test-refs-repair, status: done, modules: 4}
F-c2c996: {slug: checkpoint-events, status: done, modules: 3}
F-c48eb2: {slug: scan-source-roots, status: done, modules: 5}
Expand Down
9 changes: 9 additions & 0 deletions src/cli/clad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {runAudit} from '../stages/audit.js';
import {runCommit} from '../stages/commit.js';
import {runCov} from '../stages/cov.js';
import {runDrift} from '../stages/drift.js';
import {primeTestRunCache} from '../stages/test-run-cache.js';
import {runLint} from '../stages/lint.js';
import {runPerf} from '../stages/perf.js';
import {runSecret} from '../stages/secret.js';
Expand Down Expand Up @@ -466,6 +467,11 @@ export function runCheckStages(opts: {internal?: boolean; strict?: boolean; tier
const pulseKindOf = (s: GateStatus): PulseKind =>
s === 'pass' ? 'pass' : s === 'liveness' ? 'note' : s === 'na' ? 'skip' : isBlocking(s) ? 'fail' : 'skip';
const collected: {stage: string; label: string; status: GateStatus; exitCode: number; stderr?: string; findings?: readonly DriftFinding[]}[] = [];
// Test-run dedup (F-97abf5db): share ONE suite run across the unit + coverage
// stages within this gate pass. Cleared in finally so the long-lived MCP
// server never reuses a test result across runs.
primeTestRunCache(true);
try {
for (const [name, run] of stages) {
const r = run({}) as {
pass: boolean;
Expand All @@ -492,6 +498,9 @@ export function runCheckStages(opts: {internal?: boolean; strict?: boolean; tier
if (isBlocking(status)) printStageDetails(r);
}
}
} finally {
primeTestRunCache(false);
}
// STRICT SKIP-POLICY (F-67d2e9, generalizes the 0.5.x unit-only guard).
// Under --strict, a skipped stage the spec DEMANDS is a fail: 1.1 when a
// declared language ships done features, 2.1 when done features declare
Expand Down
Loading
Loading