Skip to content
Closed
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
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,31 @@ jobs:
run: |
cd docs/assets && python gen_brand_assets.py --check

# The packaged emitted-artifact templates (src/chock/data/templates/) hold real
# shell and workflow YAML with inert __TOKEN__ placeholders, so each file is linted
# as its own language. The ci/step.yaml fragment is not a complete workflow, so
# actionlint cannot check it; tests/test_template_data.py pins it instead.
template-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.12"
- name: Install template linters
run: |
python -m pip install --require-hashes --upgrade -r requirements/pip.txt
python -m pip install --require-hashes -r requirements/template-lint.txt
- name: Shell templates lint as shell
run: |
shellcheck src/chock/data/templates/git-hook/shim.sh src/chock/data/templates/hooks/*.sh
# The in-agent guard command is a shebang-less one-liner embedded in hook JSON.
shellcheck -s sh src/chock/data/templates/in-agent/guard-command.sh
- name: Workflow template lints as a complete workflow
run: actionlint src/chock/data/templates/scaffold/ci-workflow.yml

validate:
strategy:
fail-fast: false
Expand Down Expand Up @@ -212,6 +237,9 @@ jobs:
"$CHOCK" init . --skip-hooks
# init ships no policies, so the scaffolding is what proves the binary works.
[ -f AGENTS.md ] && [ -f .chock/config.yaml ] && [ -f .agents/policies/INDEX.md ]
# The pointer block is rendered from packaged template data, so this line
# proves the frozen binary can read src/chock/data/templates/.
grep -q 'chock:pointer:start' AGENTS.md

acceptance:
name: acceptance (tier 1)
Expand Down
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ code_comments: {
keep: [noqa, pragma, "type:", "fmt:", "ruff:", shebang, adopter_template_markers],
target: prose_to_code <=0.15, enforcement: advisory
}
externalized_text: {
applies_to: [emitted_artifacts(non_python), vendor_facts],
location: data_or_template_files(read_by_code), never: python_string_literals,
placeholders: {token: __TOKEN__, swap: str.replace, never: format_or_fstring},
lint: own_language(actionlint|shellcheck|ruff),
coverage_tested: [package_data, frozen_binary_spec],
stays_in_code: [error_messages, behaviour]
}
progressive_disclosure: {SKILL.md: activation_surface, depth: references/, inline: false}
budgets: {SKILL.md: <=150, description: <=500, references: <=300, ambient_rule: <=2}
validation: {pre_change: chock check, touched: [validate, eval]}
Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

## Unreleased

- **Emitted-artifact templates move out of Python source into package data, and the CLI
command table becomes data.** Every template for an emitted artifact written in another
language -- the CI gate step (YAML), the git-hook shim and the hook installer's
dispatcher/wrappers (shell), the in-agent guard one-liners (shell + PowerShell), the
scaffold workflow, `.gitattributes`, guardrail and AGENTS.md pointer blocks, the
skills-bridge marker, and the runtime-bundle handler sources (`.py.tmpl`) -- now lives
under `src/chock/data/templates/`, loaded via `importlib.resources`
(`chock.resources.template_text`/`render_template`). Placeholders are inert `__TOKEN__`
markers swapped by `str.replace`, so each file is valid in its own language as-is and CI
lints it as such (new `template-lint` job: shellcheck for shell, actionlint for the
complete workflow template; the step fragment and the Python/YAML templates are pinned
by `tests/test_template_data.py`, which also asserts every template is rendered and
every token round-trips). The CLI's command table moves to `src/chock/data/commands.json`
(name -> module/fn/help/alias_of), read by `chock.cli`, which keeps its lazy-import
dispatch; `chock --help` is frozen byte-for-byte by `tests/fixtures/cli_help.txt`. The
template tree is covered by package-data (pinned in `tests/test_wheel_install.py`) and
by the PyInstaller spec's `collect_data_files("chock")` (the binary smoke now proves a
frozen template read). Emitted bytes are unchanged: emitter goldens, runtime goldens and
a full-artifact before/after diff (sync + plugin build + marketplace build, 490 files)
are byte-identical.

- **In-agent membership derives from agentseam's capability matrix, and the surface
extends to seven new vendors** (design C3, `docs/design/derive-from-vendor-config.md`).
`IN_AGENT_TODAY`, `SURFACE_AGENTS`, `RUNTIME_AGENTS` and `VENDORED_RUNTIMES` stop being
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ build-backend = "setuptools.build_meta"
where = ["src"]

[tool.setuptools.package-data]
"chock" = ["data/*.json"]
# templates/**/* : the emitted-artifact templates (shell, YAML, PowerShell, .py.tmpl)
# every emitter renders at run time; pinned by tests/test_wheel_install.py.
"chock" = ["data/*.json", "data/templates/**/*"]
"chock.validation" = ["schemas/*.json", "frontier_standards/*.json"]
"chock.hooks" = ["data/*"]
# `**/*` does NOT match dotfiles or dot-directories, so the chock-init
Expand Down
2 changes: 2 additions & 0 deletions requirements/template-lint.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
actionlint-py
shellcheck-py
16 changes: 16 additions & 0 deletions requirements/template-lint.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --generate-hashes --no-index --output-file=requirements/template-lint.txt --strip-extras requirements/template-lint.in
#
actionlint-py==1.7.12.24 \
--hash=sha256:7571b0724fde79b2572b98b2b53792c470249d4db29951b57fc49b9cd3eaf11e
# via -r requirements/template-lint.in
shellcheck-py==0.11.0.1 \
--hash=sha256:1b274df81de5b000ff78db433e7328b87e52e3c38481c60f8e488c3095beef05 \
--hash=sha256:5c620c88901e8f1d3be5934b31ea99e3310065e1245253741eafd0a275c8c9cc \
--hash=sha256:6b88d0a244c82ed07e06a53e444da841f69330ca59ae15d4a66c391655dae7a0 \
--hash=sha256:784156289ecb17e91c692cd783ab5152333309588cabb10032a047331c63e759 \
--hash=sha256:b6a3fee28efda2e16e38d6e6d59faf7224300256456639727370d404730849e8
# via -r requirements/template-lint.in
80 changes: 18 additions & 62 deletions src/chock/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@

