@alexcodeplace/slopgate 0.3.0 → 0.3.3

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/README.md CHANGED
@@ -93,6 +93,28 @@ slopgate baseline --prune --config .slopgate/config.toml
93
93
  slopgate --self-test --config "$(npm root -g)/slopgate/rules/baseline/selftest.config.toml"
94
94
  ```
95
95
 
96
+ ### Run immutable full-repository CI gate:
97
+ ```bash
98
+ slopgate scan --scope repo --tier commit --format github --config .slopgate/config.toml
99
+ ```
100
+
101
+ ### Record reviewer defects and enforce rule harvesting:
102
+ ```bash
103
+ slopgate defect record --class missing-button-type --file src/app.tsx --line 42 --source code-review --config .slopgate/config.toml
104
+ slopgate harvest --check --config .slopgate/config.toml
105
+ ```
106
+
107
+ Second distinct occurrence requires `.slopgate/rules/ast/<class>.yml` plus `.slopgate/fixtures/<class>.invalid.ts[x]` and `<class>.valid.ts[x]`. `--self-test` proves rule fires on configured fixtures.
108
+
109
+ ### Reusable GitHub gate:
110
+ ```yaml
111
+ jobs:
112
+ slopgate:
113
+ uses: alexcodeplace/slopgate/.github/workflows/slopgate.yml@v1
114
+ ```
115
+
116
+ Self-hosted jobs reject fork pull requests before checkout. Use repository-owned branches or isolated disposable runners for untrusted forks.
117
+
96
118
  ### Install or reinstall hooks:
97
119
  ```bash
98
120
  slopgate install-hooks --config .slopgate/config.toml
@@ -701,12 +723,10 @@ Tool crash / timeout → `⚠ skipped: <id> (<reason>)` warning, gate continues
701
723
 
702
724
  - **Git-only** — no other VCS support
703
725
  - **No auto-fix** — violations are reported, not automatically corrected
704
- - **No CI integration yet** — hooks are local and Claude Code only; CI layer is future work
705
726
  - **`slopgate audit` command** — planned for v2 (non-gating architecture-health report: hotspots, module shape, co-change coupling, ratchet progress tracking)
706
727
  - **Embeddings-based semantic duplicate detection** — planned, not in v1
707
728
  - **API-surface diff gate** — track breaking changes to public exports (future)
708
729
  - **LLM-judge skill** — on-demand deep review of architectural debt (separate sub-project)
709
- - **Rule harvesting** — auto-generate rules from repeated violations (separate sub-project)
710
730
 
711
731
  ---
712
732
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # Slopgate PreToolUse hook — runs --staged before a git commit. Exit 1 → commit blocked.
3
3
  TOOL_JSON=$(cat)
4
- CMD=$(node -e "
4
+ CMD=$(/usr/bin/bun -e "
5
5
  let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{process.stdout.write(JSON.parse(d).tool_input?.command||'')}catch{process.stdout.write('')}});" <<< "$TOOL_JSON" 2>/dev/null)
6
6
  echo "$CMD" | grep -qE 'git[[:space:]]+commit' || exit 0
7
7
 
@@ -9,4 +9,4 @@ ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
9
9
  CONFIG="$ROOT/.slopgate/config.toml"
10
10
  [ -f "$CONFIG" ] || exit 0
11
11
  HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
- exec node "$HERE/../bin/slopgate" --staged --config "$CONFIG"
12
+ exec /usr/bin/bun "$HERE/../bin/slopgate" --staged --config "$CONFIG"
@@ -2,7 +2,7 @@
2
2
  # Slopgate PostToolUse hook — single-file scan after Edit/Write.
3
3
  # Exit 2 → stderr feeds back into the agent turn. FAIL-OPEN: any error/timeout → exit 0.
4
4
  TOOL_JSON=$(cat)
5
- FILE=$(node -e "
5
+ FILE=$(/usr/bin/bun -e "
6
6
  let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{process.stdout.write(JSON.parse(d).tool_input?.file_path||'')}catch{process.stdout.write('')}});" <<< "$TOOL_JSON" 2>/dev/null) || exit 0
7
7
  [ -n "$FILE" ] || exit 0
8
8
  case "$FILE" in *.test.ts|*.test.tsx) exit 0 ;; *.ts|*.tsx|*.astro) ;; *) exit 0 ;; esac
@@ -14,6 +14,6 @@ CONFIG="$ROOT/.slopgate/config.toml"
14
14
  [ -f "$CONFIG" ] || exit 0
15
15
 
16
16
  HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
- OUT=$(timeout 5 node "$HERE/../bin/slopgate" --file "$FILE" --config "$CONFIG" 2>&1)
17
+ OUT=$(timeout 5 /usr/bin/bun "$HERE/../bin/slopgate" --file "$FILE" --config "$CONFIG" 2>&1)
18
18
  [ "$?" -eq 1 ] && { echo "$OUT" >&2; exit 2; }
19
19
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexcodeplace/slopgate",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "Global code-quality / anti-slop gate — engine shared, rules per-project.",
6
6
  "license": "MIT",
@@ -22,7 +22,6 @@
22
22
  "rules",
