- components/InceptionCard.vue: the one form, two homes — mode="create" in
the New-project modal's second step (emits the choices; the create carries
`inception`), mode="decide" on ProjectView for the owner of an undecided
project (loads that project's defaults, records the decision). Always-on
rulebooks listed checked (uncheck = exclude), others unchecked (check =
subscribe), design system select, seed-Systems toggle (disabled once the
project has Systems). Tokens only; modal canon (#2855); .btn-* canon.
- ProjectView: the card while undecided, one "Inheritance decided <date> via
… · …" line after; onDecided refreshes the project.
- ProjectRulesTab: "Excluded always-on rulebooks" section with include-back.
- api/inception.ts (types, fetchInceptionDefaults, decideInception);
api/rulebooks.ts: ApplicableRules.excluded_always_on, exclude/include
wrappers; REST POST/DELETE /api/projects/<id>/exclusions/rulebooks/<rb>.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- MCP create_project(..., exclude_always_on_rulebooks, subscribe_rulebooks,
design_system_id (0 unstated / -1 none / n), seed_systems): any inception
arg → inception.decide(via="mcp") after the create; none → undecided with
an inception_hint. New decide_project_inception(project_id, …) records or
re-records; nothing given = an inherit-all decision, stated.
- enter_project carries `inception` ONLY for the caller's own, undecided
project: inception_ask() = the project's current defaults + what to ask the
operator once + the exact call (the #2683 ask shape). Absent otherwise.
- REST: POST /api/projects accepts `inception` (validated before the create);
POST /api/projects/<id>/inception decides/re-decides; GET …/inception/defaults
is the card's payload; GET project already carries inception via to_dict.
- _INSTRUCTIONS: ORIENT names the ask; START a project names the questions —
never create a project bare by default (product behaviour, P#119).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- inception.decide(user, project, choices=, via=): owner-only; validates the
choices (pure) and every target (owned rulebook / always-on for an
exclusion / readable design system) BEFORE any effect; then, each
idempotent: exclude always-on rulebooks, subscribe rulebooks, point the
design system (None = explicitly none), seed the standard Systems if asked
and the project has none; writes projects.inception LAST. Re-deciding is
additive for exclusions/subscriptions, replaces the design system, never
re-seeds.
- inception.current_defaults(): what binds if nobody decides — the ask's
payload (always-on / other rulebooks, standing exclusions + subscriptions,
design system + the choices, Systems count).
- services/systems.STANDARD_SYSTEMS (name + generic charter) + seed_standard_systems();
mcp/tools/systems names the same list in the bootstrap ask — one vocabulary.
- Integration tests: effects land and the record says why; bad targets apply
nothing; outsiders cannot decide.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A project that opted out of an always-on rulebook at inception must not see
it anywhere: list_always_on_rules(project_id=) and the subscription-derived
set skip it (an exclusion is total), get_applicable_rules / rules_payload
carry `excluded_always_on` as the seventh key so the departure is visible
wherever the rules are, and the SessionStart block built for a bound project
names it ("Excluded for this project by its inception decision …"). MCP:
list_always_on_rules takes project_id; exclude_always_on_rulebook /
include_always_on_rulebook mirror suppress/unsuppress (owner-only; the
rulebook must be always_on — subscribed rulebooks are left by unsubscribing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A project's inheritance becomes a decision, not a default (milestone 297).
- projects.inception (JSONB, NULL = undecided): {decided_at, decided_by, via
mcp|ui|legacy, choices {exclude_always_on_rulebooks, subscribe_rulebooks,
design_system_id, seed_systems}}; on to_dict.
- project_rulebook_exclusions: a project's opt-out of a whole always-on
rulebook — the sibling of the rule/topic suppressions, CASCADE both ways.
- services/inception.py (first cut): the vocabulary, validate_inception
(pure, all-or-nothing), normalize_choices, is_decided. Effects come in
step 3.
- Migration 0085 backfills every existing project via="legacy" with its
current standing (no exclusions, its subscriptions, its design_system_id,
no seed) so the ask fires only for projects created after this ships.
- Backup v10: rulebook_exclusions section; project rows carry inception and
design_system_id, restored in a post-pass once rulebooks/design systems are
mapped (design_system_id was not restored before — fixed in passing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First deploy of v26.08.21.1 showed two gaps:
- Every one-line CSS rule followed by a blank line hashed to sha1("") — the
declarations live on the selector line, which the #2872 "declarations only"
fingerprint dropped — so 68 unrelated one-liners across 17 files read as
one body-identical copy at the top of the derive readout. The selector
line's tail after "{" is now part of the hash; an all-blank remainder falls
back to the whole block.
- The proposer only examined unjudged rows, so consumers that were already
classified (auth.create_invitation → hash_token) never got a uses edge: 3
edges for hash_token after the first refresh. Judged rows are now scanned
for references (once per body), no proposal is made on them.
- 0084 migration docstring reworded: "function that …" at a line start parsed
as a definition (extractor false positive).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A shape can follow one convention canon AND call several helper canons; the
row's single snippet_id made the 2026-08 audit pick (hash_token won, the
service-function convention lost), and hook evidence — pulled a snippet, then
wrote code naming it — was stamped as instance when it is a uses fact.
- code_shape_uses (migration 0084): shape → snippet, basis, evidence; unique
per pair; cascades with both ends. USE_BASES: reference | hook | agent |
audit | import. A judgment-grade basis overwrites a mechanical one, never
the reverse.
- classify_shapes items and classify_shapes_by_rule take uses=[snippet ids]
(targets validated like snippet_id; all-or-nothing).
- The write-path hook writes a uses edge for every pulled canon the payload
names (the instance stamp is unchanged); the proposer writes a uses edge
for every canon a body names (reference_canons: kind + language family +
stoplist, same rules as the reference basis) — the mechanical form of
"auto-confirm own-import references" deferred from #2871.
- list_shapes(uses=N) lists the consumers of a canon; get_snippet's consumer
map gains `uses` beside instances/variants.
Operator decision on #2870 (2026-08-21): keep one snippet_id, add uses edges.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- code_shapes.reason_code (migration 0083): an optional code from a fixed
catalogue (scoped-css, one-off-handler, test-helper, convention-plumbing,
pure-helper, generated, script, typed-record) beside the prose reason, so
the ledger can be filtered/aggregated by kind of one-off; validated in
classify_shapes and classify_shapes_by_rule; on to_dict/to_compact.
- Snippet location lookups match repo case-insensitively in both dialects
(location_matches / location_jsonpath via like_regex flag "i") — "Scribe"
vs "FabledScribe" vs "fabledscribe" recorded free-form hid half the canon
from list_snippets(repo=, path=).
- coverage line names the next action: "top canon #N ×k" (biggest proposal
queue) and "top copy <label> ×files" (widest body-identical group).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Project 2 is bound to main, so every consolidation of the 2026-08 audit was
invisible to the ledger until the dev→main merge; the operator works on dev
(rule 1). repo_bindings.ref (migration 0082, nullable) is the branch the
coverage refresh reads; NULL keeps the forge default branch. set_binding takes
ref (name sets, "" clears, None leaves standing); bindings_for_project feeds
the refresh; bind_repo exposes ref ("-" clears). to_dict carries it.
Operator decision on #2873 (2026-08-21): per-binding ref, chosen at bind time,
default the repo default branch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 2026-08 audit consolidated identical bodies under different names and
files (five auth views' CSS, two Workspace formatDate()s, four modal blocks)
while the derive readout led with name groups (to_dict ×25, main ×5, load ×6)
that were convention or coincidence.
- proposal_summary ranks dup:<sha> groups above name groups, wider file spread
first, and carries `files`; scoped rows (#2869) are in the readout, since
that is where view-level copies live.
- extract_definitions fingerprints a CSS rule by its declarations — the row
identity already carries the selector — so .closed-msg / .error-block /
.success-msg with one body are one dup group. One-time effect: judged css
rows whose stored fingerprint predates this may show a recheck on the next
refresh (the judgment stands; re-confirm).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 2026-08 audit left 77% of Scribe's ledger `exempt`, most of it a Vue
component's scoped <style> rules and <script setup> functions — one-offs by
construction (unreachable from any other file) that add nothing when judged
one by one and bury the rows a person should look at.
- coverage: Definition carries its line; scoped_definitions() names, per
.vue file, every sym and every css rule inside <style scoped>; ArchiveShape
carries the flag.
- sync: such rows are stamped status=scoped / classified_by=mechanical with
the by-construction reason (history event recorded); un-stamped back to
unclassified if a later tree makes them ordinary; a judgment overrides.
- The machine still sees them: proposer, derive grouping, divergence, hook
evidence, canonical stamping and classify_shapes_by_rule's default all
treat unclassified + scoped as the unjudged set (_MECHANICAL_TODO). Only
the human todo (status=unclassified) and largest_gaps exclude them.
- accounting counts `scoped`; coverage line and the project card legend show
it; SHAPE_STATUSES gains it (no DB CHECK on status — no migration).
- shape-accounting skill documents the bucket; plugin 0.1.37.
Operator decision on #2869 (2026-08-21): keep extracting everything, stamp
mechanically, keep `exempt` a human judgment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 2026-08 audit judged 3,427 rows in 14 hand-driven batches through a raw
MCP client because a list_shapes page overflowed the tool budget and every
row had to be sent back one by one. Now:
- list_shapes(compact=True): path · symbol · kind · status · signature, plus
snippet_id / by / proposal / diverges_from / recheck only when set. A full
500-row page fits. CodeShape.to_compact() is the row shape.
- classify_shapes_by_rule(project_id, path, status, pattern=, kind=,
snippet_id=, reason=, via=, include_judged=): ONE judgment over every
unclassified live row under a directory whose symbol matches a glob;
judged rows are untouched unless include_judged; canonical is refused;
same gates as the row form; one transaction; returns count + sample.
shape_ledger.classify_shapes_where / rule_matches (pure) carry it.
Tests: compact row pinned, rule_matches directory/glob/kind semantics, the
tool mount, and an integration sweep (unclassified-only, include_judged,
gates).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 2026-08 audit examined 407 proposals. Every cross-language hit was wrong:
the Python MCP tool-module canon (symbol `register`) was offered for each auth
view's handleSubmit (it calls authStore.register()) and for the TS auth store's
own `register`; Minstrel/Forge TS canon matched Python bodies by resemblance.
Every cross-project semantic proposal was noise.
- Canon carries the snippet's language; match_canon skips a sym canon whose
family (py / js / css / sh / sql, by language ↔ by path extension) differs
from the shape's. Unknown on either side = no gate.
- The reference basis ignores a stoplist of generic verbs (register, load,
save, get, …): a bare mention is not a call site of THIS canon; the symbol
basis still catches a second definition, and the call-site relation moves
to `uses` edges with #2870.
- The semantic arm only reaches canon in the shape's own project and family;
symbol/text still reach family canon elsewhere (note 2786).
- _PROPOSER_VERSION 2 → 3 so standing proposals re-examine on the next refresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- assets/auth-shared.css: the five auth views carried byte-identical scoped
copies of the page/card/brand/footer/field/input/error rules (~60 lines
each); they now load one stylesheet the way the editors load
editor-shared.css. .closed-msg/.error-block/.success-msg (identical bodies)
are one .auth-note; the form rules are scoped under .auth-card so nothing
leaks into the rest of the app.
- api/client.apiErrorMessage(e, fallback): the one place the {"error"} envelope
is unpacked; replaces ten six-line `"body" in e` catch blocks.
- utils/dateFormat: fmtDate / fmtStamp / fmtLogStamp replace eight local
formatDate/formatTime copies (three byte-identical pairs); the file’s old
Calendar/Home helpers had no callers and are gone. useRelativeTime gains
relativeTimeOrDate for the two workspace panels’ identical variant.
- components.css now owns the .modal-* shape (overlay/card/title/message/
actions/btn/primary/danger). It was copied into four views and lived in
editor-shared.css, which ConfirmDialog — styleless, teleported to <body> —
silently depended on: opened from SnippetDetailView before any editor view
had loaded, it rendered unstyled. Views keep only their own overrides.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- background.start_periodic(interval, work, label=) replaces the three hand-rolled
while-True/sleep/try loops in logging, auth and notifications.
- services/scheduler.ScheduledJob replaces the four private BackgroundScheduler
copies in recurrence/version_pinning/trash/db_maintenance schedulers; public
start_/stop_/reschedule_ surfaces unchanged.
- api_keys.hash_token is the one sha256 helper; auth.py used to inline it 5x.
- auth.is_registration_open reads via settings.get_admin_setting; notification
prefs read via settings.get_setting; _fire_share_email uses _get_user_email.
- projects.get_project_summary / milestones.get_project_milestone_summary are
now the one-id view of their batch siblings instead of a second copy of the
queries; sharing.best_permission_by (was _deduplicate_by_permission) is the
one rank-dedup, now also used by list_projects_for_user.
- backup: the row builders for every section both exporters carry are named
functions, so a column added to one export cannot silently miss the other.
- iso() from models.base replaces the attr.isoformat()-if-attr-else-None idiom
and db_maintenance._iso across services; backup keeps its explicit shape.
- trash.py hoists the sql_delete/timedelta imports it re-imported per function.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reading the 28 route modules against each other and against the MCP tools:
- routes/notes.py carried a PUT and a PATCH handler that were the same
function minus the supersedes contract on one of them — one handler now
serves both verbs, so both carry it.
- The two _attach_supersession copies (REST + MCP) become
supersession_svc.attach_relations(uid, note_id, data, hint=) — the seam
the two surfaces must agree through; only the agent surface adds the
one-sentence reading hint.
- Three local _uid() wrappers over g.user.id → scribe.auth.get_current_user_id
like every other module; design_systems' private _not_found → routes.utils.
not_found; the four "********" literals → settings_svc.SECRET_MASK with the
read/write contract written once.
- routes/plugin.py: the project_id/repo resolution block and the
comma-separated id parse were copied into three endpoints — _project_scope()
and _int_list() now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reading the 16 tool modules against each other: the six-key applicable-rules
block (applicable_rules, applicable_rules_truncated, subscribed_rulebooks,
project_rules, suppressed_rules, suppressed_topics) was hand-built in five
places — enter_project, get_project, get_task (legacy plans), get_milestone
(three of the six) and services/planning.start_planning. rulebooks_svc.
rules_payload() is now the one place that names them; get_milestone gains the
three it lacked, so every rules-carrying payload reads the same. list_rules /
list_always_on_rules share _rule_summary. mcp/auth.resolve_bearer_to_user_id
duplicated resolve_bearer's parsing and had no product caller (only its own
tests) — removed; the tests now exercise resolve_bearer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reading all 28 models against each other: 54 `x.isoformat() if x else None` /
`x.isoformat()` expressions in 23 to_dict methods, in two guarded/unguarded
wordings, become iso() from models/base.py — uniform, and a row read before
flush serialises as null instead of raising. Rulebook / RulebookTopic / Rule
carried byte-identical copies of TimestampMixin's two columns;
InvitationToken / PasswordResetToken / NoteUsageEvent carried CreatedAtMixin's —
all six now use the mixin. AppLog and RetrievalLog keep their explicit
created_at, commented: their composite index orders on `created_at.desc()`,
which needs the column object in the class body. Schema-neutral (same column
definitions) — no migration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second pass over the tests/ ledger after bbee0d0. fake_record(**attrs) is the
one MagicMock-with-real-attributes builder (to_dict mirrors them; the
note-2109 hazard documented once); fake_note/fake_task/fake_snippet/
fake_project/fake_milestone/fake_system/fake_rulebook/fake_topic/fake_rule
carry each model's ordinary defaults on top of it, replacing 14 per-file
factories (two rulebook trios in tool-vs-service wordings, _fake_task, _fake_ms,
_fake_project, _plan_note, _fake_snippet, two _snippet adapters now one-liners
over fake_snippet). FakeMCP replaces the five closure-over-a-list registrar
fakes (+ _Recorder); loc() and design_token_stub() replace the paired _loc /
_token / _T stand-ins; every hand-built async_session mock (9 helper defs and
14 inline copies) now starts from make_mock_session(). Call sites rewritten by
AST with each file's former defaults made explicit, so behaviour is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shape ledger showed the same test scaffolding defined over and over:
_bind_user x12 (byte-identical), _dispose_engine x10 in three wordings,
_no_supersession x3, _make_mock_session x7 in three subsets, a get-or-create
User helper x2 (+3 inlined), and fifteen hand-rolled MagicMock note factories
each re-explaining the same "an auto-MagicMock attribute is truthy" hazard
(note 2109).
Now: conftest.py carries _bind_user / _dispose_engine / _no_supersession as
opt-in fixtures (pytestmark = usefixtures(...) per module, so unit tests pay
nothing), and tests/helpers.py carries make_mock_session(), ensure_user() and
fake_note(**attrs) — the hazard documented once, real values on every
attribute the product reads. Call sites were rewritten by AST so titles with
dashes and commas survived; the three SimpleNamespace _note stand-ins that
only feed a single function stay local.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First live run: a canon whose recorded code opens with a call-site example
(confirmed()'s onTrash) made every `async function x(): Promise<void>`
resemble it at 0.86, and the write-path semantic floor paired alembic
upgrade()/downgrade() bodies with unrelated canons at 0.68-0.75. Rows now
remember (body, ruleset) so a tightened rule looks again once.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every judgment now goes through one helper that remembers the fingerprint
judged (classified_sha) and writes a code_shape_events row; the sync writes
vanished / reappeared / drifted events and flags recheck_at when a body
moves under an instance/variant. The refresh flags diverges_from on shapes
new since the previous computation that sit where one canon dominates the
judged siblings of their directory+kind and were not proposed as that canon
(a first seed flags nothing); the write-path hint asks the same question
in-band for the shapes the hook names. list_shapes(flag=divergence|recheck),
shape_history(project_id, path, symbol) (read-only), coverage line/payload/
card carry divergent + recheck. Backup v8 carries the history. Plugin 0.1.36.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shapes now carry a content fingerprint (signature + whitespace/comment-
insensitive body_sha; migration 0080) and the proposer runs inside the
coverage refresh, the one moment bodies exist: symbol elsewhere → textual
containment → body references the canon → signature resemblance → semantic
(capped per refresh, unreached rows stay unexamined for the next). A hit is
a proposal on the row (proposed_snippet_id/basis/score), never a
classification; rows with no canon hit group by the derive-first rule
(identical body in ≥2 places, same name in ≥3 files) as proposal_basis=
derive + a group key. list_shapes(proposal=any|canon|derive|<basis>) is the
queue; confirm_shape_proposals(project_id, snippet_id|path|basis) confirms
in batches as agent instances; any classify_shapes/hook stamp retires the
proposal. Readout carries proposed + derive_groups (line, payload, card).
Plugin 0.1.35 (skill: the machine proposes, judgment classifies).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The prior-art hook now names the shapes being written (shapes=kind:name —
every definition in the payload, or the one enclosing an Edit found by
walking the file upward) and the server stamps them as instance rows when
the session PULLED a snippet inside PULL_WINDOW that the payload references
by symbol or that the semantic arm scored for this very payload.
classified_by=hook, evidence in reason; never overrides a judgment or a
canonical row, overridable by classify_shapes. Offered-but-unopened stamps
nothing. Pulled-and-already-seen snippets stay in the semantic query as
evidence without re-entering the deduped menu. A brand-new shape gets a
provisional row the next sync confirms or vanishes. Read-scoped keys get
the hint, never the stamp. Plugin 0.1.34.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The UI Refresh button must not be the only seed path (operator directive,
hit live: the P7 backfill stalled waiting for a click). Three parts:
- enter_project fire-and-forgets refresh_if_stale on the project OWNER —
absent or day-old readouts recompute in the background (same spawn the
webhook path uses), the enter stays fast, forge-less owners exit quietly
(rule #115 baseline), and an in-flight guard keeps concurrent enters from
fetching the same tarball N times.
- refresh_pattern_coverage(project_id): the synchronous agent-facing form —
write-gated, owner-keyring resolution, and ValueError messages that name
the fix (add a connection / bind_repo) instead of measuring nothing
silently.
- plugin 0.1.33 ships the shape-accounting skill: the five statuses, the
seed/todo/judge loop, and the derive-first rule, triggered by the
coverage line or any proved code-to-canon relationship.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
verify_snippet docstring: consumers enumerated while checking are
classify_shapes instance rows; detail keeps the WHY and what changed,
nothing that belongs in a row. The MCP _INSTRUCTIONS REUSE bullet gains
"classify shapes against canon — a consumer map is rows, never prose"
(budget rebalanced to 1,998/2,000: the rules bullet lost its historical
push-optimisation clause, the task-note line tightened). The reusing-code
skill gains the full contract: instance/variant/exempt with required
reasons, list_shapes(status=unclassified) as the standing todo, and the
derive-one-first rule for repeating shapes with no canon. Plugin 0.1.32.
The operator-side halves of this step live outside the repo: the Drift
Audit process gained step 7 (classify what the walk proved) and the DRY
Pass s9 now sends consumer maps to the ledger with the note keeping only
the narrative. The Forge P7 backfill payload is enumerated and parked on
task 2790 — it fires after the next deploy + coverage refresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>