#834. The pre-compaction complement to the shipped post-compaction re-grounding
banner. Because Scribe records progress as you go (task status, work-logs,
decision notes), a compaction at a clean work-seam is lossless — so guide the
model to recommend it proactively rather than letting auto-compact fire mid-task.
Placed in the ALWAYS-loaded channels (operator wants it consistently in context,
not relevance-gated like a skill): MCP _INSTRUCTIONS (every handshake) + the
static SessionStart floor (every session, MCP-independent). Behavior: at the end
of a block of work in a long session, ensure in-flight state is logged, then tell
the operator it's a safe moment to /compact (naming what was logged); recommend
at seams, not every turn; the model can't run /compact itself.
plugin.json 0.1.8 → 0.1.9 so clients re-pull the static-context change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the Phase 5 follow-up: rules now get the same update-over-create
gate. Title-based only (rules aren't a semantic-retrieval/RAG surface), scoped
to the same topic (rulebook rule) or same project (project rule). force=true
overrides; fail-open like the note/task gate.
Deferred-item decisions (operator): REST/web gating SKIPPED (kept MCP-only —
humans rarely double-create and a hard block needs UI affordance); orphan scope
kept orphan↔orphan (no change). So this rule gate is the only remaining build.
- services/dedup.py: find_duplicate_rule(title, topic_id|project_id).
- create_rule + create_project_rule: force param + gate.
- tests: rule title match, scope-required guard, tool gate (block + force).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Phase 5 gate added a DB query before every create_note/create_task. When
that query fails (DB unreachable, etc.) the create must NOT error — a dedup
check is advisory infrastructure, not a correctness gate. Wrap the title query
so any failure degrades to "no duplicate found" and the create proceeds.
Also fixes 7 existing create tests that don't mock the DB: they now exercise
the fail-open path (no Postgres in the unit-test job) instead of erroring.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#755 Phase 5. create_note / create_task now BLOCK a near-duplicate instead of
silently inserting: they return {"duplicate": true, "existing_id", message}
pointing at the record to UPDATE. Fights store bloat and stale competing copies
that semantic search (RAG) would otherwise resurface for reconciliation. A
force=true override creates anyway for genuinely-distinct records.
- services/dedup.py: find_duplicate_note — two signals, scoped to owner + same
project + same kind: (1) normalized-title exact match (cheap, always); (2)
semantic cosine ≥ 0.90 but ONLY when body ≥ 200 chars (short/title-only
embeddings false-positive — the pre-pivot lesson). Project-less (orphan)
records compare only to other orphans on BOTH signals (orphan_only on the
semantic call) — they're not matched across every project.
- Gate wired into the MCP create_note/create_task tools (the LLM write path)
with force override; _INSTRUCTIONS documents the duplicate response + force.
- Opt-in by design: the service helper is only called from the interactive
create tools. Internal/programmatic creates (recurrence spawn, imports) go
straight through services.create_note and are NOT gated — a recurring task
spawning its next same-titled instance must not be blocked.
- Scope v1: MCP tools only. REST/web (human CRUD, needs a UI affordance) and
create_rule (not a RAG surface; _INSTRUCTIONS already steer it) are follow-ups.
- tests: dedup service (title/semantic/body-gate/type-filter) + tool gate
(blocks, force bypasses) for notes and tasks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#755 Phase 4. Saved Scribe Processes (DRY pass, Drift Audit, …) now surface as
auto-triggered Claude Code skills instead of pull-only get_process calls.
Design correction vs the plan: stubs live in the USER's ~/.claude/skills/, NOT
plugin/skills/_instance/. The plugin is git-cloned and identical per install, so
instance-specific generated files can't ride in it; personal skills are
live-detected within the session (verified via claude-code-guide). MCP prompts
were the alternative but are pull-only (no relevance auto-surface), so skills are
the right primitive.
- backend: GET /api/plugin/processes manifest (services/plugin_context.
build_process_manifest) — {name, slug, description} per Process; description is
the auto-surface trigger (title + preview); slugs deduped, blanks skipped.
- plugin: scribe_sync_processes.sh writes ~/.claude/skills/scribe-proc-<slug>/
SKILL.md (body = "call get_process(name), follow verbatim") and PRUNES stale
scribe-proc-* stubs. Fail-open + silent; a transient fetch failure never wipes
existing stubs. Runs as a 2nd SessionStart hook + via the /scribe:sync command.
- plugin.json 0.1.7 → 0.1.8; README updated.
- tests: build_process_manifest (render, slug dedupe, blank-title skip, preview
truncation). Sync script's write+prune validated in isolation (plugin/** is not
CI-covered): correct stubs created, stale pruned, unrelated skills untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_create_task_passes_kind asserted create_task forwards kind=plan; the
hard-retire guard now rejects that. Exercise passthrough with kind=issue
instead. (Service-level create_note still accepts task_kind=plan by design —
the guard lives at the user-facing tool/route layer, not the primitive.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit of the plugin + MCP surface after milestone-as-plan (T3): every path
that could still create a kind=plan task or describe the old plan-task model
is now aligned with the hard-retire decision.
- create_task (MCP + REST POST /api/tasks): reject kind=plan with a message
pointing to start_planning. The 'plan' enum value stays valid so legacy
plan-tasks remain readable; update paths never touch kind, so they round-trip.
- create_task / get_task docstrings: 'plan' dropped from creatable kinds;
get_task's rules-augmentation noted as legacy-only (get_milestone for new plans).
- skills/writing-plans: rewritten for milestone-as-plan (body = design, steps =
child tasks, get_milestone to read back).
- skills/using-scribe: "plans live in milestones via start_planning", not kind=plan.
- TaskEditorView Kind selector: offers Work/Issue; "Plan (legacy)" shown only
when the loaded task is already kind=plan (display round-trip).
- test: create_task rejects kind=plan.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The milestone becomes the plan container: a new nullable milestones.body
holds the design/intent (Goal/Approach/Verification) and individual steps
live as first-class child tasks (milestone_id) instead of checkboxes crammed
into one kind=plan task body. start_planning now creates a MILESTONE seeded
with the body template (not a kind=plan task) and returns it with applicable
rules; a new get_milestone MCP tool reads the plan back (body + steps + rules).
kind=plan is hard-retired going forward — start_planning never creates one.
The 'plan' task_kind enum value stays valid so the 11 historical plan-tasks
remain readable in place; no body-shredding backfill (corpus review showed
auto-splitting their checklists into tasks would be lossy: embedded code
blocks, a non-binary [~] state, tables, ID-encoded hierarchy).
- migration 0066: add milestones.body
- model/service/route/MCP: body passthrough on create+update; get_milestone
- server _INSTRUCTIONS: "plan" = milestone w/ body + child step-tasks
- UI: ProjectView shows/edits a milestone's plan body; start_planning expands
the new milestone and opens its plan editor
- tests updated to the milestone contract + new body/get_milestone coverage
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The TypeScript-typecheck job intermittently failed at 'Cache npm download
cache' (transient cache-backend hiccup), which skipped install + type check and
marked the run red — 3x during the issues+systems build, all on pushes the
cache step had no bearing on. continue-on-error: true degrades a cache failure
to 'install without cache' instead of failing the job.
Closes the rerun churn from task #828.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In the full task editor (TaskEditorView) sidebar:
- Kind selector (Work / Plan / Issue), mirroring the Status/Priority selects.
- Systems multi-select (checkboxes of the project's systems, fetched via the
systems store), shown when a project is set.
Both wired through load (prefill from task.task_kind / task.systems), dirty
tracking, and save (kind + system_ids via the store's IssueFields). No new
colors — existing sb-field/sb-select tokens.
Deferred: the arose-from (provenance) picker — least-critical control and the
riskiest (task-search UI); the field is already supported by API/store/route for
a later add. NEEDS operator browser verification (CI typechecks only).
Refs plan 825 (S4b editor).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the REST gap S4b's UI needs (S2 only extended MCP tools):
- routes/tasks.py: create/update accept system_ids (set-semantics) + arose_from_id;
GET/create/update return the task's associated systems. kind=issue already
flowed via task_kind. Associations set via services/systems (ACL-checked;
can_write_note already gated).
- services/dashboard.py: _open_issues section (owner-scoped, ranked like other
task lists, capped) added to build_dashboard. Dashboard test updated for the
new key.
Refs plan 825 (S4b, backend half).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vue-tsc TS2345: System.color is string|null, but updateSystem's data param
typed color as string, so the store's Partial<Pick<System,...>> wasn't
assignable. Widen the param's color to string|null (clearing a color is valid).
Refs plan 825 (S4a).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frontend foundation for Issues + Systems (spec #825, S4a).
- frontend/src/api/systems.ts: typed client (System + list/create/update/delete)
over /api/projects/<id>/systems, matching the rulebooks api style.
- frontend/src/stores/systems.ts: Pinia store keyed by project (fetch/create/
update/archive/unarchive/delete), toast-on-error.
- frontend/src/components/SystemsSection.vue: a Systems management section —
cards (color swatch, name, description, 'N open' issue-count badge) with
inline create/edit, archive (hidden behind a 'show archived' toggle), and a
delete-confirm modal. v1 quality: loading skeleton, empty state, error toasts,
keyboard a11y, focus rings; reuses existing CSS tokens (no new colors).
- ProjectView.vue: new 'Systems' tab (between Notes and Rules), rendering
<SystemsSection :project-id>, wired like the existing rules tab.
S4b (next) adds issue-editor controls (kind=issue/system multi-select/arose-from),
open-issues lists, and the dashboard surface. NEEDS operator browser verification
(CI typechecks but can't render).
Refs plan 825 (S4a).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Third slice of Issues + Systems (spec #825).
routes/systems.py (nested /api/projects/<id>/...): GET/POST systems (list adds
per-system open_issue_count via one grouped query), GET/PATCH/DELETE a system
(GET returns records split into issues/tasks/notes), GET .../systems/<id>/records
(kind/open_only filters), GET .../issues (project's open issues for the project
view + dashboard roll-up). login_required; project access via get_project_for_user;
writes gated by can_write_project (clean 403); system.project_id verified to match
the path. Blueprint registered in app.py.
services/systems.py: + open_issue_counts_by_system (one grouped query) and
list_issues (project issues, open by default).
Tests: structural (blueprint registered + in app, handlers callable, service
contracts take user_id) — matches the house route-test pattern.
Refs plan 825 (S3).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second slice of Issues + Systems (spec #825).
New mcp/tools/systems.py: create_system, list_systems, get_system (records
split into issues/tasks/notes), update_system (incl. archive via status),
list_system_records (kind/open_only filters), delete_system. Registered in
register_all; read tools (get_system, list_systems, list_system_records) added
to the read-only-key allowlist (write tools default-deny).
create_task/update_task: kind now accepts 'issue'; new system_ids (set-semantics
associations) and arose_from_id (provenance, 0=unchanged/-1=clear) args.
create_note/update_note: new system_ids arg (notes associate with systems too).
services/notes.create_note: arose_from_id passthrough (update_note already
handles it via setattr).
Tests: MCP system tools + create_task issue-wiring (kind/provenance/systems),
service layer mocked.
Refs plan 825 (S2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First slice of the Issues + Systems feature (spec #825, plan #819 T2).
Schema (migration 0065):
- task_kind CHECK expands work|plan -> work|plan|issue (same-change, rule 36)
- notes.arose_from_id: optional self-FK for issue->originating-task provenance
(distinct from parent_id sub-task hierarchy)
- systems: per-project, self-describing (name + description) subsystem/area
- record_systems: M2M join linking any note/task/issue to systems (mutable)
Models: System + RecordSystem; note.py gains arose_from_id (+ index, to_dict).
Service services/systems.py: CRUD, archive, soft-delete, set/list associations,
records-for-system, open-issue count — all gated via services/access.py project
permissions (rule 78, no bare-owner filters). Unit tests lock the ACL gating;
the migration is exercised by CI's integration lane (alembic upgrade head).
is_task stays a derived property (status is not None) — unchanged. T1 (typing-
axis rationalization) intentionally NOT bundled; this only adds the enum value.
Refs plan 825 (S1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Superpowers was uninstalled but its replacements were never built (only
using-scribe shipped) — a live functional hole. Author the 4 the operator
wants back, each integrated with Scribe's toolset rather than generic copies:
- writing-plans -> start_planning / kind=plan task, not local .md
- systematic-debugging -> capture issue (symptom->cause->fix, tag issue) on resolve
- verification -> log results to the task work-log; honest done
- brainstorming -> recall prior thinking first; capture the decision note
Skipped TDD + receiving-code-review per operator (well-covered by Claude/them).
Manifest + using-scribe list now advertise only the 4 that ship. Remove the
stale docs/superpowers/*.md reference in _INSTRUCTIONS (superpowers is gone).
Plugin 0.1.6 -> 0.1.7.
Refs plan 821 (Phase 3 of 755).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deliver 'don't silently lose work at compaction' via the mechanism that
actually works. Verified contract: a PreCompact hook CANNOT make the model
flush to Scribe (host hooks can't trigger model tool calls, and can't know the
in-flight task ids), and its additionalContext only shapes the one-shot summary.
The correct tool is SessionStart scoped to source=compact, which fires AFTER
compaction and injects context the model reads.
Our SessionStart hook is matcher-less, so it already fires on compact — it just
said nothing compaction-specific. Now it reads the stdin event and,
when source==compact, leads with a banner telling the model to reload the active
project + in-flight tasks from Scribe and reconcile half-remembered state.
Durable path = record-as-you-go (A4/B8) + this post-compaction reload.
Refs plan 812 (A7); supersedes the literal 'PreCompact hook' idea.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Finish the breakfix/issue-logging gap as a lightweight convention: when
recording a solved problem, capture symptom -> root cause -> fix and tag it
'issue' so it's findable instead of re-diagnosed. Pairs with the B9 trigger
('log when a problem is found'). No schema change — a structured note_type/
task_kind=issue is deferred to a joint schema pass with B7.
Refs plan 812 (B8 convention; B7 deferred).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fail-open but no longer silent. When the dynamic context fetch yields nothing,
append a short status line to the injected context so a session can tell
'couldn't load live context' apart from 'Scribe had nothing to say':
- endpoint+token present but fetch empty/failed -> 'instance unreachable / request failed'
- endpoint present but token absent -> fingerprints the known Claude Code
userConfig export gap ('API token did not reach this hook')
A fully unconfigured install (no url AND no token) stays quiet — static-only is
the intended mode there. Static Tier 1 still always carries the mandate.
Refs plan 812 item A2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the '(This instance's rules carry the specifics.)' pointer — universal
_INSTRUCTIONS must not assume this install has a particular rulebook. State the
ACL principle on its own so it holds for any Scribe install/fork.
Refs plan 812 (instance-agnostic product principle).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MCP instructions are domain-neutral except a thin layer of dev vocabulary
and one project-specific paragraph (B10 audit, task 812). Make the data store's
own instructions serve any domain, and add the missing positive write-mandate.
B9 (neutralize):
- 'before writing code' -> 'before you dive in'
- Note examples 'dev-logs' -> 'logs of what happened'
- record trigger 'a merge, a shipped feature, a finished plan' + 'dev-log note'
-> 'finishing a task, or hitting/discovering a problem that changes direction'
(folds in B8: log pivots, not just wins; mirrors the static-tier wording)
- recall examples 'ticket/dev-log' -> 'task/prior note' (server + SKILL.md)
- 'Engineering and workflow rules' -> 'Workflow and standards rules'
- slim the 'developing Scribe itself' ACL paragraph to a neutral one-liner
(project-specific specifics already live in rules #47/#78)
A4 (write-mandate): state up front that Scribe is the system of record — record
work here, recall before acting, don't keep project work in local files.
Refs plan 812 (B9, A4, B8-trigger); B10 audit work-log.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plain-language 'related prior work' instead of 'prior art'; replace the
dev-shaped 'meaningful landing (a merge, a shipped feature, a finished plan)'
with concrete neutral triggers — log on task completion and when a problem is
found, so direction pivots are captured, not just successes. Keeps the static
mandate domain-neutral (pre-empts B9 drift in plan 812).
Refs task 809 / plan 812 item A1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The SessionStart push channel was single-tier: it curled /api/plugin/context
with a Bearer token and, on any failure (missing/unexported token, network
error, missing curl), injected nothing and exited 0 — silently. A known
upstream Claude Code gap (sensitive userConfig not reliably exported to hook
subprocesses) trips this routinely, so a fresh session gets no signal to reach
for Scribe and falls back to local file-memory (root cause of unlogged work on
remote/rc sessions).
Split into two tiers:
- Tier 1 (static, keyless, networkless, always fires): inject bundled
scribe_static_context.md — the load-bearing behavioral mandate. Cannot be
suppressed by the upstream key bug.
- Tier 2 (dynamic, best-effort, fails open): existing curl for live rules +
active-project context, appended below the static block. Lights up as
enrichment once the key reaches the hook.
Only jq is now required (JSON envelope); curl/token gate the dynamic tier only.
Bump plugin 0.1.5 -> 0.1.6 so clients pick up the change.
Refs milestone 55; task 809; decision note 810.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>