Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"name": "claude-code",
"source": "./plugins/claude-code",
"description": "Reference implementation of the Ironclad standard — multi-agent dev harness for Claude Code.",
"version": "0.10.0",
"version": "0.10.1",
"author": {
"name": "qwerfunch"
},
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ assignees: ''

## Environment

- cladding version: <!-- output of `node bin/clad --version` -->
- cladding version: <!-- output of `node bin/clad.mjs --version` -->
- Node version: <!-- `node --version` -->
- OS: <!-- macOS / Linux / Windows + version -->
- Toolchain languages in use: <!-- typescript · python · rust · go · java · php · ruby · elixir · dotnet — list any active for this repo -->
Expand Down
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ GOVERNANCE.md §4.3 is the source of truth for the PR contract.
- [ ] `npm test` — all tests pass
- [ ] `npm run stage:drift` — zero error-severity findings
- [ ] `npm run conformance` — 26/26 fixtures matched
- [ ] `node bin/clad check` — 15-stage gate green on a clean tree
- [ ] `node bin/clad.mjs check` — 15-stage gate green on a clean tree
- [ ] If this PR touches a shipped feature, `spec.yaml` (or the relevant `spec/features/F-NNN.yaml`) is updated
- [ ] A `CHANGELOG.md` entry is added under the next-release heading in the right section (`Added` / `Changed` / `Deprecated` / `Removed` / `Fixed` / `Security`)

Expand Down
138 changes: 137 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,144 @@ jobs:
# class the header warns about. Run them on every change.
run: npm run conformance

- name: Platform-surface floor
# The published bundle must not import a platform surface the declared
# floor lacks. A dependency upgrade that reaches above it is how the
# original defect shipped, so enumerate the bundle's imports and resolve
# each one. This step runs on the build Node; the floor release itself is
# covered by the `entry` job's floor cell.
run: node scripts/check-node-surface.mjs

- name: Self-drift gate (spec-native, strict)
# cladding dogfoods its OWN drift gate under --strict: stale module
# paths, hollow `done` features, and archived-but-live modules fail
# CI instead of accumulating silently. Deterministic, no toolchain.
run: node bin/clad check --tier=pre-commit --strict
run: node bin/clad.mjs check --tier=pre-commit --strict

pack:
# Builds the published archive ONCE, on a platform and release that can run
# the full toolchain, and hands the exact same bytes to every entry cell.
# Building per cell was wrong twice over: it is slow, and it made each cell
# depend on the public test-count guard, which legitimately varies by
# platform (one transaction test cannot run on Windows). That guard belongs
# in the verify job, where it runs on one known platform.
name: pack the published archive
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install and build
run: npm ci && npm run build

- name: Pack
# `npm pack` does not create the destination directory; without the mkdir
# it fails with ENOENT after the archive is already built.
run: mkdir -p archive && npm pack --pack-destination archive

