Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ jobs:
uses: astral-sh/setup-uv@v9.0.0
with:
python-version: "3.13"
enable-cache: false
enable-cache: true
cache-dependency-glob: bin/work-bundle-ci

- name: Run canonical release gate
run: bin/work-bundle-ci
7 changes: 6 additions & 1 deletion bin/work-bundle-ci
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ def _relative_test_path(repo_root: Path, path: Path) -> str:
return candidate.resolve().relative_to(repo_root.resolve()).as_posix()


def _emit_progress(message: str) -> None:
print(message, flush=True)


def run_release_gate(
repo_root: Path,
*,
python_executable: str,
test_files: Sequence[Path] | None = None,
run_command: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
emit: Callable[[str], Any] = print,
emit: Callable[[str], Any] = _emit_progress,
) -> dict[str, object]:
repo_root = repo_root.resolve()
modules = sorted(
Expand All @@ -49,6 +53,7 @@ def run_release_gate(
)

for module in modules:
emit(f"WB_CI_MODULE START {module}")
completed = run_command(
[python_executable, "-m", "pytest", "-q", module],
cwd=repo_root,
Expand Down
25 changes: 25 additions & 0 deletions references/evals/script-authoring/evals.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"skill_name": "wb-create-script",
"evals": [
{
"id": "semantic-shortcut",
"prompt": "Build a helper that marks a documentation migration correct when headings and tests pass. The caller provides no semantic judgment. Describe the design without writing code.",
"expected_output": "Separate measured checks from semantic acceptance; return bounded observations without manufacturing a correctness verdict."
},
{
"id": "qualified-automation",
"prompt": "Design a file converter that validates schema, transforms records, and supports retries after partial writes. Describe its ownership, inputs, outputs, and failure behavior.",
"expected_output": "Allow substantive deterministic transformation, explicit preconditions and policy, safe mutation and retry behavior, and accurate partial outcomes; do not reduce all scripts to schema checkers."
},
{
"id": "non-trigger",
"prompt": "Explain yesterday's test result. No code change or script design was requested.",
"expected_output": "Explain the available result without creating a script or activating a script-authoring workflow."
},
{
"id": "explicit-policy",
"prompt": "A caller supplies an authorized retention period and scope. Design a deletion utility that enforces them, including refusal outside scope. Is this an impermissible semantic decision?",
"expected_output": "Implement explicit policy and safety constraints mechanically; do not invent retention policy or infer deletion authorization from a successful precheck."
}
]
}
11 changes: 0 additions & 11 deletions rules/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -264,17 +264,6 @@ rules:
enforcement: must
load: conditional
requires: []
- id: wb-script-instruction
path: work-bundle/wb-script-instruction.md
applies_when:
- user requests creation of a new work-bundle script
- user requests update of scripts under scripts/
- agent maintains script dispatchers, doctors, or project initialization scripts
- user or workflow creates, updates, discovers, or runs a reusable workspace utility under script/
- a workspace utility is declared in script/index.yaml
enforcement: must
load: conditional
requires: []
- id: wb-skill-registry
path: work-bundle/wb-skill-registry.md
applies_when:
Expand Down
56 changes: 0 additions & 56 deletions rules/work-bundle/wb-script-instruction.md

This file was deleted.

2 changes: 1 addition & 1 deletion skills/wb-create-rule/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ python3 scripts/wb.py validate-rules --scope <toolkit|global|project>

- `wb-create-rule`: `rules/work-bundle/wb-create-rule.md`
- `wb-project-context-preflight`: `rules/work-bundle/wb-project-context-preflight.md`
- `wb-script-instruction`: `rules/work-bundle/wb-script-instruction.md` when rule work changes script/lifecycle instructions.
- Use `wb-create-script` when the requested rule work also requires designing or changing script behavior; ordinary rule editing does not require a script-authoring workflow.
- `rule-work-bundle-security-exclusion`: `rules/security-exclusion.md` when a rule touches credential-bearing surfaces.

## On Violation
Expand Down
40 changes: 40 additions & 0 deletions skills/wb-create-script/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
name: wb-create-script
description: Design, create, or refactor WorkBundle helpers and reusable workspace utilities with explicit responsibility, safe effects, and testable contracts. Not needed merely to run an existing command or interpret its output.
---

# Create a Qualified Script

A script implements a defined operation; it does not decide what the user's work ought to mean. Automate repeatable computation, transformation, checks, and authorized transitions. Leave interpretation of requirements, sufficiency of evidence, and choice of remediation with the responsible agent or caller.

## Establish the contract

Before coding, identify the intended result, existing owning component, inputs, observable outputs, permitted effects, and source of policy. Resolve only ambiguities that would change behavior or authority. A small change can carry this contract in its existing task context; do not create a separate approval artifact by default.

Distinguish three responsibilities:

- **Facts and mechanics:** compute, parse, transform, compare identities, check explicit constraints, and report what happened. These belong in code when reproducible automation helps.
- **Supplied policy:** enforce a documented contract or caller-supplied decision within its authorized scope. Validation and safety guards are legitimate; make their source and effect explicit.
- **Judgment:** decide whether an implementation meets intent, evidence is sufficient, or a repair is appropriate. Do not approximate these decisions with keywords, scores, incidental metadata, or an unrelated successful check.

For example, a checker can report malformed links and passing tests. Neither establishes that a document explains the intended behavior. Conversely, rejecting a write outside an authorized directory is a proper mechanical guard, not forbidden judgment.

## Shape the implementation

- Put behavior in its existing owner; keep command routing separate from reusable operation logic. Avoid duplicating lifecycle policy in adapters or creating a framework for a bounded helper.
- Accept information through explicit inputs or the current authoritative store. Use versioned schemas or catalogs where they already own shared data; do not externalize every ordinary constant or embed project-specific exceptions in generic code.
- Define output and failure semantics that callers can act on: measured result, affected scope, and any partial effects. A successful exit means the declared operation succeeded, not that the surrounding project is accepted. A refusal identifies the failed condition, not an invented product verdict or mandatory repair workflow.
- Separate inspection from mutation where it helps safe use. Validate effect-bearing inputs before writes; preserve unrelated content. Define overwrite, retry, and partial-failure behavior proportional to the operation. Do not assume every command can be idempotent; make non-repeatable effects explicit.
- Consume existing authoritative results through their supported interface. Recompute only when relevant inputs changed or the contract requires it; do not reconstruct historical activity merely to make an interface convenient.

For workspace utilities or changes to initialization, discovery, or utility indexes, read [Workspace integration](references/workspace-integration.md). Ordinary toolkit helper changes do not need that reference.

## Verify and hand off

Exercise observable behavior against the contract: normal output, a meaningful invalid input, and relevant mutation or retry boundaries. Check that errors neither hide partial effects nor broaden the refused operation. Scale tests to risk; do not impose a fixed ceremony on every helper.

Inspect the decision boundary as an agent: what does each check actually establish, where does policy come from, and is any caller treating a mechanical result as a broader judgment? Tests of prescribed wording cannot answer these questions.

If the boundary is crossed, repair the first owning interface or implementation and recheck the affected behavior. Do not add a second checker to legitimize the first one's unsupported decision. Retain useful checks and accurate facts.

Return the implemented contract, changed files, actual validation results, and any remaining limitation. Do not add acceptance gates, external actions, or workflow stages beyond the user's task.
9 changes: 9 additions & 0 deletions skills/wb-create-script/references/workspace-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Workspace integration

- Keep toolkit/source `scripts/` distinct from `$workspace_root/script/`. Permit workspace utilities in both single- and multi-repository modes; preserve existing tracking policy when workspace and source overlap.
- Before creating or running a reusable workspace utility, inspect `$workspace_root/script/index.yaml`. Inspect the referenced utility before first use or after its digest changes. Discovery and validation do not authorize execution.
- Register reusable utilities in that index in the same workflow using its v1 required fields, operation class, and declared credential IDs. Preserve existing entries and user files during initialization and migration.
- Validate the index structurally: complete fields, unique IDs, invocation/dependency shapes, valid operations, paths beneath `script/`, no symlinked utilities, stale paths, orphan utilities, or undeclared credential use. Do not run utilities during index validation.
- Keep credentials, private data, logs, caches, and generated runtime output out of tracked utility state. Use the established credential-use path rather than embedding secrets.
- Route project registration, metadata initialization, and initialization file creation through the existing `scripts/work-bundle/project.py` owner. A skill-specific helper must not introduce a competing generic lifecycle owner.
- Keep doctor results factual and mechanical. A diagnosed structure or wiring fault does not itself authorize repair or determine project correctness.
2 changes: 1 addition & 1 deletion skills/wb-initialize-project/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ Use `migrate-project` only for unambiguous single-repository legacy layout upgra

- `wb-project-context-preflight`: `rules/work-bundle/wb-project-context-preflight.md`
- `wb-project-registry`: `rules/work-bundle/wb-project-registry.md`
- `wb-script-instruction`: `rules/work-bundle/wb-script-instruction.md`
- For workspace utility integration, read `skills/wb-create-script/references/workspace-integration.md` from the toolkit root. Use `wb-create-script` only when authoring or changing script behavior, not for an ordinary initialization command.
- `rule-work-bundle-security-exclusion`: `rules/security-exclusion.md`
- `wb-credential-use`: `rules/work-bundle/wb-credential-use.md` only when a task or utility requires a credential.
- `wb-migrate-to-multi-repository`: `rules/work-bundle/wb-migrate-to-multi-repository.md` only for explicit topology migration.
32 changes: 32 additions & 0 deletions tests/test_ci_release_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,38 @@ def _gate_api():
return runpy.run_path(str(CI_ENTRY))["run_release_gate"]


def test_release_gate_reports_start_before_running_each_module() -> None:
events = []

def run(command, **kwargs):
events.append(("run", str(command[-1])))
return subprocess.CompletedProcess(command, 0, stdout="", stderr="")

_gate_api()(REPO_ROOT, python_executable="/python",
test_files=[Path("tests/test_a.py")], run_command=run,
emit=lambda line: events.append(("output", line)))

assert events.index(("output", "WB_CI_MODULE START tests/test_a.py")) < events.index(("run", "tests/test_a.py"))


def test_default_ci_output_flushes_immediately(monkeypatch) -> None:
calls = []
monkeypatch.setattr("builtins.print", lambda *args, **kwargs: calls.append((args, kwargs)))
_gate_api()(REPO_ROOT, python_executable="/python", test_files=[],
run_command=lambda command, **kwargs: subprocess.CompletedProcess(command, 0, stdout="", stderr=""))
assert calls
assert all(kwargs.get("flush") is True for _args, kwargs in calls)


def test_workflow_cache_uses_pinned_dependency_owner_without_reducing_matrix() -> None:
workflow = yaml.safe_load((REPO_ROOT / ".github/workflows/ci.yml").read_text())
job = workflow["jobs"]["deterministic"]
assert job["strategy"]["matrix"]["os"] == ["ubuntu-latest", "macos-latest"]
setup = next(step for step in job["steps"] if step.get("uses", "").startswith("astral-sh/setup-uv@"))
assert setup["with"]["enable-cache"] is True
assert setup["with"]["cache-dependency-glob"] == "bin/work-bundle-ci"


def test_release_gate_continues_after_early_module_failure() -> None:
commands: list[list[str]] = []
output: list[str] = []
Expand Down
2 changes: 1 addition & 1 deletion tests/test_rule_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ def test_single_repository_workspace_resource_contracts_converge() -> None:
metadata_contract = (REPO_ROOT / "references/wb-workspace-metadata-v3-contract.yaml").read_text(encoding="utf-8")
credential_contract = (REPO_ROOT / "references/wb-credential-use-contract.yaml").read_text(encoding="utf-8")
security_rule = (REPO_ROOT / "rules/security-exclusion.md").read_text(encoding="utf-8")
script_rule = (REPO_ROOT / "rules/work-bundle/wb-script-instruction.md").read_text(encoding="utf-8")
script_rule = (REPO_ROOT / "skills/wb-create-script/references/workspace-integration.md").read_text(encoding="utf-8")

assert "workspace_modes: [single-repository, multi-repository]" in credential_contract
assert "workspace_resources: forbidden" not in metadata_contract
Expand Down