Skip to content

Merge dev → main (automated) - #2124

Merged
SorraTheOrc merged 88 commits into
mainfrom
release/dev-to-main-20261023210955
Oct 23, 2026
Merged

SorraTheOrc merged 88 commits into
mainfrom
release/dev-to-main-20261023210955

Conversation

@SorraTheOrc

Copy link
Copy Markdown
Member

Automated release created by ship skill.\n\nIncludes CHANGELOG.md with work-item summaries from this release.

pi added 30 commits October 18, 2026 00:34
…rst, WL-0MUYJ8WJG0087VSU)

Add three specs for the grey background on In Review headings:
- In Review heading contains \x1b[48;5; background prefix (it.fails)
- Other headings do NOT contain background escape (it, regression guard)
- Selected In Review heading has both reverse-video and grey bg (it.fails)

All 17 tests green. Implementation child WL-0MVD2OFRK000JA2Q flips
it.fails → it once the grey background lands.
pi added 28 commits October 22, 2026 21:26
…er one

A background `wl sync` could replace a just-persisted audit with an older
copy of the same item's audit, leaving the item's lifecycle (status/stage,
comments) intact while the stored audit showed a stale verdict — e.g. an
item demoted to open/plan_complete while `wl audit-show` reported the old
"Ready to close: Yes".

Root cause: the audit merge was local-precedence only. The local side won
regardless of audit time, so whichever side was stale could shadow the other
indefinitely; and the destructive full-snapshot import re-inserted the merged
snapshot after `clearWorkItems()` cascade-deleted the audit rows, so any
stale snapshot reaching the persist layer silently replaced a newer audit.

- `mergeAuditResults` (src/sync.ts) is now last-write-wins by `auditedAt`:
  the newest audit wins whichever side it comes from, local wins exact ties,
  and unparseable timestamps fall back to local precedence. Sync converges in
  both directions but can never downgrade.
- `SqlitePersistentStore.saveAuditResults` (bulk path used by every sync
  import: pull merge, JSONL refresh, doctor/import bulk loads) is monotonic:
  an incoming audit strictly older than the stored row is skipped. Explicit
  single-item writes (`wl audit-set` -> `saveAuditResult`) stay unrestricted.
- `WorklogDatabase.import` snapshots audits before the cascade-deleting
  `clearWorkItems()`, keeps them when no audit set is supplied, and folds an
  incoming set with the same newest-wins rule instead of blind replacement.
- Docs: DATA_SYNCING.md ("Audit Results: Newest Wins, Never Downgraded") and
  docs/design/incremental-sync.md section 4.3 record the decision (AC2/AC3).

Tests: tests/cli/sync-audit-newest-wins.test.ts (real git + bare remote E2E:
newer local survives an older remote snapshot, newer remote wins over a stale
local, audit-less legacy snapshot leaves the local audit unchanged) and
tests/unit/audit-sync-monotonic.test.ts (merge LWW + bulk/full persist
guards). tests/sync-audit-results.test.ts updated for the new merge semantics.
Full node suite: 109 cli files + 47 unit files green.
A sync can no longer downgrade a held claim. Before persisting a pull the
sync re-reads the store's current rows (cache-bypassing, so a claim written
by another process during the sync window is visible) and refuses any
status/stage downgrade of a claim held within syncClaimHoldTtlHours
(default 24h, 0 disables), preserving the claim's status/stage pair and its
updatedAt/activityAt. Newer, more active transitions, attributed deletes and
expired claims are unaffected. Every refusal is recorded in the sync log and
in --json output (sync.claimHolds), and the sync's store identity is reported
as sync.store.

Root cause: the sync's merged set is built from the pre-fetch snapshot, so a
claim landing in the read->write window (lock age-expiry) is absent from it;
import()/upsertItems() then wrote the stale row back and re-stamped it newer.
Also fixes the stage-ordering gap (plan_complete -> prd_complete resolved by
timestamp).
.worklog/config.yaml is tracked, so every managed worktree checks out a
.worklog/ directory that holds configuration but no store, and the shared
resolver treated it as a valid root: a wl command run inside a worktree bound
to a second store with its own mutation lock and last-synced-ref watermark.

resolveWorklogDir() now honours a worktree .worklog/ only when it is a genuine
store (wl init wrote 'initialized' and/or worklog.db), otherwise binds to the
main checkout's store, and fails with an actionable error naming
--worklog-dir when neither exists. A sync also reports the store it operated
on and flags the second-store condition (sync.store.secondStore).
CLI.md documents the new sync.claimHolds/sync.store JSON fields;
DATA_SYNCING.md documents what a held claim is, what the guard does and does
not do (TTL bound, attributed deletes, newer-more-active transitions), the
syncClaimHoldTtlHours config key, and the worktree store-resolution rule.
…eadings; keep documented per-call timeouts
@SorraTheOrc
SorraTheOrc merged commit a6860d4 into main Oct 23, 2026
1 of 2 checks passed
@SorraTheOrc
SorraTheOrc deleted the release/dev-to-main-20261023210955 branch October 23, 2026 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant