feat(rulings): a command or edit touching an area's files shows its rulings, once per session (milestone 444 step 4, #4757)
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 1m6s
CI & Build / Python tests (push) Failing after 1m22s
CI & Build / Build & push image (push) Skipped
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 1m6s
CI & Build / Python tests (push) Failing after 1m22s
CI & Build / Build & push image (push) Skipped
A System's rulings (the Rulings section of its description) now reach the work by path, not by similarity. Both PreToolUse arms resolve the files a command or edit names to the Systems whose path_patterns cover them, and the first touch in a session shows each area's rulings in one line; a repeat is a one-line reference. A lookup, so no floor, no budget, no retrieval_logs row. - services/system_rulings: parse_rulings, command_paths (reads and writes, relative to the repo root from any cwd; flags, URLs, globs skipped), rulings_for_paths - /tool-rules takes root, cwd and seen_ruling_systems; /prior-art takes seen_ruling_systems; both return ruling_system_ids - hooks share <sid>.rulings.ids (cleared on compaction by the ledger naming convention); the Bash hook sends the repo root and cwd - system_usage_events (migration 0114): surfacings by source, pulls from get_system; carried by backup (v20) through the system map - writing-records: rulings also arrive when the area's files are touched Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,7 @@ from scribe.services.retrieval_telemetry import record_retrieval
|
||||
from scribe.services.settings import get_setting
|
||||
from scribe.services.systems import system_names_for
|
||||
from scribe.services.text import elide
|
||||
from scribe.services import system_rulings as system_rulings_svc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -2187,6 +2188,7 @@ async def build_write_path_hint(
|
||||
exclude_derive: list[str] | None = None,
|
||||
exclude_rule_ids: list[int] | None = None,
|
||||
held_rule_ids: list[int] | None = None,
|
||||
seen_ruling_systems: list[int] | None = None,
|
||||
) -> dict:
|
||||
"""Prior-art hint for the plugin's PreToolUse hook on Write/Edit.
|
||||
|
||||
@@ -2253,7 +2255,7 @@ async def build_write_path_hint(
|
||||
cfg = await get_writepath_config(user_id)
|
||||
empty = {"context": "", "note_ids": [], "sync_note_ids": [], "config": cfg,
|
||||
"suggested": [], "divergence": [], "derive": [], "derive_keys": [],
|
||||
"rule_ids": []}
|
||||
"rule_ids": [], "ruling_system_ids": []}
|
||||
path = (path or "").strip()
|
||||
if not cfg["enabled"] or not path:
|
||||
return empty
|
||||
@@ -2590,9 +2592,21 @@ async def build_write_path_hint(
|
||||
design_text, design_dedup = await _design_arm(
|
||||
user_id, project_id, path, set(exclude_derive or []),
|
||||
)
|
||||
# The rulings arm (milestone 444), decided here for the design arm's
|
||||
# reasons: a lookup by path, so a write that matched no prior art still
|
||||
# carries its area's rulings, and it never switches the ranked arms on.
|
||||
rulings = await system_rulings_svc.rulings_for_paths(
|
||||
user_id, project_id, [path], seen=seen_ruling_systems,
|
||||
source="rulings_write_path",
|
||||
)
|
||||
if not staleness and not synced and not menu and not suggested and not divergence and not derive:
|
||||
if design_text:
|
||||
return {**empty, "context": design_text, "derive_keys": [design_dedup]}
|
||||
if design_text or rulings["lines"]:
|
||||
return {
|
||||
**empty,
|
||||
"context": "\n".join(rulings["lines"] + ([design_text] if design_text else [])),
|
||||
"derive_keys": [design_dedup] if design_dedup else [],
|
||||
"ruling_system_ids": rulings["system_ids"],
|
||||
}
|
||||
return empty
|
||||
|
||||
owners = await owner_names_for({
|
||||
@@ -2614,7 +2628,9 @@ async def build_write_path_hint(
|
||||
# Seeded with the staleness line, which is decided above the early
|
||||
# return and so cannot wait for this list to exist.
|
||||
lines: list[str] = list(staleness)
|
||||
# First after staleness: it BINDS, where everything below is prior art.
|
||||
# First after staleness: rulings and the design system BIND, where
|
||||
# everything below is prior art.
|
||||
lines.extend(rulings["lines"])
|
||||
if design_text:
|
||||
lines.append(design_text)
|
||||
sync_note_ids: list[int] = []
|
||||
@@ -2890,6 +2906,7 @@ async def build_write_path_hint(
|
||||
),
|
||||
"rule_ids": rule_ids,
|
||||
"checkpoint": checkpoint,
|
||||
"ruling_system_ids": rulings["system_ids"],
|
||||
}
|
||||
|
||||
|
||||
@@ -2901,18 +2918,46 @@ async def build_tool_rule_hint(
|
||||
project_id: int = 0,
|
||||
exclude_rule_ids: list[int] | None = None,
|
||||
held_rule_ids: list[int] | None = None,
|
||||
root: str = "",
|
||||
cwd: str = "",
|
||||
seen_ruling_systems: list[int] | None = None,
|
||||
) -> dict:
|
||||
"""Standing rules that may apply to the action about to be taken — matched
|
||||
directly (`_tool_rule_hint`, where the design is written), then reached
|
||||
through a linked lesson (`_rules_via_lessons`, #4633)."""
|
||||
through a linked lesson (`_rules_via_lessons`, #4633) — and the rulings of
|
||||
any area whose files the command names (milestone 444).
|
||||
|
||||
`root` and `cwd` are the repo's absolute root and the command's working
|
||||
directory, from the hook; they are what turn the paths a command names
|
||||
into the repo-relative paths a System's patterns are written in."""
|
||||
out = await _tool_rule_hint(
|
||||
user_id, tool_name, command, project_id=project_id,
|
||||
exclude_rule_ids=exclude_rule_ids, held_rule_ids=held_rule_ids,
|
||||
)
|
||||
return await _add_rules_via_lessons(
|
||||
out = await _add_rules_via_lessons(
|
||||
user_id, out, project_id=project_id, exclude_rule_ids=exclude_rule_ids,
|
||||
held_rule_ids=held_rule_ids, where=f"to this {tool_name} call",
|
||||
)
|
||||
# Its own arm, not part of the rule search: a lookup by path, with no
|
||||
# floor and no budget, so it adds to the rule lines rather than competing
|
||||
# with them. First, because a ruling is the operator's own decision.
|
||||
# `ruling_system_ids` is set only when a ruling was shown: this arm fires
|
||||
# on every command, and the hook reads an absent list as an empty one.
|
||||
try:
|
||||
paths = system_rulings_svc.command_paths(command, root=root, cwd=cwd) if project_id else []
|
||||
if paths and (await get_writepath_config(user_id)).get("enabled"):
|
||||
rulings = await system_rulings_svc.rulings_for_paths(
|
||||
user_id, project_id, paths,
|
||||
seen=seen_ruling_systems, source="rulings_pre_tool",
|
||||
)
|
||||
if rulings["lines"]:
|
||||
out["context"] = "\n".join(
|
||||
rulings["lines"] + ([out["context"]] if out.get("context") else [])
|
||||
)
|
||||
out["ruling_system_ids"] = rulings["system_ids"]
|
||||
except Exception:
|
||||
logger.debug("pre-tool rulings arm failed", exc_info=True)
|
||||
return out
|
||||
|
||||
|
||||
async def _tool_rule_hint(
|
||||
|
||||
Reference in New Issue
Block a user