from __future__ import annotations

import json
import sys
from importlib import import_module

from chock import __version__
from chock.pipe import guard_stdout, silence_interpreter_flush
from chock.resources import package_data_dir


def _module_main(module_name: str):
Expand All @@ -28,68 +30,22 @@ def _main(argv: list[str] | None) -> int:
return _main


EVERYDAY = {
"init": (_module_main("chock.scaffold.init"), "Scaffold a consumer repo (wiring only -- no policies)"),
"add": (_module_main("chock.scaffold.add"), "Install a policy or skill from a catalog and compile it"),
"remove": (_module_main("chock.scaffold.remove"), "Remove an installed policy and resync"),
"sync": (
_module_fn("chock.lifecycle", "sync_main"),
"Recompile + rewire so the repo matches its policies (--ci/--skills for extras)",
),
"check": (
_module_fn("chock.lifecycle", "check_main"),
"Run every truth check: validate, verify, evals, matrix (--only to narrow)",
),
"status": (
_module_fn("chock.lifecycle", "status_main"),
"Policy states and coverage (--only registry,log for more)",
),
"enable": (_module_fn("chock.toggles", "enable_main"), "Enable a policy by id"),
"disable": (_module_fn("chock.toggles", "disable_main"), "Disable a policy by id"),
}

AUTHORING = {
"new": (_module_main("chock.scaffold.new"), "Create a deterministic artifact skeleton"),
"compile": (_module_main("chock.compile.compiler"), "Low-level single-policy compile"),
"install-skills": (
_module_main("chock.scaffold.skills"),
"Install bundled authoring skills into agent skill dirs (write mode; check via CI)",
),
"registry": (_module_main("chock.registry.cli"), "Scan/list/resolve the artifact registry"),
"plugin": (
_module_main("chock.plugin.cli"),
"Package policies as installable plugins (plugin build [--format claude] [--check])",
),
"marketplace": (
_module_main("chock.plugin.marketplace"),
"Emit marketplace index files over a built plugin tree (marketplace build --dist <dir>)",
),
"gateway": (
_module_main("chock.gateway.__main__"),
"Run the MCP gateway proxy (gateway run --repo . -- <server command>)",
),
"review": (
_module_main("chock.review.cli"),
"Produce or check reviewer evidence (review emit | review verify <file>)",
),
"compliance": (
_module_main("chock.authoring.compliance"),
"Generate a compliance coverage report (compliance report --framework owasp_asi)",
),
}

