Skip to content

feat(session): redesign the session page around intent, a session summary and drift - #631

Draft
gcko wants to merge 265 commits into
mainfrom
feature/session-redesign
Draft

gcko wants to merge 265 commits into
mainfrom
feature/session-redesign

Conversation

@gcko

@gcko gcko commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

DRAFT. Do not merge to main until the owner signs off.

Redesigns the session page into one column: Intent, What the session has done, Drift, then a collapsed Full activity. The new card shows a deterministic outline of the session on Claude Code and Codex with no model call, and a cited, stored model summary on Claude Code. Each departure is a card saying what happened and why it is drift.

Model calls a reader starts (Analyze drift, goal summaries, session summaries) now run without a consent step. The first call of each kind shows a one-time notice naming what is sent and to whom. Pause model calls stops all of them; Turn off summaries stops summaries only and is disabled while paused. Unasked drift checks stay off by default. The owner's rulings are recorded in docs/design-session-summary.md (DEC-28) and SECURITY.md.

The sample session is replaced by a milestone-length run with its own stored summary and reading, and the tour is rewritten.

What is new

  • session_source, session_outline: an ordered, paged, bounded read of a session with stable fact ids and fingerprints, and the outline built from it.
  • session_summary: the producer (16 KiB windows, immutable blocks, code-assigned bullet kinds, the completion check on the summary's own voice).
  • summary_store: a store of its own that survives Discard everything, with redaction in and out and an aggregate bound.
  • summary_jobs: the multi-part job with its own grant, ledger, per-part receipts, ceilings (37 calls, 768 KiB, 600 s) and rolling cap (200 per 24 hours).
  • Frontend: the layout, the drift and Intent cards, the notice, Pause and Resume, the summary card, and the sample.

How it was checked

  • Measured on four real sessions before the ceilings were set: 49 model calls, 0 wrong citations in 87 sampled bullets, a 45 MiB burndown summarized in about 5 minutes.
  • Every lane was built in its own worktree, tested by Haiku 5.5, and reviewed by Codex (GPT-6.1-Sol), AGY (Gemini 4 Argon) and Claude lenses, with an arbiter that reproduced each finding. A whole-branch pass used seven lenses and a completeness critic (27 confirmed findings fixed, then 14 browser-walk defects). Later changes went through the same lane review. A fresh-eyes usability review of the built page (three personas and two copy reviewers) produced 20 ranked findings, which are fixed except the ones the owner declined.
  • The built page was driven in Chrome against a real isolated backend, and at 400px width through chrome-devtools.

Test plan

  • Full pre-PR suite from AGENTS.md, serially: ruff, ruff format, mypy, validate_plugins.py, version-guard diff, biome, typecheck, test:styles, pnpm test, Storybook build and 312 stories, test:dev, build:check, client fixtures check, dashboard suite (5295), scripts suite (1614)
  • Coverage 89.6% against the 73 threshold
  • claude plugin validate ./compass --strict and agy plugin validate ./compass pass (agy warns that SKILL.md is 112,841 bytes and only the first 24,000 are loaded)
  • No version field touched
  • Commits signed off
  • CI on this branch
  • Owner review of the open decisions listed in the comments below

Decisions the owner made on 2026-10-20

  • The Steer back correction carries each departure's explanation, uses local time, and quotes the agent's words without Compass entry numbers.
  • Unasked checks stay reachable: Claude Code is the default provider, and a visible switch grants Codex permission.
  • The completion check accepts more attributed phrasing, and the prompts ask for lines it keeps.
  • Analyze runs on an idle session, Steer back stays beside the departures, and withheld summary lines are collapsed.

Decisions the owner made on 2026-10-21

  • Editing the goal keeps the reading's original start by default; reading from the edit onwards is an explicit choice (DRC-4923).
  • Discard everything also deletes the session's summary. This reverses the earlier rule that the summary store survives it (DRC-4928).

Known limits

  • One test, test_lifecycle hangup, times out under heavy load and passes alone. A second test, test_default_on_reader_permission_never_authorizes_an_unasked_call, failed once in a three-worker run and did not reproduce in three reruns; the owner had it removed instead of chasing it.
  • The summary check still withholds about 13% of lines in the measured windows (target was 12%).
  • An existing compass-summaries.json from a developer build reads as unavailable until it is deleted.

Implements DRC-4902
Implements DRC-4903
Implements DRC-4904
Implements DRC-4905
Implements DRC-4906
Implements DRC-4907
Implements DRC-4908
Implements DRC-4909
Implements DRC-4910
Implements DRC-4911
Implements DRC-4912
Implements DRC-4913
Implements DRC-4914
Implements DRC-4915

Every path, not only the finished analysis

Nine walks of the built page on real sessions, one per path a reader takes (a finished session, a running one, updating the intent, a later direction, Steer back, Mark as inaccurate, Discard everything, and creating and deleting a summary), are recorded in docs/redesign/. docs/redesign/recommended-changes.md merges them. They are grouped into the milestone Session page: every path a reader takes, not only the finished analysis, and that work lands on this pull request:

Implements DRC-4919
Implements DRC-4920
Implements DRC-4921
Implements DRC-4922
Implements DRC-4923
Implements DRC-4924
Implements DRC-4925
Implements DRC-4926
Implements DRC-4927
Implements DRC-4928
Implements DRC-4929
Implements DRC-4930
Implements DRC-4931
Implements DRC-4932
Implements DRC-4933
Implements DRC-4934
Implements DRC-4935
Implements DRC-4936
Implements DRC-4937
Implements DRC-4938
Implements DRC-4939
Implements DRC-4941

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Coverage

Name                                                               Stmts   Miss Branch BrPart  Cover
----------------------------------------------------------------------------------------------------
compass/skills/compass/agy_hook.py                                    86     15     28      7  78.9%
compass/skills/compass/compass_runtime/__init__.py                     0      0      0      0 100.0%
compass/skills/compass/compass_runtime/aggregate.py                  351      2    118      2  99.1%
compass/skills/compass/compass_runtime/annotations.py               1056     23    422     21  97.0%
compass/skills/compass/compass_runtime/asks.py                       110      0     28      0 100.0%
compass/skills/compass/compass_runtime/claude_data.py                549     49    246     19  91.4%
compass/skills/compass/compass_runtime/cli.py                        284     21     66      6  91.7%
compass/skills/compass/compass_runtime/collectors/__init__.py          0      0      0      0 100.0%
compass/skills/compass/compass_runtime/collectors/antigravity.py     427     41    178     24  87.6%
compass/skills/compass/compass_runtime/collectors/claude.py          321     17    122     15  92.3%
compass/skills/compass/compass_runtime/collectors/codex.py           258     18     96     15  90.7%
compass/skills/compass/compass_runtime/collectors/copilot.py         153      4     52      2  97.1%
compass/skills/compass/compass_runtime/collectors/cursor.py          287     19    108     16  90.6%
compass/skills/compass/compass_runtime/collectors/droid.py            34      3      6      1  90.0%
compass/skills/compass/compass_runtime/collectors/gemini.py           54      7     16      4  84.3%
compass/skills/compass/compass_runtime/collectors/goose.py            95      6     28      4  91.9%
compass/skills/compass/compass_runtime/collectors/opencode.py        124      4     44      4  95.2%
compass/skills/compass/compass_runtime/collectors/pi.py              328     34    152     20  88.8%
compass/skills/compass/compass_runtime/config.py                     365      3     30      2  98.7%
compass/skills/compass/compass_runtime/copied_corrections.py         330     28     98     20  88.8%
compass/skills/compass/compass_runtime/correction.py                 337      6    112      4  97.8%
compass/skills/compass/compass_runtime/deliveries.py                 128     12     30      7  88.0%
compass/skills/compass/compass_runtime/departures.py                 135      6     30      6  92.7%
compass/skills/compass/compass_runtime/diagnostics.py                 89      3     26      3  94.8%
compass/skills/compass/compass_runtime/dismissals.py                 111      3     32      3  95.8%
compass/skills/compass/compass_runtime/ends.py                        86      5     18      0  95.2%
compass/skills/compass/compass_runtime/entry_numbers.py              121      5     38      5  93.7%
compass/skills/compass/compass_runtime/events.py                     230      1     86      3  98.7%
compass/skills/compass/compass_runtime/focus.py                       72      2     20      2  95.7%
compass/skills/compass/compass_runtime/frontend_dev.py               142      8     46      9  91.0%
compass/skills/compass/compass_runtime/git_status.py                  59      1     22      2  96.3%
compass/skills/compass/compass_runtime/goal_find.py                  136      4     46      4  95.6%
compass/skills/compass/compass_runtime/history.py                    236      9     60      7  94.6%
compass/skills/compass/compass_runtime/http_api.py                  1836    199    588     60  89.0%
compass/skills/compass/compass_runtime/interaction_prototype.py      709    219    198     38  65.9%
compass/skills/compass/compass_runtime/io.py                         301      8     72      6  96.2%
compass/skills/compass/compass_runtime/irreversible.py                39      0      6      0 100.0%
compass/skills/compass/compass_runtime/levels.py                     315      4    102      5  97.8%
compass/skills/compass/compass_runtime/lifecycle.py                  451     53    160     20  86.7%
compass/skills/compass/compass_runtime/live_estimate.py              282     18     84     14  91.3%
compass/skills/compass/compass_runtime/live_lease.py                 250      6     82      8  95.8%
compass/skills/compass/compass_runtime/live_monitor.py               389     20    122     18  92.6%
compass/skills/compass/compass_runtime/notifications.py              264     11     92      6  95.2%
compass/skills/compass/compass_runtime/observation.py                412      5    122      2  98.7%
compass/skills/compass/compass_runtime/observer.py                   725     36    276     22  94.2%
compass/skills/compass/compass_runtime/probe.py                       44      0     18      1  98.4%
compass/skills/compass/compass_runtime/project_context.py           4181    225   1744    159  92.9%
compass/skills/compass/compass_runtime/quota.py                      392      7    144      6  97.2%
compass/skills/compass/compass_runtime/reach.py                       78      6     30      4  90.7%
compass/skills/compass/compass_runtime/reading.py                   1803     24    640     28  97.9%
compass/skills/compass/compass_runtime/reading_jobs.py               223     14     52      4  93.5%
compass/skills/compass/compass_runtime/reading_policy.py             480     24    154     20  93.1%
compass/skills/compass/compass_runtime/reading_route.py              299     12     96      4  95.9%
compass/skills/compass/compass_runtime/record_pages.py               223     11     72     10  92.9%
compass/skills/compass/compass_runtime/records.py                    404      8    166     11  96.7%
compass/skills/compass/compass_runtime/semantic_history.py           332     12    126     15  94.1%
compass/skills/compass/compass_runtime/session_outline.py            142      7     42      5  93.5%
compass/skills/compass/compass_runtime/session_source.py             512     19    142     17  94.5%
compass/skills/compass/compass_runtime/session_summary.py            895     19    282     19  96.8%
compass/skills/compass/compass_runtime/sessions.py                   252      5    100      5  97.2%
compass/skills/compass/compass_runtime/snapshot.py                    43      0      6      0 100.0%
compass/skills/compass/compass_runtime/spacedock.py                  516     50    264     26  90.0%
compass/skills/compass/compass_runtime/state.py                       95      0      2      0 100.0%
compass/skills/compass/compass_runtime/stream.py                      57      0      8      0 100.0%
compass/skills/compass/compass_runtime/summary_jobs.py               328     36     92     21  85.5%
compass/skills/compass/compass_runtime/summary_store.py              295     23     82      9  91.5%
compass/skills/compass/compass_runtime/supervise.py                  431     95    126     10  78.3%
compass/skills/compass/compass_runtime/transcripts.py                673     42    328     29  92.9%
compass/skills/compass/compass_runtime/tripwires.py                  233     11     94     11  93.3%
compass/skills/compass/compass_runtime/turns.py                      209     11    104     12  92.7%
compass/skills/compass/compass_runtime/unasked.py                    177      6     50      5  95.2%
compass/skills/compass/compass_runtime/web/__init__.py                 0      0      0      0 100.0%
compass/skills/compass/compass_runtime/web/page.py                   114      8     42      6  91.0%
compass/skills/compass/event_hook.py                                 218      6     92      4  96.8%
compass/skills/compass/mcp_server.py                                 382     23    112     14  92.1%
compass/skills/compass/notify_hook.py                                 49     15      6      1  67.3%
compass/skills/compass/server.py                                       3      0      2      1  80.0%
compass/skills/compass/statusline_hook.py                            136     14     46      8  86.8%
scripts/abstention_ledger.py                                         440     42    148     18  89.8%
scripts/analyze_campaign.py                                          776     64    290     59  88.5%
scripts/bench_collect.py                                             214     13     54      6  92.9%
scripts/bench_event_latency.py                                        67     21     14      1  67.9%
scripts/build_sample_fixture.py                                      186     62     68      8  66.9%
scripts/bump_version.py                                               60     12     24      5  77.4%
scripts/capture_focus_raise.py                                       729    131    236     16  79.4%
scripts/capture_hook.py                                              293     30     90     11  88.8%
scripts/capture_team_registry.py                                     172     33     48      6  80.5%
scripts/capture_terminal_identity.py                                 347    108    104     14  66.3%
scripts/claude_route_verification.py                                 334     25     38      5  90.9%
scripts/cut_sample_session.py                                         49     12     12      1  78.7%
scripts/derive_prompt_shapes.py                                      210     16     88     14  89.3%
scripts/drift_closure_grading.py                                     400     14    204     14  95.4%
scripts/drift_closure_operator.py                                    329     31    126     33  85.9%
scripts/drift_replay.py                                             1643    230    600    106  83.1%
scripts/drift_study.py                                               239     23     98     16  88.4%
scripts/drift_study_controls.py                                      155     11     42      9  89.8%
scripts/frontend_inventory.py                                        143    143     66      0   0.0%
scripts/frozen_page.py                                                13      0      4      0 100.0%
scripts/levels_cases.py                                              778     99    254     30  85.4%
scripts/live_analyze_campaign.py                                      59      4     14      3  90.4%
scripts/mark_abstention.py                                          1272    166    462     64  85.0%
scripts/ordinary_turn_driver.py                                      475     33    156     30  90.0%
scripts/redact_session.py                                            173     25     50      7  83.9%
scripts/regen_client_fixtures.py                                     565     56     68      7  86.9%
scripts/release_transition.py                                        344     73    114      5  75.5%
scripts/run_tests.py                                                 187     37     60      5  78.1%
scripts/sanitize_session.py                                          370     35    122      9  89.4%
scripts/score_abstention.py                                         1527    110    554     58  91.8%
scripts/store_paths.py                                                 7      0      0      0 100.0%
scripts/update_homebrew.py                                            83      5     26      2  93.6%
scripts/update_marketplace.py                                         59      3     18      2  93.5%
scripts/validate_plugins.py                                          839    218    466     66  71.6%
scripts/verify_release_archive.py                                    222     21     50      4  90.8%
----------------------------------------------------------------------------------------------------
TOTAL                                                              41045   3577  14340   1535  89.7%

Threshold: fail_under in pyproject.toml · label coverage-exception to bypass (visible in PR timeline).

gcko and others added 29 commits October 19, 2026 12:44
Adds compass_runtime/session_summary.py, a producer with no store, thread
or subprocess. It turns the bounded in-order source into the closed shape
DEC-28 rules on: a lede and phases of titled, cited bullets, written in
16 KiB windows through the existing guarded model seam.

- The menu labels the person's words, relayed text (other agents,
  resumed-conversation summaries), the agent's words, checks with the
  tool-reported result, and file writes. Every line is redacted before
  clipping and before it enters a prompt.