23
23
  "skills",
24
24
  "vendor",
25
- "!rules/baseline/fixtures",
26
25
  "!bin/slopgate-rs"
27
26
  ],
28
27
  "engines": {
@@ -0,0 +1,5 @@
1
+ [
2
+ { "rule": "no-circular", "severity": "error", "from": "src/a.ts", "to": "src/b.ts" },
3
+ { "rule": "no-orphans", "severity": "warn", "from": "src/orphan.ts", "to": "src/orphan.ts" },
4
+ { "rule": "fyi-only", "severity": "info", "from": "src/ui/page.ts", "to": "src/db/client.ts" }
5
+ ]
@@ -0,0 +1,11 @@
1
+ {
2
+ "summary": {
3
+ "violations": [
4
+ { "type": "cycle", "from": "src/a.ts", "to": "src/b.ts", "rule": { "severity": "error", "name": "no-circular" } },
5
+ { "type": "module", "from": "src/orphan.ts", "to": "src/orphan.ts", "rule": { "severity": "warn", "name": "no-orphans" } },
6
+ { "type": "dependency", "from": "src/ui/page.ts", "to": "src/db/client.ts", "rule": { "severity": "info", "name": "fyi-only" } }
7
+ ],
8
+ "error": 1, "warn": 1, "info": 1
9
+ },
10
+ "modules": []
11
+ }
@@ -0,0 +1,3 @@
1
+ [
2
+ { "firstFile": "src/features/a.ts", "firstStart": 10, "firstEnd": 27, "secondFile": "src/features/b.ts", "secondStart": 40, "secondEnd": 57, "lines": 18 }
3
+ ]
@@ -0,0 +1,13 @@
1
+ {
2
+ "duplicates": [
3
+ {
4
+ "format": "typescript",
5
+ "lines": 18,
6
+ "tokens": 120,
7
+ "firstFile": { "name": "src/features/a.ts", "start": 10, "end": 27, "startLoc": { "line": 10, "column": 1 }, "endLoc": { "line": 27, "column": 2 } },
8
+ "secondFile": { "name": "src/features/b.ts", "start": 40, "end": 57, "startLoc": { "line": 40, "column": 1 }, "endLoc": { "line": 57, "column": 2 } },
9
+ "fragment": "function dup() { /* body */ }"
10
+ }
11
+ ],
12
+ "statistics": {}
13
+ }
@@ -0,0 +1,6 @@
1
+ [
2
+ { "type": "files", "file": "src/orphan.ts", "line": 1, "name": "src/orphan.ts" },
3
+ { "type": "exports", "file": "src/util.ts", "line": 14, "name": "unusedHelper" },
4
+ { "type": "types", "file": "src/util.ts", "line": 2, "name": "UnusedType" },
5
+ { "type": "dependencies", "file": "package.json", "line": 12, "name": "left-pad" }
6
+ ]
@@ -0,0 +1,31 @@
1
+ {
2
+ "files": ["src/orphan.ts"],
3
+ "issues": [
4
+ {
5
+ "file": "src/util.ts",
6
+ "dependencies": [],
7
+ "devDependencies": [],
8
+ "optionalPeerDependencies": [],
9
+ "unlisted": [],
10
+ "binaries": [],
11
+ "unresolved": [],
12
+ "exports": [{ "name": "unusedHelper", "line": 14, "col": 14, "pos": 300 }],
13
+ "types": [{ "name": "UnusedType", "line": 2, "col": 13, "pos": 30 }],
14
+ "enumMembers": {},
15
+ "duplicates": []
16
+ },
17
+ {
18
+ "file": "package.json",
19
+ "dependencies": [{ "name": "left-pad", "line": 12, "col": 6, "pos": 200 }],
20
+ "devDependencies": [],
21
+ "optionalPeerDependencies": [],
22
+ "unlisted": [],
23
+ "binaries": [],
24
+ "unresolved": [],
25
+ "exports": [],
26
+ "types": [],
27
+ "enumMembers": {},
28
+ "duplicates": []
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,5 @@
1
+ [
2
+ { "file": "src/a.ts", "line": 12, "code": "TS2322", "message": "Type 'string' is not assignable to type 'number'." },
3
+ { "file": "src/b.tsx", "line": 3, "code": "TS2304", "message": "Cannot find name 'foo'." },
4
+ { "file": "src/long.ts", "line": 7, "code": "TS2345", "message": "Argument of type '{ a: string; }' is not assignable to parameter of type 'Opts'. Property 'b' is missing in type '{ a: string; }' but required in type 'Opts'." }
5
+ ]
@@ -0,0 +1,4 @@
1
+ src/a.ts(12,5): error TS2322: Type 'string' is not assignable to type 'number'.
2
+ src/b.tsx(3,1): error TS2304: Cannot find name 'foo'.
3
+ src/long.ts(7,9): error TS2345: Argument of type '{ a: string; }' is not assignable to parameter of type 'Opts'.
4
+ Property 'b' is missing in type '{ a: string; }' but required in type 'Opts'.
@@ -0,0 +1,5 @@
1
+ [
2
+ { "file": "src/api/handler.ts", "line": 42, "name": "data" },
3
+ { "file": "src/api/handler.ts", "line": 55, "name": "response" },
4
+ { "file": "src/legacy/blob.ts", "line": 7, "name": "payload" }
5
+ ]
@@ -0,0 +1,5 @@
1
+ /repo/src/api/handler.ts:42:18: data
2
+ /repo/src/api/handler.ts:55:3: response
3
+ src/legacy/blob.ts:7:10: payload
4
+ 2912 / 2930 99.38%
5
+ type-coverage success.
@@ -0,0 +1,25 @@
1
+ // Fixture for slopgate self-test. Contains deliberate canary tokens.
2
+ // no-stubs SHOULD match stub markers (not bare placeholder UI/i18n tokens):
3
+ // placeholder for now
4
+ // TODO: implement later
5
+ // not implemented
6
+ export const x = __SLOPGATE_AST_CANARY__;
7
+ // negative: must NOT match no-stubs — placeholder={...}, placeholder: class, i18n keys
8
+ const _neg = <input placeholder={t('x')} className="placeholder:text-ink" />;
9
+ const _titleKey = admin.incidents.titlePlaceholder;
10
+ const namePlaceholder = 1;
11
+ // ts-suppress SHOULD match @ts-* only (not eslint-disable):
12
+ // @ts-ignore
13
+ /* eslint-disable zync/no-raw-html-in-pages */
14
+ // changed from foo to bar
15
+ function bad(el: HTMLElement) { el.innerHTML = '<b>hi</b>'; }
16
+ // live-secrets canary:
17
+ const _sk = "sk_live_aaaaaaaaaaaa";
18
+ // eval-ban canary:
19
+ // eval(userCode); — commented to avoid actual eval in fixture
20
+ const _evalStr = 'eval(userCode)';
21
+ // pii-logs canary:
22
+ console.log(user.phone, user.email);
23
+ // weak-hash canary:
24
+ // createHash('md5') — weak hash
25
+ const _wh = "createHash('md5').update(data)";
@@ -0,0 +1,4 @@
1
+ export function Loc() {
2
+ const href = window.location.href;
3
+ return <span>{href}</span>;
4
+ }
@@ -0,0 +1,3 @@
1
+ export function swallow(fn: () => void) {
2
+ try { fn(); } catch (e) {}
3
+ }
@@ -0,0 +1,4 @@
1
+ export function Swallow({ fn }: { fn: () => void }) {
2
+ try { fn(); } catch (e) {}
3
+ return <div />;
4
+ }
@@ -0,0 +1,3 @@
1
+ export function Link() {
2
+ return <a target="_blank" href="https://example.com">x</a>;
3
+ }
@@ -0,0 +1,43 @@
1
+ // Fixtures for the UX module ast rules. Each construct below must trigger
2
+ // exactly the rule named in its comment (selftest asserts every yml fires).
3
+ // Parsed syntactically by ast-grep — no imports / type-checking involved.
4
+
5
+ // triggers ux-div-onclick — onClick on a non-semantic element, no role
6
+ export const ClickyDiv = () => <div onClick={onThing}>Open</div>;
7
+
8
+ // triggers ux-img-no-dimensions — <img> with alt but no width/height
9
+ export const Hero = () => <img src="/hero.png" alt="hero" />;
10
+
11
+ // triggers ux-img-no-alt — <img> with dimensions but no alt
12
+ export const NoAlt = () => <img src="/x.png" width={32} height={32} />;
13
+
14
+ // triggers ux-anchor-no-href — <a onClick> with no href
15
+ export const FakeLink = () => <a onClick={onThing}>go</a>;
16
+
17
+ // triggers ux-button-no-type — <button> with no explicit type
18
+ export const BareButton = () => <button onClick={onThing}>Save</button>;
19
+
20
+ // triggers ux-media-no-dimensions — <iframe> with no width/height
21
+ export const Embed = () => <iframe src="/player" />;
22
+
23
+ // triggers ux-async-onclick-no-disable — async onClick, no disabled (type set so
24
+ // it does NOT also trip ux-button-no-type)
25
+ export const AsyncSave = () => <button type="button" onClick={async () => save()}>Save</button>;
26
+
27
+ // triggers ux-modal-no-close — Dialog with no onClose/onDismiss/onOpenChange
28
+ export const Sheet = ({ open }) => <Dialog open={open}>body</Dialog>;
29
+
30
+ // negative: all of these are correct and must NOT trigger anything
31
+ export const Ok = () => (
32
+ <>
33
+ <button type="button" onClick={onThing}>Open</button>
34
+ <button type="button" disabled onClick={async () => save()}>Save</button>
35
+ <a href="/page" onClick={onThing}>real link</a>
36
+ <img src="/ok.png" alt="ok" width={320} height={200} />
37
+ <Dialog open onOpenChange={onThing}>ok</Dialog>
38
+ </>
39
+ );
40
+
41
+ function onThing() {}
42
+ function save() {}
43
+ declare const Dialog: (props: Record<string, unknown>) => unknown;
Binary file
Binary file
Binary file
Binary file
Binary file