ALIASES = {
"validate": (_module_main("chock.validation.engine"), "alias of: check --only validate"),
"verify": (_module_main("chock.lock"), "alias of: check --only verify"),
"eval": (_module_main("chock.eval.cli"), "alias of: check --only evals"),
"check-matrix": (_module_main("chock.authoring.matrix"), "alias of: check --only matrix"),
"recompile": (_module_fn("chock.toggles", "recompile_main"), "alias of: sync"),
"refresh": (_module_main("chock.index.cli"), "alias of: sync / check --only index"),
"install-hooks": (_module_main("chock.hooks.install"), "alias of: sync (hooks part)"),
"install-ci": (_module_main("chock.scaffold.install_ci"), "alias of: sync --ci"),
"policies": (_module_fn("chock.toggles", "policies_main"), "alias of: status"),
"gate-log": (_module_main("chock.gatelog"), "alias of: status --only log"),
}
def _load_command_groups() -> dict[str, dict[str, tuple]]:
"""The command table from data/commands.json: group -> name -> (lazy handler, help)."""
spec = json.loads((package_data_dir("chock", "data") / "commands.json").read_text(encoding="utf-8"))
groups: dict[str, dict[str, tuple]] = {}
for group, commands in spec.items():
groups[group] = {}
for name, entry in commands.items():
fn = _module_fn(entry["module"], entry["fn"]) if "fn" in entry else _module_main(entry["module"])
groups[group][name] = (fn, entry.get("help") or f"alias of: {entry['alias_of']}")
return groups


_GROUPS = _load_command_groups()
EVERYDAY = _GROUPS["everyday"]
AUTHORING = _GROUPS["authoring"]
ALIASES = _GROUPS["aliases"]

COMMANDS = {**EVERYDAY, **AUTHORING, **ALIASES}

Expand Down
17 changes: 2 additions & 15 deletions src/chock/compile/emitters/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,7 @@
from chock.compile.emitters.advisory import repo_root_from_output, template_message
from chock.emit import write_generated, write_generated_json
from chock.gate.build import build_gate_json, vendor_runner

STEP_TEMPLATE = """# Auto-generated by chock compile.
# Policy: {policy_id}
- name: chock-ci-gate ({policy_id})
run: |
PY=""
for c in python3 python py; do
if command -v "$c" >/dev/null 2>&1 && "$c" -c 'import tomllib' >/dev/null 2>&1; then PY="$c"; break; fi
done
[ -n "$PY" ] || {{ echo "gate: no python >= 3.11 (with tomllib) found on PATH" >&2; exit 2; }}
base="${{GITHUB_BASE_REF:?ci-gate needs GITHUB_BASE_REF -- run this step on the pull_request event}}"
"$PY" .chock/bin/gate.py run --gate {gate_path} --event ci --base "origin/$base" \\
--head-ref "${{GITHUB_HEAD_REF:-}}"
"""
from chock.resources import render_template


