Skip to content

v2.0.8

Choose a tag to compare

@sohei56 sohei56 released this 02 Aug 05:54
· 71 commits to main since this release

Maul Team for Mac v2.0.8

Reliability release. No Mac app UI changes — everything here is in the
bundled framework.

A whole-codebase audit (8 read-only axes, 81 raw findings, ~71 distinct)
turned up 12 real bugs. Six of them were invisible from inside the
framework repo: they only fire in deployed target projects, which is why
the test suite stayed green throughout and CI never caught them. The
framework repo is immune to each for a structural reason — files are
git-tracked here, and the guards under discussion are deliberately not
registered here — so the masking was systematic rather than incidental.

The phase/path guard was inert in every deployed project (#85)

hooks/status-gate.sh emitted a top-level {"decision":"allow"|"deny"}.
Claude Code's top-level decision enum is ["approve","block"] —
allow/deny belong to the nested
hookSpecificOutput.permissionDecision. The object failed schema
validation, no permission decision was applied, and with no exit 2 path
the tool call proceeded.

Unenforced as a result: "only pbi-designer may write
docs/design/specs/", phase-based source-write gating, catalog.md
read-only, and catalog-config enablement.

The tell was in-repo: hooks/lib/validate.sh calls exit 2 "the Claude
Code hook deny convention", all five other guards use it, and
status-gate.sh's own header comment described the contract it did not
implement. Verified against the shipped binary (2.1.220).

The allow path deliberately stays a bare exit 0 rather than
{"decision":"approve"} — an explicit approve would bypass the user's
normal permission prompt
for every Write/Edit.

Also: Claude Code matchers built only from [A-Za-z0-9_|, -] take the
exact-name membership branch, not a regex branch, so "Write|Edit" never
substring-matched NotebookEdit. All three PreToolUse guards now name the
notebook tools explicitly, in both settings surfaces.

Two breaking schema changes shipped with no migration (#85)

Both affected files sit in migrate-state.sh's blocking STRICT_MAP,
so an affected project hard-failed launch at exit 65 and the team never
spawned — with no documented repair.

  • 003-drop-improvement-category.sh — e1958ec removed category from
    improvements.schema.json under additionalProperties: false. Its own
    commit message said "Existing state files must be migrated (strip the
    key)"
    ; none was written.
  • 004-drop-pbi-state-phase.sh — 001-legacy-to-ssot.sh skipped per-PBI
    files on the stated grounds that "projects in flight legitimately carry
    richer fields", which contradicts additionalProperties: false. A v1
    file still carrying phase blocked the launch gate.

Each migration's first test asserts the pre-migration file is genuinely
rejected, so the launch-block is proven, not assumed.

Cross-model review was unreachable from PBI worktrees (#85)

Reviewers cd into .scrum/worktrees/<pbi-id> before sourcing
codex-invoke.sh, and a worktree checked out at sprint.base_sha cannot
see untracked files from the main working tree. In any project set up by
setup-user.sh, the source failed and every codex second opinion reported
itself "unavailable" — for an entire Sprint at a time.

The helper now deploys to .scrum/scripts/lib/, which is symlinked into
every worktree. This also stops setup-user.sh scattering four
framework-repo-only helpers into the target's scripts/ tree, where a
plain cp could clobber a same-named file.

Gates that passed vacuously (#85)

  • Pragma audit. jq -r '.pragma_pattern' yields JSON text for the
    schema-sanctioned object form and the literal null when the key is
    absent. Either matches nothing, and all over an empty array is true —
    so the pragma leg of Definition of Done was a silent no-op. All three
    shapes are now handled explicitly and an unresolvable one fails loudly.
    Narrowing the schema instead was rejected: config.json is in
    STRICT_MAP, so that would have created a third launch-brick.
  • c0_threshold / c1_threshold were mandated by four documents but
    declared in no schema and in no example config — a typo silently
    reinstated the 100% requirement.

Stalls, duplicate work, and dropped events (#85)

  • A dead Developer teammate stranded its PBI at escalated with a null
    reason, so the handler never wrote escalation-resolution.md and
    completion-gate.sh blocked every subsequent SM Stop. Adds the
    teammate_unrecoverable escalation reason end to end.
  • session-context.sh reported merge-queued PBIs as active pipelines (the
    in_progress_merge exclusion its two sibling copies carry was missing),
    triggering duplicate Developer re-spawn.
  • session-context.sh echoed hookEventName:"PostCompact", which is not a
    member of the hook output union, so post-compaction context was silently
    discarded.
  • json_update_atomic took no lock on a read-modify-write that
    dashboard-event.sh runs on every PostToolUse from every concurrent
    agent; a dropped subagent_stop inflated count_in_flight_subagents
    into a spurious "N subagent(s) still running — do NOT re-spawn".
  • pbi-merge collapsed preflight refusals (64/65/67) with real conflicts
    (exit 1), so the SM told a Developer to resolve conflicts in a worktree
    that had none while the actual remedy sat in discarded stderr.

Hardening and hygiene (#85)

  • All 23 scrum-state wrappers now resolve schemas through
    lib/atomic.sh::resolve_schema_dir instead of an inline probe that used
    only its first candidate, with no $PWD fallback and no diagnostic.
  • The four top-level contract schemas under docs/contracts/, which
    nothing validated, gain fixtures and 13 tests — valid fixtures must
    pass and invalid ones must be rejected. A drift guard asserts every
    criterion_key a reviewer declares is in the envelope enum, the exact
    defect 1b2d9aa fixed by hand. Both guards were mutation-tested.
  • Stale references, flock-vs-mkdir lock drift in four places (the
    difference is load-bearing: a mkdir lock does not auto-release on process
    death), undocumented StopFailure/PostCompact hooks, the missing
    retrospective → complete phase edge, and ~158 lines of duplicated prose
    collapsed to pointers. Deliberate duplication was left intact and
    documented as such.
  • Target-project Sprint identifiers removed from three files, per this
    repo's own no-private-project-references rule.

Upgrading

Existing projects pick all of this up on the next scrum-start.sh launch:
the two new migrations run automatically and idempotently before the team
spawns. No manual step.

Verified: shellcheck clean · ruff clean · 715 tests green
(unit+lint 225, scrum-state 445, integration 45).

Full diff: #85