- uses: actions/upload-artifact@v4
with:
name: published-archive
path: archive/*.tgz
retention-days: 1

entry:
# The published entry point, exercised through npm's own bin shim — the path
# a user hits, which `node bin/clad.mjs` never covers. It exists because an
# extensionless entry shipped and died inside Node's ESM loader on an
# unsupported release, on a platform and a Node version this workflow tested
# on neither axis. The floor cell is the release a user actually reported
# from; the Node 14 cell sits below the floor, proving the refusal still
# speaks instead of crashing for a release the tool genuinely cannot support.
# Every cell installs the SAME archive the pack job produced.
name: entry · ${{ matrix.os }} · node ${{ matrix.node }}
needs: pack
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-latest, node: 16, expect: version}
- {os: ubuntu-latest, node: 20, expect: version}
- {os: ubuntu-latest, node: 22, expect: version}
- {os: windows-latest, node: 22, expect: version}
- {os: ubuntu-latest, node: 14, expect: refusal}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}

- uses: actions/download-artifact@v4
with:
name: published-archive
path: archive

- name: Install the published archive globally
shell: bash
# The `./` prefix is load-bearing: without it npm reads `archive/<file>`
# as a GitHub owner/repo shorthand and tries to clone it over SSH.
run: npm install -g ./archive/*.tgz

- name: Unpack the archive so the surface check reads the shipped bundle
shell: bash
run: tar -xzf ./archive/*.tgz

- name: The entry point reports its version
if: matrix.expect == 'version'
shell: bash
run: |
set -euo pipefail
clad --version

- name: Every platform surface the shipped bundle needs exists here
if: matrix.expect == 'version'
shell: bash
run: node scripts/check-node-surface.mjs package/dist/clad.js

- name: Real work on the floor release, not just a version string
# The surface check reads static imports only, so a bundled CommonJS
# dependency reaching for a newer surface through `require` slips past it.
# Running actual commands on the floor release is what catches that. The
# gate is deliberately not run here: this project's own eslint needs
# Node 18, which is the user's toolchain rather than cladding's floor.
if: matrix.expect == 'version' && matrix.node == 16
shell: bash
run: |
set -euo pipefail
clad sync
# Not `clad status | head -3`: head closes the pipe, the writer takes
# EPIPE, and pipefail turns a healthy run red.
clad status > status.txt
head -3 status.txt

- name: Install dev dependencies for the runner tests
# --ignore-scripts on purpose: the prepare hook would run the full build,
# and with it the public test-count guard, which cannot hold on Windows.
if: matrix.expect == 'version' && runner.os == 'Windows'
run: npm ci --ignore-scripts

- name: The spawn runner behaves the same on this platform
# `npm` is `npm.cmd` on Windows and the previous spawning dependency hid
# that. These tests drive real child processes, so this cell is the only
# place the Windows path is actually executed.
if: matrix.expect == 'version' && runner.os == 'Windows'
run: npx vitest run tests/core/run-sync.test.ts

- name: The entry point refuses an unsupported Node with a sentence
if: matrix.expect == 'refusal'
shell: bash
run: |
set +e
output=$(clad --version 2>&1)
status=$?
set -e
echo "$output"
test "$status" -ne 0 || { echo "expected a non-zero exit below the floor"; exit 1; }
grep -q 'requires Node 16 or newer' <<<"$output"
grep -q 'Upgrade Node' <<<"$output"
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ Contributor install (clones the repo and pulls dev dependencies):
git clone https://github.com/qwerfunch/cladding && cd cladding && npm install
```

Requires Node ≥ 20.
Requires Node ≥ 20 to develop — the test runner and linter need it. The published tool itself
runs on Node ≥ 16.

## 3. Verify before pushing

Expand All @@ -30,7 +31,7 @@ Run all four. The first three must pass cleanly; the fourth must be green (the 1
npm test
npm run typecheck
npm run lint
node bin/clad check
node bin/clad.mjs check
```

## 4. Code & comment style
Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ All notable changes to Cladding are documented here.
Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/).
Versioning: [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).

## [0.10.1] — Runs on Node 16, and says so honestly (2026-09-11)

**In one line:** A global install on Node 16 used to die inside Node's own module loader before printing anything; the command-line entry point now loads on every release, and the supported floor drops from Node 20 to Node 16 because the dependency that was holding it up has been removed.

> Heads-up: nothing changes if you are on Node 18 or newer — that already worked. If you are on Node 16 or 17, cladding now runs where it previously refused or crashed. Below Node 16 it stops with one sentence naming the version it needs instead of a stack trace. Two things stay outside the floor: the model-assisted onboarding path needs the network client built into Node 18, so on an older release it names that one missing capability and leaves every other command working; and a check that delegates to your own linter or type-checker still depends on what *those* tools require, which is reported as a tool finding, not a crash.

### Fixed

- **A global install on Node 16 crashed before producing any message.** The published entry file had no file extension, and the module loader refuses such a file in a package that declares modules. The failure happened inside Node, before a single line of cladding ran, so nothing could explain it. The entry file now carries an extension and loads on every release. This was never specific to Windows — the same rejection reproduces on Linux.
- **The supported floor was higher than anything actually required.** It had been set from a dependency's own declaration rather than from measurement. Measured against running releases, the published engine already worked on Node 18, and exactly four runtime surfaces stood between it and Node 16 — three of them from a single dependency used only to launch external commands, and one promise-flavoured module import of our own. Because the engine ships as one bundled file, that dependency's requirement had silently become the whole tool's requirement.
- **External commands now go through one small launcher of our own**, which keeps the behaviour the old dependency provided: the Windows resolution of commands that are really batch files, a single trailing newline trimmed from captured output, a capture limit large enough that a verbose tool's output is not truncated, and an absent exit status when a command could not be started at all. Getting any of those wrong changes a check's verdict silently, so each was measured against the old behaviour before the swap.
- **One interactive prompt no longer decides whether the whole tool loads.** It imported a promise-flavoured module that only exists from Node 17, and that single import made the entire bundle unloadable on Node 16.

### Added

- **A check that the supported floor cannot quietly rise again.** It reads every runtime module the published bundle imports and confirms each one exists on the release being tested. That is how the original problem shipped unnoticed, so continuous integration now runs the check on the floor release itself, alongside a real install from a packed archive on several releases and on Windows.

### Changed

- **The contributor and user requirements are now stated separately.** Running cladding needs Node 16; working on cladding needs Node 20, because its test runner and linter do.

## [0.10.0] — Every acceptance criterion has an address a test can claim (2026-09-10)

**In one line:** Spec schema 0.2 — a feature says what it is for before what it does, every acceptance criterion has an address you can point at, a test claims a criterion by naming it in its own title, one compiled model answers questions about how the pieces relate, each level of assurance has a named check profile recorded in a new attestation format, and a reviewed path carries an old project across.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Thanks for your interest in helping make code iron-clad.
npm test
npm run typecheck
npm run lint
node bin/clad check # 15-stage gate, green on a clean tree
node bin/clad.mjs check # 15-stage gate, green on a clean tree
```
When you change a stage, a detector, or the conformance contract, also run `npm run conformance` to re-verify the 26 fixtures. The runner is a contributor self-audit tool — it depends on dev-only toolchain binaries (`tsc` / `eslint` / `madge` / `secretlint` / `vitest`), so it works after a contributor install (`npm install`), **not** after the end-user install (`npm install -g cladding`).
5. **Add a CHANGELOG entry** under the next-release heading, in the right [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) section (`Added` / `Changed` / `Deprecated` / `Removed` / `Fixed` / `Security`).
Expand Down
6 changes: 3 additions & 3 deletions GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Pre-1.0, minor versions may include backwards-incompatible internal changes and

The maintainer initiates a release with a single instruction (e.g. *"v0.1.0 release"*). Open a `develop → main` PR, merge it with a merge commit (never fast-forward, squash, or rebase), tag and push that merge commit, then mandatory back-merge `main → develop` before publishing. This keeps `develop` a release-commit superset and matches the maintainer ritual in [`CLAUDE.md`](CLAUDE.md).

Pre-F6/current shipped releases retain their existing gate command. After F6, the 0.10.0 final release gate runs once as `node bin/clad check --profile release --strict`; legacy aliases have fixture parity and do not justify a repeated full gate. Release communication distinguishes self profile-complete L2 from separately reported L4 mechanism and reference-host evidence; this design does not rewrite current README assurance values.
Pre-F6/current shipped releases retain their existing gate command. After F6, the 0.10.0 final release gate runs once as `node bin/clad.mjs check --profile release --strict`; legacy aliases have fixture parity and do not justify a repeated full gate. Release communication distinguishes self profile-complete L2 from separately reported L4 mechanism and reference-host evidence; this design does not rewrite current README assurance values.

## 4. Contributor Policy

Expand Down Expand Up @@ -98,10 +98,10 @@ A reviewer (the maintainer or a delegated independent agent — never the PR aut

If this is your first time touching cladding, the path from clone to opened PR is intentionally short. Read this section once and you should be able to land a small fix without further hand-holding:

1. **Clone and install.** `git clone https://github.com/qwerfunch/cladding && cd cladding && npm install`. Node ≥ 20.
1. **Clone and install.** `git clone https://github.com/qwerfunch/cladding && cd cladding && npm install`. Node ≥ 20 for the development toolchain; the published tool runs on Node ≥ 16.
2. **Pick a starting point.** Browse [issues tagged `good-first-issue`](https://github.com/qwerfunch/cladding/issues?q=is%3Aissue+is%3Aopen+label%3A%22good-first-issue%22) or, if you have your own idea, open an issue first to confirm the proposal fits §4.1 / §4.2 before writing code.
3. **Branch off `develop`**, not `main`. Convention: `feature/<slug>` or `fix/<slug>`. Never push to `main` — releases ship via §3.
4. **Run the four-check loop before pushing**: `npm test && npm run typecheck && npm run lint && node bin/clad check`. The first three must be clean; `clad check` must be green (15-stage gate) on a clean working tree.
4. **Run the four-check loop before pushing**: `npm test && npm run typecheck && npm run lint && node bin/clad.mjs check`. The first three must be clean; `clad check` must be green (15-stage gate) on a clean working tree.
5. **Open the PR against `develop`.** The repository's `.github/PULL_REQUEST_TEMPLATE.md` walks you through the §4.3 contract as a checkbox list. A maintainer (or a delegated independent reviewer) signs off before merge.

For code style and comment policy across every language cladding supports, see [`AGENTS.md`](AGENTS.md) §4-5. For the broader first-PR experience, see `CONTRIBUTING.md`. For drift detector conventions specifically (especially the status-aware rule for `UNTESTED_AC` and `MISSING_TESTS`), see [`src/stages/detectors/README.md`](src/stages/detectors/README.md).
Expand Down
Loading
Loading