Design surface: starter roles, theme literals, and the view that could only inspect itself #97

Merged
bvandeusen merged 11 commits from dev into main 2026-08-04 11:02:42 -04:00
3 changed files with 65 additions and 1 deletions
Showing only changes of commit 841506b10c - Show all commits
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "scribe", "name": "scribe",
"description": "Scribe system-of-record for Claude Code: MCP tools over your notes/tasks/projects/rules, a session-start push channel that surfaces your always-on rules + active-project context, process-skills (writing-plans, systematic-debugging, verification, brainstorming, reusing-code), and your saved Scribe Processes auto-surfaced as skills (/scribe:sync). Replaces superpowers + file-memory with one app-backed plugin.", "description": "Scribe system-of-record for Claude Code: MCP tools over your notes/tasks/projects/rules, a session-start push channel that surfaces your always-on rules + active-project context, process-skills (writing-plans, systematic-debugging, verification, brainstorming, reusing-code), and your saved Scribe Processes auto-surfaced as skills (/scribe:sync). Replaces superpowers + file-memory with one app-backed plugin.",
"version": "0.1.22", "version": "0.1.23",
"author": { "name": "Bryan Van Deusen" }, "author": { "name": "Bryan Van Deusen" },
"mcpServers": { "mcpServers": {
"scribe": { "scribe": {
+25
View File
@@ -61,6 +61,31 @@ prepend() { if [ -n "$out" ]; then out="$1"$'\n\n---\n\n'"${out}"; else out="$1"
# --- Tier 1: static behavioral mandate (always, keyless, networkless) --- # --- Tier 1: static behavioral mandate (always, keyless, networkless) ---
[ -f "$here/scribe_static_context.md" ] && out=$(cat "$here/scribe_static_context.md") [ -f "$here/scribe_static_context.md" ] && out=$(cat "$here/scribe_static_context.md")
# --- Which version is actually RUNNING (keyless, networkless) ---
#
# An install has two halves and only one self-updates:
#
# marketplaces/…/scribe-plugin/ git clone — pulls on its own
# cache/…/scribe/<version>/ what EXECUTES — refreshed only when the
# manifest version changes
#
# So inspecting the clone shows a fix present while the broken copy keeps
# running, and the obvious debugging move actively misleads (#2209). Twice, the
# only detector was the operator saying "I don't think it updated".
#
# Naming the running version in every session makes that answerable from the
# transcript instead of by archaeology in the cache directory. Deliberately NOT
# a server round-trip or a stored per-user record: the state most needing
# diagnosis is the one where credentials never arrive, and this line still
# appears there.
manifest="$here/../.claude-plugin/plugin.json"
if [ -f "$manifest" ]; then
plugin_version=$(jq -r '.version // empty' "$manifest" 2>/dev/null) || plugin_version=""
if [ -n "$plugin_version" ]; then
append "> Scribe plugin **v${plugin_version}** is executing in this session. A fix merged after this version has not reached it — the marketplace clone updates on its own, but the cache that runs only refreshes when the manifest version changes."
fi
fi
# --- Tier 2: dynamic rules + active-project context (best-effort) --- # --- Tier 2: dynamic rules + active-project context (best-effort) ---
url=${SCRIBE_URL:-${CLAUDE_PLUGIN_OPTION_API_ENDPOINT:-}} url=${SCRIBE_URL:-${CLAUDE_PLUGIN_OPTION_API_ENDPOINT:-}}
token=${SCRIBE_TOKEN:-${CLAUDE_PLUGIN_OPTION_API_TOKEN:-}} token=${SCRIBE_TOKEN:-${CLAUDE_PLUGIN_OPTION_API_TOKEN:-}}
+39
View File
@@ -307,6 +307,44 @@ def check_local_prior_art_needs_no_instance() -> None:
ok("prior-art local arm: answers with no instance configured") ok("prior-art local arm: answers with no instance configured")
def check_session_context_reports_its_version() -> None:
"""The SessionStart context must name the plugin version it is running.
An install has two halves and only one self-updates: the marketplace clone
pulls on its own, while the CACHE is what executes and refreshes only when
the manifest version changes. So a shipped fix can sit unreached while
inspecting the clone shows it present — the obvious debugging move
misleads, and twice the only detector was a human saying "I don't think it
updated" (#2209, #2220).
Asserted WITHOUT credentials on purpose. The state most needing diagnosis
is the one where the token never arrives, and a marker that vanished there
would be missing exactly when it is wanted.
"""
script = HOOKS_DIR / "scribe_session_context.sh"
if not script.is_file() or not shutil.which("jq"):
skip("version marker: hook or jq missing")
return
manifest_v = manifest_version()
if manifest_v is None:
fail("version marker: could not read the manifest version")
return
try:
proc = _run_hook(script, json.dumps({"source": "startup"}), {})
except subprocess.TimeoutExpired:
fail("version marker: hook hung")
return
if proc.returncode != 0:
fail(f"version marker: hook exited {proc.returncode}")
elif manifest_v not in proc.stdout:
fail(f"version marker: session context never names v{manifest_v}"
f"a stale install would be undetectable from the transcript")
else:
ok(f"version marker: session context reports v{manifest_v}, no credentials needed")
def _git(*args: str) -> tuple[int, str]: def _git(*args: str) -> tuple[int, str]:
proc = subprocess.run( proc = subprocess.run(
["git", *args], capture_output=True, text=True, cwd=ROOT ["git", *args], capture_output=True, text=True, cwd=ROOT
@@ -399,6 +437,7 @@ def main() -> int:
check_shellcheck() check_shellcheck()
check_fail_open() check_fail_open()
check_local_prior_art_needs_no_instance() check_local_prior_art_needs_no_instance()
check_session_context_reports_its_version()
if not args.no_version: if not args.no_version:
check_version_bump(args.base) check_version_bump(args.base)