Repository navigation
Releases: renzrollon/interlock
Release list
v1.7.0
Added
- A checkpoint advisor. After a clean artifact review,
/interlock:specspawns the read-onlyinterlock:advisoragent once on a brief the CLI assembles (interlock advisor <change> --brief). The draft is validated and rendered by code (--check, exit 0 once the change resolves) toopenspec/changes/<change>/advisor.md, and a consult that did not run is written down the same way. Nothing gates on the file and the advisor never writesdecisions.md.interlock advisor --verdict --labels <dir>tallies the ten Spec Range eval cases' human labels against caps ininterlock limits. The spec meter shows the advisor result, its questions, the consult spawn and awaiting on youline. - Compound notes. A review finding may carry an optional
specGap: true; the domain and technical-lead reviewers set it for a requirement no spec stated.interlock note derive|add|matchkeep a table atdocs/notes/compound.md, both adjudicators append to it (a failure is spoken asCOMPOUND NOTES NOT WRITTEN, never an exit code), and the next spec's brief is shown the rows that name a file its tasks touch. interlock evals triage --eval-dir <dir>joins grader types from the case definitions so the first judged graders are classified correctly.
Fixed
- Two writers to
docs/notes/compound.mdno longer lose each other's rows. A ship review's adjudication andinterlock note addon the same checkout each read the whole table and rewrote it, so a concurrent append was lost and an id could be handed out twice (12 concurrentnote addprocesses landed 9 rows). Each append now reads, numbers and writes under an exclusive lock file, staging the table and renaming it into place. A lock still held past a short wait is spoken asCOMPOUND NOTES NOT WRITTEN, never an exit code; a lock left by a writer that died is broken. - Eval triage no longer counts a grader the harness left out of the score. A trial grader written
scored: false(a with-only indicator under--ablation with-without) is excluded, as the harness itself excludes it. - The CI eval jobs can run their cases. Both jobs now pass
--trust-plugin, because without a terminalclaude plugin evalrefuses an untrusted directory before any case runs. They also grant the operator-gated tools their cases list (--allow-tools Bashfor smoke,Bash Edit Writefor the full job). Without the grant the harness withholds those tools, and no Spec Range case could passcheck-ranoradvisor-written.test/evals.test.mjsderives the grant from the cases and pins it.
Changed
- The
interlock advisorandinterlock notehandlers moved frombin/interlocktolib/advisor-cli.mjs(no behaviour change), and a test now fails any shipped file within 16 KiB of the plugin directory's 256 KiB per-file limit.
v1.6.3
Changed
- A halt resume card no longer puts the band above the prompt. The band drew whenever a card of an open change was on disk, and a card stays there until its change is archived, so every session start in that repository showed it, each card as an absolute path, until the next
Hide. The band now draws only for afailorwarncheck, a preflight that could not run or be parsed, or an interrupted run spoken at that session start, and it never lists a card./interlock-preflightstill lists every card and/interlock-handoffstill renders them; the preflight still records them in.claude/ship/preflight.json, and nothing marks, moves or deletes one.
v1.6.2
Fixed
- A missing openspec CLI is named at the first command, with the install line.
/interlock:spec,/interlock:bootstrapand/interlock:dispatchnow runopenspec --versionfirst and, when it prints no version, stop before any exploration or writing.command not foundis reported as a missing CLI withnpm install -g @fission-ai/openspec@latest; any other error is reported as a CLI that is installed but does not run (usually Node.js older than 20.19.0), with its error and no install line. Dispatch no longer reads the missing CLI as an empty project and routes it to bootstrap.interlock doctor's no-CLI fix text spells that command (OPENSPEC_INSTALLinlib/doctor.mjs), and the doctor now warns whenopenspecis on PATH but its version probe does not run, instead of reporting ok. The session-start closer names/interlock:specand/interlock:bootstrapalongside/interlock:shiponly when anopenspecorbinariescheck fails; a missing test profile or allowlist rule still holds back ship alone.
v1.6.1
- chore: bump version to 1.6.1
- fix(ship): recover relays and stop transport failures halting runs
v1.6.0
Added
-
A merge verdict and score, computed by the CLI (
lib/merge-confidence.mjs).assessMergeConfidencetakes the facts a review and a run already produce and returnsMERGE,HUMANorBLOCK, a score, and one entry per registered signal with its band (green,amber,red,unobserved), its flags, its evidence and its reason. The verdict is the worst band over the gating signals, and an unobserved signal ranks as amber. A flag lowers the score and never moves a band. The score sits inside the worst band's range, so a red never outscores an amber. Signals live in one frozen registry (SIGNALS, seventeen gating signals and the informationalplan.fidelity) and flags in one published list (FLAGS);combineSignalsis the formula and names no signal. The module is pure.- Every gating signal has a producer.
conformance.livingandspec.coveragewere registered with no producer and read unobserved; both now read the facts their rules name (see below), so a clean, reviewed, verified change can readMERGE. A signal reads unobserved only because a fact about the change is missing, and its reason says which.
- Every gating signal has a producer.
-
interlock merge-confidence [change] --changed <files...>. It reads the change's artifacts, its decision ledger, its delta-spec scenario count, drift andHEADfrom the repository, and--review,--verify,--handoff,--pipelineand--coveragefrom files the caller names.--base <ref>adds the diff shape fromgit diff --numstat, and every path that diff reports joins the changed paths, so a path left out of--changedstill reaches the risk classifier; a base that isHEADor ahead of it reads the diff shapeunobserved. It is offline and never callsgh,glabor the network. It exits 1 onBLOCK, or on anything butMERGEunder--require-merge.HUMANexits 0. An input it cannot read or parse also exits 1, with the reason on stderr and nothing on stdout, asgatedoes. The pipeline file and the coverage file are defined here and produced by later changes (E3, E8). -
MERGE_BANDSinlib/limits.mjsandMERGE_ALLOWED_CLASSESinlib/risk.mjs.MERGE_BANDSholds the band ceilings and every threshold the verdict applies. It is a separate export besideLANE_CAPS,interlock limitsprints one row per entry and per ceiling,--jsonpublishes it asmergeBands, and the cap-authority sweep covers it.MERGE_ALLOWED_CLASSESholds the risk classes the merge verdict reads green. It sits besideCONTINUITY_ALLOWED_CLASSESand holds the same classes today, so the two policies can diverge without one moving the other. -
The
verify judgeverdict carriestypecheck: { ran, exitCode, passed }, ornullwhen no type-check result was reported, in every context. A passing type check used to leave no trace, so "passed" could not be told from "never reported". No key is renamed and no halting set moves: a failed type check at the final gate still prints its non-blocking banner and does not halt. -
Coverage figures the CLI computes (E8). A pure lcov reader gives a project total and changed-line coverage on canonical repository paths, whether the report wrote a file absolute, relative or with a leading
./; a record outside the repository is left out and counted.interlock verify baseline [--report <lcov>] [--baseline <file>]records{ schema, sha, ref, totalPct, generatedAt }for the current commit, and refuses (exit 1, previous baseline untouched) when the report has no project total. It writes to the working root's testing directory, never the state home.interlock verify coverage --base <ref> [--report <lcov>] [--baseline <file>] [--compute-base]prints the coverage inputmerge-confidence --coveragereads and no band:configured,headTotalPct,baseTotalPct,baseSha,baseIsAncestor,diffPct,changedLines. A baseline is usable only when its commit is an ancestor of the head, which is read from git and never from an age. A profile with no coverage command printsconfigured: false; a declared report that is missing or not lcov exits 1 with nothing on stdout.--compute-basemeasures the merge base in a temporary worktree that is removed whether the command passed or not.verify execis unchanged. The brief'sverify exec coverage --write-baselinewas not built, becauseverify execis a pass-through.
-
## Always Human ReviewinREVIEW.md, and policy read from the base (E4). The section is parsed with the## Do Not Reportgrammar and enforced only by the merge verdict; it never moves a finding or a gate.--base <ref>oninterlock reviewandinterlock review-policyreads both path lists from theREVIEW.mdcommitted at the ref and the prose from the working tree, and says when the two copies differ. A ref that names no commit exits 1 before a review is decided. Without it both commands read the working tree as before.merge-confidence --basemakespolicy.humanPathsobservable: amber, needing a person, for a changed path under a listed path, for any edit toREVIEW.mditself, and for a committed section that could not be read in full; green otherwise; unobserved when no base was given.- A threshold-shaped list item is now rejected in both path sections, as a threshold-shaped key already was.
startShaon the run manifest. A ship run records the commit it started on and reads the lists from it. A manifest written before this change has none and reads the working tree.- Two banners:
POLICY DIFFERS FROM BASEwhen the working copy differs from the start commit's, andREVIEW BASE NOT PINNEDwhen no start commit could be recorded. Both reach the close summary.
-
A
domainreview dimension (E5). A seventh rubric,skills/review-code/dimensions/domain.md, whose reviewer is handed the living requirements in force for the capabilities a change touches, quoted as data. Capabilities are selected from reliable spec-to-file edges and from the capabilities the change's own delta specs modify.LIMITS.maxDomainSpecCharsbounds the text at a requirement boundary and names what was left out.interlock limitsprints it asdomain spec text budget (chars), and--jsoncarries it.interlock domain-context [change] --changed <files...> [--base <ref>] [--json]serves the same block to the review skill and exits 0 on a completed report; a--basethat names no commit exits 1.- A finding may carry
requirement: openspec/specs/<cap>/spec.md#<heading>. Adomainblocker with no citation is a problem; the CLI checks the citation against the spec at the base and reports a failed check without dropping the finding. "Blocker" is read as the gate reads it, soBLOCKERandblockerare reported too. domain-skippedflag onreview.ranwhen the dimension was skipped for want of a graph. It leaves the band to the rest of the record.- Two banners:
DOMAIN REVIEW SKIPPEDandDOMAIN REVIEW PARTIAL, each with its reason.
-
interlock spec-audit [--json](E7). A non-gating report in thedriftfamily of what each living capability states: requirements, scenarios without aGIVEN, failure and edge-case scenarios, bound and prohibition lines, numeric literals in requirement prose for a person to classify, and with a graph the broken file links and the coverable source no spec names. A measure it cannot take is stated with its reason and never printed as zero. It always exits 0. The suite pins the structural invariants of this repository's committed specs and never their counts. -
docs/15-merge-confidence.md. The baseline recipe with the default-branch job,--compute-baseand its limits, the always-human-review section and why lists are read from the base, and how to read a spec audit. -
conformance.living: the handoff confirms the living requirements a change touches (E6). The signal compares the handoff'srequirementsCheckedandrequirementsUnconfirmedwith the number of entries in a living list the command counts itself; arequirementsRequiredin the input is ignored. Green when every listed requirement was checked and none is unconfirmed, amber (needing more evidence) when any is unconfirmed, a count is missing or the counts differ, unobserved when the list has entries and no handoff was given.interlock conformance --living [--base <ref>]lists, after the scenarios, the living requirements in force for the capabilities the change touches (L1..Ln, spec path, line and heading, never the body), selected, read from the base and bounded exactly as thedomainreview's text is, so the two cannot disagree. It exits 0 in every case; a--basenaming no commit is the reason the list was not built. Without--livingthe output is unchanged.- The ship handoff asks about them under
--conformance, andHANDOFF_RESULT_SCHEMAcarries the optional countsrequirementsCheckedandrequirementsUnconfirmed, recorded on the manifest and printed as aliving requirements:row. requirements-omittedflag when the size bound left requirements out. It lowers the score and never moves a band.
-
spec.coverage: is the source a change adds named in a spec? (E6) Amber, needing a fix, for a coverable source file the diff adds that no living spec and none of the change's delta specs names, and for a kept file whose only naming spec the diff changed or deleted. An edited file nothing ever named is thelegacy-unspeccedflag, with the repository's named-over-total figure. It reads spec text and git, never the graph, throughspecMentionedPathsinlib/spec-text.mjs(the rule the graph's extractor now shares) andlib/spec-coverage.mjs, so it reads the same in CI as on a developer's machine. Unobserved with no--base. -
One gather for the command and the close (
lib/merge-evidence.mjs).gatherMergeEvidenceassembles the assessment's input for `inter...
v1.5.1
Changed
docs/08is about Interlock's own harness. It was a survey of OpenClaw, Hermes Agent and DeepSeek Harness. It now maps the parts of the harness, what holds on each host, and what is left out on purpose; the other projects are one comparison table. The page no longer says lane isolation is closed on the Workflow host, which still halts withLANE BASE MISMATCHafter the first fold.- The README says what the one network call sends. It said a push happens when
INTERLOCK_NTFY_TOPICis set. It now names what goes out (the close summary's first line, the change name and the run id; the change name at the spec checkpoint; the title and body of anotifyyou type), which server receives it, that the topic is the only thing in the request that identifies you, and that no request is made without one. It also says the plugin'sntfy topicoption reaches the CLI through the session's environment, and thatinterlock doctorreads a model credential by name and never by value. The plugin directory holds a plugin that reads a value from the environment and posts to a server until a reviewer has read this.
Fixed
- The session-start hook returns the forward itself. The
classic.SessionStarthook inhooks/mod.mjsheld the forward in a variable, awaited the preflight read, and returned the variable. The plugin directory's reader could not tell that return from a first message or a permission decision of the hook's own, and held the version. The hook now returns the forward call, continued once to read the preflight's report and hand back the value the chain resolved with. Nothing a session sees changes: the read still finishes before the event resolves, and a chain that rejects still skips it.test/mod/session-start.test.tsandtest/spine/mod-pins.test.mjscover it unchanged.
v1.5.0
- chore: bump version to 1.5.0
- feat(readme): improve and add interlock gif
- Merge pull request #15 from renzrollon/fix/fit-ship-and-graph-to-compiled-repos
- fix(ship,graph): bound rejected reports and say when a graph is partial
- fix(ship,graph): fit ship runs and the graph to compiled-language repos
v1.4.2
- chore: bump version to 1.4.2
- fix(meter): follow wrapped ship relays and keep the live pane's tail in view
v1.4.1
- chore: bump version to 1.4.1
- fix(ship): correct the replay-cache key and the 1.4.0 resume note
- feat(meter): draw the ship pane as a timeline with a time gutter
v1.4.0
- chore: bump version to 1.4.0
- feat(ship): show lanes as tasks and name every agent in the meter