Skip to content

v0.21.3

Choose a tag to compare

@hyoshi hyoshi released this 24 Sep 15:48
a991f85

Fixed

  • A not_collected record no longer demands the account id the failed
    collection could not resolve
    (#794). mureo_state_platform_not_collected_set
    required account_id with minLength: 1, while the bulk Write path the
    _mureo-shared skill documents has always spelled an unknown id "". So
    the right answer depended on which write path the agent took — and on the
    MCP path, for the one failure whose CAUSE is that no account could be
    resolved (Google Ads authenticated with zero accessible accounts, no
    customer id configured), there was no legal value to send. The agent
    invented one: account_id: "unknown" on both Google Ads and Meta Ads,
    which the reports view then read as ONE ad account held under two keys and
    withheld the client total over (#793). That tool now accepts "" / null
    / whitespace, alone among the platform writers — a platform whose figures
    you DID collect knows which account they came from, so
    mureo_state_platform_metrics_set, _daily_set and
    mureo_state_set_conversion_events still require a real id. An unknown id
    is stored as the empty id mureo reads as unknown everywhere (it joins
    nothing, including another unknown id, so two platforms that both failed
    this way stay two entries), and it is never written over an id the entry
    already holds
    : a platform that failed today still describes the ad
    account it described yesterday, and blanking it would break that entry's
    per-account conversion override and its place in the duplicate join.
    set_platform_not_collected otherwise behaves exactly as before — the
    campaigns, rollups and conversion override are carried over, attempted_at
    is server-stamped, and last_synced_at is not re-stamped. daily-check
    step 13 and the _mureo-strategy / _mureo-shared skills now say what to
    pass, and say not to invent a placeholder.

  • A placeholder account id no longer joins two platform entries into one
    ad account
    (#793). #794 stops new placeholders from being written; this
    covers the documents that already carry one. The account join folded only
    a missing, empty or whitespace-only account_id to unknown, so two
    entries an agent had stamped "unknown" — Google Ads and Meta Ads, both
    recorded as not collected — were read as one ad account under two keys.
    The Reports client list withheld that client's totals, and the repair hint
    on screen (mureo repair platform-key --key <key> --drop-duplicate) told
    the operator to delete a platform entry that was never a duplicate. These
    ids are now read as unknown, compared case-insensitively after trimming
    and after the act_ prefix is stripped (act_unknown included):
    unknown, none, null, n/a, undefined, tbd. Nothing else is:
    na, - and every other value stay ids, because a plugin platform may
    issue a short or punctuated id and folding a real one would silently join
    or split genuinely different accounts. There is no migration: a stored
    placeholder stays in the file and now reads the same as "" — it joins
    no other entry (so no duplicate finding, no withheld total, no
    --drop-duplicate hint, and --drop-duplicate finds no duplicate to
    remove), it never matches a per-account conversion override, and an
    unrecognised key holding it says its ad account is unknown. Writing one
    through mureo_state_platform_not_collected_set stores "", and never
    over an id the entry already holds. The four writers that carry collected
    figures — mureo_state_platform_metrics_set,
    mureo_state_upsert_campaign, mureo_state_platform_daily_set and
    mureo_state_set_conversion_events — now refuse a placeholder outright,
    naming it and pointing to mureo_state_platform_not_collected_set,
    instead of blanking a known entry's id or storing a conversion override
    that can never match. One write-guard shift follows from the fold: an
    entry holding a placeholder is an unknown entry, so stamping a real id
    onto it is now allowed even when another key already holds that id
    (before, it was refused as a re-point) — the same repair path an empty
    id takes, which makes the duplicate visible to the join and to
    mureo repair platform-key. "" remains the spelling to write.

  • A report card said when it was last written, and an operator read it as
    how old the numbers are
    (#798). The three KPI cells and the "Updated N
    hours ago" line under them both come from one rollup,
    platforms[<key>].periods[<window>], and fetched_at is stamped at WRITE
    time. /daily-check's rollup step is best-effort and conditional — it
    writes only the windows a run already gathered, must not fire an extra API
    call to fill one, and in incremental mode may hold no YESTERDAY window at
    all — so a run could persist its report and its display, report that it
    had
    , and leave the card untouched. Nothing on the card contradicted it:
    the label read "Updated 14 hours ago" over figures covering the day before
    yesterday, and a YESTERDAY rollup is not even called stale until two days
    after it was written. Nothing stored anywhere said which calendar dates a
    rollup covered — period is the window token and fetched_at is the write
    time, and neither answers it. A rollup may now carry period_end, the last
    calendar date its figures cover (YYYY-MM-DD), beside fetched_at;
    staleness is judged on THAT when it is present and parseable, and on the
    write time only when it is not. Both are reported either way — they are
    different facts, and this is the defect that follows from treating them as
    one. The server does not derive it: "yesterday" is yesterday in the ad
    account's own timezone, which mureo does not reliably know, and a coverage
    date silently off by one is worse than none, so the writer supplies it —
    /daily-check step 13 and the mureo_state_platform_metrics_set schema now
    say so. An unparseable value is kept verbatim and decides nothing, exactly
    as an unparseable fetched_at already is. The coverage date is judged
    against the UTC calendar date, so an account west of UTC can see its figures
    called stale late in its own day, before its grace day has fully run out.

  • On screen (#798): where a rollup states its coverage, the client card's
    freshness line and the per-platform rows now name the day the figures run to
    beside the update time — "To 2026-09-22, updated 14h ago" ("Stale
    2026-09-22, updated 14h ago"
    when mureo will not vouch for them), and
    {date}まで・{ago}更新 in Japanese. Where no coverage is stated the line
    reads exactly as it did. The line is now clipped rather than wrapped: it
    carries two facts in a card track ~204px wide, and wrapping it makes every
    card in the grid taller; the full line is the element's tooltip. The card's
    stale chip, the restated figures under its cells, the alert row above the
    grid and the detail view's withheld-figures note now name that same covered
    date — "Figures stop at 2026-09-20", "Figures to 2026-09-20, collected
    14h ago: …"
    — whenever the server judged the figures on it, instead of
    quoting the write time beside a line that says the opposite.

  • A skipped rollup step is no longer silent (#798). /daily-check step 13
    must now name which windows it persisted and which it did not, per platform
    — windows_not_persisted_for_<platform>, the notation step 4 already uses
    for analytics_not_available_for_<platform>. A run that wrote the report
    and the display and said so read as a complete run whether or not the cards
    moved, which is what made this easy to miss for days at a time.

Removed

  • Unused prompt-building helpers from an earlier LLM design (post-v0.21.2
    audit). mureo/google_ads/_intent_classifier.py and
    mureo/google_ads/_rsa_insights.py are gone, as is MessageMatchEvaluator
    (with MessageMatchResult) from mureo/google_ads/_message_match.py, which
    now holds only LPScreenshotter. The always-empty _suggest_by_intent /
    _get_strategic_context_for_intent stubs in
    mureo/google_ads/_analysis_search_terms.py and the branch of
    suggest_negative_keywords that forwarded to them are removed; its output
    is unchanged (the branch never added anything).
  • Private helpers nothing called (post-v0.21.2 audit):
    _resolve_budget_amount_micros (mureo/google_ads/client.py),
    get_search_console_client (mureo/mcp/_client_factory.py),
    parse_form_body (mureo/web/_helpers.py), and KNOWN_PARTS /
    SetupParts.all_installed (mureo/web/setup_state.py).
  • Deprecated _load_existing / _atomic_write_json aliases in
    mureo/providers/config_writer.py (post-v0.21.2 audit). Nothing imported
    them since the atomic-JSON helpers moved to mureo/core/atomic_json.py
    (#500); import load_existing_json / atomic_write_json from there.
    ConfigWriteError is still importable from config_writer.
  • google_ads_search_terms_review no longer returns the always-empty
    intent_analysis field
    (post-v0.21.2 audit), whose note claimed an
    analysis mureo never runs. _apply_intent_analysis and the
    use_intent_analysis parameter of review_search_terms /
    suggest_negative_keywords (mureo/google_ads/_analysis_search_terms.py)
    are removed; the MCP tools never exposed that parameter.
  • Functions and methods that only the test suite called. Nothing in
    mureo, its skills or its extensions reached them:
    ByodMetaAdsClient.get_metrics_daily / get_ad_set_insights_daily /
    get_ad_insights_daily / get_creatives (mureo/byod/_meta_client.py),
    byod_active_platforms (mureo/byod/runtime.py),
    is_reversible_native_tool (mureo/mcp/native_reversal.py),
    ConfigureSession.get_oauth_status_all (mureo/web/session.py),
    removable_credential_sections (mureo/web/env_var_writer.py),
    _shipped_skill_names (mureo/web/status_collector.py),
    ResolvedPeriod.is_preset (mureo/meta_ads/_period.py) and
    _reset_update_cache (mureo/web/version_check.py; its tests now reset
    the module cache themselves).
  • Configure-UI strings and styles no screen renders any more (both
    locales for the i18n keys): common.back, common.next, common.skip
    and auth_wizard.search_console.step1_title, which nothing has rendered
    since #99; dashboard.reports_hint and the .dashboard-reports-hint
    rule, orphaned by #309; dashboard.reports_client_label, orphaned by
    #308; and dashboard.reports_latest_title and the
    .report-latest-period rule, orphaned by #695. No rendered element used
    them, so nothing on screen changes.
  • The configure UI's cleanup of pre-skills slash commands (post-v0.21.2
    audit). Workflow commands became skills in #77 (May 2026); the landing
    notice that offered to delete the old ~/.claude/commands/*.md files, its
    POST /api/legacy/cleanup route, the legacy_commands_present field of
    /api/status and the legacy_commands step of "Clear all" are removed,
    as are HostPaths.commands_dir and the commands_path argument of
    ConfigureWizard / run_configure_wizard, which only fed that cleanup.
    The notice only appeared while those files existed, so a current install
    sees no change. If you upgraded from a pre-May-2026 install and still have
    them, delete them from ~/.claude/commands/ by hand: onboard.md,
    daily-check.md, rescue.md, search-term-cleanup.md,
    creative-refresh.md, budget-rebalance.md, competitive-scan.md,
    goal-review.md, weekly-report.md, sync-state.md, learn.md.
  • The Codex hooks.json top-level PreToolUse migration (post-v0.21.2
    audit). mureo setup codex and the credential-guard remove path now read
    and write only the nested hooks.PreToolUse list Codex loads; they no
    longer move mureo's entries out of the top-level list mureo wrote before
    #393. A top-level list left by such a version is not touched (Codex never
    loads it, so it is inert) and can be deleted by hand. mureo upgrade
    still installs the current guard in the nested list when it finds
    mureo's tagged entries only in that top-level list and no nested list
    exists yet; once the guard is removed, an upgrade does not bring it back.