From ea82ce4765f20a41c10946db77104588422ceb22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rochel?= Date: Fri, 18 Sep 2026 09:29:06 +0200 Subject: [PATCH 1/4] refactor(skill-authoring): simplify mechanics and clean entrypoints --- docs/specs/skill-authoring.md | 5 +++ .../.claude-plugin/plugin.json | 2 +- .../.codex-plugin/plugin.json | 2 +- .../darrow-skill-authoring/README.md | 8 ++-- .../skills/author-agent-skill/SKILL.md | 4 +- .../author-agent-skill/backend/pyproject.toml | 2 +- .../src/darrow_skill_authoring/inspector.py | 16 +------ .../src/darrow_skill_authoring/shell_tests.py | 42 +++++++------------ .../tests/shell}/inspect-skill.test.sh | 2 +- .../tests/shell}/interpreter-routing.test.sh | 0 .../tests/shell}/verify-shell-tests.test.sh | 2 +- .../backend/tests/test_shell_tests.py | 39 +++++++++++++++++ .../skills/author-agent-skill/backend/uv.lock | 2 +- .../tests/fresh-install.test.sh | 6 +-- 14 files changed, 75 insertions(+), 57 deletions(-) rename plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/{scripts => backend/tests/shell}/inspect-skill.test.sh (99%) rename plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/{scripts => backend/tests/shell}/interpreter-routing.test.sh (100%) rename plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/{scripts => backend/tests/shell}/verify-shell-tests.test.sh (98%) diff --git a/docs/specs/skill-authoring.md b/docs/specs/skill-authoring.md index caa78301..e02cfae7 100644 --- a/docs/specs/skill-authoring.md +++ b/docs/specs/skill-authoring.md @@ -106,6 +106,11 @@ checkable, and recorded evidence for trigger behavior and workflow quality. their executing interpreter in nested implementation calls. A packaged skill remains useful with no MCP server or other Darrow plugin installed unless its own manifest declares and supplies that dependency. + This plugin exposes its inspector and shell-test matrix only through their + frozen UV console entrypoints, without legacy launchers or module-execution + aliases. Internal simplification preserves argument consumption, interpreter + selection, diagnostic order, output records, and exit statuses. Regression + shell suites live with the backend tests, not as runtime skill scripts. - **SA-C8 — Evaluation separates prompt from criteria.** Participant-visible eval prompts contain the task and repository evidence but not their pass criteria. Hidden deterministic checks or rubrics cover workflow behavior, diff --git a/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json b/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json index 20c55811..79c380ee 100644 --- a/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json +++ b/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-skill-authoring", "description": "Create and improve reliable cross-runtime agent skills", - "version": "0.2.1", + "version": "0.2.2", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", diff --git a/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json b/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json index 3e5a082a..2884883b 100644 --- a/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json +++ b/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-skill-authoring", - "version": "0.2.1", + "version": "0.2.2", "description": "Create and improve reliable cross-runtime agent skills", "author": { "name": "Björn Rochel", diff --git a/plugins/foundation/darrow-skill-authoring/README.md b/plugins/foundation/darrow-skill-authoring/README.md index c4a495e5..e8c79a07 100644 --- a/plugins/foundation/darrow-skill-authoring/README.md +++ b/plugins/foundation/darrow-skill-authoring/README.md @@ -34,16 +34,16 @@ uv sync --quiet --frozen --no-dev --project skills/author-agent-skill/backend ``` From the repository root, run the Python quality checks with -`bun run check:python`. From this plugin directory, run the compatibility shell +`bun run check:python`. From this plugin directory, run the shell regression suites through the version-aware matrix: ```text uv run --quiet --frozen --no-dev \ --project skills/author-agent-skill/backend \ verify-shell-tests -- \ - skills/author-agent-skill/scripts/inspect-skill.test.sh \ - skills/author-agent-skill/scripts/verify-shell-tests.test.sh \ - skills/author-agent-skill/scripts/interpreter-routing.test.sh + skills/author-agent-skill/backend/tests/shell/inspect-skill.test.sh \ + skills/author-agent-skill/backend/tests/shell/verify-shell-tests.test.sh \ + skills/author-agent-skill/backend/tests/shell/interpreter-routing.test.sh ``` The matrix reports observed interpreter versions. Exit `3` leaves an unavailable diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/SKILL.md b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/SKILL.md index cf5c6a8f..f65672f0 100644 --- a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/SKILL.md +++ b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/SKILL.md @@ -37,8 +37,8 @@ backend: uv run --quiet --frozen --no-dev --project "/backend" verify-shell-tests -- ""... ``` -Base every shell-version claim on its emitted evidence. The small scripts with -the same command names preserve compatibility for existing POSIX callers. +Base every shell-version claim on its emitted evidence. Invoke both packaged +commands directly; this plugin has no runtime shell launchers. ## Workflow diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/pyproject.toml b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/pyproject.toml index 6e12d27b..3ed25a77 100644 --- a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/pyproject.toml +++ b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "darrow-skill-authoring" -version = "0.1.0" +version = "0.1.1" description = "Portable deterministic helpers for Darrow skill authoring" requires-python = ">=3.10,<3.14" dependencies = [] diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/src/darrow_skill_authoring/inspector.py b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/src/darrow_skill_authoring/inspector.py index dade18ba..7b7008ee 100644 --- a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/src/darrow_skill_authoring/inspector.py +++ b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/src/darrow_skill_authoring/inspector.py @@ -113,14 +113,6 @@ def _validate_metadata(metadata: Metadata, skill_directory: Path) -> Metadata: return Metadata(name, description) -def _is_within(path: Path, root: Path) -> bool: - try: - path.relative_to(root) - except ValueError: - return False - return True - - def _skill_directory(skill_input: str, plugin_root: Path) -> Path: attempted = _attempted_path(skill_input) if not attempted.is_dir(): @@ -128,7 +120,7 @@ def _skill_directory(skill_input: str, plugin_root: Path) -> Path: f"skill file is not a readable regular file: {attempted / 'SKILL.md'}" ) skill_directory = _readable_directory(skill_input, "skill directory") - if not _is_within(skill_directory, plugin_root): + if not skill_directory.is_relative_to(plugin_root): raise InspectionError(f"skill directory escapes plugin root: {skill_directory}") return skill_directory @@ -182,7 +174,7 @@ def _local_reference(target: str, skill_directory: Path, plugin_root: Path) -> P f"local reference parent is not readable: {parent_input}" ) from error reference = parent / relative.name - if not _is_within(reference, plugin_root): + if not reference.is_relative_to(plugin_root): raise InspectionError(f"local reference escapes plugin root: {reference}") if not reference.is_file() or not os.access(reference, os.R_OK): raise InspectionError( @@ -297,7 +289,3 @@ def run(arguments: list[str], *, stdout: TextIO, stderr: TextIO) -> int: def entrypoint() -> None: raise SystemExit(run(sys.argv[1:], stdout=sys.stdout, stderr=sys.stderr)) - - -if __name__ == "__main__": - entrypoint() diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/src/darrow_skill_authoring/shell_tests.py b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/src/darrow_skill_authoring/shell_tests.py index 2a24c66e..ff240822 100644 --- a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/src/darrow_skill_authoring/shell_tests.py +++ b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/src/darrow_skill_authoring/shell_tests.py @@ -30,33 +30,24 @@ class BashInterpreter: version: str -def _consume_shell_options( - arguments: list[str], candidates: list[str], position: int -) -> tuple[list[str], int]: - if position >= len(arguments): - return candidates, position - argument = arguments[position] - if argument == "--shell": - return _consume_shell_argument(arguments, candidates, position) - if argument == "--": - return candidates, position + 1 - if argument.startswith("-"): - raise ValueError +def _consume_shell_options(arguments: list[str]) -> tuple[list[str], int]: + candidates: list[str] = [] + position = 0 + while position < len(arguments) and arguments[position] == "--shell": + if position + 1 >= len(arguments): + raise ValueError + candidates.append(arguments[position + 1]) + position += 2 return candidates, position -def _consume_shell_argument( - arguments: list[str], candidates: list[str], position: int -) -> tuple[list[str], int]: - if position + 1 >= len(arguments): - raise ValueError - candidates.append(arguments[position + 1]) - return _consume_shell_options(arguments, candidates, position + 2) - - def _parse_arguments(arguments: list[str]) -> tuple[list[str], list[str], bool]: - candidates, position = _consume_shell_options(arguments, [], 0) + candidates, position = _consume_shell_options(arguments) tests = arguments[position:] + if tests[:1] == ["--"]: + tests = tests[1:] + elif tests and tests[0].startswith("-"): + raise ValueError if not tests: raise ValueError return candidates or list(DEFAULT_CANDIDATES), tests, bool(candidates) @@ -64,8 +55,7 @@ def _parse_arguments(arguments: list[str]) -> tuple[list[str], list[str], bool]: def _canonical_file(value: str) -> Path: path = Path(value) - parent = path.parent if path.parent != Path("") else Path.cwd() - return parent.resolve(strict=True) / path.name + return path.parent.resolve(strict=True) / path.name def _resolve_interpreter(requested: str) -> Path | None: @@ -243,7 +233,3 @@ def run(arguments: list[str], *, stdout: TextIO, stderr: TextIO) -> int: def entrypoint() -> None: raise SystemExit(run(sys.argv[1:], stdout=sys.stdout, stderr=sys.stderr)) - - -if __name__ == "__main__": - entrypoint() diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/scripts/inspect-skill.test.sh b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/shell/inspect-skill.test.sh similarity index 99% rename from plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/scripts/inspect-skill.test.sh rename to plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/shell/inspect-skill.test.sh index 0974215d..4e8ee8b5 100755 --- a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/scripts/inspect-skill.test.sh +++ b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/shell/inspect-skill.test.sh @@ -2,7 +2,7 @@ set -u SCRIPT_DIR=$(cd "$(dirname "$0")" 2>/dev/null && pwd -P) -BACKEND=$(cd "$SCRIPT_DIR/../backend" 2>/dev/null && pwd -P) +BACKEND=$(cd "$SCRIPT_DIR/../.." 2>/dev/null && pwd -P) TAB=$(printf '\t') FAILURES=0 TEMPS=() diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/scripts/interpreter-routing.test.sh b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/shell/interpreter-routing.test.sh similarity index 100% rename from plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/scripts/interpreter-routing.test.sh rename to plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/shell/interpreter-routing.test.sh diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/scripts/verify-shell-tests.test.sh b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/shell/verify-shell-tests.test.sh similarity index 98% rename from plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/scripts/verify-shell-tests.test.sh rename to plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/shell/verify-shell-tests.test.sh index 28064eae..e55da880 100755 --- a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/scripts/verify-shell-tests.test.sh +++ b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/shell/verify-shell-tests.test.sh @@ -2,7 +2,7 @@ set -u SCRIPT_DIR=$(cd "$(dirname "$0")" 2>/dev/null && pwd -P) -BACKEND=$(cd "$SCRIPT_DIR/../backend" 2>/dev/null && pwd -P) +BACKEND=$(cd "$SCRIPT_DIR/../.." 2>/dev/null && pwd -P) TAB=$(printf '\t') FAILURES=0 TEMPS=() diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/test_shell_tests.py b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/test_shell_tests.py index 1a20a726..62603e61 100644 --- a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/test_shell_tests.py +++ b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/tests/test_shell_tests.py @@ -103,6 +103,45 @@ def test_missing_test_is_identified_with_exit_two(tmp_path: Path) -> None: ) +@pytest.mark.parametrize( + ("arguments", "candidates", "tests"), + [ + ( + ["--shell", "first", "--shell", "second", "test.sh"], + ["first", "second"], + ["test.sh"], + ), + (["--shell", "--", "test.sh"], ["--"], ["test.sh"]), + (["--shell", "", "--", "test.sh"], [""], ["test.sh"]), + (["--", "--unknown"], None, ["--unknown"]), + (["test.sh", "--shell", "--"], None, ["test.sh", "--shell", "--"]), + ], +) +def test_argument_boundaries_reach_the_matrix( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + arguments: list[str], + candidates: list[str] | None, + tests: list[str], +) -> None: + monkeypatch.chdir(tmp_path) + for name in tests: + (tmp_path / name).write_text("exit 0\n", encoding="utf-8") + with patch.object( + shell_tests, "discover_interpreters", return_value=[] + ) as discover: + status, output, error = invoke(*arguments) + discover.assert_called_once_with( + candidates if candidates is not None else list(shell_tests.DEFAULT_CANDIDATES), + explicit=candidates is not None, + ) + assert status == 3 + assert error == "" + assert output.splitlines()[1 : 1 + len(tests)] == [ + f"test_script\t{tmp_path.resolve() / name}" for name in tests + ] + + def test_explicit_shell_discovery_error_is_reported(tmp_path: Path) -> None: test = tmp_path / "passing.sh" test.write_text("exit 0\n", encoding="utf-8") diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/uv.lock b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/uv.lock index 8bc57b4f..8f7c2b5e 100644 --- a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/uv.lock +++ b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/uv.lock @@ -86,7 +86,7 @@ toml = [ [[package]] name = "darrow-skill-authoring" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } [package.dev-dependencies] diff --git a/plugins/foundation/darrow-skill-authoring/tests/fresh-install.test.sh b/plugins/foundation/darrow-skill-authoring/tests/fresh-install.test.sh index 8e829bca..470d73f0 100755 --- a/plugins/foundation/darrow-skill-authoring/tests/fresh-install.test.sh +++ b/plugins/foundation/darrow-skill-authoring/tests/fresh-install.test.sh @@ -28,9 +28,9 @@ printf '%s\n' "$inspection" | grep '^status[[:space:]]valid$' >/dev/null set +e matrix=$(uv run --quiet --frozen --no-dev --project "$backend" \ verify-shell-tests -- \ - "$skill/scripts/inspect-skill.test.sh" \ - "$skill/scripts/verify-shell-tests.test.sh" \ - "$skill/scripts/interpreter-routing.test.sh") + "$backend/tests/shell/inspect-skill.test.sh" \ + "$backend/tests/shell/verify-shell-tests.test.sh" \ + "$backend/tests/shell/interpreter-routing.test.sh") matrix_status=$? set -e case "$matrix_status" in From eb07d23a35d781d9b906a8e47a5c6ee3bab9b0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rochel?= Date: Fri, 18 Sep 2026 09:29:32 +0200 Subject: [PATCH 2/4] refactor(discovery): simplify frontier parsing and rendering --- docs/specs/discovery.md | 3 +++ .../.claude-plugin/plugin.json | 2 +- .../.codex-plugin/plugin.json | 2 +- .../backend/pyproject.toml | 2 +- .../backend/src/darrow_discovery/frontier.py | 27 +++++++------------ .../plan-implementation/backend/uv.lock | 2 +- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/docs/specs/discovery.md b/docs/specs/discovery.md index 054825e1..80099628 100644 --- a/docs/specs/discovery.md +++ b/docs/specs/discovery.md @@ -348,6 +348,9 @@ skill. from the installed skill directory and invoke the same locked package entrypoint directly. Fresh-artifact checks exercise that installed layout without repository-relative references or platform-specific launchers. + Internal simplification preserves literal flag values, last-wins scalar + options, ordered repeated option labels, validation order, and removal of + exactly one terminal period before rendering prose. ## Evaluation requirements diff --git a/plugins/capability/darrow-discovery/.claude-plugin/plugin.json b/plugins/capability/darrow-discovery/.claude-plugin/plugin.json index 6c4c38f0..bfa903f8 100644 --- a/plugins/capability/darrow-discovery/.claude-plugin/plugin.json +++ b/plugins/capability/darrow-discovery/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-discovery", "description": "Explicit grilling, feature discovery, and implementation planning", - "version": "0.2.0", + "version": "0.2.1", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-discovery/.codex-plugin/plugin.json b/plugins/capability/darrow-discovery/.codex-plugin/plugin.json index c6ed0b37..ff883ada 100644 --- a/plugins/capability/darrow-discovery/.codex-plugin/plugin.json +++ b/plugins/capability/darrow-discovery/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-discovery", - "version": "0.2.0", + "version": "0.2.1", "description": "Explicit grilling, feature discovery, and implementation planning", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-discovery/skills/plan-implementation/backend/pyproject.toml b/plugins/capability/darrow-discovery/skills/plan-implementation/backend/pyproject.toml index 548c0c11..f9fea884 100644 --- a/plugins/capability/darrow-discovery/skills/plan-implementation/backend/pyproject.toml +++ b/plugins/capability/darrow-discovery/skills/plan-implementation/backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "darrow-discovery" -version = "0.2.0" +version = "0.2.1" description = "Deterministic helpers for Darrow discovery and planning" requires-python = ">=3.10,<3.14" dependencies = [] diff --git a/plugins/capability/darrow-discovery/skills/plan-implementation/backend/src/darrow_discovery/frontier.py b/plugins/capability/darrow-discovery/skills/plan-implementation/backend/src/darrow_discovery/frontier.py index 8ffedf38..c4812cfa 100644 --- a/plugins/capability/darrow-discovery/skills/plan-implementation/backend/src/darrow_discovery/frontier.py +++ b/plugins/capability/darrow-discovery/skills/plan-implementation/backend/src/darrow_discovery/frontier.py @@ -8,12 +8,12 @@ from io import TextIOWrapper from typing import cast +PROGRAM_NAME = "darrow-render-plan-frontier" USAGE = ( - "usage: {program} --evidence TEXT --question TEXT --option LABEL " + f"usage: {PROGRAM_NAME} --evidence TEXT --question TEXT --option LABEL " "--option LABEL [--option LABEL ...] --choice LABEL --rationale TEXT " "--deferred TEXT" ) -PROGRAM_NAME = "darrow-render-plan-frontier" FIELD_FLAGS = { "--evidence": "evidence", "--question": "question", @@ -47,10 +47,9 @@ def parse(arguments: Sequence[str]) -> Frontier: """Parse the renderer's stable flag-and-value command contract.""" fields = dict.fromkeys(FIELD_FLAGS.values(), "") options: list[str] = [] - index = 0 - while index < len(arguments): - if index + 1 >= len(arguments): - raise UsageError + if len(arguments) % 2: + raise UsageError + for index in range(0, len(arguments), 2): flag, value = arguments[index : index + 2] if flag == "--option": options.append(value) @@ -58,7 +57,6 @@ def parse(arguments: Sequence[str]) -> Frontier: fields[FIELD_FLAGS[flag]] = value else: raise UsageError - index += 2 return Frontier(options=tuple(options), **fields) @@ -128,15 +126,11 @@ def validate(frontier: Frontier) -> None: _validate_choice(frontier) -def _without_one_period(value: str) -> str: - return value[:-1] if value.endswith(".") else value - - def render(frontier: Frontier) -> str: """Render an already validated frontier in the canonical Markdown shape.""" - evidence = _without_one_period(frontier.evidence) - rationale = _without_one_period(frontier.rationale) - deferred = _without_one_period(frontier.deferred) + evidence = frontier.evidence.removesuffix(".") + rationale = frontier.rationale.removesuffix(".") + deferred = frontier.deferred.removesuffix(".") return ( f"Evidence: {evidence}.\n\n" f"Q1 — {frontier.question}\n\n" @@ -146,14 +140,13 @@ def render(frontier: Frontier) -> str: ) -def main(arguments: Sequence[str] | None = None, *, program: str | None = None) -> int: +def main(arguments: Sequence[str] | None = None) -> int: """Run the renderer and return its public process status.""" actual_arguments = tuple(sys.argv[1:] if arguments is None else arguments) - actual_program = PROGRAM_NAME if program is None else program try: frontier = parse(actual_arguments) except UsageError: - print(USAGE.format(program=actual_program), file=sys.stderr) + print(USAGE, file=sys.stderr) return 2 try: validate(frontier) diff --git a/plugins/capability/darrow-discovery/skills/plan-implementation/backend/uv.lock b/plugins/capability/darrow-discovery/skills/plan-implementation/backend/uv.lock index 05942681..94ab4712 100644 --- a/plugins/capability/darrow-discovery/skills/plan-implementation/backend/uv.lock +++ b/plugins/capability/darrow-discovery/skills/plan-implementation/backend/uv.lock @@ -86,7 +86,7 @@ toml = [ [[package]] name = "darrow-discovery" -version = "0.2.0" +version = "0.2.1" source = { editable = "." } [package.dev-dependencies] From e34594750583ff071be2feacb92ffe1f6c623787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rochel?= Date: Fri, 18 Sep 2026 09:30:04 +0200 Subject: [PATCH 3/4] refactor(verification): simplify assessment escaping and errors --- docs/specs/verification.md | 3 +++ .../.claude-plugin/plugin.json | 2 +- .../.codex-plugin/plugin.json | 2 +- .../verify-change/backend/pyproject.toml | 2 +- .../src/darrow_verification/assessment.py | 26 ++++++++++--------- .../skills/verify-change/backend/uv.lock | 2 +- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/docs/specs/verification.md b/docs/specs/verification.md index 80bd4e6f..8bd333ae 100644 --- a/docs/specs/verification.md +++ b/docs/specs/verification.md @@ -94,6 +94,9 @@ delivery under #155. It does not execute QA or create reviewer-facing evidence p skill directory and invoke the same frozen package entrypoint directly. Fresh copied-artifact checks MUST exercise that installed layout without repository-relative or sibling-plugin references. + Internal simplification MUST preserve assessment-before-report validation, + exact diagnostics and exit statuses, assessment bytes, and the existing + seven-character destination escape set without double-escaping replacements. ## Consumer handoff diff --git a/plugins/capability/darrow-verification/.claude-plugin/plugin.json b/plugins/capability/darrow-verification/.claude-plugin/plugin.json index 36694558..a2e659df 100644 --- a/plugins/capability/darrow-verification/.claude-plugin/plugin.json +++ b/plugins/capability/darrow-verification/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-verification", - "version": "0.2.0", + "version": "0.2.1", "description": "Bounded acceptance verification through replaceable independent code review", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", "email": "bjoern@bjro.de" } diff --git a/plugins/capability/darrow-verification/.codex-plugin/plugin.json b/plugins/capability/darrow-verification/.codex-plugin/plugin.json index 273e2d9a..2d8554d1 100644 --- a/plugins/capability/darrow-verification/.codex-plugin/plugin.json +++ b/plugins/capability/darrow-verification/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-verification", - "version": "0.2.0", + "version": "0.2.1", "description": "Bounded acceptance verification through replaceable independent code review", "author": { "name": "Björn Rochel", "email": "bjoern@bjro.de" }, "skills": "./skills/", diff --git a/plugins/capability/darrow-verification/skills/verify-change/backend/pyproject.toml b/plugins/capability/darrow-verification/skills/verify-change/backend/pyproject.toml index 1af073a6..10710843 100644 --- a/plugins/capability/darrow-verification/skills/verify-change/backend/pyproject.toml +++ b/plugins/capability/darrow-verification/skills/verify-change/backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "darrow-verification" -version = "0.2.0" +version = "0.2.1" description = "Deterministic helpers for Darrow verification" requires-python = ">=3.10,<3.14" dependencies = [] diff --git a/plugins/capability/darrow-verification/skills/verify-change/backend/src/darrow_verification/assessment.py b/plugins/capability/darrow-verification/skills/verify-change/backend/src/darrow_verification/assessment.py index 16b44735..aec37a1d 100644 --- a/plugins/capability/darrow-verification/skills/verify-change/backend/src/darrow_verification/assessment.py +++ b/plugins/capability/darrow-verification/skills/verify-change/backend/src/darrow_verification/assessment.py @@ -15,6 +15,17 @@ "--provider-report ABSOLUTE_FILE" ) ERROR_PREFIX = "render-assessment" +DESTINATION_ESCAPES = str.maketrans( + { + "%": "%25", + " ": "%20", + "#": "%23", + "?": "%3F", + "<": "%3C", + ">": "%3E", + "\\": "%5C", + } +) class BinaryWriter(Protocol): @@ -82,13 +93,8 @@ def canonical_input(value: str, label: str) -> Path: def escape_destination(value: str) -> str: - """Escape the Markdown URI delimiters escaped by the original renderer.""" - destination = value.replace("%", "%25") - destination = destination.replace(" ", "%20") - destination = destination.replace("#", "%23") - destination = destination.replace("?", "%3F") - destination = destination.replace("<", "%3C").replace(">", "%3E") - return destination.replace("\\", "%5C") + """Escape only the Markdown URI delimiters in the handoff contract.""" + return value.translate(DESTINATION_ESCAPES) def validate(arguments: Sequence[str]) -> AssessmentInputs: @@ -124,17 +130,13 @@ def main( """Run the renderer and return its public process status.""" try: inputs = validate(arguments) + output = render(inputs) except UsageError: _write_error(stderr, USAGE) return 2 except InputError as error: _write_error(stderr, str(error)) return 2 - try: - output = render(inputs) - except InputError as error: - _write_error(stderr, str(error)) - return 2 stdout.write(output) return 0 diff --git a/plugins/capability/darrow-verification/skills/verify-change/backend/uv.lock b/plugins/capability/darrow-verification/skills/verify-change/backend/uv.lock index 4f4fd133..2950350a 100644 --- a/plugins/capability/darrow-verification/skills/verify-change/backend/uv.lock +++ b/plugins/capability/darrow-verification/skills/verify-change/backend/uv.lock @@ -86,7 +86,7 @@ toml = [ [[package]] name = "darrow-verification" -version = "0.2.0" +version = "0.2.1" source = { editable = "." } [package.dev-dependencies] From 262cbdf8af282fbc6bbb8e45dc2409b024599178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Rochel?= Date: Fri, 18 Sep 2026 09:49:52 +0200 Subject: [PATCH 4/4] docs(python): require idiomatic conversions without redundant shims --- AGENTS.md | 7 ++++++ docs/specs/python-quality.md | 22 +++++++++++++++++++ docs/specs/skill-authoring.md | 7 ++++++ .../.claude-plugin/plugin.json | 2 +- .../.codex-plugin/plugin.json | 2 +- .../references/plugin-mechanics.md | 22 +++++++++++++++++-- 6 files changed, 58 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 20068d27..8337cbb5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,6 +44,13 @@ Review agents must not run Git or GitHub commands against this repository. support. Commit `pyproject.toml` and `uv.lock`, keep runtime and development dependencies separate, invoke runtime entrypoints with frozen resolution, and register the package in `python-packages.txt`. +- For Python conversions, preserve required public behavior rather than + translating Bash verbatim. Simplify the design with idiomatic Python, + standard-library operations, and focused reuse within the owning package. + Remove unnecessary Bash shims, obsolete aliases, and compatibility branches; + update callers and tests to invoke the frozen package entrypoints directly. + Retain an adapter only for an explicitly required external contract or an + actual host protocol, not merely because the old script path existed. - Keep genuinely small host-specific launchers and command glue in portable Bash, using baseline Unix utilities and the host CLIs the capability wraps. Do not introduce JavaScript/TypeScript, Ruby, JVM, compiled, or shared Darrow diff --git a/docs/specs/python-quality.md b/docs/specs/python-quality.md index 1040368e..29499318 100644 --- a/docs/specs/python-quality.md +++ b/docs/specs/python-quality.md @@ -27,6 +27,28 @@ Automation cannot judge every aspect of those rules. Reviewers inspect names, module cohesion, function focus, error boundaries, and whether assertions verify observable behavior rather than implementation details. +## Converting existing mechanics + +Treat a Bash implementation as evidence of required behavior, not a Python +design template. Establish the public inputs, outputs, refusals, side effects, +and ordering constraints before refactoring. Prefer idiomatic Python and +standard-library operations over hand-written equivalents; replace shell-style +control flow and duplicated mechanics with direct operations and focused reuse +within the owning module or package. Keep abstractions proportional to their +actual callers and keep plugins independently installable. + +Audit the complete invocation surface during the conversion. Remove unnecessary +Bash shims, obsolete aliases, and backward-compatibility branches. Update skill +instructions, documentation, callers, fixtures, and tests to the canonical +frozen package entrypoints. Retain an adapter only when an explicitly required +external contract or actual host protocol needs it; an old script path alone +is not such a requirement. Keep shell regression tests as tests, not runtime +entrypoints, and verify the copied plugin without the removed paths. + +Use behavioral and differential checks to preserve the required interface while +changing the internals. High coverage is a guard for simplification, not a reason +to keep redundant code or mirror the old implementation in new tests. + ## Tests and coverage Each package keeps fast unit tests for local behavior and integration tests for diff --git a/docs/specs/skill-authoring.md b/docs/specs/skill-authoring.md index e02cfae7..080052f3 100644 --- a/docs/specs/skill-authoring.md +++ b/docs/specs/skill-authoring.md @@ -82,6 +82,13 @@ checkable, and recorded evidence for trigger behavior and workflow quality. contract inputs instead of importing them from the authoring skill's source repository. Do not add a script when existing tools and instructions are already reliable. + A migration preserves the required public behavior, not the source + implementation's structure. When Python is selected, use idiomatic Python + and standard-library operations, simplify control flow, and reuse focused + mechanics within the package instead of translating shell code verbatim. + Remove obsolete wrappers, aliases, and compatibility branches, updating + callers and tests to the canonical entrypoints. A legacy path alone does not + establish a requirement to retain a shim. - **SA-C6 — Fail closed and identify evidence.** A required unreadable skill, manifest, configuration, instruction, or referenced local resource blocks the dependent result. Model-facing script output identifies inspected inputs and diff --git a/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json b/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json index 79c380ee..f17c512b 100644 --- a/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json +++ b/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-skill-authoring", "description": "Create and improve reliable cross-runtime agent skills", - "version": "0.2.2", + "version": "0.2.3", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", diff --git a/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json b/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json index 2884883b..48371754 100644 --- a/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json +++ b/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-skill-authoring", - "version": "0.2.2", + "version": "0.2.3", "description": "Create and improve reliable cross-runtime agent skills", "author": { "name": "Björn Rochel", diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/references/plugin-mechanics.md b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/references/plugin-mechanics.md index 70c22bac..926779f5 100644 --- a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/references/plugin-mechanics.md +++ b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/references/plugin-mechanics.md @@ -37,12 +37,30 @@ established conventions. Invoke public operations through the target's locked package command. Prefer a host-provided installed skill directory when one skill owns the helper, and do not add a launcher solely to calculate that package location. Retain a small -compatibility launcher only when an existing caller contract requires a stable -command or the launcher adapts a host lifecycle or protocol. It only dispatches +compatibility launcher only when an explicitly required external contract needs +a stable command or the launcher adapts an actual host lifecycle or protocol. +An old script path alone does not establish that requirement. It only dispatches to the same entrypoint and contains no duplicated mechanics. Give every public operation the same arguments, output, refusal, and exit-status contract on each supported platform. +## Convert existing mechanics + +1. Establish the required public inputs, outputs, refusals, side effects, and + ordering constraints. Preserve those contracts rather than the old code's + structure. +2. Design for the selected language. When the target chooses Python, use its + idioms and standard library, simplify control flow, and reuse focused + mechanics within the module or package. Do not translate Bash verbatim or + introduce a general framework to share a few lines. +3. Audit every entrypoint and caller. Remove unnecessary Bash shims, obsolete + aliases, and backward-compatibility branches; update instructions, + documentation, fixtures, and tests to the canonical locked commands. Keep + shell regression suites with tests, separate from runtime entrypoints. +4. Verify behavior at public seams, using before/after comparisons where useful. + Exercise a fresh copied plugin without the removed entrypoints. Treat + coverage as support for simplification, not justification for redundant code. + ## Test and release the helper Keep fast unit and integration tests beside the package. Apply the target