@sriinnu/omit 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clinerules/omit.md +1 -1
- package/.cursor/rules/omit.mdc +1 -1
- package/.github/copilot-instructions.md +15 -0
- package/.github/workflows/publish.yml +69 -0
- package/.github/workflows/test.yml +40 -0
- package/.windsurf/rules/omit.md +1 -1
- package/AGENTS.md +11 -2
- package/README.md +72 -15
- package/action.yml +18 -1
- package/bench/run.mjs +25 -4
- package/bin/omit.mjs +518 -58
- package/hooks/command-sentinel.mjs +39 -11
- package/hooks/dep-sentinel.mjs +184 -30
- package/hooks/final-draft-gate.mjs +231 -37
- package/hooks/hazard-sentinel.mjs +157 -39
- package/hooks/hooks.json +18 -1
- package/hooks/leak-sentinel.mjs +55 -0
- package/hooks/lint-sentinel.mjs +34 -11
- package/lib/danger.mjs +9 -1
- package/lib/deps.mjs +341 -45
- package/lib/exec.mjs +18 -0
- package/lib/git.mjs +107 -0
- package/lib/hazards.mjs +29 -4
- package/lib/leaks.mjs +313 -0
- package/lib/lint.mjs +85 -5
- package/lib/receipts.mjs +356 -0
- package/package.json +5 -1
- package/skills/omit/SKILL.md +25 -13
package/.clinerules/omit.md
CHANGED
|
@@ -4,7 +4,7 @@ Great software is edited, not written. You are the editor, not just the author.
|
|
|
4
4
|
|
|
5
5
|
**Before code: the Seven Omissions** (stop at the first that holds): (1) Omit the feature: speculative need = needless until proven needed; write nothing. (2) Omit the new code: the codebase already does this; reuse it. (3) Omit the custom: stdlib covers it. (4) Omit the script: the platform does it natively. (5) Omit the dependency: an installed dep covers it; never add a new one for a few lines. (6) Omit the ceremony: one plain line beats a pattern. (7) What survives editing, ships.
|
|
6
6
|
|
|
7
|
-
**Fact-Check**: no omission counts until verified now
|
|
7
|
+
**Fact-Check**: no omission counts until verified now, and verified means checkable by something other than you. Record each in `.omit/receipts.jsonl`: reuse → `{"claim":"reuse","rung":2,"file":"src/x.ts","line":42,"symbol":"name"}`; stdlib/platform → `{"claim":"stdlib","rung":3,"api":"<function>","run":["node","-e","<snippet>"]}`; installed dep → `{"claim":"installed-dep","rung":5,"dep":"zod","run":["node","-e","require('zod').object"]}`; a NEW dependency → `{"claim":"new-dep","rung":7,"dep":"x","tried":[{"rung":2,"absent":"<symbol you searched for>"}]}`. `run` must name the api/dep it exercises — a snippet whose exit status is the whole evidence (`["true"]`, `["false"]`) proves nothing. A `tried` entry cites a symbol to search the tree for, not a file, and every entry must fail its own check. `run` is argv, never a shell string. `omit verify` re-checks them all; a claim that does not survive is not a claim.
|
|
8
8
|
|
|
9
9
|
**Final Draft**: after tests go green, one ruthless edit of your own diff; report the net (±lines, files, new deps: target 0). Done = final draft, not green tests.
|
|
10
10
|
|
package/.cursor/rules/omit.mdc
CHANGED
|
@@ -14,7 +14,7 @@ BEFORE code: the Seven Omissions (stop at the first that holds):
|
|
|
14
14
|
6. Omit the ceremony: one plain line beats a pattern.
|
|
15
15
|
7. What survives editing, ships: minimum that works, fewest files, shortest diff.
|
|
16
16
|
|
|
17
|
-
FACT-CHECK: no omission counts until verified now
|
|
17
|
+
FACT-CHECK: no omission counts until verified now, and verified means checkable by something other than you. Record each in `.omit/receipts.jsonl`: reuse → `{"claim":"reuse","rung":2,"file":"src/x.ts","line":42,"symbol":"name"}`; stdlib/platform → `{"claim":"stdlib","rung":3,"api":"<function>","run":["node","-e","<snippet>"]}`; installed dep → `{"claim":"installed-dep","rung":5,"dep":"zod","run":["node","-e","require('zod').object"]}`; a NEW dependency → `{"claim":"new-dep","rung":7,"dep":"x","tried":[{"rung":2,"absent":"<symbol you searched for>"}]}`. `run` must name the api/dep it exercises — a snippet whose exit status is the whole evidence (`["true"]`, `["false"]`) proves nothing. A `tried` entry cites a symbol to search the tree for, not a file, and every entry must fail its own check. `run` is argv, never a shell string. `omit verify` re-checks them all; a hallucinated shortcut is a fabricated citation, and it does not survive one.
|
|
18
18
|
|
|
19
19
|
AFTER green: the Final Draft: one ruthless edit of your own diff (dead branches, unused params/imports, speculative options, restating comments, single-caller indirection). Report the net: ±lines, files, new deps (target 0). Done = final draft, not green tests.
|
|
20
20
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# omit: Omit needless code.
|
|
2
|
+
|
|
3
|
+
Great software is edited, not written. You are the editor, not just the author. Draft less, cite everything, cut last.
|
|
4
|
+
|
|
5
|
+
**Before code, the Seven Omissions** (stop at the first that holds): (1) Omit the feature: speculative need is needless until proven needed; write nothing. (2) Omit the new code: the codebase already does this; reuse it. (3) Omit the custom: stdlib covers it. (4) Omit the script: the platform does it natively. (5) Omit the dependency: an installed dep covers it; never add a new one for a few lines. (6) Omit the ceremony: one plain line beats a pattern. (7) What survives editing, ships.
|
|
6
|
+
|
|
7
|
+
**Fact-Check**: no omission counts until verified now, and verified means checkable by something other than you. Record each in `.omit/receipts.jsonl`: reuse → `{"claim":"reuse","rung":2,"file":"src/x.ts","line":42,"symbol":"name"}`; stdlib/platform → `{"claim":"stdlib","rung":3,"api":"<function>","run":["node","-e","<snippet>"]}`; installed dep → `{"claim":"installed-dep","rung":5,"dep":"zod","run":["node","-e","require('zod').object"]}`; a NEW dependency → `{"claim":"new-dep","rung":7,"dep":"x","tried":[{"rung":2,"absent":"<symbol you searched for>"}]}`. `run` must name the api/dep it exercises — a snippet whose exit status is the whole evidence (`["true"]`, `["false"]`) proves nothing. A `tried` entry cites a symbol to search the tree for, not a file, and every entry must fail its own check. `run` is argv, never a shell string. `omit verify` re-checks them all; a claim that does not survive is not a claim.
|
|
8
|
+
|
|
9
|
+
**Final Draft**: after tests go green, one ruthless edit of your own diff; report the net (plus/minus lines, files, new deps, target 0). Done means final draft, not green tests.
|
|
10
|
+
|
|
11
|
+
**Never cut load-bearing lines**: validation at trust boundaries, error handling preventing data loss, security, accessibility, concurrency correctness, explicit requests. Announce them (`load-bearing: <reason>`), never skip them.
|
|
12
|
+
|
|
13
|
+
**Footnotes**: record deliberate omissions: `// omitted: <what>; <when to add it back>`.
|
|
14
|
+
|
|
15
|
+
**Voice**: root causes, not symptoms; boring beats clever; deletion is the strongest edit.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: "20"
|
|
21
|
+
registry-url: "https://registry.npmjs.org"
|
|
22
|
+
|
|
23
|
+
- name: Verify tag matches package.json version
|
|
24
|
+
run: |
|
|
25
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
26
|
+
PKG_VERSION="$(node -p "require('./package.json').version")"
|
|
27
|
+
PKG_NAME="$(node -p "require('./package.json').name")"
|
|
28
|
+
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
|
29
|
+
echo "Tag v$TAG_VERSION does not match package.json version $PKG_VERSION"
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
echo "PKG_NAME=$PKG_NAME" >> "$GITHUB_ENV"
|
|
33
|
+
echo "PKG_VERSION=$PKG_VERSION" >> "$GITHUB_ENV"
|
|
34
|
+
|
|
35
|
+
- name: Skip if already published
|
|
36
|
+
id: check
|
|
37
|
+
run: |
|
|
38
|
+
if npm view "$PKG_NAME@$PKG_VERSION" version >/dev/null 2>&1; then
|
|
39
|
+
echo "Already on the registry, nothing to do."
|
|
40
|
+
echo "already_published=true" >> "$GITHUB_OUTPUT"
|
|
41
|
+
else
|
|
42
|
+
echo "already_published=false" >> "$GITHUB_OUTPUT"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
- name: Publish
|
|
46
|
+
if: steps.check.outputs.already_published == 'false'
|
|
47
|
+
env:
|
|
48
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
49
|
+
run: |
|
|
50
|
+
if npm publish --access public --provenance; then
|
|
51
|
+
echo "Publish succeeded."
|
|
52
|
+
exit 0
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# npm's registry can 404 a fresh scoped package's first publish for a
|
|
56
|
+
# few seconds after the write actually lands (read-replica lag), which
|
|
57
|
+
# makes npm publish report failure even though the version is live.
|
|
58
|
+
# Poll before trusting the error.
|
|
59
|
+
echo "npm publish reported failure; checking whether it landed anyway..."
|
|
60
|
+
for i in $(seq 1 10); do
|
|
61
|
+
sleep 6
|
|
62
|
+
if npm view "$PKG_NAME@$PKG_VERSION" version >/dev/null 2>&1; then
|
|
63
|
+
echo "Version $PKG_VERSION is live on the registry — treating as success."
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
done
|
|
67
|
+
|
|
68
|
+
echo "Version never appeared on the registry after retries. Publish failed."
|
|
69
|
+
exit 1
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- uses: actions/setup-node@v4
|
|
15
|
+
with:
|
|
16
|
+
node-version: "20"
|
|
17
|
+
|
|
18
|
+
- run: npm test
|
|
19
|
+
|
|
20
|
+
# action.yml is read by GitHub's action loader, not by anything in this
|
|
21
|
+
# repo, so an invalid one fails silently: the Action simply never runs and
|
|
22
|
+
# no check here notices. It shipped invalid once already — an unquoted
|
|
23
|
+
# ": " inside the description — which no test, and no reviewer, caught.
|
|
24
|
+
- name: action.yml parses
|
|
25
|
+
run: |
|
|
26
|
+
python3 - <<'PY'
|
|
27
|
+
import sys
|
|
28
|
+
try:
|
|
29
|
+
import yaml
|
|
30
|
+
except ImportError:
|
|
31
|
+
# Announced rather than silent: on a runner without PyYAML this
|
|
32
|
+
# guard is advisory, and saying so beats implying it ran.
|
|
33
|
+
print("PyYAML unavailable on this runner — action.yml was NOT validated")
|
|
34
|
+
sys.exit(0)
|
|
35
|
+
doc = yaml.safe_load(open("action.yml"))
|
|
36
|
+
for key in ("name", "description", "runs"):
|
|
37
|
+
assert key in doc, f"action.yml is missing a required key: {key}"
|
|
38
|
+
assert doc["runs"].get("using") == "composite", "runs.using must be composite"
|
|
39
|
+
print("action.yml parses and carries its required keys")
|
|
40
|
+
PY
|
package/.windsurf/rules/omit.md
CHANGED
|
@@ -4,7 +4,7 @@ Great software is edited, not written. You are the editor, not just the author.
|
|
|
4
4
|
|
|
5
5
|
**Before code: the Seven Omissions** (stop at the first that holds): (1) Omit the feature: speculative need = needless until proven needed; write nothing. (2) Omit the new code: the codebase already does this; reuse it. (3) Omit the custom: stdlib covers it. (4) Omit the script: the platform does it natively. (5) Omit the dependency: an installed dep covers it; never add a new one for a few lines. (6) Omit the ceremony: one plain line beats a pattern. (7) What survives editing, ships.
|
|
6
6
|
|
|
7
|
-
**Fact-Check**: no omission counts until verified now
|
|
7
|
+
**Fact-Check**: no omission counts until verified now, and verified means checkable by something other than you. Record each in `.omit/receipts.jsonl`: reuse → `{"claim":"reuse","rung":2,"file":"src/x.ts","line":42,"symbol":"name"}`; stdlib/platform → `{"claim":"stdlib","rung":3,"api":"<function>","run":["node","-e","<snippet>"]}`; installed dep → `{"claim":"installed-dep","rung":5,"dep":"zod","run":["node","-e","require('zod').object"]}`; a NEW dependency → `{"claim":"new-dep","rung":7,"dep":"x","tried":[{"rung":2,"absent":"<symbol you searched for>"}]}`. `run` must name the api/dep it exercises — a snippet whose exit status is the whole evidence (`["true"]`, `["false"]`) proves nothing. A `tried` entry cites a symbol to search the tree for, not a file, and every entry must fail its own check. `run` is argv, never a shell string. `omit verify` re-checks them all; a claim that does not survive is not a claim.
|
|
8
8
|
|
|
9
9
|
**Final Draft**: after tests go green, one ruthless edit of your own diff; report the net (±lines, files, new deps: target 0). Done = final draft, not green tests.
|
|
10
10
|
|
package/AGENTS.md
CHANGED
|
@@ -20,11 +20,20 @@ Try to omit, in order: stop at the first omission that holds:
|
|
|
20
20
|
|
|
21
21
|
## The Fact-Check
|
|
22
22
|
|
|
23
|
-
No omission counts until verified in this session
|
|
23
|
+
No omission counts until verified in this session — and verified means checkable by something other than you. Record each one in `.omit/receipts.jsonl`, one JSON object per line, naming its own evidence:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
{"claim":"reuse","rung":2,"file":"src/x.ts","line":42,"symbol":"parseRange"}
|
|
27
|
+
{"claim":"stdlib","rung":3,"api":"crypto.randomUUID","run":["node","-e","crypto.randomUUID()"]}
|
|
28
|
+
{"claim":"installed-dep","rung":5,"dep":"zod","run":["node","-e","require('zod').object"]}
|
|
29
|
+
{"claim":"new-dep","rung":7,"dep":"left-pad","tried":[{"rung":2,"absent":"padTo"}]}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Evidence is bound to the claim: a snippet's exit status proves nothing on its own, so `run` must also name what it exercises and the argv must mention it. `run` is argv, never a shell string. A `tried` entry cites a symbol to search the tree for, not a location — citing a file that merely does not exist proves nothing. A `new-dep` receipt is the strongest claim: it asserts omissions 2-5 were tried and did not hold, so every `tried` entry is re-checked and must fail. `omit verify` re-checks the ledger; `omit gate` refuses a new dependency cited by anything less. No receipt, no omission.
|
|
24
33
|
|
|
25
34
|
## After code works: the Final Draft
|
|
26
35
|
|
|
27
|
-
One ruthless edit of your own diff: dead branches, unused params/imports, speculative options, comments restating code, single-caller indirection.
|
|
36
|
+
One ruthless edit of your own diff: dead branches, unused params/imports, speculative options, comments restating code, single-caller indirection. Write the net report to `.omit/final-draft.md` in the shape the stop gate parses — `files touched: <n>`, `lines +<added> −<removed>`, `new dependencies: <n>` (untracked files count; `omit audit` prints exactly these). Done = final draft, not green tests.
|
|
28
37
|
|
|
29
38
|
## Load-Bearing Lines: never cut
|
|
30
39
|
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ AI agents are prolific authors and terrible editors. Left alone they add abstrac
|
|
|
24
24
|
| Part | What it does |
|
|
25
25
|
|---|---|
|
|
26
26
|
| **The Seven Omissions** | Before writing anything, try seven ways to *not* write it: omit the feature, the new code, the custom, the script, the dependency, the ceremony: stopping at the first omission that holds. What survives editing, ships. |
|
|
27
|
-
| **The Fact-Check** | No omission counts without a citation
|
|
27
|
+
| **The Fact-Check** | No omission counts without a citation **the tool can re-check itself**: `file:line:symbol` for "the codebase has this", an executable snippet for "stdlib covers it", a declared dep + snippet for "the dep handles it". A citation only its author can read is a self-report, and a hallucinated shortcut is a fabricated citation. |
|
|
28
28
|
| **The Final Draft** | Working code is a first draft. After tests go green, one ruthless edit of the agent's own diff: then a net report: files, ±lines, new deps (target: 0). Done means final draft, not green tests. |
|
|
29
29
|
| **Load-Bearing Lines** | Editing cuts fat, not walls. Validation, error handling, security, accessibility, concurrency correctness, and explicit requests are never cut: and adding them is announced, never smuggled or skipped. |
|
|
30
30
|
|
|
@@ -40,27 +40,78 @@ Every other skill in this genre is words the agent can ignore under context pres
|
|
|
40
40
|
|
|
41
41
|
| Mechanism | What it does |
|
|
42
42
|
|---|---|
|
|
43
|
-
| **Command sentinel** (hook) | Inspects every shell command BEFORE it runs and blocks the classic agent disasters: `rm -rf ~`, recursive deletes of system/drive roots, deletes through unset variables (`rm -rf $OUT/*` with `$OUT` empty), `dd` to block devices, `mkfs`, fork bombs. The user's machine is load-bearing. |
|
|
44
|
-
| **Dep sentinel** (hook) | A new dependency hits a manifest with no receipt in `.omit/receipts.jsonl` → the edit is objected to on the spot.
|
|
45
|
-
| **Hazard sentinel** (hook) | Hardcoded API keys/secrets and injection-prone patterns (string-built SQL, `eval`, shell concatenation, `innerHTML`, unsafe deserialization) are blocked the moment they land in a file. Secrets have no override
|
|
46
|
-
| **
|
|
47
|
-
| **
|
|
48
|
-
| **
|
|
43
|
+
| **Command sentinel** (hook) | Inspects every shell command BEFORE it runs and blocks the classic agent disasters: `rm -rf ~`, recursive deletes of system/drive roots, deletes through unset variables (`rm -rf $OUT/*` with `$OUT` empty), `dd` to block devices, `mkfs`, fork bombs. The user's machine is load-bearing. Waiving one takes a trailing comment carrying a real reason — `# omit-allow: <reason>`. The bare token, a token inside a string literal, and a reason-less marker are ignored. |
|
|
44
|
+
| **Dep sentinel** (hook) | A new dependency hits a manifest with no *verified* receipt in `.omit/receipts.jsonl` → the edit is objected to on the spot. The receipt has to name the omissions that were tried, and each one is re-checked: if an omission actually applies, the receipt is refuted and the dependency is refused. |
|
|
45
|
+
| **Hazard sentinel** (hook) | Hardcoded API keys/secrets and injection-prone patterns (string-built SQL, `eval`, shell concatenation, `innerHTML`, unsafe deserialization) are blocked the moment they land in a file. **Secrets have no override** — the secret rules run before any marker is consulted, so no `omit-allow:` waives one. Injection lines take a trailing comment with a real reason, and only that form. |
|
|
46
|
+
| **Leak sentinel** (hook) | Blocks shell commands that print an *existing* secret's raw value to stdout before they run, or a live key typed straight into the command line: macOS Keychain, Linux `secret-tool`/`pass`/`gpg -d`, 1Password/Vault/AWS/GCP/Azure/kubectl secret CLIs, bare `env`/`printenv`, `env \| grep`-ing a KEY/TOKEN/SECRET/connection-string var, or `cat`-ing a `.env`/`credentials`/`*.pem`/`id_rsa` file. Redirecting to a real file or piping into a non-printing sink (clipboard, `--password-stdin`) is recognized as safe — the goal is keeping secrets out of the transcript, not off disk. Adversarially reviewed (3 lenses, every finding re-verified by execution, not inspection) before shipping — one known gap stays undetected on purpose rather than chasing a fragile fix: a `for`/`do`/`done > file` loop's trailing redirect isn't attributed back to the loop body. The agent's own transcript is not a safe place for a real key. |
|
|
47
|
+
| **Lint sentinel** (hook) | omit ships no lint rules. It detects the linter the repo already configured (eslint, biome, ruff, flake8) and runs it on every edited file, so the agent hears objections immediately instead of at CI time. A linter that is configured but cannot be run — no `node_modules`, nothing on PATH — is reported as *not run* with the reason, never as a pass; "no linter configured" is now reserved for a repo that genuinely has none. |
|
|
48
|
+
| **Final Draft gate** (hook) | The session cannot end with an edited tree and no current `.omit/final-draft.md` net report — and the report is read, not just stat'd. Its files/lines/deps counts are cross-checked against the actual diff, so a stub or a stale draft does not pass. The deletion pass is a gate, not a suggestion. |
|
|
49
|
+
| **Receipts ledger** | Every Fact-Check citation is appended to `.omit/receipts.jsonl` as a claim *plus the evidence that settles it*, and `omit verify` re-checks the lot. Run it on a PR: "17/17 claims survived" is a number a reviewer can act on, and "3 refuted" names exactly which shortcuts were invented. |
|
|
49
50
|
|
|
50
|
-
Hooks install automatically with the Claude Code plugin. Escape hatch for humans: `OMIT_OFF=1`.
|
|
51
|
+
Hooks install automatically with the Claude Code plugin. Codex CLI has its own hooks system in the same shape (`PreToolUse` fires with `tool_input.command` for Bash, exit 2 blocks) — run `npx @sriinnu/omit hook install codex` to write `.codex/hooks.json`. The command and leak sentinels are verified against Codex's documented schema and payload shape (not yet a live Codex session firing them end-to-end); the file-based sentinels (dep/hazard/lint) and the Final Draft gate are wired too but best-effort, since Codex's `apply_patch` input shape for those isn't verified. Escape hatch for humans: `OMIT_OFF=1`.
|
|
52
|
+
|
|
53
|
+
## What this does not catch
|
|
54
|
+
|
|
55
|
+
A table of mechanisms invites you to read it as a guarantee. It isn't one, so here is the rest of it. Every item below is a known, reproduced limit, not a hypothetical.
|
|
56
|
+
|
|
57
|
+
- **Writes whose path cannot be read out of the command reach no file sentinel.** `python -c "open('f','w').write(...)"`, `curl -o f`, `node -e fs.writeFileSync`. A literal secret in such a command is still caught by the command-text scan; its *injection* patterns are not. The same goes for a file rewritten through a tool no sentinel is wired to.
|
|
58
|
+
- **`omit gate` is a git hook.** `git commit --no-verify` skips it, and `core.hooksPath` shadows it entirely — `omit audit` and `omit gate` both report that in the verdict, and `omit hook install` writes to whichever directory git actually runs hooks from, but nothing can stop you bypassing your own pre-commit hook.
|
|
59
|
+
- **The dependency allowlist is short.** `setup.py`, `build.gradle`, `Package.swift` and `*.csproj` are dependency-shaped and unparsed. They are now *reported* (`unparsed deps: …`) rather than counted as zero, and a changed one suppresses the `✅` on the deps row — but the gate does not fail on them, because a `setup.py` in a repo is not evidence of anything.
|
|
60
|
+
- **The lint sentinel runs your linter in your session**, which executes your lint config — the same trust as running `npm run lint` yourself. In the GitHub Action it is gated: with the default `exec: false`, CI does not run the linter at all, and the verdict says *not run* rather than claiming a pass.
|
|
61
|
+
- **Receipt evidence is bound to its claim textually.** A `run` snippet must name what it exercises and the argv must mention it, which closes snippets that exit on demand (`["true"]`, `["false"]`). It does not catch a snippet that names a symbol without calling it. Proving that would mean the verifier writing the snippet, and then it is testing the verifier.
|
|
62
|
+
- **`omit`'s own directory is exempt** from the hazard scan, because the ledger holds evidence strings by construction. Its execution risk is what `OMIT_HOOK_EXEC` gates.
|
|
63
|
+
- **The Action does not run a PR's receipts** unless you set `with: { exec: true }`. A PR's `run` snippets are untrusted code, so the default is not to execute them.
|
|
64
|
+
|
|
65
|
+
`// omitted: a mechanism for the first item above` would be a shell parser, and a wrong shell parser is worse than none. It is left out on purpose.
|
|
66
|
+
|
|
67
|
+
## Receipts: the claim and the evidence, in one line
|
|
68
|
+
|
|
69
|
+
Minimalism is a taste, and taste has no receipt. The failure mode underneath it does: the agent says *"the codebase already does this"* or *"stdlib covers it"*, and nobody checks. Every rule file in this genre **tells** the agent not to lie. `omit` checks.
|
|
70
|
+
|
|
71
|
+
Each claim lands in `.omit/receipts.jsonl` with the evidence that settles it:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{"claim":"reuse","rung":2,"file":"src/util.ts","line":42,"symbol":"parseRange"}
|
|
75
|
+
{"claim":"stdlib","rung":3,"api":"crypto.randomUUID","run":["node","-e","crypto.randomUUID()"]}
|
|
76
|
+
{"claim":"installed-dep","rung":5,"dep":"zod","run":["node","-e","require('zod').object"]}
|
|
77
|
+
{"claim":"new-dep","rung":7,"dep":"left-pad","tried":[{"rung":2,"absent":"padTo"}]}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
$ npx @sriinnu/omit verify
|
|
82
|
+
✅ line 1 verified reuse
|
|
83
|
+
⛔ line 2 failed new-dep
|
|
84
|
+
[new-dep] tried[0] (rung 2) actually HOLDS: padTo is present at src/util.ts — so the omission applies and this dep is not needed
|
|
85
|
+
|
|
86
|
+
1/2 claims survived re-checking · 1 refuted · 0 unverifiable
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
That second one is the whole idea. A `new-dep` receipt exists to say *"I tried the earlier omissions and none of them held"* — so it cites the symbol it looked for, `omit` searches the tree for itself, and a symbol that **is** there refutes the agent's own conclusion. When the author supplies the question instead of the evidence, citing your way past an omission you never tried stops being possible.
|
|
90
|
+
|
|
91
|
+
Two bindings make that hold, and neither is decorative:
|
|
92
|
+
|
|
93
|
+
- **`run` must name what it exercises** (`api`, or the dependency), and the argv must mention it. Exit status alone proves nothing: `["true"]` exits 0 and `["false"]` exits 1 while testing neither the standard library nor anything else. A snippet that exits on demand is not evidence.
|
|
94
|
+
- **A `tried` entry cites a search, not a location.** Naming a file that merely doesn't exist proves nothing — and it used to be the cheapest possible way to fake "I tried reuse", because the check only asked whether it failed.
|
|
95
|
+
|
|
96
|
+
`run` is an argv array, never a shell string: nothing expands, nothing hides in an argument. `.omit/receipts.jsonl` is executable in the same sense a Makefile is — read it in a PR the way you'd read one. `OMIT_NO_EXEC=1` downgrades those checks to "unverifiable" instead of executing them, and the dependency hook does not execute them at all unless you set `OMIT_HOOK_EXEC=1`: a *Write* of the ledger would otherwise run code as you, with no approval prompt.
|
|
97
|
+
|
|
98
|
+
**Upgrading from 0.3.x:** the shapes above are the 0.4.0 schema. A receipt written before it — a prose `receipt` string, or a `tried` entry citing a file — reads as `unverifiable`, which `omit verify` names individually. They are not silently accepted, and they are not silently dropped.
|
|
51
99
|
|
|
52
100
|
## Any provider, same gates
|
|
53
101
|
|
|
54
|
-
The enforcement logic lives in a zero-dependency CLI, not in any one vendor's hook system: Claude Code's hooks are just thin adapters over it. For Cursor,
|
|
102
|
+
The enforcement logic lives in a zero-dependency CLI, not in any one vendor's hook system: Claude Code's and Codex's hooks are both just thin adapters over it. For Cursor, Copilot, or anything else, enforce at the two chokepoints every agent passes through:
|
|
55
103
|
|
|
56
104
|
```
|
|
57
105
|
npx @sriinnu/omit hook install # git pre-commit: audits the staged diff,
|
|
58
106
|
# fails on secrets, injections, uncited deps
|
|
59
|
-
npx @sriinnu/omit audit # net diff, new deps, hazards
|
|
107
|
+
npx @sriinnu/omit audit # net diff (untracked files included), new deps, hazards
|
|
60
108
|
npx @sriinnu/omit check <files> # hazard-scan specific files (wire into any hook system)
|
|
61
109
|
npx @sriinnu/omit lint [files] # run the repo's OWN linter on changed files
|
|
110
|
+
npx @sriinnu/omit verify # re-check every claim in .omit/receipts.jsonl
|
|
62
111
|
npx @sriinnu/omit guard "<cmd>" # is this shell command a disaster? (wire into any hook system)
|
|
112
|
+
npx @sriinnu/omit leak "<cmd>" # would this command print a real secret to stdout?
|
|
63
113
|
npx @sriinnu/omit gate # the pre-commit check, callable from anywhere
|
|
114
|
+
npx @sriinnu/omit hook install codex # write .codex/hooks.json — live sentinels inside Codex CLI
|
|
64
115
|
```
|
|
65
116
|
|
|
66
117
|
And server-side, the GitHub Action comments the verdict on every PR regardless of what wrote the code:
|
|
@@ -76,6 +127,9 @@ jobs:
|
|
|
76
127
|
- uses: actions/checkout@v4
|
|
77
128
|
with: { fetch-depth: 0 }
|
|
78
129
|
- uses: sriinnu/omit@main
|
|
130
|
+
# with: { exec: true } # execute receipts' `run` snippets to verify them
|
|
131
|
+
# fully. Off by default: a PR's receipts are
|
|
132
|
+
# untrusted code, and this runs on pull requests.
|
|
79
133
|
```
|
|
80
134
|
|
|
81
135
|
```
|
|
@@ -84,7 +138,14 @@ jobs:
|
|
|
84
138
|
- new deps: 0 ✅
|
|
85
139
|
- hazards: 0 ✅
|
|
86
140
|
- footnotes: 3 recorded · load-bearing: 1 marked
|
|
87
|
-
-
|
|
141
|
+
- receipts: 17/17 verified
|
|
142
|
+
- lint: eslint ✅
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
// omitted: a composite "omit score": any weight vector over these counts is
|
|
147
|
+
// invented, and footnotes could raise it by adding lines. The counts above are
|
|
148
|
+
// the receipts; a single number would just be another uncited claim.
|
|
88
149
|
```
|
|
89
150
|
|
|
90
151
|
## The referee (experimental)
|
|
@@ -162,10 +223,6 @@ skills/omit/SKILL.md → .claude/skills/omit/SKILL.md (project)
|
|
|
162
223
|
- `/omit [margin|redline|rewrite|off]`: switch or show the current mode
|
|
163
224
|
- `/omit-edit`: run an editor's pass over the current diff: flag bloat, uncited claims, missing footnotes, and cut opportunities
|
|
164
225
|
|
|
165
|
-
## Benchmarks
|
|
166
|
-
|
|
167
|
-
None yet: and we won't publish numbers we can't hand you the harness for. `benchmarks/METHODOLOGY.md` defines the measurement we consider honest (paired tasks, agentic baseline, net LOC / new deps / defect rate / load-bearing violations, full transcripts). Reproducible runs are the most welcome PR this repo can receive.
|
|
168
|
-
|
|
169
226
|
## Prior art
|
|
170
227
|
|
|
171
228
|
The minimalism-pressure idea was popularized by [ponytail](https://github.com/DietrichGebert/ponytail), which deserves its stars. `omit` differs where it matters: shortcuts require citations, the diff is edited *after* it works, safety lines are enumerated and never cut, and what's left out is footnoted instead of silent.
|
package/action.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: omit audit
|
|
2
|
-
description: Comment the omit verdict (net diff, new deps, hazards,
|
|
2
|
+
description: Comment the omit verdict (net diff, new deps, hazards, lint) on pull requests — works whatever agent or editor wrote the code.
|
|
3
3
|
branding:
|
|
4
4
|
icon: scissors
|
|
5
5
|
color: gray-dark
|
|
@@ -8,11 +8,28 @@ inputs:
|
|
|
8
8
|
description: Base ref to diff against
|
|
9
9
|
required: false
|
|
10
10
|
default: ''
|
|
11
|
+
exec:
|
|
12
|
+
description: >
|
|
13
|
+
Execute the `run` snippets in .omit/receipts.jsonl to fully verify them.
|
|
14
|
+
Off by default: a PR's receipts are untrusted code, and this action runs on
|
|
15
|
+
pull requests. Turn it on for repos whose PRs you already run code from.
|
|
16
|
+
Also gates the repo's own linter, which a PR's config file can make
|
|
17
|
+
arbitrary: with exec off, the verdict reports the linter as not run.
|
|
18
|
+
required: false
|
|
19
|
+
default: 'false'
|
|
11
20
|
runs:
|
|
12
21
|
using: composite
|
|
13
22
|
steps:
|
|
14
23
|
- name: Run omit audit
|
|
15
24
|
shell: bash
|
|
25
|
+
env:
|
|
26
|
+
# Inverted on purpose. The obvious `exec == 'true' && '' || '1'` never
|
|
27
|
+
# enables anything: '' is falsy in an expression, so the `&&` yields
|
|
28
|
+
# false and the `||` always takes '1'. Here the value being truth-tested
|
|
29
|
+
# is '1', which is never ambiguous, and the enabled branch yields '0' —
|
|
30
|
+
# an explicit off-value for OMIT_NO_EXEC, so the input can never mean
|
|
31
|
+
# "maybe". Anything that isn't exactly 'true' stays disabled.
|
|
32
|
+
OMIT_NO_EXEC: ${{ inputs.exec != 'true' && '1' || '0' }}
|
|
16
33
|
run: |
|
|
17
34
|
BASE="${{ inputs.base }}"
|
|
18
35
|
if [ -z "$BASE" ]; then BASE="origin/${{ github.event.pull_request.base.ref }}"; fi
|
package/bench/run.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { tmpdir } from 'node:os'
|
|
|
10
10
|
import { basename, join, resolve } from 'node:path'
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
12
12
|
import { isManifest, addedDeps } from '../lib/deps.mjs'
|
|
13
|
+
import { fileAtRevision } from '../lib/git.mjs'
|
|
13
14
|
import { findHazards } from '../lib/hazards.mjs'
|
|
14
15
|
|
|
15
16
|
const configPath = process.argv[2]
|
|
@@ -51,7 +52,7 @@ for (const task of cfg.tasks) {
|
|
|
51
52
|
const durationMs = Date.now() - t0
|
|
52
53
|
|
|
53
54
|
// metrics vs the base commit
|
|
54
|
-
let added = 0, deleted = 0, newDeps = [], hazards = []
|
|
55
|
+
let added = 0, deleted = 0, newDeps = [], hazards = [], metricsError = null
|
|
55
56
|
try {
|
|
56
57
|
sh('git add -A', dir)
|
|
57
58
|
for (const row of sh('git diff --cached --numstat', dir).split('\n').filter(Boolean)) {
|
|
@@ -59,11 +60,30 @@ for (const task of cfg.tasks) {
|
|
|
59
60
|
if (a === '-') continue
|
|
60
61
|
added += +a
|
|
61
62
|
deleted += +d
|
|
62
|
-
|
|
63
|
+
// Declared dependency sets across the two revisions, not diff lines:
|
|
64
|
+
// the arm's manifest is read the same whether the agent wrote it
|
|
65
|
+
// minified or pretty-printed.
|
|
66
|
+
if (isManifest(path)) {
|
|
67
|
+
let after = ''
|
|
68
|
+
try {
|
|
69
|
+
after = readFileSync(join(dir, path), 'utf8')
|
|
70
|
+
} catch {}
|
|
71
|
+
// Three outcomes, not string-or-null: "git could not answer" used to
|
|
72
|
+
// coalesce to '' and report every dependency the manifest declares as
|
|
73
|
+
// newly added — a measurement nobody made, in a file of measurements.
|
|
74
|
+
const before = fileAtRevision(dir, 'HEAD', path)
|
|
75
|
+
if (before.failed) throw new Error(`git could not read ${path} at HEAD: ${before.reason}`)
|
|
76
|
+
newDeps.push(...addedDeps(basename(path), before.present ? before.text : '', after))
|
|
77
|
+
}
|
|
63
78
|
}
|
|
64
79
|
const addedLines = sh('git diff --cached', dir).split('\n').filter((l) => l.startsWith('+') && !l.startsWith('+++')).map((l) => l.slice(1))
|
|
65
80
|
hazards = findHazards(addedLines)
|
|
66
|
-
} catch {
|
|
81
|
+
} catch (e) {
|
|
82
|
+
// Swallowing this printed "deps +0 · hazards 0" for a run nothing measured.
|
|
83
|
+
// The harness keeps going (it is a referee, not a gate) but the run's
|
|
84
|
+
// record says which metrics are missing, and the summary counts the run.
|
|
85
|
+
metricsError = e.message
|
|
86
|
+
}
|
|
67
87
|
|
|
68
88
|
let testPass = null
|
|
69
89
|
if (task.testCmd) {
|
|
@@ -71,10 +91,11 @@ for (const task of cfg.tasks) {
|
|
|
71
91
|
testPass = t.status === 0
|
|
72
92
|
}
|
|
73
93
|
|
|
74
|
-
const rec = { task: task.id, arm: arm.name, added, deleted, net: added - deleted, newDeps, hazards: hazards.length, testPass, durationMs, dir }
|
|
94
|
+
const rec = { task: task.id, arm: arm.name, added, deleted, net: added - deleted, newDeps, hazards: hazards.length, testPass, durationMs, dir, ...(metricsError ? { metricsError } : {}) }
|
|
75
95
|
results.push(rec)
|
|
76
96
|
writeFileSync(join(benchRoot, `${task.id}--${arm.name}.log`), `${run.stdout ?? ''}\n--- stderr ---\n${run.stderr ?? ''}`)
|
|
77
97
|
console.log(` net ${rec.net >= 0 ? '+' : ''}${rec.net} · deps +${newDeps.length} · hazards ${rec.hazards} · tests ${testPass === null ? 'n/a' : testPass ? 'pass' : 'FAIL'} · ${(durationMs / 1000).toFixed(0)}s`)
|
|
98
|
+
if (metricsError) console.log(` ⚠ metrics incomplete: ${metricsError}`)
|
|
78
99
|
}
|
|
79
100
|
}
|
|
80
101
|
|