Releases: edycutjong/armsmith
Release list
v1.3.2
v1.3.2 (2026-08-09)
This release is published under the MIT License.
Bug Fixes
- ci: Make the arm64 record leg actually diagnose, and stop overstating what a bundle can answer (
e6ffdee)
The record -> diagnose leg proved the path ran, but the bundle it produced enabled zero rules and all thirteen skipped. "0 rules enabled, 13 skipped" is a green tick on a step that demonstrated nothing, which is precisely the shape of evidence this project refuses everywhere else.
The runner now produces REAL instrument artifacts and hands them to record:
pip-install.log (tee of a real pip install -v numpy) -> R8 a numpy venv (--python points at it) -> R3 build.log (real gcc -v on the bench kernel) -> R2 lscpu / THP (captured by record itself)
and the repo copy is enabled again so the static rules run too. The assertion is now the thing that matters: the bundle must be synthetic:false, it must enable at least one probe rule, and at least four rules must actually RUN rather than skip. A bundle that captured nothing can no longer pass.
Separately, rules_enabled was itself overstating. It used a hand-maintained probe->rule map that credited a rule as soon as ONE of its probes arrived, so a bundle with only build_log claimed R2 was answerable when R2 also needs lscpu. It now reads each rule's own requires from the pack, so the claim cannot drift from the rules it describes. A test asserted the old behaviour; that test was encoding the overstatement and has been corrected, plus a new one pins that a multi-probe rule appears only when every probe is present.
456 tests, 100% coverage.
- record: Don't recurse when the bundle lives inside the repo (
43479ad)
RecursionError on CI, and it affects the DOCUMENTED command: the README tells you to run armsmith record . --out ./armsmith-bundle, which puts the bundle inside the tree being copied, so shutil.copytree descended into the directory it was writing until the interpreter gave up.
It only stayed hidden because every earlier test used --no-copy-repo or an output path in /tmp. The ignore callback now excludes the bundle root and anything under it, and a regression test records into a directory inside the repo and asserts the bundle did not copy itself.
457 tests, 100% coverage.
Documentation
- Api reference, and a PR body that leads with what works today (
c9fadba)
Two of the last three fixable deductions.
docs/API.md documents the six importable modules that the README has been advertising as reusable without ever showing a signature: benchstats (the median-of-N / scaled-MAD refuse-to-claim rule, the part most worth stealing), gate, report, witness, rules, and benchcmd. Every claim in it was executed before committing - compare() really returns -40.0%, plan_interleaved really emits w:a w:b m:a m:b m:a m:b, count_witness really finds the smmla, load_pack really returns 13. A docs file that lies would be worse than none on this project in particular.
The PR body no longer ships 'TODO(S1) wiring' to end users as its closing note. It now leads with the verification that actually works today - armsmith verify re-derives every statistic from the embedded samples, offline - and demotes the unwired cosign invocation to a clearly-labelled reference line.
Detailed Changes: v1.3.1...v1.3.2
v1.3.1
v1.3.1 (2026-08-09)
This release is published under the MIT License.
Bug Fixes
- Two verified false results, a broken wheel path, and the front-page numbers (
2ffd1bd)
Found by pointing the tool at real repos again, and by a cold install.
R4 was blind to dtype casts. np.array(1.0).astype(np.float32) pins the dtype one call later, so the constructor's default never survives - reporting it is a false positive on correct code, and it fires on onnx's backend test models. The visitor now marks a constructor whose result is immediately .astype()/.view()'d and skips it. NodeVisitor reaches the outer call first, so the inner one is already marked by the time it is visited.
R12 could not resolve the commonest matrix form. The docstring claimed it handled a plain key (matrix.platform), but walk() only appended when the node was a scalar AND no keys remained, so a terminal list yielded nothing - an amd64-only plain-key matrix silently reported CLEAN. That is a false NEGATIVE, the worse kind of miss, and the docstring was describing a capability the code did not have.
bench-live was unusable from a pip install. The kernel sources lived at the repo root, outside the package, so they never reached a wheel - and the PyPI README told people to run the command anyway. Same failure mode as the report schema had, and the same fix: the .c files now live in src/armsmith/bench/ and ship as package data. Verified by installing the wheel into a clean venv and running bench-live from /tmp.
Front-page numbers were wrong. A bad shell substitution in an earlier commit had left "Tests | 1 passing" in the rigor table, and "CI jobs per push | 8" when there are 10. On a project whose entire thesis is numeric honesty, those are the most expensive kind of typo.
The tagline claimed something the code contradicts. "The LLM plans; the silicon decides" sat above a ClaudePlanner that raises NotImplementedError. It now reads "The planner proposes; the silicon decides", with one line saying the shipped planner is a deterministic sort and the Claude loop is contract-pinned and not wired. Nothing about the thesis depended on the planner being an LLM - the gate is what makes a result trustworthy - so the honest phrasing costs nothing.
Also: - scan --json emits EVERY match with full evidence, fix and citations. A table is for a human; JSON is for a pipeline, and truncating to one exemplar there would make a scan look cleaner than the repo is. - record now says at the point of pain that a Mac produces an empty bundle by design, and the README Prerequisites say which commands work on Darwin. - The site and deck now show the SECOND measured instruction (SMMLA 0 -> 1, -71.9% under +i8mm) beside the first, and carry the drift note the README already made. That engineering existed only in CI and was scoring zero.
455 tests, 100% coverage.
Detailed Changes: v1.3.0...v1.3.1
v1.3.0
v1.3.0 (2026-08-09)
This release is published under the MIT License.
Continuous Integration
- Require the SMMLA witness now that the runner has proven it (
216fecb)
The mmla case landed without --require-witness so an unexpected toolchain result would report rather than redden the build. The runner has now measured SMMLA 0 -> 1 with verdict keep, so the instruction demonstrably emits there.
Turning the assertion on: if a future GCC or runner image stops emitting SMMLA, the premise of this case is gone and CI should fail loudly rather than keep timing two builds that are secretly identical.
Features
- bench-cmd: Point the reproduce gate at the operator's own workload (
92b6edd)
The gate is the whole product, and it could only ever be aimed at this project's own bench/int8_dot.c. So the one thing the tool is FOR - deciding whether a proposed fix is real - could not be applied to a fix you actually made. An independent review put it plainly: the value proposition in the tagline was not deliverable by a stranger.
armsmith bench-cmd --rule R3 \ --baseline-cmd "python serve_bench.py --config before.yaml" \ --candidate-cmd "python serve_bench.py --config after.yaml"
Identical statistics to everything else here: ABAB interleaving so drift lands on both sides, median-of-N, scaled-MAD noise band, output-hash equality, and an ed25519-signed report that armsmith verify re-derives from the embedded raw samples. A delta inside the band is reported as no change, never as a win.
What it deliberately does NOT do:
- No ISA witness. There is no binary to disassemble, so artifacts record isa_witness.available = false with a pointer to bench-live, rather than zero counters a reader could mistake for a measurement. A test asserts no numeric counter ever appears there. - No running off aarch64. A wall-clock number from an x86 box is not an Arm result; require_arm=False exists for a deliberate non-Arm comparison and the host arch is recorded either way. - No non-deterministic workloads. If stdout changes between runs the gate cannot tell an improvement from a different computation, so it refuses to measure rather than compare two different things.
Fixes a bug found by testing verify on its own output: a run without --rule stamped rule_id "-" into the finding, which fails the report schema's ^R\d+$ and made a signed report unverifiable. An operator-driven A/B now emits no finding at all rather than inventing a rule id - a fake id in a signed report is exactly what this tool exists not to do. The commands are recorded in artifacts.workload regardless.
Also adds the R9 before/after snippet, so 12 of 13 migration cards now carry a paste-able diff; only R13 stays diagnostic.
Verified end to end on a real numpy float64-vs-float32 workload: +4.42% inside a +/-0.164 s band -> no_change -> gate drop. It refused to claim a win from noise on a workload it had never seen. 447 tests, 100% coverage.
- bench-live: Second live case β SMMLA under +i8mm (
47a953e)
One measured kernel is a data point. The live leg now runs two, on different ISA extensions and different instructions, so it reads as a harness rather than one lucky microbenchmark:
--case dot (default) int8_dot.c +dotprod -> SDOT --case mmla int8_mmla.c +i8mm -> SMMLA
bench/int8_mmla.c is a 2x8 * 8x2 -> 2x2 int8 matmul, the exact shape SMMLA implements and the shape KleidiAI and llama.cpp's int8 GEMM kernels are built around. Compiled twice from one source, differing only in the -march flag.
HONEST about how the speedup happens, because it differs from the dot case. There, GCC's vectorizer discovers SDOT from plain C. Here the fast path is the ACLE intrinsic vmmlaq_s32 behind __ARM_FEATURE_MATMUL_INT8, because GCC does not reliably auto-vectorize this shape and pretending otherwise would be the kind of claim this tool exists to refuse. The source says so in its header. Feature-gated kernels are also what real libraries ship - KleidiAI selects its micro-kernel per detected CPU capability exactly this way - so the flag genuinely gates the code path, and that gate is what is measured. Both paths compute identical arithmetic, so any divergence is dropped by the gate on output-hash inequality.
Verified locally before landing: the +i8mm build contains 1 SMMLA in mmla_i8 and the baseline contains 0 - the same 0->1 witness shape as SDOT. (The intrinsic build SIGILLs on this M1 Max, which has no i8mm; that is the instruction being real, not a defect.)
livebench is now parameterised by a BenchCase - source, symbol, both variant specs, rule id, scenario - instead of hardcoding int8_dot.c and dot_i8, and the report's workload block records which case ran.
CI runs the new case on the arm64 runner and prints the SMMLA counts. It deliberately does NOT pass --require-witness on this first landing: the counts are asserted by reading the signed report, so an unexpected toolchain result is reported rather than turning the build red.
Also de-flakes the bench-cmd --strict test, which depended on two identical commands landing inside the noise band - true only usually, and a test that turns on scheduler luck fails in CI for no reason. It now uses a candidate that is deliberately slower.
449 tests, 100% coverage.
Detailed Changes: v1.2.2...v1.3.0
v1.2.2
v1.2.2 (2026-08-09)
This release is published under the MIT License.
Bug Fixes
- rules: Stop R4 suggesting a patch it cannot prove; resolve R12 matrix vars (
d59b8d7)
Two false-positive classes, both found by pointing the tool at real repos.
R4 β the surviving finding still carried the corrupting patch. The dtype guard cut TGI from 5 findings to 1, but that one printed "add dtype=np.float32" for np.array(adapter_indices) β exactly the edit the rule's own docstring warns would break an index array. Reporting the call is right; suggesting that fix for it is not. Calls are now classified PROVEN (the constructor or a literal payload guarantees float64) vs UNPROVABLE (a name whose element type we cannot see). PROVEN keeps the mechanical patch. UNPROVABLE is reported as a question - "CONFIRM the payload is float before pinning dtype ... leave this call alone" - and Fix.kind becomes "advisory" when nothing is provable.
Also recognises integer comprehensions: np.array of a range comprehension is int64, and was being flagged on vllm.
R12 β an unresolved matrix expression was reported as a match. The platforms input was read literally, so llama.cpp was told it had no arm64 while its matrix explicitly includes linux/arm64. The rule now resolves matrix paths against the job's own strategy block (including include: entries). When the matrix is built at runtime and cannot be resolved, the rule stays SILENT and says so rather than guessing - silence beats a false positive. Status gates on locations, not evidence, so those notes cannot make the rule fire.
Verified on the repos that exposed them: llama.cpp CLEAN, TGI still MATCHED on its genuinely amd64-only build, vllm's comprehension no longer flagged. 429 tests, 100% coverage.
Also in this commit β provenance labelling, which had drifted:
- cli.py printed the REPLAY MODE / synthetic banner unconditionally. It now reads report["synthetic"], so a recorded bundle gets "RECORDED - real observations captured on a host" instead of being called fabricated. - The PR body stamped "SYNTHETIC DATA" on any replayed report, including bundles whose manifest says synthetic:false. - README claimed "every other report carries mode replay + synthetic true", contradicting the provenance table twelve sections earlier. - The live-measurement table is pinned to the run it came from (31301665280) with that run's real figures, and now says out loud why the last digits move between runs: the job re-measures on every push, and a number that never drifted would be a number nobody was measuring.
CI: the arm64 job now runs record -> diagnose. Everywhere else in CI the probe rules read fixtures we wrote; on that runner lscpu and the THP sysfs node genuinely exist, so it is the only place a bundle can come from observations nobody authored. The step asserts synthetic:false and that lscpu was really captured, so a green tick cannot mean "captured nothing".
UX: armsmith --version works (it is what people type first); doctor fails ONCE with the complete working invocation instead of teaching itself through two consecutive errors; action.yml and the README Action snippet now say that replay: points at a bundle in YOUR repo and show how to record one.
Kitchen leak: rule packs, detectors and generated migration cards cited crawl/clean/sdk_*.md - private research paths absent from this repo. Replaced with the public upstream docs they summarise.
Detailed Changes: v1.2.1...v1.2.2
v1.2.1
v1.2.1 (2026-08-09)
This release is published under the MIT License.
Bug Fixes
- packaging: Give PyPI its own README so the page actually renders (
3c7af38)
The PyPI page showed broken images. Three reasons, all structural:
- README.md's hero and icon are relative paths (docs/.svg). PyPI renders long_description standalone, so those resolve against pypi.org and 404. - They are SVG, and GitHub raw serves .svg as text/plain β an
would not render them even with an absolute URL. - The file carries 17 repo-relative links (action.yml, LICENSE, .github/, #anchors) that only resolve inside the repo.
README-pypi.md is the same project described with absolute https URLs only, and leads with install rather than hackathon framing. Its hero is the PNG the live site already serves β correct Content-Type, already deployed, zero new bytes in the repo.
Verified through readme_renderer (the renderer PyPI itself uses): 7 images survive sanitisation, all absolute https, no relative srcs; twine check passes on both wheel and sdist, and the sdist carries the file.
GitHub's README.md is unchanged β it keeps the animated SVG hero, which renders fine there.
Documentation
- site: Audit landing + deck against what actually shipped (
079c9c1)
Both surfaces predated today's work and were quietly wrong in four ways.
- armsmith record appeared NOWHERE on either page, despite being the change that lets the probe rules run on a stranger's repo at all. The landing quickstart now shows record -> diagnose, and a new artifact card explains the manifest declares synthetic:false. - The deck counted 7 commands. There are 8. - Neither page linked PyPI or GitHub Releases. Added to the landing footer, the hero CTA row, and the deck's link slide; the deck numgrid gains a uvx tile. - The migration-template card still described the cards as anti-pattern + fix + citation. They now carry a paste-able before->after diff on 11 of 13, and the card says which two do not and why.
Also corrects 'add a 14th rule with zero core changes' to the truth: one YAML, one detector, one import.
Checked with a headless render of both pages: 0 console errors, 0 empty hrefs, no horizontal overflow.
Detailed Changes: v1.2.0...v1.2.1
v1.2.0
v1.2.0 (2026-08-09)
This release is published under the MIT License.
Continuous Integration
- release: Link the pypi deployment to the package page (
aacd397)
The Deployments panel showed a bare 'pypi' environment with nothing to click. environment.url points it at the published package, so the entry resolves to https://pypi.org/project/armsmith/.
Takes effect on the next publish; existing deployment records keep the url they were created with.
- release: Only publish to PyPI when a version was actually cut (
024ce36)
The release workflow fires after every successful ci run, and publish-pypi was gated only on the PUBLISH_TO_PYPI variable β so it rebuilt and attempted a publish on every push regardless of whether semantic-release cut anything. Three publish attempts and three deployment records inside 25 minutes, each a no-op saved only by skip-existing, and each one a deployment event.
The release job now exposes semantic-release's own 'released' output and publish-pypi requires it. Docs/chore/ci pushes stop touching PyPI entirely.
Documentation
- Point the demo links at the re-cut video (
fb1497d)
Scene 6 of the demo was a replay-labelled -35% from the synthetic bundle. It is now the real measurement: the live A/B on a Neoverse-N2 runner, SDOT 0 -> 1, -86.5%, gate keep, every figure copied from the signed report-live.json CI artifact.
Replaced rather than inserted because the rules cap the video at three minutes and the cut was already 2:55.8. The new segment is frame-exact (497 frames, VO padded to the same 16.55s), so nothing downstream moved.
YouTube cannot swap the file on an existing upload, so the re-cut is a new id: vq15rK1iCww -> JsT83BYMWd0.
- progress: Record the shipped record command and the real test count (
3ff5eee)
PROGRESS.md still said 234 tests (it is 410, at 100% line coverage) and still listed bundle recording as owed. armsmith record shipped on 2026-08-09; what remains of that item is ssh:// targets only, so a laptop can record a bundle for a remote Arm box.
The Action Marketplace listing stays described as publish-pending β that one is still true. Only PyPI is published.
Features
- rules: Put a real before/after diff in every actionable migration card (
8109236)
The 13 cards were advertised as x86->Arm migration templates and as the rubric's reusable artifact, but grep '```' across all of them returned zero β they were 23 lines of prose each. An index of Arm Learning Path links is a useful thing, but it is not a template: you cannot paste a paragraph into a Dockerfile.
Rule descriptors now take optional before/after snippets plus a language to fence them in, and the export renders a 'Before -> after' section. The smallest honest edit for each anti-pattern, e.g. R12:
platforms: linux/amd64 platforms: linux/amd64,linux/arm64
11 of 13 rules carry one. R9 and R13 do not, on purpose: both are diagnostic β they tell you where the time is going and redirect the optimization, rather than naming a specific line to change. Inventing a snippet for them would be the same failure mode as inventing a measurement, so they render no fence at all.
The loader treats an empty string as absent, and a test asserts no rule ever ships half a pair β a 'before' with no 'after' renders an anti-pattern the reader has no fix for.
414 tests, 100% coverage; counts synced across README, site, deck, CI and CONTRIBUTING.
Detailed Changes: v1.1.0...v1.2.0
v1.1.0
v1.1.0 (2026-08-09)
This release is published under the MIT License.
Documentation
- Lead with the PyPI install now that armsmith 1.0.4 is published (
15342ef)
armsmith is on PyPI, published from CI via Trusted Publishing (OIDC, no token in the repo), with sdist + wheel attached to the GitHub Release. Verified from a clean venv outside the repo: pip install armsmith and uvx armsmith both resolve, load all 13 rule packs from inside the wheel, and scan a repo they have never seen.
Adoption previously meant 'git clone + pip install -e', which filters out every drive-by user. Now:
uvx armsmith scan .
- README and site quickstarts lead with the zero-install one-liner, with the clone kept for the path that genuinely needs it: reproducing the gate against the replay bundles, which live in the repo. - Installation section splits 'to use it' from 'to hack on it'. - PyPI version badge in the top matter. - Reuse section names the install as a first-class artifact.
Held back until the package actually resolved β advertising an install command that does not work is the one claim this project cannot make.
Features
- record: Capture a real replay bundle from the host you run on (
a2e1871)
The largest gap in the tool: diagnose --replay was required, and no command produced a bundle. Ten of thirteen rules and the CI gate could only ever run against fixtures shipped in this repo, so a stranger got a three-rule static scan and nothing else. armsmith record closes that.
armsmith record . --out ./bundle --python .venv/bin/python armsmith diagnose --replay ./bundle
Captures what the host can honestly answer (lscpu, THP state, and the BLAS numpy reports for the interpreter you name) and copies in verbatim any real instrument output you already have: --build-log (R2), --pip-log (R8), --cmake-cache (R10), --gguf (R5), --perf (R9), --ort-session (R7), --llama-bench + --hyperfine (R13).
--python exists because R3 is a claim about the venv that serves YOUR model. Probing our own interpreter would answer the wrong question; armsmith does not even depend on numpy.
Honesty contract, enforced in code and asserted by tests: - manifest declares "synthetic": false β nothing here is invented - env and proc_maps are NEVER captured, so R6 cannot run from a recorded bundle and R11 stays half-fed. A bundle is published; an env block carries CI tokens and a maps dump carries host paths. - anything unobserved is omitted, not guessed; record prints which rules the bundle can and cannot answer before you run diagnose
Fixes a provenance bug this surfaced: build_report derived synthetic from mode ('replay' implied synthetic), conflating transport with provenance. A recorded bundle is replayed but real, and was being stamped synthetic β understating a genuine measurement as badly as the reverse would overstate one. The two are now separate axes and diagnose passes the manifest's own flag; the fallback keeps existing callers unchanged.
Verified end to end against a repo armsmith has never seen: recorded from huggingface/text-generation-inference, R3 ran on real numpy config and returned clean, unavailable probes skipped by name, report signed and VERIFY OK. 410 tests, 100% coverage.
Detailed Changes: v1.0.4...v1.1.0
v1.0.4
v1.0.4 (2026-08-09)
This release is published under the MIT License.
Bug Fixes
- release: Attach the built sdist+wheel to the GitHub Release (
99c77d1)
v1.0.3 built armsmith-1.0.3.tar.gz and the wheel β the log says 'Successfully built' β and then shipped a Release page with zero downloads. upload_to_vcs_release is honoured by semantic-release's 'publish' command, not by 'version', and the action only runs 'version'. So the artifacts were produced and discarded.
Adds publish-action@v9, gated on released == 'true' so it is a no-op on runs that cut nothing. PyPI is unaffected either way: that job does its own build.
Detailed Changes: v1.0.3...v1.0.4
v1.0.3
v1.0.3 (2026-08-09)
This release is published under the MIT License.
Bug Fixes
- rules: Stop R4 flagging integer arrays as float64 coercion (
fbeccf2)
R4 flagged any numpy constructor call missing dtype=, with no type inference at all. On a fresh clone of huggingface/text-generation-inference that produced 5 findings, 4 of them false β integer permutation arrays in the Marlin GPTQ path (gptq.py:461,463, util.py:134,136). Worse than noise: the fix it proposed was 'add dtype=np.float32', which applied to numpy.argsort(numpy.array([0,2,4,6,1,3,5,7])) would silently turn an index array into floats.
numpy infers dtype from the data. Verified against real numpy: np.array([0, 2, 4]) -> int64 np.zeros(3) -> float64 np.array([[1,2],[3,4]]) -> int64 np.ones(3) -> float64 np.array([True, False]) -> bool np.empty(3) -> float64 np.array([1.0, 2]) -> float64 np.linspace(0,1)-> float64 np.full(3, 0) -> int64 np.full(3, 0.5) -> float64
So R4 now reasons per constructor: zeros/ones/empty/linspace are float64 whatever you pass them and are always reported; array/full are reported only when the payload is not a provable integer literal (recursing through nested lists and unary +/-).
Anything unprovable stays flagged β np.array(x) with a non-literal argument is still reported, because under-reporting a real float64 coercion on an inference path costs more than one honest question. On TGI that leaves exactly one hit, segments.py:17, which is that conservative case.
TGI: 5 findings / 4 false -> 1 finding / 0 proven wrong. Documented in the README as a precision demo on a repo we have never seen, and pinned by test_r4_does_not_flag_an_integer_permutation_array. 386 tests, 100% cov.
Also aligns the README intro with the roadmap: PR rendering is dry-run and now says so in the first paragraph, not only 350 lines later.
Continuous Integration
- release: Keep v1 floating, wire PyPI trusted publishing (
61c5f5e)
Two adoption blockers, both packaging rather than code.
v1 never existed. README and action.yml both document 'uses: edycutjong/armsmith@v1', but only v1.0.0/v1.0.1/v1.0.2 were ever tagged, so github.com/edycutjong/armsmith/tree/v1 404s and a stranger copy-pasting the CI snippet gets 'Unable to resolve action'. The release job now force-moves v1 onto each new 1.x release, so the documented reference resolves and tracks the latest patch.
Nothing was installable. build_command was empty by design ('nothing is published to an index'), so adoption meant git clone + pip install -e. Now the release builds sdist+wheel and a publish job ships them to PyPI via Trusted Publishing β OIDC, no API token stored in the repo.
The publish job is gated on the PUBLISH_TO_PYPI repo variable and stays inert until the PyPI-side trusted publisher exists: a missing publisher would fail the run and paint the release red for what is purely account setup. Setup steps are in the job comment. The name 'armsmith' is free on PyPI (checked).
The README still says clone-and-install, and will keep saying it until a release actually lands on PyPI β advertising an install command that does not work yet is the exact kind of claim this project refuses to make.
Also drops 'opens PRs' from the package description, which is the text PyPI and GitHub both display; rendering is dry-run today.
Documentation
- readme: Lift the fold β quickstart above, hedges below (
12a32a8)
Getting Started sat at line ~294 of 444. A developer who wanted to run the thing scrolled past ~30 badges, the problem statement, the architecture, the rigor tables and an honesty caveat before reaching a command.
- The four-line quickstart now sits directly under the badges, with the outputs it actually produces (386 passing, 4 kept / 2 dropped β both verified just now), plus pointers to make all and CONTRIBUTING, which the front door never mentioned. - python3, not python: macOS ships no bare 'python', so the documented venv line failed on the first machine a judge is likely to use. - 'Built with' and 'Quality gates' badge rows move down into Engineering Rigor, where they are evidence rather than an obstacle. Top matter keeps the CTA row, build status, and the Arm platform row. - The Status caveat moves below the Solution as 'What is measured, and what is replayed'. It is the right disclosure and the wrong first impression; the thesis line β the LLM plans, the silicon decides β now lands immediately after the pitch instead of behind a hedge.
Detailed Changes: v1.0.2...v1.0.3
v1.0.2
v1.0.2 (2026-08-09)
This release is published under the MIT License.
Bug Fixes
- schema: Serve report.schema.json at the $id it declares (
919ca52)
The repo-root schema/ path is a symlink to the packaged copy, which git stores as a symlink blob β so github.com and raw.githubusercontent both 404 on schema/report.schema.json even though the file is right there. Anyone told to 'build your own viewer against it' hit a dead link.
- README links now point at src/armsmith/schema/report.schema.json,
which resolves for a browser and for curl. - site/schema/report.schema.json makes the declared $id
(https://armsmith.edycu.dev/schema/report.schema.json) actually serve.
A copy, not a symlink: Vercel will not follow one. - CI diffs the served copy against the packaged original, so the
published schema can never drift from the one that validates reports.
The symlink stays β ci.yml reads through it.
Documentation
- Link the badge at the live Devpost submission (
779a195)
Submitted to the Arm AI Optimization Challenge (Cloud AI) as https://devpost.com/software/armsmith-7j1lzt β verified live, and the page carries the repo, armsmith.edycu.dev and the demo video.
The Devpost badge now points at the submission itself rather than the hackathon landing page; the hackathon keeps its own badge beside it, relabelled to name the track. 'docs:' deliberately, so semantic-release does not cut a version for a badge.
- contributing: Fix the org in action.yml, document the detector contract (
d7fc655)
Three things a stranger hit immediately:
-
action.yml's own usage comment said 'uses: edycu/armsmith@v1'. That org
does not exist; copy-pasting it gets 'Unable to resolve action'. The
README had it right, the file itself did not. -
CONTRIBUTING told new contributors to expect 219 tests. It is 384, so
the first checkpoint on the on-ramp failed. -
'Add a 14th rule with zero core changes' was not true: the detector also
needs an import in detectors/init.py or the @register decorator never
runs and the loader raises 'rules without detectors'. Now documented as
one YAML + one detector + one import, with the real detect() signature
spelled out β it was previously undocumented anywhere. -
site: Publish the measured Neoverse-N2 result, retire [PENDING] (
97c356d)
The site and deck were written before the live Arm leg existed and still said the hardware number was [PENDING] in seven places, while the README led with SDOT 0->1 and -86.5%. The two most-visited surfaces contradicted the headline and read as 'they never got real silicon'.
- Hero stat tile + figcaption now carry -86.5% / SDOT 0->1 on Neoverse-N2. - FAQ Q2 answers 'yes, one leg is real hardware' with the full table (0.059975s -> 0.008123s, band +/-0.000144s, gate keep) instead of [PENDING]. - Deck slide 09 moves the measurement into the 'real today' column and updates the CI matrix (8 jobs, 5 native arm64 β it claimed 6 and 2). - Stale counts corrected everywhere: 219 -> 384 tests, ~93% -> 100% coverage.
What stays unclaimed is stated more precisely than before: a throughput multiplier for YOUR model. The measured number is one int8 microkernel on one runner and is never extrapolated. The replay witness stays 0->4 β that is the synthetic fixture and is a different number from the live 0->1.
Detailed Changes: v1.0.1...v1.0.2