Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b6c14b4
docs: remove every em dash from tracked text
Exploitacious Oct 19, 2026
0e5f807
ci: add the gate workflow
Exploitacious Oct 19, 2026
a899975
docs(skills): add third-party notices
Exploitacious Oct 19, 2026
754ed60
docs: match the README, deployment and contributing docs to the code
Exploitacious Oct 19, 2026
394f332
docs(changelog): one 2026-10-19 release entry
Exploitacious Oct 19, 2026
4b271f7
ci(gate): check ES modules on any node; probe bwrap like sandbox-run
Exploitacious Oct 19, 2026
96564e8
docs(contributing): say when CI runs for a fork
Exploitacious Oct 19, 2026
521789a
fix(workflows): close the context-coherence prompt in harness-audit.js
Exploitacious Oct 19, 2026
283e526
ci(gate): check workflow scripts as the async bodies they run as
Exploitacious Oct 19, 2026
d3d3111
docs: say how to check a workflow script; cite the push gate by step
Exploitacious Oct 19, 2026
af19a07
ci(gate): upgrade an older git before the suites
Exploitacious Oct 19, 2026
3e0e6da
docs: state the git 2.45 floor for branch cleanup
Exploitacious Oct 19, 2026
3e7f06a
ci(gate): run on ubuntu-26.04, where branch-gc can run
Exploitacious Oct 19, 2026
c465ce2
docs: add GNU coreutils 9.5 to the branch cleanup requirements
Exploitacious Oct 19, 2026
ff12047
docs(contributing): exit non-zero when a suite fails
Exploitacious Oct 19, 2026
270930f
docs(deployment): keep the throwaway-HOME recipe from pushing
Exploitacious Oct 19, 2026
b341a8c
fix(ci): check JavaScript with a parse a body cannot escape
Exploitacious Oct 19, 2026
bedaf47
docs(bin): keep one copy of the local suite command
Exploitacious Oct 19, 2026
60a5d38
docs: name the suite floor and where OPS_DIR checks fall short
Exploitacious Oct 19, 2026
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
27 changes: 12 additions & 15 deletions .claude-config/bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,21 +288,18 @@ Runnable suites for the scripts above and for deploy's rc and core-link
wiring (`deploy-rc-wiring-test.sh`, which extracts the real functions from
`deploy.sh` and runs them against mktemp fixtures with a `sudo` shim, never a
live rc). Each exits 0 when every case passes. A suite that starts with the
sandbox guard exits 97 outside `sandbox-run`, so run every suite through it
from the checkout root (the sandbox's own tests start `sandbox-run` and run
bare):

```bash
cd ~/OPS
for t in .claude-config/bin/tests/*-test.sh .claude-config/bin/tests/*-test.py; do
case "$t" in *.py) run=(python3 -B) ;; *) run=(bash) ;; esac
case "$t" in */sandbox-run-test.sh|*/sandbox-run-fd-test.py) "${run[@]}" "$t" || echo "FAILED: $t"; continue ;; esac
.claude-config/bin/sandbox-run --no-bind --no-net --ro-bind "$PWD" -- "${run[@]}" "$t" || echo "FAILED: $t"
done
```

The older `*-selftest.sh` scripts beside their subjects (`verify-ops`,
`model-probe`, `claude-window-ping`, and the hooks) work the same way.
sandbox guard exits 97 outside `sandbox-run`, so every suite runs through it
from the checkout root, except the sandbox's own tests, which start
`sandbox-run` themselves. The command that runs them all, with the
`*-selftest` scripts beside their subjects and the PowerShell suites, and
exits non-zero if any fails, is step 2 of "Before you open the PR" in
`CONTRIBUTING.md`; CI runs the same loops (`.github/workflows/gate.yml`).
One file, one command: no second copy here to drift.

`tests/ci-gate-test.py` runs the gate's JavaScript check over every tracked
`.js` and a set of fixtures. Under `sandbox-run` it skips when `node` lives in
your HOME (nvm, fnm), which the sandbox hides, and says so; run it bare then.
In GitHub Actions a missing `node` fails it instead.

## `grabit`: file transfer over Tailscale

Expand Down
8 changes: 4 additions & 4 deletions .claude-config/bin/claude-window-ping-selftest.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env bash
# claude-window-ping-selftest.sh — prove the window-ping pipe handles every
# claude-window-ping-selftest.sh: prove the window-ping pipe handles every
# path it can hit unattended: good profiles, config typos, missing binary,
# nonzero/timeout rcs, empty config, overlap locking, and tmp hygiene. Runs
# entirely against a MOCK claude binary and a scratch state dir (the
# CC_WINDOW_PING_BIN / CC_WINDOW_PING_STATE_DIR test hooks) — no real API
# CC_WINDOW_PING_BIN / CC_WINDOW_PING_STATE_DIR test hooks), no real API
# usage, no touch of the real ~/.local/state/window-ping.
#
# Exit 0 = every case behaves; 1 = defect.
Expand Down Expand Up @@ -71,7 +71,7 @@ CC_WINDOW_PING_MODEL="custommodel" CC_WINDOW_PING_PROFILES="m=$WORK/cfg-m" bash
chk "CC_WINDOW_PING_MODEL passed through" "$(grep -q -- "--model custommodel" "$MOCK_RECORD"; echo $?)"