- Windows are cut before the last person message in the back half. At
  most 32 are kept, newest first, and the earlier part becomes a gap that
  points to the outline.
- Parsing follows reading.parse_reply: a fenced reply parses, and a cut
  reply keeps only complete phases. A window with nothing valid becomes a
  stated gap while the others survive.
- The code sets each bullet's kind from its citations (failed > did >
  asked > said). A bullet citing only person and agent words is asked.
- The completion backstop covers the summary's own voice only, and
  attributed speech and the tool's own report are admitted. Failing
  strings are withheld in place with a reason.
- Ceilings, output bounds and the summary rolling cap are RuntimeConfig
  fields taken from the measurement. The plan refuses before the first
  call, and an empty reply is retried once.
- update() summarizes only the appended spans. A changed, truncated or
  replaced source is reported to the caller without a model call.

The design record gains a Producer contract section and an amendment, by
name, narrowing the DEC-28 question 1 backstop to the summary's own voice.

Implements DRC-4904

Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A reader whose session drifted now sees each departure as its own card in
the Drift card: which constraint it departs from, that constraint's own
saved words as the headline, What as the cited entries by number with what
each entry is in Compass's words, and "Why Compass reads this as drift" as
the reading's stored detail. Nothing about the reading changes: the
producer, what it stores and the evidence rules are as before.

- The card header names the model and "Analysis last run HH:MM"; the level
  line says "Analysis last run" too. The drift__level comparison projects
  that prefix back to the recorded words; the recording is untouched and
  the departure is recorded under Held to recorded answers.
- Every other line follows a divider as one quiet row, in the words
  resultStatus and claimStatus already use. "Model assessment" and
  "Show cited entries" are gone; each cited number opens Full activity at
  that entry, and "Load the cited entries" keeps the older-page read where
  no cited entry is held yet.
- Steer back follows the rows with its primary rule unchanged. Mark as
  inaccurate and What the analysis read share one footer row.
- The sample's Back to the finding is no longer held as an edit, and the
  tour's marks follow the new order.
- One story per result state, with assertions on citations and attribution
  beside neverDone; the shared checks move to stories/drift.checks.tsx.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…otice

The first press of Analyze drift now runs the reading. The consent card
("Send this session to ... for analysis?", Allow and analyze, Not now) is
gone. When the server records a notice for a press (first time, a changed
destination, a wider content tier or a new tool-output destination), a
non-blocking notice names what is sent in the server's own words, the
provider, where the words go and where tool output goes, with Turn off
readings beside it. The running job carries the notice, so a reload
mid-run still shows it; a press whose reply carries none shows none.

A stored off now reads as off: Analyze is not offered, the card says
readings are off, and the one control is Turn on readings and analyze,
which sends the turn-on fields and runs the reading in the same press.
Turn off readings shows whenever readings are on, no longer from the
legacy per-provider Allow map. Keep runs its analysis directly.

Goal summaries in the Console lose their browser Allow card: Summarize
this session is offered when the run allows it, a goal notice shows with
Turn off readings beside it, and a readings-off status reads as off.

The recorded admission answers in route.differential.test.ts and the
consent case in ReadingCapBanner.test.tsx are withdrawn narrowly, with
the departure recorded under Held to recorded answers. Reader-state rows,
the migration inventory, the sample session's reading permission and its
README follow. Stories with play functions cover the first press and its
notice, a second press with none, a changed destination, readings off,
and tool output named in the notice.

Implements DRC-4915 (frontend half)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…he latest chip press

- Own the outline chip reader-state row in the migration inventory, so the
  inventory check and its dashboard test pass again.
- The card reads the focused context itself once per board revision, so the
  outline loads under --no-annotations.
- Only the latest chip press moves focus or leaves the missing-entry note.
- A failed refresh no longer stamps the kept outline as updated.
- Say the outline starts partway only when start_offset is above zero or a
  region is before_window, not for a time or byte stop from offset 0.
- Count dropped groups as earlier parts of this session, not messages of yours.
- Drop the parsed fields the card never draws.
- Stories use outline check ids that differ from the record's, as measured, and
  cover a cold prompt chip, the latest-press race, the missing-entry note,
  annotations off and a failed refresh; the matched-id story is labelled as the
  future case.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…oal came from

