diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 0715aba..1874341 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -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.10.01.1242", + "version": "2026.10.01.1245", "author": { "name": "Bryan Van Deusen" }, diff --git a/plugin/hooks/scribe_after_write.sh b/plugin/hooks/scribe_after_write.sh index d9db77f..4c647bd 100644 --- a/plugin/hooks/scribe_after_write.sh +++ b/plugin/hooks/scribe_after_write.sh @@ -141,7 +141,7 @@ while IFS= read -r rel_path; do names=$(printf '%s' "$code" | scribe_defs | sort -u | head -12 || true) # What this write defined, for the end-of-turn question (milestone 439) — # recorded before the arms below, which skip a write that defines nothing. - printf '%s\n' "$names" | scribe_written_append "$safe_sid" "$rel_path" "$fresh" + printf '%s\n' "$names" | scribe_written_append "$safe_sid" "$rel_path" "$fresh" "$code" # Nothing DEFINED in what was written (prose, data, a call-site edit) → # nothing to say; the arms are about shapes. [ -n "$names" ] || continue diff --git a/plugin/hooks/scribe_defs.sh b/plugin/hooks/scribe_defs.sh index a88b97a..6652be9 100644 --- a/plugin/hooks/scribe_defs.sh +++ b/plugin/hooks/scribe_defs.sh @@ -1163,22 +1163,32 @@ SCRIBE_LEDGER_DIRS="scribe-priorart scribe-autoinject" # end of the turn (scribe_shape_check.sh). Its own file — one file, one # question (lesson #4226) — under the prior-art directory, so the session # clear above already sweeps it. Read `kindname` lines on stdin (the -# scribe_defs format). A NEW file adds one `file` line named by its stem: a -# component, module or package file is a candidate in its own right, in any -# language, without a framework rule saying so. +# scribe_defs format). +# +# A NEW file that is a unit in its own right adds one `file` line named by its +# stem — the same test the server's sync applies (coverage.is_file_unit): it +# RENDERS (markup names classes, or it opens a \n\n", True), + ("web/views/Card.vue", "\n", True), + # A TSX component already has its row: the function named after the file. + ("web/Card.tsx", "export function Card() {\n return
;\n}\n", False), + # A JSX file whose markup names classes but whose component is named otherwise. + ("web/index.jsx", "function App() {\n return
;\n}\n", True), + # Modules render nothing: their definitions account for them. + ("internal/api/upnext.go", "func stageOf(x int) int {\n\treturn x\n}\n", False), + ("src/app/util.py", "def helper():\n pass\n", False), + ("web/button.css", ".btn {\n color: red;\n}\n", False), +]) +def test_a_file_is_a_unit_when_it_renders_and_nothing_inside_carries_its_name(path, text, unit): + """Milestone 439: one structural test, not a framework list.""" + from scribe.services.coverage import class_references, extract_definitions, is_file_unit + + assert is_file_unit(path, text, extract_definitions(text), class_references(path, text)) is unit