Repository navigation
Releases
v1.24.0
The spec track (Track S, Experimental): three advisory skills before a ticket exists. A spec is
read by fresh reviewers and gets a counted verdict before any code. None of the three closes a gate,
calls a lifecycle skill, or creates a ticket or an epic; the one link to the lifecycle is a
Spec: <path> line a user may add to an epic or a ticket, which design reads (S4).
- Added (skills):
/mango:brainstorm(idea → brief: one open question per turn, reviewer lenses
only after a readiness gate the user passes),/mango:spec(brief or notes → spec: one section at a
time, the user chooses every decision from at most three unranked options, the spec is compared with
its brief and never edited to match it),/mango:spec-review(one reviewer per lens in parallel; a
lens with no result line is retried once, thennot run, never zero findings; resolve as CONTEST,
ESCALATE, PROPOSE FIX or DECLINE; the review record goes into the document itself, on the user's yes). - Added (agent):
spec-reviewer—tools: Read, Grep, Glob,omitClaudeMd: true, Sonnet.
validate.pypins both host keys. The host behaviours it relies on were checked on Claude Code
2.1.321 against the docs and a reproducible throwaway plugin (docs/spikes/spec-track-host.md);
omitClaudeMdneeds 2.1.271 or later and does not stop a subdirectory CLAUDE.md from attaching. - Fixed before release (manual trial): a real
/mango:spec-reviewround on a sample spec found
that three of four reviewers could not locateprinciples/spec-review.mdand one read a copy from
another checkout. The dispatch now passes the principle's absolute path, andspec-reviewerreads
only that path and never searches for a copy (agent 353 → 375 words, ledger raised). The round
itself caught the planted defect — a per-tenant purge over a shared directory — through all four
lenses. - Added (principle, templates):
principles/spec-review.mdholds the lenses, severities, resolve
rules,not runand convergence once for all three skills;templates/spec.md,templates/brief.md. - Added (counted lines):
BRIEF,SPEC,DIVERGENCE,SPEC REVIEW, parsed bycheck_lines.py
under the new--phase brief | spec | spec-review. A spec phase holds a document only to its own
lines, never to a lifecycle line. Ten envelope tests. - Changed (
design, S4): a ticket or epic with aSpec: <path>line gets one design evidence
record in Phase 2 — path,sha256, the spec's lastSPEC REVIEW:line,provenance: reported—
shown at Gate 2 with any open Blocker ornot runlens. It informs Gate 2 and never closes it. No
Spec:line, no change.analysisis not touched andspeccreates no ticket file: the plan's
wider S4 was cut so the spec skills stay independent of the lifecycle.design3090 → 3164 words
(ledger raised): the record's fields are the behaviour, and no existing text covers them. - Changed (
doctor): check 11, informational — a host below Claude Code 2.1.271 is a ⚠, since it
does not support theomitClaudeMdkeyspec-reviewerrelies on. - Added (config, optional):
specs_dir(defaultdocs/specs/),spec_review.max_rounds(default 3). - Decided, not added: no skill sets
disable-model-invocation, like every other mango skill; a
narrowdescriptionlimits unwanted auto-invocation, and the flag is reconsidered only if measured.
The spec track's grammars ship before Phase 2'sGATEline rather than after it: noGATEchange is
in flight, and the new grammars only add registry entries. - Size: the full-run words rise 34,360 → 34,447 (+87): one row in
PRINCIPLES.md's companion index,
whichvalidate.pyrequires for every companion (ledger raised 2653 → 2666, +13), and thedesign
spec link (+74).maturity.md923 → 979
for the new Experimental entry. The three skills total 1,756 words; nothing new loads in a full run. - Smoke guard: six new fixtures (
spec-review-not-run,spec-review-contest-evidence,
spec-zero-decision,spec-divergence,brainstorm-readiness-gate,design-spec-evidence); their
30 dispatch-free self-tests pass. Each fixture passed 3× fresh (--no-cache, sequential; CLI 2.1.322, model
cli-default). That is a smoke guard, not a regression suite. The whole-guard run then failed one
wording token onmulti-clause-want(the outcome was right: two rows, the single-row ✅ rejected, but
it said "two separate behaviours", not "clauses"); the token is widened to
clause|behaviou?rs?|halves, over wording only, with a self-test proving both ways. The whole guard
then passed fresh: 14/14 fixtures, 148/148 assertions, at3b16bec.
v1.23.2
The RULE SECTIONS shape no longer reads as a list split by /. The red 1.23.0 e2e run wrote
its sections with / between them, copying ✅ / N/A (reason), …, where / meant "or";
check_lines counted one section against 3 applicable and stopped the run at Gate 1 (fail-closed,
not false-green).
- Changed (skill text —
analysis,quick; templateticket.md): the shape now shows two
entries,§<id> (<source>) ✅ <what it constrains>, §<id> (<source>) N/A because <reason>, …, and
statesSeparate entries with,only.TheN/A because <reason>form the prose already required
is now in the shape itself.check_lines's canonical form,validate.py's rule-first check and
docs/reference/lines.mdfollow it. - Not changed: the checker.
/is still not a separator, so a line written that way still fails
the count — a new envelope test pins both the,pass and the/fail. - Size:
analysis3621 → 3620 words,ticket.md4273 → 4272 (a redundant clause cut in each). - Smoke guard:
multi-clause-want(mapped toanalysis) green, 57/57, fresh run. That fixture
does not emit aRULE SECTIONSline, so it shows the edit broke nothing it covers, not that agents
now write the line correctly; the golden e2e is that check.quickhas no mapped fixture.
v1.23.1
Three defects the 1.23.0 golden e2e found (3 runs on 1865fb1: 2 green, 1 red; the red run is
explained under Not changed below). Scripts only: no skill, agent, principle or template text changed.
- DISCLOSURE no longer claims a review ran.
run_contract.py disclosure-seedwroteON — the rule-book-grounded reviewer ranandON — the ticket-blind challenger ranbefore any phase had
run. A run that stopped at Gate 1 carried both claims; the agent had to correct them by hand. Line
one now readsON — enabled for this run, and states that whether the seat ran is the review phase's
record. proof.pyreads aunittest discovercommand. The unittest preset put-vbeforediscover,
which unittest rejects (unrecognized arguments: -s tests -t .), so a design naming
python3 -m unittest discover -s tests -t .wascould-not-run.-vnow goes afterdiscover.
The run disclosed it and measured with--command; nothing read clean.ticket_stats.pyreads an embedded working doc. Underwork_doc_mode: embedthe header sits
below the separator, after the ticket's own##sections; both green runs printedlane=unknown scope=unknown(exit 3). The script now reads below the separator, throughcheck_lines's own
reading of it, and also reads the backticked header shape (`TIER: full`).- Not changed: the red run. It wrote
RULE SECTIONSwith/between the sections;
check_linescounted one section against3 applicableand the gate stayed closed, so the run
stopped at Gate 1 with a correct account. The shape inskills/analysisreads✅ / N/A (reason), …,
where/means "or"; making that unambiguous is a skill edit and needs its smoke guard, so it is
left to its own change.
v1.23.0
Aggregate rows from real tickets. A script prints one row of counts from a working doc, in the
project, so cost and challenger figures can leave a real project without its text.
scripts/ticket_stats.py <workdoc> [--row]. Read-only. PrintsCOST: lane=… scope=… dispatches=… dispatch_tokens=… unmeasured=… session_tokens=… minutes=… gates=… not_met=… mango=… date=…, or with--rowthe same fields as adocs/COST.mdtable row. Every field has a fixed shape
(a number, a fixed word, a version, a date) and is checked against it before printing, so no ticket
key, path, name or text from the doc can reach the output; no message quotes the doc.- Read from the doc, never estimated. Lane from
TIERand aDISCLOSURElist under## Phase 5;
scope fromSCOPEand theReviewed atfile list; dispatches, tokens andunmeasuredcells from the
Cost ledger; gates fromGate <n> status: cleared; raisednot metfrom the firstCHALLENGER VERDICT; the date fromLast updated:. Session tokens, minutes, the mango version and how many
not metitems were real are not in the doc: they come from--session-tokens,--minutes,
--mango,--real, or printunknown. Exit 3 when a field the doc should carry was not found. docs/COST.md. The table's columns are now the script's fields.validate.pyrefuses a header
that differs from them, a row with another number of cells, and any cell outside its field's shape.
The USD,cost_tierand model columns are gone: they are outside the approved aggregate list. The
Field report issue template now asks for the printed row only.- Runtime words unchanged. No skill, agent, principle or template text changed.
v1.22.0
Measured proof. For a working doc that declares PROTOCOL: v0.2, review no longer takes "fails
without the change" on the author's word: a script runs the proving test twice and counts the result.
PROTOCOL:header field.check_linesreads- **PROTOCOL:** v0.2(or`PROTOCOL: v0.2`)
from the doc's header — before its first##section, so a quoted mention never declares — and names
it on thedocline. A doc without the field islegacy: its report is unchanged, andPROOFis
never required of it. An unknown version is NOT-CHECKABLE (exit 3).templates/ticket.mdnow declares
v0.2, so new tickets opt in.proof.pyand thePROOFline. Run 1: the base source with the test files the branch added or
changed (verification.proof.test_paths). Run 2: the head; a test that does not pass is re-run once
there. Each test is classified by the kind of failure, never by how many fail:proven,
regression-guard,green@base,env-fault@base(an import of something the diff does not add, a
connection error),not green@head(failed, errored, skipped),flaky,could-not-run. Both runs
happen in throwaway worktrees, removed on every path out.- Registered grammar and gates.
PROOF: <n> proving test(s) | <p> proven | <r> regression-guard | <z> green@base | <e> env-fault@base | <g> not green@head | <f> flaky | <c> could-not-run, identity
n = p+r+z+e+g+f+c. Required at review of a v0.2 doc. Blocks Gate 4 whenz > 0,g > 0or
n = 0.PROOF waived (verification.proof.enabled: false)waives the requirement and is disclosed. designdeclares regression guards on a**Regression guards:**line (tests green at base by
design: refactor, docs, config); Gate 2 approves it.reviewstep 6b runsproof.pyand pastes
its output; exit 2 is not clean, exit 3 is disclosed.- Presets.
unittestonly (FAIL and ERROR are red; results read from-voutput). pytest and every
other runner arecould-not-rununtil a recorded sample proves how to read their output: no pytest
was available to record one. - Config.
verification.proof.enabled(default true),timeout_s(default 300),test_paths. - Maturity. Measured proof is Experimental (
principles/maturity.md). - Words.
design,reviewandtemplates/ticket.mdstay within their ledger: the additions are
paid for by sentences that restated a reason.design3,100 → 3,090,ticket.md4,278 → 4,273,
principles/maturity.md929 → 923,reviewunchanged at 3,504.
v1.21.1
autorun leaves no stray files. It now writes one file outside the change and the working doc —
the run contract — and that file has an end of life.
.mango/ignores itself.autorunwrites.mango/.gitignorecontaining*, so the folder never
shows ingit status. It never edits the project's.gitignoreor.git/info/exclude.- The spec is scratch.
spec-<KEY>.jsonis written to amktemp -ddirectory outside the repo and
removed afterrun_contract.py write. - DISCLOSURE is never a file. It goes at the top of the PR body and under the working doc's
## Phase 5heading. - The contract's end of life.
reconcile.py run <contract> --phase post-mergeprintsDONEand
thermcommand when every declared condition holds. It never deletes the file itself. - The golden e2e judges it.
assert_run.pyis red when.mango/holds anything but its.gitignore
and the run contract, when the folder shows ingit status, or when DISCLOSURE is missing from the PR
body, the working doc and the final output. - Skill text. The retired forced-case control and the
--treeparagraph inautorunlose their
history sentences (kept here and inRATIONALE.md), so the skill stays under its word budget. - Upgrading. Folders left by earlier runs are not touched. After each merged ticket's post-merge
check, remove its files:rm .mango/run-contract-<KEY>.txt .mango/disclosure-<KEY>.* .mango/spec-<KEY>.json.
v1.21.0
TREE-COMPARISON is generated, and waits for the merge. Golden e2e findings #1 and #5 (#20).
- The problem, measured. In the 12 RUN CONTRACTs kept from golden e2e runs of one ticket, the floor
condition'scheckmeant one of two things:- 4 compared the base to the branch. That is the condition's intended meaning (envelope test T5),
but before the merge it is BROKEN by construction, so every correctautorunclosed onq > 0. - 8 compared the remote branch to the local one. That holds at close, but it repeats
LOCAL-HEAD-PUSHED, and it HOLDS on a correction pushed after the merge, the incident the condition
exists for. That is a false green. - Every one of the 12 passed the old contract checks, which looked only for
git diff --quietand
refused a grep or an ancestry predicate. The path list took 4 shapes and thebaseheader 5.
- 4 compared the base to the branch. That is the condition's intended meaning (envelope test T5),
- A — the floor checks are generated.
run_contract.py writerendersTREE-COMPARISONas
git diff --quiet <remote>/<base-branch> <remote>/<branch> -- UNBOUND ${CHANGED_PATHS}. It renders
LOCAL-HEAD-PUSHEDas arev-parse --verifycomparison ofrefs/heads/<branch>with
refs/remotes/<remote>/<branch>.- A spec or contract carrying any other spelling is refused (exit 2), never silently corrected.
baseis now<branch>@<sha>, its sha derived bygit rev-parse. A supplied sha the repo
contradicts is refused.branchandremotemust be plain ref names, because they reach a shell.CHANGED_PATHSis bound at Gate 2 to the approved change list, plain path tokens only.- The grep and ancestry refusals are kept.
- B —
pending-mergeand--phase post-merge.- The RECONCILE line gains a seventh axis:
… | <c> could-not-run | <w> pending-merge. - At
close,TREE-COMPARISONis not run and is countedpending-merge: never holding, and never
reported as verified. --phase post-mergeruns every condition. A BROKEN there prints READ THIS FIRST; a ref that did
not resolve (a remote branch deleted at merge and pruned) is named as unverified.- An unknown phase is refused.
t0is unchanged. - The DISCLOSURE seed adds a
POST-MERGEline carrying the exact command (git fetch <remote>, then
reconcile.py run <contract> --phase post-merge).autorunputs it in the PR body and does not
run it itself.
- The RECONCILE line gains a seventh axis:
- The golden e2e judge now requires the contract to validate at close and re-runs RECONCILE at
post-mergeafter its simulated squash. Every condition must hold, with nonepending-merge. - Incompatible with older contracts. A ≤ 1.20.0 contract carries an authored check and a free-form
base, so 1.21.0 refuses it. This matters only for a run that spans a plugin upgrade, and the
contract'splugin-versionnames the cause. - Tests and enforcement:
validate.pyenforces each item above.- Envelope tests:
- render and refuse;
- every observed free-form
baseand both observed tree forms; - pending-merge at close, and post-merge HOLDING after a squash;
- post-merge BROKEN on a correction pushed after merge, with the refused form shown HOLDING on that
same correction; - t0 unchanged, and the disclosure line.
v1.20.0
Two gate contracts the golden e2e found unstable. Both were found across repeated unattended runs of
the same ticket. Each run was correct, but the artifact a gate rests on came out in a different shape.
CHALLENGER VERDICT:, a new counted line emitted byreview.- Grammar:
CHALLENGER VERDICT: <n> requirement(s) rebuilt | <m> met | <x> not met | <u> can't tell | <g> not-met covered by a recorded exclusion. - Why: review's "clean" rests on the challenger finding every requirement met. The challenger's brief
fixes three counts and no grammar, so across five runs the same verdict appeared as
7 requirements reconstructed — 7 met, …,7 met | 0 not met | …,7 met / 0 not met / …and
7/7 … met, …. - Review now counts the rows of the challenger's numbered table into this line.
check_lines.py
checksn = m + x + u,g ≤ xandn > 0, and the gate requiresm + g = n. A requirement not
met, or can't tell, outside a recorded exclusion fails the gate with exit 2. - When it is required: at review, in the full lane only. It is not required when the doc records
CHALLENGER: OFF (--no-challenger), so a recorded waiver stays the only way to omit it. - The challenger agent is unchanged; only review's transcription is new.
- Grammar:
- From finalise on, the tree under review is the
Reviewed atSHA.finalise, and theautorunandsolvegate callers, previously passed--tree $(git rev-parse HEAD). So a lesson committed after review made every evidence block recorded at the reviewed
commit "from ANOTHER tree". One run re-ran its suite just to satisfy the checker; another ran the
checker before committing.- Finalise's step 1, the stale-review guard, already proves that only bookkeeping lies between the
reviewed commit andHEAD. That commit is therefore the tree under review, and finalise now passes
it. - Still one tree, checked strictly. Through review,
--treestays$(git rev-parse HEAD).
validate.pyenforces both. Nine envelope tests cover the new line both ways: pass, sum mismatch,
not met, can't tell, excluded not-met, zero requirements, missing at review, the waiver, and not
required before review or in the lite lane.
v1.19.2
Fix: pre-change evidence no longer stops every honest run at review. Since 1.14.0, check_lines.py --tree HEAD refused every empirical-output block not run on the tree under review — including the
three blocks the lifecycle requires to run before the change exists: analysis's BASELINE, design's
blast-radius trace, and execute's fails-without-the-change run. An unattended autorun therefore could
not close Gate 4 on a correctly written working doc. Found by the first live run of a golden end-to-end
test on a generated sample project; neither the envelope suite nor the per-skill smoke guard exercises
two phases together, so neither could see it.
Ran at <sha> (pre-change)— a block that by design ran before the change declares that role.
check_lines.pyaccepts it only when--base <sha>is given and the SHA is that base (the
commit the branch was cut from; for the fails-without run, that code plus only the new proving
tests). It is counted on a new trailing field of theevidence :line,<p> pre-change on the base,
and never as evidence on the tree under review.- Nothing else is excused. A
(pre-change)block naming any other SHA, or checked without
--base, is refused exactly like stale evidence (exit 2). An undeclared block naming the base is
still stale.(pre-change)does not rescueprovenance-unknown(exit 3). Post-change runs still
carry the reviewed tree's SHA. review,autorun,solveandfinalisepass--base $(git merge-base HEAD <default branch>);
analysis,design,executeand the ticket template name the role.validate.pyenforces both;
six envelope tests prove the role both ways.
v1.19.1
Patch: adoption fixes from a review of 1.19.0 — first-run stumbles, a one-screen README, and
adoption and cost pages. No gate, agent brief or doctor check changed; one skill (challenge)
gains a STOP for a case it left undefined. Released as one version rather than several, so the
Releases page does not read as constant churn.
- The hand-fill path now passes
doctor. Both READMEs andharness.minimal.jsontold a user to
copy the minimal config, replaceREPLACE_MEand createtickets_dir— butdoctorcheck 3 reports
❌ when the file atrulebook_pathdoes not exist, so a user who did every step still could not start
solve. They now also say to create the rule book file (an empty file is enough:doctorreports it
as a ⚠ stub, not a ❌) or to run/mango:init.doctoris unchanged — it still fails on a missing rule
book.validate_harness_minimalnow requires the instruction in both READMEs and the config comment. /mango:challengeleads the root README. The zero-config entry point sat afterinit/doctor
/solve, inside a paragraph. A new Try it in 2 minutes section right after Why mango? shows the
three commands; the old paragraph is removed so it is introduced once.validate_challenge_standalone
checks it appears in the first 40 lines and exactly once./mango:challenge <KEY>with no ticket read configured now stops. Outside local mode the skill
read a KEY only throughtracker.read_mcp; a project with onlytracker.clifell into an undefined
case. It now stops with "no ticket read is configured; pass the ticket as a file path", and never
reads throughtracker.cli, the write channel. Two contract tokens added forchallenge.- The pin example names the current version. The README showed
#v1.18.2, a tag without
/mango:challenge, so copying it verbatim installed an older mango. It now shows the current tag, and
validate_release_workflowfails unless every pin example and the version badge matchplugin.json—
the badge was the release-checklist item most often left stale, and it is now enforced too. - Root README is 120 lines, from 261. The autorun detail, the
SECTIONS/ROWSand
surfaces provenexamples, the full prerequisites list and thecodify/promoteparagraphs
moved to the plugin README (new Prerequisites and What the artifacts look like sections) or were
already there; the cross-skill-regression example moved todocs/MATURITY.md. The challenger table
stays, under Try it in 2 minutes, since that is what the command prints. Nothing was dropped: each
removed sentence was checked against the README, plugin README,docs/andCONTRIBUTING.md.
validate_root_readme_docsfails a root README over 120 lines. harness.example.jsonis grouped REQUIRED / RECOMMENDED / ADVANCED. Section markers
("//== REQUIRED (doctor fails without these) =="and so on) head each group; no key was added,
removed or renamed, and the parsed content is identical apart from the three markers.
validate_harness_minimalchecks the markers are in order and that the REQUIRED group is exactly
doctorcheck 2's list.docs/ADOPTION.md— a four-week rollout, one action and one number per week, starting with
/mango:challengeon already-merged PRs; when not to use mango; an FAQ.docs/COST.md— the measurement protocol and an empty table. It holds no figures: none have
been measured. Token counts come from the working doc's dispatch-only Cost ledger plus the host's
session figure, since mango does not measure the main loop. A one-lineCOST:summary format is
proposed there and is not implemented.validate_root_readme_docsfails a data row that carries
no run date, and an empty table that does not say it is empty. Both pages join the jargon scan.docs/STABILITY.md— semver over the skill / config / counted-line API, a one-minor deprecation
notice before any removal, and batching related changes into one release.mainstays the release
channel. Linked from the README andCONTRIBUTING.md.- Issue templates —
bug_report.yml, andfield_report.ymlwhose fields match thedocs/COST.md
columns, so external runs can be added to that table. Blank issues are off.