def emit(policy_dir: Path, output_dir: Path, manifest: dict[str, Any]) -> list[Path]:
Expand All @@ -43,6 +30,6 @@ def emit(policy_dir: Path, output_dir: Path, manifest: dict[str, Any]) -> list[P

gate_path = f".chock/compiled/{policy_id}/ci-gate/gate.json"
step = output_dir / "step.yaml"
write_generated(step, STEP_TEMPLATE.format(policy_id=policy_id, gate_path=gate_path))
write_generated(step, render_template("ci/step.yaml", {"__POLICY_ID__": policy_id, "__GATE_PATH__": gate_path}))
emitted.append(step)
return emitted
16 changes: 2 additions & 14 deletions src/chock/compile/emitters/git_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,7 @@
from chock.compile.emitters.advisory import repo_root_from_output, template_message
from chock.emit import write_generated, write_generated_json
from chock.gate.build import build_gate_json, vendor_runner

SHIM_TEMPLATE = """#!/usr/bin/env bash
# Auto-generated by chock compile. Declarative gate: {policy_id}
set -eu
repo_root="$(git rev-parse --show-toplevel)"
PY=""
for c in python3 python py; do
if command -v "$c" >/dev/null 2>&1 && "$c" -c 'import tomllib' >/dev/null 2>&1; then PY="$c"; break; fi
done
[ -n "$PY" ] || {{ echo "gate: no python >= 3.11 (with tomllib) found on PATH" >&2; exit 2; }}
exec "$PY" "$repo_root/.chock/bin/gate.py" run \\
--gate "$repo_root/.chock/compiled/{policy_id}/git-hook/gate.json" --event {event}
"""
from chock.resources import render_template


def _emit_shims(output_dir: Path, policy_id: str, events: list[str]) -> list[Path]:
Expand All @@ -35,7 +23,7 @@ def _emit_shims(output_dir: Path, policy_id: str, events: list[str]) -> list[Pat
else:
continue
shim = output_dir / script_name
write_generated(shim, SHIM_TEMPLATE.format(policy_id=policy_id, event=event_arg))
write_generated(shim, render_template("git-hook/shim.sh", {"__POLICY_ID__": policy_id, "__EVENT__": event_arg}))
try:
shim.chmod(0o755)
except OSError:
Expand Down
17 changes: 3 additions & 14 deletions src/chock/compile/emitters/in_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from chock import vendors
from chock.emit import write_generated_json
from chock.resources import render_template_line

GUARD_SCRIPTS = {
"block-destructive-commands": "block-destructive.sh",
Expand Down Expand Up @@ -142,23 +143,11 @@ def emit_pre_tool_use(policy_dir: Path, output_dir: Path, manifest: dict[str, An


def _bash_command(adapter: str, guard: str) -> str:
return (
'repo="$(git rev-parse --show-toplevel)"; '
'PY="$(command -v python3 || command -v python || command -v py)"; '
'[ -n "$PY" ] || { echo "chock: no python interpreter found" >&2; exit 1; }; '
f'exec "$PY" "$repo/{adapter}" --guard "$repo/{guard}"'
)
return render_template_line("in-agent/guard-command.sh", {"__ADAPTER__": adapter, "__GUARD__": guard})


def _powershell_command(adapter: str, guard: str) -> str:
return (
"$repo = (git rev-parse --show-toplevel); "
"$py = (Get-Command python3, python, py -ErrorAction SilentlyContinue | "
"Where-Object { $_.Source -and $_.Source -notlike '*WindowsApps*' } | "
"Select-Object -First 1).Source; "
"if (-not $py) { [Console]::Error.WriteLine('chock: no python interpreter found'); exit 1 }; "
f'$input | & $py "$repo/{adapter}" --guard "$repo/{guard}"; exit $LASTEXITCODE'
)
return render_template_line("in-agent/guard-command.ps1", {"__ADAPTER__": adapter, "__GUARD__": guard})


def build_entry(policy_dir: Path, manifest: dict[str, Any]) -> dict[str, Any] | None:
Expand Down
65 changes: 65 additions & 0 deletions src/chock/data/commands.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"everyday": {
"init": {"module": "chock.scaffold.init", "help": "Scaffold a consumer repo (wiring only -- no policies)"},
"add": {"module": "chock.scaffold.add", "help": "Install a policy or skill from a catalog and compile it"},
"remove": {"module": "chock.scaffold.remove", "help": "Remove an installed policy and resync"},
"sync": {
"module": "chock.lifecycle",
"fn": "sync_main",
"help": "Recompile + rewire so the repo matches its policies (--ci/--skills for extras)"
},
"check": {
"module": "chock.lifecycle",
"fn": "check_main",
"help": "Run every truth check: validate, verify, evals, matrix (--only to narrow)"
},
"status": {
"module": "chock.lifecycle",
"fn": "status_main",
"help": "Policy states and coverage (--only registry,log for more)"
},
"enable": {"module": "chock.toggles", "fn": "enable_main", "help": "Enable a policy by id"},
"disable": {"module": "chock.toggles", "fn": "disable_main", "help": "Disable a policy by id"}
},
"authoring": {
"new": {"module": "chock.scaffold.new", "help": "Create a deterministic artifact skeleton"},
"compile": {"module": "chock.compile.compiler", "help": "Low-level single-policy compile"},
"install-skills": {
"module": "chock.scaffold.skills",
"help": "Install bundled authoring skills into agent skill dirs (write mode; check via CI)"
},
"registry": {"module": "chock.registry.cli", "help": "Scan/list/resolve the artifact registry"},
"plugin": {
"module": "chock.plugin.cli",
"help": "Package policies as installable plugins (plugin build [--format claude] [--check])"
},
"marketplace": {
"module": "chock.plugin.marketplace",
"help": "Emit marketplace index files over a built plugin tree (marketplace build --dist <dir>)"
},
"gateway": {
"module": "chock.gateway.__main__",
"help": "Run the MCP gateway proxy (gateway run --repo . -- <server command>)"
},
"review": {
"module": "chock.review.cli",
"help": "Produce or check reviewer evidence (review emit | review verify <file>)"
},
"compliance": {
"module": "chock.authoring.compliance",
"help": "Generate a compliance coverage report (compliance report --framework owasp_asi)"
}
},
"aliases": {
"validate": {"module": "chock.validation.engine", "alias_of": "check --only validate"},
"verify": {"module": "chock.lock", "alias_of": "check --only verify"},
"eval": {"module": "chock.eval.cli", "alias_of": "check --only evals"},
"check-matrix": {"module": "chock.authoring.matrix", "alias_of": "check --only matrix"},
"recompile": {"module": "chock.toggles", "fn": "recompile_main", "alias_of": "sync"},
"refresh": {"module": "chock.index.cli", "alias_of": "sync / check --only index"},
"install-hooks": {"module": "chock.hooks.install", "alias_of": "sync (hooks part)"},
"install-ci": {"module": "chock.scaffold.install_ci", "alias_of": "sync --ci"},
"policies": {"module": "chock.toggles", "fn": "policies_main", "alias_of": "status"},
"gate-log": {"module": "chock.gatelog", "alias_of": "status --only log"}
}
}
12 changes: 12 additions & 0 deletions src/chock/data/templates/ci/step.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Auto-generated by chock compile.
# Policy: __POLICY_ID__
- name: chock-ci-gate (__POLICY_ID__)
run: |
PY=""
for c in python3 python py; do
if command -v "$c" >/dev/null 2>&1 && "$c" -c 'import tomllib' >/dev/null 2>&1; then PY="$c"; break; fi
done
[ -n "$PY" ] || { echo "gate: no python >= 3.11 (with tomllib) found on PATH" >&2; exit 2; }
base="${GITHUB_BASE_REF:?ci-gate needs GITHUB_BASE_REF -- run this step on the pull_request event}"
"$PY" .chock/bin/gate.py run --gate __GATE_PATH__ --event ci --base "origin/$base" \
--head-ref "${GITHUB_HEAD_REF:-}"
11 changes: 11 additions & 0 deletions src/chock/data/templates/git-hook/shim.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Auto-generated by chock compile. Declarative gate: __POLICY_ID__
set -eu
repo_root="$(git rev-parse --show-toplevel)"
PY=""
for c in python3 python py; do
if command -v "$c" >/dev/null 2>&1 && "$c" -c 'import tomllib' >/dev/null 2>&1; then PY="$c"; break; fi
done
[ -n "$PY" ] || { echo "gate: no python >= 3.11 (with tomllib) found on PATH" >&2; exit 2; }
exec "$PY" "$repo_root/.chock/bin/gate.py" run \
--gate "$repo_root/.chock/compiled/__POLICY_ID__/git-hook/gate.json" --event __EVENT__
Loading