@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 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, and Codex CLI, ready in 60 seconds.
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
  [![CI](https://github.com/softspark/ai-toolkit/actions/workflows/ci.yml/badge.svg)](https://github.com/softspark/ai-toolkit/actions/workflows/ci.yml)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
  [![Skills](https://img.shields.io/badge/skills-94-brightgreen)](app/skills/)
8
8
  [![Agents](https://img.shields.io/badge/agents-44-blue)](app/agents/)
9
- [![Tests](https://img.shields.io/badge/tests-618%20passing-success)](tests/)
9
+ [![Tests](https://img.shields.io/badge/tests-658%20passing-success)](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 (618 tests)
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.5.0",
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",
@@ -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 fields in logger calls |
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
- | Email sending without TLS config | WARN | PHI in email must use TLS |
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
- | Pattern | Severity | Description |
200
- |---------|----------|-------------|
201
- | PHI route file without any auth keywords in same file | WARN | POTENTIAL access control gap — verify auth middleware covers these routes (§164.312(d)) |
202
- | `Access-Control-Allow-Origin:\s*\*` or `origin:\s*(true\|\*)` in PHI-adjacent files | HIGH | Unrestricted cross-origin access to PHI endpoints |
203
- | Routes marked `public`, `noAuth`, `anonymous` exposing PHI keywords | HIGH | PHI must require authentication |
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 fields in logger", "1"),
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" and lang not in languages:
299
- continue
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 it injects a Codex config block via markers (used by install --local --editors codex)
442
- // agents-md generates the full agent list which is the standalone AGENTS.md
443
- if (name === 'codex-md') continue;
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