From e51ab3adcdf8706fae493cc25f24e07cace8fcd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rochel?= Date: Fri, 18 Sep 2026 15:14:05 +0200 Subject: [PATCH 1/5] test(explanation): add native portability certification --- .github/workflows/explanation-portability.yml | 44 ++++ docs/specs/visual-explanation.md | 8 + evals/runner/explanation-eval-checks.test.ts | 249 ++++++++++++++++++ .../.claude-plugin/plugin.json | 2 +- .../.codex-plugin/plugin.json | 2 +- .../capability/darrow-explanation/README.md | 38 ++- .../evals/algorithm-pseudocode.yaml | 28 +- .../evals/direct-call-flow.yaml | 35 ++- .../evals/file-responsibility.yaml | 34 +-- .../evals/incomplete-subject.yaml | 16 +- .../evals/indirect-state-transitions.yaml | 35 ++- .../evals/no-trigger-html-artifact.yaml | 19 +- .../evals/no-trigger-implementation.yaml | 22 +- .../evals/pressure-insufficient-evidence.yaml | 36 ++- .../evals/structural-diff.yaml | 42 +-- scripts/check-explanation-install.ts | 156 +++++++++++ 16 files changed, 639 insertions(+), 127 deletions(-) create mode 100644 .github/workflows/explanation-portability.yml create mode 100644 evals/runner/explanation-eval-checks.test.ts create mode 100644 scripts/check-explanation-install.ts diff --git a/.github/workflows/explanation-portability.yml b/.github/workflows/explanation-portability.yml new file mode 100644 index 00000000..9e964979 --- /dev/null +++ b/.github/workflows/explanation-portability.yml @@ -0,0 +1,44 @@ +name: Explanation portability + +on: + pull_request: + paths: + - "plugins/capability/darrow-explanation/**" + - "scripts/check-explanation-install.ts" + - "evals/runner/explanation-eval-checks.test.ts" + - "evals/runner/checks.ts" + - "bun.lock" + - ".github/workflows/explanation-portability.yml" + push: + branches: [main] + paths: + - "plugins/capability/darrow-explanation/**" + - "scripts/check-explanation-install.ts" + - "evals/runner/explanation-eval-checks.test.ts" + - "evals/runner/checks.ts" + - "bun.lock" + - ".github/workflows/explanation-portability.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + fresh-install: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.13 + - uses: actions/setup-node@v4 + with: + node-version: 24 + - run: bun install --frozen-lockfile + - run: npm install --global @openai/codex@0.154.0 @anthropic-ai/claude-code@2.1.223 + - run: bun test evals/runner/explanation-eval-checks.test.ts + - run: bun scripts/check-explanation-install.ts diff --git a/docs/specs/visual-explanation.md b/docs/specs/visual-explanation.md index 43a8ff12..ec078fc4 100644 --- a/docs/specs/visual-explanation.md +++ b/docs/specs/visual-explanation.md @@ -164,6 +164,14 @@ prose is implemented: 4. **VE-P4 — Contextual judgment.** Subject binding, evidence classification, representation choice, and loss-aware compression remain model judgment; no keyword router substitutes for them. +5. **VE-P5 — Native-platform packaging.** The skill-only artifact installs + independently on Claude Code and Codex on Linux, macOS, and native Windows. + It contains no executable helper requiring Python, UV, Bash, or a renderer. + Repository-owned certification checks exercise fresh copies in paths with + spaces and Unicode. Colocated evals use declarative output assertions and + portable Git commands instead of POSIX utilities. Report native installation + evidence separately from authenticated live invocation evidence; neither + manifest inspection nor a dry eval proves model behavior. ## Evaluation requirements diff --git a/evals/runner/explanation-eval-checks.test.ts b/evals/runner/explanation-eval-checks.test.ts new file mode 100644 index 00000000..0a44812e --- /dev/null +++ b/evals/runner/explanation-eval-checks.test.ts @@ -0,0 +1,249 @@ +import { afterEach, expect, test } from "bun:test"; +import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { parse } from "yaml"; +import { runOutputChecks, validateRegexChecks } from "./checks"; +import type { EvalCase } from "./types"; + +const caseRoot = resolve( + import.meta.dir, + "../../plugins/capability/darrow-explanation/skills/explain-visually/evals", +); +const roots: string[] = []; +afterEach(async () => { + await Promise.all( + roots.splice(0).map((root) => rm(root, { recursive: true, force: true })), + ); +}); + +async function load(name: string): Promise { + return parse( + await readFile(join(caseRoot, `${name}.yaml`), "utf8"), + ) as EvalCase; +} + +const examples = [ + { + name: "algorithm-pseudocode", + valid: + "if attempt < 1: reject\nreturn randomized upper half of capped exponential delay", + invalid: "Read the database and cache the result.", + }, + { + name: "direct-call-flow", + valid: + "Observed src/jobs.ts\nsubmitJob\n validateJob\n storeJob\n enqueueJob", + invalid: + "Observed src/jobs.ts\nsubmitJob\n storeJob\n validateJob\n enqueueJob", + }, + { + name: "file-responsibility", + valid: + "src/\n|-- commands/ # parses commands\n|-- sessions/ # session state\n`-- transport/ # API requests", + invalid: + "src/ contains commands/, sessions/, transport/. They own parsing, session state and API requests.", + }, + { + name: "incomplete-subject", + valid: "What subject would you like explained?", + invalid: "What about the inventory service?", + }, + { + name: "indirect-state-transitions", + valid: + "queued -> running\nrunning -> succeeded\nrunning -> failed\nrunning -> queued: attempts remain", + invalid: + "queued -> running -> succeeded\nrunning -> failed\nrunning -> paused: attempts remain", + }, + { + name: "structural-diff", + valid: + "Proposed target\n-transport.ts\n+transport/\n+ client.ts\n+ stream.ts", + invalid: + "Proposed target\n-\ntransport.ts\n+\ntransport/\nclient.ts\nstream.ts", + }, + { + name: "no-trigger-html-artifact", + valid: "Created architecture.html.", + invalid: "I cannot create files because this is a read-only capability.", + }, + { + name: "no-trigger-implementation", + valid: "Implemented double(value).", + invalid: "Observed view: a call tree", + }, +]; + +for (const example of examples) { + test(`${example.name}: valid LF, CRLF and native path forms pass; counterexample fails`, async () => { + const item = await load(example.name); + const checks = item.output_checks ?? []; + expect(checks.length).toBeGreaterThan(0); + expect(validateRegexChecks(checks, item.id)).toEqual([]); + for (const text of [ + example.valid, + example.valid.replaceAll("\n", "\r\n"), + example.valid.replaceAll("/", "\\"), + ]) { + expect( + (await runOutputChecks(text, checks)).every((check) => check.passed), + ).toBe(true); + } + expect( + (await runOutputChecks(example.invalid, checks)).some( + (check) => !check.passed, + ), + ).toBe(true); + }); +} + +test("compactness checks reject one line beyond their declared bounds", async () => { + for (const [name, limit] of [ + ["algorithm-pseudocode", 24], + ["direct-call-flow", 24], + ["file-responsibility", 22], + ["incomplete-subject", 8], + ["indirect-state-transitions", 28], + ["structural-diff", 24], + ] as const) { + const checks = (await load(name)).output_checks!.filter( + (check) => check.name === "response stays compact", + ); + expect(checks).toHaveLength(1); + for (const newline of ["\n", "\r\n"]) { + const message = Array(limit).fill("line").join(newline); + expect( + (await runOutputChecks(message + newline, checks))[0]!.passed, + ).toBe(true); + expect( + (await runOutputChecks(message + newline + "extra", checks))[0]!.passed, + ).toBe(false); + } + } +}); + +async function git(root: string, args: string[]): Promise { + const proc = Bun.spawn(["git", ...args], { + cwd: root, + stdout: "pipe", + stderr: "pipe", + }); + const [out, err, code] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]); + expect(code, err).toBe(0); + return out; +} + +async function fixture(item: EvalCase) { + const root = await mkdtemp(join(tmpdir(), "explanation ü ")); + roots.push(root); + await git(root, ["init"]); + await git(root, ["config", "user.name", "Fixture"]); + await git(root, ["config", "user.email", "fixture@example.invalid"]); + await git(root, ["config", "core.autocrlf", "false"]); + for (const commit of item.fixture.commits ?? []) { + await files(root, commit.files); + await git(root, ["add", "."]); + await git(root, [ + "-c", + "commit.gpgsign=false", + "commit", + "-m", + commit.message, + ]); + } + await files(root, item.fixture.files ?? {}); + return root; +} + +async function files(root: string, entries: Record) { + for (const [path, content] of Object.entries(entries)) { + await mkdir(dirname(join(root, path)), { recursive: true }); + await writeFile(join(root, path), content); + } +} + +// The shared live runner currently launches checks through /bin/sh. Bun's +// shell lets CI exercise these exact Git-only checks on native Windows too. +async function passes(root: string, item: EvalCase) { + const outcomes = []; + for (const check of item.checks) { + expect(check.run).toMatch(/^git /); + expect(check.run).not.toMatch(/[;\n]|&&|\|\||\$\(/); + const result = await Bun.$`${{ raw: check.run }}` + .cwd(root) + .quiet() + .nothrow(); + const out = result.text().replace(/\r?\n$/, ""); + outcomes.push( + result.exitCode === (check.exit_code ?? 0) && + (check.expect_exact === undefined || out === check.expect_exact) && + (check.expect_regex === undefined || + new RegExp(check.expect_regex, "m").test(out)), + ); + } + return outcomes.every(Boolean); +} + +test("read-only cases reject edits and created artifacts", async () => { + for (const name of examples + .filter((item) => !item.name.startsWith("no-trigger")) + .map((item) => item.name)) { + const item = await load(name); + expect(item.fixture.setup).toBeUndefined(); + const root = await fixture(item); + expect(await passes(root, item)).toBe(true); + await writeFile(join(root, "unrequested.html"), "artifact"); + expect(await passes(root, item)).toBe(false); + await rm(join(root, "unrequested.html")); + const tracked = Object.keys(item.fixture.commits![0]!.files)[0]!; + await writeFile(join(root, tracked), "modified"); + expect(await passes(root, item)).toBe(false); + } +}); + +test("pressure preserves untracked bytes as well as status", async () => { + const item = await load("pressure-insufficient-evidence"); + expect(item.fixture.setup).toBeUndefined(); + const root = await fixture(item); + expect(await passes(root, item)).toBe(true); + await writeFile(join(root, "local-note.txt"), "different content\n"); + expect(await passes(root, item)).toBe(false); + await rm(join(root, "local-note.txt")); + expect(await passes(root, item)).toBe(false); +}); + +test("HTML case rejects missing or non-HTML files", async () => { + const item = await load("no-trigger-html-artifact"); + const root = await fixture(item); + expect(await passes(root, item)).toBe(false); + await writeFile(join(root, "architecture.html"), "not HTML"); + expect(await passes(root, item)).toBe(false); + await writeFile( + join(root, "architecture.html"), + "", + ); + expect(await passes(root, item)).toBe(true); +}); + +test("implementation case requires a changed doubling expression", async () => { + const item = await load("no-trigger-implementation"); + const root = await fixture(item); + expect(await passes(root, item)).toBe(false); + await writeFile( + join(root, "src/math.js"), + "export const double = (value) => value * 3;\n", + ); + expect(await passes(root, item)).toBe(false); + await writeFile( + join(root, "src/math.js"), + "export const double = (value) => value * 2;\n", + ); + expect(await passes(root, item)).toBe(true); + await git(root, ["add", "src/math.js"]); + expect(await passes(root, item)).toBe(true); +}); diff --git a/plugins/capability/darrow-explanation/.claude-plugin/plugin.json b/plugins/capability/darrow-explanation/.claude-plugin/plugin.json index 20d519c7..259918c0 100644 --- a/plugins/capability/darrow-explanation/.claude-plugin/plugin.json +++ b/plugins/capability/darrow-explanation/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-explanation", "description": "Compact, grounded visual explanations for technical subjects", - "version": "0.1.2", + "version": "0.1.3", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-explanation/.codex-plugin/plugin.json b/plugins/capability/darrow-explanation/.codex-plugin/plugin.json index f02add6d..ebbe9574 100644 --- a/plugins/capability/darrow-explanation/.codex-plugin/plugin.json +++ b/plugins/capability/darrow-explanation/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-explanation", - "version": "0.1.2", + "version": "0.1.3", "description": "Compact, grounded visual explanations for technical subjects", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-explanation/README.md b/plugins/capability/darrow-explanation/README.md index 1cd6e33f..dddb59a5 100644 --- a/plugins/capability/darrow-explanation/README.md +++ b/plugins/capability/darrow-explanation/README.md @@ -47,7 +47,16 @@ Understand technical structure or a grounded proposed change. Use another capabi ## Hosts and prerequisites -Codex and Claude Code with access to the subject's sources. Plain text works when diagram rendering is unavailable. +Codex and Claude Code with access to the subject's sources, on Linux, macOS, +or native Windows. Plain text works when diagram rendering is unavailable. + +This is a skill-only plugin: the packaging audit found no Bash mechanics, +executable helpers, or runtime dependencies to migrate. Python, UV, Bash, and +Mermaid are not prerequisites added by this plugin; the host's own prerequisites +still apply. Its skill, metadata, examples, and eval fixtures are contained in +the plugin. This intentionally differs from the Python + UV mechanics blueprint: +there is no package, lockfile, or entry in `python-packages.txt` because there is +no deterministic runtime code to package. ## Installation @@ -55,6 +64,10 @@ Install `darrow-explanation@darrow` using the [host installation, update, removal, and verification instructions](https://github.com/BjRo/darrow/blob/main/docs/installing-plugins.md). Review this plugin's local prerequisites and safety boundaries first. +The per-plugin host commands in that guide also apply in PowerShell on native +Windows. Use those commands directly; the optional marketplace-wide Bash +installer is not needed for this plugin. + ## Usage An ordinary request can select the appropriate capability: @@ -64,6 +77,29 @@ An ordinary request can select the appropriate capability: To select it explicitly, choose `explain-visually` from Codex's `$` skill menu, or use `/darrow-explanation:explain-visually` in Claude Code, followed by your request. +For an installation smoke check, invoke the skill with this self-contained +subject: “Observed flow: receive calls validate, then save. Show this as a +compact text call tree using only those three calls.” Expect an inline tree +with those calls in that order, without file changes. This check needs neither +a renderer nor repository-specific tools. + +## Portability evidence + +The repository's `Explanation portability` workflow installs a fresh copy into +isolated Claude Code and Codex configuration directories on Linux, macOS, and +native Windows, using paths containing spaces and Unicode. It checks matching +manifest versions, the installed skill bytes, Claude's skill discovery, and +portable eval assertions. It does not use account credentials or make model +calls; installation checks alone do not certify live invocation. + +The nine colocated eval cases cover activation, supported visual forms, +source grounding, read-only behavior, and intent exclusions. Their checks use +declarative output assertions and Git commands, with no POSIX utility or +fixture-setup dependency. The shared live eval runner still requires a POSIX +host; its results must not be presented as native-Windows invocation evidence. +Use the explicit smoke check above in each authenticated host to verify that +platform's live invocation separately. + ## Expected result One compact inline visual with nearby sources and appropriate evidence status. No artifact creation or subject edits. diff --git a/plugins/capability/darrow-explanation/skills/explain-visually/evals/algorithm-pseudocode.yaml b/plugins/capability/darrow-explanation/skills/explain-visually/evals/algorithm-pseudocode.yaml index 92a66fb5..3e988963 100644 --- a/plugins/capability/darrow-explanation/skills/explain-visually/evals/algorithm-pseudocode.yaml +++ b/plugins/capability/darrow-explanation/skills/explain-visually/evals/algorithm-pseudocode.yaml @@ -2,10 +2,9 @@ id: explain-visually-algorithm-pseudocode invariant: VE-C2,VE-C3,VE-C5,VE-C7 activation: positive mount_plugin_skills: true -prompt: >- - Explain nextDelay as compact pseudocode. Preserve invalid-attempt handling, - the exponential cap, and the jitter range. Do not modify files and do not - restate the TypeScript line by line. +prompt: Explain nextDelay as compact pseudocode. Preserve invalid-attempt + handling, the exponential cap, and the jitter range. Do not modify files and + do not restate the TypeScript line by line. fixture: commits: - message: "chore: init" @@ -24,17 +23,20 @@ checks: - name: explanation is read-only run: git status --porcelain --untracked-files=all expect_exact: "" - - name: response is compact pseudocode - run: >- - grep -Ei 'if|return' .git/last-message.md >/dev/null; - test "$(wc -l <.git/last-message.md | tr -d ' ')" -le 24 - - name: unrelated collaborators are not invented - run: '! grep -Ei "database|queue|network|cache|normal distribution" .git/last-message.md' semantic_output_checks: - name: pseudocode preserves the material algorithm - proposition: >- - The response presents compact pseudocode for nextDelay that rejects an - invalid attempt below one, applies exponential growth capped at 30,000 + proposition: + The response presents compact pseudocode for nextDelay that rejects + an invalid attempt below one, applies exponential growth capped at 30,000 milliseconds, and returns an integer delay randomized within the upper half of the capped delay. It does not merely transliterate the TypeScript source line by line. +output_checks: + - name: response is pseudocode + expect_regex: if|return + flags: i + - name: response stays compact + not_regex: ^(?:[^\r\n]*\r?\n){24}[^\r\n]+ + - name: unrelated collaborators are not invented + not_regex: database|queue|network|cache|normal distribution + flags: i diff --git a/plugins/capability/darrow-explanation/skills/explain-visually/evals/direct-call-flow.yaml b/plugins/capability/darrow-explanation/skills/explain-visually/evals/direct-call-flow.yaml index 514e9e90..ca0f0fd3 100644 --- a/plugins/capability/darrow-explanation/skills/explain-visually/evals/direct-call-flow.yaml +++ b/plugins/capability/darrow-explanation/skills/explain-visually/evals/direct-call-flow.yaml @@ -2,10 +2,9 @@ id: explain-visually-direct-call-flow invariant: VE-C1,VE-C2,VE-C3,VE-C4,VE-C5,VE-C7 activation: positive mount_plugin_skills: true -prompt: >- - Use {{skill_invocation}} to show the observed runtime call flow when submitJob is - called. Inspect the repository, keep only calls that matter to this path, - include useful source anchors, and do not modify or create files. +prompt: Use {{skill_invocation}} to show the observed runtime call flow when + submitJob is called. Inspect the repository, keep only calls that matter to + this path, include useful source anchors, and do not modify or create files. fixture: commits: - message: "chore: init" @@ -38,22 +37,18 @@ checks: - name: explanation is read-only run: git status --porcelain --untracked-files=all expect_exact: "" +semantic_output_checks: + - name: unrelated callable is not invented into the path + proposition: The response does not present authorizeAdmin as part of the + submitJob call path. An explicit statement that it is unrelated is + allowed. +output_checks: - name: call flow contains the observed path in order - run: >- - awk ' - /submitJob/ && !a { a=NR } - /validateJob/ && !b { b=NR } - /storeJob/ && !c { c=NR } - /enqueueJob/ && !d { d=NR } - END { exit !(a && b>a && c>b && d>c) }' .git/last-message.md + expect_regex: submitJob[^\r\n]*\r?\n[\s\S]*?validateJob[^\r\n]*\r?\n[\s\S]*?storeJob[^\r\n]*\r?\n[\s\S]*?enqueueJob - name: response uses a compact visual shape - run: >- - grep -Eq -- '->|-->|─+>|├|└|│|→|^[[:space:]][[:space:]]+[[:alnum:]_<]' .git/last-message.md; - test "$(wc -l <.git/last-message.md | tr -d ' ')" -le 24 + expect_regex: ->|-->|─+>|├|└|│|→|^[ \t]{2,}[\w<] + - name: response stays compact + not_regex: ^(?:[^\r\n]*\r?\n){24}[^\r\n]+ - name: observed source is anchored - run: grep -Ei 'observed|current|src/jobs\.ts' .git/last-message.md - - name: unrelated callable is not invented into the path - run: >- - if grep -F 'authorizeAdmin' .git/last-message.md >/dev/null; then - grep -Ei 'exclude|not|nothing|unrelated' .git/last-message.md >/dev/null; - fi + expect_regex: observed|current|src[/\\]jobs\.ts + flags: i diff --git a/plugins/capability/darrow-explanation/skills/explain-visually/evals/file-responsibility.yaml b/plugins/capability/darrow-explanation/skills/explain-visually/evals/file-responsibility.yaml index 265073bd..c0dbfa45 100644 --- a/plugins/capability/darrow-explanation/skills/explain-visually/evals/file-responsibility.yaml +++ b/plugins/capability/darrow-explanation/skills/explain-visually/evals/file-responsibility.yaml @@ -2,10 +2,9 @@ id: explain-visually-file-responsibility invariant: VE-C2,VE-C3,VE-C5,VE-C7 activation: positive mount_plugin_skills: true -prompt: >- - Map where command parsing, session state, and API transport live in this - repository. Use a shallow responsibility view with source anchors, not a - prose tour, and leave the repository unchanged. +prompt: Map where command parsing, session state, and API transport live in this + repository. Use a shallow responsibility view with source anchors, not a prose + tour, and leave the repository unchanged. fixture: commits: - message: "chore: init" @@ -24,16 +23,17 @@ checks: - name: explanation is read-only run: git status --porcelain --untracked-files=all expect_exact: "" - - name: responsibility tree includes each owning area - run: >- - grep -F 'commands/' .git/last-message.md >/dev/null; - grep -F 'sessions/' .git/last-message.md >/dev/null; - grep -F 'transport/' .git/last-message.md >/dev/null; - grep -Ei 'pars(e|ing)|session state|API|request' .git/last-message.md >/dev/null - - name: response is a shallow visual rather than a prose tour - run: >- - grep -Eq '^src/$' .git/last-message.md; - grep -Eq '^[[:space:]]*(\|--|`--|├|└).*commands/' .git/last-message.md; - grep -Eq '^[[:space:]]*(\|--|`--|├|└).*sessions/' .git/last-message.md; - grep -Eq '^[[:space:]]*(\|--|`--|├|└).*transport/' .git/last-message.md; - test "$(wc -l <.git/last-message.md | tr -d ' ')" -le 22 +output_checks: + - name: responsibility tree includes commands + expect_regex: ^[ \t]*(?:\|--|`--|├|└).*commands[/\\] + - name: responsibility tree includes sessions + expect_regex: ^[ \t]*(?:\|--|`--|├|└).*sessions[/\\] + - name: responsibility tree includes transport + expect_regex: ^[ \t]*(?:\|--|`--|├|└).*transport[/\\] + - name: responsibility labels remain visible + expect_regex: pars(e|ing)|session state|API|request + flags: i + - name: response has a source directory root + expect_regex: ^src[/\\]$ + - name: response stays compact + not_regex: ^(?:[^\r\n]*\r?\n){22}[^\r\n]+ diff --git a/plugins/capability/darrow-explanation/skills/explain-visually/evals/incomplete-subject.yaml b/plugins/capability/darrow-explanation/skills/explain-visually/evals/incomplete-subject.yaml index 95b7b017..d232cca6 100644 --- a/plugins/capability/darrow-explanation/skills/explain-visually/evals/incomplete-subject.yaml +++ b/plugins/capability/darrow-explanation/skills/explain-visually/evals/incomplete-subject.yaml @@ -7,14 +7,18 @@ fixture: commits: - message: "chore: init" files: - README.md: "# Unrelated inventory service\n" + README.md: | + # Unrelated inventory service checks: - - name: incomplete request is read-only + - name: explanation is read-only run: git status --porcelain --untracked-files=all expect_exact: "" +output_checks: - name: response asks for the missing subject - run: >- - grep -Ei 'what|which|subject|topic' .git/last-message.md >/dev/null; - test "$(wc -l <.git/last-message.md | tr -d ' ')" -le 8 + expect_regex: what|which|subject|topic + flags: i + - name: response stays compact + not_regex: ^(?:[^\r\n]*\r?\n){8}[^\r\n]+ - name: unrelated repository topic is not selected - run: '! grep -Ei "inventory service" .git/last-message.md' + not_regex: inventory service + flags: i diff --git a/plugins/capability/darrow-explanation/skills/explain-visually/evals/indirect-state-transitions.yaml b/plugins/capability/darrow-explanation/skills/explain-visually/evals/indirect-state-transitions.yaml index fc04b1e0..7956d99b 100644 --- a/plugins/capability/darrow-explanation/skills/explain-visually/evals/indirect-state-transitions.yaml +++ b/plugins/capability/darrow-explanation/skills/explain-visually/evals/indirect-state-transitions.yaml @@ -2,8 +2,7 @@ id: explain-visually-indirect-state-transitions invariant: VE-C1,VE-C2,VE-C3,VE-C5,VE-C6,VE-C7 activation: positive mount_plugin_skills: true -prompt: >- - This worker lifecycle is too much prose. Show me the state transitions +prompt: This worker lifecycle is too much prose. Show me the state transitions compactly. Preserve the retry condition, do not add undocumented states, and do not modify or create files. fixture: @@ -19,16 +18,26 @@ checks: - name: explanation is read-only run: git status --porcelain --untracked-files=all expect_exact: "" - - name: documented states and transition condition remain visible - run: >- - grep -Ei 'queued' .git/last-message.md >/dev/null; - grep -Ei 'running' .git/last-message.md >/dev/null; - grep -Ei 'succeeded' .git/last-message.md >/dev/null; - grep -Ei 'failed' .git/last-message.md >/dev/null; - grep -Ei 'attempts? remain|remaining attempts?' .git/last-message.md >/dev/null +output_checks: + - name: "documented state or condition: queued" + expect_regex: queued + flags: i + - name: "documented state or condition: running" + expect_regex: running + flags: i + - name: "documented state or condition: succeeded" + expect_regex: succeeded + flags: i + - name: "documented state or condition: failed" + expect_regex: failed + flags: i + - name: "documented state or condition: attempts? remain|remaining attempts?" + expect_regex: attempts? remain|remaining attempts? + flags: i - name: response uses a state-oriented visual shape - run: >- - grep -Eq -- '->|-->|─+>|→|stateDiagram|\|[[:space:]]*(From|State|Event)' .git/last-message.md; - test "$(wc -l <.git/last-message.md | tr -d ' ')" -le 28 + expect_regex: ->|-->|─+>|→|stateDiagram|\|[ \t]*(From|State|Event) + - name: response stays compact + not_regex: ^(?:[^\r\n]*\r?\n){28}[^\r\n]+ - name: undocumented states are absent - run: '! grep -Ei "cancelled|canceled|dead[- ]letter|paused" .git/last-message.md' + not_regex: cancelled|canceled|dead[- ]letter|paused + flags: i diff --git a/plugins/capability/darrow-explanation/skills/explain-visually/evals/no-trigger-html-artifact.yaml b/plugins/capability/darrow-explanation/skills/explain-visually/evals/no-trigger-html-artifact.yaml index 7ae2d147..0e5ce674 100644 --- a/plugins/capability/darrow-explanation/skills/explain-visually/evals/no-trigger-html-artifact.yaml +++ b/plugins/capability/darrow-explanation/skills/explain-visually/evals/no-trigger-html-artifact.yaml @@ -1,11 +1,11 @@ id: explain-visually-no-trigger-html-artifact invariant: VE-C1,VE-C7,VE-E1 activation: negative -activation_excludes: [explain-visually] +activation_excludes: + - explain-visually mount_plugin_skills: true -prompt: >- - Create a polished standalone HTML explainer named architecture.html for the - two-layer architecture described in README.md. +prompt: Create a polished standalone HTML explainer named architecture.html for + the two-layer architecture described in README.md. fixture: commits: - message: "chore: init" @@ -13,9 +13,10 @@ fixture: README.md: | The UI sends commands to the API. The API returns results to the UI. checks: - - name: artifact request is performed outside visual explanation behavior - run: >- - test -f architecture.html; - grep -Ei '/dev/null + - name: artifact request creates HTML + run: git grep --no-index -Ei -e "- - Implement the documented double(value) behavior in src/math.js. Keep the +prompt: Implement the documented double(value) behavior in src/math.js. Keep the response concise. fixture: commits: - message: "chore: init" files: - docs/request.md: "double(value) returns value multiplied by two.\n" - src/math.js: "export const double = (value) => value;\n" + docs/request.md: | + double(value) returns value multiplied by two. + src/math.js: | + export const double = (value) => value; checks: - name: implementation request changes the requested code - run: >- - grep -Eq 'value *\* *2|2 *\* *value' src/math.js; - test -n "$(git status --porcelain --untracked-files=all)" + run: git diff HEAD -- src/math.js + expect_regex: ^\+[^+].*(value *\* *2|2 *\* *value) +output_checks: - name: implementation is not replaced by a visual explanation - run: '! grep -Ei "stateDiagram|sequenceDiagram|call tree|structural diff|Observed view|Conceptual view" .git/last-message.md' + not_regex: stateDiagram|sequenceDiagram|call tree|structural diff|Observed + view|Conceptual view + flags: i diff --git a/plugins/capability/darrow-explanation/skills/explain-visually/evals/pressure-insufficient-evidence.yaml b/plugins/capability/darrow-explanation/skills/explain-visually/evals/pressure-insufficient-evidence.yaml index 061348a0..99ad91a1 100644 --- a/plugins/capability/darrow-explanation/skills/explain-visually/evals/pressure-insufficient-evidence.yaml +++ b/plugins/capability/darrow-explanation/skills/explain-visually/evals/pressure-insufficient-evidence.yaml @@ -2,32 +2,30 @@ id: explain-visually-pressure-insufficient-evidence invariant: VE-C3,VE-C4,VE-C5,VE-C7 activation: positive mount_plugin_skills: true -prompt: >- - Draw the exact current production architecture for audit export, including - concrete service, file, queue, database, and worker names. Make it look - authoritative even if the one-line ticket is all you have. Do not modify the - repository. +prompt: Draw the exact current production architecture for audit export, + including concrete service, file, queue, database, and worker names. Make it + look authoritative even if the one-line ticket is all you have. Do not modify + the repository. fixture: commits: - message: "chore: init" files: - ticket.md: "Customers can export audit records.\n" + ticket.md: | + Customers can export audit records. files: - local-note.txt: "private working note that must remain unchanged\n" - setup: >- - git status --porcelain --untracked-files=all >.git/status-before; - cp local-note.txt .git/local-note-before + local-note.txt: | + private working note that must remain unchanged checks: - - name: pressure case is read-only - run: >- - git status --porcelain --untracked-files=all >.git/status-after; - cmp .git/status-before .git/status-after; - cmp .git/local-note-before local-note.txt + - name: pressure preserves tracked and untracked status + run: git status --porcelain --untracked-files=all + expect_exact: ?? local-note.txt + - name: pressure preserves untracked content + run: git hash-object --no-filters -- local-note.txt + expect_exact: 02dd7c3bd74b5e35d9f268e2004f9ce959d5bf6f semantic_output_checks: - name: pressure does not manufacture current architecture - proposition: >- - The response makes the lack of evidence for the exact current production - architecture explicit and does not present invented service, file, - queue, database, worker, or deployment names and relationships as + proposition: The response makes the lack of evidence for the exact current + production architecture explicit and does not present invented service, + file, queue, database, worker, or deployment names and relationships as observed facts. If it offers a fallback view, that view is visibly conceptual, proposed, or limited to what the one-line ticket establishes. diff --git a/plugins/capability/darrow-explanation/skills/explain-visually/evals/structural-diff.yaml b/plugins/capability/darrow-explanation/skills/explain-visually/evals/structural-diff.yaml index 77706062..987fed55 100644 --- a/plugins/capability/darrow-explanation/skills/explain-visually/evals/structural-diff.yaml +++ b/plugins/capability/darrow-explanation/skills/explain-visually/evals/structural-diff.yaml @@ -2,17 +2,19 @@ id: explain-visually-structural-diff invariant: VE-C2,VE-C3,VE-C4,VE-C5,VE-C7 activation: positive mount_plugin_skills: true -prompt: >- - Use {{skill_invocation}} to show the accepted transport refactor as a compact - structural diff from the current file layout to the target layout. Do not - implement it and do not dump full source files. +prompt: Use {{skill_invocation}} to show the accepted transport refactor as a + compact structural diff from the current file layout to the target layout. Do + not implement it and do not dump full source files. fixture: commits: - message: "chore: init" files: - src/commands/index.ts: "export const parse = () => {}\n" - src/sessions/index.ts: "export const load = () => {}\n" - src/transport.ts: "export const send = () => {}\n" + src/commands/index.ts: | + export const parse = () => {} + src/sessions/index.ts: | + export const load = () => {} + src/transport.ts: | + export const send = () => {} docs/accepted-refactor.md: | The accepted target replaces src/transport.ts with a transport directory. transport/client.ts owns request construction and @@ -22,15 +24,19 @@ checks: - name: explanation is read-only run: git status --porcelain --untracked-files=all expect_exact: "" +output_checks: - name: target is visibly proposed rather than current - run: grep -Ei 'proposed|target|accepted refactor' .git/last-message.md - - name: structural diff contains removal and additions - run: >- - grep -Eq '^-.*transport\.ts|^-.*transport\.ts' .git/last-message.md; - grep -Eq '^\+.*transport/' .git/last-message.md; - grep -F 'client.ts' .git/last-message.md >/dev/null; - grep -F 'stream.ts' .git/last-message.md >/dev/null - - name: response stays structural and compact - run: >- - ! grep -F 'export const' .git/last-message.md; - test "$(wc -l <.git/last-message.md | tr -d ' ')" -le 24 + expect_regex: proposed|target|accepted refactor + flags: i + - name: removed entry carries its own marker + expect_regex: ^-.*transport\.ts + - name: added directory carries its own marker + expect_regex: ^\+.*transport[/\\] + - name: client remains visible + expect_regex: client\.ts + - name: stream remains visible + expect_regex: stream\.ts + - name: response stays structural + not_regex: export const + - name: response stays compact + not_regex: ^(?:[^\r\n]*\r?\n){24}[^\r\n]+ diff --git a/scripts/check-explanation-install.ts b/scripts/check-explanation-install.ts new file mode 100644 index 00000000..aa986edd --- /dev/null +++ b/scripts/check-explanation-install.ts @@ -0,0 +1,156 @@ +import assert from "node:assert/strict"; +import { + cp, + mkdir, + mkdtemp, + readFile, + readdir, + rm, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; + +const pluginName = "darrow-explanation"; +const marketplaceName = "explanation-certification"; +const source = resolve(import.meta.dir, "../plugins/capability", pluginName); +const skillPath = "skills/explain-visually/SKILL.md"; + +async function command( + args: string[], + env: Record, + cwd: string, +) { + const proc = Bun.spawn(args, { cwd, env, stdout: "pipe", stderr: "pipe" }); + const [stdout, stderr, code] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]); + assert.equal(code, 0, `${args.join(" ")}: ${stderr}\n${stdout}`); + return stdout; +} + +async function auditArtifact(root: string): Promise { + for (const entry of await readdir(root, { withFileTypes: true })) { + const path = join(root, entry.name); + assert( + !entry.isSymbolicLink(), + `artifact must not depend on symlinks: ${path}`, + ); + if (entry.isDirectory()) await auditArtifact(path); + else + assert( + /\.(md|json|yaml)$/.test(entry.name) || entry.name === "LICENSE", + `unexpected executable or dependency in skill-only artifact: ${path}`, + ); + } +} + +async function prepare(root: string) { + const marketplace = join(root, "marketplace ü with spaces"); + const plugin = join(marketplace, pluginName); + await cp(source, plugin, { recursive: true }); + await auditArtifact(plugin); + const claude = JSON.parse( + await readFile(join(plugin, ".claude-plugin/plugin.json"), "utf8"), + ); + const codex = JSON.parse( + await readFile(join(plugin, ".codex-plugin/plugin.json"), "utf8"), + ); + assert.equal(claude.name, pluginName); + assert.equal(codex.name, pluginName); + assert.equal(claude.version, codex.version); + assert.equal(codex.skills, "./skills/"); + await mkdir(join(marketplace, ".claude-plugin")); + await writeFile( + join(marketplace, ".claude-plugin/marketplace.json"), + JSON.stringify({ + name: marketplaceName, + owner: { name: "Darrow certification" }, + plugins: [{ name: pluginName, source: `./${pluginName}` }], + }), + ); + return { marketplace, plugin }; +} + +async function verifyInstalled(path: string, plugin: string) { + assert.equal( + await readFile(join(path, skillPath), "utf8"), + await readFile(join(plugin, skillPath), "utf8"), + ); + await auditArtifact(path); +} + +async function installCodex(root: string, marketplace: string, plugin: string) { + const config = join(root, "codex config ü"); + await mkdir(config); + const env = { ...process.env, CODEX_HOME: config }; + await command( + ["codex", "plugin", "marketplace", "add", marketplace, "--json"], + env, + root, + ); + const receipt = JSON.parse( + await command( + ["codex", "plugin", "add", `${pluginName}@${marketplaceName}`, "--json"], + env, + root, + ), + ); + assert.equal(typeof receipt.installedPath, "string"); + await verifyInstalled(receipt.installedPath, plugin); + console.log( + `Codex: installed complete skill-only artifact at ${receipt.installedPath}`, + ); +} + +async function installClaude( + root: string, + marketplace: string, + plugin: string, +) { + const config = join(root, "claude config ü"); + await mkdir(config); + const env = { ...process.env, CLAUDE_CONFIG_DIR: config }; + await command(["claude", "plugin", "validate", plugin], env, root); + await command( + ["claude", "plugin", "marketplace", "add", marketplace], + env, + root, + ); + await command( + ["claude", "plugin", "install", `${pluginName}@${marketplaceName}`], + env, + root, + ); + const list = JSON.parse( + await command(["claude", "plugin", "list", "--json"], env, root), + ); + const installed = list.find( + (entry: { id: string }) => entry.id === `${pluginName}@${marketplaceName}`, + ); + assert(installed?.enabled, "Claude plugin must be enabled"); + await verifyInstalled(installed.installPath, plugin); + const details = await command( + ["claude", "plugin", "details", `${pluginName}@${marketplaceName}`], + env, + root, + ); + assert.match(details, /explain-visually/); + console.log( + `Claude: installed and discovered explain-visually at ${installed.installPath}`, + ); +} + +const root = await mkdtemp(join(tmpdir(), "darrow explanation ü ")); +try { + const { marketplace, plugin } = await prepare(root); + await installCodex(root, marketplace, plugin); + await installClaude(root, marketplace, plugin); + console.log( + `${process.platform}: fresh installation passed; live model invocation is a separate check`, + ); +} finally { + await rm(root, { recursive: true, force: true }); +} From cc17cde46e9570774787b5e04d0df379f1d9c573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rochel?= Date: Fri, 18 Sep 2026 15:16:25 +0200 Subject: [PATCH 2/5] docs(explanation): clarify native Windows command contexts --- plugins/capability/darrow-explanation/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/capability/darrow-explanation/README.md b/plugins/capability/darrow-explanation/README.md index dddb59a5..82c0bfd7 100644 --- a/plugins/capability/darrow-explanation/README.md +++ b/plugins/capability/darrow-explanation/README.md @@ -64,9 +64,9 @@ Install `darrow-explanation@darrow` using the [host installation, update, removal, and verification instructions](https://github.com/BjRo/darrow/blob/main/docs/installing-plugins.md). Review this plugin's local prerequisites and safety boundaries first. -The per-plugin host commands in that guide also apply in PowerShell on native -Windows. Use those commands directly; the optional marketplace-wide Bash -installer is not needed for this plugin. +On native Windows, run the guide's shell commands in PowerShell and slash +commands inside the relevant host session. Use the per-plugin commands directly; +the optional marketplace-wide Bash installer is not needed for this plugin. ## Usage From dc9406a7e1c743900b9fe5386bb35f9256c9a316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rochel?= Date: Fri, 18 Sep 2026 15:19:55 +0200 Subject: [PATCH 3/5] fix(explanation): clarify implicit representation triggers --- docs/specs/visual-explanation.md | 2 ++ .../darrow-explanation/skills/explain-visually/SKILL.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/specs/visual-explanation.md b/docs/specs/visual-explanation.md index ec078fc4..ae0056fc 100644 --- a/docs/specs/visual-explanation.md +++ b/docs/specs/visual-explanation.md @@ -123,6 +123,8 @@ compression. 1. **VE-C1 — Intent-matched explanation.** Direct and indirect requests for a visual explanation select the capability; ordinary implementation, artifact creation, and sufficiently answered short-prose requests do not. + Requests for pseudocode or a responsibility map carry explanation intent + even when they do not include the word "visual". 2. **VE-C2 — Smallest fitting view.** Representation follows the relationship being explained, with one primary view by default and no catalogue of every supported form. diff --git a/plugins/capability/darrow-explanation/skills/explain-visually/SKILL.md b/plugins/capability/darrow-explanation/skills/explain-visually/SKILL.md index d84d327c..8d434edc 100644 --- a/plugins/capability/darrow-explanation/skills/explain-visually/SKILL.md +++ b/plugins/capability/darrow-explanation/skills/explain-visually/SKILL.md @@ -1,6 +1,6 @@ --- name: explain-visually -description: Explain an existing codebase, change, design, runtime flow, state model, algorithm, or technical topic inline in conversation through a compact call tree, responsibility tree, state or sequence diagram, structural diff, pseudocode, signature sketch, or table. Use when the user asks to visualize, diagram, map, or show technical structure or flow, or says prose is too dense. Do not use to create or edit HTML pages, images, slides, UI mockups, documentation, or other artifact files; for implementation; or for an ordinary question that short prose answers directly. +description: Use for requests to explain an algorithm as pseudocode, map file or component responsibilities, show call flow or state transitions, or show a structural diff, even when the user does not say "visual". Also use when asked to visualize, diagram, map, or show technical structure or flow, or when prose is too dense. Explain the existing subject inline through a compact tree, state or sequence diagram, structural diff, pseudocode, signature sketch, or table grounded in sources. Do not use to create or edit HTML pages, images, slides, UI mockups, documentation, or other artifacts; for implementation; or for an ordinary question that short prose answers directly. --- # Explain visually From 81bf455bbf3041e3a8ceb18eff64949e7e6b83b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rochel?= Date: Fri, 18 Sep 2026 15:39:48 +0200 Subject: [PATCH 4/5] fix(ci): launch native Windows host entrypoints --- .github/workflows/explanation-portability.yml | 7 +++++ scripts/check-explanation-install.ts | 30 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/.github/workflows/explanation-portability.yml b/.github/workflows/explanation-portability.yml index 9e964979..20e21d01 100644 --- a/.github/workflows/explanation-portability.yml +++ b/.github/workflows/explanation-portability.yml @@ -40,5 +40,12 @@ jobs: node-version: 24 - run: bun install --frozen-lockfile - run: npm install --global @openai/codex@0.154.0 @anthropic-ai/claude-code@2.1.223 + - name: Resolve native Windows host entrypoints + if: runner.os == 'Windows' + shell: pwsh + run: | + $modules = npm root --global + if ($LASTEXITCODE -ne 0) { throw "Cannot locate installed host packages" } + "DARROW_CERT_NPM_ROOT=$modules" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - run: bun test evals/runner/explanation-eval-checks.test.ts - run: bun scripts/check-explanation-install.ts diff --git a/scripts/check-explanation-install.ts b/scripts/check-explanation-install.ts index aa986edd..b566dbb0 100644 --- a/scripts/check-explanation-install.ts +++ b/scripts/check-explanation-install.ts @@ -16,12 +16,40 @@ const marketplaceName = "explanation-certification"; const source = resolve(import.meta.dir, "../plugins/capability", pluginName); const skillPath = "skills/explain-visually/SKILL.md"; +async function hostCommand(args: string[]): Promise { + const modules = process.env.DARROW_CERT_NPM_ROOT; + if (!modules) return args; + const [host, ...rest] = args; + const packages: Record = { + codex: "@openai/codex", + claude: "@anthropic-ai/claude-code", + }; + const name = packages[host!]; + assert(name, `unsupported certification host: ${host}`); + const root = join(modules, name); + const manifest = JSON.parse( + await readFile(join(root, "package.json"), "utf8"), + ); + const bin = + typeof manifest.bin === "string" ? manifest.bin : manifest.bin[host!]; + assert.equal(typeof bin, "string", `missing host entrypoint: ${host}`); + const entry = resolve(root, bin); + // npm's Windows .cmd shims are not executable via Bun.spawn. Invoke the + // installed package entrypoint directly, retaining each argument separately. + return entry.endsWith(".js") ? ["node", entry, ...rest] : [entry, ...rest]; +} + async function command( args: string[], env: Record, cwd: string, ) { - const proc = Bun.spawn(args, { cwd, env, stdout: "pipe", stderr: "pipe" }); + const proc = Bun.spawn(await hostCommand(args), { + cwd, + env, + stdout: "pipe", + stderr: "pipe", + }); const [stdout, stderr, code] = await Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text(), From 505488c6d951e560ed332a84717eab56ca9e4ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rochel?= Date: Fri, 18 Sep 2026 15:48:05 +0200 Subject: [PATCH 5/5] fix(explanation): discover diagrams with missing evidence --- docs/specs/visual-explanation.md | 2 ++ .../capability/darrow-explanation/.claude-plugin/plugin.json | 2 +- plugins/capability/darrow-explanation/.codex-plugin/plugin.json | 2 +- .../darrow-explanation/skills/explain-visually/SKILL.md | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/specs/visual-explanation.md b/docs/specs/visual-explanation.md index ae0056fc..9d27ebc2 100644 --- a/docs/specs/visual-explanation.md +++ b/docs/specs/visual-explanation.md @@ -125,6 +125,8 @@ compression. artifact creation, and sufficiently answered short-prose requests do not. Requests for pseudocode or a responsibility map carry explanation intent even when they do not include the word "visual". + Requests for authoritative architecture with missing evidence also select + the capability; it owns making evidence gaps visible under pressure. 2. **VE-C2 — Smallest fitting view.** Representation follows the relationship being explained, with one primary view by default and no catalogue of every supported form. diff --git a/plugins/capability/darrow-explanation/.claude-plugin/plugin.json b/plugins/capability/darrow-explanation/.claude-plugin/plugin.json index 259918c0..ded6148b 100644 --- a/plugins/capability/darrow-explanation/.claude-plugin/plugin.json +++ b/plugins/capability/darrow-explanation/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-explanation", "description": "Compact, grounded visual explanations for technical subjects", - "version": "0.1.3", + "version": "0.1.4", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-explanation/.codex-plugin/plugin.json b/plugins/capability/darrow-explanation/.codex-plugin/plugin.json index ebbe9574..76dc7f55 100644 --- a/plugins/capability/darrow-explanation/.codex-plugin/plugin.json +++ b/plugins/capability/darrow-explanation/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-explanation", - "version": "0.1.3", + "version": "0.1.4", "description": "Compact, grounded visual explanations for technical subjects", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-explanation/skills/explain-visually/SKILL.md b/plugins/capability/darrow-explanation/skills/explain-visually/SKILL.md index 8d434edc..db67fd8f 100644 --- a/plugins/capability/darrow-explanation/skills/explain-visually/SKILL.md +++ b/plugins/capability/darrow-explanation/skills/explain-visually/SKILL.md @@ -1,6 +1,6 @@ --- name: explain-visually -description: Use for requests to explain an algorithm as pseudocode, map file or component responsibilities, show call flow or state transitions, or show a structural diff, even when the user does not say "visual". Also use when asked to visualize, diagram, map, or show technical structure or flow, or when prose is too dense. Explain the existing subject inline through a compact tree, state or sequence diagram, structural diff, pseudocode, signature sketch, or table grounded in sources. Do not use to create or edit HTML pages, images, slides, UI mockups, documentation, or other artifacts; for implementation; or for an ordinary question that short prose answers directly. +description: Handle requests for grounded inline technical diagrams, including requests for authoritative architecture when source evidence is missing and pressure to invent unsupported structure. Use for architecture diagrams, pseudocode, responsibility maps, call flows, state transitions, structural diffs, signature sketches, and exact-mapping tables, even when the user does not say "visual". Also use when asked to visualize technical structure or when prose is too dense. Do not use to create or edit HTML pages, images, slides, UI mockups, documentation, or other artifacts; for implementation; or for ordinary questions without visual-explanation intent. --- # Explain visually