The Intent card now follows the designer's mockup:

- A lede saying what the goal is for and that Compass never writes into the session.
- A header status derived in one place (intentStatus) from the held edit, the pending save
  request and the confirmed receipt: Saving..., Not confirmed, Not saved, Unsaved changes or
  Saved. A pending save outranks every receipt, so "Saved" never sits beside "Saving...", and an
  edit held over a newer board revision stays "Unsaved changes".
- One goal source line (goalSourceLine) from the annotation's provenance and the held state:
  Typed / From your prompt, saved or not saved, and "From your prompt, edited" for a prompt that
  was adopted and then typed over. The not-saved prompt line names Analyze's 1,000-character read
  (reading.LEDGER_WORDS_CAP_CHARS).
- "Use your prompt" lists "Your prompts in this session", each with its label and time on its own
  line above the prompt text. The option label the recorded answers compare is unchanged; the
  menu takes the longer text from the choice it names.
- "Expected outcome · up to 6 lines", numbered lines, and the mockup's placeholders.
- Save intent before Undo changes, and the controls moved from the retired More in one quiet
  wrapping row beneath them.

The keep-or-clear outcome lines question, the pending later-direction line and the ended-session
caveat keep their behaviour. Stories with play functions cover typed, from-prompt,
adopted-then-edited, saving, saved, not confirmed, refused, concurrent revision, prompt menu pick,
the moved controls and the ended-session caveat.

Implements DRC-4906.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
The review found the own-voice relaxation implemented as ruled when the
owner has accepted only the shape and the ceilings. The backstop now
withholds every completion word, quoted, attributed or named, except a
check's own report ("<check> reported <result>, as the tool reported").
It folds NFKC, withholds mixed-script words and covers finish, solve,
repair, implement, deliver, deploy and accomplish. A passed check no
longer licenses pass or green. The own-voice rule stays a proposal in
the design record.

Updates:
- Prior blocks must tile the source; an entry between two spans is
  late_evidence and makes no call.
- A pending check resolved inside the newest block re-summarizes that
  block with the appended entries instead of returning source_changed.
- An update with no room left under the output bound refuses before
  any call (output_bound_full), and a failed new lede keeps the prior.

Producer:
- A complete reply followed by prose is unreadable, not salvaged.
- The lede reports cites_cut; malformed bullet rows are shape_fault
  markers in place; a refused retry is not counted; any seam exception
  stops the job and keeps finished blocks; a bytes drop reports bytes.

Docs: the producer contract names a summary-specific guard rather than
the reading GuardedModel, and SECURITY.md lists the carriers and clips.

Implements DRC-4904

Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ined claim

Review fixes for the departure cards.

- A contradicted claim with an empty or blank stored detail again says no
  explanation was retained and lists each cited entry's record under
  Evidence, as the account did before the cards.
- A cited number shows only the kind of entry Compass names, never the
  entry's own summary, so an agent's "Done" or check mark cannot be drawn
  inside the result. A story cites such a message so neverDone and
  noCheckMark test the invariant.
- The reason for Can't tell stays inside Evidence, as the 2026-10-14 ruling
  keeps it; the amendment now says it does not supersede that.
- Remove the unreachable "departure with no held entry" branch: the shape
  demotes a departure with no resolved citation. The record says so.
- A repeated cite draws one number.
- The header stamp hides while an analysis runs, as the body does.
- DriftStamp joins the Tailwind source list.
- Reader-state rows, comments and the on-demand parity proof use the
  number and "Load the cited entries" wording.
- A story asserts the single-outcome label "Departure · Expected outcome".

The tracked built page is stale on this branch by design; the integrator
rebuilds once after the lanes merge.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Review fixes for the notice change.

- Console goal summaries: the notice the last Summarize reply carried is
  held in the board store per context key, so the passive read at the
  next revision no longer removes it. It stays until the next Summarize
  reply or a turn-off. Turn off readings is drawn whenever readings are
  on, inside the notice or beside Summarize. The readings-off sentence
  says where readings are turned back on.
- A goal-summary decline this browser stored before the upgrade is
  kept. Summarize is withheld behind Turn on summaries, and the loader
  refuses the request.
- The reading notice names the provider it was recorded for. It draws
  the route's disclosure parts only while the route still uses that
  provider, and otherwise a neutral line based on the content tier.
- Notices are announced once through the polite region, and the notice
  card is a named region.
- With readings off, a running job and an open direction question, the
  page draws one job box and the off sentence. No second box, Cancel or
  run control is drawn. Turn off readings is not drawn twice beside a
  notice.
- policyReason follows the server's ranking: run-disabled, then
  store-unavailable, then the cap, then off. The refusal line puts the
  reason that stops turning readings on first.
- A current result no longer draws Turn on readings and analyze.
- The migration inventory drops drift-parity.mjs from the Analyze notice
  row, and UNCONSENTED is renamed FIRST_PRESS. The sample fixture build
  now writes the readings-on policy, and its README says so plainly.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
… adoptions and edits

Review fixes for the Intent card:

- A landed prompt adoption clears an earlier "changed" or "refused" request note, and an untouched
  draft's adoption marks the save, so the header no longer stays on "Not saved" or "Not confirmed"
  after a retry lands.
- "Unsaved changes" now means only what Save intent would write. Typing the stored goal back over a
  chosen prompt drops the choice.
- The drafted-prompt line uses the ruled cap sentence, "Analyze reads at most the first 1,000
  characters of any prompt.", from one constant in sentences.ts. test_reader_words pins it to
  reading.LEDGER_WORDS_CAP_CHARS.
- An adopted goal's source line carries no time, so a lines-only revision cannot restamp it. The
  saved excerpt note now follows a full stop.
- "From your prompt, edited" is decided by word overlap with the adopted prompt, so words typed over
  a selected-all prompt read "Typed".
- A prompt menu item's title shows the whole prompt. Every character count reads "n / cap".
- Removed the dead draft-marks CSS and the draft-source badge variant and its story. Updated the
  on-demand e2e selectors and the editor deviations projection, recorded the departure under Held to
  recorded answers, documented the held `cleared` set in the reader-state record and restored the
  25px rationale beside the source line.
