@softspark/ai-toolkit 2.5.0 → 2.6.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/AGENTS.md +1 -1
- package/CHANGELOG.md +34 -0
- package/README.md +19 -3
- package/app/.claude-plugin/plugin.json +3 -2
- package/app/ARCHITECTURE.md +11 -0
- package/app/skills/hipaa-validate/SKILL.md +39 -23
- package/app/skills/hipaa-validate/scripts/hipaa_scan.py +64 -7
- package/bin/ai-toolkit.js +20 -5
- package/kb/reference/opencode-compatibility.md +161 -0
- package/llms-full.txt +168 -1
- package/llms.txt +1 -0
- package/manifest.json +1 -1
- package/package.json +6 -3
- package/scripts/generate_opencode.py +117 -0
- package/scripts/generate_opencode_agents.py +126 -0
- package/scripts/generate_opencode_commands.py +158 -0
- package/scripts/generate_opencode_json.py +133 -0
- package/scripts/generate_opencode_plugin.py +169 -0
- package/scripts/install_steps/ai_tools.py +117 -1
- package/scripts/install_steps/install_state.py +1 -1
package/AGENTS.md
CHANGED
|
@@ -86,7 +86,7 @@ Legacy code investigation and understanding specialist. Trigger words: legacy co
|
|
|
86
86
|
|
|
87
87
|
Code review and security audit expert. Use for security reviews, Devil's Advocate analysis, quality audits, best practices validation. Triggers: review, security, audit, quality, best practices, vulnerability.
|
|
88
88
|
|
|
89
|
-
**Tools:** `Read, Edit`
|
|
89
|
+
**Tools:** `Read, Edit, Grep, Glob`
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,40 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v2.6.1 — HIPAA Scanner Precision (2026-04-17)
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **`hipaa-validate` Cat 3 regexes tightened to cut false positives** — `CERT_NONE` → `ssl\.CERT_NONE` (module-anchored, no longer matches constants like `USE_CERT_NONE_MODE`); `ssl\s*=\s*False` → `[,(]\s*ssl\s*=\s*False\b` (argument-position anchored, no longer matches feature flags like `is_ssl_enabled = False`).
|
|
14
|
+
- **`SECURE_SSL_REDIRECT = False` severity HIGH → WARN** — commonly `False` in dev/local Django settings; compliance reviewer must confirm production config.
|
|
15
|
+
- **Cat 1 Python logger patterns merged** — three overlapping regexes (`logging.*`, `logger.*`, `pprint.*`) collapsed into one `(logging|logger|pprint)\.\w+\(...` pattern; eliminates duplicate findings on the same line.
|
|
16
|
+
- **SQLAlchemy session data-op regexes word-anchored** in Cat 2 and Cat 5 (`\bsession.(query|add|execute|delete|merge)\b`) — prevents false matches on unrelated identifiers.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **`LANG_EXTENSIONS` file-extension filter** in `scripts/hipaa_scan.py` — language-tagged patterns now fire only when the file's extension matches the tag. Eliminates cross-language double-flagging (e.g. Python `logger.\w+` regex no longer triggers on `.java` files in mixed projects).
|
|
20
|
+
- **`tests/test_hipaa_scan.bats`** — 11 fixture-driven tests covering Python positive/negative cases for the new patterns and the cross-language isolation guarantee. Test count: 647 → 658.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## v2.6.0 — opencode Integration (2026-04-15)
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **opencode editor support** — `ai-toolkit install --editors opencode` generates a full native integration: `AGENTS.md` (shared with Codex via distinct marker sections), `.opencode/agents/ai-toolkit-*.md` (44 subagents), `.opencode/commands/ai-toolkit-*.md` (62 slash commands), `.opencode/plugins/ai-toolkit-hooks.js` (JS plugin bridging Bash hooks to opencode lifecycle events), and `opencode.json` with MCP servers merged from `.mcp.json`.
|
|
28
|
+
- **Global opencode configs** — `ai-toolkit install --editors opencode` (without `--local`) installs the **complete surface** to `~/.config/opencode/{AGENTS.md, agents/, commands/, plugins/ai-toolkit-hooks.js, opencode.json}`. Tracked in `state.json`, auto-refreshed on `update`. (Earlier pre-release iteration shipped only AGENTS.md/agents/commands globally; plugin and opencode.json are now also installed globally so hooks and MCP work without `--local`.)
|
|
29
|
+
- **Expanded hook coverage** — plugin now bridges `session.compacted` → `pre-compact.sh` + `pre-compact-save.sh`, `permission.asked` → `guard-destructive.sh`, and `command.executed` → `post-tool-use.sh` on top of the original session/tool/message events. Maximises opencode lifecycle coverage without introducing new Bash hooks.
|
|
30
|
+
- **Auto-detection** — projects with `opencode.json` or `.opencode/` are detected by `ai-toolkit install --local` (no explicit `--editors` needed).
|
|
31
|
+
- **MCP translation** — `generate_opencode_json.py` translates Claude-style `.mcp.json` into opencode's `mcp` shape (local `command` + args flattened, remote `url` + headers preserved, `enabled: true` default). User-authored keys in `opencode.json` are preserved; re-runs are idempotent.
|
|
32
|
+
- **New CLI commands** — `ai-toolkit opencode-md`, `opencode-agents`, `opencode-commands`, `opencode-plugin`, `opencode-json`. Included in `generate-all`.
|
|
33
|
+
- **Skill reuse** — opencode reuses the Codex skill adapter (`codex_skill_adapter.py`) for Claude-only orchestration primitives since both lack `Agent`/`TeamCreate`/`TaskCreate` — no duplication.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- README `What's New`, package.json description, manifest.json, and `app/.claude-plugin/plugin.json` all list opencode as a supported editor.
|
|
37
|
+
- **Plugin exports** — `.opencode/plugins/ai-toolkit-hooks.js` now uses a single named export (`AiToolkitHooks`) per opencode spec. The prior redundant `export default` was removed.
|
|
38
|
+
- **Agent frontmatter** — `model` field and source-model-hint comment are no longer emitted because opencode requires `provider/model-id` format, which ai-toolkit does not store. opencode falls back to the user's configured default model.
|
|
39
|
+
- **Global install layout fix** — global generators now write directly under `~/.config/opencode/{agents,commands,plugins}/` instead of the incorrect `~/.config/opencode/.opencode/…` nesting. Generators accept a `config_root` parameter to distinguish project vs global layout.
|
|
40
|
+
- **Test suite** — grew from 641 to 647 tests covering new event bridges, named-export invariant, missing-source-model-hint regression, and global-layout contract.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
10
44
|
## v2.5.0 — Community Skills, Automated Scanners & Design Craft (2026-04-15)
|
|
11
45
|
|
|
12
46
|
### Added
|
package/README.md
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
# ai-toolkit
|
|
2
2
|
|
|
3
|
-
> Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 94 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo Code, Aider, Augment, Google Antigravity,
|
|
3
|
+
> Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 94 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo Code, Aider, Augment, Google Antigravity, Codex CLI, and opencode, ready in 60 seconds.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/softspark/ai-toolkit/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[](tests/)
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## What's New in v2.6.1
|
|
14
|
+
|
|
15
|
+
- **HIPAA scanner precision** — Cat 3 TLS regexes anchored (`ssl.CERT_NONE`, `[,(] ssl=False`) to eliminate false positives on feature flags and constant names
|
|
16
|
+
- **Cross-language isolation** — new `LANG_EXTENSIONS` filter ensures Python regexes don't match `.java` files in mixed projects, and vice versa
|
|
17
|
+
- **`SECURE_SSL_REDIRECT = False` HIGH → WARN** — commonly False in dev settings; reviewer confirms prod config instead of flooding every Django project with HIGH findings
|
|
18
|
+
- **11 new bats tests** (`tests/test_hipaa_scan.bats`) — fixture-driven positive/negative cases covering the Python pattern expansion and isolation guarantee. Test count: 647 → 658
|
|
19
|
+
|
|
20
|
+
## What's New in v2.6.0
|
|
21
|
+
|
|
22
|
+
- **opencode integration** — `ai-toolkit install --editors opencode` generates `AGENTS.md`, `.opencode/agents/`, `.opencode/commands/`, plus a JS plugin bridging our Bash hooks to opencode's lifecycle events
|
|
23
|
+
- **opencode MCP merge** — `.mcp.json` servers are translated into `opencode.json` under the `mcp` key, preserving user-authored entries
|
|
24
|
+
- **Global opencode configs** — `~/.config/opencode/{AGENTS.md,agents/,commands/}` managed from `state.json`, auto-refreshed on `update`
|
|
25
|
+
- **Shared AGENTS.md** — opencode and Codex CLI both read the same `AGENTS.md` via distinct marker sections; installing both does not clobber either
|
|
26
|
+
|
|
13
27
|
## What's New in v2.5.0
|
|
14
28
|
|
|
15
29
|
- **`/seo-validate`** — 9-category SEO scanner with automated `seo-scanner.py` script (community contribution)
|
|
@@ -107,6 +121,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
|
|
|
107
121
|
| Augment | `.augment/rules/ai-toolkit-*.md` | project |
|
|
108
122
|
| Google Antigravity | `.agent/rules/*.md` + `.agent/workflows/*.md` | project |
|
|
109
123
|
| Codex CLI | `AGENTS.md` + `.agents/rules/*.md` + `.agents/skills/*` + `.codex/hooks.json` | project + global plugin |
|
|
124
|
+
| opencode | `AGENTS.md` + `.opencode/{agents,commands,plugins}/*` + `opencode.json` | project + global (`~/.config/opencode/`) |
|
|
110
125
|
|
|
111
126
|
> Claude Code is always installed (primary platform). Other editors on demand with `--editors`. All platforms receive the same agent/skill catalog, guidelines, and registered custom rules.
|
|
112
127
|
|
|
@@ -143,7 +158,7 @@ ai-toolkit/
|
|
|
143
158
|
│ └── ARCHITECTURE.md # Full system design
|
|
144
159
|
├── kb/ # Reference docs, procedures, plans
|
|
145
160
|
├── scripts/ # Validation, install, evaluation scripts
|
|
146
|
-
├── tests/ # Bats test suite (
|
|
161
|
+
├── tests/ # Bats test suite (658 tests)
|
|
147
162
|
└── CHANGELOG.md
|
|
148
163
|
```
|
|
149
164
|
|
|
@@ -267,6 +282,7 @@ Need multi-agent coordination?
|
|
|
267
282
|
| Distribution Model | [kb/reference/distribution-model.md](kb/reference/distribution-model.md) |
|
|
268
283
|
| Ecosystem Comparison | [kb/reference/comparison.md](kb/reference/comparison.md) |
|
|
269
284
|
| Codex CLI Compatibility | [kb/reference/codex-cli-compatibility.md](kb/reference/codex-cli-compatibility.md) |
|
|
285
|
+
| opencode Compatibility | [kb/reference/opencode-compatibility.md](kb/reference/opencode-compatibility.md) |
|
|
270
286
|
| Maintenance SOP | [kb/procedures/maintenance-sop.md](kb/procedures/maintenance-sop.md) |
|
|
271
287
|
|
|
272
288
|
---
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-toolkit",
|
|
3
3
|
"description": "Professional-grade Claude Code toolkit with persona presets, skill security auditor, expanded lifecycle hooks, experimental opt-in plugin packs, benchmark harvesting, and multi-tool support.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.6.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SoftSpark",
|
|
7
7
|
"url": "https://github.com/softspark"
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"skills",
|
|
31
31
|
"agents",
|
|
32
32
|
"cursor",
|
|
33
|
-
"codex"
|
|
33
|
+
"codex",
|
|
34
|
+
"opencode"
|
|
34
35
|
],
|
|
35
36
|
"marketplace": {
|
|
36
37
|
"category": "developer-tools",
|
package/app/ARCHITECTURE.md
CHANGED
|
@@ -309,6 +309,17 @@ Lead Session (You)
|
|
|
309
309
|
|
|
310
310
|
Language rules are propagated to **all configured editors** — not just Claude. `dir_rules_shared.build_language_rules()` reads `app/rules/<lang>/*.md`, strips frontmatter, and returns combined content per language. Each directory-based generator (Cursor, Windsurf, Cline, Roo, Augment, Antigravity, Codex) emits `ai-toolkit-lang-<lang>` files in its native format. Registered custom rules (`~/.softspark/ai-toolkit/rules/`) are similarly propagated as `ai-toolkit-custom-<name>` files via `build_registered_rules()`.
|
|
311
311
|
|
|
312
|
+
### opencode Integration (v2.5.0+)
|
|
313
|
+
opencode is the 11th supported editor. Five generators handle its integration surface:
|
|
314
|
+
|
|
315
|
+
- `generate_opencode.py` → `AGENTS.md` body (shared with Codex; separate marker section prevents clobbering).
|
|
316
|
+
- `generate_opencode_agents.py` → `.opencode/agents/ai-toolkit-*.md` (one per ai-toolkit agent, `mode: subagent`).
|
|
317
|
+
- `generate_opencode_commands.py` → `.opencode/commands/ai-toolkit-*.md` (one per user-invocable skill, uses required `template: |` field; knowledge-only skills excluded).
|
|
318
|
+
- `generate_opencode_plugin.py` → `.opencode/plugins/ai-toolkit-hooks.js` (single named export `AiToolkitHooks`; bridges `session.created`, `session.compacted`, `session.deleted`, `message.updated/part.updated`, `tool.execute.before/after`, `permission.asked`, `command.executed` to the shared Bash hooks in `~/.softspark/ai-toolkit/hooks/` via Bun `$` — script path bound as a JS constant, payload on stdin, no shell interpolation).
|
|
319
|
+
- `generate_opencode_json.py` → `opencode.json` MCP merge (translates `.mcp.json` local/remote shapes; preserves user-authored keys; idempotent).
|
|
320
|
+
|
|
321
|
+
All five generators accept a `config_root` / `output_path` parameter so the same code path handles project-local (`./.opencode/…`) and global (`~/.config/opencode/…`, no `.opencode/` nesting) layouts. Skill adaptation for Claude-only orchestration primitives (`Agent`/`TeamCreate`/`TaskCreate`) is handled by reusing `codex_skill_adapter.py` — opencode, like Codex CLI, lacks these primitives, so the same translation layer applies (no duplication). Global install: `~/.config/opencode/{AGENTS.md,agents/,commands/,plugins/,opencode.json}`. Auto-detection markers: `opencode.json`, `.opencode/`, `.opencode/agents`, `.opencode/commands`.
|
|
322
|
+
|
|
312
323
|
### Extension API (`inject-hook`, `inject-rule`)
|
|
313
324
|
The `inject_section_cli.py` script provides a stable marker-based injection API. Any tool can add sections to `CLAUDE.md`, `constitution.md`, or `ARCHITECTURE.md` without overwriting user content, using `<!-- TOOLKIT:START:<id> -->` / `<!-- TOOLKIT:END:<id> -->` markers. `inject_hook_cli.py` injects hooks into `settings.json` with `_source` tags — supports both local files and HTTPS URLs (cached in `~/.softspark/ai-toolkit/hooks/external/`, auto-refreshed on `update`). Shared URL fetch logic lives in `url_fetch.py`.
|
|
314
325
|
|
|
@@ -100,7 +100,11 @@ Scan the full project for log/print statements that reference PHI keywords.
|
|
|
100
100
|
| `console\.\w+\(.*req\.body` | WARN | JS/TS | Raw request body may contain PHI |
|
|
101
101
|
| `JSON\.stringify\(.*patient` | WARN | JS/TS | Full patient object serialization |
|
|
102
102
|
| `print\(.*\b(patient\|ssn\|social.security)` | HIGH | Python | PHI in print statements |
|
|
103
|
-
| `logging\.\w+\(.*\b(patient\|ssn\|mrn\|dob)` | HIGH | Python | PHI
|
|
103
|
+
| `(logging\|logger\|pprint)\.\w+\(.*\b(patient\|ssn\|mrn\|dob)` | HIGH | Python | PHI in logger/named logger/pprint output |
|
|
104
|
+
| `print\(.*request\.(data\|json\|form\|POST\|body)` | WARN | Python | Raw request body may contain PHI (Django/Flask/FastAPI) |
|
|
105
|
+
| `(logging\|logger)\.\w+\(.*request\.(data\|json\|form\|POST\|body)` | WARN | Python | Raw request body in logger |
|
|
106
|
+
| `\brepr\(.*\b(patient\|ssn\|mrn)` | WARN | Python | repr() may expose PHI fields |
|
|
107
|
+
| `\bvars\(.*\b(patient\|ssn\|mrn)` | WARN | Python | vars() dumps all PHI fields |
|
|
104
108
|
| `fmt\.Print.*\b(patient\|ssn\|mrn)` | HIGH | Go | PHI in fmt output |
|
|
105
109
|
| `log\.\w+\(.*\b(patient\|ssn\|mrn)` | HIGH | Go/Any | PHI in log calls |
|
|
106
110
|
| `System\.out\.print.*\b(patient\|ssn\|mrn)` | HIGH | Java | PHI in stdout |
|
|
@@ -110,7 +114,7 @@ Scan the full project for log/print statements that reference PHI keywords.
|
|
|
110
114
|
| `Console\.Write.*\b(patient\|ssn\|mrn)` | HIGH | C# | PHI in Console output |
|
|
111
115
|
| `_logger\.\w+\(.*\b(patient\|ssn\|mrn\|dob)` | HIGH | C# | PHI in ILogger calls |
|
|
112
116
|
|
|
113
|
-
> **Language coverage note**: JS/TS patterns are the most comprehensive. Go, Ruby, and Java have baseline coverage for common log patterns. Contributions for additional language-specific patterns are welcome.
|
|
117
|
+
> **Language coverage note**: JS/TS and Python patterns are the most comprehensive. Go, Ruby, and Java have baseline coverage for common log patterns. Contributions for additional language-specific patterns are welcome.
|
|
114
118
|
|
|
115
119
|
**Minimum Necessary violations** (§164.502(b)):
|
|
116
120
|
|
|
@@ -135,7 +139,7 @@ Scan the full project for files that handle PHI data operations but lack audit-r
|
|
|
135
139
|
|
|
136
140
|
**PHI route file definition**: A file qualifies if it contains BOTH:
|
|
137
141
|
1. A healthcare keyword from Step 0 (`patient`, `diagnosis`, `medication`, etc.)
|
|
138
|
-
2. A data operation pattern: `router`, `app.get`, `app.post`, `app.put`, `app.delete`, `@RequestMapping`, `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, `Model.find`, `Model.save`, `Model.update`, `db.query`, `db.execute`, `cursor.execute`, `repository.`, `findBy`, `save(`, `delete(`
|
|
142
|
+
2. A data operation pattern: `router`, `app.get`, `app.post`, `app.put`, `app.delete`, `@RequestMapping`, `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, `Model.find`, `Model.save`, `Model.update`, `db.query`, `db.execute`, `cursor.execute`, `repository.`, `findBy`, `save(`, `delete(`, `@app.route`, `@blueprint.route`, `@api_view`, `ViewSet`, `APIView`, `\bsession.(query|add|execute|delete|merge)\b` (word-anchored — SQLAlchemy only, avoids matching Express `req.session.save`)
|
|
139
143
|
|
|
140
144
|
Files with a healthcare keyword but no data operation pattern are excluded.
|
|
141
145
|
|
|
@@ -158,13 +162,20 @@ See: [reference/hipaa-rules.md](reference/hipaa-rules.md) §164.312(b) for audit
|
|
|
158
162
|
|
|
159
163
|
*Context-gated: scans PHI-adjacent files only.*
|
|
160
164
|
|
|
161
|
-
| Pattern | Severity | Description |
|
|
162
|
-
|
|
163
|
-
| `http://` in API calls (not `localhost`/`127.0.0.1`) | HIGH | §164.312(e)(1) requires encryption in transit |
|
|
164
|
-
| Missing TLS/SSL config in database connections | HIGH | Database connections must be encrypted |
|
|
165
|
-
| `rejectUnauthorized:\s*false` | HIGH | TLS verification disabled |
|
|
166
|
-
| `ws://` (WebSocket without TLS) | WARN | Unencrypted WebSocket may carry PHI |
|
|
167
|
-
|
|
|
165
|
+
| Pattern | Severity | Language | Description |
|
|
166
|
+
|---------|----------|----------|-------------|
|
|
167
|
+
| `http://` in API calls (not `localhost`/`127.0.0.1`) | HIGH | Any | §164.312(e)(1) requires encryption in transit |
|
|
168
|
+
| Missing TLS/SSL config in database connections | HIGH | Any | Database connections must be encrypted |
|
|
169
|
+
| `rejectUnauthorized:\s*false` | HIGH | Any | TLS verification disabled |
|
|
170
|
+
| `ws://` (WebSocket without TLS) | WARN | Any | Unencrypted WebSocket may carry PHI |
|
|
171
|
+
| `verify\s*=\s*False` | HIGH | Python | TLS verification disabled (requests/httpx) |
|
|
172
|
+
| `InsecureRequestWarning` | WARN | Python | TLS warning suppressed |
|
|
173
|
+
| `[,(]\s*ssl\s*=\s*False\b` | HIGH | Python | SSL disabled in connector call (anchored to arg position to avoid matching `is_ssl_enabled = False`) |
|
|
174
|
+
| `ssl\.CERT_NONE` | HIGH | Python | TLS certificate verification disabled (anchored to `ssl.` module) |
|
|
175
|
+
| `check_hostname\s*=\s*False` | HIGH | Python | TLS hostname verification disabled |
|
|
176
|
+
| `urllib3\.disable_warnings` | WARN | Python | TLS warnings suppressed (urllib3) |
|
|
177
|
+
| `SECURE_SSL_REDIRECT\s*=\s*False` | WARN | Python | Django HTTPS redirect disabled (commonly False in dev settings — verify production config) |
|
|
178
|
+
| `NODE_TLS_REJECT_UNAUTHORIZED.*0` | HIGH | JS/TS | TLS rejection disabled globally |
|
|
168
179
|
|
|
169
180
|
See: [reference/hipaa-rules.md](reference/hipaa-rules.md) §164.312(e)(1) for transmission security requirements.
|
|
170
181
|
|
|
@@ -194,13 +205,16 @@ See: [reference/phi-identifiers.md](reference/phi-identifiers.md) for the full l
|
|
|
194
205
|
|
|
195
206
|
*Context-gated: scans PHI-adjacent files only. Heuristic — flags potential gaps.*
|
|
196
207
|
|
|
197
|
-
**Auth keywords** (co-occurrence check): `auth`, `authenticate`, `requireAuth`, `isAuthenticated`, `protect`, `guard`, `Authorize`, `login_required`, `Permission`
|
|
208
|
+
**Auth keywords** (co-occurrence check): `auth`, `authenticate`, `requireAuth`, `isAuthenticated`, `protect`, `guard`, `Authorize`, `login_required`, `Permission`, `permission_required`, `LoginRequiredMixin`, `PermissionRequiredMixin`, `IsAuthenticated`, `Depends`, `Security`
|
|
198
209
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
|
202
|
-
|
|
203
|
-
|
|
|
210
|
+
**Data operation patterns** (Python frameworks): `@app.route`, `@blueprint.route`, `@api_view`, `ViewSet`, `APIView`, `cursor.execute`, `\bsession.(query|execute)\b` (word-anchored — SQLAlchemy only)
|
|
211
|
+
|
|
212
|
+
| Pattern | Severity | Language | Description |
|
|
213
|
+
|---------|----------|----------|-------------|
|
|
214
|
+
| PHI route file without any auth keywords in same file | WARN | Any | POTENTIAL access control gap — verify auth middleware covers these routes (§164.312(d)) |
|
|
215
|
+
| `Access-Control-Allow-Origin:\s*\*` or `origin:\s*(true\|\*)` in PHI-adjacent files | HIGH | Any | Unrestricted cross-origin access to PHI endpoints |
|
|
216
|
+
| Routes marked `public`, `noAuth`, `anonymous` exposing PHI keywords | HIGH | Any | PHI must require authentication |
|
|
217
|
+
| `permission_classes\s*=.*AllowAny` | WARN | Python | DRF AllowAny — verify no PHI exposed |
|
|
204
218
|
|
|
205
219
|
> **Note**: Auth middleware is commonly applied at router-level or app-level. The co-occurrence heuristic checks the same file only. False positives expected when auth is configured globally. Use `.hipaaignore` to suppress.
|
|
206
220
|
|
|
@@ -253,13 +267,15 @@ Instead of per-finding rows, emit a single **BAA Verification Checklist** in the
|
|
|
253
267
|
|
|
254
268
|
Detects PHI storage patterns without encryption references. Per §164.312(a)(2)(iv), ePHI must be encrypted when stored.
|
|
255
269
|
|
|
256
|
-
| Pattern | Severity | Description |
|
|
257
|
-
|
|
258
|
-
| `encrypt\s*[:=]\s*false` in database config files | HIGH | §164.312(a)(2)(iv) — Encryption explicitly disabled |
|
|
259
|
-
| File write operations (`writeFile`, `fs.write`, `open(.*w`, `File.Create`) in PHI-adjacent code without encryption references | WARN | PHI written to disk may lack encryption at rest |
|
|
260
|
-
| Database connection without `ssl`, `encrypt`, or `tls` keywords in PHI-adjacent config | WARN | Database storing PHI should enforce encrypted connections |
|
|
261
|
-
| `localStorage.setItem` or `sessionStorage.setItem` with PHI keywords | HIGH | Browser storage is unencrypted — PHI must not be stored client-side without encryption |
|
|
262
|
-
| `SharedPreferences` or `UserDefaults` with PHI keywords | HIGH | Mobile local storage is unencrypted by default |
|
|
270
|
+
| Pattern | Severity | Language | Description |
|
|
271
|
+
|---------|----------|----------|-------------|
|
|
272
|
+
| `encrypt\s*[:=]\s*false` in database config files | HIGH | Any | §164.312(a)(2)(iv) — Encryption explicitly disabled |
|
|
273
|
+
| File write operations (`writeFile`, `fs.write`, `open(.*w`, `File.Create`) in PHI-adjacent code without encryption references | WARN | Any | PHI written to disk may lack encryption at rest |
|
|
274
|
+
| Database connection without `ssl`, `encrypt`, or `tls` keywords in PHI-adjacent config | WARN | Any | Database storing PHI should enforce encrypted connections |
|
|
275
|
+
| `localStorage.setItem` or `sessionStorage.setItem` with PHI keywords | HIGH | JS/TS | Browser storage is unencrypted — PHI must not be stored client-side without encryption |
|
|
276
|
+
| `SharedPreferences` or `UserDefaults` with PHI keywords | HIGH | Java/Any | Mobile local storage is unencrypted by default |
|
|
277
|
+
| `pickle\.(dump\|dumps)\(` with PHI keywords | HIGH | Python | pickle serialization is unencrypted — PHI must be encrypted at rest |
|
|
278
|
+
| `shelve\.open\(` with PHI keywords | HIGH | Python | shelve storage is unencrypted — PHI must be encrypted at rest |
|
|
263
279
|
|
|
264
280
|
See: [reference/hipaa-rules.md](reference/hipaa-rules.md) §164.312(a)(2)(iv) for encryption requirements.
|
|
265
281
|
|
|
@@ -62,6 +62,19 @@ LANGUAGE_INDICATORS = {
|
|
|
62
62
|
"*.csproj": "csharp",
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
# Map language tag to file extensions. Used by scan_patterns to avoid
|
|
66
|
+
# cross-language double-flagging when the same regex is valid for more
|
|
67
|
+
# than one language (e.g. `logger.info(...)` in Python vs Java).
|
|
68
|
+
LANG_EXTENSIONS = {
|
|
69
|
+
"js": {".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"},
|
|
70
|
+
"ts": {".ts", ".tsx"},
|
|
71
|
+
"python": {".py", ".pyi"},
|
|
72
|
+
"java": {".java", ".kt", ".scala"},
|
|
73
|
+
"go": {".go"},
|
|
74
|
+
"ruby": {".rb", ".erb"},
|
|
75
|
+
"csharp": {".cs"},
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
# ---------------------------------------------------------------------------
|
|
66
79
|
# Pattern definitions per category
|
|
67
80
|
# ---------------------------------------------------------------------------
|
|
@@ -72,9 +85,15 @@ CAT1_PATTERNS = [
|
|
|
72
85
|
(r"console\.log\(.*patient", "HIGH", "js", "Patient data in console.log", "1"),
|
|
73
86
|
(r"console\.\w+\(.*req\.body", "WARN", "js", "Raw request body may contain PHI", "1"),
|
|
74
87
|
(r"JSON\.stringify\(.*patient", "WARN", "js", "Full patient object serialization", "1"),
|
|
75
|
-
# Python
|
|
88
|
+
# Python (logger pattern covers logging.*, logger.*, and pprint.* in one regex
|
|
89
|
+
# to avoid overlapping findings and double-flag with the Java `logger.` pattern.
|
|
90
|
+
# Language is enforced by file extension via LANG_EXTENSIONS.)
|
|
76
91
|
(r"print\(.*\b(patient|ssn|social.security)", "HIGH", "python", "PHI in print statement", "1"),
|
|
77
|
-
(r"logging\.\w+\(.*\b(patient|ssn|mrn|dob)", "HIGH", "python", "PHI
|
|
92
|
+
(r"(logging|logger|pprint)\.\w+\(.*\b(patient|ssn|mrn|dob)", "HIGH", "python", "PHI in Python logger/pprint", "1"),
|
|
93
|
+
(r"print\(.*request\.(data|json|form|POST|body)", "WARN", "python", "Raw request body may contain PHI", "1"),
|
|
94
|
+
(r"(logging|logger)\.\w+\(.*request\.(data|json|form|POST|body)", "WARN", "python", "Raw request body in logger", "1"),
|
|
95
|
+
(r"\brepr\(.*\b(patient|ssn|mrn)", "WARN", "python", "repr() may expose PHI fields", "1"),
|
|
96
|
+
(r"\bvars\(.*\b(patient|ssn|mrn)", "WARN", "python", "vars() dumps all PHI fields", "1"),
|
|
78
97
|
# Go
|
|
79
98
|
(r"fmt\.Print.*\b(patient|ssn|mrn)", "HIGH", "go", "PHI in fmt output", "1"),
|
|
80
99
|
(r"log\.\w+\(.*\b(patient|ssn|mrn)", "HIGH", "go", "PHI in log call", "1"),
|
|
@@ -101,6 +120,14 @@ CAT3_PATTERNS = [
|
|
|
101
120
|
(r"NODE_TLS_REJECT_UNAUTHORIZED.*0", "HIGH", "js", "TLS rejection disabled globally", "3"),
|
|
102
121
|
(r"verify\s*=\s*False", "HIGH", "python", "TLS verification disabled (requests)", "3"),
|
|
103
122
|
(r"InsecureRequestWarning", "WARN", "python", "TLS warning suppressed", "3"),
|
|
123
|
+
# Anchored to argument position ([, or () to avoid matching `is_ssl_enabled = False`
|
|
124
|
+
(r"[,(]\s*ssl\s*=\s*False\b", "HIGH", "python", "SSL disabled in connector call", "3"),
|
|
125
|
+
(r"ssl\.CERT_NONE", "HIGH", "python", "TLS certificate verification disabled (ssl.CERT_NONE)", "3"),
|
|
126
|
+
(r"check_hostname\s*=\s*False", "HIGH", "python", "TLS hostname verification disabled", "3"),
|
|
127
|
+
(r"urllib3\.disable_warnings", "WARN", "python", "TLS warnings suppressed (urllib3)", "3"),
|
|
128
|
+
# WARN (not HIGH): commonly False in DEBUG/dev settings — compliance reviewer
|
|
129
|
+
# must confirm production config. Avoids flooding every Django project.
|
|
130
|
+
(r"SECURE_SSL_REDIRECT\s*=\s*False", "WARN", "python", "Django HTTPS redirect disabled (verify prod config)", "3"),
|
|
104
131
|
]
|
|
105
132
|
|
|
106
133
|
# Category 4: Hardcoded PHI/Test Data
|
|
@@ -119,6 +146,9 @@ CAT7_PATTERNS = [
|
|
|
119
146
|
(r"sessionStorage\.setItem\(.*\b(patient|ssn|mrn|phi|health)", "HIGH", "js", "PHI in session storage", "7"),
|
|
120
147
|
(r"SharedPreferences.*\b(patient|ssn|mrn|phi|health)", "HIGH", "java", "PHI in unencrypted mobile storage", "7"),
|
|
121
148
|
(r"UserDefaults.*\b(patient|ssn|mrn|phi|health)", "HIGH", "any", "PHI in unencrypted UserDefaults", "7"),
|
|
149
|
+
# Python
|
|
150
|
+
(r"pickle\.(dump|dumps)\(.*\b(patient|phi|health|ssn|mrn)", "HIGH", "python", "PHI in unencrypted pickle", "7"),
|
|
151
|
+
(r"shelve\.open\(.*\b(patient|phi|health|ssn|mrn)", "HIGH", "python", "PHI in unencrypted shelve storage", "7"),
|
|
122
152
|
]
|
|
123
153
|
|
|
124
154
|
# Category 8: PHI Temp File Exposure
|
|
@@ -132,12 +162,17 @@ CAT8_PATTERNS = [
|
|
|
132
162
|
CAT5_AUTH_KEYWORDS = [
|
|
133
163
|
"auth", "authenticate", "requireAuth", "isAuthenticated",
|
|
134
164
|
"protect", "guard", "Authorize", "login_required", "Permission",
|
|
165
|
+
# Python frameworks
|
|
166
|
+
"permission_required", "LoginRequiredMixin", "PermissionRequiredMixin",
|
|
167
|
+
"IsAuthenticated", "Depends", "Security",
|
|
135
168
|
]
|
|
136
169
|
|
|
137
170
|
CAT5_PATTERNS = [
|
|
138
171
|
(r"Access-Control-Allow-Origin:\s*\*", "HIGH", "any", "Unrestricted CORS on PHI endpoint", "5"),
|
|
139
172
|
(r"origin:\s*(true|\*)", "HIGH", "any", "Unrestricted CORS origin", "5"),
|
|
140
173
|
(r"\b(public|noAuth|anonymous)\b.*\b(patient|phi|health|medical)", "HIGH", "any", "Public route exposing PHI", "5"),
|
|
174
|
+
# Python
|
|
175
|
+
(r"permission_classes\s*=.*AllowAny", "WARN", "python", "DRF AllowAny — verify no PHI exposed", "5"),
|
|
141
176
|
]
|
|
142
177
|
|
|
143
178
|
# HIPAA rule citations per category
|
|
@@ -284,7 +319,15 @@ def detect_languages(root: Path) -> set[str]:
|
|
|
284
319
|
|
|
285
320
|
def scan_patterns(files: list[Path], patterns: list[tuple],
|
|
286
321
|
languages: set[str], root: Path) -> list[dict]:
|
|
287
|
-
"""Run regex patterns against files, return findings.
|
|
322
|
+
"""Run regex patterns against files, return findings.
|
|
323
|
+
|
|
324
|
+
Language-tagged patterns fire only when:
|
|
325
|
+
1. The project's detected languages include the tag, AND
|
|
326
|
+
2. The file's extension matches that language (per LANG_EXTENSIONS).
|
|
327
|
+
|
|
328
|
+
This prevents cross-language double-flagging when two languages share
|
|
329
|
+
a regex (e.g. `logger.info(patient)` valid in both Python and Java).
|
|
330
|
+
"""
|
|
288
331
|
findings = []
|
|
289
332
|
for fpath in files:
|
|
290
333
|
try:
|
|
@@ -292,11 +335,16 @@ def scan_patterns(files: list[Path], patterns: list[tuple],
|
|
|
292
335
|
except (OSError, PermissionError):
|
|
293
336
|
continue
|
|
294
337
|
rel = str(fpath.relative_to(root))
|
|
338
|
+
ext = fpath.suffix.lower()
|
|
295
339
|
|
|
296
340
|
for line_num, line in enumerate(lines, 1):
|
|
297
341
|
for pat, severity, lang, desc, cat in patterns:
|
|
298
|
-
if lang != "any"
|
|
299
|
-
|
|
342
|
+
if lang != "any":
|
|
343
|
+
if lang not in languages:
|
|
344
|
+
continue
|
|
345
|
+
exts = LANG_EXTENSIONS.get(lang)
|
|
346
|
+
if exts and ext not in exts:
|
|
347
|
+
continue
|
|
300
348
|
if re.search(pat, line, re.IGNORECASE):
|
|
301
349
|
findings.append({
|
|
302
350
|
"file": rel,
|
|
@@ -333,7 +381,12 @@ def scan_cat2_audit_gaps(phi_files: list[Path], root: Path) -> list[dict]:
|
|
|
333
381
|
r"@(Request|Get|Post|Put|Delete)Mapping|"
|
|
334
382
|
r"Model\.(find|save|update|delete)|"
|
|
335
383
|
r"db\.(query|execute)|cursor\.execute|"
|
|
336
|
-
r"repository\.|findBy|\.save\(|\.delete\(
|
|
384
|
+
r"repository\.|findBy|\.save\(|\.delete\(|"
|
|
385
|
+
# Python frameworks (SQLAlchemy session methods only — avoids matching
|
|
386
|
+
# Express req.session.save/destroy which use different method names)
|
|
387
|
+
r"@app\.route|@blueprint\.route|"
|
|
388
|
+
r"@api_view|ViewSet|APIView|"
|
|
389
|
+
r"\bsession\.(query|add|execute|delete|merge)\b)",
|
|
337
390
|
re.IGNORECASE,
|
|
338
391
|
)
|
|
339
392
|
audit_kw = re.compile(
|
|
@@ -382,7 +435,11 @@ def scan_cat5_access_gaps(phi_files: list[Path], languages: set[str],
|
|
|
382
435
|
)
|
|
383
436
|
data_ops = re.compile(
|
|
384
437
|
r"(router|app\.(get|post|put|delete)|"
|
|
385
|
-
r"@(Request|Get|Post|Put|Delete)Mapping
|
|
438
|
+
r"@(Request|Get|Post|Put|Delete)Mapping|"
|
|
439
|
+
# Python frameworks (session methods anchored to SQLAlchemy)
|
|
440
|
+
r"@app\.route|@blueprint\.route|"
|
|
441
|
+
r"@api_view|ViewSet|APIView|"
|
|
442
|
+
r"cursor\.execute|\bsession\.(query|execute)\b)",
|
|
386
443
|
re.IGNORECASE,
|
|
387
444
|
)
|
|
388
445
|
|
package/bin/ai-toolkit.js
CHANGED
|
@@ -30,6 +30,7 @@ const GENERATORS = {
|
|
|
30
30
|
'augment-rules': { script: 'generate_augment.py', dest: path.join('.augment', 'rules', 'ai-toolkit.md'), mkdir: '.augment/rules' },
|
|
31
31
|
'agents-md': { script: 'generate_agents_md.py', dest: 'AGENTS.md' },
|
|
32
32
|
'codex-md': { script: 'generate_codex.py', dest: 'AGENTS.md' },
|
|
33
|
+
'opencode-md': { script: 'generate_opencode.py', dest: 'AGENTS.md' },
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
// ---------------------------------------------------------------------------
|
|
@@ -98,10 +99,15 @@ const COMMANDS = {
|
|
|
98
99
|
'codex-md': 'Generate AGENTS.md for OpenAI Codex CLI',
|
|
99
100
|
'codex-rules': 'Generate .agents/rules/ for OpenAI Codex CLI',
|
|
100
101
|
'codex-hooks': 'Generate .codex/hooks.json for OpenAI Codex CLI',
|
|
102
|
+
'opencode-md': 'Generate AGENTS.md for opencode',
|
|
103
|
+
'opencode-agents': 'Generate .opencode/agents/ for opencode (subagents)',
|
|
104
|
+
'opencode-commands': 'Generate .opencode/commands/ for opencode (slash commands)',
|
|
105
|
+
'opencode-plugin': 'Generate .opencode/plugins/ai-toolkit-hooks.js (lifecycle bridge)',
|
|
106
|
+
'opencode-json': 'Merge .mcp.json servers into opencode.json',
|
|
101
107
|
'agents-md': 'Regenerate AGENTS.md from agent definitions',
|
|
102
108
|
'compile-slm': 'Compile toolkit into a minimal SLM system prompt (--budget, --model-size, --dry-run)',
|
|
103
109
|
'llms-txt': 'Generate llms.txt and llms-full.txt',
|
|
104
|
-
'generate-all': 'Generate all platform configs at once (agents, cursor, windsurf, copilot, gemini, cline, roo, aider, augment, antigravity, codex, llms)',
|
|
110
|
+
'generate-all': 'Generate all platform configs at once (agents, cursor, windsurf, copilot, gemini, cline, roo, aider, augment, antigravity, codex, opencode, llms)',
|
|
105
111
|
help: 'Show this help message',
|
|
106
112
|
};
|
|
107
113
|
|
|
@@ -225,7 +231,7 @@ function showHelp() {
|
|
|
225
231
|
console.log(' --persona <p> Persona preset: backend-lead, frontend-lead, devops-eng, junior-dev');
|
|
226
232
|
console.log(' --modules <list> Install specific modules (e.g. core,agents,rules-typescript)');
|
|
227
233
|
console.log(' --lang <list> Explicitly select language rules (e.g. typescript, go,python)');
|
|
228
|
-
console.log(' --editors <list> Install editor configs: cursor,windsurf,cline,roo,aider,augment,copilot,antigravity,codex (or "all")');
|
|
234
|
+
console.log(' --editors <list> Install editor configs: cursor,windsurf,cline,roo,aider,augment,copilot,antigravity,codex,opencode (or "all")');
|
|
229
235
|
console.log(' Default with --local: auto-detect from existing project files');
|
|
230
236
|
console.log(' --auto-detect Detect project languages and install matching rule modules');
|
|
231
237
|
console.log(' --list, --dry-run Dry-run: show what would be applied');
|
|
@@ -438,9 +444,10 @@ function handleConfig(args) {
|
|
|
438
444
|
*/
|
|
439
445
|
function handleGenerateAll(_args) {
|
|
440
446
|
for (const [name, gen] of Object.entries(GENERATORS)) {
|
|
441
|
-
// Skip codex-md
|
|
442
|
-
//
|
|
443
|
-
|
|
447
|
+
// Skip codex-md and opencode-md — they inject into AGENTS.md via markers
|
|
448
|
+
// (used by install --local --editors codex|opencode), not as standalone files.
|
|
449
|
+
// agents-md generates the full agent list which is the standalone AGENTS.md.
|
|
450
|
+
if (name === 'codex-md' || name === 'opencode-md') continue;
|
|
444
451
|
writeGeneratorOutput(gen);
|
|
445
452
|
}
|
|
446
453
|
// Directory-based generators (multi-file output)
|
|
@@ -452,6 +459,10 @@ function handleGenerateAll(_args) {
|
|
|
452
459
|
run(scriptPath('generate_augment_rules.py'), [CWD]);
|
|
453
460
|
run(scriptPath('generate_codex_rules.py'), [CWD]);
|
|
454
461
|
run(scriptPath('generate_codex_hooks.py'), [CWD]);
|
|
462
|
+
run(scriptPath('generate_opencode_agents.py'), [CWD]);
|
|
463
|
+
run(scriptPath('generate_opencode_commands.py'), [CWD]);
|
|
464
|
+
run(scriptPath('generate_opencode_plugin.py'), [CWD]);
|
|
465
|
+
run(scriptPath('generate_opencode_json.py'), [CWD]);
|
|
455
466
|
// Single-file generators
|
|
456
467
|
const conventionsOut = runGenerator('generate_conventions.py');
|
|
457
468
|
fs.writeFileSync(path.join(CWD, 'CONVENTIONS.md'), conventionsOut);
|
|
@@ -559,6 +570,10 @@ const SPECIAL_HANDLERS = {
|
|
|
559
570
|
'augment-dir-rules': (_args) => run(scriptPath('generate_augment_rules.py'), [CWD]),
|
|
560
571
|
'codex-rules': (_args) => run(scriptPath('generate_codex_rules.py'), [CWD]),
|
|
561
572
|
'codex-hooks': (_args) => run(scriptPath('generate_codex_hooks.py'), [CWD]),
|
|
573
|
+
'opencode-agents': (_args) => run(scriptPath('generate_opencode_agents.py'), [CWD]),
|
|
574
|
+
'opencode-commands': (_args) => run(scriptPath('generate_opencode_commands.py'), [CWD]),
|
|
575
|
+
'opencode-plugin': (_args) => run(scriptPath('generate_opencode_plugin.py'), [CWD]),
|
|
576
|
+
'opencode-json': (_args) => run(scriptPath('generate_opencode_json.py'), [CWD]),
|
|
562
577
|
'generate-all': handleGenerateAll,
|
|
563
578
|
};
|
|
564
579
|
|