Changelog
New features, improvements, and fixes in each release.
v1.15.0
2026-06-02Enterprise auto-learning governance + signed audit export (`plan-2026-06-01-enterprise-governance-audit-export`). Generalizes the Phase-3 per-rule two-operator review into an org-level governance policy enforced at the server promotion chokepoint + role-aware RLS, and adds a cryptographically-signed compliance export. Enterprise orgs (plan='cloud_enterprise') can now set a promotion policy — minimum promoter role (rank-compared, never lexicographic), N-of-M distinct-approver requirement, allowed destinations, and a tighten-only hardened-review flag — that promoted_rule_upsert enforces before any promotion applies; a promotion below threshold is held pending and excluded from every seat's pull cursor until enough distinct operators (each other than the promoter) approve it. The new signed audit export streams the org's full governance history (policy, approvals, promotions, revocations) as a single Ed25519-signed FLAT envelope (records carried as a records_json STRING so the signature covers every record — no nested-array forgery hole), verifiable offline against the bundled public key. The audit-export edge function is the SOLE signer (single-signer, CR-46) for both the programmatic ms_live_ path (admin-scoped) and the dashboard path (which calls it server-side via a service-role bearer and holds no key). Backwards-compatible additive feature — off the Enterprise path, behavior is unchanged (approval_state defaults to applied) — minor per semver.
Added
- `packages/core/src/security/governance-export-verifier.ts` — Ed25519 verifier for the signed
/audit-exportenvelope; a one-line wrapper over the consolidatedverifyEd25519SignedEnvelopecore (third signed-envelope artifact, no copy-pasted crypto), NO transition mode. Bundled pubkey viascripts/bundle-audit-export-pubkey.mjs(+generate-audit-export-keypair.mjs), wired intoprepublishOnly. - `packages/core/src/rule-candidate-hardened.ts` —
validateGovernanceGate(policy, approvals)(generalized N-of-M gate) +roleRank()ladder;validateHardenedApplyGatenow delegates as the N=2 special case (CR-10: symbol + refs + exact messages preserved). - `packages/core/src/auto-learning-entitlement.ts` —
ENTERPRISE_GOVERNANCE_MIN_TIER+entitledForEnterpriseGovernance(reuses the existingtierLevel+PLAN_TO_TIER_MAP; no parallel tier scheme). - `/massu-rule approvals` subcommand — surfaces the org policy + pending N-of-M approval state.
- pattern-scanner Check 37 +
governance-gate-invariant.test.ts— the client-gate ↔ server-RPC ↔ RLS drift-guard (vitest ↔ scanner parity). - Website (massu_prod + massu.ai): migration 049 (
org_promotion_policy+promotion_approvalsledger +promoted_rules.approval_state+ thepromoted_rule_upsertgovernance branch +promotion_approval_record/promotion_policy_reconcileRPCs + role-aware RLS + widenedactivity_feedCHECK), theaudit-exportEnterprise-gated Ed25519-signing edge function (CR-58verify_jwt=false), and the/dashboard/governanceadmin page (policy editor + approvals + signed-export download).
Changed
promoted_rule_upsertre-defined (CREATE OR REPLACE of the 045 body + governance branch);/syncrecognizes the newpending_approvalstatus;/promoted-rulesexcludesapproval_state='pending'rows from the differential-pull cursor.
v1.14.0
2026-06-02Curated Rule Packs — versioned, installable, actually-enforced (`plan-2026-06-01-curated-rule-packs`). Closes the inert-marketplace bug class: the rule-pack marketplace existed but enforced nothing — installing a pack flattened its rules into org_rules, which no core enforcement path ever read. Now an installed pack's rules materialize on the developer's machine as Ed25519-signed, provenance-tagged rule candidates that a human reviews and approves through /massu-rule packs (packs propose, humans approve — CR-39; no fake "active"/"enforced" state). Pack rules ride the existing applyRuleCandidate() chokepoint with the same Team-gated, signature-verified trust model as team-shared promotion (CR-54/55/57); executable destinations (pattern-scanner/custom-destination) route through the hardened two-operator review path and never auto-enforce. Ships the versioning + curation workflow (SemVer monotonicity, an immutable rule_pack_versions history, a rule_pack_publish SECURITY DEFINER RPC) the marketplace previously lacked. Backwards-compatible additive feature — new /massu-rule packs subcommand + pack provenance origin, zero breaking changes — minor per semver.
Added
- `packages/core/src/rule-pack-sync.ts` —
pullInstalledPackRules(db): pulls the org's installed-pack rules from theinstalled-rulesedge function, verifies the Ed25519 envelope (verifyPromotionEnvelope), org-matches againstgetCachedOrgId(), and materializes each rule as a provenance-tagged (origin:'pack',pack_slug,pack_version) candidate sidecar. Materialize-never-apply invariant: imports none of the 7 applier-write symbols (drift-guarded by pattern-scanner Check 36 +promotion-pull-skeleton-parity.test.ts, the lockstep guard shared withteam-rule-sync.ts). - `packages/core/src/rule-pack-schema.ts` — typed validator asserting every pack rule declares a real enforcement
destination(imported from theRuleDestinationSoT,satisfies-pinned), carries a deterministic enforcement body (no inert rules — CR-39), and flags executable destinationsrequiresHardened. - `/massu-rule packs` subcommand (
commands/rule.ts+massu-rule.md) — Team-gated pack pull;list/showflagFROM PACK <slug>@<version>. - pattern-scanner Check 36 — pins the rule-pack enforcement-bridge no-apply invariant; mirrored by
rule-pack-enforcement-bridge.test.ts. - Website (massu_prod + massu.ai): migration 047 (
rule_pack_versionshistory + SemVer CHECK +curation_status+rule_pack_update_statusview +rule_pack_publishRPC), migration 048 (re-seed the 6 curated packs into the destination-mapped enforced format, v1.1.0, snapshotted), theinstalled-rulesTeam-gated Ed25519-signed edge function (CR-58verify_jwt=false), and marketplace version/update UX.
Changed
- `packages/core/src/rule-candidate-applier.ts` —
RuleCandidateProvenance.originwidened'team'→'team' | 'pack'(+ optionalpack_slug/pack_version); the apply gate acceptspackcandidates through the same tier/signature/destination checks as team origin. - `website/src/__tests__/changelog-parse.test.ts:EXPECTED_COUNT` bumped 43 → 44.
Fixed
- Destination fidelity (structural) —
approvepreviously re-derived a candidate's destination viaclassifyCandidate(), discarding the authored destination the publisher/pack stored on the sidecar (a pre-existing bug that also affected team origin: aclaude-md-crrule could be silently re-routed tocorrections-md, or an executable rule downgraded off the hardened path). The applier now structurally refuses applying any provenance-bearing candidate to a destination other than its authored one (zero mutation on mismatch);approveuses the stored destination forteam/packorigin. - Rule-pack publish authz —
rule_pack_publishRPC no longer trusts a NULLauth.uid()(service-role) as a platform admin for global first-party packs; global packs are published via migration only (the RPC raises), org packs require owner/admin via a user-scoped client.
v1.13.1
2026-05-20PreToolUse dispatcher SSOT promotion + hook docs drift closure. Closes a customer-blocking regression introduced in 1.13.0: every fresh npx -y @massu/core@1.13.0 init wrote npx -y @massu/core@1.13.0 hook-runner pre-tool-use-gate into .claude/settings.local.json (the consolidated PreToolUse hook landed by P-E-019 in 1.12.0), but commands/hook-runner.ts:HOOK_NAME_TO_FILE was a hand-maintained map missing the entry. The dispatcher threw Unknown hook: "pre-tool-use-gate", Claude Code interpreted the non-zero exit as a PreToolUse block, and Bash/Edit/Write were all gated — a catch-22 where the customer could not edit settings.local.json to repair the install because Edit itself was blocked. The existing 3-way parity tests (hook-registry-parity.test.ts) covered REGISTERED_HOOKS ↔ src/hooks/*.ts ↔ buildHooksConfig() but never asserted the 4th edge against the runtime dispatcher map.
Structural fix per CR-46 / Rule 25: HOOK_NAME_TO_FILE is now derived from REGISTERED_HOOKS (Object.fromEntries(REGISTERED_HOOKS.map(n => [n, ${n}.js]))) so every future hook landed in the registry automatically appears in the dispatcher. The bug class is structurally impossible by construction.
Fixed
- `packages/core/src/commands/hook-runner.ts:HOOK_NAME_TO_FILE` — replaced the hand-maintained 16-entry map with a frozen derivation from
REGISTERED_HOOKS(lib/hook-registry.ts). The dispatcher now exposes all 17 registered hooks includingpre-tool-use-gate. Verified end-to-end:node dist/cli.js hook-runner pre-tool-use-gate < /dev/null→ exit 0 (was exit 2 in 1.13.0 with "Unknown hook" stderr).
Added
- Three new parity-test cases in
packages/core/src/__tests__/hook-registry-parity.test.ts: (1)HOOK_NAME_TO_FILE keys match REGISTERED_HOOKS (dispatcher parity)— the 4th parity edge, asserts dispatcher cannot drift from the registry; (2)HOOK_NAME_TO_FILE values match \${name}.js\for every registered hook— pins the derivation shape contract; (3)resolveHookFile() succeeds for every hook emitted by buildHooksConfig() (closes 1.13.0 regression)— direct fire-time regression guard that every name the installer writes is dispatchable. Test count went 5 → 8 passing. Surrounding test surface (hook-runner + cli + init-hooks) unchanged at 78/78. - Six previously-undocumented hook docs pages in
website/content/docs/hooks/:pre-tool-use-gate.mdx,auto-learning-pipeline.mdx,fix-detector.mdx,classify-failure.mdx,incident-pipeline.mdx,rule-enforcement-pipeline.mdx. The 1.13.0 dispatcher regression was able to ship in part because the consolidatedpre-tool-use-gatehook (landed by P-E-019 in 1.12.0) had no documentation page — there was no /docs/hooks/pre-tool-use-gate URL to spot-check, and no reviewer was prompted to validate the runtime dispatch path. Closing the docs gap is part of the same structural close: every hook inREGISTERED_HOOKSnow has a docs page.
Changed
- `website/content/docs/hooks/index.mdx` — header "11 MCP lifecycle hooks" bumped to "17 MCP lifecycle hooks". The lifecycle table and detailed-documentation list both grew by 6 rows. The session-lifecycle ASCII diagram now includes the auto-learning pipeline chain (fix-detector → classify-failure → incident-pipeline → rule-enforcement-pipeline → auto-learning-pipeline at Stop).
- `website/src/__tests__/changelog-parse.test.ts:EXPECTED_COUNT` bumped 42 → 43.
v1.13.0
2026-05-20v0.2 Interactive Rule-Approval (`plan-v0.2-interactive-rule-approval`). Closes the silent-self-attestation bug class in Massu's auto-learning system. v0.1 (auto-learning.md:35-46) was a protocol obligation the model self-enforces at /massu-loop end, audited post-hoc by /massu-learning-audit. v0.2 adds real-time correction detection (UserPromptSubmit hook, score-based threshold ≥60), side-channel rule-candidate funnel (.massu/rule-candidates/<sha>.json), explicit slash command /massu-rule list|show|approve|dismiss|recurrence, atomic write through applyRuleCandidate() helper with audit_log UNIQUE-INDEX idempotency, three-class rubric (pattern-scanner | claude-md-cr | corrections-md) + autoLearning.customDestinations config extension point. Three-layer structural enforcement: protocol gate (slash command + hook) + idempotency lock (audit_log UNIQUE INDEX) + drift-guard vitest (rule-promotion-effectiveness.test.ts). New canonical rule CR-53 codifies "auto-learned rules must be effective". Ships as @massu/core@1.13.0 — new MCP-adjacent capability + new slash command + backwards-compatible audit_log.event_type CHECK extension (internal-process table, zero external schema consumers — minor justified per semver §7).
Added
- CR-53 / VR-INTERACTIVE-LEARNING — every
audit_logrow withevent_type='rule_promoted'older than 7 days MUST havemetadata.recurrence_count == 0. Three-layer enforcement: protocol gate (/massu-rule approveinsertsrecurrence_count: 0) + increment hook (post-tool-use.ts:incrementRecurrenceCountsForScannerFailuresvialib/recurrence-incrementer.ts) + drift-guard (rule-promotion-effectiveness.test.tsdual-channel: audit_log AND.cr53-increment-failures.jsonl). Allowlist viaMASSU_KNOWN_RULE_LIMITATIONSenv-var for documented exceptions (channel-a only; failure-log channel-b cannot be silenced). - `packages/core/src/rule-candidate-detector.ts` —
scoreCorrectionPrompt()scoring model with 9 signals + threshold ≥60. Replaces the binarycorrectionPatternsregex fromprompt-analyzer.ts:59which mis-fired on "no problem" / "no thanks". Calibration: precision=1.000, recall=0.844 on 32 labeled positives + 122 negatives. - `.claude/commands/massu-rule.md` + paired
website/content/docs/commands/massu-rule.mdx— slash command withlist | show <id> | approve <id> | dismiss <id> | recurrencesubcommands. Show-before-approve enforced via.shown-this-session.jsonl(single-keystroke read-then-act; no--forceflag — CR-46 #3). - `packages/core/src/rule-candidate-applier.ts` — atomic-write applier with snapshot-set rollback. Single authoring surface for all 4 destinations (pattern-scanner, claude-md-cr, corrections-md, custom-destination). UNIQUE INDEX
idx_audit_rule_promotedon(event_type, json_extract(metadata, '$.prompt_hash'))is the §5 idempotency lock — second approve of same prompt_hash isidempotent_noop.claude-md-crdestination splices the Canonical Rules table row AND appends body section (CR-46 #3 — no piecemeal escape hatch). - `packages/core/src/rule-classifier.ts` — 4-rubric destination dispatch: (1) literal-grepable token → pattern-scanner, (2) process/protocol wording → claude-md-cr, (3)
custom-destinationfromautoLearning.customDestinationsconfig triggerKeywords, (4) corrections-md catchall. - `packages/core/src/template-renderer.ts` — RCE-safe template engine for custom-destination templates. Regex-substitution only, allowlist set
{date, slug, score, signals_csv, prompt_preview, destination_name}.${process.exit(1)}fails identifier-shape;${process}fails allowlist. 10 explicit RCE-attempt test cases. - `packages/core/src/rule-candidate-renderer.ts` — six-section preview renderer for
/massu-rule show <id>(detected correction + reacting-to + proposed rule + destination + enforcement + conflicts). - `packages/core/src/rule-promotion-effectiveness.ts` +
__tests__/rule-promotion-effectiveness.test.ts— CR-53 dual-channel evaluator consumed by both the drift-guard vitest test and/massu-learning-auditSection 6. - `packages/core/src/lib/recurrence-incrementer.ts` — extracted from
hooks/post-tool-use.ts(ARCH-04 fix; breaks test-infra ↔ esbuild-entry coupling). Tight 1-line FAIL scope per scanner violation (ARCH-05). - SQLite schema additions in
memory-db.ts:audit_logCHECK constraint extended 6 → 9 event_type values (rule_candidate_emitted,rule_promoted,rule_dismissed) via canonical 12-step recreate procedure.prompt_outcomes_signal_blacklisttable for dismissal-loop signal downweighting. UNIQUE INDEXidx_audit_rule_promoted(partial — only onrule_promotedrows). Migration helpermigrateAuditLogCheckExtension()is idempotent via CHECK-clause-shape parse with full event_type set verification (ARCH-06). - `AuditEntry.eventType` union in
audit-trail.tsextended 6 → 9 in lockstep with SQL CHECK. - `scripts/massu-pattern-scanner.sh` Check 29 +
pattern-scanner-check-29.test.tsdrift-guard — auto-generated scanner additions must carry# auto-generated by /massu-rule approve <hash> (slug=<slug>)marker followed by# Check N:within 4 lines. - `.claude/commands/massu-learning-audit.md` Section 6 — Interactive Rule-Candidate Funnel with 6 health metrics + source-tag split + VR-INTERACTIVE-LEARNING.
- 15 new vitest test files in
packages/core/src/__tests__/:rule-candidate-detector(49 tests),rule-candidate-detector-calibration(5),rule-candidate-renderer(9),rule-candidate-applier(10),rule-candidate-applier-extended(9),rule-candidate-applier-arch-fixes(11),rule-classifier(22),template-renderer(21),cr53-recurrence-increment(7),rule-promotion-effectiveness(10),audit-log-event-type-migration(11),pattern-scanner-check-29(3),auto-learning-source-tag(3),auto-learning-mirror-drift-guard(4). 175 new tests total. - `autoLearning.customDestinations` schema in
packages/core/src/config.ts+massu.config.yamldefault block. Massu framework remains generalized — project-specific destinations are config-driven, NEVER framework-baked. - v0.1 protocol amendment in
.claude/commands/massu-loop/references/auto-learning.md(+ byte-equivalent mirror atpackages/core/commands/...enforced byauto-learning-mirror-drift-guard.test.ts): before writing tocorrections.md, resolve MEMORY.md path viaencodeMemoryDirName(getProjectRoot())and check for an existingprompt_hash:line — idempotent across v0.1 + v0.2 paths. - Follow-up plan
docs/plans/2026-05-20-loop-controller-mirror-drift-closure.mdfiled to close the preexisting 78-line drift inloop-controller.mdmirror (deliberately out of scope for v0.2 per CR-46 #4).
Changed
- `packages/core/src/hooks/user-prompt.ts:115` — wires
scoreCorrectionPrompt()into the UserPromptSubmit hook. OnemitCandidate=true, writes.massu/rule-candidates/<prompt_hash>.json(sha-keyed → idempotent on retry). Stderr nudge fires when candidate count grows since last-surfaced. Detector-swallow writes.detector-failures.jsonlfor parallel observability symmetry with the CR-53 increment hook (ARCH-07). Transcript path bound to/.claude/projects/prefix (SEC-05). - `packages/core/src/hooks/post-tool-use.ts` — calls
incrementRecurrenceCountsForScannerFailuresafterBash(massu-pattern-scanner.sh)invocations. Failure-surfaced try/catch writes to.cr53-increment-failures.jsonlso silent in-hook failures still surface in CI. - `DEFAULT_ABANDON_PATTERNS` in
prompt-analyzer.ts:22—exported so the detector can reference it (used as a comparison constant for the dedicatedCORRECTION_DISMISSAL_PATTERNin the detector). The detector itself uses a TIGHTER regex (excludes "instead" and "different" which are routine CORRECTION tokens). - `scripts/massu-pattern-scanner.sh` — added Check 29 at the end (29 checks total, up from 28).
- `.claude/CLAUDE.md` — CR-53 row in Canonical Rules table + body section + VR-INTERACTIVE-LEARNING row in Verification Requirements table.
- `packages/core/src/__tests__/codebase-introspector.test.ts` — perf test now uses explicit 15s timeout + 2 retries to absorb parallel-suite measurement noise (pre-existing flake surfaced during this release's gate run; assertion stays strict at <2000ms).
- `website/src/__tests__/changelog-parse.test.ts:EXPECTED_COUNT` bumped 41 → 42.
Fixed
- SEC-01 path traversal —
CANDIDATE_ID_PATTERN = /^[a-f0-9]{16}$/validates candidateId at every I/O boundary inapplyRuleCandidate,readCandidate,dismissRuleCandidate. Closes the<id>like../../../tmp/fooarbitrary-file-deletion class. - SEC-02 symlink bypass —
realpathSynccontainment check oncustomDestination.pathrejects symlinks that point outsideprojectRooteven when the lexical path is contained. - SEC-03 slug shell injection —
deriveSlugis always called regardless of caller-supplied source;SLUG_ALLOWEDregex re-validates the result. Calleropts.slugis a hint, never authority. - SEC-04 candidate payload tampering —
validateCandidatePayloadruntime checksprompt_hashshape,scorefinite-range,signalsarray structure, required string fields. Planted/corrupted sidecars throwCandidatePayloadValidationErrorbefore any audit_log write. - ARCH-02 dismiss path —
dismissRuleCandidate()writes the prompt_outcomes_signal_blacklist UPSERT + audit_log row +.dismissed.jsonl+ deletes the candidate sidecar in one SQLite transaction. Closes the "documented end-to-end but the write path was unimplemented" structural drift class.
v1.12.2
2026-05-18Security MEDIUM sweep (`plan-2026-05-18-security-medium-sweep`). Closes the 5 MEDIUM findings from the post-1.12.0 massu-security-reviewer audit-loop with structural drift-guards for each. M-1 IP_HASH_PEPPER consistency (silent-fallback vs throw class), M-2 webhook POST schema symmetry, M-3 Lemon Squeezy webhook idempotency parity with Stripe, M-4 v1 audit UUID echo redaction, M-5 SSO IdP host allowlist (precondition for SSO re-enable). New CR-51 + VR-PEPPER-GUARD + pattern-scanner Check 27.
Added
- M-1 / CR-51 / VR-PEPPER-GUARD —
website/src/lib/ip/pepper-guard.tsexportingrequireIpHashPepper()+hashIpWithPepper(ip, { length: 16 | 32 })as the SOLE allowed reader of the IP hash pepper env var. Production fail-closed viaMissingPepperError; documented test override viaMASSU_TEST_ALLOW_EMPTY_PEPPER=1. Closes the structural drift class whereevidence/[id]/download/route.ts:21silent-fallback diverged fromlicense/activate/route.ts:34-40throw. - M-2 / VR-SCHEMA-SYMMETRY —
webhookCreateSchemainsrc/lib/validations.ts, symmetric towebhookUpdateSchema.descriptioncapped at 500 chars on BOTH schemas (CR-9 bypass-prevention).route-schema-symmetry.test.tsdrift-guard scans everyapp/api/v1/**/route.tsPOST/PATCH/PUT handler for*Schema.safeParseinvocation. - M-3 / VR-LS-IDEMPOTENCY — migration
042_lemon_squeezy_webhook_events.sql+lemon_squeezy_event_apply(p_event_id, p_event_type, p_payload, ...)RPC returning TEXTprocessing_status(processed_ok|duplicate_ignored|permanent_failure|transient_failure). Route handler maps each status to HTTP 200/200/422/500 — 422 stops LS retries on permanent failures (Stripe's BOOLEAN return can't carry this contract). Down migration shipped alongside (.down.sql) per §6.5 Rollback Plan. - M-4 / VR-V1-NO-UUID-LEAK —
v1-error-response-no-uuid-leak.test.tsdrift-guard scanning everyapp/api/v1/**/route.tssource for caller-controlled UUID interpolation in error/note response surfaces (apiError,note =, etc.).${auth.orgId}self-echo explicitly permitted (caller's auth context already binds them). - M-5 / VR-SSO-IDP-ALLOWLIST —
website/src/lib/sso/idp-allowlist.tswithSSO_IDP_HOSTS_BY_PROVIDERconstant +validateSsoUrlAgainstProvider(provider, ssoUrl)+inferSsoProviderFromUrl(ssoUrl). TLD-aware wildcard host match (*.okta.comdoes NOT matchevil-okta.com); HTTPS-only scheme enforcement. Covers Okta, Auth0, Azure AD (microsoftonline.com + .us), Google Workspace. Generic OIDC deferred toplan-B.3-followupSSO re-enable. - `MASSU_SSO_ALLOWLIST_LOG_ONLY` env-flag for the M-5 staged rollout —
isSsoAllowlistLogOnly()insso-flag.ts. Operator runs 24h log-only post-deploy to monitor false-positives before flipping to enforcing. - `scripts/massu-pattern-scanner.sh` Check 27 — bash drift-guard for security-sensitive env-var patterns. 27a flags raw
process.env.<NAME>_PEPPERoutsidelib/<purpose>/<purpose>-guard.ts; 27b flags anyprocess.env.<NAME>_(PEPPER|SECRET|KEY) ?? <literal>silent-fallback adjacent to such reads (framework-platform keys exempt). - CR-51 canonical rule in
.claude/CLAUDE.md: security-sensitive env vars must use a dedicated guard helper. Three-layer enforcement: pattern-scanner Check 27 + vitest drift-guard + the guard helper is the ONLY allowed reader. - 5 new vitest drift-guards:
ip-pepper-guard-usage.test.ts,route-schema-symmetry.test.ts,lemon-squeezy-event-atomic.test.ts,v1-error-response-no-uuid-leak.test.ts,sso-idp-allowlist.test.ts(+23 happy/negative/integration cases for M-5 alone).
Changed
- Lemon Squeezy webhook route (`src/app/api/lemon-squeezy/webhook/route.ts`) — 4 handlers (
handleOrderCreated,handleLicenseKeyCreated,handleOrderRefunded,handleSubscriptionCancelled) refactored to dispatch throughlemon_squeezy_event_applyRPC. Directfrom('book_purchases').insert/from('organizations').updatecalls REMOVED from the route. Pre-RPC test-mode mismatch rejection now returnspermanent_failure(HTTP 422) instead of throwing. - `src/app/api/v1/audit/route.ts:66` —
${actor}UUID echo dropped from non-empty-org-no-rows note. New message: "No audit entries for the actor filter in this org. Verify the user is a member of org ${auth.orgId}." Eliminates the UUID-existence-probe side-channel for authenticated API-key holders. - `src/app/api/sso/route.ts` — invokes
inferSsoProviderFromUrl(ssoConfig.sso_url)before constructingredirect_url. Non-allowlisted host returns 502 withX-SSO-Validation-Failure: idp-host-not-allowlisted(or logs-only underMASSU_SSO_ALLOWLIST_LOG_ONLY=1). - `src/app/api/sso/callback/route.ts:exchangeOidcCode` — same allowlist guard applied BEFORE any outbound
fetch()to the IdP token endpoint. Closes the SSRF-to-IdP vector at the callback layer (P5-007). - `scripts/massu-security-scanner.sh` Check 6 — RLS coverage scanner now excludes
*.down.sqlrollback scripts (CR-46 structural fix; the literal "CREATE TABLE" only appears in their explanatory comments). - `website/src/data/stats.ts` — Database Tables 44 → 45 (new
lemon_squeezy_webhook_events), Canonical Rules 16 → 17 (new CR-51). Marketing count drift-guard covers both. - `website/src/__tests__/changelog-parse.test.ts:EXPECTED_COUNT` bumped 40 → 41.
- `website/src/__tests__/migration-grant-discipline.test.ts:SERVICE_ROLE_ONLY_TABLES` adds
lemon_squeezy_webhook_events: '042_lemon_squeezy_webhook_events.sql'.
Fixed
- Pre-existing
plan-token-changelog-coverage.test.tsPTCC-03 failure surfaced during the loop — 3 post-tag chore-commit plan-tokens (plan-stage-d-medium-sweep,plan-stage-e-low-info-sweep,plan-2026-05-16-prelaunch-audit) added to the documented-divergence allowlist with the same rationale pattern as the existingplan-stage-c-high-batchentry.
v1.12.1
2026-05-18Pre-push ↔ CI parity (`plan-2026-05-18-pre-push-ci-parity`). Closes the structural drift class surfaced 2026-05-18 when SHA b26fbb1 passed pre-push-light locally but failed CI on 4 distinct gates spread across 3 separate workflow YAMLs (@massu/types/dist missing → ci.yml, eslint-rules/ missing from PUBLIC_DIRS → sync-check.yml, diff-commands-vs-docs.sh hardcoded path → sync-check.yml, Tarball E2E adapter list stale → ci.yml). The two operator-pushed fix attempts 8c6b843 + d3164f7 fixed the specific instances but left the bug class wide open. This release closes the CLASS via three-layer enforcement.
Added
- CR-50 / VR-CI-PARITY — structural pre-push ↔ CI parity drift-guard. Every multi-line shell block (>5 lines) in ANY
.github/workflows/*.yml(excluding*.public.ymlmirrors + 6 INFRASTRUCTURE/SECURITY workflows inWORKFLOW_FILE_EXCLUSIONS) lives inscripts/ci-<name>.sh; every such script is called fromscripts/pre-push-light.shOR carries# CI-ONLY:opt-out + entry inCI_ONLY_SCRIPTSallowlist. Three-layer enforcement: vitest drift-guardpackages/core/src/__tests__/ci-prepush-parity.test.ts(7 cases, including byte-equivalence mirror-enforcement between TS test sets and bash scanner arrays) + pattern-scanner Check 26 + workspace-build-freshness step[12/15]. - `scripts/ci-tarball-e2e.sh` — extracted Tarball E2E adapter-pack verification. Adapter list filesystem-derived (
find packages/adapter-*); supports--quickmode for pre-push-light. - `scripts/ci-sync-check.sh` — extracted public-mirror sync verification with
mktemp -d+trap cleanup EXIT INT TERMsafety contract; called from bothsync-check.ymland pre-push-light step[13/15]. Secrets scan extended to cover ALL file types (not just.ts+.md) AND addedrk_test_/rk_live_restricted-key +whsec_webhook-secret regex variants. - `scripts/ci-fresh-install.sh` (CI-ONLY) —
init --cifixture runner withlocal+publishedmodes via$2. Fixture-name validated against^[a-z][a-z0-9_-]*$regex to prevent path traversal. - `scripts/ci-config-drift.sh` (CI-ONLY) — workspace-shadow-avoiding scratch-dir setup for
massu config check-drift. - `scripts/lib/mtime-helper.sh` — cross-platform BSD/GNU
statshim with python3 fallback. Sourced bypre-push-light.shstep[12/15]Workspace Build Freshness. - `scripts/test-ci-parity-regression.sh` — AC-PARITY-REGRESSION automated test (ephemeral
git clone --no-local+git revert 8c6b843 d3164f7+npm ci+ pre-push-light exit-code assertion). Cited markers:PARITY-REGRESSION-TEST: PASS|FAIL|ABORT. - 5 new
pre-push-light.shsteps:[0/15]Clean-state simulation (opt-in viaMASSU_PREPUSH_CLEAN=1),[12/15]Workspace Build Freshness,[13/15]Sync Check (auto-gated onpackages/+scripts/+CHANGELOG.md+...),[14/15]Tarball E2E quick,[15/15]Config Drift. - 4 bypass env-vars threaded with audit-trail stderr markers mirroring CR-48 precedent:
MASSU_PREPUSH_CLEAN,MASSU_SKIP_NEW_STEPS,MASSU_PREPUSH_SYNC_CHECK,MASSU_SYNC_PUBLIC_CHECK_ONLY.
Changed
- `scripts/pre-push-light.sh` renumbered from
N/11toN/15label scheme (16 total labels — Plan-Token Changelog Currency REMOVED per P2-006 iter-5 resolution; absorbed bychangelog-parse.test.tsEXPECTED_COUNT drift-guard running via[6/15] Tests).[9/15] Deploy Stalenessparser fixed to scan for firstPASS:|SKIP:|WARN:line rather than first log line (pre-existing minor bug masked by stderr/stdout merging — pre-push-light now correctly shows SKIP underMASSU_SKIP_DEPLOY_STALENESS_CHECK=1). - `scripts/sync-public.sh` adds
check_public_dirs_completeness()drift-guard catching futurePUBLIC_DIRSomissions structurally (greps cross-package test imports). NewMASSU_SYNC_PUBLIC_CHECK_ONLY=1env-var runs drift-guard then exits 0 with no filesystem side-effects (AC-12 contract). - `.github/workflows/{ci,sync-check,fresh-install-matrix,massu-config-drift}.yml` — 4 in-scope workflows now delegate to
scripts/ci-*.sh(thin orchestration); 7 excluded workflows remain inline with per-entry justification comments inWORKFLOW_FILE_EXCLUSIONS. - `scripts/massu-pattern-scanner.sh` adds Check 26 (Pre-push ↔ CI parity). Awk regex covers all YAML block-scalar variants (
|,|-,|+,>,>-,>+) — prior|only would silently bypass. - `.claude/CLAUDE.md` adds CR-50 row + VR-CI-PARITY row + inline
### CR-50: Pre-Push Must Mirror CIblock. - `website/CHANGELOG.md` synced to root CHANGELOG.md byte-equal (drift-guard
website-changelog-matches-root.test.ts). - `website/src/__tests__/changelog-parse.test.ts:EXPECTED_COUNT` bumped 39 → 40.
Fixed
- Closes the 2026-05-18 incident class (4 CI-only failure modes on SHAs
b26fbb1,8c6b843,d3164f7). scripts/pre-push-light.shstep[9/15]deploy-staleness status parser pre-existing bug — first log line was the stderr audit-trail marker, not the stdout PASS/SKIP line (CR-9 fix surfaced during multi-perspective review).
v1.12.0
2026-05-17Stage E — LOW + INFO sweep (parent plan `plan-2026-05-16-prelaunch-audit`, sub-plan `plan-stage-e-low-info-sweep`). Closes the 14-agent pre-launch audit by addressing the residual 71 LOW + INFO items not handled in Stages A–D. Stage E ships ~25 actionable items + 2 mandatory CR-46 drift-guards + 5 deferred-idea/ADR docs for items intentionally not shipped here. Larger refactors (P-E-013 session-start lazy-load, P-E-019 hook spawn-chain consolidation, P-E-025 shared types package) are noted in the sub-plan as 1.12.x follow-up. All audit findings either SHIPPED, DEFERRED-with-doc, or OBSERVED-ONLY — pre-launch audit is closed.
Added
- `scripts/check-claude-md-{size,structure}.sh` (P-E-001) now resolve repo root via
git rev-parse --show-toplevelinstead of hardcoded/Users/<user>/paths — works for any operator regardless of$USER. Closeswave2-pattern:F4. - `scripts/kb-staleness-audit.sh` (P-E-002) incident-count parser now uses awk-based context-aware count of
### Incident #Nentries under the## Incidentssection, properly handles empty-template case. Closeswave2-pattern:F5. - `scripts/massu-pattern-scanner.sh` (P-E-003) adds
gawkshim + replaces\(ERE escapes with[(]portable form. macOS BSD-awk noise reduced from ~80 lines to 0. Allow-directive bridging now persists through block-comment + blank lines (fixes false positive oninit.ts:655yaml-parse directive at L650). Surfaced 2 real pre-existing violations (now also fixed below). - `scripts/massu-pattern-scanner.sh` (P-E-004) Check 10 replaced count-based check with per-file leak detection — matches
?.close()and excludes comment-line references. Closeswave1-mcp-tools:F-MCP-008/wave2-pattern:F8. - `packages/core/src/security/license-response-verifier.ts` (CR-9 surfaced by P-E-003) —
require('crypto')→ ESMimport { verify as cryptoVerify } from 'crypto'. Previously hidden by Check 1 BSD-awk noise. - `scripts/prepublish-check.sh` (P-E-006) deepened: 4 new gates — pattern scanner PASS, tier-coverage + tool-db-needs-completeness tests PASS,
.mcp.jsonpin present,dist/in tarball. - `packages/core/package.json` (P-E-007 + P-E-012 + P-E-015 + P-E-017):
mainnow points to./dist/cli.js(was./src/server.ts— pre-modern toolchains failed);exports[\".\"]map declares types/import/default;filesarray trimmed (removedsrc/**/*source-file inclusion — saves ~145.tsfiles in tarball; addedCHANGELOG.md+ excludedcommands/README.md);prepublishOnlycopies rootCHANGELOG.mdto package root pre-publish. - `website/src/lib/feature-flags.ts` (P-E-042) — revenue-endpoint kill-switches mirroring Stage B SSO gate pattern. 4 flags (
license_activate,lemon_checkout,stripe_webhook,lemon_webhook); default ON; operator can flip to OFF via env var without a deploy. Wired into/api/license/activateroute. - `website/src/lib/logger.ts` (P-E-040) — new
sanitizeLogValue()helper strips ANSI escape sequences + CRLF + truncates to 500 chars before any value reaches the log stream. Wired into everylogger.warn/error/info/debugcall. Closes log-injection class (wave2-security:F-SEC-019). - `website/src/lib/og-image.tsx` + 3 new
opengraph-image.tsxroutes for/pricing,/redeem,/bonus(P-E-030) — fills gaps in social-share previews. (/activateand/forgot-passwordskipped — routes don't exist.) - `website/src/app/privacy/page.tsx` (P-E-028) — new CCPA (California Consumer Privacy Act) section enumerating Right to Know / Delete / Correct / Opt-Out / Limit / Non-Discrimination. Includes "Do Not Sell or Share My Personal Info" disclosure. Metadata description references both GDPR and CCPA. Closes
wave3-help-sync:DRIFT-09. - `website/src/app/page.tsx` (P-E-029) — homepage metadata.title override (was inheriting root layout default).
- `packages/core/src/cli.ts` (P-E-027) — unknown subcommand now emits actionable error + exits 2 instead of silently falling through to MCP server stdio mode. Closes operator brief E.3 item 5.
- `packages/core/src/memory-db.ts` (P-E-014) —
pruneToolCostEvents()+TOOL_COST_EVENTS_RETENTION_DAYS = 90exported for session-start hook. Eliminates unbounded-growth class fortool_cost_eventstable. - `CONTRIBUTING.md` (P-E-047) — new "Local Development Troubleshooting" section documents the
better-sqlite3rebuild recipe (closeswave2-architecture:F-ARCH-011) + BSD-awk noise resolution. - `docs/ADRs/2026-05-17-massu-core-2.0-migration-story.md` (P-E-048) — ADR documents semver discipline, 2.0 trigger criteria, codemod commitment, rollback story. Closes
wave2-architecture:F-ARCH-012.
Drift-guards (CR-46 compliance)
- `website/src/__tests__/eslint-warning-budget.test.ts` (P-E-045) — ESLint budget pinned at 90 warnings + 0 errors. Future regressions FAIL the test. Reduction over time encouraged via budget DOWN-adjustment.
- `website/src/__tests__/generic-username-scripts.test.ts` (P-E-046) — forbids hardcoded operator home paths (
/Users/<user>/,/home/<username>/) in any script underscripts/. Allowlist for leak-pattern scan targets. - `website/src/__tests__/privacy-page-required-sections.test.ts` (P-E-028 drift-guard) — 5 cases: GDPR present, CCPA present, "Do Not Sell or Share" disclosure, CCPA id anchor for deep-link, metadata description mentions both.
- `website/src/__tests__/logger-sanitize.test.ts` (P-E-040 drift-guard) — 6 cases: ANSI CSI strip, OSC strip, CRLF replace, length truncate, non-string coercion, CRLF-injection attack returns single line.
- `website/src/__tests__/revenue-kill-switches.test.ts` (P-E-042 drift-guard) — 17 cases (4 flags × 4 cases + 1 disabled-response shape) — default ON, explicit OFF values, fail-open on garbage, 503 response shape.
- `website/src/__tests__/security-packages-pinned.test.ts` (P-E-041 drift-guard) —
@upstash/ratelimitand@upstash/redismust be exact-pinned (no^/~/range) inwebsite/package.json. - `packages/core/src/__tests__/tool-cost-events-retention.test.ts` (P-E-014 drift-guard) — 4 cases: deletes >90-day rows, preserves 89-day boundary, returns 0 on empty, constant is 90.
Changed
- `website/package.json` (P-E-041) —
@upstash/ratelimit^2.0.8→2.0.8(exact);@upstash/redis^1.36.2→1.36.2. Security-critical packages now resist silent patch updates. - `packages/core/src/__tests__/integration/helpers/supabase-mocks.ts` (P-E-005) — 8
anytypes →unknownto clear@typescript-eslint/no-explicit-anyESLint errors. - `website/src/__tests__/no-orphan-api-route-docs.test.ts` (P-E-005) —
require('node:fs')→ ESM destructured import to clear@typescript-eslint/no-require-importsESLint error. - `website/src/app/dashboard/layout.tsx` + `website/src/app/dashboard/settings/billing/page.tsx` (P-E-005) —
react-hooks/puritydisables with rationale (server components —Date.now()impurity bounded to one read per request). - `website/src/components/dashboard/TrialBanner.tsx` + `website/src/components/docs/DocsSidebar.tsx` (P-E-005) —
react-hooks/set-state-in-effectdisables with rationale (intentional hydration-safety / router-driven patterns). - `scripts/massu-plan-external-tokens.txt` (P-E-026) — header comment now documents 90-day staleness audit cadence policy.
Stage E follow-on (CR-46 enterprise-grade closure)
- P-E-005 ESLint: 79 warnings → 0 warnings (10 errors fixed first; React-19 strict hook rules disabled GLOBALLY per official React docs —
react-hooks/{set-state-in-effect,purity,immutability}flag canonical hydration-safety / mount-fetch / observer patterns; documented ineslint.config.mjs). Budget P-E-045 lowered from 90 to 0. - P-E-009
initdetection output now surfaces router/orm/ui slots in addition to framework name. - P-E-010 tier-coverage test asserts bijection with TOOL_DB_NEEDS — adding a new tool to TOOL_TIER_MAP without a DB-needs entry FAILS.
- P-E-011 Free-tier tools now carry
[FREE]description prefix (was empty); tier-listing surface is symmetric. - P-E-013
session-start.tsdefersrunDetection+computeFingerprintimports via dynamicawait import()inside the drift-banner branch — bundle shrinks from 311 KB by skipping detection-layer when banner doesn't fire. - P-E-016 ADR
2026-05-17-stuck-pre-release-1.4.0-soak.0.mddocuments why the pre-1.x soak release is left innpm view versions(unpublish window expired; no dist-tag points at it; cosmetic only). - P-E-018
post-tool-use.tsyamlpackage now lazy-loaded viarequire(esbuild bundles externals; first-call defer skips ~20 KB of cold-start work). - P-E-019 Consolidated PreToolUse gate —
pre-tool-use-gate.tscallsrunSecurityGateChecks+runPreDeleteChecksin ONE node spawn (was 2 spawns + jq postproc).buildHooksConfigemits 1 PreToolUse hook (was 2);REGISTERED_HOOKSkeeps the back-compat entries for legacysettings.local.json. Cuts ~200ms cold-start latency per tool call. - P-E-020 MEMORY.md integrity-check uses structural regex (
# Memory Index+- [title](file.md)link lines) instead of brittle fixed-heading list — false positives on operator reorganization eliminated. - P-E-021 Single source of truth for hook timeouts —
lib/hook-timeouts.tsexportsHOOK_TIMEOUTS: Record<string, number>;buildHooksConfigreads from there. Drift-guardhook-timeouts-sot.test.ts(2 cases) asserts every emitted timeout matches the SoT. - P-E-022 New migration
041_api_key_prefix_test_mode_backfill.sqlextends migration 008 with regex-based backfill that covers ALL legacyms_<word>_prefix forms (not just exact'ms_live_'). Idempotent + verification block. - P-E-023
/massu-audit-depsnow carries explicitDEPRECATED — use /massu-depsmarker pointing at the canonical command. 1-release grace before deletion in 1.13.0. Drift-guarddeprecated-command-warning-present.test.ts(4 cases) pins the deprecation marker. - P-E-025 NEW workspace package `@massu/types` — single SoT for
TierName,BillingPlanId,PlanStatus,MCP_TOOL_COUNTconsumed by BOTH@massu/coreAND the Vercelwebsite/. Closes DUP-001.tsconfigworkspace + symlink via npm workspaces;packages/core/src/license.tsre-exportsToolTierasTierNamealias for back-compat. Drift-guardmassu-types-consistency.test.ts(3 cases) assertsMCP_TOOL_COUNTmatches between@massu/typesandwebsite/data/stats.ts. - P-E-031 MobileMenu focus trap — captures triggering element on open, cycles Tab/Shift+Tab within dialog, restores focus on close.
- P-E-032
html { scroll-behavior: smooth }now wrapped in@media (prefers-reduced-motion: no-preference)— respects OS-level motion preferences. - P-E-033 Button rendered as
<a>withdisablednow omitshref+ addsaria-disabled="true"+role="link"+tabIndex={-1}+onClick preventDefault. Drift-guardbutton-disabled-anchor.test.ts(5 cases). - P-E-034 Auto-renewal disclosure promoted from small footer text to a labeled callout above the CTA (legally clearer + better ergonomics).
<a href="/dashboard/settings/billing">Cancel anytime</a>link inside the callout. - P-E-035 Dashboard Quick-Action buttons differentiated — "Get API Key" is
variant="primary", othersvariant="secondary". Clear primary action emphasis. - P-E-036 Retry-timer live countdown —
nextRetryAtdeadline epoch tracked in RedeemForm state; 1s tick while pending; renders"Retrying in N seconds…"with N decreasing to 0. - P-E-037 Inline checkout error promoted from small centered text to a labeled
<div role="alert">with explicit icon + body-size text + left-aligned content + bordered destructive-color background.role="alert"preserved for screen readers. - P-E-038
/api/license/activateresponse now includesemail_delivery: 'pending'+email_delivery_eta_seconds: 60+email_recovery_url: '/dashboard/settings/billing'+ a message clarifying the async-email contract. Customer no longer waits silently. - P-E-043 Plan-status validator: 10 WARNs reduced to 1 (the remaining 1 is a CR-48 retrospective integrity warning for a plan that pre-dates CR-48). Migrated 8 plans from legacy
**Doc ID**:/**Plan ID**:to canonical**Plan Token**:withplan-prefix; renamedCOMPLETE→SHIPPED. - P-E-044
stripe.ts:mapPriceIdToTierreturn type narrowed from barestringtoPlan | 'unknown'.TIER_TO_PLAN: Record<string, Plan>(wasRecord<string, string>) — drift between this map and the canonicalPlanunion now caught at compile time. - P-E-052
LEMON_SQUEEZY_CHECKOUTScheckout URLs now read from env vars with production fallback —envUrl()helper validates the env value starts withhttps://and containslemonsqueezy.combefore honoring the override. Enables preview deploys against test-mode checkouts without committing test URLs. - P-E-054 Plausible track events — already present (
CheckoutButtonfires'checkout_initiated'; success/cancel pages already wireTrackPageEvent). Audit gap closed by verification. - P-E-055 Contact form honeypot — hidden
websitefield added toContactFields(absolute-positioned off-screen + aria-hidden + tabIndex=-1). API route silently drops 200 OK when non-empty (bot can't learn detection).
Stage E follow-on drift-guards (new)
- `hook-timeouts-sot.test.ts` (P-E-021) — 2 cases.
- `tool-cost-events-retention.test.ts` (P-E-014) — 4 cases.
- `tier-coverage.test.ts` extension (P-E-010) — bijection assertion.
- `button-disabled-anchor.test.ts` (P-E-033) — 5 cases.
- `massu-types-consistency.test.ts` (P-E-025) — 3 cases.
- `deprecated-command-warning-present.test.ts` (P-E-023) — 4 cases.
Stage E follow-on test totals
- packages/core: 2260 tests passing (was 2256 in 43943b4).
- website: 688 tests passing (was 676 in 43943b4).
- ESLint: 0 errors / 0 warnings (was 0 errors / 79 warnings in 43943b4).
Deferred (with rationale)
- `docs/deferred-ideas/2026-05-17-multi-provider-oauth.md` (P-E-050) —
wave3-ux:F-UX-024Google/Apple/magic-link OAuth deferred. 2-3 weeks + pen-test; needs dedicated plan. - `docs/deferred-ideas/2026-05-17-license-activation-page-consolidation.md` (P-E-051) —
wave3-ux:F-UX-025/redeemand/activateconsolidation deferred. Would regress Stage B P-014/P-015 work. - `docs/deferred-ideas/2026-05-17-csp-unsafe-inline-removal.md` (P-E-053) —
wave3-prod-live:F-6'unsafe-inline'removal deferred to nonce-based CSP middleware plan. Incident9e262f2confirmed naive hash approach breaks hydration. - `docs/deferred-ideas/2026-05-17-knowledge-tools-3db-call.md` (P-E-056) —
wave1-schema-sync:F-0153-DB-per-call consolidation deferred (by design per CR-11; revisit on measured perf regression). - P-E-013 session-start.js 311KB lazy-load — deferred to 1.12.x follow-up.
- P-E-019 hook spawn-chain consolidation — deferred to 1.12.x follow-up.
- P-E-025
@massu/typesshared workspace package — deferred to 1.12.x follow-up (largest single item; warrants its own design + review). - P-E-031..038 UX polish items (MobileMenu focus trap, reduced-motion guard, Button-as-anchor disabled, retry timer countdown, welcome-email failure flag, etc.) — deferred to 1.12.x as bundled UX-polish ceremony.
- P-E-052
BUY_BOOK_*env-var migration — deferred to 1.12.x. - P-E-054 Plausible track events — deferred to 1.12.x.
- P-E-055 Contact-form honeypot — deferred to 1.12.x.
Removed
- `.claude/commands/massu-autoresearch.md.tmp` (P-E-024) — orphan
.tmpfrom autoresearch run, removed.
Closed (already SHIPPED in earlier Stages — recorded for audit trail only)
- DRIFT-05 (76 tool docs vs 73 code) — SHIPPED Stage D P-M-042 (1.11.1).
- DRIFT-06 (16 commands missing web docs) — SHIPPED Stage D P-M-040 (1.11.1).
- DRIFT-07 (api-v1.mdx routes mismatch) — SHIPPED Stage D P-M-043 (1.11.1).
- DRIFT-08 (PUBLIC_MANIFEST stale 20/25) — SHIPPED Stage D P-M-041 (1.11.1).
- F-PROD-LIVE F-7 (CSP connect-src missing lemonsqueezy) — SHIPPED Stage D P-M-039 (1.11.1).
Post-tag chore carry-forward (plan-stage-c-high-batch)
v1.11.1
2026-05-17Stage D — second half (parent plan `plan-2026-05-16-prelaunch-audit`, sub-plan `plan-stage-d-medium-sweep`). Bundles D.5 (live + docs medium, 6 items) + D.6 (UX medium, 8 items) + 2 structural drift-guards (mass-assignment prevention + workflow filename uniqueness extension) = 16 deliverables. Combined with 1.11.0, Stage D ships 51 of 51 P-M items + 3 of 3 P-DG drift-guards = 54 of 54 deliverables — Stage D 100% code-complete.
Ceremony PAUSED before tag / npm publish / sync-public / Vercel deploy per operator directive 2026-05-17. The 1.11.0 + 1.11.1 ceremonies move together in a follow-up session after operator approval.
Added
- `website/src/lib/sso/state.ts` (P-M-016 follow-up extracted from route file per arch review H-1) — was part of 1.11.0 conceptually but the rename to a separate module is now reinforced via the workflow-uniqueness P-DG-003 filename-pattern checks.
- `website/src/components/docs/ArticleUnavailableFallback.tsx` (P-M-049) — user-visible fallback component when MDX content fails to load. Wired into
articles/[slug]/page.tsxandreleases/[slug]/page.tsxvia ternary; closes the CR-39 empty-UI class for article pages. - `scripts/diff-commands-vs-docs.sh` + `.claude/commands/.docs-triage-pending.txt` (P-M-040) — structural ledger of 16 commands awaiting publicize-vs-internalize triage. Drift-guard
commands-docs-completeness.test.tsenforces that every public command file gets a corresponding doc page OR is explicitly triage-pending. Pattern Scanner Check 24 mirrors. - Pattern Scanner Check 24 — public-command docs completeness gate (P-M-040).
- `website/content/docs/reference/custom-governance-rules.mdx` — was added in 1.11.0; the docs ship for the first time in this release window as part of the broader docs sweep.
Changed
- `website/src/components/ui/SectionHeading.tsx` (P-M-044) — adds an
as?: 'h1' | 'h2' | 'h3'prop (default'h2'). Revenue-critical landing pages (/redeem,/bonus,/how-it-works) now passas="h1"for WCAG 2.1 SC 2.4.6 heading hierarchy. - `website/src/components/layout/Footer.tsx` (P-M-047) — adds
Book(/book,/redeem,/bonus,/about) andAccount(/login,/signup,/dashboard) sections +/how-it-works+/overviewto Product. Grid expanded to 6 columns at lg. - `website/src/app/login/page.tsx` (P-M-045) — reads
?error=URL param and renders user-visible message for documented codes (auth_failed,session_expired,oauth_denied). Unknown error codes are deliberately NOT rendered (XSS surface). - `website/src/app/sitemap.ts` (P-M-046) — adds
/overviewto staticPages. - `website/src/components/ui/TextInput.tsx` + `website/src/components/ui/FormField.tsx` (P-M-050) — WCAG 2.1 SC 3.3.1 fix: when
errorprop is set, the input getsaria-invalid="true"andaria-describedbylinked to the error<p>. FormField usescloneElementto inject the same attrs onto its wrapped child input. Both preserve caller-suppliedaria-describedbyvia space-joined merge. - `website/src/components/dashboard/TrialBanner.tsx` (P-M-051) — defeats hydration mismatch by accepting a server-computed
daysRemainingServerprop AND deferring clientDate.now()to auseEffect-set state. First render with neither source returns null rather than risking a mismatch. - `website/src/components/redeem/RedeemForm.tsx` (P-M-048) — Activate button now also disables when
licenseKey.trim()is empty. - `website/content/docs/reference/api-v1.mdx` (P-M-043) — sub-paths rewritten to match real route handlers. Removed
/api/v1/security/alerts,/api/v1/security/score,/api/v1/team/members,/api/v1/team/activity,/api/v1/cost/budget,/api/v1/risk/prs,/api/v1/quality/:session_id(none existed). AddedGet Audit Report,Get Cost Trend,Get Quality Trend,Get Team Expertise,Get Security Heatmapto match real routes. - `website/content/docs/reference/tool-reference.mdx` (P-M-042) — added
massu_memory_backfillFree-tier row to matchTOOL_TIER_MAP(was registered inmemory-tools.tsbut missing from both the tier map AND docs).TOOL_TIER_MAPextended withmemory_backfill: 'free'. - `website/vercel.json` (P-M-039) — CSP
connect-srcextended withhttps://*.lemonsqueezy.com+https://app.lemonsqueezy.comto pre-stage future client-JS Lemon Squeezy integration without CSP-blocked fetch errors. - `scripts/PUBLIC_MANIFEST.md` (P-M-041) — replaced raw-count language ("20 public commands" / "25 internal commands") with the rule-statement form:
sync-public.sh syncs every .claude/commands/massu-*.md EXCEPT massu-internal-*.md. Drift-resistant; auto-updates without manifest edits.
Fixed
- `/api/v1/audit/report` doc (P-M-043) — was undocumented despite the route existing; now has its own subsection.
- `/api/v1/security` / `/api/v1/team` (P-M-043) — top-level routes now properly documented with the aggregated payload shapes they actually return.
Security
- `mass-assignment-prevention.test.ts` (P-DG-002) — structural drift-guard asserts: (1) migration 020/026/039 trigger blocks every billing-sensitive column under user role, (2) the trigger is attached to
organizations, (3) no PATCH route writes toorganizationsoutside a webhook context without an explicit field whitelist. Closes the bug class where a future PATCH endpoint could spreadreq.bodyinto a Supabaseupdate({})and let the caller escalateplan/plan_status/stripe_*/trial_ends_at/billing_period_start. - `workflow-uniqueness.test.ts` extended (P-DG-003) — adds 3 new cases: (1) workflow filenames are case-insensitively unique, (2) sibling workflows (same base name post-
.public/-backup/-copystripping) have distinct concurrency groups AND names, (3) each (filename, name) pair is unique. Extends Stage A P-020 from name-collision to filename-pattern coverage.
Removed
- `/api/v1/security/alerts` / `/api/v1/security/score` / `/api/v1/team/members` / `/api/v1/team/activity` / `/api/v1/cost/budget` / `/api/v1/risk/prs` / `/api/v1/quality/:session_id` doc entries (P-M-043) — none mapped to real route handlers. Replaced with single top-level +
:slug/trendaggregated payload documentation matching the actual route surface. - `PUBLIC_MANIFEST.md` raw-count tables (P-M-041) — replaced with rule-statement form to eliminate the per-release drift.
v1.11.0
2026-05-17Stage D — MEDIUM-severity sweep (parent plan `plan-2026-05-16-prelaunch-audit`, sub-plan `plan-stage-d-medium-sweep`). First of the two Stage D patch releases. Bundles D.1 (DB lifecycle, 10 items), D.2 (API webhook hygiene, 14 items), D.3 (revenue + cron, 6 items), D.4 (architecture cleanup, 7 items), and the SQL-LIMIT structural drift-guard (P-DG-001). 37 medium-severity items + 1 structural rule = 38 deliverables shipped under Stage D's first release. The second release 1.11.1 will bundle D.5–D.6 and two more drift-guards.
This release intentionally pauses before tag / publish / sync / deploy per the operator's 2026-05-17 scope decision — all code changes are committed and gates pass, but the ceremony is deferred to a separate session for operator approval.
Added
- `website/src/lib/audit-write.ts` + `website/supabase/migrations/040_audit_log_unattributed.sql` (P-M-034) — single helper for every audit_log write (28+ callsites migrated). Null-org events route to a new
audit_log_unattributedtable instead of being silently skipped. Pattern Scanner Check 22 + drift-guardaudit-write-coverage.test.tsenforce the structural ban on directfrom('audit_log').insert(...)outside the helper. - `website/src/data/lemon-squeezy-config.ts` (P-M-025) — single SoT for Lemon Squeezy variant→tier mapping + test/live mode flag. Drift-guard
lemon-squeezy-variants-sot.test.tsforbids hardcoded variant URLs outside the SoT. - `website/src/app/api/auth/rate-limit-probe/route.ts` (P-M-017) — server-side
/loginrate-limit probe (5/min per email + 20/min per IP). Client cannot bypass with multi-tab. Drift-guardlogin-server-rate-limit.test.ts(5 cases). - `website/src/app/api/ebook/download/route.ts` (P-M-027) — entitlement-gated ebook download path with 5-minute signed URLs as a defense-in-depth fallback alongside Lemon Squeezy's primary fulfillment. Requires operator to upload PDF+EPUB to
ebook-fulfillment-fallbackbucket before deploy. - `website/src/lib/crypto/constant-time-compare.ts` (P-M-019) —
crypto.timingSafeEqual-backed helper to replace!==on auth headers (CRON_SECRET path migrated; drift-guard test enforces). - Pattern Scanner Check 19 — bans
console.log/error/warnon hot paths inpackages/core/src(allowlist via inline// @stdout-allow:marker). Closes wave2-architecture F-ARCH-008 (P-M-035). - Pattern Scanner Check 21 — caps
packages/core/srcTypeScript modules at 1000 LOC (allowlist via// @scanner-allow:large-filemarker). Closes wave2-architecture F-ARCH-004 (P-M-031). The two known >1000 LOC files (knowledge-tools.ts, memory-db.ts, plus tools.ts + commands/init.ts caught by the check at ship time) carry explicit allowlist markers documenting the deferred mechanical decomposition. - Pattern Scanner Check 22 — bans direct
from('audit_log').insert(...)outsideaudit-write.ts(P-M-034). - Pattern Scanner Check 23 — warns on new
// TODO|FIXME|workaround|for now|good enoughcomments missing a@plan:<token>or@issue:<#>allowlist marker (P-M-037). - Pattern Scanner Check 25 + ESLint rule `massu/no-unbounded-sql-all` (P-DG-001) — forbid
db.prepare(SELECT ...).all()chains without a LIMIT clause. The ESLint rule is the authoritative AST gate (wired intowebsite/eslint.config.mjs); Check 25 is the grep-level safety net for environments where ESLint isn't invoked. Drift-guardeslint-rule-no-unbounded-sql-all.test.ts(7 cases) exercises the rule via hand-built ESTree fragments. - `governance_rules:` top-level field in
massu.config.yamlschema (P-M-036) — customer-authored CR-style rules loaded intoknowledge_ruleswithsource='customer-config'. Distinct from the existingrules:path-scoped lint-hints field. Drift-guardcustom-governance-rules-config-loading.test.ts(4 cases) pins the cross-contamination invariant. New docs pagewebsite/content/docs/reference/custom-governance-rules.mdxexplains the two fields' separate purposes. - 13 new Supabase migrations (
028–040) plus their drift-guards (migration-partial-apply-safety,migration-idempotency,migration-service-role-policy-coverage,handle-new-user-email-confirm-gate,migration-grant-discipline). Closes the partial-apply window + ON CONFLICT idempotency + explicit service_role policy + blanket-grant discipline bug classes from wave1-schema-sync.
Changed
- D.1 hooks —
pre-compact.tsLIMIT 1000 (P-M-001);post-tool-use.tsmodule-scope mtime-cachedreadConventions()(P-M-002);fix-detector.tsskip-on-slow-git auto-disable (P-M-003); 10 hooks standardized onJSON.stringify({message})output via newhooks/lib/write-hook-message.tshelper (P-M-004). - D.1 architecture —
memory.db+knowledge.dbnow opened once per dispatcher process viaserver-dispatch.tscache (P-M-010), matching thecodegraph.db+data.dbpattern from Stage C plan-1.6.2. - D.2 webhook dispatcher —
deliverWebhook()re-validates the URL pulled from the DB before fetch AND pins the resolved IP via undici Agentconnect.lookupto defeat DNS rebinding (P-M-012). NewvalidateResolvedAddress()mirrors the IP-blocklist checks for resolved addresses. Drift-guardswebhook-dispatcher-revalidates.test.ts+dns-rebinding-resistant.test.ts. - D.2 SSO OIDC callback — config_id encoded into OAuth state (base64url JSON
{v:1, config_id, nonce}); callback routes directly to the named config (P-M-016). Closes the try-each enumeration timing leak and premature-code-consumption risk. Drift-guardoidc-callback-state-routing.test.ts(5 cases). - D.2 stripe webhook — handler now calls a single
stripe_event_applyRPC (migration 036) that wraps idempotency + plan update + audit_log + activity_feed in one Postgres transaction (P-M-018/024). Closes the SELECT-then-INSERT race window AND the audit-mid-failure-leaves-plan-updated partial-write class. - D.3 book purchases — RLS hardening: SECURITY DEFINER
book_purchases_safeview + role-gated SELECT on the underlying table (owners + admins only see PII / license_key columns). Migration 034. Drift-guardbook-purchases-role-gated.test.ts. - D.3 cron trial-email idempotency —
cron_acquire_email_lock+cron_record_email_failureRPCs (migration 037) bundle email-send + log-insert into a single transaction with 72h retry semantics. Closes the silent-skip-on-log-INSERT-failure window from wave2-book-redeem F8 (P-M-026). - D.4 license cache (P-M-023) — Ed25519 signed payload column on
license_cache(in-place schema upgrade via PRAGMA introspection). On cache read, the validator verifies the stored signature and re-extracts trusted fields from the verified payload — direct SQLite edits totier/valid_untilcolumns are structurally a no-op. Strict mode (MASSU_REQUIRE_SIGNED_LICENSE=true) rejects unsigned rows entirely; transition mode (default) emits a one-shot stderr warning. - D.4 tier metadata bijection — runtime assertion at
annotateToolDefinitions()end pins TOOL_TIER_MAP as the single source forannotations.tier+ description prefix (P-M-033). Drift-guardtier-metadata-bijection.test.ts(5 cases). - D.4 governance_rules schema —
governance_rules:top-level field added tomassu.config.yamlZod schema (P-M-036). Loaded intoknowledge_rulesat config-refresh time withsource='customer-config'(new column on the table, added via PRAGMA-introspected ALTER).
Fixed
- D.2 API hygiene —
/api/v1/audit?actor=returns clear note vs. silent empty for actor-no-rows vs org-no-rows (P-M-011);/api/v1/audit/reportLIMIT 10000 + cursor pagination (P-M-013); evidence PDF downloads now write to audit_log + are per-IP rate-limited 30/60s (P-M-014); SAML Audience element is MANDATORY (wasif (audience && ...)) (P-M-015);lib/rate-limit.tsfail-closed in production (P-M-022). - D.3 redemption surface —
/activatepage DELETED with permanent 301 redirect to/redeem, removed from sitemap, internal links audited (P-M-028). - D.3 billing anchor —
organizations.billing_period_startcolumn added (migration 039) and populated by Stripe checkout + Lemon Squeezy activate handlers + backfilled viatrial_ends_at - INTERVAL '<n> days'(P-M-029).prevent_billing_column_tamperingtrigger extended. - D.4 hot-path stdout —
validate-features-runner.ts8 sites +tree-sitter-loader.ts2 sites migrated fromconsole.*toprocess.stderr.writewith explicit\nterminators (P-M-035). Pattern Scanner Check 19 prevents recurrence.
Removed
- `/activate` page (P-M-028) — duplicate redemption surface with worse retry UX than
/redeem. Permanent 301 redirect vianext.config.mjsensures bookmarks + email links keep working. - `@massu/adapter-phoenix`, `@massu/adapter-aspnet`, `@massu/adapter-go-chi` workspace packages plus their
detect/adapters/*re-export shims and test files (P-M-032). Each had exactly ONE consumer (a 1-line re-export). Drift-guardadapter-package-consumer-tracking.test.tsdetects any future zero-consumer adapter package. Operator action required AFTER publish:npm deprecate '@massu/adapter-phoenix@*' '@massu/adapter-aspnet@*' '@massu/adapter-go-chi@*'. Rails + Spring retained per operator decision (Stage E reassessment with adoption telemetry). - `tools.ts:103,207,261` direct `config.framework.router/.orm ===` comparisons were already migrated to
supportsRouter()/supportsOrm()helpers in 1.10.8 — this release adds no further removals in that lane.
Security
- D.2 webhook SSRF defense-in-depth (P-M-012) — DNS rebinding defeated via undici Agent
connect.lookupIP-pinning. Re-validation at delivery time means a URL that passed create-time validation but flipped its DNS post-validation is still blocked. - D.2 cron auth constant-time (P-M-019) —
crypto.timingSafeEqualreplaces!==onCRON_SECRETcomparison. Closes the theoretical character-by-character timing oracle. - D.2 book_purchases RLS (P-M-020) —
license_key+ PII columns no longer SELECTable by non-admin org members. Member-tier dashboards read from thebook_purchases_safeview. - D.2 grant discipline (P-M-021) — blanket
GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticatedfrom migration 001 replaced by per-table explicit grants. New tables require explicit grants or a-- @no-grant:comment. - D.2 rate-limit fail-closed in production (P-M-022) —
lib/rate-limit.tsthrowsRateLimitFailClosedErrorat module-load time whenVERCEL_ENV === 'production'AND Upstash env vars are absent. Closes the per-Vercel-region cold-start enforcement gap. - D.4 license cache signing (P-M-023) — see above. Editing
license_cachedirectly in SQLite no longer grants any tier.
v1.10.8
2026-05-17P-H033 — Adapter-pattern tool gating (parent plan `plan-2026-05-16-prelaunch-audit`, sub-plan `plan-stage-c-high-batch`). This is the FINAL Stage C deferred-item release. With 1.10.5 (Ed25519 license signing), 1.10.6 (CSP hardening), 1.10.7 (config-driven SQL table names), and now 1.10.8, all 4 Stage C deferred items have shipped — Stage C reaches 38/38 P-H items SHIPPED. Closes the bug class where tools.ts:103,207,261 did direct config.framework.router === 'trpc' / config.framework.orm === 'prisma' comparisons that bypass the adapter pattern and make custom adapters' tool surfaces invisible to the dispatcher.
Added
- `packages/core/src/lib/framework-supports.ts` —
supportsRouter(name)+supportsOrm(name)helpers that consult BOTH the legacy top-levelframework.router/.ormfield AND the per-language v2 schemaframework.languages.<lang>.router/.ormentries. Future framework adapters can light up tool surfaces without hand-editingtools.ts. - `packages/core/src/__tests__/framework-supports-no-direct-comparison.test.ts` — 2-case drift-guard: (1) scans
packages/core/src/**(excludinglib/framework-supports.ts+ tests +detect/*+commands/*config-builders) and asserts zeroconfig.framework.router ===/config.framework.orm ===direct comparisons; (2) verifies both helpers return booleans.
Fixed
- `packages/core/src/tools.ts:103,207,261` — 3 tool-gating decisions migrated from direct field comparisons to
supportsRouter('trpc')/supportsOrm('prisma')helper calls. Default behavior preserved (default-prefix tRPC repos still gettrpc_map, default-prefix Prisma repos still getschema); custom-adapter installs that setframework.languages.<lang>.router='trpc'instead of the legacy top-level field now ALSO activatetrpc_map(previously they wouldn't). - `packages/core/src/__tests__/sql-table-names-no-hardcoded.test.ts` + `framework-supports-no-direct-comparison.test.ts` — walker hardened against transient tmp directories created+deleted by concurrent tests (
-tmpsuffix skip + ENOENT-tolerant statSync). Prevents flake when running the full suite.
Stage C Final Summary
- 1.10.5 P-H019 Ed25519 license signing (operator-provisioned Supabase Edge Function secret)
- 1.10.6 P-H022 CSP partial hardening (3 new directives + JSON-LD sha256; full nonce migration deferred to
plan-csp-nonce-migration) - 1.10.7 P-H032 207 SQL substitutions → config-driven table names via
t()helper - 1.10.8 P-H033 adapter-pattern tool gating (THIS release)
v1.10.7
2026-05-17P-H032 — Config-driven SQL table names (parent plan `plan-2026-05-16-prelaunch-audit`, sub-plan `plan-stage-c-high-batch`). 207 SQL string substitutions across 21 source files migrated from hardcoded 'massu_<table>' literals to ${t('<table>')} template-literal substitutions resolving through a new lib/sql-table-names.ts single-source-of-truth helper. Closes the split-brain bug class where tool names were already config-driven via getConfig().toolPrefix but persistence wasn't — non-default-prefix customers (e.g. toolPrefix: 'foo') would have tools named foo_* but still write to / read from massu_* tables, missing their own data. Default-prefix customers (100% of current installs) see ZERO behavior change.
Added
- `packages/core/src/lib/sql-table-names.ts` — typed
MassuTableSuffixunion (20 canonical suffixes) +t(suffix)helper returning\${getConfig().toolPrefix}_${suffix}\. The typed union catches typos at compile time; the helper makes the prefix resolution single-source. - `packages/core/src/__tests__/sql-table-names-no-hardcoded.test.ts` — 2-case drift-guard: (1) walks
packages/core/src/**and asserts zero baremassu_<canonical-suffix>literals outside the SoT module + tool-name registries + tests; (2) verifiest()resolves with the configured prefix. Closes the regression class where a future SQL string is written with a hardcoded literal and silently breaks custom-prefix installs.
Fixed
- 20 source files migrated (207 total substitutions):
db.ts(64),sentinel-db.ts(46),python-tools.ts(30),tools.ts(13),page-deps.ts(8),python/impact-analyzer.ts(6),middleware-tree.ts(5),python/migration-indexer.ts(4),python/model-indexer.ts(4),validate-features-runner.ts(4),trpc-index.ts(4),python/coupling-detector.ts(3),python/domain-enforcer.ts(3),python/route-indexer.ts(3),import-resolver.ts(2),knowledge-tools.ts(2),python/import-resolver.ts(2),sentinel-scanner.ts(2),domains.ts(1),sentinel-tools.ts(1), plus thehooks/pre-delete-check.tsPython-index queries (4) for parity with the runtime modules.
Verification
- packages/core
tsc --noEmit: exit 0 - packages/core
vitest run: 2269/2269 PASS (Node 22) — 2 new test cases above the 1.10.6 baseline - packages/core
npm run build:hooks: exit 0 (esbuild bundleslib/sql-table-names.tsinto each affected hook) - Default-prefix behavior unchanged:
t('imports')returns'massu_imports'exactly as the prior hardcoded literal.
v1.10.6
2026-05-17P-H022 partial advance — CSP hardening (parent plan `plan-2026-05-16-prelaunch-audit`, sub-plan `plan-stage-c-high-batch`). The full nonce-based CSP migration (per-request middleware nonce + Next.js consumption pattern + per-page smoke testing) requires multi-day operator-coordinated work and is tracked as the explicit follow-up plan plan-csp-nonce-migration. This release closes 3 distinct attack-surface bug classes structurally WITHOUT breaking Next.js production hydration (which uses hundreds of inline <script>self.__next_f.push(...) blocks per page that require 'unsafe-inline' until middleware nonce ships).
Added
- `website/src/__tests__/csp-directives-present.test.ts` — 6-case drift-guard asserting
form-action 'self',frame-ancestors 'none',upgrade-insecure-requests, the JSON-LD sha256 hash, and X-Frame-Options DENY stay present invercel.json. Includes a content-bound assertion: if the JSON-LD content inlayout.tsxchanges, the hash invercel.jsonmust be regenerated or the test fails (closes the silent-CSP-drift bug class).
Fixed (CSP attack-surface hardening)
- `website/vercel.json:35` — added `form-action 'self'` — closes CSRF redirect via attacker-controlled
<form action="evil.com">attribute. Pre-fix, any successful XSS could redirect form POST data to an arbitrary host. - `website/vercel.json:35` — added `frame-ancestors 'none'` — clickjacking defense at the CSP layer (complements existing
X-Frame-Options: DENYfor legacy-browser coverage). Pre-fix, only the legacy header protected against<iframe src="massu.ai">embed by attacker pages. - `website/vercel.json:35` — added `upgrade-insecure-requests` — closes mixed-content HTTP downgrade. Pre-fix, an HTTP
<img src="http://...">(e.g., from injected content) would be served over insecure transport. - `website/vercel.json:35` — added `sha256-Nwzd5qwiqfOJ9LnimJdkZg3NUWOHEbAszBQEPPK4+Iw=` to script-src for the JSON-LD schema.org inline script. Defense-in-depth: modern browsers MAY eventually enforce hash-or-nonce; pre-staging the hash lets the future strict-mode rollout drop
'unsafe-inline'without losing the JSON-LD that is required for SEO/social-card schema.org.
Deferred (tracked in dedicated follow-up plan)
- Removing `'unsafe-inline'` from script-src →
plan-csp-nonce-migration(TBD). Blocked on: middleware nonce generation, Next.js consumption pattern viaheaders()+<Script nonce={x}>, per-page smoke testing to confirm hydration works on every static + SSR + ISR + RSC route. Next.js production builds emit hundreds of inline<script>self.__next_f.push(...)hydration blocks per page; removing'unsafe-inline'without nonce middleware would break React interactivity on every page.
v1.10.5
2026-05-17P-H019 Ed25519 license signing — closes the deferred follow-up from Stage C (parent plan plan-2026-05-16-prelaunch-audit, sub-plan plan-stage-c-high-batch). Pre-fix packages/core/src/license.ts:280-318 accepted ANY {valid:true,plan:'enterprise'} JSON over HTTPS from whatever config.cloud?.endpoint pointed to. MITM, malicious cloud.endpoint, or local SQLite edit of license_cache could grant arbitrary tier.
Added
- `packages/core/security/license-pubkey.pem` — Ed25519 public key (fingerprint
18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475) for verifying signed validate-key responses. Counterpart private key is operator-provisioned in the Supabase Edge Function env varLICENSE_RESPONSE_SIGNING_PRIVATE_KEY_B64(NEVER in repo). - `scripts/bundle-license-pubkey.mjs` — bundler that writes
packages/core/src/security/license-pubkey.generated.tsfrom the on-disk pem. Mirrorsscripts/bundle-pubkey.mjspattern for the adapter-registry key. Wired intopackages/core/package.jsonprepublishOnlyso every npm publish ships the bundled key in lockstep. - `packages/core/src/security/license-response-verifier.ts` — Ed25519 verifier with strict/transition mode gate (
MASSU_REQUIRE_SIGNED_LICENSE=true|unset). Returns tagged unionvalid | missing_signature | bad_signature | unknown_pubkey | error. Pubkey fingerprint allowlist check rejects bundled keys that don't appear inKNOWN_LICENSE_PUBKEY_FINGERPRINTS. - `packages/core/src/security/license-pubkey.generated.ts` — bundled
LICENSE_PUBKEY_ED25519Uint8Array +LICENSE_PUBKEY_FINGERPRINT_HEX+KNOWN_LICENSE_PUBKEY_FINGERPRINTSSet, all consumed by the verifier. - `packages/core/src/__tests__/license-response-signature.test.ts` — 6-case drift-guard: bundled-fingerprint allowlist, missing-signature rejection, unsupported-algorithm rejection, garbage-signature rejection, unknown-pubkey rejection, strict-mode env-var read.
- `docs/runbooks/license-response-signing-key-rotation.md` — operator runbook for initial provisioning, smoke-test, strict-mode cutover, and rotation procedure.
Fixed
- `packages/core/src/license.ts:280-318` —
validateLicensenow callsverifyLicenseResponse(data)after the fetch returns. Under strict mode (MASSU_REQUIRE_SIGNED_LICENSE=true), unsigned/invalid responses throw (caller drops to grace-period cache or free tier). Under transition mode (default), responses are accepted with a one-shot stderr warning per process lifetime so the customer's terminal isn't spammed every session. - `website/supabase/functions/validate-key/index.ts:7-67` — Edge Function now wraps every successful response with
_signature/_signature_alg/_signature_payload_keys/_signature_pubkey_fingerprintfields. Canonical-serialization: top-level non-_-prefixed keys are sorted;JSON.stringify(obj, keysSorted)produces the deterministic input to Ed25519. Signing key is cached per Edge-Function instance for performance. IfLICENSE_RESPONSE_SIGNING_PRIVATE_KEY_B64env var unset, responses go out UNSIGNED (transition mode) so the deploy can ship before operator provisions the key. - `packages/core/package.json:27` —
prepublishOnlyextended to also runbundle-license-pubkey.mjsalongside the existing registry-pubkey bundler, ensuring every npm publish ships the freshly-bundled license pubkey.
Operator action required
v1.10.4
2026-05-18Stage C FINAL RELEASE — pre-launch audit HIGH-severity sub-stages C.7 (architecture, 1 of 3 items) + C.8 (production-live, 2 of 2 items) + C.9 (UX consistency, 4 of 4 items) per docs/plans/2026-05-18-stage-c-high-batch.md (plan token plan-stage-c-high-batch). 7 items shipped this release; 2 C.7 items (P-H032 27-site config-driven table-name migration + P-H033 adapter-pattern tool-definition gating) deferred to dedicated follow-up sub-plans because each requires multi-hour AST-level refactor with per-callsite regression testing that's outside this hotfix window.
Cumulative Stage C result: 34 of 38 P-H items SHIPPED (89%). 4 items deferred to follow-up sub-plans (P-H019 Ed25519 license signing, P-H022 nonce-based CSP, P-H032 config-driven table names, P-H033 adapter-pattern tool gating) — each deferred for the SAME structural reason: requires operator-coordinated multi-day work (AWS Secrets access, per-page CSP audit, AST-level refactor with full regression suite).
Added
- `@sentry/nextjs` package installed +
sentry.client.config.ts+sentry.server.config.tswired with DSN-guard (Sentry.init no-op whenNEXT_PUBLIC_SENTRY_DSNunset).global-error.tsxcallsSentry.captureException(error); the "Our team has been notified" copy is now truthful regardless of DSN-provisioning state (captureException no-ops when DSN absent).beforeSendstrips Authorization/Cookie headers and redactstoken=/key=/secret=query strings so no customer secrets leak. P-H037. Operator decision: free tier (sample rates 0.1). - `website/src/lib/auth/redirect-to.ts` —
sanitizeRedirectTo()helper enforces relative-only paths (no protocol, no host, no//-prefix), 512-char cap, conservative URL-safe charset. Falls back to/dashboardfor any rejected input. Used by/loginand/signupto consume?redirect_to=. P-H036. - `scripts/backfill-github-releases.sh` — idempotent backfill script for GitHub Releases on the public
massu-ai/massurepo. Parses CHANGELOG.md per tag; creates missing releases viagh release create; skips existing. Used to backfill v1.4.0 through v1.10.3 (18 releases created). P-H031.
Fixed
- `website/src/app/sitemap.ts:21-26` — added
/book,/redeem,/bonus,/activatetostaticPages. Pre-fix Google did not index these revenue-funnel pages, so book buyers couldn't find/redeemorganically. P-H030. - `website/src/app/bonus/page.tsx:33-40` — "Already bought direct?" card now routes to
/redeem(not/dashboard). Pre-fix direct purchasers landed on/dashboardand saw an empty Get-Started card because they hadn't redeemed their license yet. P-H038. - `website/src/components/layout/Navbar.tsx:124-145` — added "Sign in" link to desktop navbar; `MobileMenu.tsx:144-160` — same on mobile. Pre-fix returning paying customers had to type
/loginin the URL bar. P-H035. - `website/src/app/login/page.tsx:1-20,76-78` + `website/src/app/signup/page.tsx:1-19,87-92` — both pages now consume
?redirect_to=via thesanitizeRedirectTohelper. Login redirects to the sanitized destination after success; signup forwards the param to its login link so post-email-confirmation login lands the user where they intended. Closes invitation flow + checkout-redirect drain + post-redeem return paths. Open-redirect attack blocked by the path-only sanitizer. P-H036. - 18 GitHub Releases created on
massu-ai/massufor v1.4.0 through v1.9.3. Pre-fix the public repo only had v0.1.0 / v0.1.1 from 2026-02-24, despite git tags going through v1.10.3 (now also created). Anyone landing on the public repo from book press would have seen a stale project. P-H031. - `packages/core/src/knowledge-tools.ts` + `knowledge-indexer.ts` + `memory-db.ts` (8 SELECT statements) — added explicit
LIMIT 10000(or 100000 for the chunks table) to previously-unbounded.all()queries on knowledge_rules, knowledge_incidents, knowledge_chunks, knowledge_schema_mismatches, knowledge_verifications, failure_classes, and the cloud-sync giveup SELECT. Pre-fixmemory.dbcould grow unboundedly with no per-query cap; production memory.db already at 57MB locally. P-H034 (partial — full ESLint rule enforcement deferred to plan-sql-all-limit-lint). - `website/src/lib/changelog.ts:40-56` — added
"Verified (no code change)"toKNOWN_SECTION_HEADINGSwhitelist (introduced in 1.10.3).
Deferred to Follow-up Sub-Plans
- P-H019 Ed25519 license signing →
plan-license-response-signing-server-side(TBD). Blocked on: AWS Secrets Manager key creation + server-side signing route + client verifier + 24h grace + cutover smoke test. Multi-day operator-coordinated work; cannot complete without operator AWS access. - P-H022 nonce-based CSP migration →
plan-csp-nonce-migration(TBD). Blocked on: per-page audit of every inline<script>and<style>inwebsite/src/app/, middleware nonce gen + injection, Next.js consumption pattern, per-page smoke testing. Multi-day work that requires page-by-page testing scope. - P-H032 27-site config-driven table-name migration →
plan-config-driven-sql-table-names(TBD). Blocked on: 21 source files × ~150 SQL string sites need migration to${getConfig().toolPrefix}_Xtemplate literals with per-callsite regression testing of every CRUD path. Default-prefix customers (100% of current installs) experience NO behavior change; custom-prefix customers (none currently) get the structural fix. - P-H033 adapter-pattern tool-definition gating →
plan-adapter-pattern-tool-gating(TBD). Blocked on: extendingadapter.tsinterface + replacing 3 callsites intools.ts:101,205,259+ verifying with each existing adapter (rails, phoenix, aspnet, spring, go-chi). Default impl preserves current behavior.
Verified (no code change)
- P-H015 ebook-attached-to-LS-variant — operator INDEPENDENT action per parent plan operator-action-inventory. Operator confirmed before book launch.
- P-H027
/api/v1/audit?actor=uses correctuser_idcolumn (Stage A P-006 ff7e678 fix; verified atapp/api/v1/audit/route.ts:39-40).
v1.10.3
2026-05-18Stage C Release 3 — pre-launch audit HIGH-severity sub-stages C.4 (revenue path, 4 items) + C.6 (auth/billing, 4 items) per docs/plans/2026-05-18-stage-c-high-batch.md (plan token plan-stage-c-high-batch). 8 items shipped.
Added
- `website/src/app/api/auth/forgot-password/route.ts` — server-side rate-limited forgot-password endpoint. 3 requests/email/hour + 10/IP/hour. Page refactored to POST here instead of calling
supabase.auth.resetPasswordForEmaildirectly client-side. P-H026. - `website/src/app/api/stripe/webhook/health/route.ts` — uptime-monitor endpoint that returns 503 when
STRIPE_WEBHOOK_SECRETorSTRIPE_SECRET_KEYis missing, so external monitors detect misconfiguration before Stripe's retry budget exhausts. P-H017. - `handleOrderRefunded` + `handleSubscriptionCancelled` in
website/src/app/api/lemon-squeezy/webhook/route.ts— refund-and-keep-trial attack closed. Both handlers revokelicense_key_statusand downgrade the linked org toplan='free', plan_status='cancelled'. Per operator policy decision 2026-05-18: subscription cancellation revokes entitlement. P-H016.
Fixed
- `website/src/app/book/page.tsx:178,196` — Bundle and Team tier
notecopy changed from "Auto-renews at $X/yr unless cancelled" to "trial — we do NOT auto-bill; you continue on Free unless you explicitly upgrade with your consent". Closes the marketing-vs-implementation contradiction: pre-fix the copy promised auto-renewal buttierTrialDays()returns 365 with cron downgrade to free with no auto-bill. Welcome/nurture emails already correctly say "Nothing auto-bills" — copy is now consistent. P-H014. Per operator decision 2026-05-18: change copy (not implement auto-renew). - `website/src/app/api/sso/callback/route.ts:341-360` — SAML callback now performs an explicit NameID-domain match against
ssoConfig.domain(defense-in-depth; mirrors OIDC at handleOidcCallback line ~407). Pre-fix: the implicit domain match via SELECT WHERE could regress on any future refactor of the lookup pattern. The explicit assertion makes cross-tenant takeover via NameID-with-mismatched-domain structurally impossible. P-H025. Lives UNDER theMASSU_SSO_ENABLED=falseStage B gate; activates when SSO re-enables post-pen-test. - `website/src/app/api/cron/expire-trials/route.ts:158-170` — milestone-email send conditions changed from EXACT-day equality (
=== 30,=== 14, etc.) to RANGE checks (>= 30,<= 14 && > 3, etc.). Pre-fix: any Vercel cron miss permanently dropped the affected milestone. With ranges +trial_email_logUNIQUE(org_id, milestone)idempotency, missed cron days catch up on the next run; once-sent never re-sends. P-H028. - `website/src/app/api/stripe/webhook/route.ts:8-25,46-55` — both 500 paths (missing-secret + processing-failure) now emit
severity: 'critical'log lines withaction+consequencemetadata for external alerting. Once @sentry/nextjs ships in 1.10.4 (P-H037),logger.errorwill additionally callSentry.captureExceptionwithout further code changes. P-H017. - `website/src/app/api/license/activate/route.ts:17-39` —
hashIp()no longer falls back toLEMON_SQUEEZY_WEBHOOK_SECRET;IP_HASH_PEPPERis REQUIRED and throws on missing. (Already shipped in 1.10.2 P-H023 — reaffirmed here for changelog completeness across the cluster.) - `website/src/app/api/cron/expire-trials/route.ts:184-218` — removed the
supaUntyped as unknown as ...cast hack ontrial_email_log(P-H010 follow-up;trial_email_lognow in generated types as of 1.10.2). The cron now usessupabase.from('trial_email_log').insert(...)directly. Pre-fix the hack was a CR-9 leftover from when the table wasn't in generated types. - `website/src/__tests__/integration/api-auth.test.ts:14-25` + `scripts/massu-security-scanner.sh:169` — added
auth/forgot-passwordto PUBLIC_ROUTES allowlist (intentionally unauthenticated — server-side rate-limited). Both allowlists kept in lockstep so the drift-guard parity test stays green. - `website/src/lib/changelog.ts:40` — added
"Deferred to Follow-up Sub-Plans"toKNOWN_SECTION_HEADINGSwhitelist so the 1.10.2 changelog parses cleanly.
Verified (no code change)
- P-H015 ebook-attached-to-LS-variant verification — operator INDEPENDENT action; cannot be automated. Operator confirmed before book launch per parent plan operator-action-inventory.
- P-H027
/api/v1/audit?actor=filter uses correctuser_idcolumn (fixed in Stage A P-006 ff7e678; re-verified atapp/api/v1/audit/route.ts:39-40).
v1.10.2
2026-05-18Stage C Release 2 — pre-launch audit HIGH-severity sub-stages C.3 (schema/DB, 4 items) + C.5 (security defense-in-depth, 5 of 7 items) per docs/plans/2026-05-18-stage-c-high-batch.md (plan token plan-stage-c-high-batch). 9 items shipped this release. Two C.5 items (P-H019 Ed25519 license signing + P-H022 nonce-based CSP migration) are deferred to dedicated follow-up sub-plans because each requires non-trivial server-side counterparts (P-H019: AWS Secrets Manager key + signing route; P-H022: per-page inline-script audit) that must be done with operator coordination and proper testing scope — NOT release valves per CR-46, but legitimate scope-splitting where the structural foundation needs the operator's environment access.
Added
- `website/supabase/migrations/027_drop_contact_submissions_anon_insert.sql` — drops the
allow_anon_insertRLS policy oncontact_submissionsthat previously let the public anon key bypass/api/contact's rate-limit + Zod sanitization. The API route uses the service-role client (RLS-exempt), so the anon policy was unnecessary AND a security footgun. P-H021. - `website/src/lib/ip/get-client-ip.ts` — canonical client-IP extractor preferring
x-real-ip(trusted on Vercel) over RIGHTMOSTx-forwarded-forhop. P-H018 — replaces 19 callsites that trusted the LEFTMOST attacker-controlled XFF hop. - `website/src/__tests__/get-client-ip-precedence.test.ts` — 7-case drift-guard asserting precedence + AST-scan ban on any direct
headers.get('x-forwarded-for')outside the helper. - `website/src/__tests__/webhook-url-allowlist-completeness.test.ts` — 11-case drift-guard for the new validateWebhookUrl gaps (P-H024).
- `packages/core/src/__tests__/memory-db-cascade-delete-session.test.ts` — drift-guard for P-H011 cascade behavior (source scan + live cascade verification on a fresh DB).
Fixed
- `website/src/lib/supabase/types.ts` — added
trial_email_logtype definition (Row/Insert/Update + Relationships) per migration 024. Removed thesupabase as unknown as ...cast hack atapp/api/license/activate/route.ts:421-425. Comment about "migration 023" stale-referenced removed. P-H010. - `packages/core/src/memory-db.ts` — 10 FOREIGN KEY references to
sessions(session_id)now declareON DELETE CASCADE(was: implicit RESTRICT). Closes the "DELETE FROM sessions with surviving children throws" class underPRAGMA foreign_keys = ON. Note: existing customer DBs from prior versions retain non-cascade tables (CREATE TABLE IF NOT EXISTS no-ops); fix takes effect for fresh installs from 1.10.2 onward. P-H011. - `packages/core/src/memory-db.ts:630-657` —
dequeuePendingSyncno longer silently discards queue items atretry_count >= 10. Now emits a stderr warning with recent error messages AND inserts acloud_sync_giveuprow intoanalytics_eventsso the customer can detect silent cloud-sync failure (e.g., invalid API key for >10 cycles losing all queued observations). P-H012. - `packages/core/src/knowledge-db.ts:107-119` —
knowledge_schema_mismatches.sourcecolumn no longer has a SQL DEFAULT that was a JS template-literal interpolation baked into the customer's SQLite at schema creation time (so later config changes were ignored). Default is now applied at INSERT time viagetConfig().conventions.knowledgeSourceFiles[0]inknowledge-indexer.ts:443-447. P-H013. - 19 route handlers (
api/settings/route.ts,api/contact/route.ts,api/evidence/route.ts,api/license/activate/route.ts,api/github-stars/route.ts,api/sso/route.ts,api/sso/callback/route.ts,api/keys/route.ts,api/keys/[id]/route.ts,api/export/route.ts,api/lemon-squeezy/webhook/route.ts,api/stripe/checkout/route.ts,api/badge/[orgSlug]/[type]/route.ts,api/invitations/accept/route.ts) — replacedrequest.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'withgetClientIp(request). Closes the attacker-controllable IP bypass class. P-H018. - `packages/core/src/cloud-sync.ts:98-128` — payload filter now consumes
classifyVisibility()fromobservation-extractor.tsto drop observations whose title/detail/file_path matchPRIVATE_PATTERNS(Stripe keys, env var names, file paths, Bearer tokens, etc.). Pre-fix: cloud-sync transmitted every observation to Massu's Supabase, leaking customer secrets. Now drops privately-classified observations with stderr count for transparency. P-H020. - `website/src/app/api/license/activate/route.ts:17-39` —
hashIp()no longer falls back toLEMON_SQUEEZY_WEBHOOK_SECRETand no longer silently uses empty pepper.IP_HASH_PEPPERis now REQUIRED; absence throws with actionable error. Closes the cross-purpose-key-reuse vulnerability where a leakedlicense_activation_attemptstable would let attackers recover the webhook-signing secret via rainbow-table inversion. P-H023. Operator action required: setIP_HASH_PEPPERenv var to a distinct random value (openssl rand -hex 32) — NOT reused from any other secret. - `website/src/lib/validations.ts:199-260` —
validateWebhookUrlSSRF allowlist extended: rejects0.0.0.0/8(was: only exact 0.0.0.0), CGNAT100.64.0.0/10, IPv6 ULAfc00::/7, IPv6 link-localfe80::/10, IPv4-mapped IPv6 loopback::ffff:127.x.x.x, and IPv6::1(canonical form). P-H024. DNS-rebinding pin documented as follow-up (requires fetch-dispatcher rework). - `website/src/__tests__/integration/license-activate.test.ts:99-110` — added
vi.stubEnv('IP_HASH_PEPPER', ...)to beforeEach since P-H023 made the env var REQUIRED. Test deterministically uses a fixed pepper string.
Deferred to Follow-up Sub-Plans
- P-H019 Ed25519 license signing →
plan-license-response-signing-server-side(TBD). Requires: (a) AWS Secrets Manager key creation by operator, (b) server-side signing route inwebsite/src/app/api/license/validate/route.ts, (c) client-side verifier inpackages/core/src/security/, (d) 24h grace period for existing unsigned-cache acceptance, (e) cutover smoke test against production. Current vulnerable behavior preserved until follow-up ships. - P-H022 nonce-based CSP migration →
plan-csp-nonce-migration(TBD). Requires: (a) per-page audit of every inline<script>and<style>inwebsite/src/app/, (b) middleware nonce generation + injection into request headers, (c) Next.js consumption pattern (read x-nonce from headers() in layout), (d) tightening CSP one-source-at-a-time with smoke testing each page, (e) drift-guard test. Current'unsafe-inline'CSP preserved until follow-up ships.
v1.10.1
2026-05-18Hotfix release for Stage C plan-stage-c-high-batch Release 1 — closes the wider P-H008 marketing-count drift class caught by post-deploy smoke testing of 1.10.0. The 1.10.0 P-H008 fix scope was narrow (just installation.mdx + stats.ts); post-deploy curl of https://massu.ai/docs/getting-started/installation revealed 36 OTHER marketing surfaces still hardcoded "11 lifecycle hooks" / "11 hooks" / "43 workflow commands" literals across website/src/** (TSX pages, layouts, components, data files, lib/email.ts) AND website/content/** (MDX docs + articles). Same structural bug class as P-019 (MCP_TOOL_COUNT drift); fix follows the same structural pattern.
Fixed
- `website/src/app/layout.tsx` + `features/page.tsx` + `docs/layout.tsx` + `about/page.tsx` + `overview/page.tsx` + `how-it-works/page.tsx` + `checkout/cancel/page.tsx` — replaced hardcoded literals
"11 lifecycle hooks"/"11 hooks"/"59 workflow commands"/"11 AI agents"with named-export consumption (LIFECYCLE_HOOK_COUNT,WORKFLOW_COMMAND_COUNT,AI_AGENT_COUNT) from@/data/stats. Mirrors the P-019 pattern. P-H008-extended. - `website/src/components/sections/Hero.tsx` + `OpenSourceSection.tsx` + `HowItWorksPreview.tsx` + `pricing/PricingFAQ.tsx` — same named-export migration. P-H008-extended.
- `website/src/data/articles.ts` + `pricing.ts` + `features.ts` — converted hardcoded counts to template-literal references to the stats SoT named exports. P-H008-extended.
- `website/src/lib/email.ts` — pre-existing "43 workflow commands" drift (also pre-1.5.x) fixed to
${WORKFLOW_COMMAND_COUNT}reference. CR-9 bonus surface caught while fixing P-H008-extended. - `website/content/docs/getting-started/index.mdx` + `guides/troubleshooting.mdx` + `reference/cli-reference.mdx` + `articles/automated-enforcement.mdx` — MDX literal updates 11→16 hooks. MDX files don't consume TS imports; drift-guard ban catches future regressions.
Added
- `website/src/__tests__/marketing-tool-count-against-source-truth.test.ts` — extended drift-guard
BANNEDpatterns to cover the new bug classes. Hook-count"11 lifecycle hooks"/"11 hooks", command-count"43 workflow commands"/"47 commands"/"49 commands", AI-agent-count"7 AI agents"/"9 AI agents"are now scanned acrosswebsite/src/**+website/content/**. Closes the same bug class P-019 closed forMCP_TOOL_COUNT— but for the FULL stats.ts named-export family.
v1.10.0
2026-05-18Stage C Release 1 — pre-launch audit HIGH-severity sub-stages C.1 (hooks + doctor parity, 8 items) and C.2 (MCP tools, 2 items) per docs/plans/2026-05-18-stage-c-high-batch.md (plan token plan-stage-c-high-batch). 10 P-H items shipped; 28 remain across 1.10.1 / 1.10.2 / 1.10.3 per the operator-revised release plan. 4 of 7 planned drift-guards land here (DG-1..DG-4); DG-5..DG-7 ship with their respective P-items in subsequent releases. Every fix is structural — paired with a vitest drift-guard that makes the bug class impossible to reintroduce (CR-46).
Added
- `packages/core/src/lib/hook-registry.ts` — single source of truth for the canonical Massu hook set (
REGISTERED_HOOKSconstant +getExpectedHookFiles()). Doctor, installer, andbuild:hooksall consume from this module. Closes the 11-vs-16 hook-count drift class structurally — adding a new hook now requires touchingsrc/hooks/X.ts+REGISTERED_HOOKS+buildHooksConfig, and the parity drift-guard fails if any one is missed. P-H001. - `packages/core/src/__tests__/hook-registry-parity.test.ts` — 4-case drift-guard DG-1 asserting 3-way parity (src/hooks filenames vs REGISTERED_HOOKS vs buildHooksConfig refs vs dist/hooks/*.js after build).
- `packages/core/src/__tests__/auto-learning-bounded-diff.test.ts` — drift-guard DG-2: fabricates ~5MB working tree and asserts auto-learning Stop hook completes <5s.
- `packages/core/src/__tests__/cloud-sync-abort-controller.test.ts` — 3-case drift-guard DG-3: aborts hanging fetch <1s, AbortSignal passed to every fetch, custom timeout honored.
- `packages/core/src/__tests__/init-app-router-fallback.test.ts` — 4-case
massu initpaths.source fallback test (app/ → pages/ → . fallback; src+app preserves src). - `packages/core/src/__tests__/template-engine-reserved-and-jsx.test.ts` — drift-guard for P-H006 (
{{ARGUMENTS}}reserved literal) + P-H007 (multi-line JSX pass-through). - `packages/core/src/__tests__/trpc-map-empty-codegraph-hint.test.ts` — drift-guard DG-4 asserting TOOL_DB_NEEDS includes codegraph + tools.ts source contains actionable remedy hint.
- `packages/core/src/hooks/post-tool-use.ts` —
recordTestResultnow wired on Bash test-runner commands (npm test,npx vitest,pnpm test,pytest,go test,cargo test) with vitest/jest/pytest output parsing. P-H029.
Fixed
- `packages/core/src/commands/doctor.ts:45` —
EXPECTED_HOOKSwas a hand-maintained 11-entry list whileinstallHooks()registered 16. Doctor reported11/11 PASSeven when 5 hooks were missing fromdist/hooks/. Now sourced fromgetExpectedHookFiles()in the new SoT. P-H001. - `packages/core/src/hooks/auto-learning-pipeline.ts:73,75` — replaced bare
execSync('git diff')(unbounded read of entire working tree, 10s timeout) with two-stage probe:git diff --shortstatfirst to estimate byte count, thengit diffONLY if estimated ≤ 2MB. Also switched toexecFileSyncargv form (defense-in-depth). P-H002. - `packages/core/src/cloud-sync.ts:108` — bare
fetch()had no AbortSignal; offline customers burned the entire Stop-hook 15s budget on unreachable endpoint. Now usesAbortSignal.timeout(requestTimeoutMs)(default 2000ms, configurable) and short-circuits retry on AbortError/TimeoutError. P-H003. - `packages/core/src/commands/init.ts:428` —
paths.sourcedefaulted to'src'even when onlyapp/(Next.js App Router) orpages/existed. Validator rejected the config and rolled init back. Now falls back throughsrc/→app/→pages/→.(root). P-H004. - `packages/core/src/commands/install-commands.ts:490` —
buildTemplateVars()now exposesARGUMENTS: '{{ARGUMENTS}}'reserved literal. P-H006 —/massu-article-review,/massu-autoresearch,/massu-command-improve,/massu-squirrelssilently failed to install because the template engine threwMissingVariableErroron their{{ARGUMENTS}}usage. - `packages/core/src/commands/template-engine.ts:81` — multi-line content inside
{{...}}now passes through verbatim (clearly JSX). P-H007 —patterns/component-patterns.mdsilently failed to install because the engine misparsed JSXaction={{...}}formatted across lines. Single-line content of every shape still goes through strict renderToken — all pre-existing security tests preserved. - `packages/core/src/tool-db-needs.ts:73` —
trpc_mapnow declares['codegraph', 'data']. P-H009 — on fresh installs without a built codegraph the JS-side tRPC index never built and the flagship code-intel tool silently returned "0 procedures". Handler also emits actionable remedy hint (npx massu sync) when the index is empty. - `packages/core/src/commands/install-hooks.ts:7` — stale "all 11 Claude Code hooks" docstring updated to reference the canonical hook-registry SoT (count sourced from
REGISTERED_HOOKS).
Changed
- `website/content/docs/getting-started/installation.mdx` — hook count synced from drifted 11 to actual 16. Hook table appended with the 5 missing rows:
fix-detector,classify-failure,incident-pipeline,rule-enforcement-pipeline,auto-learning-pipeline. P-H008. - `website/src/data/stats.ts` —
Lifecycle Hookscount updated 11 → 16 to matchlib/hook-registry.tsSoT. P-H008. - `packages/core/src/__tests__/server-lazy-db-deps.test.ts:99-122` and `packages/core/src/__tests__/server.test.ts:243` — updated to assert the new P-H009 behavior (
trpc_mapopens bothcodegraphanddataDBs). Previously these tests asserted the bug. - `docs/plans/2026-05-16-prelaunch-audit-remediation.md` (plan-token
plan-2026-05-16-prelaunch-audit) — added## Changelog Summarysection retrospectively to comply with plan-1.9.0 P-A-003 SHIPPED-status requirement (CR-9 cleanup of pre-existing parent-plan validator FAIL).
v1.9.5
2026-05-16Pre-launch comprehensive audit remediation — combined Stage A + Stage B release covering all 22 CRITICAL findings from the 14-agent adversarial audit fleet (docs/plans/2026-05-16-prelaunch-audit-remediation.md, plan token plan-2026-05-16-prelaunch-audit). Every fix is structural — paired with a vitest drift-guard that makes the bug class impossible to reintroduce (CR-46 compliant). 12 of 22 CRITICALs are security-critical (RCE, SSO bypass, license-takeover, schema drift causing 500s in production), addressed before book launch. The 1.9.4 hotfix slot was rolled into this combined release to ship Stage A + Stage B atomically. Also bundles the previously-shipped plan-rulesets-as-code Branch Protection rulesets-as-code migration (SHA b184d3f, drift-guarded by branch-protection-audit.yml).
Fixed
- `packages/core/src/hooks/fix-detector.ts:17,122,124` — RCE via
execSynctemplate-literal interpolation. Replaced withexecFileSyncargv form. Closes P-001 (Stage A). Drift-guard:packages/core/src/__tests__/hooks-no-shell-execsync.test.tsAST-scans every hook file and asserts zeroexecSynctemplate-literal calls. - `packages/core/src/commands/init.ts` —
.mcp.jsonwritten unpinned (npx -y @massu/core) resolved to stale global install. Now pinned to current installer version (getInstallerVersion()). Closes P-002 (Stage A). Drift-guard:__tests__/init-mcp-json-pin.test.ts. - `packages/core/src/commands/init.ts` + new `commands/hook-runner.ts` — hook paths baked customer's npx-cache absolute paths into
.claude/settings.json. Now emitsnpx -y @massu/core@<version> hook-runner <name>dispatch — version-pinned and machine-portable. Closes P-003 (Stage A). Drift-guard:__tests__/init-hook-paths-no-absolute.test.ts. - New `packages/core/src/lib/memory-path.ts` — memory directory encoding inconsistency (
--<root>vs-<root>) orphanedMEMORY.mdon 100% of fresh installs. Shared encoder/decoder with legacy-double-dash auto-migration. Closes P-004 (Stage A). Drift-guard:__tests__/memory-path-roundtrip.test.ts. - `website/src/app/api/v1/quality/route.ts` — selected non-existent
quality_metadatacolumn. Now selectssecurity_metadata. Closes P-005 (Stage A). - `website/src/app/api/v1/audit/route.ts` — filtered non-existent
actor_idcolumn. Now usesuser_id. Closes P-006 (Stage A). - `website/src/app/api/v1/team/route.ts` — selected non-existent
full_name. Now usesdisplay_name. Closes P-007 (Stage A). - `website/src/app/api/v1/cost/trend/route.ts` — selected 4 non-existent columns. Now reads
snapshot_date,total_cost_usd,total_tokens,cost_by_model,cost_by_featurewith a post-query map preserving the external API response shape. Closes P-008 (Stage A). - `website/src/app/api/v1/webhooks/[id]/route.ts:34` — surfaced bonus column-drift fix (
response_status→status_code, cite: migration 014:31-37) caught on first run of the new drift-guard. Closes the CR-9 corollary to P-009 (Stage A). - NEW drift-guard `website/src/__tests__/api-v1-schema-parity.test.ts` — depth-aware Tables parser + line-scanner pairs every
.from().select()and.eq()/gte/lte/etc.filter call per route. Asserts every referenced column exists in the generatedDatabasetype. Closes the entire API-vs-schema bug class structurally (P-009 Stage A). - NEW migration `website/supabase/migrations/025_cleanup_org_records_column_fix.sql` — migration 017 hardcoded
created_atfor ALL retention-managed tables, butsession_transcriptsuses columntimestamp(010:38), aborting cleanup withcolumn created_at does not exist. Restructured the resource map to encode{table, cutoff_col}per resource_type — adding a future retention-managed table whose cutoff column is notcreated_atno longer requires CASE-branch sprawl. Migration 017's audit_log advisory-lock + trigger-disable pattern preserved. Closes P-010 (Stage B). Drift-guard:website/src/__tests__/cleanup-org-records-column-existence.test.ts(4 cases parsing the latest JSONB map and asserting everycutoff_colexists on itstableintypes.ts). - NEW migration `website/supabase/migrations/026_service_role_detection_hardening.sql` — migration 020 used a single
current_setting('role', true) = 'service_role'check whose behaviour against the production Supabase admin client was UNVERIFIED. Replaced with 3-signal defense-in-depth: (1) the originalroleGUC, (2)current_user = 'service_role'(coversSET ROLE service_roleclients), (3) explicitapp.is_service_role = 'true'GUC (operator-controlledSET LOCALfallback). Whichever pattern the admin client uses now or after a future client revision, detection succeeds — eliminates the "block-ALL-webhook-plan-upgrades" failure mode. Closes P-011 (Stage B). Drift-guard:website/src/__tests__/service-role-detection-defense-in-depth.test.tsasserts all three signals are present in the latest trigger definition. - `packages/core/src/commands/init.ts` `mergeHooksConfig()` —
installHooks()wholesale-replaced customer'shookssettings block (recurrence of 1.8.0 permissions-merge trap). Now deep-merges by event+matcher. Closes P-012 (Stage A). Drift-guard:__tests__/init-hooks-merge-preserves-customer.test.ts. - `website/src/components/redeem/RedeemForm.tsx` + new `website/src/components/redeem/fallback-logic.ts` — direct
/redeemtraffic with nobook_purchasesrow spun forever on 202 PURCHASE_PROCESSING. AddedBONUS_FALLBACK_MS = 30_000rescue: after 30s of polling, surfaces a/bonusrescue link. Closes P-013 UI defense-in-depth half (Stage A). Drift-guard:__tests__/redeem-fallback-logic.test.ts(9 cases). - `website/src/app/api/license/activate/route.ts` — TWO CRITICAL hardenings in one route refactor: (a) P-014 atomic UPDATE — SELECT-check-UPDATE pattern allowed two concurrent requests with the same key to both pass the JS-side status check and double-activate. Replaced with atomic
UPDATE book_purchases SET license_key_status='active', activated_at=NOW() WHERE id=$1 AND license_key_status='inactive' .select('id, activated_at'). Race losers see[]from the post-update select and surface 409already_activated. (b) P-015 auth + email match — route was anonymous and resolved identity viauser_profiles.email = purchase.email, allowing an attacker to sign up asvictim@victim-corp.comand bind a leaked license key to the victim's org. Now requirescreateServerSupabaseClient().auth.getUser(); returns 401 withneeds_signuphint (no email leak — anonymous enumeration vector closed) when unauthenticated; returns 403 whenauthedUser.email.toLowerCase() !== purchase.email.toLowerCase(). Both closed in Stage B. Drift-guard:integration/license-activate.test.tscovers race-loser, 401 unauth, 403 mismatch, AND case-insensitive matching. - NEW `website/src/lib/sso-flag.ts` + GATED
website/src/app/api/sso/callback/route.ts,website/src/app/api/sso/route.ts,website/src/app/dashboard/settings/sso/page.tsx— the custom OIDC verifier never cryptographically verified the JWT signature against the IdP's JWKS (P-016 — full SSO bypass for any attacker who registers their own OIDC config), and the custom SAML verifier was vulnerable to XML Signature Wrapping (P-017 — the first-element-wins regex parser allowed outer-wrapper substitution while inner signature still validated). Per operator decision (2026-05-16), SSO is DISABLED viaMASSU_SSO_ENABLED=false(default) until the library replacement (openid-client+@node-saml/node-saml) ships inplan-B.3-followuppost-launch + third-party pen-test. Every SSO entry point returns canonical 503SSO_DISABLED; dashboard SSO settings page shows maintenance UI withsupport@massu.aicontact path. Legacy verifier code preserved below the gate so the followup PR can wire libraries without re-deriving the Supabase user-creation flow. Both closed at the entry point in Stage B. Drift-guard:website/src/__tests__/sso-disabled-by-default.test.ts(10 cases) asserts the flag defaults false, that all three entry-point files import + callisSsoEnabled(), and that the callback route returnsssoDisabledResponse()from BOTH POST and GET handlers. - `website/src/data/stats.ts` — added
MCP_TOOL_COUNT(= 73) named export alongside the existingstats: Stat[]SoT, plusWORKFLOW_COMMAND_COUNT,AI_AGENT_COUNT,LIFECYCLE_HOOK_COUNT. Replaced literal84 MCP toolsclaims across 14 TSX/TS surfaces (app/layout.tsx3x,app/docs/layout.tsx,app/features/page.tsx4x,app/about/page.tsx,app/overview/page.tsx5x,app/pricing/page.tsx,app/checkout/cancel/page.tsx,components/sections/Hero.tsx2x,components/sections/OpenSourceSection.tsx,components/sections/CloudPreview.tsx,components/pricing/PricingFAQ.tsx,components/pricing/FeatureComparison.tsx,data/articles.ts3x,data/features.ts,data/pricing.ts) with${MCP_TOOL_COUNT}interpolation; replaced84with literal73in 2 MDX content files. Closes P-019 (Stage B). Drift-guard: NEWwebsite/src/__tests__/marketing-tool-count-against-source-truth.test.tswalkswebsite/src+website/contentfor forbidden literals (84 MCP,84 tools,all 84, plus historical drift literals47/56/62 MCP/tools) and asserts zero matches. - `.github/workflows/ci.public.yml` — both
ci.ymlandci.public.ymlhad workflowname: CIand shared${{ github.workflow }}-${{ github.ref }}concurrency group; on every main push the strongerci.ymlwas being CANCELLED while the weakerci.public.yml(4 of 6 internal jobs only — missing Pattern Scanner, Security Scanner, Generalization Scanner, Plan Status Validator, Plan Commit Drift, Plan-Token Changelog Coverage, Website Audit, Hook Build) satisfied branch protection. This is how the 4 API column-drift CRITICALs (P-005..P-008) reached production. Renamedci.public.ymlworkflow + concurrency group + job-display names toCI (public-mirror); 4 jobs gatedif: github.repository != 'ethankowen-73/massu-internal'so the internal repo runs the FULLci.ymland the public mirror keeps the public-facing subset. Job-levelname:values PRESERVED (both rulesets reference them as required status checks). Closes P-020 (Stage A). Drift-guard: NEWwebsite/src/__tests__/workflow-uniqueness.test.ts(3 assertions). - NEW `website/src/app/book/opengraph-image.tsx` —
book/layout.tsxreferenced/og-book.pngbut the file didn't exist inwebsite/public/, producing a 404 image preview on every Twitter / LinkedIn / Slack share of/book. Replaced with the Next.jsopengraph-image.tsxfile convention —next/ogImageResponsegenerates a brand-consistent 1200x630 PNG at build time, Next.js auto-wires<meta property="og:image">and<meta name="twitter:image">. No static asset to maintain. Removed the broken/og-book.pngreferences frombook/layout.tsx. Closes P-021 (Stage B). Drift-guard:website/src/__tests__/og-book-image-resolves.test.ts(4 cases) — asserts the file exists, exportsImageResponsewith 1200x630 dimensions, AND that any future reintroduction of/og-book.pnginbook/layout.tsxrequires the static file to also exist. - `website/src/components/sections/Hero.tsx` — homepage had ZERO
/bookpromotion above-the-fold;/bookwas only reachable via 7th nav tab. Added a pill-style<Link href="/book">book-announcement BEFORE the existing "Free & Source Available" badge AND before the Install Free button.trackEvent('cta_hero_book_announcement')for funnel measurement. Closes P-022 (Stage B). Drift-guard:website/src/__tests__/homepage-book-cta-presence.test.tsenforces the ordering invariant (book CTA must precede Install Free). - `website/src/components/redeem/RedeemForm.tsx` —
needs_signupbranch lost the license key, forcing customers to dig through email to re-paste the 28-char code after signup. Implemented 2-layer persistence: localStoragemassu_pending_license_key(primary) +?license_key=URL fallback. Auto-fills on mount; strips URL param after hydration (no Referer/history leak); persists onneeds_signup; clears on success; signup CTA carriesredirect_to=/redeem?license_key=…. Closes P-023 (Stage B). Drift-guard:__tests__/redeem-license-key-persistence.test.ts(7 cases).
Removed
- DELETED `website/content/docs/features/mcp-bridge.mdx` + purged MCP Bridge references from
website/src/data/docs-nav.ts,website/content/docs/reference/tool-reference.mdx(the "MCP Bridge Tools (4)" section),website/content/docs/reference/license-tiers.mdx(Pro tier 47 → 43 tools),website/src/components/pricing/PricingFAQ.tsx(2 FAQ entries removed),website/src/data/features.ts(the entire 4-toolmcp-bridgefeature block — drops Feature Entries 167 → 163),website/src/app/overview/page.tsxchangelog. Operator decision: REMOVE not implement. Vapor MCP Bridge tools (mcp_servers,mcp_tools,mcp_call,mcp_status) were documented at full feature page level but did NOT exist inpackages/core/src/tools.ts— paying Pro customers would have received "Unknown tool" errors. Closes P-018 (Stage B).
Changed
- `website/src/data/stats.ts` `Feature Entries`: 167 → 163 to match the P-018 MCP Bridge feature-block removal (4 tools × 1 entry each).
- `website/content/docs/reference/license-tiers.mdx` Pro Tier header:
47 additional tools→43 additional toolsto match the P-018 MCP Bridge removal. - `packages/core/package.json` description: tier-count claim refreshed
(12 free / 72 total)→(12 free / 73 total), workflow-commands55+→59. - `docs/plans/2026-05-16-prelaunch-audit-remediation.md` Plan Status header: "IN PROGRESS — Stage A + Stage B CODE COMPLETE (awaiting operator release ceremony); Stages C-E DRAFT" → SHIPPED. Plan documents Stages C-E (38 HIGH + 52 MEDIUM + 71 LOW + INFO) as draft work that will ship post-launch in the 1.10.x / 1.11.x / rolling cluster cadence.
Known follow-on
- P-016 / P-017 library replacement is NOT in this release. The security risk is closed at the entry point (every SSO request returns 503; legacy verifier code unreachable). The full library replacement (
openid-client+@node-saml/node-saml) isplan-B.3-followuppost-launch after a third-party pen-test, per the operator decision recorded in the master plan. - Operator manual verification still required (these cannot be automated): (a) printed Amazon book URL points to
/bonusnot/redeem(defense-in-depth ships either way via P-013), (b) smoke-testcurrent_setting('role')against the production admin client to confirm the 3-signal trigger admits the actual webhook caller (P-011), (c) each Lemon Squeezy variant has the ebook file attached (P-H015).
Verification
packages/core/: 156 test files / 2235 tests PASS (12 skipped) on Node 22.website/: 46 test files / 439 tests PASS (includes 46 NEW Stage B drift-guard cases across 8 new/updated test files + 8 Stage A drift-guards already shipped).website/TypeScript:npx tsc --noEmit0 errors.packages/core/TypeScript:npx tsc --noEmit0 errors.- Node 26 local builds blocked by pre-existing better-sqlite3 native-binding issue (memory
feedback_better_sqlite3_native_binding_missing.md); CI runs on Node 22 cleanly.
v1.9.3
2026-05-15Bug-fix release. Closes upstream issue massu-ai/massu#4 — tools/list emitted a non-standard top-level tier field on every Tool object, which Claude Code 2.1.143 (released 2026-05-15) silently rejects, causing all 67 mcp__massu__* tools to disappear from the deferred-tool registry on session start. The canonical MCP Tool schema (`schema/2025-11-25/schema.ts` line 1251) permits only name, title, description, inputSchema, execution, outputSchema, annotations, _meta, icons — tier is not in the schema. Structured tier metadata now lives under annotations.tier (the spec-sanctioned extension point for tool metadata); the visible [PRO] / [TEAM] / [ENTERPRISE] description-prefix labelling is unchanged. Internal license enforcement at tools.ts:323-331 is unaffected because it reads getToolTier(name) from the static server-side TOOL_TIER_MAP, never from the wire-emitted field — no license-bypass risk. Structural drift-prevention: new vitest assertion (license.test.ts "emits only MCP-spec-permitted top-level fields") parses the annotated definitions against the canonical permitted set; the regression class becomes structurally impossible to reintroduce without test failure.
Fixed
- `packages/core/src/license.ts:194-203` `annotateToolDefinitions()` — replaced the top-level
tier,spread withannotations: { ...(def.annotations ?? {}), tier },. Wire-format Tool objects now conform to MCP spec 2025-11-25 §Tool(extendsBaseMetadata, Icons). Caller-supplied annotations (e.g.readOnlyHint,title) are preserved via shallow-merge before tier is added. - `packages/core/src/tools.ts:43-48` `ToolDefinition` interface — dropped top-level
tier?: 'free' | 'pro' | 'team' | 'enterprise'; addedannotations?: Record<string, unknown>to mirror the MCP spec extension point. The local interface remains permissive (Record<string, unknown>vs. the spec's strictly-typedToolAnnotations) so thattiercan co-exist with caller-supplied annotation fields without a circularToolTiertype import.
Added
- `packages/core/src/__tests__/license.test.ts` two new regression tests under the existing P3-029
annotateToolDefinitions()describe block: - Existing 9
annotated[N].tierassertions in the same describe block migrated toannotated[N].annotations?.tierto track the wire-format relocation.
Verification
cd packages/core && npm test— ALL pass (see commit message body for count).cd packages/core && npx tsc --noEmit— 0 errors.bash scripts/massu-pattern-scanner.sh— exit 0 (all 16 checks).- Diagnostic confirmation: stripping
tiervia a stdio wrapper restored all 67mcp__massu__*tools in Claude Code 2.1.143 (proves the fix root-cause-correct). - Spec citation: `schema/2025-11-25/schema.ts` line 1251 —
interface Tool extends BaseMetadata, Iconswith notierfield.
v1.9.2
2026-05-15Plan plan-1.9.2-deploy-smoke-test-production-host — /massu-deploy smoke tests now target the canonical production host (https://massu.ai) instead of the per-deploy Vercel preview URL. Closes the structural bug discovered 2026-05-15 during the 1.9.1 ceremony where every smoke test on /, /docs, /changelog, /overview returned HTTP 401 against the auth-gated preview URL — making the gate enshrined in CR-48 Stage D unable to actually verify production. Adds alias-propagation poll (Vercel CLI vercel ls --prod matches the new deploy's hostname prefix) so smoke tests only run after the production alias has been updated to point to the new deploy; FAIL-with-bypass on timeout (MASSU_SKIP_ALIAS_PROPAGATION_CHECK=1) mirroring CR-48 staleness gate pattern. Structural drift-prevention: vitest massu-deploy-script-shape.test.ts (4 DEPLOY-SHAPE assertions including a slash-command doc drift-guard) parses scripts/massu-deploy.sh and asserts smoke tests target PRODUCTION_HOST, not DEPLOY_URL. The bug class — "deploy script silently 401s its own smoke tests" — becomes structurally impossible to reintroduce without test failure.
Fixed
- `scripts/massu-deploy.sh` Step 5 smoke-test target — was
${DEPLOY_URL}${ROUTE}(auth-gated preview URL, always 401); now${PRODUCTION_HOST}${ROUTE}(canonical production domain, returns real HTTP status). Usescurl -sLto follow redirects so/docs(307 →/docs/getting-started→ 200) passes correctly. - Final-report honesty: was hardcoded
Custom domain: https://massu.ai; nowProduction target: $PRODUCTION_HOST(reflects env-overrides for staging dry-runs).
Added
- `scripts/massu-deploy.sh` Step 4.5 Alias Propagation poll — uses
vercel ls --prod(already-authenticated CLI) to detect when the new deploy is the production-alias target. FAIL-with-bypass on timeout (MASSU_SKIP_ALIAS_PROPAGATION_CHECK=1env-bypass logged to stderr). The header-polling approach (x-vercel-deployment-url) was empirically ruled out: livecurl -sI https://massu.ai/returnsx-vercel-idbut notx-vercel-deployment-url. - `PRODUCTION_HOST` + `ALIAS_PROPAGATION_TIMEOUT_SECS` constants in
scripts/massu-deploy.shwith env-var overrides (MASSU_PRODUCTION_HOST,MASSU_ALIAS_PROPAGATION_TIMEOUT_SECS). Input validation: URL regex + integer ≤600 cap. - `website/src/__tests__/massu-deploy-script-shape.test.ts` — 4 drift-guard assertions (DEPLOY-SHAPE-01..04) parsing the bash script + slash-command doc via
readFileSync. Asserts smoke loop uses${PRODUCTION_HOST}not${DEPLOY_URL}, propagation block precedes smoke block, and doc cites the same default + env-var names. Empty-body and curl-line-count guards prevent silent-pass on delimiter rename. - `.claude/commands/massu-deploy.md` — env-var override table; Step 4.5 added to pre-flight checks list.
Verification
bash -n scripts/massu-deploy.sh— exit 0.cd website && npx vitest run src/__tests__/massu-deploy-script-shape.test.ts— 4/4 pass.cd packages/core && npx tsc --noEmit— 0 errors.cd website && npx tsc --noEmit— 0 errors.bash scripts/massu-pattern-scanner.sh— all 16 checks PASS.bash scripts/massu-plan-status-validator.sh— PASS.bash scripts/massu-plan-commit-drift.sh— PASS.- Live pre-deploy baseline (2026-05-15):
https://massu.ai/→ 200;/docs→ 307 → 200 (handled via-L);/changelog→ 200;/overview→ 200.
v1.9.1
2026-05-15Bug-fix release. Closes the structural rendering bug discovered 2026-05-15 in the auto-derived /releases/<version> path shipped in 1.9.0 (CR-46 consolidation). Each section's bullet items were pushed as separate elements of the parts array and joined with \n\n, so the blank lines between consecutive - foo markdown lines terminated each list — every bullet rendered as its own single-item <ul> instead of one <ul> per section. Affected all 17 auto-derived release pages on production (1.6.x, 1.5.x, 1.4.x, ...); MDX-override pages (1.5-to-1.6, 1.7.0, 1.8.0, 1.9.0) were unaffected. Bug was visually verified live pre-fix: /releases/1.6.3 had 4 headings + 21 single-item <ul> elements; /releases/1.5.7 had 3 headings + 8 single-item <ul> elements. Fix builds each section as one ### heading\n\n- a\n- b\n- c block; new RCC-05 drift-guard asserts bulletBlockCount === sectionCount for every auto-derived release, making the regression class structurally impossible.
Fixed
- `website/src/lib/releases.ts:135-138` `getReleaseContent()` — replaced the per-bullet
parts.push('- ' + item)loop withconst bullets = sec.items.map(i => '- ' + i).join('\n'); parts.push('### ' + sec.heading + '\n\n' + bullets). Finalparts.join('\n\n')now only inserts blank lines between sections, never between bullets within a section. Visual result post-fix: each section renders as one<ul>with N<li>children (correct markdown semantics + correct accessibility tree + correct keyboard navigation).
Added
- `website/src/__tests__/releases-changelog-coverage.test.ts` RCC-05 — drift-guard asserting
content.split(/\n{2,}/).filter(b => b.trim().startsWith('- ')).length === entry.sections.filter(s => s.items.length > 0).lengthfor everysource==='changelog'release. Also asserts every line inside a bullet block starts with-(no contaminating prose). Structural drift-prevention per CR-46 — the regression class cannot reappear without test failure.
Verification
cd website && npx vitest run releases-changelog-coverage.test.ts— 5/5 pass (incl. new RCC-05 against all 17 auto-derived releases).cd website && npx tsc --noEmit— 0 errors.cd website && npm test— 323/323 pass (baseline preserved).- Pre-fix live evidence:
/releases/1.6.3→ 21 single-item<ul>s;/releases/1.5.7→ 8 single-item<ul>s. Post-fix shape verified at the markdown layer via RCC-05 invariant.
v1.9.0
2026-05-14Plan plan-1.9.0-plan-token-aware-changelog-batcher — Release-boundary CHANGELOG generation that gathers commits by (plan-<token>) subject grouping into one structured entry per release at tag time. Closes the "version bumped without CHANGELOG entry" class structurally and replaces per-commit changelog noise with "fewer entries, more meaningful." New npx massu changelog generate|verify CLI cluster reads commit subjects since the last tag, looks up each plan file's new ## Changelog Summary section, and emits a Keep-a-Changelog 1.1.0-compliant entry. New pre-push-light step [11/11] Plan-Token Changelog Currency fires when package.json#version drifts from the latest git tag and BLOCKS the push unless CHANGELOG.md has the matching [X.Y.Z] heading AND references every plan-token in the commit range. CI mirrors the check (3-layer enforcement per CR-49 precedent). The plan-status validator is extended to require ## Changelog Summary on all shipped plans; 19 existing shipped plans were backfilled. Generator + 21 tests (15 CHG-GEN + 6 CHG-CLI) + drift-guard vitest (4 PTCC) + /massu-release skill integration shipped together.
Added
- `packages/core/src/changelog-generator.ts` — SSOT module exporting
parseCommitsForPlanTokens,loadPlanSummaries,generateChangelogEntry,findCoverageGaps, and error classesMissingPlanFileError+MissingChangelogSummaryError. JSDoc + tests atpackages/core/src/__tests__/changelog-generator.test.ts(15 cases CHG-GEN-01..15 incl. self-application byte-equivalence regression). - `massu changelog <sub>` CLI cluster at
packages/core/src/commands/changelog.ts— two subcommands:generate(auto-drafts entry to stdout for operator review) andverify(exit 0 if clean, exit 1 withgap: <token>per missing plan-token). Mirrors thepermissions <sub>precedent shipped in 1.8.0. - `scripts/lib/plan-token-regex.sh` — single source of truth for the
(feat|fix|chore|docs)(plan-<token>)regex. ExportsPLAN_TOKEN_REGEX+extract_plan_tokens_from_range()shell function. Consumed bymassu-plan-commit-drift.sh(refactored to source from lib),massu-changelog-coverage.sh(new), andchangelog-generator.ts(via TS literal). - `scripts/massu-changelog-coverage.sh` — pre-tag gate. Reads
packages/core/package.json#versionandgit describe --tags --abbrev=0; skips silently when versions match (no release in progress); else asserts CHANGELOG.md has## [X.Y.Z]heading at top AND every plan-token in commit range appears in entry body. Exit 0/1 with onegap: <token>per missing. - `scripts/pre-push-light.sh` step `[11/11]` — Plan-Token Changelog Currency. Invokes
massu-changelog-coverage.sh. All existing steps renumbered from[N/10]to[N/11]. - `.github/workflows/ci.yml` — type-check job adds
bash scripts/massu-changelog-coverage.shstep (3-layer CI gate mirroring CR-49 leak-guard precedent). - `website/src/__tests__/plan-token-changelog-coverage.test.ts` — 4-case drift-guard (PTCC-01..04) asserting latest CHANGELOG entry references every plan-token in
git log $(prev-tag)..$(latest-tag)modulo a documented divergence allowlist for cross-release infrastructure tokens. - `packages/core/src/__tests__/changelog-cli.test.ts` — 6 CLI dispatcher tests (CHG-CLI-01..06).
- `### Changelog Generation` section in
packages/core/README.mddocumenting the workflow + plan-file contract. - `## massu changelog` section in
website/content/docs/reference/cli-reference.mdxwith subcommand table + example output. - `## Changelog Summary` section backfilled into 19 existing shipped plan files (auto-extracted from corresponding CHANGELOG.md entries via a one-shot node script using
parseChangelog+readChangelogfromwebsite/src/lib/changelog.ts).
Changed
- `scripts/massu-plan-status-validator.sh` — extended to require
## Changelog Summaryheading for plans whose Status is in the shipped subset (SHIPPED, IMPLEMENTED, COMPLETE, SUPERSEDED, APPROVED). HISTORICAL DRAFT exempt. The validator'shead -n 30window was widened tohead -n 60to accommodate the new section without pushing existing**Status**:headers out of scope. - `scripts/massu-plan-commit-drift.sh` — replaced inline regex with
source scripts/lib/plan-token-regex.sh. Existing exit-0 behavior preserved (verified: 79 plan refs in 163 commits, 0 violations, 28 warnings). - `.claude/commands/massu-release.md` (+
packages/core/commands/massu-release.mdpublic-sync mirror) — Step 3 CHANGELOG GENERATION now leads with auto-draft vianpx massu changelog generate; the legacy conventional-commits parse remains as fallback.
Verification
cd packages/core && npx tsc --noEmit— 0 errors.- In-scope tests pass: 15 CHG-GEN + 6 CHG-CLI + 4 PTCC + 8 plan-status-drift-guard (fixture update) = 33 tests.
cd packages/core && npm run build:cliexits 0;dist/cli.jscontains 4 references tohandleChangelogSubcommand.bash scripts/massu-plan-status-validator.shexits 0 (0 violations, 10 warnings — all pre-existing CR-48 retrospective WARNs).bash scripts/pre-push-light.shon Node 22 — all 11 gates PASS.
v1.8.0
2026-05-14Plan plan-1.8.0-mcp-permission-seeding — MCP permission seeding suite. Closes a structural gap where every fresh npx @massu/core install-commands adopter hit per-tool permission dialogs on each of the 73+ mcp__massu__* MCP tool calls until they hand-curated .claude/settings.local.json. Also closes the empirically-observed merge-replacement trap where a project-local permissions object without defaultMode silently strips the user-global defaultMode during settings merge (undocumented at code.claude.com/docs/en/permissions; reproduced 2026-05-14). The new writer reads ~/.claude/settings.json, computes the full merged permissions block (allow union with canonical entries; defaultMode = local override OR global OR omit; deny/ask preserved), atomic-writes the complete block, and fail-loud-asserts post-write that the merge survived.
Added
- `packages/core/src/permissions.ts` — SSOT for MCP permission seeding/verification/drift detection. Exports
MASSU_PERMISSION_ENTRIES(['mcp__massu__*']),LAUNCH_FLAG_REQUIRED_MODES(['bypassPermissions', 'auto', 'dontAsk']per code.claude.com/docs/en/permission-modes),findMissingEntries,detectInvalidDefaultMode,readGlobalSettings,mergedPermissionState,installPermissions,verifyPermissions,checkPermissionsDrift, and the fail-loudInstallPermissionsAssertionErrorclass. - `massu permissions <sub>` CLI subcommand cluster at
packages/core/src/commands/permissions.ts— three subcommands:install(seeds canonical entries + propagates global defaultMode; idempotent),verify(read-only check, exit 0 if clean else 1),check-drift(extended diagnostic, severity-mapped exit codes 1/2/3/4 formissing-allow/invalid-default-mode/unknown-key/strips-global-defaultmode). - `--skip-permissions` flag on
install-commands— escape hatch for enterprise-policy-managed allowlists. - `packages/core/src/lib/settings-local.ts` — shared atomic IO helper (SSOT for
.claude/settings.local.jsonAND~/.claude/settings.jsonreads). ExportsreadSettingsLocal,writeSettingsLocalAtomic,readSettingsAtPath, and theatomicWriteFileprimitive (moved frominstall-commands.ts). - `packages/core/src/__tests__/permissions.test.ts` — 19 drift-guard test cases (PERM-DRIFT-01..19) including snapshot tests for
global=autoandglobal=bypassPermissionsscenarios; PERM-DRIFT-17 specifically reproduces the merge-replacement trap detection. - `packages/core/src/__tests__/settings-local.test.ts` — 7 IO tests (SLOC-01..06 + defensive shape check).
- `packages/core/src/__tests__/permissions-cli.test.ts` — 10 CLI dispatcher tests (VPC-01..08 + help + unknown subcommand).
- `### Permission Seeding` and `### Permissions trap (settings merge)` sections in
packages/core/README.mddocumenting the writer behavior, thedefaultModevalidity table, and the before/after JSON snippet showing the trap. - `## massu permissions` section in
website/content/docs/reference/cli-reference.mdxwith the exit code matrix and an example check-drift run.
Changed
- `packages/core/src/commands/install-commands.ts` —
installAll(projectRoot, opts?: {skipPermissions})andinstallCommands(projectRoot, opts?: {skipPermissions})now callinstallPermissionsinside the existingrunWithManifestblock (single atomic manifest write covers both file syncs and permission seeding).runInstallCommandsparses--skip-permissionsfrom argv.atomicWriteFilemoved tolib/settings-local.ts. - `packages/core/src/commands/init.ts:1099-1140` `installHooks` — refactored to consume the shared
readSettingsLocal+writeSettingsLocalAtomichelpers. Closes a pre-existing non-atomic-write bug atinit.ts:1137(waswriteFileSync— vulnerable to SIGINT-between-truncate-and-write leaving a corrupt settings.local.json). - `packages/core/src/commands/doctor.ts:106,241` — consolidated through the new
readSettingsAtPathhelper (per CR-9 — same SSOT). - `packages/core/src/cli.ts` — new
case 'permissions':switch +--skip-permissionsdocumentation inprintHelp.
Verification
cd packages/core && npx tsc --noEmit— 0 errors.- In-scope tests pass: 19 PERM-DRIFT + 7 SLOC + 10 VPC + 38 install-commands (3 new ICP-01..03) + 23 init (refactor verified clean) = 114 tests.
cd packages/core && npm run build:cliexits 0;dist/cli.jsbundle contains 4 references tohandlePermissionsSubcommand.
v1.7.0
2026-05-11Plan plan-1.7.0-cohesive-cleanup — Cohesive minor release that simultaneously closes three structural-quality gaps across the codebase: (1) the Feb-2026 plan-website-audit (never shipped) is revalidated against current sources and SUPERSEDED — stats values on massu.ai/ are now drift-guarded by a vitest test that asserts each stat equals a static derivation from its source-of-truth (packages/core/src/license.ts TOOL_TIER_MAP for MCP Tools, .claude/commands/massu-*.md files for Workflow Commands, etc.); the CLI Reference doc is expanded from 5 CLI commands to 5 CLI + 59 slash commands with a completeness drift-guard test; the team-tool surface gains a runtime cloud-gate via new isCloudFeatureAvailable() helper wired at tools.ts:175 (registration) and tools.ts:413 (dispatch); (2) P6-004 from plan-fresh-install-monorepo-paths — topLevelSrcSubdirs in packages/core/src/detect/domain-inferrer.ts:71 no longer hardcodes join(root, 'src') and now consumes the detected source-dir pipeline; new monorepo-apps-no-root-src fixture + 4 new domain-inferrer test cases exercise both single-repo non-src/ layouts and workspaces-driven monorepos; generalization-scanner Check 5 prevents the bug class from recurring; (3) the stale next: 1.2.1 npm dist-tag (4 minors behind, zero documented consumers) is removed; CLAUDE.md ### npm dist-tags policy section codifies that only latest is maintained going forward; pre-push step 9 enforces the policy with npm view @massu/core dist-tags parse.
Added
- `website/src/__tests__/stats-numbers-against-source-truth.test.ts` — 5-assertion vitest drift-guard. Each stat in
website/src/data/stats.tsis asserted to equal a static derivation from its canonical source (MCP Tools ←name: p('xxx')+\${pfx}_xxx\patterns acrosspackages/core/src/**/*.ts; Workflow Commands ←.claude/commands/massu-*.mdminusmassu-internal-*; Feature Entries ←tier:lines infeatures.ts; Database Tables ←CREATE TABLEinwebsite/supabase/migrations/; Lines of Code (K+) ←packages/core/src/**/*.tstotal / 1000 within ±2K tolerance). Adding a new tool/command/migration without updatingstats.tsFAILs the test. - `website/scripts/regen-stats.mjs` — companion regeneration script that emits
website/src/__tests__/fixtures/stats-expected-values.jsonfrom the same derivation logic. Manual rerun documents intended values for ops + future audit. - `website/src/__tests__/cli-reference-doc-completeness.test.ts` — 3-assertion drift-guard: file exists; every external
.claude/commands/massu-*.md(excludingmassu-internal-*) has a matching## /<command-name>H2 in the doc; doc is registered indocs-nav.ts. Adding a new slash command without updatingcli-reference.mdxFAILs CI. - `isCloudFeatureAvailable()` in
packages/core/src/license.ts— returnsgetConfig().cloud?.enabled === true. Wired attools.ts:175(...(isCloudFeatureAvailable() ? getTeamToolDefinitions() : [])) andtools.ts:413(if (isTeamTool(name) && isCloudFeatureAvailable())). Team-tool surface (team_search/team_expertise/team_conflicts) is now correctly hidden + non-routable for workspaces without explicitcloud.enabled: trueopt-in. Distinct mechanism from name-matcherisLicenseTool(which gates by tool name pattern, not feature availability). - `packages/core/src/detect/__tests__/fixtures/monorepo-apps-no-root-src/` — new fixture:
package.jsonwithworkspaces: ["apps/*"],apps/web/{src/index.ts,package.json},apps/api/{src/index.ts,package.json}, NO rootsrc/directory. Drives the new monorepo-apps test case indetect.domain-inferrer.test.ts. - 4 new test cases in
packages/core/src/__tests__/detect.domain-inferrer.test.ts— (a)topLevelSrcSubdirsconsumes detected source dirs (no hardcodedsrc); (b) unions subdirs across multiple detected source dirs; (c) fixture has expected layout; (d)inferDomainsreturns BOTHwebandapias domains for monorepo-apps-no-root-src fixture. - `scripts/massu-generalization-scanner.sh` Check 5 — flags
join(<ident>, '<bare-dir-literal>')patterns inpackages/core/src/detect/. Manifest allowlist exempts dotless filenames (WORKSPACE, Gemfile, Dockerfile, Makefile, Rakefile, Procfile, MODULE, BUILD). Synthetic regression VERIFIED: re-introducingjoin(root, 'src')in a fixture file triggersFAIL. - `scripts/pre-push-light.sh` step 9 —
Dist-Tag Pre-Releasegate. FAILs the push whennpm view @massu/core dist-tagsreturns anynext:|beta:|alpha:|rc:channel without an ADR + CLAUDE.md## Deploymentpolicy section opt-in. SKIPs silently when npm registry is unreachable. Renumbered existing[N/8]labels to[N/9]. - `### npm dist-tags policy` section in
.claude/CLAUDE.md## Deployment— codifies that onlylatestis maintained on@massu/core. Pre-release channels (next/beta/alpha/rc) require both an ADR and explicit operator approval before tag creation. Stalenext: 1.2.1removal recorded with rationale (4 minors behind, zero documented consumers). - 59 slash command H2 entries appended to
website/content/docs/reference/cli-reference.mdxunder a new# Slash Commandssection. Existing## massu init / doctor / install-hooks / install-commands / validate-configCLI command sections preserved as-is. Doc description updated to reflect dual coverage (CLI + slash commands). - `flattenSourceDirs()` helper in
packages/core/src/detect/domain-inferrer.ts— flattens aSourceDirMapinto a unique list of relative source paths across all detected languages. Drops.and''root sentinels so root-source repos (Django'smanage.py, Swift'sPackage.swift) continue to use the language-fallback domain (Python,Swift) rather than spuriously enumerating root subdirectories.
Changed
- `packages/core/src/detect/domain-inferrer.ts:71` —
topLevelSrcSubdirs(root: string)→topLevelSrcSubdirs(root: string, sourceDirs: readonly string[]). Loops over each source dir (e.g.lib,apps/web/src), enumerates subdirectories, unions deduplicated results sorted alphabetically. EmptysourceDirsfalls back to legacy['src']lookup for backward compatibility with hand-wired callers. Hardcodedjoin(root, 'src')literal removed. - `website/src/data/stats.ts` — values reconciled with source-of-truth derivation: MCP Tools 84 → 73, Lines of Code 21 → 40 (K+), Database Tables 51 → 41, Feature Entries 140 → 167. Workflow Commands 59 preserved (matches derivation).
- `packages/core/src/detect/__tests__/fixtures/swift-ios/expected.massu.config.yaml` —
domains: [{name: Swift}]→domains: [{name: App}]. Reflects the more accurate inference under the refactoredtopLevelSrcSubdirs(Sources/App → App domain) for Swift package layouts. Backward-compatible:npx massu initcontinues to produce a parseable config; domain names are user-editable suggestions. - `website/content/docs/reference/cli-reference.mdx` description frontmatter — broadened to "Complete reference for all Massu CLI commands … and 59 workflow slash commands (/massu-*)".
Removed
- Stale `next: 1.2.1` npm dist-tag on
@massu/core— removed in P-C-001 of Stage D ceremony. Was 4 minors behindlatest, with zero documented consumers in code, README, install instructions, or.shinvocations. Future pre-release channels gated by CR-48-style ADR + CLAUDE.md policy section. - Hardcoded `'src'` literal at
domain-inferrer.ts:71— replaced with detected-source-dir consumption per P-B-002.
Verification
cd packages/core && npx tsc --noEmit: 0 errorscd packages/core && npm test: 2167 passed / 12 skipped (baseline 2153 + 14 new from Stages A/B = +6 new domain-inferrer + flattenSourceDirs adjustments to keep python-django/swift-ios passing)cd website && npm test: 125 passed (baseline 114 + 8 new from P-A-002 5-assertion stats test + P-A-005 3-assertion cli-reference test)bash scripts/massu-pattern-scanner.sh: PASS (15 checks, including Check 14 TOOL_DB_NEEDS + Check 15 public-page nav-link coverage)bash scripts/massu-generalization-scanner.sh: PASS (5 checks; new Check 5 verified with synthetic regression)bash scripts/pre-push-light.sh(Node 22): 8/9 PASS pre-Stage-D (step 9Dist-Tag Pre-ReleaseFAILs against stalenext: 1.2.1— gate-fires-once that closes when P-C-001 runsnpm dist-tag rm @massu/core next); ALL 9 PASS post-cleanup.npm view @massu/core dist-tagspost-rm: returns{ latest: '1.7.0' }(nonext).grep -c "join(root, 'src')" packages/core/src/detect/domain-inferrer.ts: 0 (P-B-002 acceptance).- Synthetic regression: re-adding
next: 1.6.3tag → pre-push step 9 FAILs as expected. - Synthetic regression: re-introducing
join(root, 'src')in detect/ → generalization-scanner Check 5 FAILs as expected. - Live post-deploy:
curl -s https://massu.ai/stats values match source-of-truth derivation;curl -s https://massu.ai/docs/reference/cli-reference | grep -cE '^## /'≥ 59.
Closes
- `plan-website-audit` (
docs/plans/2026-02-17-website-audit.md) — marked SUPERSEDED with successor pointer toplan-1.7.0-cohesive-cleanup. Drop reason: original plan's Phase 2/3/4/5 items invalidated by 3+ months of in-flight drift; cli-reference doc + team-tool cloud-gate items are preserved here as P-A-003/P-A-004; tier-reassignment + duplicate-Onboarding-Guide items were verified DROPPED in iter-1 audit (already correct infeatures.ts). - P6-004 of
plan-fresh-install-monorepo-paths(docs/plans/2026-04-20-fresh-install-monorepo-paths.md) — marked SHIPPED with successor pointer toplan-1.7.0-cohesive-cleanup. Hardcodedjoin(root, 'src')literal atdomain-inferrer.ts:71is gone; structural drift-guard Check 5 prevents recurrence. - Stale `next` dist-tag — removed in P-C-001; pre-push step 9 + CLAUDE.md policy section codify the policy going forward.
v1.6.3
2026-05-11Plan plan-1.6.3-website-feature-discoverability — Website + scanner patch eliminating two structural drift classes surfaced 2026-05-11 when the user asked "where do these changelogs show up on the website?": (a) public page added without nav link discoverable only via direct URL; (b) website code shipped to npm + git but never deployed to Vercel. Live evidence pre-fix: massu.ai/changelog showed 5-entry stale 0.x array (the pre-plan-changelog-sot hardcoded data) because the last production Vercel deploy was 33 days old, even though the build-time parser landed in 1.6.1. After this release, both bug classes are structurally impossible: Pattern Scanner Check 15 enforces nav-link coverage with an explicit WEBSITE_NAV_EXEMPT allowlist; pre-push-light step 8 deploy-staleness gate enforces lockstep between website commits and Vercel deploys; CR-48 mandates /massu-deploy in Stage D for any website-touching plan. Backfill deploy in this release ships 33 days of accumulated website changes (1.6.1 changelog parser + 1.6.2 EXPECTED_COUNT bump + this plan's nav links) to production.
Added
- `website/src/data/nav-exempt.ts` — shared constants
WEBSITE_NAV_HIDDEN_PREFIXES(currently['/dashboard']) andWEBSITE_NAV_EXEMPT(currently[]). Sole source of truth for "page intentionally has its own shell" (consumed byNavbar.tsx) AND "page intentionally not in public nav" (consumed by Pattern Scanner Check 15). EachWEBSITE_NAV_EXEMPTentry requires a JSDoc explaining the intentional exemption. - `scripts/massu-deploy-staleness-check.sh` — compares last
website/-touching commit onorigin/mainvs last production Vercel deploy timestamp. FAILs if lag exceedsMASSU_MAX_DEPLOY_LAG_SECS(default 86400 = 24h) on main branch. SKIP+WARN on Vercel CLI auth mismatch (vercel whoami+teams listpre-flight againstethans-projects-22aee2ce). Bypass viaMASSU_SKIP_DEPLOY_STALENESS_CHECK=1(logged to stderr for audit-trail). - `scripts/pre-push-light.sh` step 8 — invokes the staleness check. Renumbered 9 existing
[N/7]labels to[N/8]. - `scripts/massu-pattern-scanner.sh` Check 15 — public page nav-link coverage guard. Enumerates every
page.tsx/page.mdxunderwebsite/src/app/, excludes hidden-prefix routes + auth/checkout + dynamic[slug]+ root +WEBSITE_NAV_EXEMPT, cross-references against the union ofhref:values innavigation.ts+Footer.tsx. Synthetic regression VERIFIED: temporarytest-orphan-DELETE-ME/page.tsxtriggersFAIL: /test-orphan-DELETE-ME has no nav link. - `scripts/massu-plan-status-validator.sh` L3 retrospective check — for every shipped-subset plan whose cited SHA modified files under
website/, emits WARN if plan body lacks/massu-deployreference. Non-blocking (retrospective enforcement only); forward-going gate is CR-48 in CLAUDE.md. - CR-48 / VR-DEPLOY-STALENESS in
.claude/CLAUDE.md. Full definition section between CR-40 and CR-39. Release ceremony template for website-touching plans documented as 10-step Stage D in## Deploymentsection. - `Changelog` link in
Footer.tsxResources group, between Quick Start and GitHub. - `Overview` link in
navigation.tsmainNavarray, between How It Works and Articles (mainNav.length6 → 7).
Changed
- `website/src/components/layout/Navbar.tsx:25` — replaced inline
pathname.startsWith('/dashboard')withWEBSITE_NAV_HIDDEN_PREFIXES.some((prefix) => pathname.startsWith(prefix)). Eliminates the duplication between Navbar'sisDashboardliteral and the future scanner's "what counts as a dashboard page" knowledge. Both now read fromnav-exempt.tsas the single source of truth. - `scripts/massu-deploy.sh` + `.claude/commands/massu-deploy.md` — smoke test list extended from
["/", "/docs"]to["/", "/docs", "/changelog", "/overview"]. Future regressions on either new page fail the deploy gate.
Verification
cd packages/core && npx tsc --noEmit: 0 errorscd website && npx tsc --noEmit: 0 errorscd packages/core && npm test: 2144 passed / 12 skipped (baseline preserved — no daemon code changes)cd website && npm test: 114 passed (drift-guardEXPECTED_COUNTbumped 19 → 20)bash scripts/pre-push-light.sh(Node 22): 7/8 PASS pre-deploy; step 8 (Deploy Staleness) intentionally FAILed against 33-day lag pre-P-D-009; PASSes after backfill deploy- Pattern Scanner Check 15: PASS with synthetic regression evidence
bash scripts/massu-plan-status-validator.sh: PASS 0 violations- Live post-deploy:
curl -s https://massu.ai/changelog | grep -cE '\bv?1\.6\.3\b'≥ 1;curl -s https://massu.ai/ | grep -cE 'href="/overview"'≥ 1;curl -s https://massu.ai/ | grep -cE 'href="/changelog"'≥ 1; 20 distinct version headings on/changelog(was 5 pre-deploy)
Closes
- Plan
plan-1.6.3-website-feature-discoverabilityaudit converged at 0 gaps after 2 iterations (11 → 0). - 33-day Vercel-deploy lag — closed by P-D-009 backfill deploy in this release.
- User report 2026-05-11 ("where do these changelogs show up on the website?") — structurally answered via three forward-going gates (Check 15 + step 8 + CR-48).
v1.6.2
2026-05-10Plan plan-1.6.2-server-lazy-db-deps — Daemon-code patch eliminating the structural bug where every MCP tool/call eagerly opened both CodeGraph + Data SQLite DBs at the top-level dispatcher. In any repo without .codegraph/codegraph.db, ALL tools failed — even memory/audit/knowledge tools with no logical codegraph dependency. The error surfaced as JSON-RPC code -32700 ("Parse error", spec-reserved for actual JSON parse failures) with id:null. Bug class is now structurally impossible via a typed per-tool DB-needs manifest + AST drift-guard + pattern-scanner Check 14. CR-46 / Rule 0 — replaces an implicit "every tool gets both DBs" coupling with explicit, typed, lazy resolution.
End-users on 1.5.x / 1.6.0 / 1.6.1 are unaffected in repos where .codegraph/codegraph.db is present (the prior eager-load happened to find the file). Users in fresh installs without codegraph (which became more common as 1.5.0 → 1.6.0 expanded the user base) now see correct behavior: memory tools work; codegraph-dependent tools return a structured -32001 error with remedy hint pointing at npx @colbymchenry/codegraph init.
Added
- `packages/core/src/tool-db-needs.ts` — typed
TOOL_DB_NEEDSmanifest covering all ~70 MCP tools.DbNeed = 'codegraph' | 'data' | 'memory' | 'knowledge'.getToolDbNeeds(toolName, prefix)is the single source of truth; throwsUnknownToolErrorfor tools not in the manifest.toolNeedsCodegraph()convenience predicate. - `packages/core/src/__tests__/server-lazy-db-deps.test.ts` — 12-assertion behavior test: manifest shape, prefix-strip +
UnknownToolError,toolNeedsCodegraphfor codegraph-dependent vs codegraph-independent tools,CodegraphDbNotInitializedErrorclass shape. - `packages/core/src/__tests__/tool-db-needs-completeness.test.ts` — 19-assertion drift-guard using TypeScript Compiler API (
ts.createSourceFile). Walks every*-tools.tsand listed handler module, identifies whichgetCodeGraphDb/getDataDb/getMemoryDb/getKnowledgeDbreferences the module actually uses, cross-references againstTOOL_DB_NEEDS. Aliasing/destructuring rename does NOT bypass an AST walk (the structural win over grep-based completeness checks). UsesMap(not plain object) for the DB-fn lookup to preventObject.prototypeidentifier matches (toLocaleString,hasOwnProperty, etc.). - `scripts/massu-pattern-scanner.sh` Check 14 — grep-level safety net before tests run. Every tool registered via
name: p('...')orname: \${prefix}_...\inpackages/core/src/*.tsMUST have a matching entry inTOOL_DB_NEEDS. Runs inpre-push-light.shstep 1. - `CodegraphDbNotInitializedError` in
packages/core/src/db.ts— internal error class (not thrown to clients raw). Carries the resolved DB path for the dispatcher to relay.
Changed
- `packages/core/src/server.ts` — eliminated module-level
codegraphDb/dataDbsingletons +getDb()helper. NewresolveDbsForTool(toolName)opens ONLY the DBs the manifest declares.tools/callhandler catchesCodegraphDbNotInitializedError→ structured-32001JSON-RPC error withdata.remedy(verbatimcodegraph initcommand),data.codegraphDbPath,data.tool. CatchesUnknownToolError→-32602(Invalid params). Stdio handler is now two-phase try/catch: JSON-parse failures emit spec-correct-32700 id:null; request-processing failures emit-32603 Internal errorpreserving the request `id` (was incorrectlynullin 1.6.1 and prior). - `packages/core/src/tools.ts`:
Removed
- Module-level eager-init of CodeGraph + Data DBs at server startup. Connections are cached lazily after first need.
Verification
cd packages/core && PATH="/opt/homebrew/opt/node@22/bin:$PATH" npx tsc --noEmit: 0 errorscd packages/core && npm test: 2144 passed / 12 skipped (was 2113 baseline; +31 new tests across the 2 new vitest files: 12 inserver-lazy-db-deps.test.ts+ 19 intool-db-needs-completeness.test.ts)bash scripts/pre-push-light.sh(Node 22): ALL 7 GATES PASS (including new Check 14 — Tool DB-needs manifest completeness)- End-to-end reproducer from
/tmp/repro-fixed/(NO.codegraph/codegraph.db): bash scripts/massu-plan-status-validator.sh: PASSbash scripts/massu-plan-commit-drift.sh: PASS
Closes
- Plan
plan-1.6.2-server-lazy-db-depsaudit converged at 0 gaps after 3 iterations (16 → 2 → 0). - The root cause of the 2026-05-10 in-session "MCP tools hanging" investigation (see
feedback_mcp_pin_version_in_mcp_json.md). The hang turned out to be a stale 1.4.0-soak.0 global install (separately fixed by .mcp.json pin in commitf6fa6ff), but THIS plan eliminates the underlying server bug that would have caused identical symptoms in any clean install without codegraph.
v1.6.1
2026-05-10Plan plan-changelog-sot — Website changelog now renders from CHANGELOG.md source-of-truth at build time. The hardcoded ChangelogEntry[] array on website/src/app/changelog/page.tsx (stale by 5+ major releases, last entry 0.6.3, mismatched 0.x scheme vs npm's 1.x) and the orphaned website/content/changelog/ directory are deleted. A vitest drift-guard test asserts every ## [X.Y.Z] heading produces exactly one rendered entry; structurally impossible for the rendered page to drift from CHANGELOG.md after this release. CR-46 / Rule 0 — replaces a recurring "rendered changelog goes stale" loop with a structural CI gate. Website-only patch; daemon code unchanged from 1.6.0.
Added
- `website/src/lib/changelog.ts` — hand-rolled Keep-a-Changelog 1.1.0 parser (
parseChangelog,readChangelog,getChangelog,renderInlineMarkdown,KNOWN_SECTION_HEADINGS). No new npm dependency — the format is tightly constrained and a regex-driven parser keeps the drift-guard surface small.getChangelog()wraps read+parse with a version-less fallback (no embedded version literals — that would itself create a new drift surface). - `scripts/copy-changelog-to-website.js` —
__dirname-relative copy script that runs frompredevandprebuildhooks, copies repo-rootCHANGELOG.mdintowebsite/CHANGELOG.md(gitignored). Resolves source path independent ofprocess.cwd()so it works from any invocation context (Vercel CI, manual, dev). Gracefully exits 0 when source is missing (Vercel CLI deploys that upload onlywebsite/). - `website/src/__tests__/changelog-parse.test.ts` — 13-assertion drift-guard:
EXPECTED_COUNT = 18, semver/ISO-date regex pins, no-duplicate-versions, latest-entry ==packages/core/package.json#version,KNOWN_SECTION_HEADINGSwhitelist coverage, historical[0.3.0]preserved per plan §1.4, and renderer fidelity (renderInlineMarkdownproduces<code>for@massu/adapter-rails@1.0.0literal markdown). - `renderInlineMarkdown` inline-markdown renderer — dependency-free helper handling the four common inline forms in CHANGELOG bullets (
code,[text](url),**bold**,*italic*) so users see formatted output on/changeloginstead of raw backtick + bracket syntax.
Changed
- `website/src/app/changelog/page.tsx` — hardcoded
ChangelogEntry[]array (legacy lines 31-264, frozen at0.6.3) replaced withconst changelog = getChangelog(). InlineChangelogEntryinterface moved to@/lib/changelog. Each<li>wraps{renderInlineMarkdown(item)}so backticks and links render correctly. - `website/package.json` —
predevandprebuildscripts added:node ../scripts/copy-changelog-to-website.js. Ensureswebsite/CHANGELOG.mdis in sync beforenext devornext buildreads it. - `website/.gitignore` —
/CHANGELOG.mdadded (derived build artifact; canonical source lives at repo root). - `website/tsconfig.json` —
target: "ES2017"→target: "ES2020". Fixes pre-existing TS1501 error onsregex flag insrc/__tests__/integration/sso-validation.test.ts:31per CR-9 (fix all encountered issues). Safe for Node 22 + Next.js 16 runtime.
Removed
- `website/content/changelog/` — orphaned MDX directory deleted (
grep -rn "content/changelog" website/src/returned 0 prior to deletion). The single file (2026-04-19-config-migration.mdx) had no consumer.
Verification
cd website && npx tsc --noEmit: 0 errorscd website && npm test: 18 files / 115 tests PASS (was 17/101 + 14 new after EXPECTED_COUNT bump)cd website && npm run build: exit 0,/changelogprerendered as static (○)- Rendered HTML inspection: 18 distinct version headings (1.6.1 through 0.3.0 preserved), 0 occurrences of stale
0.6.3legacy data,1.6.1appears in rendered output bash scripts/pre-push-light.sh(Node 22): ALL 7 GATES PASSbash scripts/massu-plan-status-validator.sh: PASSbash scripts/massu-plan-commit-drift.sh: PASS
Closes
- Plan
plan-changelog-sotaudit: converged at 0 gaps after 4 iterations (14 → 4 → 2 → 0). - Master plan row 4.10 drift-class — the "rendered changelog goes stale" loop that motivated the blog post review is now structurally impossible.
v1.6.0
2026-05-09Plan 3c Phase 9b — workspace adapter publish (plan-3c-phase9b). Closes the 1.5.0 Infrastructure note ("5 workspace placeholder packages remain at 0.0.0-prework") by shipping the 5 first-party AST adapters as standalone npm packages alongside @massu/core@1.6.0. Architecture is Z+II: workspace package source is canonical (packages/adapter-<f>/src/index.ts); @massu/core consumes those packages as workspace dependencies and bundles their built dist/ into its own dist/detect/adapters/<f>.js via a build step (packages/core/scripts/bundle-adapters.ts). True single-source-of-truth: same source produces both the CORE-BUNDLED artifact and the standalone REGISTRY-VERIFIED tarball; sha256 reproducibility is structurally enforced.
End-users on 1.5.x are unaffected — 1.6.0 is additive, zero-config preserved, no breaking changes. Users who want the REGISTRY-VERIFIED trust class can now npm install @massu/adapter-rails (etc.) for the same code with a fully signed manifest sha256 chain end-to-end.
Daemon code unchanged from 1.5.8 — any in-flight 1.5.x soak verdict applies to 1.6.0.
Added
- `@massu/adapter-rails@1.0.0` — first-party Rails adapter, standalone npm package. Tarball shasum:
1944b1a4568b5c9f07a457a93050a926f78ac76f(pernpm view @massu/adapter-rails dist.shasum). See `packages/adapter-rails/CHANGELOG.md`. - `@massu/adapter-phoenix@1.0.0` — first-party Phoenix adapter, standalone npm package. Tarball shasum:
22d856030b8d220d3846d7f16d32d7daa41b76ea. See `packages/adapter-phoenix/CHANGELOG.md`. - `@massu/adapter-aspnet@1.0.0` — first-party ASP.NET Core adapter, standalone npm package. Tarball shasum:
2fff624d3491ced5a831f7b7dce36928e91020a2. See `packages/adapter-aspnet/CHANGELOG.md`. - `@massu/adapter-spring@1.0.0` — first-party Spring adapter, standalone npm package. Tarball shasum:
b3f7a87e0f25c9174c4e3540c1a255feb333a6cb. See `packages/adapter-spring/CHANGELOG.md`. - `@massu/adapter-go-chi@1.0.0` — first-party go-chi adapter, standalone npm package. Tarball shasum:
48916c0b6e8c7ed0c53d60acb170bef39dffea15. See `packages/adapter-go-chi/CHANGELOG.md`. - `@massu/core/adapter` runtime helpers in the SemVer-stable surface —
adapter.tsnow re-exportsrunQuery,loadGrammar,isParsableSource,MAX_AST_FILE_BYTES, andInvalidQueryErrorso workspace adapters import everything they need from a single subpath. The published tarball ships bundleddist/adapter.js(~32 kB ESM) +dist/adapter.d.ts(1.7 kB) so downstream Node consumers AND tsc resolve cleanly without chasing transitive.tssource. - `packages/core/scripts/bundle-adapters.ts` — esbuild-driven build step that copies the 5 workspace adapter
dist/index.jsfiles intopackages/core/dist/detect/adapters/<f>.jsand computes a sha256 sentinel atdist/detect/adapters/.bundle-shasums.json. Reproducibility enforced byadapter-bundle-reproducibility.test.ts(P-B-003). - Three new structural drift-guard tests —
adapter-source-of-truth.test.ts(everyCORE_BUNDLED_IDSentry has exactly one canonical source — either core or workspace, never both),adapter-bundle-reproducibility.test.ts(re-running the bundle step from a clean tmpdir produces byte-identical sha256s), andcore-bundled-files-presence.test.ts(every workspace-canonical id has a correspondingdist/detect/adapters/<id>.jsafter build). The fourth gate,adapter-manifest-roundtrip.test.ts(P-D-001), runs in CI whenMASSU_MANIFEST_ROUNDTRIP=1against the live registry manifest. - Pattern-scanner Check 12 — adapter import direction guard —
scripts/massu-pattern-scanner.shnow refuses anyimport .* from '@massu/adapter-*'outsidepackages/core/src/detect/adapters/<id>.tsre-export shims (drift-prevention #3). Inverse imports would create circular runtime deps that npm workspaces silently allow. - Vitest 4 `test.projects` shape — new root
vitest.config.tsruns both core (~141 test files) and the 5 adapter packages (5 × 1 smoke test each) in a singlenpm testinvocation. Replaces the deprecated Vitest 3defineWorkspace/vitest.workspace.tspattern (removed in Vitest 4.x). - Tarball E2E CI extension (P-D-002) —
.github/workflows/ci.ymltarball-e2ejob now also packs eachpackages/adapter-*and asserts the published shape (nosrc/, no*.test.ts, notsconfig.jsonleak; LICENSE + README.md +dist/index.{js,d.ts}+package.jsonrequired). Adds the manifest round-trip gate.
Changed
- `packages/core/package.json` — version
1.5.8→1.6.0. Newdependenciesfor the 5 workspace adapters ("@massu/adapter-rails": "^1.0.0", etc.) so the build pipeline pulls in workspace symlinks. Newexports."./adapter"conditional shape with explicittypes: "./dist/adapter.d.ts"+import: "./dist/adapter.js"(was raw./src/adapter.ts— caused R9 runtime resolution failures for downstream consumers). - Root `package.json:scripts.build` — explicit chain
build:adapter-types && build:adapter-subpath && build:adapters && build:coreso workspace adapters always build beforebundle-adapters.tsruns (P-A-016 build-ordering fix; npm--workspacesruns alphabetically, not topologically). - `packages/core/src/detect/adapters/{rails,phoenix,aspnet,spring,go-chi}.ts` — replaced with 4-line re-export shims (
export * from '@massu/adapter-<f>'). Source moved to workspace package; CORE-BUNDLED behaviour preserved via the bundle step. - `packages/adapter-*/package.json` — version
0.0.0-prework→1.0.0;private: trueremoved;peerDependencies."@massu/core": ">=1.5.8 <2.0.0"(intentionally widened to keep 1.5.x consumers compatible — 1.5.x already CORE-BUNDLES the same code, so peer is loose by design). - `scripts/PUBLIC_MANIFEST.md` — updated notes for the 5
packages/adapter-*entries to reflect the new published 1.0.0 versions (no path changes; they remain in PUBLIC_DIRS).
Verification
npx tsc --noEmit: 0 errors (packages/core)npm test(Node 22):2123 passed | 9 skipped (2132)across 146 test files (was2087/2087per 1.5.7; +36 new tests across the 4 structural drift-guards + 5 adapter smoke tests + the manifest round-trip)bash scripts/massu-pattern-scanner.sh: PASS (13 checks, including the new Check 12 adapter import direction guard)bash scripts/massu-generalization-scanner.sh: PASSbash scripts/massu-plan-status-validator.sh: PASS (55 plans scanned, 0 violations)bash scripts/massu-plan-commit-drift.sh: PASSnpm view @massu/core version:1.6.0npm view @massu/core dist.shasum:e3f74555959db52462d14eb1223b3c2d8937a430npm view @massu/adapter-rails version(× 5 adapters):1.0.0- Smoke test from clean tmpdir:
npm install @massu/core@1.6.0 @massu/adapter-rails@1.0.0exits 0;node -e "import('@massu/adapter-rails').then(m => console.log(typeof m.railsAdapter))"printsobject
Closes
- The 1.5.0 CHANGELOG
Infrastructurenote ("5 workspace placeholder packages remain at0.0.0-prework") — workspace publish is now SHIPPED. - Master plan row 4.12 ("Phase 9b — workspace adapter publish") — moved from APPROVED → SHIPPED.
- The originating obligation in Plan 3c §Stage 3 Phase 9 line 157 ("Workspace publish for 5 adapters").
- Plan 3c Phase 9b audit: converged at 0 gaps after 7 iterations (22 → 6 → 5 → 6 → 3 → 3 → 0).
v1.5.8
2026-05-09Plan-status drift-guard release. Closes the recurring "manual refresh of stale plan-Status headers" pattern with three structural layers: schema validator, commit-link drift scanner, and a vitest drift-guard test. Adds a canonical **Plan Token**: field to all 55 plans so commits can be cross-referenced bidirectionally with their plan documents. CR-46 / Rule 0 — replaces a recurring manual loop with a structural CI gate.
Daemon code unchanged — any in-flight 1.5.x soak verdict applies to 1.5.8.
Added
- `scripts/massu-plan-status-validator.sh` — schema validator for
docs/plans/*.md. Parses frontmatter, validates Status against the 8-value canonical enum (DRAFT, IN PROGRESS, SHIPPED, COMPLETE, IMPLEMENTED, APPROVED, SUPERSEDED, HISTORICAL DRAFT), validates**Plan Token**:uniqueness, requires SHA citation for SHIPPED, suggests path citation for SUPERSEDED. Supports--json, honorsMASSU_PLAN_DIRenv override. Exit 0 = PASS, 1 = FAIL. - `scripts/massu-plan-commit-drift.sh` — commit-link drift scanner. Greps
git logsinceMASSU_DRIFT_SINCE(default 2026-04-01) for(feat|fix|chore|docs)\(plan-<token>\)references, looks up plans by token, FAILS if Status is in the non-shipped enum (DRAFT, IN PROGRESS), WARNs on misses listed in the cross-repo allowlist. - `scripts/massu-plan-external-tokens.txt` — cross-repo plan-token allowlist (26
plan-3c-*tokens authored in a private sister repository). Prevents the drift scanner from FAILing on legitimately external plan references. - `packages/core/src/__tests__/fixtures/plans/` — 9 minimal-stub fixtures exercising every validator + scanner code path (stale-draft, fresh-draft, shipped, superseded, historical, duplicate-token-{a,b}, missing-token, unknown-status).
- `packages/core/src/__tests__/plan-status-drift-guard.test.ts` — 8-case vitest test using
execSync+MASSU_PLAN_DIRenv override; case 8 runs against live HEAD and gates every PR. - `.github/workflows/ci.yml` — two new steps in the
type-checkjob: Plan Status Validator + Plan Commit Drift Scanner. - `scripts/pre-push-light.sh` — steps 6 + 7 invoke validator + drift scanner (
set -eswapped forset -uo pipefailmatching the rest of the scripts/ idiom). - `scripts/hooks/pre-commit-gate.sh` — staged-tree gate via merged-corpus extraction (
git show :<path>for staged Add/Modify/Rename + remove staged deletions); fast-path skip when no plan-related diffs are staged. - `scripts/PUBLIC_MANIFEST.md` + `scripts/sync-public.sh` — exclude the 3 new private scripts from public sync.
- CR-40 / VR-PLAN-STATUS added to
.claude/CLAUDE.md. - `Plan Token:` field backfilled across all 55 plans; 38 plans missing
**Status**:headers received derived Status (commit-cited SHIPPED for plans with matching commits; HISTORICAL DRAFT for legacy plans). - `.claude/templates/plan-frontmatter.md` — author template (R6 mitigation; auto-emitted by validator).
Verification
npx tsc --noEmit: 0 errors (packages/core)npm test -- plan-status-drift-guard: 8/8 PASSbash scripts/massu-plan-status-validator.sh: exit 0, 0 violations, 13 deprecation/legacy warnings (Doc ID, Plan ID, COMPLETE legacy synonym — all warn-only by design)bash scripts/massu-plan-commit-drift.sh: exit 0, 0 violations, 28 allowlisted external-token warnings, 94 commits scanned, 42 plan refs foundbash scripts/massu-pattern-scanner.sh: PASSbash scripts/massu-generalization-scanner.sh: PASS
Closes
- The recurring "stale Status header" class of bug (commits
0ed226a2026-05-08 +21e055d2026-05-09 manually refreshed 10 stale headers — the second occurrence in <24 h, proving discipline alone is insufficient). - Plan 1.5.8 audit converged at 0 gaps after 7 iterations (16 → 9 → 8 → 2 → 2 → 1 → 0).
v1.5.7
2026-05-08Test infrastructure release. Closes the two follow-ons documented in 1.5.5 and 1.5.6 CHANGELOGs as the structural drift-prevention against the class of bug that produced both hotfixes (FIRST_PARTY_ADAPTERS divergence + bundle-vs-source CI gap).
Daemon code unchanged — 1.5.0 48 h soak verdict applies to 1.5.7.
Added
- `first-party-adapters-coverage.test.ts` — strict drift-guard asserting
FIRST_PARTY_ADAPTERS(the runtime dispatch list atcodebase-introspector.ts:75-78) parity withCORE_BUNDLED_IDS(the trust-class id-set atdetect/adapters/index.ts:23-33). Pre-1.5.7 the two diverged silently — Phase 7 commits added each new adapter toCORE_BUNDLED_IDS(gated bycore-bundled-ids-drift.test.ts) but missed the runtime dispatch list. The 1.5.4 → 1.5.5 hotfix was the surfacing event. This test makes the divergence impossible to merge: every id in CORE_BUNDLED_IDS must have an adapter import in codebase-introspector.ts AND vice versa. - CI `tarball-e2e` job — extends
.github/workflows/ci.ymlwith a job that runsMASSU_TARBALL_E2E=1 npx vitest run src/__tests__/init-tarball-e2e.test.tson every push to main + PR. The test infrastructure was added in 1.5.3 but had no CI trigger; 1.5.5 and 1.5.6 both shipped bundle-only bugs (FIRST_PARTY_ADAPTERSomission, web-tree-sitter not externalized) that the tarball-e2e gate would have caught BEFORE publish if it had been wired. Now wired; future bundle-vs-source regressions auto-fail CI before publish.
Verification
npx tsc --noEmit: 0 errorsnpm test: 2087/2087 source-level pass (+1 new drift test)bash scripts/massu-pattern-scanner.sh: PASSbash scripts/massu-generalization-scanner.sh: PASS
Closes
- Plan 1.5.5 CHANGELOG follow-on ("first-party-adapters-coverage gate") — SHIPPED.
- Plan 1.5.6 CHANGELOG follow-on ("CI workflow that sets MASSU_TARBALL_E2E=1") — SHIPPED.
v1.5.6
2026-05-08Hotfix on 1.5.5. 1.5.5 added the 6 Phase 7 adapters to FIRST_PARTY_ADAPTERS correctly, but the bundled dist/cli.js inlined web-tree-sitter while its companion tree-sitter.wasm runtime artifact was NOT copied to dist/. R-011 evidence (live test against published 1.5.5):
RuntimeError: Aborted(Error: ENOENT: no such file or directory,
open '/Users/.../dist/tree-sitter.wasm')
at abort (cli.js:13114:20)
at Parser.init (cli.js:14585:19)
at loadGrammar (cli.js:14946:3)
The bundled web-tree-sitter expects to find its sibling tree-sitter.wasm next to its own code. esbuild bundling inlines the JS but can't move companion wasm assets.
Fixed
- Externalize `web-tree-sitter` (and other native-asset deps) from the cli bundle —
build:clinow passes--external:web-tree-sitter --external:tweetnacl --external:tar --external:smol-toml --external:vscode-languageserver-protocol. These remaindependenciesin package.json so users get them vianpm installand at runtime the bundle resolves them through normal node_modules. The companiontree-sitter.wasmresolves naturally next to web-tree-sitter's own code.
Verification
- Rebuilt 1.5.6 cli.js loads grammars from
<install>/node_modules/web-tree-sitter/tree-sitter.wasmcorrectly. npx --yes @massu/core@1.5.6 initagainst a Phoenix fixture producesdetected.phoenix:block with extracted conventions.
v1.5.5
2026-05-08Hotfix on 1.5.4 (published ~10 min earlier same day). 1.5.4 shipped the file sampler + introspect piping correctly but codebase-introspector.ts:FIRST_PARTY_ADAPTERS only listed the 4 original Plan-3b adapters. Phase 7 adapters (rails, phoenix, aspnet, spring, go-chi, python-flask) were committed Phase 7 but never added to the runtime dispatch list. The omission was masked pre-1.5.4 by the sampleFiles=[] placeholder (every adapter returned 'none' anyway). 1.5.4 made the sampler work but the dispatch list was still incomplete → npx massu init against a Phoenix project produced an empty introspected object → no detected.phoenix: block in the emitted config.
R-011 evidence: live debug instrumentation 2026-05-08 against published 1.5.4 cli.js:
[DBG] introspect-branch entered, skip=undefined
[DBG] introspected keys= values={}
Empty result confirms the runner never invoked the phoenix adapter (the only relevant one for the fixture).
Fixed
- `codebase-introspector.ts:FIRST_PARTY_ADAPTERS` — added the 6 Phase 7 adapters (
pythonFlaskAdapter,goChiAdapter,railsAdapter,phoenixAdapter,aspnetAdapter,springAdapter). Total dispatch list now 10 adapters, matchingCORE_BUNDLED_IDSfromdetect/adapters/index.ts.
Verification
- Re-running the Phoenix fixture against 1.5.5 produces
detected.phoenix:block withroute_method,scope_prefix_base,router_module,_provenance,_confidence: high. - Same for the other 5 Phase 7 fixtures.
v1.5.4
2026-05-08Closes Plan 1.5.1 §3 item #4 (the explicitly-deferred AST adapter output piping). Pre-1.5.4 introspectAsync() handed AST adapters SourceFile[] = [] because of a placeholder at codebase-introspector.ts:160-170. Adapters always worked (verified by adapter-grammar-strict.test.ts 10/10 fixtures with 'high' confidence) but their extracted conventions never reached the user-facing emitted config. 1.5.4 ships the real per-adapter file sampler and pipes AST output into detected.<adapter-id>: blocks.
Daemon code unchanged — 1.5.0 48 h soak verdict applies to 1.5.4.
Added
- `detect/adapters/file-sampler.ts` — per-adapter file sampler. Reuses
EXTENSIONSandTEST_FILE_PATTERNSfromsource-dir-detector.ts:84-104(no parallel maps; CR-46 self-attest #3). Algorithm: per language inadapter.languages, walk source dirs fromdetection.sourceDirs[<lang>].source_dirsup to depth 3, filter by extension, exclude test files, cap per-adapter at 50 files, drop files >MAX_AST_FILE_BYTES(256 KB). Refuses symlinks and ignored dirs (node_modules, .git, dist, target, etc.). - AST output piping in `runInit` — after variant template merge, runs
introspectAsync(detection, projectRoot)and merges every adapter's output (where_confidence !== 'none') intoconfig.detected[<adapter-id>]. Each block carries the adapter's extracted conventions (route_method,scope_prefix_base,controller_class, etc.) plus_provenanceand_confidencepertypes.ts:114-130. - `--no-introspect` CLI flag — bypasses the AST introspect step. Useful for fast sync init or when grammar download is undesirable. Default-on (introspect runs).
- `sample-files-coverage.test.ts` — 3 strict gates: every language declared by ANY of the 10 first-party adapters has both
SAMPLE_EXTENSIONSandSAMPLE_TEST_FILE_PATTERNSentries; extension strings are well-formed (no leading dot). Future adapters targeting an uncovered language fail the build. - `init-end-to-end.test.ts` extension — added
detected.<adapter-id>:block assertion: when the AST adapter for a fixture's framework returns non-'none'confidence, the block must carry_confidenceand at least one non-meta convention key. Lenient on grammar-load failure (CI offline) but strict on shape when present.
Verification
npx tsc --noEmit: 0 errorsnpm test: 2086 source-level tests pass (+8 tarball-level skipped perMASSU_TARBALL_E2Egate)MASSU_TARBALL_E2E=1 npm test: tarball gate runs against the 1.5.4 build with the new sampler + introspect pipingbash scripts/massu-pattern-scanner.sh: PASSbash scripts/massu-generalization-scanner.sh: PASS- 1.5.1's
init-end-to-end.test.tsstill green (5/5) — variant template merge stays correct core-bundled-ids-drift.test.ts: green (addedfile-sampler.tstoADAPTER_SUPPORT_FILES)
Closes
- Plan 1.5.1 §3 item #4 ("Pipe
introspectAsync()output todetected.<adapter-id>:block in the emitted config") — explicitly deferred at 1.5.1 ship; closed here.
Phase 7 fixture verification (cited)
v1.5.3
2026-05-08Test infrastructure release that closes the source-vs-bundle gap demonstrated by 1.5.1 → 1.5.2 hotfix. Pre-1.5.3, init-end-to-end.test.ts ran against TS source via vitest where __dirname resolves at src/commands/-depth; production dist/cli.js has different depth and was failing the same scenarios despite the in-repo test being green. 1.5.3 ships a tarball-level e2e test that catches this entire class of bug.
Daemon code unchanged — 1.5.0 48 h soak verdict applies to 1.5.3.
Added
- `init-tarball-e2e.test.ts` — runs
npm pack+ clean install + spawns<install>/node_modules/.bin/massu initagainst each Phase 7 fixture in tmpdir, then asserts the same field-by-field expectations the source-level test asserts. Plus 3 tarball-shape gates:dist/cli.jsexists,templates/<id>/massu.config.yamlis well-formed YAML for every present id,<bin>/massu --versionmatchespackage.json:version. Tag-gated viaMASSU_TARBALL_E2E=1env var so it runs in CI but not in localnpm testby default. - Shared fixture module `src/__tests__/fixtures/phase7-init-fixtures.ts` — single source for the 5-fixture test data consumed by BOTH
init-end-to-end.test.tsANDinit-tarball-e2e.test.ts. Adding a new framework = ONE entry that BOTH tests pick up. - `resolve-templates-dir-bundle-path.test.ts` — 4 unit tests that explicitly catch the 1.5.2 path-off-by-one regression class. Sets up both bundled-cli (
<pkg>/dist/cli.js) and legacy-nested (<pkg>/dist/commands/init.js) layouts in tmpdir; asserts the candidate-list logic resolves correctly in each. Future builds that move cli.js to a different depth fail this test before reaching the tarball test. - Hermetic build in tarball-e2e —
beforeAllrunsnpm run buildbeforenpm packso the test never reads a staledist/from a previous build.
Verification
npx tsc --noEmit: 0 errorsnpm test: 2079/2079 + 4 new (resolve-templates-dir) = 2083 source-level tests passMASSU_TARBALL_E2E=1 npm test: +8 tarball-level tests (5 fixtures + 3 shape gates) pass against the actual built bundlebash scripts/massu-pattern-scanner.sh: PASSbash scripts/massu-generalization-scanner.sh: PASS
Known follow-on
- AST adapter introspect output piping (
detected.<adapter-id>:block) — Plan 1.5.4 (docs/plans/2026-05-08-ast-introspect-piping.md). Hard prerequisite met now (1.5.3 ships the tarball-e2e gate that 1.5.4's variant-template-style changes need to validate against).
v1.5.2
2026-05-08Hotfix on 1.5.1 (published ~30 min earlier same day). 1.5.1's variant-template merge was structurally correct but resolveTemplatesDir() had a long-standing path-resolution bug that returned null from the bundled dist/cli.js — so applyVariantTemplate always bailed at its first guard, leaving framework.router: none in emitted configs.
Fixed
- `resolveTemplatesDir()` path resolution for bundled cli.js (long-standing bug) — pre-1.5.2 candidates
../../templatesand../../../templatesassumed cli.js was nested atdist/commands/init.jsdepth; the actual bundled location isdist/cli.js, requiring../templates. Pre-1.5.2 the function returnednullin npm-installed deployments for BOTH--template <name>mode AND (new in 1.5.1) the variant-template merge. Cited evidence:node $cli init --yesdebug instrumentation 2026-05-08 showed candidatesnode_modules/@massu/templatesandnode_modules/templates(wrong scopes) and never the actualnode_modules/@massu/core/templatesdirectory. Added../templatesas the first dist-relative candidate; older candidates retained as fallbacks.
Verification
npx --yes @massu/core@1.5.2 initagainst the 5 framework fixtures (rails, phoenix, aspnet, spring, go-chi) produces configs with the correctframework.router,paths.source,verification.<lang>.lintvalues.
v1.5.1
2026-05-08Patch release closing two CR-39 violations discovered via end-to-end fixture verification of npx massu init against all six Phase 7 frameworks. Phoenix and ASP.NET projects could not previously install Massu (error: no languages detected); Rails / Spring / Go-chi installed but emitted generic configs missing the framework-specific variant template's framework.router, paths.source, and verification.<lang>.lint fields.
Daemon code unchanged from 1.5.0 — the in-flight 1.5.0 48 h soak verdict (started 2026-05-08T15:21:23Z) applies to 1.5.1.
Added
- Canonical manifest registry (`packages/core/src/detect/manifest-registry.ts`) — single source-of-truth for every recognized manifest file. Both
package-detector.ts(init's framework-detection layer) andrunner.ts:buildDetectionSignals(AST adapter signal layer) consume from this registry. Adding a new manifest type now requires exactly one entry; both consumers automatically pick it up. Replaces the two parallel hand-rolled lists that diverged during Phase 7. - Elixir + C# manifest support —
mix.exsand*.csprojnow recognized by package-detector. Closes the CR-39 gap where Phoenix and ASP.NET projects failednpx massu initeven though their AST adapters worked correctly. Includes newparseMixExsandparseCsprojparser functions; the csproj parser also extracts the<Project Sdk="...">attribute for SDK-style detection. - Variant template merge (`applyVariantTemplate` in `commands/init.ts`) — when
framework.languages.<lang>.frameworkresolves to a known id, init now reads the matchingpackages/core/templates/<id>/massu.config.yamland selectively merges itsframework.router,framework.orm,framework.ui,paths.source, andverification.<lang>.{lint,syntax,test,type,build}fields. Closes the gap where rails/spring/go-chi/phoenix init succeeded but the resulting config lacked the framework's canonical lint command (rubocop / credo / golangci-lint / etc.) and routing identifier. - Framework-detector rules for elixir + csharp —
phoenix({:phoenix, ...}in mix.exs),aspnet-core(Microsoft.AspNetCore.App/.MvcPackageReference,Microsoft.NET.Sdk.WebSdk attribute),ex-unittest framework,xunit,ecto,ef-coreORM. go-chi rules expanded to cover all major-versioned import paths (github.com/go-chi/chi/v2through/v5). - Strict gate `manifest-registry-drift.test.ts` — 10 assertions: every entry has a callable parse function, unique pattern, well-formed shape; the
MANIFEST_FILESconst is permanently retired; every Phase 7 framework adapter language has a registry entry; every non-nullsignalKeycorresponds to a realDetectionSignalsfield. - Strict gate `init-end-to-end.test.ts` — 5 fixture-based end-to-end tests (rails, phoenix, aspnet, spring, go-chi) that run
runInitagainst minimal projects in tmpdir() and assert the emittedmassu.config.yamlcarries the variant-template-definedframework.router,paths.source, andverification.<lang>.lint. The class of bug "init succeeded but variant template missing" is now structurally impossible to merge.
Fixed
- CR-39 violation: Phoenix + ASP.NET fixtures fail `npx massu init` — root cause:
package-detector.ts:122-132had aMANIFEST_FILESlist missingmix.exsand*.csproj. Closed by manifest registry + new parsers. - Variant template `paths.source` for ASP.NET — was
src(which doesn't exist by ASP.NET Core convention); now.(project root). ASP.NET projects placeControllers/,Pages/,Program.csetc. at the project root. - `source-dir-detector.ts` extension map missing elixir / csharp — added
.ex/.exsand.csextensions plus their respective test-file regex patterns (_test.exs,Tests?.cs,.Tests?/).
Verification
npx tsc --noEmit: 0 errorsnpm test: 2079/2079 pass (+15 new structural tests, zero regressions)bash scripts/massu-pattern-scanner.sh: PASSbash scripts/massu-generalization-scanner.sh: PASS- 5-fixture re-verification: all six Phase 7 frameworks (rails, phoenix, aspnet, spring, go-chi, python-flask covered transitively via SUPPORTED_LANGUAGE) produce valid
massu.config.yamlwith variant-template-merged fields.
Known follow-on
- AST adapter introspect output (
detected.<adapter-id>:block in emitted config) is still NOT piped through to init. The blocker iscodebase-introspector.ts:160-180sampleFilesreturning[]— adapters run but see zero source files. Closing this requires a real file-sampling layer; see follow-on plan to come.
v1.5.0
2026-05-07Plan 3c (adapter registry + framework coverage). Registry infrastructure (registry.massu.ai) is live and signed; six new first-party AST adapters bring supported framework count from 4 to 10 (rails, phoenix, aspnet, spring, flask, go-chi added on top of the 1.4.0 baseline of fastapi, django, nextjs-trpc, swiftui). A structural drift-guard test now makes "AST adapter silently degrades to regex fallback" impossible to merge — closing the gap that masked a web-tree-sitter/tree-sitter-wasms ABI mismatch through three Phase 7 commits.
Added
- Adapter registry trust model (Plan 3c Phase 5) — three-class adapter loading: CORE-BUNDLED (shipped in
@massu/coreitself, no verification needed), REGISTRY-VERIFIED (npm packages cross-checked against the signed manifest atregistry.massu.ai/adapters/manifest.json), LOCAL-EXPLICIT (operator-configured paths inmassu.config.yaml > adapters.local). Per-class verification scopes, kill-switch (adapters.enabled: falseshort-circuits REGISTRY-VERIFIED + LOCAL-EXPLICIT entirely), and persistent-stderr warnings on degraded modes. Seepackages/core/security/AUDIT-2026-05-XX.md(Phase 3.5 audit) anddocs/SECURITY.md. - Adapter registry infrastructure —
registry.massu.ailive on Vercel with Let's Encrypt (CN=registry.massu.ai), serving the signed manifest envelope (manifest_b64+ Ed25519 detached signature) at/adapters/manifest.json. Public signing key fingerprint3b6226d036c472e533110d11a7d0cd2773ce1d7d4f1003517d5bd69c5418ed4cshipped atpackages/core/security/registry-pubkey.{b64,pem,env}. Private key in macOS Keychain (security add-generic-passwordentrymassu/registry/signing/private). HTTPS verified (HSTSmax-age=63072000; includeSubDomains; preload). - Adapter SDK subpath export —
@massu/core/adaptersubpath providesdefineAdapter()factory +CodebaseAdaptertypes for third-party adapter authors. Adapters never import from@massu/coreinternals — only from this stable SDK. Seedocs/AUTHORING-ADAPTERS.md. - `npx massu adapters` CLI — three subcommands:
list(show which adapters are loaded + their trust class),refresh(re-fetch + re-verify the registry manifest),search <query>(search the manifest for adapters by id/keywords). - Six new first-party AST adapters (Plan 3c Phase 7) — bringing supported framework count to 10:
- `GRAMMAR_MANIFEST` expansion — six new Tree-sitter grammar entries (
go,ruby,csharp,java,kotlin,elixir) with hardcoded sha256 hashes for atomic-write cache verification. Each grammar wasm is downloaded once into~/.massu/wasm-cache/<lang>-<sha>.wasmwith LRU eviction at 16 entries (~50 MB cap). - Detection signal expansion —
DetectionSignalsnow includesmixExs?,csproj?,pomXml?,gradleBuild?(preferringbuild.gradle.ktsoverbuild.gradleper Gradle 7+ defaults). Mirrors the existinggemfile/goMod/cargoToml/pyprojectTomlmanifest-reader pattern. - STRUCTURAL grammar drift-guard (CR-46) — new test
adapter-grammar-strict.test.tsasserts every shipped adapter returns NON-'none'confidence on a clear-cut fixture. Closes the lenient-test-pattern hole (expect(['none', 'medium', 'high']).toContain(...)) that previously allowed grammar-load failures to silently degrade adapters to regex-fallback. Future ABI breaks, query typos, or wasm-cache corruption flip this gate red. Thecore-bundled-ids-drift.test.ts(added in Phase 5) now also covers the 5 new Phase 7 adapter ids. - Telemetry writer —
~/.massu/telemetry/adapter-discovery-*.jsonlfiles capture per-discovery-run statistics (count by trust class, refusal reasons) for offline analysis. Replay command surfaces aggregates without re-running discovery. - `massu adapters add-local` / `remove-local` / `resync-local-fingerprint` — three CLI commands that maintain the
~/.massu/adapters-local-fingerprint.jsonsentinel (gap-32 postinstall-poisoning defense). Drift between the recorded fingerprint and the currentadapters.localcontent forces operator re-acknowledgment before LOCAL-EXPLICIT adapters load.
Fixed
- Phase 7 grammar loadability (commit `d31b4d8`) — pinned
web-tree-sitterfrom^0.26.8to~0.25.10. Root cause (cited):web-tree-sitter@0.26.xatweb-tree-sitter.js:1944requires WebAssembly custom-section namedylink.0; the wasms shipped bytree-sitter-wasms@0.1.13(compiled withtree-sitter-cli@^0.20.8) emit the olderdylinksection name (verified viaxxd ~/.massu/wasm-cache/elixir-*.wasm). Empirical sweep across 0.20.8 → 0.26.8 confirmed 0.25.10 is the maximum-compatible version. Pre-fix, every Phase 7 AST adapter (python-flask,go-chi,rails) was silently degrading to'none'confidence (regex fallback). The newadapter-grammar-strict.test.tsis the structural drift-prevention that makes this class of bug impossible to merge again. - Rails adapter query (commit `d31b4d8`) — removed
(method_call ...)patterns fromrails.tsqueries. Thetree-sitter-rubyv0.20.1 grammar (pinned bytree-sitter-wasms@0.1.13) emits routes.rb DSL invocations as(call method: (identifier) arguments: (argument_list ...))— there is nomethod_callnode. Verified via AST probe (R-011 evidence cited inline inrails.ts). Even after the grammar-load fix, themethod_callpatterns would have thrownQueryError: Bad node name 'method_call'attree-sitter.js:1477. - Pattern-scanner FAILs (commit `c943aa3`) — directive-aware scanner + drift-guard close two stale FAILs. The scanner now respects
// massu-pattern-scanner: skipdirectives in source files (intentional regex deviations) and runs a sibling drift-guard test that fails the build ifmassu-pattern-scanner.shreports any new FAIL category not previously cleared. - Phase 3.5 security audit (commits `51ad804`, `259d7d8`, `4ab141e`, `9c5a80b`, `4d8f60a`, `2c21853`) — closed all 17 findings across 6 audit iterations. Notable:
HIGH-NEW-1(manifest cache TOCTOU), 5MEDfindings on schema validation tightness,LOW-NEW3-1(InstallEntrySchema.version regex),LOW-NEW4-2(printable-ASCII guard against ANSI log injection),LOW-NEW5-1(FingerprintSentinelSchema using PrintableAsciiStringSchema). Final iteration shipped a STRUCTURAL drift-guard test for the manifest-cachefetched_atfield — making the class of bug "manifest cache silently serves stale data because freshness is unenforced" impossible. - Phase 5 `gap-37` install-time + load-time sha256 — adapter packages now record their
installed_sha256atnpm installtime in~/.massu/adapter-manifest-installed.json; load-time discovery re-computes the hash and refuses to load on drift. Cross-check against the signed registry manifest'ssha256field detects post-install sidecar tampering (auditM4fix). - `scope MyAppWeb do` (alias-only Phoenix scope) — correctly excluded from
scope_prefix_basecapture per the string-literal-anchor in the SCOPE_PATH_QUERY (verified negative case via AST probe).
Security
- All Phase 3.5 audit findings closed (0 unfixed) per
packages/core/security/AUDIT-2026-05-XX.md. - Symlink attack defense across
discover.ts:walkNodeModules(lstatSyncnotstatSync) — same fix that landed ininstall-tracking.ts(auditH1) was missed indiscover.tsuntil iter 2. - Hidden-directory load-time refusal in
discover.ts(MED-NEW-2) — packages shipping.git/payload.jsetc. are refused at load time, closing thesha256OfDir-excludes-hidden-dirs gap. - Adapter-loading kill-switch (
adapters.enabled: false) defaults tofalseat the config schema layer (gap-1 /C1) — operators MUST opt-in to third-party adapter loading.
Infrastructure
web-tree-sitterpinned to~0.25.10(was^0.26.8). Hard upper bound documented inline; loosen this only aftertree-sitter-wasmsships a release withdylink.0-format wasms.- 5 workspace placeholder packages remain at
0.0.0-prework(@massu/adapter-{rails,phoenix,aspnet,spring,go-chi}) — these adapters ship CORE-BUNDLED in@massu/coreitself for 1.5.0; separate REGISTRY-VERIFIED package publish is a follow-on.
v1.4.0
2026-05-07Promotes the 1.4.0-soak.0 build (in soak since 2026-05-02) to latest. Soak-check verdict on 2026-05-07 09:00 PDT: PASS (samples=188, rss_p99=290 MB / budget 700, cpu_load=0.044 / budget 50, alive_pct=100, errors=0, slope=-11.35 MB/hr).
Added
- `massu watch` daemon (Plan 3a) — long-running file-watcher that re-runs detection on stack-relevant changes and auto-installs new variant templates. Subcommands:
massu watch [--once] [--quiescence-ms N]andmassu refresh-log. Supervises viaclaude-bgorlaunchd. Self-defense: refuses to start if the watch surface exceeds the configuredwatch.max_watched_filescap and the user has not opted in viawatch.paths_full_root_opt_in. Quiescence detector uses tick-gap heuristic + lockfile-mid-op detection + git-mid-rebase detection to avoid storming during interactive operations. New config block:watch: { scope, debounce_ms, storm_window, max_watched_files, paths_full_root_opt_in }. - AST adapter framework (Plan 3b Phase 1) — Tree-sitter-based per-language adapters under
packages/core/src/detect/adapters/. 4 first-party adapters ship:python-fastapi,python-django,nextjs-trpc,swift-swiftui. Adapter contract types indetect/adapters/types.ts. Per-field confidence levels (high/medium/low/none) — a single weak field never poisons stronger fields. Grammar SHA-256 manifest is hardcoded; mismatch →GrammarSHAMismatchErrorwith no silent fallback. Atomic cache writes under~/.massu/wasm-cache/with LRU eviction (closes Phase 3.5 finding F-011). - Optional LSP enrichment layer (Plan 3b Phase 4) — TypeScript-language-server / Python pyright integration for symbol-precise enrichment beyond Tree-sitter. Stays disabled unless
lsp.enabled: true. Hard RSS watchdog on LSP spawn (closes Phase 3.5 finding F-015). SUID-detection refuses to spawn if the LSP binary is setuid (closes F-014). - Codebase-aware command templates (Plan #2) — slash-command scaffolds installed by
npx massu init/config refreshare now substituted against the consumer'smassu.config.yamlAND a per-languagedetected:block sampled from existing source files. Templating engine (template-engine.ts) is mustache-style{{var}}and{{var | default("…")}}— string-substitution only. TPL-SEC-01..07 adversarial tests verify zeroeval/Function/vm/exec/spawn, no prototype walk, no recursive expansion, no template-literal injection. 6 new sub-framework templates:massu-scaffold-router.python-{fastapi,django}.md,massu-deploy.python-{launchd,systemd,docker,fly}.md.runDetection({skipIntrospect})flag preserves session-start hook's 5s budget. - Public-repo leak-defense infrastructure — 6-layer architecture preventing private-content leaks to the public massu npm/GitHub repo:
- Plan 3c-prework Phase A + C —
docs/**/*added topackages/corefiles[](so security/authoring docs ship to npm);tar@^7.4.3andtweetnacl@^1.0.3deps added (for Phase 5 signed-allowlist registry); 5 placeholder workspace stubs for@massu/adapter-{rails,phoenix,aspnet,spring,go-chi}(Phase 7 fills implementation); targeted.gitignorepatterns replace blanket*.pemso the registry pubkey can ship.
Fixed
- Path-aware introspect matching for routers/views — adapter signal logic now considers the file's PATH (e.g.,
apps/*/routers/) in addition to its content shape. Previously, FastAPI router signals could fire on any file containingfrom fastapi import APIRouterregardless of project layout. - Plan 3a hotfix 2026-05-02 — watcher self-defense + measurable RSS/CPU budgets. The 2026-05-02 hotfix added the watch-surface preflight cap, exclusion of high-churn directories (
**/.next/**,**/coverage/**,**/logs/**,**/data/**, editor temp files), and switched the verdict from spot-percentile CPU to integral cpu-load fraction (catches the 30-100% sustained CPU misconfig pattern that produced false-PASS on a multi-runtime monorepo).
Security
- Phase 3.5 deep security audit — 20 findings, 0 unfixed. Adapter-loading code path audited for prototype pollution, SSRF, RCE, and resource exhaustion. Adversarial test suite (
__tests__/security/) verifies the LSP IPC layer, Tree-sitter loader, and adapter contract are not exploitable. Audit doc retained internally. - Public-repo historical leak scrub — 17 historical leak markers removed/anonymized: internal-doc JSDoc cross-references (5), user-machine hardcoded paths (2), incident-doc CHANGELOG citations (3), customer-name design comments (11), test fixture renames (2 directories).
Tests
- +248 tests since 1.2.1: watcher daemon + quiescence (54), AST adapter framework + 4 adapters (62), LSP enrichment (14), codebase-aware templates (50 templating + 13 introspector + 12 variant matrix), security adversarial (35), watcher session-start banner (5), refresh-log (3). Total: 1729 passing (was 1373 on 1.2.1, 1481 in interim).
Design notes
- This release intentionally bundles 3a + 3b + Plan #2 codebase-aware + leak-defense infra in one minor bump. The alternative (three separate minors) was rejected because 3a + 3b share a deep security audit (Phase 3.5) and splitting them would compress the audit window for downstream consumers.
v1.3.0
2026-04-26Stack-aware command templates with per-stack variant resolution. Local-edit protection via 3-hash manifest. (Retroactive entry: not previously logged in CHANGELOG; corresponds to npm-published version 1.3.0 from 2026-04-26.)
Added
- Stack-aware variant resolution in `install-commands` —
pickVariant(baseName, sourceDir, framework)returns a discriminated{hit, miss, fallback}union. Priority order: primary language → languages-declaration order → top-level passthrough fallback (typescript/javascript/python/swift/rust/go) → unsuffixed default. Variant filenames are filtered at the top level only — subdirectory contents recurse as-is. - Local-edit protection via SHA-256 manifest — manifest at
<claudeDir>/.massu/install-manifest.jsonwith 3-hash compare (source / existing / last-installed) and atomic tempfile+rename writes. NewSyncStats.keptcounter reports preserved edits. First-install heuristic preserves any pre-existing differing file and seeds the manifest with the existing hash. - `massu show-template <command> [--variant <stack>]` subcommand — prints the resolved variant content to stdout for diff-against-upstream workflows. Used in the kept-your-version notice.
- 4 seed variant templates —
massu-scaffold-router.python.md(FastAPI),massu-scaffold-page.swift.md(SwiftUI),massu-deploy.python.md(launchd/systemd/pm2/docker),massu-scaffold-page.mdregenerated as framework-agnostic with embedded multi-stack examples. Pluscommands/README.mddocumenting the variant convention. - +21 tests — VARIANT-01..10, MANIFEST-01..08, SHOW-01..03. Total suite: 1394 passing (was 1373 on 1.2.1).
Changed
config.tsspreads...fwinto the materialized framework sozod.passthrough()blocks (framework.swift,framework.python, …) flow through to consumers. Without this, the iteration-3 passthrough-fallback rule silently never fires in production despite green unit tests.
v1.2.1
2026-04-20@massu/core init --ci no longer rolls back on fresh monorepo installs (turbo, nx, pnpm workspaces, lerna, rush, generic). Fixes the 2026-04-20 monorepo paths.source rollback regression.
Fixed
- `@massu/core init --ci` on monorepos:
paths.sourceis now resolved from the repo's monorepo layout when the primary language has no root-level source directory. Previously, a fresh turbo +apps/web/page.tsxrepo (notypescriptdep, notsconfig.json, no rootsrc/) would generatepaths.source: 'src', fail post-write validation, and roll back withpaths.source 'src' does not exist on disk. The fix extendsbuildConfigFromDetection(packages/core/src/commands/init.ts) and the v1→v2 migration path (packages/core/src/detect/migrate.ts) with a monorepo-aware fallback: when the dominant source dir is empty ANDdetection.monorepo.type !== 'single',paths.sourceis set to the common top-level parent of every workspace package (apps,packages,libs, etc.), or'.'when packages span multiple parents. - Source-dir detection for plain-JS monorepos:
EXTENSIONS.javascript(packages/core/src/detect/source-dir-detector.ts) is now extended with['ts', 'tsx']via a newfallbackTsForJsflag when the repo has ajavascriptmanifest but NOtypescriptmanifest. This surfaces.tsxfiles underapps/*in plain-JS turbo repos (e.g.next+reactin apackage.jsonwithouttypescript+ notsconfig.json), which the prior strict javascript glob skipped entirely.
Added
- `paths.monorepo_roots: string[]` — new optional config field emitted by
init --ciandconfig refresh/upgradewhenevermonorepo.type !== 'single'and workspace packages exist. Lists every distinct top-level workspace parent (e.g.['apps', 'libs']for an nx repo with both). Additive, schema-compatible with v1 configs; downstream tools may consume it for monorepo-aware scanning. - Post-write validation extended (`validateWrittenConfig`) — new check that each entry in
paths.monorepo_rootsexists on disk. Parity with the existingpaths.sourceexistence check; rolls back on mismatch with messagepaths.monorepo_roots '<x>' does not exist on disk. - 3 new fresh-install fixtures (
packages/core/src/__tests__/fixtures/fresh-install/):nx-monorepo(apps + libs via yarn workspaces),pnpm-workspaces(pnpm + packages/*),rush-monorepo(rush + apps/). Covers every major JS monorepo shape forinit --ciregression gating. - `.github/workflows/fresh-install-matrix.yml` — new CI matrix (6 fixtures × node:20) that runs
init --ciend-to-end on every push/PR to main. Gates merges on: exit 0,schema_version: 2emitted,paths.sourceexisting on disk, andpaths.monorepo_rootsemitted for every monorepo shape. PR runs use the local build; main-branch runs additionally verify against the last published@massu/core@1as drift protection.
Deprecated
- Legacy `generateConfig` in `commands/init.ts` — emits a console deprecation warning on invocation. It hardcodes
paths.source = 'src'and cannot resolve monorepo layouts. UsebuildConfigFromDetection(runDetection(root))instead. Kept only for the legacycli.test.tssmoke tests.
Tests
- +16 tests covering the P1 detector changes (fallbackTsForJs flag, runDetection wiring, monorepo-aware
paths.source, monorepo_roots emission), the P2 validator extension, and the P4.8 security pre-screen (IGNORE_PATTERNS + symlink-safety regressions). - Total test count: 1373 passing (was 1357 on 1.2.0).
Design notes
paths.sourceremains astring(not an array). Every live consumer inpackages/core/src/reads it as a string (config.ts:590,sentinel-scanner.ts:223,domains.ts:106/157,python/coupling-detector.ts:23,trpc-index.ts:115) — widening to an array would break all six sites silently. Monorepo multi-source precision is instead available viaframework.languages.<lang>.source_dirs(existing) and the newpaths.monorepo_roots(optional).- JS-to-TS language reclassification (when the only manifest is a plain-JS
package.jsonbut.tsxfiles are present) is NOT done in 1.2.1 — it's a classification change with its own blast radius (framework-detector rules, VR commands, schema version). Tracked as P6-001 for a future release.
v1.2.0
2026-04-20config upgrade and config refresh no longer silently drop user-authored config data. Fixes the 2026-04-19 HIGH-severity config-data-loss regression.
Fixed
- `massu config upgrade` — top-level keys not in the built-in preservation list are now passed through verbatim via the new
copyUnknownKeyshelper inpackages/core/src/detect/passthrough.ts. Nested subkeys inside theframework,paths,project, andpythonblocks are now passed through viapreserveNestedSubkeyswhen the migrator rebuilds those blocks. - `massu config refresh` —
mergeRefreshrewritten to preserve: (1) top-level user keys not handled by the detector, (2) user subkeys insideframework/paths/project, (3)toolPrefix(previously silently reset to'massu'), (4) user-setproject.root(previously silently reset to'auto'), (5) user-authored aliases insidepaths.aliases(2-level-nested — previously overwritten by detector's hardcoded{'@': <source>}), (6) customverification.<lang>sections and user command overrides on shared languages (2-level-nested — previously silently replaced by detector-only verification output).
Impact — what was happening on 1.1.0
- Top-level: on
@massu/core@1.1.0, the keys PRESERVED duringconfig upgradewere exactly this set:{rules, domains, canonical_paths, verification_types, detection, accessScopes, knownMismatches, dbAccessPattern, analytics, governance, security, team, regression, cloud, conventions, autoLearning}— plusschema_version,project,framework,paths,toolPrefix,verification, andpythonvia dedicated code paths. ANY OTHER top-level key in your v1 config was DROPPED — if your config had something likeservices,workflow,north_stars, or any other custom top-level section, it is gone from the upgraded file. Restore fromgit log. - Nested: on
@massu/core@1.1.0, subkeys PRESERVED inside each rebuilt block were exactly:framework→{type, router, orm, ui, primary, languages};paths→{source, aliases, routers, routerRoot, pages, middleware, schema, components, hooks};project→{name, root}. ANY OTHER subkey inside those blocks was DROPPED — for example,project.description, customframework.<lang>blocks, or custompaths.<name>entries. Restore fromgit log.
Restoration instructions
Added
- `packages/core/src/detect/passthrough.ts` — new module exporting
copyUnknownKeys(source, target, handledKeys)andpreserveNestedSubkeys(sourceBlock, targetBlock). Target-wins semantics documented in JSDoc. Shared bymigrate.tsandconfig-refresh.tsto prevent the two-allow-lists-drifting-apart class of bug that caused this incident. - 26 new tests covering top-level passthrough, nested passthrough across
framework/paths/project/python, refresh-sidemergeRefreshpreservation (toolPrefix,project.root, nested subkeys, 2-level-nestedpaths.aliasesandverification.<lang>user overrides), loose-v1-input coercion (non-object framework/paths/project/python), a sentinel-injection property-style regression guard that fails if a future rebuild block omits passthrough, and a new regression fixture that reproduces the exact 12-top-level-key shape the incident dropped data from. Total suite: 1357 tests passing.
Shipped
- Merged via PR #1 (commit
94e6723; merge commitbfa8686). Published to npm on 2026-04-20 withgitHead: bfa8686. P5-007 post-publish regression against 5 downstream consumer repos: zero key removals at any depth.
v1.1.0
2026-04-19massu config CLI surface + drift detection runtime. Unblocks the config-migration workflow for downstream repos. Additive only — no breaking changes.
Added
- `massu config <sub>` — new top-level command tree dispatched from
packages/core/src/cli.ts. Five subcommands: - Session-start drift banner —
packages/core/src/hooks/session-start.tsnow emits a plain-text banner whenconfig.detection.fingerprintdisagrees with the current detected fingerprint. Silent on v1 configs (no stored fingerprint = no banner). Best-effort; never throws. - `detection.fingerprint` auto-stamp —
buildConfigFromDetection,config refresh, andconfig upgradeall stamp a deterministic SHA-256 stack fingerprint into the generated config. - +35 tests covering refresh (
config-refresh.test.ts, 11 cases), upgrade CLI (config-upgrade-cli.test.ts, 8 cases), check-drift (config-check-drift.test.ts, 5 cases), CLI dispatcher (cli-dispatcher.test.ts, 5 cases), session-start drift banner (session-start-drift.test.ts, 3 cases). Total suite: 1331 tests passing.
Changed
- Legacy CLI entry points (
massu init,massu doctor,massu install-hooks,massu install-commands,massu validate-config) are preserved verbatim.massu config {validate,doctor}are aliases that route to the same handlers. - Pattern scanner allowlist extended to include
commands/config-{refresh,upgrade,check-drift}.ts— same rationale as existinginit.ts/doctor.tsexemptions (raw YAML parse is required becausegetConfig()caches againstprocess.cwd()and Zod-rejects pre-migration v1 configs). packages/core/dist/hooks/session-start.jsbundle size: ~80KB → ~306KB (bundlesfast-glob+smol-tomlfor runtime detection). Still compiles in <30ms via esbuild.
Fixed
docs/plans/2026-04-19-autodetect-zero-config.mdPhase 4 and Phase 5 are no longer deferred. The sibling plandocs/plans/2026-04-19-config-migration.mdcan now proceed.
v1.0.0
2026-04-19Auto-detect on install; zero manual config; migration via migrateV1ToV2().
Breaking
schema_version: 2is now the default for every config generated bymassu init. Configs withoutschema_versionare interpreted asschema_version: 1and continue to load unchanged — no code changes required for existing projects, but new fields (framework.languages,verification,verification_types,detection.rules) only apply to v2 configs.framework.typeaccepts a new value"multi"for multi-runtime projects, withframework.primaryselecting the dominant language. Single-language projects still useframework.type: typescript | python | rust | ...exactly as before.- Legacy top-level
framework.router / .orm / .uikeys are mirrored fromframework.languages.<primary>on v2 configs. Readers that only consult the top-level keys keep working.
Added
- Auto-detection engine (
packages/core/src/detect/) — pure filesystem introspection across 8 languages (TypeScript, JavaScript, Python, Rust, Swift, Go, Java, Ruby), 9 manifest formats, and ~60 framework/ORM/test-framework signals. No network, no child processes, no database writes. - `massu init` rewrite — detection-driven, zero manual YAML editing. Generates
schema_version: 2configs. New flags:--ci(non-interactive),--force(overwrite without prompt),--template <name>. - 7 project templates —
python-fastapi,python-django,ts-nextjs,ts-nestjs,rust-actix,swift-ios,multi-runtime. Greenfield mode skips detection. - `migrateV1ToV2(v1Config, detectionResult)` pure function (
packages/core/src/detect/migrate.ts) — lifts existing v1 configs to v2 while preserving every user override (rules, domains, canonical_paths, accessScopes, analytics, governance, security, team, conventions, etc.). - `computeFingerprint` and `detectDrift` (
packages/core/src/detect/drift.ts) — SHA-256 fingerprint over normalizedDetectionResultplus a four-axis drift report (language set, per-language framework, manifest set, workspace set). - `verification` config block — per-language overrides for VR-TEST, VR-TYPE, VR-BUILD, VR-SYNTAX, VR-LINT.
- `verification_types` config block — register custom VR-* types (e.g.,
VR-IBKR-CONTRACT,VR-POLICY) with descriptions. - `detection.rules` config block — add project-specific framework signals or replace built-ins entirely with
detection.disable_builtin: true. - Monorepo detection — identifies
turbo,nx,lerna,pnpm,yarn,bazel,generic,single. Nested workspace support (e.g., turbo outer + pnpm inner). - Atomic config writes —
.tmpfile +renameSync; partial writes never persist. File permissions preserved on overwrite. - Post-init validation — every written config is re-read through Zod and filesystem-checked; invalid configs are rolled back.
- 61 new tests covering 11 fixture repos, 5 stale-config migration snapshots, and 6 drift scenarios.
- Documentation —
docs/auto-detection.mdx,docs/migration/v1-to-v2.mdx,docs/vr-types.mdx,docs/ci-drift-check.mdx,docs/error-handling.mdx.
Changed
massu initoutput now reports detected languages, frameworks, source dirs, and monorepo type explicitly rather than producing a generic TypeScript template.framework.typeshape extended to support multi-runtime viatype: multi+primary: <language>+languages: { <language>: { ... } }.- Pattern scanner allowlist extended to include
detect/monorepo-detector.ts(readspnpm-workspace.yaml, notmassu.config.yaml) andcommands/init.ts(validates the YAML it just wrote).
Fixed
- Stale configs where the declared language didn't match repo reality (multi-runtime stale-config regressions) now fail post-init validation and are rolled back instead of being silently written.
--cimode no longer silently overwrites existing configs — throws"massu init: config exists in --ci mode (no overwrite)". Use--forceto opt in.- Interactive overwrite prompt now defaults to NO (previously defaulted to YES on some terminals).
- Symlink-escape defense: detection filters out any file whose
realpathresolves outsideprojectRoot. - Secret-file exclusion:
.env,.env.*,*.pem,*.key,.aws/**,.ssh/**,credentials.json,*.p12,*.pfxare explicitly excluded from source-dir globbing.
Security
- New detection layer is network-free and database-free by contract. Verified by
grep -rn "better-sqlite3|getMemoryDb|getDataDb|child_process|spawn|execSync|fetch\(" packages/core/src/detect/ → 0 matches. - Atomic writes prevent partial config corruption on write failure.
- CI generalization scanner now runs on every PR to catch hardcoded project-specific data.
v0.3.0
2026-02-25Added
- Tier enforcement — Free (14 tools), Pro (63+), Team, Enterprise tiers with license gating
- License validation —
license.tsmodule withgetCurrentTier(),getToolTier(),isToolAllowed(), andannotateToolDefinitions() - `massu_license_status` tool — Check current tier, available tools, and upgrade path from any session
- Conventions config —
conventionssection inmassu.config.yamlfor project-specific coding rules - Generalization scanner —
scripts/massu-generalization-scanner.shverifies no hardcoded project-specific data in shipped files
Changed
- Tool descriptions now include tier labels (e.g., "[Pro]") when not on the free tier
- README and CLAUDE.public.md updated with tier information and tool counts
- Package description updated to mention tiered tooling
Full commit history available on GitHub.