- The moved-controls story now opens the Saved stamp and arms and cancels Discard everything from
  the quiet row.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Conflicts: frontend/css-budget.json (both lanes shrank independent CSS
files; budget set to the combined total, re-checked by test:styles) and
frontend/e2e/support/intent-worlds.mjs (kept the notice state from
lane/notice and the draft-marks selector from lane/intent).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Conflicts: docs/design-frontend-migration.md (kept both the Intent card
and the analysis level departure paragraphs), frontend/css-budget.json
(combined total of independent reductions, re-checked by test:styles)
and frontend/src/sample/ViewOnly.tsx (comment names both the new cited
entry controls and the readings on/off hold).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Conflict: frontend/src/sessions/SessionDetail.tsx (kept the outline's
running and harness props on WhatItDid and the drift lane's DriftStamp).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…are off

The outline card reads the focused context once per board revision whatever
annotations say, so the Intent request test now holds that the Intent card
adds no read of its own: no project-scope read and one focused read.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
… when it is out of date

Implements DRC-4907, without the job: no model call, timer or start route.

- summary_store.py: compass-summaries.json under the Compass home, keyed
  by harness and session, independent of saved intent and of Discard
  everything. Closed versioned schema; every string redacted on the way
  in and again on the way out; 64 MiB aggregate bound with eviction by
  last read or write, which cannot reach the annotation store; written
  under a process lock and the file lock through an owner-only temp file
  and rename; torn, oversized or unknown files refuse whole.
- Out of date from the stored spans and fingerprints against a fresh
  source read: unchanged, appended (Update summary), changed_earlier
  with the affected block, truncated, replaced. session_summary gains a
  model-free change_report that update() now shares.
- Publishing: a small `summary` row field declared in base_session,
  DECLARED_SESSION_FIELDS and the store's published(); blocks travel
  only on the focused /api/project-context answer as sources.summary.
  The parsed store is reused while the file is unchanged, an access
  touch is written at most hourly, and the focused freshness report is
  cached by transcript stamps, so polls pay a stat.
- POST /api/summary/delete, refused for navigation and non-loopback
  peers before the body or store is read. --forget deletes the store.
- SECURITY.md invariant 2, MANUAL.md, the store contract and the module
  map name the store; route counts and the import allowlist updated;
  client fixtures regenerated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…ng the summary store

- Do not cache a freshness report whose read stopped on the time
  allowance, as the outline already does; byte-limited reads still cache.
- --forget deletes the store when the filesystem lock is unsupported,
  denied or uncreatable; only a busy lock refuses it.
- forget and delete answer absent before locking a missing store, so no
  state home or lock file is made.
- Refuse a record whose identifiers (harness, sid, cites, block ids,
  fingerprints, source metadata) redaction would change, instead of
  storing it under a key no lookup can find.
- Bound model_stamp with summary_store_model_stamp_cap_chars (256).
- Recheck the touch interval after the locked reload, so concurrent
  focused reads rewrite the store once.
- Board rows carry the last cached focused report's kind and reason for
  the same record, still with no transcript read per row.
- SECURITY.md names the access-time write on GET /api/project-context;
  the architecture map lists both focused-session caches.
- The HTTP serving test no longer claims the generate-on-first-open
  criterion, which belongs to the job lane.

Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…cord capture

Commit the anonymized record of a ninety-minute stretch of a real
milestone burndown, the session the next sample will be made from, as
the owner's recorded exception of 2026-10-19. The page still draws the
first sample; sample-session/README.md says why this session and this
cut, the checks it passed, and the step that switches the page to it.

- scripts/cut_sample_session.py keeps one stretch of a session and only
  the record kinds the page reads, re-parenting across dropped records.
- The sanitizer replaces screenshots nested in tool results, the short
  form of a session id, and times written as text (local times turned
  into UTC), maps listed numbers held as JSON integers, and sweeps for
  tracker references, encoded home directories, machine temp paths and
  remote-session links.
- build_sample_fixture.py captures every record page back to the start
  under older-<n> cursors, and refuses a record longer than the page can
  resolve; the sample's server answers each older page and treats an
  unknown cursor as stale instead of claiming the record's start.
- Stories show citations on the newest, a middle and the oldest page
  opening in Full activity.
- frontend/build/page-size.test.mjs bounds react.html at 2,294,347 bytes
  (the page as built on 2026-10-19 plus 400 KiB) and the fixture at
  250,000 compact bytes, in pnpm test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…istory

A review against the repository found what the file-only reviews could
not: eight quoted short commit hashes, branch names, pull-request titles
and commit subjects that resolve in public history, tracker slugs that
spell issue titles, and a project key hashed from the real path.

- The sanitizer takes an optional commit_repo and regenerates every 7 to
  40 character hex token that names a commit there (from the full hash,
  so a short form stays the prefix of its full form's replacement);
  --verify fails on any surviving or newly resolving one.
- --verify now reads JSON integers too: a listed number under any key
  but a count (tokens, durations, positions) is a leak.
- milestone-session.jsonl is regenerated: branches read work/branch-<x>,
  slugs are dropped, quoted titles and subjects (whole or cut short)
  reworded, quoted pull-request description lines replaced by a
  placeholder, and the project key is the hash of the replaced path.
  Both leak reviews ran again; their findings, checked against the
  repository, name the replacements or what is kept on purpose.
- File tests: no hex token resolves as a commit, no gitBranch is a known
  ref, and the project key is the hash of the sample's own path. A test
  holds the capture's page constants to record.ts.
- The README says what is and is not replaced; the size bounds live
  only in the design record.

The built page under compass_runtime/web/ is not rebuilt here: the
integration merge must run pnpm build and pnpm build:check and commit
the regenerated artifacts, and page-size.test.mjs bounds that page.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…utes

The store lane added POST /api/summary/delete, which deletes a stored
summary and launches or sends nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
The owner narrowed DEC-28 question 1 on 2026-10-19: a summary string is
withheld when the summary itself states work is fixed, done, working or
verified, not when it reports what a named speaker said, quotes a tool's
own output, names a branch or file, or names a tracker state.

completion_claim now checks structure rather than the bare word list:
clauses split at sentence ends, commas, semicolons and so/which/and/but/
then/because/while; a completion word is admitted only under a speaker
followed directly by its speech verb in the same clause, a quote with a
trailing attribution, an implicit-subject opening on a code-set said or
asked bullet, a tracker state, a name, or the tool-report form. The
refusals stay: unattributed quotations, a second clause after a speech
verb, result verbs and named states in the summary's voice, unreported
check results, NFKC folding, mixed-script words, and the whole-string
check before any cap. "read" and "wrote" are not speech for a person or
the agent, and a later speaker without a verb ends the licence.

The design record's proposal becomes the ruled amendment, and the
producer contract and both prompts follow it.

Implements DRC-4904

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
… and ledger

Implements DRC-4913 and the session-summary half of DRC-4915 (DEC-28).

- A summary grant of its own, on by default, with a first-summary notice
  (kind "summary") and an independent stored off. Turn off summaries and
  Turn summaries on never touch the Analyze or goal-summary answers;
  --no-observer-model refuses every summary call.
- summary_jobs.py: one server-owned job per session, an identifier-only
  marker in state_dir/summary-jobs/ before the first charge, recovery that
  reports an interrupted job with its finished parts and never resumes,
  Cancel, published progress, and a guard that rechecks grant, generation
  and destination before every part and charges each attempt once against
  a new summary_spends ledger (200 calls per 24 hours), separate from
  Analyze's spends.
- Finished parts are saved incrementally; the windows not yet answered
  are a named "stopped" gap, so a cancelled summary never compares as
  unchanged while lacking its newest entries. No late reply publishes
  after Delete summary, Turn off summaries or Cancel. Discard everything
  leaves summaries alone, as the ruling requires.
- POST /api/summary (start, update, rerun; origin open only for a finished
  session with no stored summary) and POST /api/summary/cancel, refused
  before reading the body for non-loopback peers and document navigation.
  The plan refuses oversized jobs before any charge and names the largest
  read allowed.
- Board-level `summaries` block and per-row `summary.job`; client fixtures
  regenerated. SECURITY.md invariant 2 and Session summary calls updated;
  the Job and grant contract is documented in docs/design-session-summary.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
… needs recovery

Serving ran summary_jobs.recover under the summary publication lock on
every start, which created summary-jobs.lock in a fresh compass home even
with no markers to recover. The installed-copy characterization test
requires a launch and stop to leave that home empty. Recovery now returns
early when no marker exists, as reading_jobs.recover already does.

Also records the new summary_jobs module and its importers in the reviewed
runtime import graph allowlist, including http_api's session_source read
for the summary start route.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Review found the own-voice completion check admitting claims it should
withhold, and withholding attributed speech the owner's 2026-10-19
ruling admits.

Withheld now:
- The clause splitter fails closed. It also breaks at dashes, colons
  (except one directly after a speech verb), brackets, line ends (checked
  before display whitespace is collapsed), a sentence mark before a letter
  or a closing single quote, and Unicode terminators. The joiners gain
  after, although, though, yet, until, since, as, once, when, or,
  however, thus and therefore. A closed quote followed by unquoted words
  ends its clause. Cf format characters are removed after NFKC.
- Only an error speaks for a tool (read/reads/said/says). A paraphrased
  check result is withheld.
- The tool report needs its comma, and the whole match, "reported"
  included, becomes a clause break. The postfix "<check> passed, as the
  tool reported" form is dropped.
- A name needs a name's shape: a dotted token needs an extension,
  underscore or digit, filler words make a compound a phrase, and
  backticks alone no longer make one. A backticked tracker state is one
  to three words without filler. Bare "work" joins the word list.

Kept now:
- A speaker opening the reported words, objects included
- then/also/later/again/first as adverbs
- from-states, issue keys and multi-word states that end the clause
- result verbs inside names

Both prompts teach the error-output form. The design record lists the
new structures and the over-withholding that failing closed still costs.

Implements DRC-4904

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…honest

- Turn off summaries stops every running job: reason and marker are set and a
  spawned call is cancelled. A charged part rechecks the grant after its board
  publish and before the model seam, so an off landing there sends nothing.
- A stored Turn off readings also refuses summary admission and each later
  part (reason readings-off), and the reading off route stops running jobs.
  SECURITY.md no longer claims the two switches are independent.
- An update's intermediate parts carry the saved lede, so a stopped update
  keeps it.
- Producer stops (daily-cap, model-unavailable, stopping) still save the final
  result; only publication bars refuse it.
- An update re-reads the stored summary under the publication lock and refuses
  when Delete summary removed or another job replaced it.
- A job's marker is removed without the lock when the lock fails at its end;
  a torn marker no longer blocks Delete summary or leaves the session in
  flight, and recovery removes it.
- Under --no-observer-model the summaries block reports a stored off.
- SECURITY.md lists the summary disk writers in ordinal order.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
gcko and others added 22 commits October 21, 2026 17:54
…ewed import graph

The outline title cut (e7744ad) made session_source call transcripts.clip to cut a bounded prompt
title back to a whole word. transcripts depends only on config, io, records, sessions and state, so
the edge adds no cycle. The reviewed allowlist now lists it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…ist of prompts

The reader-state design record renamed its row to "An open Select popup, or the open list of
prompts" when Use your prompt became a button opening a popover. The migration inventory row takes
the same name, keeps owner DRC-4823, and adds PromptSelect.test.tsx as an oracle.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…ind prefix

A model-written bullet that already opened with "The agent" or "The person"
was drawn as "The agent said: The agent read ..." and "You said: The person
chose ...", because the page prefixed every bullet by kind unless the text
opened with a speech verb. A line whose subject is the agent now keeps its
words as written, and one whose subject is the person reads "You ..." with the
verb agreed, at the start of the line only.

The summary prompts also ask the model not to write entry numbers in text:
the citation chips carry them.

Implements DRC-4931

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…review notes

Outline: a failure is counted fixed or listed once, in the first part it
failed in; a failure the highlight cap dropped is listed by the next part;
a complete title of exactly the bound is kept whole; the server-cut title
story draws a fixture the server test holds equal to its own output.

Departure cards: the title carries the entry's time and the constraint, a
long written path keeps its file name, a long URL is cut at a separator, and
the correction dates the same entry the card titles. The correction label
drops a closing ? or ! and keeps an ASCII ellipsis.

Summary wording: withheld lines, titles and runs use plain words, an opening
summary is not called a line, the failure badge knows more runners and
refusals, the opening paragraph says "You", and the written-again note ends
with the rerun.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
… the entry counts

A session whose Claude Code worktree was removed now files under its
repository and names the worktree; the git checkout is read once per
session. Sessions rows label the worktree as "worktree <name>".

The summary size, the header and the since-intent count read one count,
which says it is a floor while older record pages remain unread. The tab
title and breadcrumb take the heading's goal or first-prompt stand-in for
an identifier-only title, and the identifier pattern no longer matches
real titles such as gpt-4 or utf-8.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
The header and summary size dropped their "at least" wording once the loaded
record reached back past the intent window, though older pages stayed unread.
The floor now follows the record alone. The design record names the tab title
and breadcrumb as heading substitutions and records the floor wording.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
… and counts

Signed-off-by: Jared Scott <jared.scott@variable.team>
…ole, anchor refusal phrases

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
… draws it

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…nces, answers and pastes

Drift: a result word shares a clause only with its own kind of check, so a
rule or an action ("must never fail", "are removed") no longer cites a pass,
and "Tests: all passing" and "don't fail" now count as claims. A claim that
names no kind says how many checks the record held. The unreachable failed
check trigger of Steer back is gone, and a departure replaced by a different
one is named on the next reading even with no correction between.

Discard: a refused or unknown summary answer no longer reads as "its summary is
gone"; a stopped job with nothing stored says so; a failed departure store now
also says what became of the summary. The racing-save tests interleave a real
concurrent save.

Answers and pastes: an answer opening its own outline phase is held by a
test, the vacuous capture assertion is dropped, and a direction on the Course
tab and the timeline draws without the harness's paste tags.

The design records, the skill body and the stale delegated-work comments and
browser proofs follow.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…mmary wording

Signed-off-by: Jared Scott <jared.scott@variable.team>

# Conflicts:
#	compass/skills/compass/compass_runtime/web/react.html
#	compass/skills/compass/compass_runtime/web/react.integrity.json
Signed-off-by: Jared Scott <jared.scott@variable.team>
…equirement

"The tests should pass", "Tests must pass" and "Make sure the tests pass" state
the result the reader expects, so a recorded passing run of that kind covers
them. The modal branch of the not-a-result guard is removed; the passive
"what was done to something" branch stays.

Signed-off-by: Jared Scott <jared.scott@variable.team>
…es, answers and

Signed-off-by: Jared Scott <jared.scott@variable.team>

# Conflicts:
#	compass/skills/compass/compass_runtime/web/react.html
#	compass/skills/compass/compass_runtime/web/react.integrity.json
Signed-off-by: Jared Scott <jared.scott@variable.team>
…rd sentences

The discard route now publishes one sentence per summary outcome, so the
live-server client fixtures moved. Generated with scripts/regen_client_fixtures.py;
frontend/test/golden is untouched.

Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gcko added a commit that referenced this pull request Oct 22, 2026
…edock tap (#635)

* feat(release): render and publish the Homebrew formula for a release

Add scripts/update_homebrew.py, which renders Formula/compass.rb for a
release tag from the checksums.txt that scripts/publish-cli.sh attaches,
and scripts/publish-homebrew.sh, which fetches that file over public
HTTPS, clones spacedock-dev/homebrew-tap with a deploy key, commits
"compass <version>" and pushes to main, retrying from a fresh clone when
another product's release lands first.

The renderer refuses a checksums file that is not exactly the four
archives of the version, and never moves the tap's formula backwards,
so a resumed older release or a re-run is a no-op. The formula installs
the whole archive folder into libexec and links it as compass and
spacedock-compass, the name Spacedock looks for on PATH.

Tests cover the rendered v1.3.0 formula against its real checksums and
the publisher against a local bare repository standing in for the tap.
The release workflow job that runs the publisher follows separately.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>

* ci(release): write the Homebrew formula into the tap after each release

Add a homebrew job to the release workflow, after attach-cli because the
formula names the archives and their published checksums. It copies the
marketplace job's shape: a checkout of exactly the target without
credentials, a read-only repository token, and one script. The tap deploy
key, HOMEBREW_TAP_DEPLOY_KEY, is bound at that one step and reaches only
spacedock-dev/homebrew-tap.

The release workflow tests pin the boundary on purpose and are updated
for it: the job set and its needs, one key per job, three secrets each
named once, and a checkout without persisted credentials.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>

* docs(release): describe the homebrew job in the release workflow

Say in AGENTS.md that the release workflow is eight jobs and what the
homebrew job holds, writes and refuses, and mention it in the contributor
release walkthrough. README and COMPATIBILITY.md are left alone until a
formula has been published to the tap, since there is nothing to install
before that.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>

* ci: read the current Antigravity validator summary line

Cherry-picked from a399b4a (feature/session-redesign, #631) so this PR's
latest-client-smoke check reads the current Antigravity summary line. Only the
workflow hunk is taken: the test_unasked.py hunk needs an `import shutil` that
lives elsewhere in #631, and main's test_unasked.py passes without it.

Signed-off-by: Jared Scott <jared.scott@variable.team>
(cherry picked from commit a399b4a)
Signed-off-by: Kent <iamcxa@gmail.com>

* feat(cli): add --version, stamped from the release in the standalone binary

`compass --version` prints the bare version on one line and exits 0.

The standalone binary is built before the release job bumps the plugin
manifests, so the manifest still names the previous release when the
archive is made. package-cli.sh therefore stamps the version it is given
(the same one that names the archive) into the bundle, and the binary
prints that. Run from source or an installed plugin, `server.py --version`
reads the manifest instead. When neither records a version it says so on
stderr and exits 1.

smoke-cli.sh now fails an archive whose --version differs from the version
in its file name. MANUAL.md gets the option row and exit-status wording,
and COMPATIBILITY.md and AGENTS.md describe the stamp.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>

* feat(release): state the version in the Homebrew formula and test it with --version

The rendered formula carries an explicit `version` line, and its test
asserts that `compass --version` prints it. The `--help` check is dropped:
`--version` already needs the same interpreter, imports and bundled files.

The never-backwards check reads the explicit field first and falls back to
the version in the download URLs, so a tap formula written before the field
existed is still compared correctly.

brew audit reports the explicit line as redundant with the version scanned
from the URL, in strict and in default mode alike.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>

* test(release): prove the Homebrew publisher keeps a concurrent tap commit

A second product's commit landing between the clone and the push must
survive. Changing the push to a force push, or to a +HEAD:main refspec,
left the suite green; now it fails.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>

* docs(release): check the tap after a tag, and say what a failed homebrew job needs

The compass-release skill checks the marketplace after a tag and has a
recovery entry for it, but had neither for the Homebrew formula. It now
names the formula in what pushing the tag will do, checks the tap's
version, and explains a failed homebrew job, including that Re-run all
jobs rebuilds the archives and breaks brew install until it succeeds again.

MANUAL.md says --version is checked before the operation flags. The
AGENTS.md homebrew bullet lists jq among the tools the job runs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>

---------

Signed-off-by: Kent <iamcxa@gmail.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Jared Scott <jared.scott@variable.team>
gcko and others added 3 commits October 22, 2026 12:52
Conflicts resolved:
- MANUAL.md: the exit-status row keeps this branch's forget wording and adds main's --version.
- http_api.py and test_http_api.py: this branch removed --allow-tool-output, so main's
  clearing of tool_output_pre_grant on "readings off" (#634) has no flag to clear here.
  Both the code and its two tests are dropped; pause keeps this branch's behaviour.
- sentences.ts, ReadingResult.tsx: both helpers kept. The idle line wins, then main's
  own-property scope lookup (#632) replaces the bare READ_SCOPE index.
- legacy_goldens.ts: LATER_FIELDS keeps checkId and adds resultReason (#636).
- vocabulary.test.ts, Activity.stories.tsx: both sides' imports and stories kept.
- drift-parity.mjs: main's beforeDepartures helper kept without the delegated-work loop
  this branch removed; the Allow-owed step keeps FIRST_PRESS with main's skip list.
- react.html, react.integrity.json: rebuilt from the merged sources (pnpm build).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…or coverage

main's coverage gate (#638) now certifies a selector list only when every selector is covered. The desktop rule .next-crumb:hover, .next-breadcrumb [aria-current=page] was certified by its :hover half alone, and the record holds no used entry for the other half until coverage is collected again, so it is held to a named exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…'s goal verdicts as claims (#642)

* chore: open the intent-from-the-session branch

An empty commit so the draft pull request can open before the first change. The plan lives in docs/plans/intent-from-the-session.md, which is gitignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(captures): record how Claude Code writes a /goal into its transcript

Shapes only, measured on Claude Code 2.1.308 (macOS): the set and verdict
goal_status attachments, the command's user and local_command records, and the
hook payloads, which carry no goal field. The README row names the shapes not
captured: replacing an active goal, Goal cleared: for an active goal, the
not-met and impossible verdicts, a very long condition, resume and interactive
sessions.

Implements [DRC-4945](https://linear.app/recce/issue/DRC-4945/record-how-claude-code-writes-a-goal-into-its-transcript)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(sessions): read a Claude Code /goal as a goal source

A Claude Code session that states its goal with /goal now publishes it as
session_goal ({text, at}), read forward and incrementally from the
goal_status and local_command records. The latest set wins, a cleared goal
publishes none, a met goal keeps its condition and its set time, and a
condition that cannot be held whole publishes nothing.

session-goal joins the closed goal sources (spelt once in records, re-exported
by reading, mirrored by the page and pinned by the vocabulary fixture), and the
server resolves it from its own row for adoption. The published mapping leaves
room for the harness verdict without a rename. Docs record the read and its
privacy limits.

Implements [DRC-4946](https://linear.app/recce/issue/DRC-4946/read-a-claude-code-goal-as-a-goal-source)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page for the session-goal source

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): read session-goal as a prompt source in the work list anchor

workList.ts kept its own three-token goal-source list, so an adopted /goal
was treated as typed words when deciding whether the window anchor sentence
applies. Use PROMPT_SOURCES like every other site.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(web): rebuild the page for the work list goal-source fix

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(intent): name a session's /goal where the log and goal box say none exists

The Intent log listed three sources and closed with "No goal available from
these sources" over a session that publishes a /goal, and the goal box said
only that there was no first prompt to draft from. Both read as a session with
no goal. The log now lists the /goal as a source and counts it, and the box
says which /goal the session holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(web): rebuild the page for the /goal source in the Intent log

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(sessions): publish an excerpt of a /goal longer than the Goal field

A /goal condition over the Goal field's 240 characters now carries offered
(the bounded excerpt an adoption stores) and cut: true on session_goal. The
server resolves offered for adoption and counts the session's /goal as the
draft time ahead of the prompts, so the page, the adoption and the store
agree on one string and nothing clips on its own.

Implements [DRC-4947](https://linear.app/recce/issue/DRC-4947/use-a-detected-goal-until-the-reader-edits-it-and-offer-a-later-one-as)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(intent): use a detected goal until edited and offer a later one as an update

A /goal the session set fills the Goal field, marked "Goal detected" with a
"From /goal" line, ahead of the prompts and over a session that opened with a
command, and Analyze drift adopts it by name. Typing or choosing a prompt
replaces it and the line says so. After the reader saves a goal, a newer /goal
is offered beside the field with Use this goal and Keep my goal, and never
replaces the saved words. A goal over the limit is offered as its excerpt with
a note that it was cut. The how-to says where a detected goal comes from.

Implements [DRC-4947](https://linear.app/recce/issue/DRC-4947/use-a-detected-goal-until-the-reader-edits-it-and-offer-a-later-one-as)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(intent): record how a detected goal is used and offered

Records the owner rulings on using a /goal until edited and offering a later
one, the excerpt fields on session_goal, and the reader state the update
offer holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(intent): inventory the declined /goal update as reader state

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(web): rebuild the page for the detected goal

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(intent): call an untouched detected goal a draft and keep the cut note once it is saved

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(intent): record that a detected goal also reads as a draft

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(web): rebuild the page for the detected goal status and cut note

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(intent): fold Expected outcome into an optional accordion

Expected outcome sits under the Goal in an accordion labelled optional,
closed until a saved or drafted line, a pending later direction or a
departure citing an outcome line is held. It opens on each rise of that
condition, so a reader's close holds. Add a line moves inside it.

Disclosure gains an openWhen prop for the rise rule. Stories, the first
view tests and the how-to are updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(intent): record the optional Expected outcome accordion

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(web): rebuild the page for the Expected outcome accordion

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(intent): drop dead CSS rule for the removed lines heading

OutcomeLines no longer renders the held-heading wrapper, so the rule
never matches. GoalField still uses the other held-heading rules.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page after dropping the dead lines-heading rule

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(intent): say in the how-to that a level needs an expected outcome

Step 1 called the expected outcome optional while the paragraph below said
no level without one. Step 1 now states the cost, and the paragraph is
shortened to stay within the popover word budget.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page for the how-to wording

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(intent): keep the how-to inside its word budget

Keep the no-outcome-line sentence the docs test pins and trim two words
elsewhere in the popover.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page for the how-to wording

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(reading): give a goal alone a level, and refuse a goal too vague to read

An analysis no longer needs an outcome line. With none, the producer prompt
reads the goal's own stated end state as the outcome, the level rules treat
the Goal row as the one line, and the reading's cutoff says it read against
the goal. Rule L7 and its reason stay as a known token for older payloads.

The model may instead answer the goal "goal_unclear" before it reads the
session. That is a new closed result on the Goal row alone: it takes no
citation, publishes every other row as not asked, leaves the analysis with no
level (reason goal-unclear), is stored like any reading and still counts
against the daily cap. The intent's prompt share rises from 9,216 to 9,728
bytes for the two added instructions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(intent): warn under the Goal field when the goal is too vague to analyze

Draw the refusal where the reader can act on it: the agreed sentence under the
Goal field, cleared when the goal is edited or saved again. The Drift card
answers a refused goal in one line with no level, quiet rows or Steer back,
and a goal alone is answered from its own row. The how-to drops the claim that
a level needs an outcome line.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs: say a goal alone is read for its own end state, or refused

Record the 2026-10-22 amendment under the four-levels ruling and the
frontend departure, and restate the promise in the README, promise map and
skill.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page for the goal-alone reading and refusal

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): match goal-only wording, hide the mark on a refusal, name /goal

A reading of the goal alone says the goal is the line no check shows, not
"a line of your intent". A refusal for a goal too vague to analyze no longer
offers Mark as inaccurate, since it sets no level and offers no Steer back.
A goal from /goal reads "from /goal" under What the analysis read, and the
reading's cutoff no longer says the prompt it came from was not found.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page for the goal-only wording and refusal fixes

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(goal): find a session's goal in its prompts on a press

Add POST /api/goal/find and the goal_find producer behind it. The route is
guarded as Analyze is: press, observer_model, navigation and origin checks,
the stored pause and the rolling cap all refuse before the prompts are read,
and the charge is taken at the model seam. Only the reader's own prompts are
sent. A goal is returned only when the model quotes the words it comes from,
each quote is verbatim in the prompt it names as Compass sent it, and the
model calls the goal clear. Nothing is stored.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(intent): add Find goal beside the prompt list

Find goal shows while no goal is saved and no /goal was detected. A press
sends one request, shows a busy state, and puts the found goal in the box
marked Goal detected, with "Detected from your prompts" and a citation to
each prompt it quotes that opens that entry in Full activity. Nothing found,
a pause and a spent day are said beside the button and leave the box alone.
A saved goal and a /goal outrank a found goal, and an edit replaces it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs: record Find goal

Add the decision record, the route and its limits in SECURITY.md and the
skill's route table, the module map row, the reader-state row and the
how-to paragraph.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page for Find goal

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(goal-find): say the prompt matches the reading rule instead of applying it

Goal finding builds its own end-state wording; it does not import
reading.GOAL_UNCLEAR_RULE. Reword the docstring, design record and
SECURITY.md to say the wording matches and a test holds it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(intent): show the quoted words under each cited prompt of a found goal

The popover shows a cited entry as the activity list abbreviates it, so a
reader could not tell which words of the prompt the goal came from. List the
server-verified quotes under the citations, led by the entry number.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page with the quoted words of a found goal

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(goal): publish the harness's met verdict and test it against the record

The latest met verdict joins the session's goal as session_goal.verdict
(met, time, masked and bounded reason). A reading tests it with code over
the recorded writes and checks and stores the answer as goal_verdict beside
the criteria: agrees, disputed or unconfirmed, with closed reason tokens.
The verdict never changes a criterion and is not a check the record holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(drift): note the harness's goal-met verdict on the Drift card

The card says Claude Code's verdict as the harness's claim, with its time
and reason, and Compass's check beside it where a reading made one. With
no analysis yet it is the claim alone and implies no level. Stories assert
the claim only, agreeing, disputed, unconfirmed, newer-than-reading and
absent states.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(goal): record that a goal-met verdict is a claim Compass tests

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* test(drift): wait for the record before asserting the level beside a goal verdict

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page with the harness goal-verdict note

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(goal): place the goal verdict in its own paragraph and give a product reason for met-only

Move the harness goal-met sentences out of the Find goal paragraph. State that
a not-met verdict is not published because the evaluator repeats it every
iteration while the goal is open, replacing the stale not-captured reason.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): match an elided verdict path by its tail and align the verdict note

The evaluator writes long paths as ".../tail", which the file matcher rejected, so a reason
that named a file read as naming none. Strip the elided head and match the tail. The note now
uses the card's own inset class so it lines up with the level and the criteria rows.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page for the verdict note fix

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(intent): analyze drift reads a found goal as the working goal

A goal found from the prompts is the working goal until edited, as a /goal
is. It no longer counts as an unsaved edit, so Analyze drift is not refused
and Undo changes has nothing to undo. The press stores the found words as
typed words, then reads against the revision that minted, adopting nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page for the found-goal analyze fix

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): read a quoted verdict path that ends a sentence

A goal-met reason that quoted a path followed by a closing quote and a
period (docs/plans/x.md".) was read as naming no file, so the Drift card
said its reason names no file or check. The word trim ran one strip then
rstrip("."), so the closing quote shielded the period. Trim in a loop
until stable and add curly quotes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(intent): add a line goes to the empty box already drawn

An empty outcome list is drawn as one empty row, and "+ Add a line" then
added a second, so typed text sat under a blank row. Adding now focuses a
trailing blank row instead of stacking another.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page for the add-a-line fix

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): dispute a goal verdict the record contradicts, and name why

A file the harness's reason or the goal names that a later rm, unlink,
rmdir or mv took away now disputes the verdict, with the file and the
moment on the card. The removal is read at the press and never sent or
published as a fact. Also: a /goal is named "/goal" wherever its source is
shown, the "could not be read whole" notice no longer appears for a /goal
that was not cut, a found goal groups its quotes under one entry number,
and a goal-only reading that finds nothing says what check would let a
level be drawn (owner decision, evidence floor kept).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page for the verdict, provenance and citation fixes

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): name the file, not its folder, when both are removed at once

A verdict whose file was removed by `rm file && rmdir folder` records both
removals at one time, and the dispute named whichever came first. On a tie
it now names the file the verdict relied on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(goal-find): clear the in-flight mark before the reply is written

The mark was cleared in a finally after the answer went out, so a press sent
the moment the previous answer arrived could be refused as in-flight (409).
Measured in CI on 2026-10-23, where the spent-day test sends presses back to
back. Both reply paths now clear the mark first, and a test checks the mark
is gone when each reply is sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(goal): read a clear by its sentinel and accept the documented 4000-character limit

Measured on Claude Code 2.1.309: clearing an active goal writes the set
sentinel with met true. It was read as the evaluator's verdict, so an
interactive clear, which writes no other record, showed the cleared goal
as marked met. A condition between 2000 and 4000 characters, accepted by
the harness, published nothing because the cap borrowed the record
reader's message cap.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(captures): record /goal clear, replace, not-met, impossible, resume and interactive shapes

Eight throwaway 2.1.309 runs, field names and closed vocabulary only. The
README row says what each shows and what was not captured; the security
and design records state the 4000 limit, the clear marker and what a
reader sees for a goal set before the 8 MiB cold read.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(goal): read only the two measured met values on a goal_status sentinel

A sentinel whose met was absent, null or not a bool was read as a clear and
wiped a live goal. Only met false (set) and met true (clear) were measured;
any other value is now ignored, as before the clear fix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(session): name a Claude session by the goal it holds, not the /goal it opened with

The session name came from the first prompt, so a replaced or cleared /goal
opener named the session by a condition it no longer held. A held goal now
names it; with none held, a /goal opener is skipped and the next real prompt
names it. A generated ai-title still wins, and sessions without /goal are
named as before.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* test(session): prove every surface names a replaced-goal session by its held goal

Stories cover the Sessions row, the tab, the crumb and the heading for a
session that replaced its /goal, and a session without /goal keeping its
name. SKILL.md and the runtime map state the naming order.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(session): keep the /goal opener as a name when no held goal is authoritative

session_goal reads at most turn_scan_max_bytes from the end, so a goal set before
that window is not seen and its absence proves nothing. The first-prompt fallback
now skips a /goal opener only when the file fits the window. Also corrects the
session_title docstring to the ai-title, held goal, first prompt order.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(session): say a command-opened session was not named, not that no title was published

A session whose only prompt was a /goal that has since ended published that
prompt; Compass declines to name a session by a command. The session heading,
tab title and breadcrumb now say "No name: the session opened with a command"
instead of "Title not published". Sessions rows keep the published-title wording.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(session): keep a cleared /goal from naming the session when the transcript outruns the read window

The goal read records whether it saw the whole file or any goal event, and the
opener is skipped only then; a mid-file read that saw nothing keeps it as the
last resort.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(session): word a command-opened session the same on every surface that names it

The Sessions row said Title not published where the heading, tab and breadcrumb
said the session opened with a command. Differential tests restate the recorded
phrase for those rows only.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(captures): record that a goal ends when Claude Code gives up on it

A real Claude Code 2.1.309 run showed the goal is ended after the
failed record: a later bare /goal and /goal clear both print the fixed
"No goal set" words and write no goal record, and a goal set afterwards
carries no link to the earlier one. Shapes and fixed words only.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(session): publish when a /goal was given up, cleared or replaced

Claude Code ends a goal after it gives up on it (measured), so a give-up
now ends the held goal and is published beside it as session_goal_gave_up
with its time and masked reason, never under verdict. A clear is published
as session_goal_cleared, and a goal that followed another carries
earlier {how, at} (replaced, met or gave-up) without the earlier words.
Absent means unknown: a cold read that starts mid-file publishes none of it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(frontend): say when a /goal was given up, cleared or replaced

The Drift card shows a give-up as the harness's claim, never met and never
a level or a check. The Intent card says under the Goal field what became
of an earlier /goal, and that a clear left no goal; where the session
opened with /goal the existing sentence carries the clear, so it is said
once. None of it shows while the update offer is on screen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(session): record that a given-up, cleared or replaced /goal is said to have ended

A dated amendment in the reading design record, the security note under the
session history store, the how-to paragraph and one sentence in the skill
body. The earlier lines that called a failed verdict neither a verdict nor
an end now point at the measured result.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* test(frontend): share the saved-goal setup between the goal stories

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(frontend): keep the cleared /goal note when a chosen prompt hides the opened-with sentence

The note deferred to the opened-with-/goal sentence whenever that sentence had
text, but the sentence is not drawn over a drafted box, so a chosen prompt
left the clear said nowhere. Also move the noDraftWhy comment back above it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(frontend): date the earlier-goal notes and name Claude Code in the give-up claim

The earlier-goal and cleared notes on the Intent card now use the dated
clock, as the Drift notes do, so an event on a previous day no longer reads
as today. The Drift give-up claim names Claude Code rather than the
payload harness label, matching the Intent card.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(frontend): name Claude Code in both Drift harness claims and story the cleared /goal note

The goal-met claim used the payload harness label ("Claude") while the
give-up claim said "Claude Code". Both now name the product. Add a story
proving the cleared note shows after a prompt is chosen over a session
that opened with /goal.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(frontend): name the prompt draft under a cleared /goal note

The cleared note sat directly above a Goal field pre-filled from a prompt, so
it could read as describing the draft. Over an untouched prompt draft it now
ends "; the draft below is your first prompt" (or your latest prompt, a prompt
you chose, a goal found in your prompts). Typed words and an empty box keep
the plain sentence, and the update offer still suppresses it.

Implements [DRC-4957](https://linear.app/recce/issue/DRC-4957)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* test(frontend): prove the cleared note yields to the update offer and names a found goal

The offer test now uses a saved goal older than a newer /goal, so goalUpdate is
non-null and the replaced note would otherwise show; it fails without the
suppression. The found-goal clause gets a unit test and a story, including
that editing the box drops it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(frontend): move the goal hint under the box and the source line above it

The Intent lede becomes a hint under the Goal textarea, beside the count, and
reads "Write what should be true when this session is done. Compass checks the
session's work against it and never writes into the session." The goal source
line moves from beside the count to directly above the box, under any ended
note, and says what the words are: "Drafted from your first prompt", "Your edit
of your first prompt", "From your /goal at 11:53", "Found in your prompts at
23:11", "You typed this". The cut suffixes name the real goal limit.

The cleared note no longer names the draft, since the source line directly
under it does.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(frontend): keep the Goal label whole at phone width and align the proofs with the new source line

The Goal label no longer breaks mid-word when Find goal and Use your prompt
squeeze the heading row: the label does not shrink or wrap, and the row wraps
at 760px and below so the buttons drop beneath it. A story at 400px proves the
label sits on one line.

The reader-state record and the on-demand editor proof now use the source
line's new wording.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(frontend): show the summary's phases collapsed by default

Every phase of What the session has done now starts closed, including
one that holds a departure. The opening paragraph stays open, and the
departure pills and failed-run count stay on the phase heading. A
phase the reader opens stays open through later boards. The departure
card's line that names the phase holding its evidence now opens that
phase before focusing its heading, so the jump lands on visible lines.

Records the reversal of "Phases arrive open" as a 2026-10-23 owner
amendment and updates the reader-state row.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(readme): say the summary phases start closed

The README still said a phase opens with its lines and cites, which you can
fold. Phases start closed since the last change, and a reader opens the
ones they want.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(sample): save the sample session a goal alone and read it again

The sample saved a goal and an expected outcome that said the same thing, so
its Drift card showed two departures on the same evidence. It now saves one
goal written as an end state, with a new reading made against it. The stored
summary is the earlier one, kept. The capture drops the cutoff sentence about
unsaved outcome lines, and a test holds the intent to a goal alone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(frontend): say how to reach the sample's page with your own session

The sample banner now lists the steps in order: open a session, set its goal,
press Analyze drift, read the departures and use Steer back. The tour drops
every expected-outcome reference and matches the new fixture and the closed
summary phases. The ended-session notice says what saving now does and what
Analyze still uses.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(reading): drop the unsaved-outcome clause from a goal-only reading

A goal-only reading said "Read against your goal; no expected outcome lines
were saved." Noting the absence of an optional field tells a reader nothing,
so it now says "Read against your goal." A reading stored before keeps its
stored sentence. The ended-session journey proof asserts the new notice.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(sample): make the summary and the reading in one run

The kept summary read as made from a replaced record, so the sample told a
reader to summarize again. The summary and the reading are now made together
from the same transcript, the sample-only cutoff rewrite is gone, and the tour
and the sample records match the new capture.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(frontend): show each cited entry in its own words on the Drift card

A departure card and the claims row draw every cited entry as one line in
its own words with its number, and drop the Evidence and Source folds that
repeated the same entries. The stored why and the harness limit move inline.

Implements [DRC-4960](https://linear.app/recce/issue/DRC-4960)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(drift): record that the citations are the source on the Drift card

Adds the 2026-10-23 owner amendment, and brings the sample tour and the
skill body in line with a card whose cited entries speak for themselves.

Implements [DRC-4960](https://linear.app/recce/issue/DRC-4960)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(frontend): draw every cited entry in its own words and drop the last Evidence fold

Quiet goal and outcome rows and a departure's titled entry now use the same
cited line as the claims row. The Evidence fold on quiet rows is gone; its
status qualifier, why, detail and limit draw in view. Stories assert no
Evidence or Source button on any row. The amendment no longer claims that
HOW_TO_USE.md was updated.

Implements [DRC-4960](https://linear.app/recce/issue/DRC-4960)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* build(frontend): rebuild the page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

---------

Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ated pull requests count as evidence (#643)

* chore(drift): open the Haiku live monitor branch

Signed-off-by: Jared Scott <jared.scott@infuseai.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(reading): amend DEC-15, DEC-26 and DEC-27 so the Live monitor may call Haiku

Record the owner's 2026-10-23 ruling as dated amendments: cadence evaluation
for the Live monitor only, while the session is viewed; the four follow-up
rulings; the lane, caps, TTL, stops and reload decisions; Analyze cancels a
tick without refunding its spend (decided). DEC-16 and DEC-26 item 5's
"raises nothing" stand. The live monitor row's prose in the reader-state
inventory now says the switch drives a server lease. No existing heading is
renamed.

Implements [DRC-4961](https://linear.app/recce/issue/DRC-4961)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(monitor): check a monitored Claude Code session with Haiku about once a minute

Add the server-callable check behind the Live monitor and its policy, with
no route that starts it yet:

- a monitor model setting (--claude-monitor-model) and validator that admit
  only an explicit Haiku id, defaulting to claude-haiku-5-5; the reading
  validator and its Sonnet floor are unchanged
- live_monitor: a window per monitored session, spacing of at least 60 s,
  the shared reading slot, a skip that costs nothing for an unchanged
  transcript, a closed reply shape held to the model-free level's floor,
  provenance on every tick, and cancel by Pause and by an Analyze press
- the monitor's own ledger and caps in reading_policy (30 ticks per session
  per window, a rolling 24 h cap across the board), apart from Analyze's
- a monitor notice kind with its "What is sent" copy
- a compass-release pre-flight item for bumping the Haiku default

Implements [DRC-4962](https://linear.app/recce/issue/DRC-4962)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(checks): count pnpm shorthand for a closed runner as a check

`pnpm vitest run X` now reads as the same check `pnpm exec vitest run X`
does, including a lone call piped to an output filter. A pnpm word that is
not a closed runner stays unread. Recorded as a dated DEC-23 amendment.

Implements [DRC-4970](https://linear.app/recce/issue/DRC-4970)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(drift): read a created pull request and a used skill as evidence

A Claude Code session that created a pull request now publishes a pull request
fact read from the structured `gitOperation.pr` on the shell result (repository,
number, URL, time), and one that launched a skill publishes the skill's name.
Neither reads command text, output or arguments. A reading is sent both under the
tool-output grant and may cite them as consistent, the goal-met verdict check
agrees on a reason that names a created pull request, and the Drift card's note
names the pull request it confirmed. Recorded as dated amendments to the closed
lists and the verdict amendment, with a shape-only capture taken against a stub
`gh`.

Implements [DRC-4969](https://linear.app/recce/issue/DRC-4969)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): disclose pull request and skill evidence and keep it out of cap artifacts

The consent sentence now names the repository and number of a created pull
request and the names of skills used, so a changed sentence asks again. Skill
and URL shapes are matched whole, dot-only repository segments are refused, a
created pull request is listed beside the entry cap and reserved like a pass,
skills rank after every counted entry so they cannot evict a written path, the
check scan sentence counts both, and the verdict check reads "PR 7", "PR (#7)"
and lists of pull requests. Contract fixtures are regenerated from the server.

Implements [DRC-4969](https://linear.app/recce/issue/DRC-4969)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* chore(build): rebuild the page bundle for the evidence fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(reading): rule the Haiku level's own None or low floor and correct the monitor amendment

Add the owner's 2026-10-23 floor ruling as its own amendment under DEC-26: a
model None or low stands when Haiku cites a turn it was sent and the estimate
shows no failed check and no unsettled later direction; no passing check does
not block it; the live-only shell blocker is not included. Correct the
amendment bullet that said every remaining decision was as recommended, say
that consent under the widening means "not paused", and mark the reader-state
row's server-lease sentence as applying once the switch is rebuilt.

Implements [DRC-4961](https://linear.app/recce/issue/DRC-4961)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(monitor): hold the Haiku level to its own floor and give the lease a clock

- the floor works both ways: a raised estimate is published over a lower model
  level, and a model None or low stands only with a cite and no failed check or
  unsettled later direction
- a success word in the reason is a verdict, however qualified
- each check re-sends the recent window beside its new turns, so a quiet turn
  cannot flip the level
- the notice and the disclosure name the commands of checks and the paths of
  files written, those rows are dropped where the destination cannot be named,
  and only this session's facts are sent
- the lease gets begin_window holders and renewal, a window accessor with the
  banner and deadline, an ended input, and an end that cannot be followed by a
  launch; a window that runs out drops its level
- an Analyze press waits long enough for a cancelled tick, turns new ticks away
  while it takes the slot, and retries once
- the in-flight limit counts one board, the prompt cap is 6,000 characters, and
  a ledger of its own cannot be combined with a job marker

Implements [DRC-4962](https://linear.app/recce/issue/DRC-4962)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(monitor): hold a Live monitor lease on the server and tick it while the session is viewed

Add POST /api/live-monitor with start, beat, confirm and stop, and a lease
table with a ticker that calls the Haiku check for a lease that is due:

- one lease per Claude Code session, held by any number of tabs, in memory
  only, so a restart starts with no lease
- a visible tab that stops beating is gone after 50 s with no stop post and no
  beacon; a hidden tab keeps the lease and pauses the checks
- the TTL clock is the server's: the question stands from 20 to 30 minutes, Yes
  resets it, No or no answer ends the lease
- the lease ends for ended, left, paused, unanswered, declined, stopped or cap,
  and the session's end is read from the row after the overlay pass
- start is refused for an ended session, another harness, the sample, paused
  model calls and --no-observer-model
- Pause ends every lease and kills a check in flight; closing the server stops
  the ticker
- the state is published on the focused context's sources.work and on no row
- SECURITY.md names what the monitor sends and the CLI-cache exposure at cadence

Implements [DRC-4963](https://linear.app/recce/issue/DRC-4963)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(drift): drive the Live monitor switch from the session view and disable it once the session ends

The switch now drives the server's lease instead of a key in the browser:

- turning it on posts start, a visible tab beats about every 20 seconds, a
  hidden tab says so once and stops, and turning it off, leaving the route or
  moving to another session posts stop, with a beacon as the page goes away
- a reload shows it off until it is pressed, and the live-estimate storage key
  is retired
- the pill and the card show the Haiku level labelled Live monitor · Haiku, an
  Analyze reading outranks it, and the estimate shows whenever the monitor has
  no level
- it is aria-disabled with its reason once the session has ended or while model
  calls are paused or off, and flips to off with a note if the session ends
  while it is viewed
- each end the server reports leaves a note in the Drift card, the monitor's
  own limit worded apart from Analyze's
- the hint names the model call, the cadence and the provider
- the sample session shows it on and never takes a lease

The two recorded answers it departs from are named in the design record and
neither golden is re-recorded.

Implements [DRC-4964](https://linear.app/recce/issue/DRC-4964)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(drift): ask "Do you still want to monitor this session?" after 20 minutes and turn the monitor off if nobody answers

The server publishes the question from 20 minutes after the monitor started,
or after the last Yes, until 10 minutes later; the page counts down to that
time on the clock it holds:

- Yes posts confirm yes, which resets the server's clock, and the next question
  comes 20 minutes on; No posts confirm no and turns the monitor off
- with no answer by the deadline the monitor turns off with a note saying why,
  and beats no more
- a lease that ends another way while the question is up clears it and shows
  that reason's note instead
- an answer from any tab clears the question in every tab, because the next
  state the server publishes carries no deadline
- the question is announced through the live region when it opens and is
  reached by keyboard
- the timings are the server's config; the shipped defaults are 20 and 10

Implements [DRC-4965](https://linear.app/recce/issue/DRC-4965)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* chore(build): rebuild the page bundle for the Live monitor lease, switch and question

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(monitor): never tick a hidden tab, keep one holder set, and answer a start without telling which sessions exist

- a start can say the tab is hidden, and a hidden holder is never ticked
- a beat or an answer from a tab that did not start cannot join the lease, so
  the holders here and in the check stay one set
- a check that cannot run is found before its inputs are built
- an ended session, one not on the board and one the board lists twice are
  refused in the same words, and a twice-listed session keeps its lease
- the ticker exits when no lease remains and returns with the next start, and a
  table made after the server closed starts none
- the route-count pin counts hyphenated routes, and SECURITY.md counts twenty
  ungated routes

Implements [DRC-4963](https://linear.app/recce/issue/DRC-4963)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): never tick a hidden tab from the page, keep the estimate after a pause or cap, and settle the question on the server's word

- the page asks whether the tab is hidden: a start from a hidden tab says so,
  each beat says what is so now, and a tab hidden by the time the start answers
  beats once as hidden and holds the lease without a visible cadence
- a start the reader left before it answered is not aborted, and its lease is
  stopped when it does answer
- a pause or the monitor's own limit leaves the model-free estimate as the
  level until the reader leaves or turns it on again; every other end shows
  nothing live
- leaving a session and turning the switch on forget the old lease, so its
  question and level cannot reach the new monitor
- at the question's deadline the page beats once and acts on the server's
  answer, so another tab's Yes is not undone
- the question is a labelled region and focus returns to the switch, or to the
  control that opens it, after an answer; the pill names the monitor
- copy: the hint and the monitor's line say what is asked and of whom, and the
  left, declined and cap notes and the unreachable refusal read as the owner
  worded them

Implements [DRC-4964](https://linear.app/recce/issue/DRC-4964)
Implements [DRC-4965](https://linear.app/recce/issue/DRC-4965)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* chore(build): rebuild the page bundle for the Live monitor fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* docs(monitor): say the Live monitor calls Haiku, what it sends and costs, and every way it stops

Reconcile the docs with the model-backed Live monitor, the lease, the switch
and the question, and with the pull request and skill evidence:

- README, the promise map, HOW_TO_USE, the skill and SECURITY.md say it asks
  Haiku about once a minute while a session is viewed, name what is sent and
  to whom, its own caps and each stop, and say its accuracy check has not been
  run yet
- Pause model calls lists the monitor, in the promise paragraph and its
  duplicate in the 2x2 items, and in a new dated amendment under the Pause
  amendment, whose heading is unchanged
- SECURITY.md names the monitor's model setting and that the estimate stays as
  its fallback; the skill documents the model flag and the lease route
- the reading's evidence lists name a created pull request and a used skill
- the estimate's docstrings say they are the monitor's model-free fallback
- a documentation test holds that no page or docstring says the monitor calls
  no model

Implements [DRC-4966](https://linear.app/recce/issue/DRC-4966)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): let a pull request or skill line say it was successfully done, and keep numbers stable

A consistent line resting only on a created pull request or a used skill no
longer has "successfully" or "confirmed" read as a stated verdict, which had
withdrawn the goal line on a live session. The words stay a verdict beside any
check, and every other success word still withdraws it. Pull requests and skills
take their places in a message's group from 1,000, so no check, file or launch
already numbered shifts when either joins the group.

Implements [DRC-4969](https://linear.app/recce/issue/DRC-4969)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): number a pull request or skill in time order like any other entry

Reverts the 1,000 place offset: a citation reading 4.1001 is worse for a reader
than the shift it avoided, and summaries and readings never read ordinals.

Implements [DRC-4969](https://linear.app/recce/issue/DRC-4969)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* feat(drift): let an analysis level rest on a created pull request or a used skill

An Analyze reading may draw None or low with no passing check when every
Consistent outcome line cites a recorded harness fact, a created pull request or
a used skill, beside the floor's unchanged blockers. The agent's words alone
still never lift the level. Recorded as a dated DEC-26 amendment extending the
monitor's floor, taken under the owner's standing instruction to act on
recommendations.

Implements [DRC-4969](https://linear.app/recce/issue/DRC-4969)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* chore(build): rebuild the page bundle for the analysis floor change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* fix(drift): drop the no-model-assessment line under a Live monitor level

A Haiku level is a model assessment, so the card no longer says there is
none. The line stays under the estimate. Also pins that a tab shown again
beats visible at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

* chore(build): rebuild the page bundle for the monitor card line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019324xLBLyAdr2QHFngRAro
Signed-off-by: Jared Scott <jared.scott@variable.team>

---------

Signed-off-by: Jared Scott <jared.scott@infuseai.io>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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