Compare commits
2
Commits
59407728e6
...
fb36599f2d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb36599f2d | ||
|
|
2811fc9025 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "scribe",
|
||||
"description": "Scribe for Claude Code: connects the scribe MCP server, adds the hooks that deliver live project state and relevant records at the right moment, ships the shared client-neutral Scribe skills (using-scribe, writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code, shape-accounting), and syncs your saved Scribe Processes as skills (/scribe:sync).",
|
||||
"version": "2026.09.15.1626",
|
||||
"version": "2026.09.15.1744",
|
||||
"author": {
|
||||
"name": "Bryan Van Deusen"
|
||||
},
|
||||
|
||||
@@ -111,6 +111,12 @@ Two constraints on *how* that's achieved:
|
||||
is just a task — don't wrap it in a milestone. Either way, do not write
|
||||
plans/specs to local `.md` files. See the **writing-plans** skill.
|
||||
|
||||
**Find the plan before you make one.** A project's existing milestones are
|
||||
often its roadmap, and a milestone with no steps yet is still open work.
|
||||
`search(content_type="milestone", project_id=...)` finds one by purpose. When
|
||||
an active milestone covers the work, add steps to it rather than opening
|
||||
another, and give any task you record for that work its `milestone_id`.
|
||||
|
||||
5. **Keep state honest.** Set a task `in_progress` when you start it, `done` the
|
||||
moment it's complete; log progress as you go. Always log when you
|
||||
**complete** a task and when you **hit or discover a problem**, so a change
|
||||
|
||||
@@ -26,6 +26,34 @@ a complete, honest record of work that didn't need a plan.
|
||||
Some projects are milestone-shaped and some are a flat task list. Read the
|
||||
project you are in rather than imposing a shape on it.
|
||||
|
||||
## Then look for the plan that already exists
|
||||
|
||||
Before you start a plan, find out whether the project already has one for this
|
||||
work. A roadmap is often written ahead of the work as milestones with a goal
|
||||
and no steps yet, and those are exactly the plans a session misses: they have
|
||||
nothing in them to show up as open tasks.
|
||||
|
||||
- `enter_project` lists the recent milestones and, under `unplanned_milestones`,
|
||||
the active ones with no steps.
|
||||
- `search(content_type="milestone", project_id=...)` finds a plan by what it is
|
||||
for. `list_milestones` reads the whole roadmap.
|
||||
|
||||
**When an active milestone already covers the work, the plan is that milestone.**
|
||||
Read it with `get_milestone`, add your steps with
|
||||
`create_records(milestone_id=<its id>, records=[…])`, and revise its body with
|
||||
`update_milestone` if what you know now changes the design. Opening a new
|
||||
milestone beside it splits one piece of work across two plans, and neither
|
||||
shows the whole. A second milestone is right only when the work is a separate
|
||||
arc: a different goal that would still make sense if the first plan were done.
|
||||
|
||||
The same holds for single tasks. Work that belongs to an existing plan is
|
||||
created with that milestone's `milestone_id`, not left loose beside it.
|
||||
|
||||
`start_planning` and `create_milestone` check too. When an active milestone in
|
||||
the project has the same title or reads as the same plan, they return it as
|
||||
`existing_milestone` and create nothing. Add to that plan, or pass `force=true`
|
||||
only when you have read it and this really is separate work.
|
||||
|
||||
## When it does: start the plan in Scribe, not a file
|
||||
|
||||
Call **`start_planning(project_id, title)`** before designing or implementing —
|
||||
@@ -59,7 +87,7 @@ record, not a file on disk. (The old `kind=plan` task is retired; `start_plannin
|
||||
no longer creates one.)
|
||||
|
||||
Before designing from scratch, **recall**: `search` Scribe for a related prior
|
||||
plan or decision. Often the thinking (or half of it) already exists.
|
||||
decision or note. Often the thinking (or half of it) already exists.
|
||||
|
||||
## What a good plan contains
|
||||
|
||||
|
||||
@@ -49,8 +49,9 @@ client reads Agent Skills) and in each tool's description. The index:
|
||||
- RECALL: search before acting, scoped with the active project_id.
|
||||
- RECORD: create_task; a fix is kind="issue". add_task_log as you go; status
|
||||
in_progress on start, done on finish. Tag system_ids as you write.
|
||||
- PLAN work with an arc: start_planning(steps=[...]). The plan is a milestone
|
||||
and each step a task.
|
||||
- PLAN work with an arc: find the existing plan first
|
||||
(search(content_type="milestone")) and add steps to it; else
|
||||
start_planning(steps=[...]). The plan is a milestone and each step a task.
|
||||
- IDS exist only once a create returns them. Records that cite each other go
|
||||
through create_records, writing {{ref:N}} for the Nth record.
|
||||
- REUSE: search snippets before building; create_snippet what you build.
|
||||
|
||||
@@ -152,6 +152,9 @@ async def create_task(
|
||||
priority: One of: low, medium, high, or 'none'. Omit (empty string) to leave unset.
|
||||
project_id: Associate with a project (0 = no project).
|
||||
milestone_id: Place within a project milestone (0 = no milestone).
|
||||
When the work belongs to an active plan — a milestone in
|
||||
enter_project's lists or found by search(content_type=
|
||||
"milestone") — pass its id, so the plan shows all of its work.
|
||||
parent_id: Make this a sub-task of another task (0 = top-level).
|
||||
tags: List of plain-string tags without # prefix.
|
||||
kind: 'work' (default), 'issue', or 'spike'.
|
||||
@@ -462,6 +465,8 @@ async def create_records(
|
||||
project_id: The project every record belongs to (0 = none, or taken
|
||||
from milestone_id).
|
||||
milestone_id: File every record under this existing milestone (0 = none).
|
||||
This is how steps are added to a plan that already exists,
|
||||
including one start_planning handed back as `existing_milestone`.
|
||||
force: Bypass the near-duplicate gate for the whole batch. By default
|
||||
the first record that near-duplicates an existing one BLOCKS the
|
||||
batch, and its existing id comes back so you can update it instead.
|
||||
@@ -553,7 +558,7 @@ async def start_planning(
|
||||
match = await dedup_svc.plan_gate(
|
||||
uid, project_id, title,
|
||||
dedup_svc.plan_candidate_text(
|
||||
body=body, step_texts=[f"{i.title}\n{i.body or ''}" for i in items],
|
||||
body=body, steps=[(i.title, i.body) for i in items],
|
||||
),
|
||||
)
|
||||
if match is not None:
|
||||
|
||||
@@ -767,16 +767,17 @@ async def get_plan_match_threshold(user_id: int) -> float:
|
||||
def plan_candidate_text(
|
||||
description: str | None = None,
|
||||
body: str | None = None,
|
||||
step_texts: list[str] | None = None,
|
||||
steps: list[tuple[str | None, str | None]] | None = None,
|
||||
) -> str:
|
||||
"""What a plan that doesn't exist yet says about itself, for the gate.
|
||||
|
||||
The steps belong in it: a plan passed with steps and no design is still
|
||||
recognisable by them, and what its steps say is most of what makes two
|
||||
plans the same plan.
|
||||
plans the same plan. Each step is (title, body), joined by embedding_text
|
||||
like every other record that becomes embedded text (#2486).
|
||||
"""
|
||||
parts = [(description or "").strip(), (body or "").strip()]
|
||||
parts += [t.strip() for t in (step_texts or [])]
|
||||
parts += [embeddings_svc.embedding_text(t, b) for t, b in (steps or [])]
|
||||
return "\n\n".join(p for p in parts if p)
|
||||
|
||||
|
||||
|
||||
@@ -147,6 +147,12 @@ TOPICS: tuple[Topic, ...] = (
|
||||
# ── process arcs — owned by their skills ──
|
||||
Topic("plan in a milestone, steps created together", "skill:writing-plans", ("start_planning", "{{ref:"),
|
||||
"a milestone earns its place when the work has an arc", index=("start_planning",)),
|
||||
# Milestone 415: sessions opened a second plan beside the roadmap milestone
|
||||
# that already covered the work, because nothing told them to look.
|
||||
Topic("find the existing plan before making one", "skill:writing-plans",
|
||||
('content_type="milestone"', "unplanned_milestones", "existing_milestone"),
|
||||
"when an active milestone already covers the work, the plan is that milestone",
|
||||
index=('search(content_type="milestone")',)),
|
||||
Topic("reuse recorded shapes; record at first build", "skill:reusing-code",
|
||||
("create_snippet", "when_to_use", "first build", "second copy"),
|
||||
"prior art offered beside a write is not noise", index=("create_snippet",)),
|
||||
|
||||
@@ -408,8 +408,9 @@ async def test_a_bad_threshold_setting_falls_back_to_the_default():
|
||||
|
||||
|
||||
def test_plan_candidate_text_carries_the_steps():
|
||||
text = plan_candidate_text(description=None, body=" design ", step_texts=["Step one\n", ""])
|
||||
assert text == "design\n\nStep one"
|
||||
text = plan_candidate_text(description=None, body=" design ",
|
||||
steps=[("Step one", None), ("Step two", "with a body"), (None, None)])
|
||||
assert text == "design\n\nStep one\n\nStep two\nwith a body"
|
||||
|
||||
|
||||
def test_plan_match_response_points_at_adding_steps_not_a_second_plan():
|
||||
|
||||
Reference in New Issue
Block a user