code-warden 3.1.1 → 3.3.1

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
@@ -1,137 +1,205 @@
1
- # code-warden
2
-
3
- > Portable AI Coding Governance Layer
4
-
5
- Code-Warden is a portable governance layer for AI coding agents. It enforces scoped planning, patch discipline, file-size limits, the zero-trust secrets policy, verification evidence, install health, and optional Claude Code pre-tool-use blocking.
6
-
7
- ## Four Layers
8
-
9
- <p align="center">
10
- <img src="../logo/layers-diagram.png" alt="Code-Warden Four Layers" width="100%" />
11
- </p>
12
-
13
- | Layer | What it does |
14
- |-------|-------------|
15
- | **Skill governance** | Scope Gate, Plan Gate, blast-radius checks, patch-first editing, research gates, drift signals, verification evidence |
16
- | **Local verification** | `warden-lint`, `verify-secrets`, `get-context` — directory-aware, no external deps |
17
- | **Installer and health** | Cross-app auto-installer, manifest-backed installs, `--doctor`, `--verify-target`, Windsurf adapter |
18
- | **Hard enforcement** | Claude Code `PreToolUse` hooks block oversized writes and hardcoded secrets before the file system is touched |
19
-
20
- ## Install
21
-
22
- ```bash
23
- git clone https://github.com/Kodaxadev/Code-Warden.git
24
- cd Code-Warden/code-warden
25
- node install.js
26
- ```
27
-
28
- ### Installer commands
29
-
30
- | Command | Purpose |
31
- |---------|---------|
32
- | `node install.js` | Scan, prompt, install to detected apps |
33
- | `node install.js --all` | Install without prompt |
34
- | `node install.js --dry-run` | Preview installs, write nothing |
35
- | `node install.js --list` | Show detected apps and detection method |
36
- | `node install.js --doctor` | Verify source integrity + per-target install health |
37
- | `node install.js --target=claude,cursor` | Force specific targets (warns if not detected) |
38
- | `node install.js --verify-target=claude` | Strict health check — exits nonzero if not installed |
39
- | `node install.js --hooks=claude` | Install PreToolUse hooks into `~/.claude/settings.json` |
40
- | `node install.js --uninstall-hooks=claude` | Remove code-warden hook entries from settings |
41
-
42
- Supported targets: **Claude Code**, **Cursor**, **Warp**, **OpenAI Codex**, **Windsurf**, **Generic Agents**.
43
-
44
- Each install writes a `.code-warden-install.json` manifest (version, target, format, timestamp).
45
-
46
- ### npm scripts
47
-
48
- ```bash
49
- npm run lint # warden-lint on full project tree
50
- npm run check-secrets # verify-secrets on full project tree
51
- npm run install-auto # node install.js
52
- npm run install-dry-run # node install.js --dry-run
53
- npm run install-list # node install.js --list
54
- npm run install-doctor # node install.js --doctor
55
- npm run test # behavioral tests (8 scanner/hook pass/fail cases)
56
- npm run ci # lint + secrets + test + doctor
57
- ```
58
-
59
- ## Usage
60
-
61
- Load at the start of any coding session. Trigger phrases:
62
-
63
- - `"load code-warden"` / `"load protocol"`
64
- - `"begin coding"` / `"new session"` / `"governance check"`
65
- - `"start a new module"` / `"review this before we write"`
66
-
67
- The session sequence is enforced before any implementation:
68
-
69
- <p align="center">
70
- <img src="../logo/session-flow.png" alt="Code-Warden Session Start Sequence" width="100%" />
71
- </p>
72
-
73
- 1. Architecture State (Re-injection Rule)
74
- 2. Session Scope (Session Scoping Rule)
75
- 3. Reference Files (Blueprint Rule)
76
- 4. **Scope Gate** — goal, non-goals, files in/out, verify commands, rollback
77
- 5. **Plan Gate** patch order, blast radius class, post-patch checks
78
-
79
- See [`examples/governed-session.md`](examples/governed-session.md) for an annotated example.
80
-
81
- ## Optional Claude Code Hooks
82
-
83
- <p align="center">
84
- <img src="../logo/hook-flow.png" alt="Code-Warden Hook Enforcement Flow" width="100%" />
85
- </p>
86
-
87
- Install hard enforcement that runs at the `PreToolUse` level — before writes happen:
88
-
89
- ```bash
90
- # Requires Claude Code target to be installed first
91
- node install.js --hooks=claude
92
- ```
93
-
94
- | Hook | Trigger | Policy |
95
- |------|---------|--------|
96
- | `warden-lint-hook.js` | `Write` or `Edit` | Blocks if resulting file exceeds line limit |
97
- | `warden-secrets-hook.js` | `Write` or `Edit` | Hardcoded credential scannerblocks if content matches any secret pattern |
98
-
99
- Both hooks use exec form (`node /path/to/hook.js`) no shell differences across platforms.
100
-
101
- Thresholds are read from `codewarden.json` in the installed skill directory.
102
-
103
- ```bash
104
- node install.js --uninstall-hooks=claude # remove hook entries from settings.json
105
- ```
106
-
107
- Doctor and `--verify-target=claude` validate hook script paths when hooks are registered.
108
-
109
- ## Configuration
110
-
111
- All thresholds in [`codewarden.json`](codewarden.json):
112
-
113
- | Setting | Default | What it controls |
114
- |---------|---------|-----------------|
115
- | `thresholds.max_file_length` | 400 | Lines before `warden-lint.js` flags a file |
116
- | `thresholds.pre_flight_trigger_lines` | 150 | Lines before a pre-flight manifest is required |
117
- | `thresholds.human_checkpoint_files` | 2 | Files touched before `[AWAITING CONFIRMATION]` is required |
118
- | `safety.exempt_from_blast_radius` | `tests/`, `docs/`, `scripts/` | Paths excluded from rollback-plan rule |
119
-
120
- See [`CONFIGURE.md`](CONFIGURE.md) for team-size profiles and tuning rationale.
121
-
122
- ## Reference Files
123
-
124
- | File | Domain |
125
- |------|--------|
126
- | `references/planning-gates.md` | Scope Gate and Plan Gate contracts |
127
- | `references/architecture.md` | Blueprint Rule, Re-injection, State Update |
128
- | `references/safety.md` | Blast Radius, Patch-First, Zero-Trust, Dependency Freeze |
129
- | `references/cognition.md` | Think Before Coding, Don't Guess Syntax, Human Checkpoint |
130
- | `references/cleanup.md` | Tech Debt format, Test Contract, Decision Log |
131
- | `references/anti-drift.md` | Anchor Check, Session Scoping, Drift Trigger Protocol |
132
- | `references/operations.md` | Verification, source-control hygiene, dependency control |
133
- | `references/research-and-fit.md` | Live research gate, stack fit, product-shape guardrails |
134
-
135
- ## Author
136
-
137
- Justin Davis MIT License
1
+ # code-warden
2
+
3
+ > Portable AI Coding Governance Layer
4
+
5
+ Code-Warden provides verifiable governance for AI-assisted development.
6
+ It does not just ask agents to follow rules — it adds Scope Gates, Plan Gates,
7
+ local checks, CI enforcement, runtime hooks where supported, and governance
8
+ artifacts that show what was checked before code was accepted.
9
+
10
+ ## Four Layers
11
+
12
+ <p align="center">
13
+ <img src="../logo/layers-diagram.png" alt="Code-Warden Four Layers" width="100%" />
14
+ </p>
15
+
16
+ | Layer | What it does |
17
+ |-------|-------------|
18
+ | **Skill governance** | Scope Gate, Plan Gate, blast-radius checks, patch-first editing, research gates, drift signals, verification evidence |
19
+ | **Local verification** | `warden-lint`, `verify-secrets`, `get-context` — directory-aware, no external deps |
20
+ | **Installer and health** | Cross-app auto-installer, manifest-backed installs, `--doctor`, `--verify-target`, Windsurf adapter |
21
+ | **Hard enforcement** | Claude Code `PreToolUse` hooks — block oversized writes and hardcoded secrets before the file system is touched |
22
+
23
+ ## Governance Evidence
24
+
25
+ Generate a machine-readable governance report that can be stored in CI, attached to PRs, or used as audit evidence:
26
+
27
+ ```bash
28
+ node tools/governance-report.js . # write .code-warden-report.json + summary
29
+ node tools/governance-report.js . --format=json # JSON to stdout
30
+ node tools/governance-report.js . --format=md # Markdown to stdout
31
+ ```
32
+
33
+ The report runs all checks in a single pass (file length, secrets, behavioral tests, source integrity) and produces a structured artifact:
34
+
35
+ ```json
36
+ {
37
+ "tool": "code-warden",
38
+ "version": "3.2.0",
39
+ "checks": {
40
+ "fileLength": { "status": "pass", "filesScanned": 34, "violations": 0 },
41
+ "secrets": { "status": "pass", "filesScanned": 34, "violations": 0 },
42
+ "behavioralTests": { "status": "pass", "tests": 8, "failures": 0 },
43
+ "installHealth": { "status": "pass" }
44
+ },
45
+ "result": "pass"
46
+ }
47
+ ```
48
+
49
+ In CI, the Markdown format pipes directly into `$GITHUB_STEP_SUMMARY` for PR-visible evidence:
50
+
51
+ | Check | Result | Details |
52
+ |-------|--------|---------|
53
+ | File length | PASS | 34 files scanned, 0 violations |
54
+ | Hardcoded credentials | PASS | 34 files scanned, 0 violations |
55
+ | Behavioral tests | PASS | 8 tests, 0 failures |
56
+ | Install health | PASS | All source files present |
57
+
58
+ See [`templates/ci/github-actions.yml`](templates/ci/github-actions.yml) for the full CI template with artifact upload.
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ npx code-warden init
64
+ ```
65
+
66
+ Or install globally:
67
+
68
+ ```bash
69
+ npm install -g code-warden
70
+ code-warden init
71
+ ```
72
+
73
+ ### CLI commands
74
+
75
+ | Command | Purpose |
76
+ |---------|---------|
77
+ | `code-warden init` | Install to all detected AI runtimes |
78
+ | `code-warden report` | Generate governance report |
79
+ | `code-warden report --format=md` | Markdown output for PR summaries |
80
+ | `code-warden doctor` | Verify source integrity + install health |
81
+ | `code-warden list` | Show detected runtimes |
82
+ | `code-warden hooks claude` | Install Claude Code PreToolUse hooks |
83
+ | `code-warden hooks codex` | Install Codex PreToolUse hooks (partial) |
84
+ | `code-warden uninstall-hooks claude` | Remove Claude Code hooks |
85
+ | `code-warden uninstall-hooks codex` | Remove Codex hooks |
86
+
87
+ ### Direct installer commands
88
+
89
+ | Command | Purpose |
90
+ |---------|---------|
91
+ | `node install.js` | Scan, prompt, install to detected apps |
92
+ | `node install.js --all` | Install without prompt |
93
+ | `node install.js --dry-run` | Preview installs, write nothing |
94
+ | `node install.js --list` | Show detected apps and detection method |
95
+ | `node install.js --doctor` | Verify source integrity + per-target install health |
96
+ | `node install.js --target=claude,cursor` | Force specific targets (warns if not detected) |
97
+ | `node install.js --verify-target=claude` | Strict health checkexits nonzero if not installed |
98
+ | `node install.js --hooks=claude` | Install PreToolUse hooks into `~/.claude/settings.json` |
99
+ | `node install.js --uninstall-hooks=claude` | Remove code-warden hook entries from settings |
100
+
101
+ Supported targets: **Claude Code**, **Cursor**, **Warp**, **OpenAI Codex**, **Windsurf**, **Generic Agents**.
102
+
103
+ Each install writes a `.code-warden-install.json` manifest (version, target, format, timestamp).
104
+
105
+ ### npm scripts
106
+
107
+ ```bash
108
+ npm run lint # warden-lint on full project tree
109
+ npm run check-secrets # verify-secrets on full project tree
110
+ npm run report # governance report, writes .code-warden-report.json
111
+ npm run report:json # governance report as JSON to stdout
112
+ npm run report:md # governance report as Markdown to stdout
113
+ npm run install-auto # node install.js
114
+ npm run install-dry-run # node install.js --dry-run
115
+ npm run install-list # node install.js --list
116
+ npm run install-doctor # node install.js --doctor
117
+ npm run test # behavioral tests (8 scanner/hook pass/fail cases)
118
+ npm run ci # lint + secrets + test + doctor
119
+ ```
120
+
121
+ ## Usage
122
+
123
+ Load at the start of any coding session. Trigger phrases:
124
+
125
+ - `"load code-warden"` / `"load protocol"`
126
+ - `"begin coding"` / `"new session"` / `"governance check"`
127
+ - `"start a new module"` / `"review this before we write"`
128
+
129
+ The session sequence is enforced before any implementation:
130
+
131
+ <p align="center">
132
+ <img src="../logo/session-flow.png" alt="Code-Warden Session Start Sequence" width="100%" />
133
+ </p>
134
+
135
+ 1. Architecture State (Re-injection Rule)
136
+ 2. Session Scope (Session Scoping Rule)
137
+ 3. Reference Files (Blueprint Rule)
138
+ 4. **Scope Gate** — goal, non-goals, files in/out, verify commands, rollback
139
+ 5. **Plan Gate** — patch order, blast radius class, post-patch checks
140
+
141
+ See [`examples/governed-session.md`](examples/governed-session.md) for an annotated example.
142
+
143
+ ## Optional Claude Code Hooks
144
+
145
+ <p align="center">
146
+ <img src="../logo/hook-flow.png" alt="Code-Warden Hook Enforcement Flow" width="100%" />
147
+ </p>
148
+
149
+ Install hard enforcement that runs at the `PreToolUse` level — before writes happen:
150
+
151
+ ```bash
152
+ # Requires Claude Code target to be installed first
153
+ node install.js --hooks=claude
154
+ ```
155
+
156
+ | Hook | Trigger | Policy |
157
+ |------|---------|--------|
158
+ | `warden-lint-hook.js` | `Write` or `Edit` | Blocks if resulting file exceeds line limit |
159
+ | `warden-secrets-hook.js` | `Write` or `Edit` | Hardcoded credential scanner — blocks if content matches any secret pattern |
160
+
161
+ Both hooks use exec form (`node /path/to/hook.js`) — no shell differences across platforms.
162
+
163
+ Thresholds are read from `codewarden.json` in the installed skill directory.
164
+
165
+ ```bash
166
+ node install.js --uninstall-hooks=claude # remove hook entries from settings.json
167
+ ```
168
+
169
+ Doctor and `--verify-target=claude` validate hook script paths when hooks are registered.
170
+
171
+ ## Configuration
172
+
173
+ All thresholds in [`codewarden.json`](codewarden.json):
174
+
175
+ | Setting | Default | What it controls |
176
+ |---------|---------|-----------------|
177
+ | `thresholds.max_file_length` | 400 | Lines before `warden-lint.js` flags a file |
178
+ | `thresholds.pre_flight_trigger_lines` | 150 | Lines before a pre-flight manifest is required |
179
+ | `thresholds.human_checkpoint_files` | 2 | Files touched before `[AWAITING CONFIRMATION]` is required |
180
+ | `safety.exempt_from_blast_radius` | `tests/`, `docs/`, `scripts/` | Paths excluded from rollback-plan rule |
181
+
182
+ See [`CONFIGURE.md`](CONFIGURE.md) for team-size profiles and tuning rationale.
183
+
184
+ ## Reference Files
185
+
186
+ | File | Domain |
187
+ |------|--------|
188
+ | `references/planning-gates.md` | Scope Gate and Plan Gate contracts |
189
+ | `references/architecture.md` | Blueprint Rule, Re-injection, State Update |
190
+ | `references/safety.md` | Blast Radius, Patch-First, Zero-Trust, Dependency Freeze |
191
+ | `references/cognition.md` | Think Before Coding, Don't Guess Syntax, Human Checkpoint |
192
+ | `references/cleanup.md` | Tech Debt format, Test Contract, Decision Log |
193
+ | `references/anti-drift.md` | Anchor Check, Session Scoping, Drift Trigger Protocol |
194
+ | `references/operations.md` | Verification, source-control hygiene, dependency control |
195
+ | `references/research-and-fit.md` | Live research gate, stack fit, product-shape guardrails |
196
+
197
+ ## Note for contributors
198
+
199
+ > If testing `npx code-warden` from inside the Code-Warden source checkout,
200
+ > npm may prefer the local package context. Test from a separate directory for
201
+ > the same behavior users will see.
202
+
203
+ ## Author
204
+
205
+ Justin Davis — MIT License
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawnSync } = require('child_process');
5
+ const path = require('path');
6
+
7
+ const ROOT = path.join(__dirname, '..');
8
+
9
+ const COMMANDS = {
10
+ init: { desc: 'Install Code-Warden to detected AI runtimes', run: ['install.js', '--all'] },
11
+ doctor: { desc: 'Verify source integrity and install health', run: ['install.js', '--doctor'] },
12
+ report: { desc: 'Generate governance report (.code-warden-report.json)', run: ['tools/governance-report.js', '.'] },
13
+ list: { desc: 'Show detected AI runtimes', run: ['install.js', '--list'] },
14
+ };
15
+
16
+ const HOOK_TARGETS = ['claude', 'codex'];
17
+
18
+ function usage() {
19
+ console.log('Usage: code-warden <command> [options]\n');
20
+ console.log('Commands:');
21
+ for (const [name, { desc }] of Object.entries(COMMANDS)) {
22
+ console.log(` ${name.padEnd(22)} ${desc}`);
23
+ }
24
+ console.log(` ${'hooks <target>'.padEnd(22)} Install PreToolUse hooks (${HOOK_TARGETS.join(', ')})`);
25
+ console.log(` ${'uninstall-hooks <target>'.padEnd(22)} Remove PreToolUse hooks`);
26
+ console.log(`\nExamples:`);
27
+ console.log(` npx code-warden init`);
28
+ console.log(` npx code-warden report`);
29
+ console.log(` npx code-warden report --format=md`);
30
+ console.log(` npx code-warden hooks claude`);
31
+ }
32
+
33
+ function run(scriptPath, args) {
34
+ const result = spawnSync(process.execPath, [path.join(ROOT, scriptPath), ...args], {
35
+ stdio: 'inherit',
36
+ cwd: process.cwd(),
37
+ });
38
+ process.exit(result.status ?? 1);
39
+ }
40
+
41
+ const args = process.argv.slice(2);
42
+ const command = args[0];
43
+ const rest = args.slice(1);
44
+
45
+ if (!command || command === '--help' || command === '-h') {
46
+ usage();
47
+ process.exit(0);
48
+ }
49
+
50
+ if (command === '--version' || command === '-v') {
51
+ const pkg = require(path.join(ROOT, 'package.json'));
52
+ console.log(pkg.version);
53
+ process.exit(0);
54
+ }
55
+
56
+ if (COMMANDS[command]) {
57
+ const entry = COMMANDS[command];
58
+ const scriptArgs = [...entry.run.slice(1), ...rest];
59
+ run(entry.run[0], scriptArgs);
60
+ }
61
+
62
+ if (command === 'hooks') {
63
+ const target = rest[0];
64
+ if (!target || !HOOK_TARGETS.includes(target)) {
65
+ console.error(`Usage: code-warden hooks <${HOOK_TARGETS.join('|')}>`);
66
+ process.exit(1);
67
+ }
68
+ run('install.js', [`--hooks=${target}`]);
69
+ }
70
+
71
+ if (command === 'uninstall-hooks') {
72
+ const target = rest[0];
73
+ if (!target || !HOOK_TARGETS.includes(target)) {
74
+ console.error(`Usage: code-warden uninstall-hooks <${HOOK_TARGETS.join('|')}>`);
75
+ process.exit(1);
76
+ }
77
+ run('install.js', [`--uninstall-hooks=${target}`]);
78
+ }
79
+
80
+ console.error(`Unknown command: ${command}\n`);
81
+ usage();
82
+ process.exit(1);
package/package.json CHANGED
@@ -1,19 +1,62 @@
1
- {
2
- "name": "code-warden",
3
- "version": "3.1.1",
4
- "description": "Production-grade AI development governance skill for Codex, Claude Code, and Cowork.",
5
- "main": "SKILL.md",
6
- "scripts": {
7
- "lint": "node tools/warden-lint.js .",
8
- "check-secrets": "node tools/verify-secrets.js .",
9
- "get-context": "node tools/get-context.js",
10
- "install-auto": "node install.js",
11
- "install-dry-run": "node install.js --dry-run",
12
- "install-list": "node install.js --list",
13
- "install-doctor": "node install.js --doctor",
14
- "test": "node tools/tests/run-tests.js",
15
- "ci": "npm run lint && npm run check-secrets && npm run test && node install.js --doctor"
16
- },
17
- "author": "Justin Davis",
18
- "license": "MIT"
19
- }
1
+ {
2
+ "name": "code-warden",
3
+ "version": "3.3.1",
4
+ "description": "Verifiable governance for AI-assisted development checks, hooks, and evidence.",
5
+ "main": "SKILL.md",
6
+ "bin": {
7
+ "code-warden": "bin/code-warden.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "tools/",
12
+ "references/",
13
+ "templates/",
14
+ "examples/",
15
+ "SKILL.md",
16
+ "CONFIGURE.md",
17
+ "DECISIONS.md",
18
+ "README.md",
19
+ "codewarden.json",
20
+ "install.js",
21
+ "install.ps1",
22
+ "install.sh"
23
+ ],
24
+ "scripts": {
25
+ "lint": "node tools/warden-lint.js .",
26
+ "check-secrets": "node tools/verify-secrets.js .",
27
+ "get-context": "node tools/get-context.js",
28
+ "report": "node tools/governance-report.js .",
29
+ "report:json": "node tools/governance-report.js . --format=json",
30
+ "report:md": "node tools/governance-report.js . --format=md",
31
+ "install-auto": "node install.js",
32
+ "install-dry-run": "node install.js --dry-run",
33
+ "install-list": "node install.js --list",
34
+ "install-doctor": "node install.js --doctor",
35
+ "test": "node tools/tests/run-tests.js",
36
+ "ci": "npm run lint && npm run check-secrets && npm run test && node install.js --doctor"
37
+ },
38
+ "engines": {
39
+ "node": ">=18"
40
+ },
41
+ "keywords": [
42
+ "ai",
43
+ "governance",
44
+ "claude",
45
+ "codex",
46
+ "cursor",
47
+ "windsurf",
48
+ "code-review",
49
+ "linter",
50
+ "secrets",
51
+ "ci",
52
+ "hooks"
53
+ ],
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/Kodaxadev/Code-Warden.git"
57
+ },
58
+ "homepage": "https://github.com/Kodaxadev/Code-Warden",
59
+ "bugs": "https://github.com/Kodaxadev/Code-Warden/issues",
60
+ "author": "Justin Davis",
61
+ "license": "MIT"
62
+ }
@@ -1,66 +1,83 @@
1
- # Code-Warden Quality Gate — Project Template
2
- # https://github.com/Kodaxadev/Code-Warden
3
- #
4
- # Copy this file to .github/workflows/code-warden.yml in your project.
5
- #
6
- # What it enforces:
7
- # - File length limits (warden-lint.js, default 400 lines per codewarden.json)
8
- # - Zero-trust secrets (verify-secrets.js, hardcoded-credential patterns)
9
- #
10
- # How code-warden is made available in CI (choose one):
11
- #
12
- # Option A Download from release (recommended, no files to commit)
13
- # Set CODE_WARDEN_VERSION below to pin a specific release.
14
- # The "Install Code-Warden" step downloads and extracts automatically.
15
- #
16
- # Option B — Commit to your repo
17
- # Run: node /path/to/code-warden/install.js --target=claude
18
- # Add .claude/skills/code-warden/ to git tracking.
19
- # Set CODE_WARDEN_PATH: .claude/skills/code-warden
20
- # Remove the "Install Code-Warden" step.
21
- #
22
- # Customise thresholds in codewarden.json after install:
23
- # max_file_length (default 400 lines)
24
- # pre_flight_trigger_lines (default 150 lines)
25
- # human_checkpoint_files (default 2 files)
26
-
27
- name: Code-Warden Quality Gate
28
-
29
- on:
30
- push:
31
- branches: [main, master]
32
- pull_request:
33
- branches: [main, master]
34
-
35
- env:
36
- CODE_WARDEN_VERSION: v3.1.0
37
- CODE_WARDEN_PATH: .code-warden-ci
38
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
39
-
40
- jobs:
41
- code-warden:
42
- name: Code-Warden Quality Gate
43
- runs-on: ubuntu-latest
44
-
45
- steps:
46
- - name: Checkout
47
- uses: actions/checkout@v4
48
-
49
- - name: Setup Node.js
50
- uses: actions/setup-node@v4
51
- with:
52
- node-version: '24'
53
-
54
- # Option A: download from GitHub release (remove if using Option B)
55
- - name: Install Code-Warden
56
- run: |
57
- curl -fsSL -o cw.zip \
58
- "https://github.com/Kodaxadev/Code-Warden/releases/download/${{ env.CODE_WARDEN_VERSION }}/code-warden-${{ env.CODE_WARDEN_VERSION }}.zip"
59
- mkdir -p ${{ env.CODE_WARDEN_PATH }}
60
- unzip -q cw.zip -d ${{ env.CODE_WARDEN_PATH }}
61
-
62
- - name: Lint — enforce file length limits
63
- run: node ${{ env.CODE_WARDEN_PATH }}/tools/warden-lint.js .
64
-
65
- - name: Secrets zero-trust scan
66
- run: node ${{ env.CODE_WARDEN_PATH }}/tools/verify-secrets.js .
1
+ # Code-Warden Quality Gate — Project Template
2
+ # https://github.com/Kodaxadev/Code-Warden
3
+ #
4
+ # Copy this file to .github/workflows/code-warden.yml in your project.
5
+ #
6
+ # What it enforces:
7
+ # - File length limits (default 400 lines per codewarden.json)
8
+ # - Zero-trust secrets (hardcoded-credential patterns)
9
+ # - Behavioral tests (scanner and hook pass/fail verification)
10
+ # - Source integrity (required files present)
11
+ #
12
+ # What it produces:
13
+ # - .code-warden-report.json machine-readable governance artifact
14
+ # - Markdown summary on the workflow run / PR (via GITHUB_STEP_SUMMARY)
15
+ # - Uploaded artifact for audit trail (90-day retention)
16
+ #
17
+ # How code-warden is made available in CI (choose one):
18
+ #
19
+ # Option A — Download from release (recommended, no files to commit)
20
+ # Set CODE_WARDEN_VERSION below to pin a specific release.
21
+ # The "Install Code-Warden" step downloads and extracts automatically.
22
+ #
23
+ # Option B — Commit to your repo
24
+ # Run: node /path/to/code-warden/install.js --target=claude
25
+ # Add .claude/skills/code-warden/ to git tracking.
26
+ # Set CODE_WARDEN_PATH: .claude/skills/code-warden
27
+ # Remove the "Install Code-Warden" step.
28
+ #
29
+ # Customise thresholds in codewarden.json after install:
30
+ # max_file_length (default 400 lines)
31
+ # pre_flight_trigger_lines (default 150 lines)
32
+ # human_checkpoint_files (default 2 files)
33
+
34
+ name: Code-Warden Quality Gate
35
+
36
+ on:
37
+ push:
38
+ branches: [main, master]
39
+ pull_request:
40
+ branches: [main, master]
41
+
42
+ env:
43
+ CODE_WARDEN_VERSION: v3.2.0
44
+ CODE_WARDEN_PATH: .code-warden-ci
45
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
46
+
47
+ jobs:
48
+ code-warden:
49
+ name: Code-Warden Quality Gate
50
+ runs-on: ubuntu-latest
51
+
52
+ steps:
53
+ - name: Checkout
54
+ uses: actions/checkout@v4
55
+
56
+ - name: Setup Node.js
57
+ uses: actions/setup-node@v4
58
+ with:
59
+ node-version: '24'
60
+
61
+ # Option A: download from GitHub release (remove if using Option B)
62
+ - name: Install Code-Warden
63
+ run: |
64
+ curl -fsSL -o cw.zip \
65
+ "https://github.com/Kodaxadev/Code-Warden/releases/download/${{ env.CODE_WARDEN_VERSION }}/code-warden-${{ env.CODE_WARDEN_VERSION }}.zip"
66
+ mkdir -p ${{ env.CODE_WARDEN_PATH }}
67
+ unzip -q cw.zip -d ${{ env.CODE_WARDEN_PATH }}
68
+
69
+ - name: Governance report
70
+ run: node ${{ env.CODE_WARDEN_PATH }}/tools/governance-report.js .
71
+
72
+ - name: Publish governance summary
73
+ if: always()
74
+ run: node ${{ env.CODE_WARDEN_PATH }}/tools/governance-report.js . --format=md >> $GITHUB_STEP_SUMMARY
75
+
76
+ - name: Upload governance artifact
77
+ if: always()
78
+ uses: actions/upload-artifact@v4
79
+ with:
80
+ name: code-warden-report
81
+ path: .code-warden-report.json
82
+ if-no-files-found: ignore
83
+ retention-days: 90
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const { spawnSync } = require('child_process');
8
+ const { countLines } = require('./lib/line-count');
9
+ const { collectFiles } = require('./lib/file-collection');
10
+ const { scanForSecrets } = require('./lib/secret-patterns');
11
+ const { loadConfig } = require('./lib/config');
12
+
13
+ const ROOT = path.join(__dirname, '..');
14
+ const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
15
+ const VERSION = PKG.version;
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // CLI
19
+ // ---------------------------------------------------------------------------
20
+
21
+ function parseArgs(argv) {
22
+ const args = argv.slice(2);
23
+ const formatArg = args.find(a => a.startsWith('--format='));
24
+ const format = formatArg ? formatArg.split('=')[1] : null;
25
+ const scanPath = args.find(a => !a.startsWith('--')) || '.';
26
+ return { format, scanPath };
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Git metadata
31
+ // ---------------------------------------------------------------------------
32
+
33
+ function gitInfo() {
34
+ const run = (gitArgs) => {
35
+ const r = spawnSync('git', gitArgs, { encoding: 'utf8', timeout: 5000 });
36
+ return r.status === 0 ? r.stdout.trim() : null;
37
+ };
38
+ return {
39
+ branch: run(['rev-parse', '--abbrev-ref', 'HEAD']),
40
+ commit: run(['rev-parse', '--short', 'HEAD']),
41
+ };
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // File length + secrets (single pass over all files)
46
+ // ---------------------------------------------------------------------------
47
+
48
+ function runScans(scanPath) {
49
+ const { maxFileLength } = loadConfig();
50
+ const resolved = path.resolve(scanPath);
51
+
52
+ if (!fs.existsSync(resolved)) {
53
+ console.error(`[CodeWarden] Error: scan path not found: ${scanPath}`);
54
+ process.exit(1);
55
+ }
56
+
57
+ const files = [];
58
+ if (fs.statSync(resolved).isDirectory()) {
59
+ collectFiles(resolved, files);
60
+ } else {
61
+ files.push(resolved);
62
+ }
63
+
64
+ const lengthViolations = [];
65
+ const secretViolations = [];
66
+
67
+ for (const f of files) {
68
+ let content;
69
+ try { content = fs.readFileSync(f, 'utf8'); } catch { continue; }
70
+
71
+ const rel = path.relative(resolved, f);
72
+
73
+ const lineCount = countLines(content);
74
+ if (lineCount > maxFileLength) {
75
+ lengthViolations.push({ file: rel, lines: lineCount, limit: maxFileLength });
76
+ }
77
+
78
+ const hit = scanForSecrets(content);
79
+ if (hit) {
80
+ secretViolations.push({ file: rel, pattern: hit.label });
81
+ }
82
+ }
83
+
84
+ return {
85
+ fileLength: {
86
+ status: lengthViolations.length === 0 ? 'pass' : 'fail',
87
+ filesScanned: files.length,
88
+ violations: lengthViolations.length,
89
+ details: lengthViolations.length > 0 ? lengthViolations : undefined,
90
+ },
91
+ secrets: {
92
+ status: secretViolations.length === 0 ? 'pass' : 'fail',
93
+ filesScanned: files.length,
94
+ violations: secretViolations.length,
95
+ details: secretViolations.length > 0 ? secretViolations : undefined,
96
+ },
97
+ };
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Behavioral tests
102
+ // ---------------------------------------------------------------------------
103
+
104
+ function checkTests() {
105
+ const testScript = path.join(__dirname, 'tests', 'run-tests.js');
106
+ if (!fs.existsSync(testScript)) {
107
+ return { status: 'skip', tests: 0, failures: 0 };
108
+ }
109
+
110
+ const r = spawnSync(process.execPath, [testScript], {
111
+ encoding: 'utf8',
112
+ timeout: 30000,
113
+ cwd: ROOT,
114
+ });
115
+
116
+ const out = (r.stdout || '') + (r.stderr || '');
117
+ const passMatch = out.match(/pass\s+(\d+)/);
118
+ const failMatch = out.match(/fail\s+(\d+)/);
119
+
120
+ let passed, failed;
121
+ if (passMatch || failMatch) {
122
+ passed = parseInt(passMatch?.[1] || '0', 10);
123
+ failed = parseInt(failMatch?.[1] || '0', 10);
124
+ } else {
125
+ passed = (out.match(/^(?:ok \d+|✔)/gm) || []).length;
126
+ failed = (out.match(/^(?:not ok \d+|✖)/gm) || []).length;
127
+ }
128
+
129
+ return {
130
+ status: r.status === 0 ? 'pass' : 'fail',
131
+ tests: passed + failed,
132
+ failures: failed,
133
+ };
134
+ }
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // Source integrity
138
+ // ---------------------------------------------------------------------------
139
+
140
+ function checkInstallHealth() {
141
+ const required = [
142
+ 'SKILL.md',
143
+ 'references',
144
+ 'tools/warden-lint.js',
145
+ 'tools/verify-secrets.js',
146
+ 'tools/get-context.js',
147
+ ];
148
+ const missing = required.filter(f => !fs.existsSync(path.join(ROOT, f)));
149
+ return {
150
+ status: missing.length === 0 ? 'pass' : 'fail',
151
+ missing: missing.length > 0 ? missing : undefined,
152
+ };
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // Runtime hook detection
157
+ // ---------------------------------------------------------------------------
158
+
159
+ function checkRuntimeHooks() {
160
+ const home = os.homedir();
161
+ const result = {};
162
+
163
+ const claudeSettings = path.join(home, '.claude', 'settings.json');
164
+ if (fs.existsSync(claudeSettings)) {
165
+ try {
166
+ const s = JSON.parse(fs.readFileSync(claudeSettings, 'utf8'));
167
+ const hooks = (s?.hooks?.PreToolUse || [])
168
+ .flatMap(m => m.hooks || [])
169
+ .filter(h => String(h.description || '').startsWith('code-warden:'));
170
+ if (hooks.length > 0) {
171
+ const valid = hooks.every(h => h.args?.[0] && fs.existsSync(h.args[0]));
172
+ result.claude = valid ? 'registered' : 'registered_broken';
173
+ } else {
174
+ result.claude = 'not_registered';
175
+ }
176
+ } catch { result.claude = 'error'; }
177
+ } else {
178
+ result.claude = 'not_configured';
179
+ }
180
+
181
+ const codexHooksPath = path.join(home, '.codex', 'hooks.json');
182
+ if (fs.existsSync(codexHooksPath)) {
183
+ try {
184
+ const h = JSON.parse(fs.readFileSync(codexHooksPath, 'utf8'));
185
+ const cw = (h?.PreToolUse || [])
186
+ .filter(e => String(e.description || '').startsWith('code-warden:'));
187
+ if (cw.length > 0) {
188
+ const valid = cw.every(e => e.args?.[0] && fs.existsSync(e.args[0]));
189
+ result.codex = valid ? 'registered' : 'registered_broken';
190
+ } else {
191
+ result.codex = 'not_registered';
192
+ }
193
+ } catch { result.codex = 'error'; }
194
+ } else {
195
+ result.codex = 'not_configured';
196
+ }
197
+
198
+ return result;
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Report assembly
203
+ // ---------------------------------------------------------------------------
204
+
205
+ function generateReport(scanPath) {
206
+ const repo = gitInfo();
207
+ const { fileLength, secrets } = runScans(scanPath);
208
+ const behavioralTests = checkTests();
209
+ const installHealth = checkInstallHealth();
210
+ const runtimeHooks = checkRuntimeHooks();
211
+
212
+ const checks = { fileLength, secrets, behavioralTests, installHealth };
213
+ const result = Object.values(checks).every(c => c.status === 'pass' || c.status === 'skip')
214
+ ? 'pass' : 'fail';
215
+
216
+ return {
217
+ tool: 'code-warden',
218
+ version: VERSION,
219
+ timestamp: new Date().toISOString(),
220
+ repository: { branch: repo.branch, commit: repo.commit },
221
+ checks,
222
+ governance: {
223
+ scopeGate: 'session_only',
224
+ planGate: 'session_only',
225
+ runtimeHooks,
226
+ },
227
+ result,
228
+ };
229
+ }
230
+
231
+ // ---------------------------------------------------------------------------
232
+ // Markdown formatter
233
+ // ---------------------------------------------------------------------------
234
+
235
+ function formatMarkdown(report) {
236
+ const badge = s => s === 'pass' ? 'PASS' : s === 'skip' ? 'SKIP' : 'FAIL';
237
+ const hookLabel = (id) => {
238
+ const s = report.governance.runtimeHooks[id];
239
+ if (s === 'registered') return 'verified';
240
+ if (s === 'registered_broken') return 'broken';
241
+ if (s === 'not_registered') return 'none';
242
+ return 'n/a';
243
+ };
244
+
245
+ const healthDetail = report.checks.installHealth.missing
246
+ ? 'Missing: ' + report.checks.installHealth.missing.join(', ')
247
+ : 'All source files present';
248
+
249
+ const lines = [
250
+ '## Code-Warden Governance Report',
251
+ '',
252
+ '| Check | Result | Details |',
253
+ '|-------|--------|---------|',
254
+ `| File length | ${badge(report.checks.fileLength.status)} | ${report.checks.fileLength.filesScanned} files scanned, ${report.checks.fileLength.violations} violations |`,
255
+ `| Hardcoded credentials | ${badge(report.checks.secrets.status)} | ${report.checks.secrets.filesScanned} files scanned, ${report.checks.secrets.violations} violations |`,
256
+ `| Behavioral tests | ${badge(report.checks.behavioralTests.status)} | ${report.checks.behavioralTests.tests} tests, ${report.checks.behavioralTests.failures} failures |`,
257
+ `| Install health | ${badge(report.checks.installHealth.status)} | ${healthDetail} |`,
258
+ `| Runtime hooks | — | Claude: ${hookLabel('claude')} / Codex: ${hookLabel('codex')} |`,
259
+ '',
260
+ `**Result:** ${report.result === 'pass' ? 'All governed checks passed.' : 'One or more checks failed.'}`,
261
+ '',
262
+ `> Generated by Code-Warden v${report.version} at ${report.timestamp}`,
263
+ ];
264
+
265
+ return lines.join('\n');
266
+ }
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // One-line summary (default mode stdout)
270
+ // ---------------------------------------------------------------------------
271
+
272
+ function formatSummary(report) {
273
+ const c = report.checks;
274
+ const parts = [
275
+ `lint:${c.fileLength.status}`,
276
+ `secrets:${c.secrets.status}`,
277
+ `tests:${c.behavioralTests.status}`,
278
+ `health:${c.installHealth.status}`,
279
+ ];
280
+ return `[CodeWarden] Governance report: ${report.result.toUpperCase()} (${parts.join(', ')})`;
281
+ }
282
+
283
+ // ---------------------------------------------------------------------------
284
+ // Main
285
+ // ---------------------------------------------------------------------------
286
+
287
+ const { format, scanPath } = parseArgs(process.argv);
288
+ const report = generateReport(scanPath);
289
+
290
+ if (format === 'md') {
291
+ console.log(formatMarkdown(report));
292
+ } else if (format === 'json') {
293
+ console.log(JSON.stringify(report, null, 2));
294
+ } else {
295
+ const json = JSON.stringify(report, null, 2);
296
+ const outPath = path.resolve('.code-warden-report.json');
297
+ fs.writeFileSync(outPath, json, 'utf8');
298
+ console.log(formatSummary(report));
299
+ console.log(`[CodeWarden] Report written to ${outPath}`);
300
+ }
301
+
302
+ process.exit(report.result === 'pass' ? 0 : 1);
@@ -1,72 +1,75 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * file-collection.js
6
- * Shared file traversal helpers for warden-lint and verify-secrets CLI tools.
7
- *
8
- * Previously each CLI tool duplicated identical SKIP_DIRS, SKIP_EXTS,
9
- * collectFiles, and expandPaths logic — any change had to be made twice.
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
-
15
- const SKIP_DIRS = new Set(['node_modules', '.git', 'target', 'dist', '.next']);
16
-
17
- const SKIP_EXTS = new Set([
18
- '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.webp',
19
- '.zip', '.tar', '.gz', '.7z', '.rar',
20
- '.dll', '.exe', '.bin', '.so', '.dylib',
21
- '.pdf', '.woff', '.woff2', '.ttf', '.eot', '.otf',
22
- '.mp4', '.mp3', '.wav', '.ogg', '.avi', '.mov',
23
- '.map', '.lock',
24
- ]);
25
-
26
- /**
27
- * Recursively collect all scannable files under a directory.
28
- *
29
- * @param {string} dir
30
- * @param {string[]} results - accumulator (mutated)
31
- */
32
- function collectFiles(dir, results) {
33
- for (const entry of fs.readdirSync(dir)) {
34
- if (SKIP_DIRS.has(entry)) continue;
35
- const full = path.join(dir, entry);
36
- const stat = fs.statSync(full);
37
- if (stat.isDirectory()) {
38
- collectFiles(full, results);
39
- } else if (!SKIP_EXTS.has(path.extname(entry).toLowerCase())) {
40
- results.push(full);
41
- }
42
- }
43
- }
44
-
45
- /**
46
- * Expand a list of CLI path arguments into a flat array of file paths.
47
- * Directories are walked recursively; individual files are included as-is.
48
- * Exits with usage error if no args provided or a path is not found.
49
- *
50
- * @param {string[]} args - process.argv slice
51
- * @param {string} toolName - used in the usage message
52
- * @returns {string[]}
53
- */
54
- function expandPaths(args, toolName) {
55
- if (args.length === 0) {
56
- console.log(`Usage: ${toolName} <file|dir> [file|dir] ...`);
57
- console.log(` node tools/${toolName} . # scan entire project`);
58
- process.exit(1);
59
- }
60
- const files = [];
61
- for (const arg of args) {
62
- if (!fs.existsSync(arg)) {
63
- console.error(`Error: path not found: ${arg}`);
64
- continue;
65
- }
66
- if (fs.statSync(arg).isDirectory()) collectFiles(arg, files);
67
- else files.push(arg);
68
- }
69
- return files;
70
- }
71
-
72
- module.exports = { collectFiles, expandPaths, SKIP_DIRS, SKIP_EXTS };
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * file-collection.js
6
+ * Shared file traversal helpers for warden-lint and verify-secrets CLI tools.
7
+ *
8
+ * Previously each CLI tool duplicated identical SKIP_DIRS, SKIP_EXTS,
9
+ * collectFiles, and expandPaths logic — any change had to be made twice.
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'target', 'dist', '.next']);
16
+
17
+ const SKIP_EXTS = new Set([
18
+ '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.webp',
19
+ '.zip', '.tar', '.gz', '.7z', '.rar',
20
+ '.dll', '.exe', '.bin', '.so', '.dylib',
21
+ '.pdf', '.woff', '.woff2', '.ttf', '.eot', '.otf',
22
+ '.mp4', '.mp3', '.wav', '.ogg', '.avi', '.mov',
23
+ '.map', '.lock',
24
+ ]);
25
+
26
+ /**
27
+ * Recursively collect all scannable files under a directory.
28
+ *
29
+ * @param {string} dir
30
+ * @param {string[]} results - accumulator (mutated)
31
+ */
32
+ function collectFiles(dir, results) {
33
+ let entries;
34
+ try { entries = fs.readdirSync(dir); } catch { return; }
35
+ for (const entry of entries) {
36
+ if (SKIP_DIRS.has(entry)) continue;
37
+ const full = path.join(dir, entry);
38
+ let stat;
39
+ try { stat = fs.statSync(full); } catch { continue; }
40
+ if (stat.isDirectory()) {
41
+ collectFiles(full, results);
42
+ } else if (!SKIP_EXTS.has(path.extname(entry).toLowerCase())) {
43
+ results.push(full);
44
+ }
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Expand a list of CLI path arguments into a flat array of file paths.
50
+ * Directories are walked recursively; individual files are included as-is.
51
+ * Exits with usage error if no args provided or a path is not found.
52
+ *
53
+ * @param {string[]} args - process.argv slice
54
+ * @param {string} toolName - used in the usage message
55
+ * @returns {string[]}
56
+ */
57
+ function expandPaths(args, toolName) {
58
+ if (args.length === 0) {
59
+ console.log(`Usage: ${toolName} <file|dir> [file|dir] ...`);
60
+ console.log(` node tools/${toolName} . # scan entire project`);
61
+ process.exit(1);
62
+ }
63
+ const files = [];
64
+ for (const arg of args) {
65
+ if (!fs.existsSync(arg)) {
66
+ console.error(`Error: path not found: ${arg}`);
67
+ continue;
68
+ }
69
+ if (fs.statSync(arg).isDirectory()) collectFiles(arg, files);
70
+ else files.push(arg);
71
+ }
72
+ return files;
73
+ }
74
+
75
+ module.exports = { collectFiles, expandPaths, SKIP_DIRS, SKIP_EXTS };