# 6. Missing binary: one rc=127 row PER configured profile, complete atomic file.
# PATH mimics cron's minimal /usr/bin:/bin — coreutils work, claude is absent.
# PATH mimics cron's minimal /usr/bin:/bin (coreutils work, claude is absent).
reset
CC_WINDOW_PING_BIN="$WORK/does-not-exist" PATH="/usr/bin:/bin" CC_WINDOW_PING_PROFILES="a=$WORK/x b=$WORK/y" bash "$SCRIPT"
chk "missing binary -> 2 rc=127 rows" "$([ "$(awk -F'\t' '$2=="127"' "$STATE/last-status.tsv" | wc -l)" = 2 ]; echo $?)"
Expand Down Expand Up @@ -108,6 +108,6 @@ if [ "$fails" -eq 0 ]; then
echo "claude-window-ping-selftest: PIPE BEHAVES"
exit 0
else
echo "claude-window-ping-selftest: $fails case(s) failing — do not trust the cron pipe"
echo "claude-window-ping-selftest: $fails case(s) failing: do not trust the cron pipe"
exit 1
fi
6 changes: 3 additions & 3 deletions .claude-config/bin/grabit
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# grabit — move files between this (often headless) OPS box and the machine
# grabit: move files between this (often headless) OPS box and the machine
# you're driving it from, over Tailscale. No SFTP, no cloud; tailnet-encrypted,
# never public (uses `tailscale serve`, not `funnel`).
#
Expand All @@ -16,7 +16,7 @@
# grabit -h | --help
#
# One-time per box (already done on this one): sudo tailscale set --operator=$USER
# so transfers need no sudo. Only ever stages files you name — never exposes the
# so transfers need no sudo. Only ever stages files you name and never exposes the
# directory they live in, so a neighbouring .env can't leak.
set -euo pipefail

