Commit Graph
1740 Commits
Author SHA1 Message Date
bvandeusen f15542d6cb Merge pull request 'Merge dev → main: family precedents say their reason once; the repeat trigger compares like with like' (#208) from dev into main
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 1m37s
CI & Build / TypeScript typecheck (push) Successful in 58s
CI & Build / Python tests (push) Successful in 2m20s
CI & Build / Build & push image (push) Successful in 22s
2026-10-06 19:00:18 -04:00
bvandeusenandClaude Opus 5.5 5323a1fbc2 fix(family): the repeat trigger compares a record only with its own kind - a snippet with snippets, a note with notes (#5130)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m38s
CI & Build / Python tests (push) Successful in 2m16s
CI & Build / Build & push image (push) Successful in 32s
A dated release log (a note) matched a signed-APK CI snippet at 0.83 on
shared release vocabulary and was opened as a family candidate, then came
back as a precedent. One chunk of a long narrative scores like the shape
it mentions, so a cross-kind score does not mean "the same thing made
twice". The search is filtered to the new record's kind and the loop
checks it too. Code built from an idea written elsewhere still reaches the
engine through the citation trigger and the shape ledger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:32:55 -04:00
bvandeusenandClaude Opus 5.5 f006f15058 fix(family): a decision read as precedent says its reason once - to_brief for precedents and idea history; the decision log keeps the full row (#5131)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m13s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 40s
Each decision stored its reason three times (reason, before.reason, after.reason)
and a promotion carried the whole criteria reasoning in evidence. Precedents
(assess, promote, get_family_adoption, get_family_idea) and get_family_idea's
history now serve FamilyDecision.to_brief(): reason once, before/after as status,
canon_version and platforms, evidence as the cited list. list_family_decisions
and the decision echoed back by a write keep to_dict. The precedent count was
already bounded at five.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:30:48 -04:00
bvandeusen b3c0cc5548 Merge pull request 'Merge dev → main: a family idea links to the rule topic holding its norms' (#207) from dev into main
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 1m26s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / Python tests (push) Successful in 2m10s
CI & Build / Build & push image (push) Successful in 1m7s
2026-10-06 18:00:52 -04:00
bvandeusenandClaude Opus 5.5 07169e1274 feat(family): an idea links to the rule topic holding its norms - set_family_topic, the readouts name the rules, the idea row shows the link (milestone 463 step 1 gap, found in step 7, #4993)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 1m9s
CI & Build / integration (push) Successful in 1m21s
CI & Build / Python tests (push) Successful in 2m2s
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / Build & push image (push) Successful in 1m29s
Step 1 added family_ideas.topic_id but nothing could set it, so the note<->topic
link the milestone promised existed only as a column. set_family_topic links or
unlinks (0) a topic in a rulebook the caller owns, one idea per topic; the
version does not move. get_family_idea and get_family_adoption list the topic's
rules; list_family_ideas and the Family page name the topic. The model
docstrings no longer claim the topic's rules are platform-scoped in retrieval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:27:33 -04:00
bvandeusen ecbadf51ce Merge pull request 'Merge dev: family canon, milestone 463 steps 1-6' (#206) from dev into main
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m13s
CI & Build / Python tests (push) Successful in 1m57s
CI & Build / Build & push image (push) Successful in 30s
2026-10-06 14:11:28 -04:00
bvandeusenandClaude Opus 5.5 b8efff43b4 fix(family): the reach tests assert on their own corpus; the shared integration database holds other tests' canon on the same platform (#4992)
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m8s
CI & Build / Python tests (push) Successful in 2m4s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / Build & push image (push) Successful in 34s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:35:42 -04:00
bvandeusenandClaude Opus 5.5 07e21c7ff9 feat(family): family canon reaches the session - entry readout, retrieval reach, skill, report cue (milestone 463 step 6, #4992)
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / Python tests (push) Successful in 2m39s
CI & Build / Build & push image (push) Skipped
CI & Build / integration (push) Failing after 1m53s
- enter_project carries a `family` key, but only when the project has
  something to answer: counts of unassessed, owed and to-recheck answers,
  each with the list_family_adoptions call that lists it. It shows on every
  entry, never by platform touch: entry is when work is chosen, and an
  unanswered idea is otherwise invisible.
- Retrieval: a widened project search (include_global_kinds) now also
  reaches the canon ideas on the project's platforms. It also reaches their
  references in the project's languages, or all of them when none matches.
  An off-platform project gets none, and the plain project filter (the
  duplicate gate) is unchanged.
- Closing a task returns `family_owed`, the owed answers filed while it was
  open, and the report cue asks for them to be named.
- New plugin skill family-canon (moment work.record) covers when to
  evaluate a promotion, answering in order, what counts as a reason, the
  precedent reflex and the conflict order. _INSTRUCTIONS, create_note,
  create_snippet, classify_shapes and reporting-back point at it.
  The plugin version is minted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:27:39 -04:00
bvandeusenandClaude Opus 5.5 5f41dbd283 feat(family): the shape ledger judges against family ideas, across languages (milestone 463 step 5, #4991)
CI & Build / TypeScript typecheck (push) Successful in 58s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 1m26s
CI & Build / Python tests (push) Successful in 2m9s
CI & Build / Build & push image (push) Successful in 1m27s
- classify_shapes takes idea_id: the shape is judged against the idea's
  reference in its language, or its first.
- A shape classified against a canon idea's reference moves the project's
  adoption row (instance -> adopted, variant -> variant). Withdrawing the
  shapes that gave an answer returns it to unassessed. This runs on
  classify_shapes, the sweep, confirm_proposals and the coverage refresh.
- assess and undo refuse an answer the shapes contradict. A conflict
  resolution re-judges the variant shapes on both sides.
- The proposer's family arm offers a canon idea's reference on a shared
  platform, in any language. It proposes only when the reference is the top
  hit over every readable snippet and scores at least 0.70. The pairs this
  was measured on are recorded beside _FAMILY_FLOOR. The proposer is now v6.
- The matrix cell shows the code that answers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:06:45 -04:00
bvandeusenandClaude Opus 5.5 233fa3eea9 fix(family): the owed task is tagged by the matching System only, and the ground fixtures build without duplicate keys (#4990)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Successful in 1m57s
CI & Build / Build & push image (push) Successful in 44s
assess_family_adoption took system_ids for the task it files, which made
the family tool module look like a System-tagging door and tripped the
parity registry. It is not one: the owed task is tagged to the project's
System matching the idea's canonical area, and update_task retags it.
The unit test for each conflict ground passed evidence/conditions twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:11:41 -04:00
bvandeusenandClaude Opus 5.5 201e09901b feat(family): the adoption ledger - assessment, the conflict order, owed->task, recheck, the adoption matrix (milestone 463 step 4, #4990)
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 1m0s
CI & Build / integration (push) Successful in 1m8s
CI & Build / Python tests (push) Failing after 1m28s
CI & Build / Build & push image (push) Skipped
- services/family_adoption.py: assess one project against one canon idea by
  the four outcomes in order (exempt, variant, adopted, owed). Every outcome
  needs a reason and adopted needs evidence. The engine records the precedents
  itself: this idea's answers elsewhere, and this project's answers to the
  nearest ideas. The same answer given twice records nothing.
- owed files a task in the OWING project, tagged to the System matching the
  idea's canonical area, naming the gap and the reference for that project's
  language. The task follows the answer: adopted closes it, exempt or variant
  cancels it, owed again reopens it. Each move is logged on the task.
- the conflict order is enforced: every ground above the deciding one must
  say why it did not decide. The losing side is folded into the idea's note as
  a trap, an alternative or a condition branch, the version moves, and both
  rows are answered against the revision.
- recheck is derived (row version != idea version). family.revise moves the
  version when substance changes. undo covers a project's latest answer too.
- set_family_references names the reference implementations.
- MCP: get/list/assess adoption, resolve_family_conflict, revise_family_idea,
  set_family_references. Web: GET /api/family/matrix.
- UI: an adoption matrix on /family (platform filter, cell detail with reason,
  recheck and owed-task link) and the same matrix narrowed to one project on
  its Family tab. The decision log now reads project-level decisions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:05:00 -04:00
bvandeusenandClaude Opus 5.5 faa1b72307 fix(family): undoing the decision that created an idea leaves no applicability or scope behind (#4989)
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / Build & push image (push) Successful in 46s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 56s
CI & Build / Python tests (push) Successful in 1m56s
When the undone decision had no recorded before-state, the synthesized prior copied the canon idea's current applies_when and platforms into a retired row. Before that decision the record was not an idea, so the restored state now has neither. Caught by test_undoing_a_promotion_restores_the_prior_state_and_keeps_judged_rows (run 8281).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:37:37 -04:00
bvandeusenandClaude Opus 5.5 8aacc1824c feat(family): the promotion engine - triggers, the three criteria, the decision log and undo (milestone 463 step 3, #4989)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Failing after 1m5s
CI & Build / Python tests (push) Successful in 1m58s
CI & Build / Build & push image (push) Skipped
The agent promotes a family idea when all three criteria hold, and no person approves it. The criteria are product text: services/family.py states them, and the promote tool's docstring names every criterion the service enforces.

- Criteria: each one vetoes on its own when its reasoning is blank. Platform terms also needs an applies-when and a platform scope; proven also needs named evidence. A veto keeps the idea a candidate and is logged, so it becomes precedent.
- Precedent: every promotion stores the decisions on the nearest ideas by meaning, plus any the caller names.
- Promotion sets canon, the applicability test and the platform scope, and opens an unassessed ledger row for each member project the promoter can write. Re-promotion moves the version past every version the idea has held.
- Retire and undo: undo reverses only the latest idea-level decision, restores its recorded before-state, and logs itself with the undone decision as its precedent. Settled here: leaving canon closes the unassessed rows but keeps the judged ones, which read as needing a recheck after a re-promotion.
- Triggers open evaluations but never promote:
  - a cross-project lineage citation ("matching #N") on a note or task write;
  - a same-meaning record in another project on a shared platform, on create, at 0.80 (measured: the known pattern's builds scored 0.79-0.82, an unrelated project's best match 0.65);
  - a milestone closing on a platform.
  Each fails open and rides the response as family_hint.
- Doors: seven MCP tools, /api/family REST endpoints, and a Family page (nav, /family) showing the criteria, the ideas, and the decision log with undo and retire.
- utils/recordHref.ts holds the one copy of "where a record opens", now shared with LessonDetailView.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:33:30 -04:00
bvandeusenandClaude Opus 5.5 1774ee3696 fix(frontend): each shared <style src> sheet sits at one block index everywhere, so vite build cannot depend on transform order (#4988)
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m40s
CI & Build / Python tests (push) Successful in 2m37s
CI & Build / Build & push image (push) Successful in 56s
Run 8271 failed in the image build: "[vite:vue] Cannot read properties of undefined (reading 'scoped')" on moments-shared.css. plugin-vue caches one descriptor per src file and answers ?index=N from whichever component registered it last. RuleEditorSlideOver had moments-shared at block 2, while its other two importers have it at block 1. Step 2's new imports changed the transform order and exposed the bug.

- RuleEditorSlideOver: moments-shared is now loaded with an @import instead of a third <style src>.
- rules-shared.css sat at block 0 in the five panes and at block 1 in LessonDetailView, LessonEditorView and RuleEditorSlideOver, which is the same latent hazard. The three move to block 0.
- tests/test_frontend_shared_styles.py: a guard that every <style src> sheet sits at one index, plus a test that the guard can fail. vue-tsc cannot see this hazard, and vite build only runs after every test has passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:58:41 -04:00
bvandeusenandClaude Opus 5.5 07d2542479 feat(family): platforms declared at inception and detected from bound repos (milestone 463 step 2, #4988)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 21s
CI & Build / TypeScript typecheck (push) Successful in 1m10s
CI & Build / integration (push) Successful in 1m48s
CI & Build / Python tests (push) Successful in 2m40s
CI & Build / Build & push image (push) Failing after 44s
A project's platforms decide which family ideas reach it. This step makes membership answerable from every door:

- services/platforms.py: the global catalog (writes are admin-only and duplicate-gated by slug); pure marker detection; and membership reads and writes. Detection only ADDS, and only where nobody has answered. It never overrides a declared or rejected row and never removes one.
- coverage: the archive scan now carries every path, and the refresh runs detection fail-open.
- inception: a platforms choice (slugs, or null for unanswered). The list is the whole answer: members left out of it become rejected.
- MCP: list_platforms and set_project_platforms; enter_project and get_project carry the project's platforms.
- REST: /api/platforms (admin writes) and /api/projects/<id>/platforms.
- UI: a platforms checklist on the inception card, a Family tab on ProjectView, and a Platforms admin tab in Settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:51:28 -04:00
bvandeusenandClaude Opus 5.5 e68ccc5884 feat(family): platforms, family ideas, the adoption ledger and its decision log (milestone 463 step 1, #4987)
CI & Build / TypeScript typecheck (push) Successful in 1m11s
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 25s
CI & Build / integration (push) Successful in 1m50s
CI & Build / Python tests (push) Successful in 2m41s
CI & Build / Build & push image (push) Successful in 1m8s
When one project solves something every project on the same platform will
meet, that solution becomes family canon and every other project on the
platform answers it. This is the storage for that.

- platforms: a global catalog in the canonical_systems shape, seeded with
  generic technology names and the file markers step 2's detection reads.
- project_platforms: declared / detected / rejected. A rejected row is kept
  so detection cannot re-add what a person said no to.
- family_ideas: a note's family state. No new record type; any note, snippet
  or lesson becomes an idea. A canon idea must state when it applies (CHECK).
- family_idea_platforms: the only scope source. A linked rule topic takes its
  scope from the idea, so the two cannot disagree.
- family_idea_references: reference implementations, explicit not inferred.
- family_adoptions: one answer per (project, idea). Variant and exempt require
  a reason (CHECK). Recheck is derived from the two canon versions, never
  stored.
- family_decisions: the append-only log, with a required reason and the
  earlier decisions each one followed. The agent decides with no approval
  step, so precedent is what keeps its calls consistent.

Backup v25 carries all seven: platforms by slug, precedent ids remapped
through the decision map. Both column guards cover the new tables, and a
real-Postgres test exercises the CHECKs and the restore remaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:33:27 -04:00
bvandeusen d4de0b9b54 Merge pull request 'Merge dev: scope-then-rank searches (#4958, #4961), CI gate on integration, milestone 456 steps 4-6' (#205) from dev into main
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 59s
CI & Build / Python tests (push) Successful in 1m52s
CI & Build / Build & push image (push) Successful in 18s
2026-10-05 22:23:48 -04:00
bvandeusenandClaude Opus 5.5 ccbccb025c fix(retrieval): every semantic search scopes first, then ranks - the note, milestone and system searches join the rule search on one shared shape, _rank_scoped (#4961)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 17s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m0s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 27s
Ordered straight off a *_embeddings table, the planner walks the HNSW index,
takes ~ef_search (40) nearest chunks across every owner and project, and only
then applies the scope: an in-scope record behind 40 nearer ones the caller
cannot see was silently dropped. #4958 fixed the rule search alone; the note,
milestone and system searches kept the fault.

_scoped_chunks builds the in-scope chunks with their distance; _rank_scoped
materializes them as a CTE and ranks exactly. All four searches go through it,
and the row shape is unchanged, so callers and mocks are untouched.

Tests: a structural guard that every semantic_search_* ranks through
_rank_scoped and orders nothing itself (with a replay of the old shape), a
compiled-SQL check of the MATERIALIZED CTE, and integration crowd tests - 80
nearer out-of-scope records - for notes (another user; the reader own other
project), milestones and systems.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:08:45 -04:00
bvandeusenandClaude Opus 5.5 d2fa723373 refactor(retrieval): the rule builders compose one moment - _rule_moment runs the arm and its via-lesson step for all three (milestone 456 step 6, #4908)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m1s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 28s
build_prompt_rule_hint, build_tool_rule_hint and build_write_path_hint
each ran the rule arm and then the via-lesson step by hand, the first two
passing the query between them through a _via_query key on the payload.
They now call one composer, _rule_moment, and the split helpers
(_prompt_rule_hint, _tool_rule_hint, _add_rules_via_lessons) and the side
channel are deleted. Output shapes are unchanged: the prompt builder
returns no checkpoint key, the tool builder always does, and
shown_rule_ids stays the direct band.

A structural test pins that only _rule_moment runs a rule arm or the
via-lesson step, and that the three builders are its only callers.

plugin_context.py: 2,418 -> 2,361 lines this step; 3,558 when the
milestone began.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:25:47 -04:00
bvandeusenandClaude Opus 5.5 869046dda2 refactor(retrieval): the specs are the registry - SURFACES, the ranked POINTS rows and RANKED_SOURCES are read off the pipeline specs (milestone 456 step 5, #4907)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 59s
CI & Build / Python tests (push) Successful in 2m1s
CI & Build / Build & push image (push) Successful in 27s
Each arm spec now carries its tuning pair (Surface, moved verbatim into
retrieval_pipeline) and a Declared block - what the telemetry readout
must know and cannot read off its rows. The two ranked stages that are
not arms (preference_slot, rule_via_lesson) are RankedSource specs, and
the note slots carry their own declaration.

- retrieval_surfaces.SURFACES = the TUNED_ARMS tuning, same order
- retrieval_registry.POINTS ranked rows = one per spec in RANKED; the
  lookups, asked, ambient and pull rows stay declared there
- rule_usage.RANKED_SOURCES = RULE_RANKED_SOURCES + moment_rule

Settings keys, defaults, prose and order are unchanged (checked field by
field against HEAD). tests/test_retrieval_specs.py pins the derivation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:11:20 -04:00
bvandeusenandClaude Opus 5.5 2b8f41229d refactor(retrieval): the notes arms run on the one pipeline - auto_inject, its reuse and lesson slots, the write path by meaning, and rule_via_lesson are specs (milestone 456 step 4, #4906)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Successful in 1m52s
CI & Build / Build & push image (push) Successful in 35s
retrieval_pipeline gains the notes half: NoteArm / NoteSlot / NoteMoment /
NoteIO / NoteResult and run_note_arm, which writes once the stages both
notes arms copied: search, withhold this response's own menu (#3739),
fresh/repeat split, the call row before any return (#3497, #3752), the
band, the reserved slots in their order (reuse evicts, lesson extends),
and the surfacing rows. The note renderer (_record_kind, _menu_name,
_menu_passage, the seen pointer, menu_entry) moves with it, and
run_via_lesson_arm takes rule_via_lesson.

Behaviour-preserving, with flags for today's differences: notes still log
BEFORE the band and rules after it (step 7's question). One deliberate
change: a notes arm now fails open like the rule arms, so a failing
search costs its lines and no longer the whole hook response.

The I/O is resolved from plugin_context at call time (_note_io), so the
existing patches keep working. The review re-run reads the AUTO_INJECT
spec instead of restating it, and its guard now compares the two live
searches. The registry declares the pipeline's notes fan-out sites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:02:37 -04:00
bvandeusenandClaude Opus 5.5 4b1060ae8b Revert the deliberate integration failure - the gate was watched rejecting run 8218 (rule 177, #4958)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / integration (push) Successful in 53s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m53s
CI & Build / Build & push image (push) Successful in 19s
Run 8218: integration failure, Build & push skipped, :dev still package
version 11098 (23:36:29Z, run 8217), and no image tagged 9a76eb0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:42:04 -04:00
bvandeusenandClaude Opus 5.5 9a76eb0a34 test(ci): TEMPORARY deliberate integration failure, to watch the publish gate reject a red run (rule 177, #4958)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Failing after 1m1s
CI & Build / Python tests (push) Successful in 1m51s
CI & Build / Build & push image (push) Skipped
Reverted next. Expect: integration red, Build & push skipped, :dev unmoved,
no image tagged with this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:38:13 -04:00
bvandeusenandClaude Opus 5.5 17f4348711 ci: the image build waits on the integration lane (rule 177, #4958)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 56s
CI & Build / Python tests (push) Successful in 1m53s
CI & Build / Build & push image (push) Successful in 18s
The integration job was added after the build gate (c6211e5) and never
joined its needs, so main run 8212 published :latest with integration red.
Both jobs carry the same ref condition, so the gate cannot be satisfied by
a skipped integration run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:34:16 -04:00
bvandeusenandClaude Opus 5.5 983fd2c4d1 fix(retrieval): scope the rule search before ranking it - an in-scope rule is no longer lost behind other owners' nearer rules (#4958)
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 20s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m11s
CI & Build / Python tests (push) Successful in 2m0s
CI & Build / Build & push image (push) Successful in 29s
Ordered straight off rule_embeddings, the planner walked the HNSW index,
which returns about hnsw.ef_search (40) nearest chunks across every owner
and project and filters by home only afterwards. A reader's own rule ranked
past the 40th chunk overall was silently dropped - on a shared install,
other users' rules fill those 40. This is the likely cause of the
intermittent test_integration_rule_scope failures, whose axis-vector
fixtures sit far from every real embedding in the graph.

The in-scope chunks now go through a MATERIALIZED CTE, which the index
cannot order, so the ranking over them is exact. A rulebook is hundreds of
chunks; exact is cheap. New test: 80 nearer rules belonging to someone else
no longer hide the reader's one rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:30:05 -04:00
bvandeusenandClaude Opus 5.5 79cd2341a6 test(rules): the scope test says why a rule search returned nothing - an error or an empty scan (#4958)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Successful in 1m6s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 18s
semantic_search_rules fails open, so both failures on run 8209 and main
run 8212 read as set() with no trace. Assert report["searched"] and carry
the swallowed traceback into the failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:34:16 -04:00
bvandeusen 25c1c778f1 Merge pull request 'Moments: skills teach moments, and a mount that misfires proposes its own removal (milestone 458 steps 8, 7b)' (#204) from dev into main
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 59s
CI & Build / Python tests (push) Successful in 1m53s
CI & Build / Build & push image (push) Successful in 26s
2026-10-05 18:29:53 -04:00
bvandeusenandClaude Opus 5.5 4e1320120d feat(moments): a mount that keeps arriving where it does not apply proposes its own removal (milestone 458 step 7b, #4955)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m19s
CI & Build / Python tests (push) Successful in 2m3s
CI & Build / Build & push image (push) Successful in 18s
The open-after-moment signal proposes a mount; nothing proposed taking one
off, so a wrong mount was noise at every occurrence until someone happened
to notice. rule_misfired(rule_id, moment, why, reached_by) records a report
against a MOUNTED pair, counted per distinct day (the MCP door carries no
session id) on a new rule_moment_judgments.misfire column (migration 0119,
backup v24). At three days the response carries a line asking the agent to
offer the operator the fix - reject takes the rule off, unmap_action stops
the action reaching the moment, confirm keeps the mount and stops the
asking - and Settings > Moments lists it as an unmount proposal with the
reasons and the actions that reached it. A re-mount clears the count.

Taught in moments.md, missed-retrieval.md and the reply hold's wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:19:59 -04:00
bvandeusenandClaude Opus 5.5 8afd6da8af docs(plugin): reference files point at each other in words, not links - one level deep (milestone 458 step 8, #4926)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 59s
CI & Build / Python tests (push) Successful in 2m11s
CI & Build / Build & push image (push) Successful in 41s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:44:22 -04:00
bvandeusenandClaude Opus 5.5 a72a422534 docs(plugin): the instruction surfaces teach moments - reading a line that arrived at one, correcting a misfire, and giving a new rule its moments (milestone 458 step 8, #4926)
CI & Build / Python lint (push) Successful in 13s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m38s
CI & Build / Python tests (push) Failing after 1m59s
CI & Build / Build & push image (push) Skipped
Until now only the tool arguments knew moments existed. The guidance
surfaces described rules as reached by resemblance alone:

- using-scribe: a short reflex paragraph and a new reference file,
  moments.md. It covers reading "at <moment>, reached by <action>", the
  reply held once at reply.report, map_action / unmap_action offered in
  one line, and the step 7 proposal line answered with judge_rule_moments.
- writing-records: asks WHEN a rule applies as well as what it is about.
  A rule, preference or process about a point in the work gets
  moments=[...] as it is written, and the trigger stays as the net.
- missed-retrieval: a missed WHEN is mounted or mapped, not reworded. A
  misfire is unmounted or unmapped.
- _INSTRUCTIONS: one clause (list_moments; mount rules about WHEN),
  1594 of 1600 chars.
- static context: injected lines include the rules mounted on a moment
  that was reached.
- test_guidance_ownership: three owned topics, so the text cannot quietly
  drop out.

Plugin minted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:39:47 -04:00
bvandeusen 2b52afcd72 Merge pull request 'Mount the corpus by proposal: a pass and an open-after-moment signal (milestone 458 step 7)' (#203) from dev into main
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 17s
CI & Build / Python tests (push) Successful in 1m59s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m12s
CI & Build / Build & push image (push) Successful in 22s
2026-10-05 16:31:42 -04:00
bvandeusenandClaude Opus 5.5 c404a3127a test(retrieval): the proposal endpoints are routed on the app (milestone 458 step 7, #4925)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Successful in 2m17s
CI & Build / Build & push image (push) Successful in 56s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:16:59 -04:00
bvandeusenandClaude Opus 5.5 570d1b6d5a test(backup): register the rule_moment_judgments builder in the import column guard (milestone 458 step 7, #4925)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 56s
CI & Build / Python tests (push) Failing after 1m44s
CI & Build / Build & push image (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:12:30 -04:00
bvandeusenandClaude Opus 5.5 dfcf4df2e9 feat(moments): mount the corpus by proposal - a pass and an open-after-moment signal, both stopping at the operator (milestone 458 step 7, #4925)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / integration (push) Failing after 43s
CI & Build / Python tests (push) Failing after 46s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Build & push image (push) Skipped
A rule written before moments existed is mounted on nothing. Step 7 records,
per (rule, moment), whether it belongs there and who said so:

- rule_moment_judgments (migration 0118, backup v23): suggested / confirmed /
  rejected, from a pass, the signal, or an edit. Moment "" is "no moment fits".
- The pass: rules_to_mount lists unjudged rules; propose_rule_moments records
  suggestions that mount nothing; rule_moment_proposals and
  judge_rule_moments put them to the operator. A confirm mounts, a reject is
  kept so the pair is never proposed again. Same service behind REST and a
  "Waiting on you" panel in Settings > Moments.
- Edits are judgments: set_rule_moments, the one mount write path, confirms
  what was added and rejects what was removed in the same transaction.
- The signal: scribe_moment.sh keeps a per-session acts ledger; when a rule
  is opened, scribe_record_opened.sh sends the last three minutes of it to
  /api/plugin/rule-opened. The acts resolve through the install's mappings;
  work.run and work.change are not evidence. Counted per distinct session
  with lesson_rules' evidence model, and once due the open returns one line
  asking the reader to offer the mount.
- scribe_session_end.sh removes the session's scribe-moment files.

Plugin 2026.10.05.2003.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:03:53 -04:00
bvandeusen 2db9e0c0c1 Merge pull request 'Skills and processes declare their moments, and the UI for moments (milestone 458 steps 5–6)' (#202) from dev into main
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m9s
CI & Build / Python tests (push) Successful in 1m59s
CI & Build / Build & push image (push) Successful in 19s
2026-10-05 15:41:56 -04:00
bvandeusenandClaude Opus 5.5 5bcc603310 fix(moments): the store loads rather than fetches, so the deadline guard does not read it as a bare fetch (milestone 458 step 6, #4924)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 52s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 2m2s
CI & Build / Build & push image (push) Successful in 45s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:47:12 -04:00
bvandeusenandClaude Opus 5.5 b73a689849 feat(moments): the human door onto mounts, mappings and per-moment telemetry (milestone 458 step 6, #4924)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 18s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / Python tests (push) Failing after 1m34s
CI & Build / Build & push image (push) Skipped
CI & Build / integration (push) Successful in 2m7s
Everything an agent can do with moments, a person can now see and change
in the app.

- Rule editor: a moment picker beside the trigger. Catalog moments are
  ticked; a named procedure's `skill.<name>` is typed and checked as the
  server checks it. `moments` is always sent, so unticking the last moment
  unmounts the rule.
- Settings, Moments section (General tab): for each moment, what it
  means, the actions that reach it on this install (shipped ones can be
  switched off, the install's own removed), how many rules are mounted on
  it, and deliveries and agent opens over the window. Below that: named
  procedures with mounts, switched-off defaults with Restore, and a form to
  add an action.
- retrieval_telemetry.moment_usage: per moment, `delivered`, `rules`,
  `opened` (agent pulls after the first delivery there; an upper bound, as
  by_source is) and `last_delivered_at`. No ratio, because a mount is a
  person's statement, not a ranker's guess. Guarded on its own, and also
  reported in retrieval_summary as `moment_usage`.
- rulebooks.mount_counts; mounted_moments now derives from it.
- GET /api/retrieval/moments carries `mounted` and `usage` (?days=).
  DELETE /moments/mappings also reads the mapping from query parameters,
  since the browser's DELETE sends no body.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:42:30 -04:00
bvandeusenandClaude Opus 5.5 c489452a2d test(moments): an install removal still drops its tool while the skill loader stays listed (milestone 458 step 5, #4923)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 1m2s
CI & Build / integration (push) Successful in 1m20s
CI & Build / Python tests (push) Successful in 2m12s
CI & Build / Build & push image (push) Successful in 33s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:28:50 -04:00
bvandeusenandClaude Opus 5.5 f1fdc4a951 feat(moments): skills and stored processes declare the moments they are for (milestone 458 step 5, #4923)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m3s
CI & Build / Python tests (push) Failing after 1m25s
CI & Build / Build & push image (push) Skipped
Loading a procedure now also reaches the moment it is for. Loading the
reporting procedure is a report; loading the release procedure is a delivery.

- Bundled skills: each SKILL.md declares `metadata: moments:`. The same
  declaration ships as Skill defaults (BUNDLED_SKILL_MOMENTS), because the
  server never sees the plugin's files. test_skill_moments holds the two
  together and pins the plugin name that qualifies the skill.
- Stored processes: `moments` on create_process and update_process, stored
  in the note's data and returned by get_process. A `scribe-proc-<slug>`
  load resolves its process through the sync manifest at load time. The
  moments are not copied into the stub, which would go stale mid-session.
- reachable_tools lists the skill loader whenever anything is mounted, since
  a process's moments are known only when it loads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:23:24 -04:00
bvandeusen 9fc2df080d Merge pull request 'Rules mount on moments, and one retrieval pipeline (milestones 458 steps 1–4, 456 steps 1–3)' (#201) from dev into main
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 1m0s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 19s
2026-10-05 12:41:56 -04:00
bvandeusenandClaude Opus 5.5 b29689d4de fix(retrieval): declare the reply moment's surfacing site in FAN_OUT_SITES (milestone 458 step 4b, #4922)
CI & Build / integration (push) Successful in 50s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / Python tests (push) Successful in 1m50s
CI & Build / Build & push image (push) Successful in 40s
moment_delivery records the mounted half's surfacing under
source=rp.MOMENT_RULE_SOURCE. That is an attribute the registry's
extractor cannot resolve, so the site is now declared with the value it
emits, read from the pipeline's own constant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:32:07 -04:00
bvandeusenandClaude Opus 5.5 c6cdfc2172 feat(moments): the reply moment holds a finished reply for one read (milestone 458 step 4b, #4922)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / integration (push) Successful in 1m1s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Python tests (push) Failing after 1m25s
CI & Build / Build & push image (push) Skipped
The reply is the one act no tool call marks, and it is where "let me
know if it works" gets said. A new Stop hook (scribe_reply_check.sh)
sends the finished reply to POST /api/plugin/reply-rules, which checks
it twice:

- mounted: every unopened RULE on reply.report, plus reply.ask when the
  reply asks a question. Deterministic.
- semantic: the reply's head and tail against every rule's trigger, on a
  new ranked surface, reply_rule. It is the backstop for whatever the
  earlier arms missed. Its floor is its stop bar (default 0.80, budget
  1), with its own Settings dials. The new stop_only stage records
  surfacing for the rule that holds and nothing else, because nothing
  else reached anyone.

Following the operator's ruling from 456 step 8, a rule that holds blocks
once, in the server's words. The hook blocks only on a reason it was
given, so an unreachable instance never stops a session, and it never
holds the rewrite. The ledger is the act checkpoint's own, so a rule
holds a session once across both doors and the per-session cap counts
both.

The turn reader moved from the report check into scribe_defs.sh
(scribe_turn_facts / scribe_turn_fact), so the two Stop hooks read a
turn the same way. The output was checked identical on a real transcript.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:30:07 -04:00
bvandeusenandClaude Opus 5.5 b3b616b20a fix(moments): the tools import the delivery module, and the tool-list cache leaves the swept directory (milestone 458 step 4a, #4922)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 49s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / Python tests (push) Successful in 1m48s
CI & Build / Build & push image (push) Successful in 32s
Two guards caught 7865313:

- test_mcp_tool_processes reads every coroutine in a tool module's
  namespace as a tool, and a name-imported attach_moment_rules looked
  like one. All seven modules now call moment_delivery.attach_moment_rules,
  and the parity guard accepts the attribute form.
- The session-ledger convention: a file in a swept directory must be a
  .ids ledger. The tool-list cache describes the install, not the
  context, so it moves to its own directory, scribe-moment, where a
  compaction does not sweep it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:24:30 -04:00
bvandeusenandClaude Opus 5.5 78653130d6 feat(moments): mounted rules arrive when their moment happens, through every door (milestone 458 step 4a, #4922)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 57s
CI & Build / Python tests (push) Failing after 1m19s
CI & Build / Build & push image (push) Skipped
A rule mounted on a moment now reaches the session when an act reaches
that moment, with no semantic match involved:

- run_moment_arm on the pipeline: a lookup, not a ranked search. Each
  line names the moment and the act that reached it ("at work.deliver,
  reached by `git push`"), so a misfire is visible where it lands and
  can be unmapped in-session. A repeat is cited, not quoted; fresh
  rules are recorded surfaced under source moment_rule with the moment
  in detail. No retrieval_logs row, as for the other lookups, so no
  latency is persisted for this arm.
- rule_scope: a rule's home clause, moved out of semantic_search_rules
  so the moment lookup scopes by the same one.
- rulebooks.rules_on_moments / mounted_moments.
- The plugin door: a catch-all PreToolUse hook (scribe_moment.sh). It
  keeps /moment-tools' answer on disk for five minutes, so a call to a
  tool that cannot reach a mounted rule sends nothing, and an install
  that has mounted nothing sends one request per window. It shares the
  rules ledger with the other arms and fails open silently.
- The MCP door: Scribe's own tools named by the shipped mappings carry
  moment_rules in their response, so a client without the plugin gets
  them too. The hook skips those tools. A guard pins the attach on
  every one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:21:45 -04:00
bvandeusenandClaude Opus 5.5 cc26054437 feat(moments): rules mount on moments, through every rule door (milestone 458 step 3, #4921)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m0s
CI & Build / Python tests (push) Successful in 1m51s
CI & Build / Build & push image (push) Successful in 29s
rule_moments (migration 0117) records which moments a rule arrives at, by
catalog name, cascading with the rule. rule_detail, the one seam every
rule door already returns through, gains moments beside system_ids: None
leaves the mounts alone, a list replaces them. get_rule and both
list_rules doors read them back, batched per page.

All five MCP rule/preference writes and the three REST ones take moments
and validate them before their create or update. An unknown name is
refused with the catalog listed and leaves no half-made rule behind; a
parity test pins that ordering on every door.

Backup v22 carries the mounts as a join table remapped through the rule
map; a real-Postgres round trip checks they land on the restored rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:03:51 -04:00
bvandeusenandClaude Opus 5.5 cf3de5bae1 feat(moments): actions map onto moments, with in-session corrections (milestone 458 step 2, #4920)
CI & Build / Plugin hooks (push) Successful in 18s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m23s
CI & Build / Python tests (push) Successful in 2m0s
CI & Build / Build & push image (push) Successful in 36s
moment_actions.resolve(tool, input) names every moment a call reaches and
the action that reached it. One call can reach several: kubectl apply is
a run, a deliver and a reach outside the workspace. Command tools match by
how each segment of the line starts, with a word boundary; other tools by
field=value arguments. The MCP server prefix and case are ignored.

56 shipped defaults cover the harness tools, Scribe tools and common
command shapes. moment_mappings (migration 0116) holds what an install
adds and the defaults it switches off. A removal is a stored row, so an
upgrade does not switch the default back on.

Per the operator ruling, corrections happen in the session: map_action
and unmap_action (write tools) return now_reaches so the fix can be
confirmed in the same reply. list_moments now shows each moment's
actions on this install. REST mirrors both doors, recorded as human.
Backup v21 carries the mappings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:58:00 -04:00
bvandeusenandClaude Opus 5.5 2ff7f2f34f feat(moments): the moment catalog rules will mount on, readable in-session (milestone 458 step 1, #4919)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 58s
CI & Build / Python tests (push) Successful in 1m53s
CI & Build / Build & push image (push) Successful in 32s
Fourteen generic moments of work (session.start, work.start … reply.ask)
plus the skill.<name> family, each with what it means and the kinds of
action that reach it, written for any kind of work rather than software
alone. The catalog is code because every install needs the same mount
points; which actions reach a moment is per-install data (step 2).

require_moment refuses an unknown name with the catalog listed, so a
typo cannot become a mount that never fires. list_moments (read-only)
and GET /api/retrieval/moments hand out the same catalog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:50:45 -04:00
bvandeusenandClaude Opus 5.5 0720ab6dcf refactor(retrieval): the completion-report preferences run through the pipeline (milestone 456 step 3, #4905)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m45s
CI & Build / Build & push image (push) Successful in 28s
reply_preferences.completion_preferences was the last hand-written
rule search + record_retrieval + record_rule_surfaced triple outside the
pipeline. It is now REPORT_PREFERENCE, a RuleArm with kind="preference"
over COMPLETION_QUERY, read back as records rather than as lines.

- RuleArm gains `kind`. _ranked asks the ranker for that kind and drops
  anything else on the way out, before the row is logged.
- RuleResult gains `shown`, the (score, rule) pairs behind the lines, for
  callers that return records.
- RuleMoment.project_id may be None: logged as given, searched as
  `project_id or None`. report_preference rows keep their NULL project.
- Guards: reply_preferences now has 0 direct rule searches. The registry
  constant-source example moves from report_preference to preference_slot,
  the pipeline slot that records through a module constant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:38:05 -04:00
bvandeusenandClaude Opus 5.5 b72c9a92e2 test(retrieval): two source guards read the pipeline where the rule arms now live (milestone 456 step 2, #4904)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 49s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / Python tests (push) Successful in 1m45s
CI & Build / Build & push image (push) Successful in 30s
CI run 8137 had two failures, both source-scanning guards whose property
moved to the pipeline. The property itself still held:

- test_every_surface_name_is_a_real_telemetry_source looked for
  source="<name>" literals. The rule arms now record under their spec field,
  so the spec is the join key, read from RULE_ARMS.
- test_every_hook_rule_search_says_which_project_it_is_for counted 4 direct
  searches in plugin_context. It now expects 0 there, so a copied arm fails
  the count. It also asserts that the pipeline has exactly one search and
  that its keyword literal carries project_id and never everywhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:07:26 -04:00
bvandeusenandClaude Opus 5.5 f6b824b214 refactor(retrieval): the three rule arms run through one pipeline (milestone 456 step 2, #4904)
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / Python tests (push) Failing after 1m28s
CI & Build / Build & push image (push) Skipped
CI & Build / integration (push) Successful in 1m15s
prompt_rule, pre_tool_rule and the rule half of the write path each wrote
the same steps out by hand: search, band, split fresh from repeats, log the
call before any early return, reserve a slot, render, record surfacings.
The copies drifted, and #3497, #3750 and #3752 were fixed one copy at a time.

- New services/retrieval_pipeline.py. run_rule_arm runs the stages in one
  order. RuleArm is the spec (source, band, compact_tail, checkpoint,
  preference_slot). RuleMoment is the query and the session ledger.
- The I/O (ranker and recorders) is passed in as RuleIO. plugin_context
  resolves it from its own names at call time, so existing patch points
  still apply.
- _rule_band, _rule_hint_line, checkpoint_for, checkpoint_reason, the band
  constant and the preference slot moved into the pipeline unchanged.
  plugin_context re-exports them.
- A recorder that raises now costs only its row, never a rendered line.
- The flags reproduce today exactly. Whether prompt_rule should band, and
  whether the act arms should reserve a preference, are step 7.
- Registry: the pipeline call sites are declared in FAN_OUT_SITES, with
  values read from the specs.
- The #3497 structural guard now checks the one implementation, and that
  plugin_context writes no rule-source row of its own.

plugin_context.py: 3,558 -> 2,980 lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:01:23 -04:00