Expand Down Expand Up @@ -73,7 +73,7 @@ serve(){
rm -rf "$SERVE_DIR"; mkdir -p "$SERVE_DIR"
local p; for p in "$@"; do [ -e "$p" ] || die "no such path: $p"; cp -r "$p" "$SERVE_DIR/"; done
# Serving a path is privileged even for an operator; needs (passwordless) sudo.
# Taildrop (the default push) does NOT need this — prefer it where sudo is absent.
# Taildrop (the default push) does NOT need this, so prefer it where sudo is absent.
sudo tailscale serve --bg "$SERVE_DIR" >/dev/null 2>&1 \
|| die "tailscale serve failed (needs sudo + tailnet HTTPS enabled; just use a plain 'grabit FILE' push instead)"
local url; url="https://$(self_dns)/"
Expand Down
6 changes: 3 additions & 3 deletions .claude-config/bin/secrets-scan.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# secrets-scan.sh — literal-credential detector for memory/lessons surfaces.
# secrets-scan.sh: literal-credential detector for memory/lessons surfaces.
#
# Usage:
# secrets-scan.sh FILE|DIR [...] scan files/dirs (md files), print hits
Expand All @@ -12,8 +12,8 @@
# <path>", "1Password entry X"), never literal values. This scanner backs
# secrets-guard.sh (write-time), verify-ops.sh (repo gate), and any
# pre-push scan. Two pattern tiers:
# HARD — unambiguous credential shapes; always flagged.
# SOFT — assignment-style (password:, api_key=); skipped when the line
# HARD: unambiguous credential shapes; always flagged.
# SOFT: assignment-style (password:, api_key=); skipped when the line
# already reads as a pointer (sops/vault/1password/.env/redacted),
# because pointers are exactly what we WANT written.
set -uo pipefail
Expand Down
275 changes: 275 additions & 0 deletions .claude-config/bin/tests/ci-gate-test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
#!/usr/bin/env python3
"""Exercise the CI JavaScript check against source, without running that source."""

import os
import shutil
import subprocess
import tempfile
import textwrap
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parents[3]
GATE = ROOT / ".github/workflows/gate.yml"


def javascript_step():
"""Read this literal run block without adding a YAML package dependency."""
lines = GATE.read_text().splitlines()
start = lines.index(" - name: Check every JavaScript script")
start = lines.index(" run: |", start) + 1
body = []
for line in lines[start:]:
if line and not line.startswith(" "):
break
body.append(line[10:])
return "\n".join(body) + "\n"


class JavaScriptGateTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
# sandbox-run gives the suite a fresh HOME, so a node installed under
# the real one (nvm, fnm) is not on PATH there. Locally that is a skip
# with the way out; in GitHub Actions it is a failure, so the gate can
# never pass this suite without running it.
if shutil.which("node"):
return
if os.environ.get("GITHUB_ACTIONS") == "true":
raise RuntimeError("node is not on PATH in CI: the JavaScript check cannot be tested")
raise unittest.SkipTest(
"node is not on PATH here (under sandbox-run, a node installed in "
"your HOME is hidden); run python3 -B "
".claude-config/bin/tests/ci-gate-test.py outside the sandbox"
)

def setUp(self):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.root = Path(tmp.name)
self.runner = self.root / "runner"
self.runner.mkdir()

def check(self, files):
for name, source in files.items():
path = self.root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(textwrap.dedent(source))
(self.runner / "node.lst").write_text("".join(name + "\n" for name in files))
return subprocess.run(
[
"bash",
"--noprofile",
"--norc",
"-eo",
"pipefail",
"-c",
javascript_step(),
],
cwd=self.root,
env={**os.environ, "RUNNER_TEMP": str(self.runner)},
capture_output=True,
text=True,
timeout=30,
check=False,
)

def assert_result(self, result, valid):
self.assertEqual(result.returncode == 0, valid, result.stdout + result.stderr)

def test_every_tracked_javascript_file_passes(self):
# The step over the repository's own .js files, so a checker change that
# rejects a shipped script fails here, not first on the hosted runner.
# This covers the skill-bundled workflow SKILLS/memory-prune/
# audit_workflow.js, which a path rule once sent to the module check.
listed = subprocess.run(
["git", "-C", str(ROOT), "ls-files", "-z", "*.js"],
capture_output=True,
text=True,
check=False,
)
if listed.returncode == 0:
names = [name for name in listed.stdout.split("\0") if name]
else:
names = [
str(path.relative_to(ROOT))
for path in sorted(ROOT.rglob("*.js"))
if ".git" not in path.relative_to(ROOT).parts
]
files = {name: (ROOT / name).read_text() for name in names}
self.assertIn("SKILLS/memory-prune/audit_workflow.js", files)
self.assert_result(self.check(files), True)

def test_workflow_outside_the_workflows_directory(self):
self.assert_result(
self.check(
{
"SKILLS/demo/audit_workflow.js": """
// A skill-bundled workflow script.
export const meta = { name: 'demo' };
const value = await Promise.resolve(1);
return value;
"""
}
),
True,
)

def test_module_exporting_meta_later_is_an_ordinary_module(self):
# Only a leading `export const meta =` makes a workflow script.
self.assert_result(
self.check(
{
"ordinary.js": """
import fs from 'node:fs';
export const meta = { fs };
"""
}
),
True,
)

def test_valid_workflow_await_and_return(self):
self.assert_result(
self.check(
{
".claude-config/workflows/valid.js": """
export const meta = {};
const value = await Promise.resolve(1);
return value;
"""
}
),
True,
)

def test_workflow_shebang_comments_and_indented_metadata(self):
source = (
"#!/usr/bin/env node\n// Heading\n/* Multiple\nlines */\n"
" export const meta = {};\nreturn await Promise.resolve(1);\n"
)
self.assert_result(
self.check({".claude-config/workflows/valid.js": source}), True
)

def test_nested_metadata_export_remains_invalid(self):
source = "function nested() {\nexport const meta = {};\n}\n"
self.assert_result(
self.check({".claude-config/workflows/broken.js": source}), False
)

def test_workflow_cannot_escape_a_textual_wrapper(self):
# A wrapper around this invalid body becomes two valid functions.
self.assert_result(
self.check(
{
".claude-config/workflows/broken.js": """
export const meta = {};
})
;(async () => {
return 1;
"""
}
),
False,
)

def test_malformed_export_is_not_stripped(self):
self.assert_result(
self.check(
{
".claude-config/workflows/broken.js": """
export const meta = {};
export if (true) {}
"""
}
),
False,
)

def test_valid_commonjs_await_identifier(self):
self.assert_result(
self.check({"ordinary.js": "var await = 1;\nconsole.log(await);\n"}), True
)

def test_valid_es_module(self):
self.assert_result(
self.check(
{
"ordinary.js": """
import fs from 'node:fs';
export default await Promise.resolve(fs);
"""
}
),
True,
)

def test_broken_es_module_fails_before_a_valid_file(self):
self.assert_result(
self.check(
{
"broken.js": "export const value = {\n",
"valid.js": "const value = 1;\n",
}
),
False,
)

def test_broken_module_with_import(self):
self.assert_result(
self.check(
{
"ordinary.js": """
import fs from 'node:fs';
export const value = {
"""
}
),
False,
)

def test_static_import_cannot_hide_a_broken_workflow(self):
self.assert_result(
self.check(
{
".claude-config/workflows/broken.js": """
export const meta = {};
import fs from 'node:fs';
const value = {
"""
}
),
False,
)

def test_unclosed_workflow_body(self):
self.assert_result(
self.check(
{
".claude-config/workflows/broken.js": """
export const meta = {};
if (true) {
"""
}
),
False,
)

def test_source_is_not_executed(self):
source = "require('node:fs').writeFileSync('executed', 'bad');\n"
self.assert_result(
self.check(
{
"ordinary.js": source,
".claude-config/workflows/valid.js": "export const meta = {};\n"
+ source,
}
),
True,
)
self.assertFalse((self.root / "executed").exists())


if __name__ == "__main__":
unittest.main()
Loading