@softspark/ai-toolkit 4.22.1 → 4.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,35 @@ Versioning follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## v4.23.0 — a compliance scanner that says nothing must say why (2026-08-06)
11
+
12
+ ### Fixed
13
+
14
+ - **`hipaa-validate` scanned a manifest-less project silently clean.** Category
15
+ 1, 3, 4 and 7 patterns are language-tagged and fire only for a detected
16
+ language, and detection read manifest files only. A directory of Python with
17
+ PHI in its loggers therefore reported `HIGH: 0` while every Python rule sat
18
+ unused — the worst possible output for a compliance tool, because zero reads
19
+ as compliant. Found by the post-release SOP on v4.22.1.
20
+
21
+ Detection now falls back to the extensions of the files being scanned when no
22
+ manifest is present. **Manifests still win**, so a project that already
23
+ declared itself keeps exactly the behaviour it had.
24
+
25
+ ### Changed
26
+
27
+ - **The scan summary reports how the language was decided.** A new
28
+ `language_detection` field says `manifest`, `file extensions (no manifest
29
+ found)`, or `none`. When it is `none` the text report writes to stderr that
30
+ language-tagged patterns did not run and that a zero-finding result means
31
+ unscanned rather than compliant.
32
+
33
+ **Upgrade note:** a project scanned without a manifest may now report HIGH
34
+ findings where v4.22.x reported none. Those findings were always there. A CI
35
+ job gating on the exit code can start failing on real PHI exposure.
36
+
37
+ ---
38
+
10
39
  ## v4.22.1 — nine skills, not four, could not find their own scripts (2026-08-06)
11
40
 
12
41
  v4.22.0 claimed to fix four skills whose documented script path never resolved.
package/README.md CHANGED
@@ -6,7 +6,16 @@
6
6
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
7
7
  [![Skills](https://img.shields.io/badge/skills-109-brightgreen)](app/skills/)
8
8
  [![Agents](https://img.shields.io/badge/agents-44-blue)](app/agents/)
9
- [![Tests](https://img.shields.io/badge/tests-1513%20passing-success)](tests/)
9
+ [![Tests](https://img.shields.io/badge/tests-1516%20passing-success)](tests/)
10
+
11
+ ## What's New in v4.23.0
12
+
13
+ **v4.23.0** — `hipaa-validate` scanned a project without a manifest silently
14
+ clean: its language-tagged patterns never ran, and the report said `HIGH: 0`.
15
+ Zero reads as compliant. Detection now falls back to file extensions, the summary
16
+ says how the language was decided, and an undecidable run warns that a zero means
17
+ unscanned. Manifest-declaring projects are unaffected. Found by the post-release
18
+ SOP, not by a user.
10
19
 
11
20
  ## What's New in v4.22.1
12
21
 
@@ -3,7 +3,7 @@
3
3
  "name": "ai-toolkit",
4
4
  "displayName": "AI Toolkit",
5
5
  "description": "Professional-grade engineering skills, agents, rules, and lifecycle guardrails for Claude Code, Claude Chat, and Cowork.",
6
- "version": "4.22.1",
6
+ "version": "4.23.0",
7
7
  "author": {
8
8
  "name": "SoftSpark",
9
9
  "url": "https://github.com/softspark"
@@ -161,6 +161,9 @@ This distinction helps compliance officers prioritize immediate remediation (def
161
161
 
162
162
  ## Gotchas
163
163
 
164
+ - **Check `language_detection` in the summary before trusting a zero.** Category 1, 3, 4 and 7 patterns are language-tagged and only fire for a detected language. Manifests decide first (`pyproject.toml`, `package.json`, `go.mod`, …); without one the scanner falls back to file extensions. If it reports `languages: ["any"]` with `language_detection: "none"`, the language rules never ran and `HIGH: 0` means *unscanned*, not *compliant* — the scanner prints that warning to stderr, so a run whose stderr is discarded loses it.
165
+ - Scanning a monorepo package or a subdirectory can put you below the manifest. The extension fallback covers the common case, but a directory of `.sql`, `.yaml` or templates resolves to no language at all — scan from the level that holds the manifest.
166
+
164
167
  - Test fixtures and seed data often contain **synthetic** PHI that looks real (SSN-shaped IDs, formatted phone numbers, sample email addresses). Flag them but lower severity — production code handling the same patterns is the actual risk.
165
168
  - HIPAA §164.312(b) requires audit logging but does not specify a format. "Logs exist" is not evidence of compliance — the logs must capture WHO (authenticated user), WHAT (action), WHEN (timestamp), WHERE (resource), and they must be immutable (append-only or write-once storage).
166
169
  - Encryption-at-rest varies silently by storage layer. RDS auto-encrypts new volumes since 2017, but older DB snapshots may not be; S3 bucket policies can override instance-level encryption. Treat "encryption enabled" as a claim to verify with the cloud provider, not a state to trust.
@@ -302,8 +302,24 @@ def context_gate(files: list[Path], keywords: list[str],
302
302
  # Language detection (Step 1)
303
303
  # ---------------------------------------------------------------------------
304
304
 
305
- def detect_languages(root: Path) -> set[str]:
306
- """Detect project languages from manifest files."""
305
+ def detect_languages(root: Path, files: list[Path] | None = None) -> tuple[set[str], str]:
306
+ """Detect project languages. Returns (languages, how_they_were_detected).
307
+
308
+ Manifest files decide when present. When none is found, fall back to the
309
+ extensions of the files actually being scanned.
310
+
311
+ The fallback exists because language-tagged patterns fire only when the
312
+ project language includes the tag, so a manifest-less directory of Python
313
+ scanned clean while every Python rule sat unused — and the report said
314
+ `HIGH: 0`, which reads as compliant rather than unscanned. Silence is the
315
+ worst possible output for a compliance tool. A monorepo package, a scan
316
+ pointed at a subdirectory, or a service whose manifest lives one level up
317
+ all hit this.
318
+
319
+ Manifests still win: a project that declares itself keeps exactly the
320
+ behaviour it had, so upgrading changes nothing for anyone who was already
321
+ being scanned properly.
322
+ """
307
323
  langs = set()
308
324
  for indicator, lang in LANGUAGE_INDICATORS.items():
309
325
  if "*" in indicator:
@@ -314,7 +330,20 @@ def detect_languages(root: Path) -> set[str]:
314
330
  else:
315
331
  if (root / indicator).exists():
316
332
  langs.add(lang)
317
- return langs or {"any"}
333
+ if langs:
334
+ return langs, "manifest"
335
+
336
+ if files:
337
+ ext_to_lang: dict[str, set[str]] = {}
338
+ for lang, exts in LANG_EXTENSIONS.items():
339
+ for ext in exts:
340
+ ext_to_lang.setdefault(ext, set()).add(lang)
341
+ for f in files:
342
+ langs |= ext_to_lang.get(f.suffix, set())
343
+ if langs:
344
+ return langs, "file extensions (no manifest found)"
345
+
346
+ return {"any"}, "none — language-tagged patterns were skipped"
318
347
 
319
348
 
320
349
  # ---------------------------------------------------------------------------
@@ -616,7 +645,8 @@ def format_text_report(findings: list[dict], mode: str,
616
645
 
617
646
  def format_json_report(findings: list[dict], mode: str,
618
647
  phi_count: int, scanned_count: int,
619
- categories_run: list[int], languages: list[str]) -> str:
648
+ categories_run: list[int], languages: list[str],
649
+ language_source: str = "manifest") -> str:
620
650
  """Format findings as structured JSON for CI integration."""
621
651
  high_count = sum(1 for f in findings if f["severity"] == "HIGH")
622
652
  warn_count = sum(1 for f in findings if f["severity"] == "WARN")
@@ -628,6 +658,7 @@ def format_json_report(findings: list[dict], mode: str,
628
658
  "files_scanned": scanned_count,
629
659
  "categories_run": sorted(categories_run),
630
660
  "languages": sorted(languages),
661
+ "language_detection": language_source,
631
662
  "high": high_count,
632
663
  "warn": warn_count,
633
664
  "total": len(findings),
@@ -723,8 +754,9 @@ def main() -> None:
723
754
  file=sys.stderr)
724
755
  print("Consider narrowing the scan path for targeted results.", file=sys.stderr)
725
756
 
726
- # Step 1: Detect languages
727
- languages = detect_languages(root)
757
+ # Step 1: Detect languages. Categories 1-2 scan everything, so the extension
758
+ # fallback looks at all collected files, not just the PHI-adjacent subset.
759
+ languages, lang_source = detect_languages(root, all_files)
728
760
 
729
761
  # Step 2: Run categories
730
762
  findings: list[dict] = []
@@ -789,10 +821,18 @@ def main() -> None:
789
821
  if args.output == "json":
790
822
  print(format_json_report(findings, args.mode, phi_count,
791
823
  scanned_count, categories_run,
792
- sorted(languages)))
824
+ sorted(languages), lang_source))
793
825
  else:
794
826
  print(format_text_report(findings, args.mode, phi_count,
795
827
  scanned_count, categories_run))
828
+ if languages == {"any"}:
829
+ # Never let an unscanned run read as a clean one.
830
+ print("\nWARNING: no project language identified "
831
+ f"({lang_source}).", file=sys.stderr)
832
+ print("Language-tagged patterns (Python, JS/TS, Go, Java, Ruby, C#) "
833
+ "did NOT run.", file=sys.stderr)
834
+ print("A zero-finding result here means unscanned, not compliant. "
835
+ "Scan from the directory holding the manifest.", file=sys.stderr)
796
836
 
797
837
  # Exit code: non-zero if HIGH findings
798
838
  high_count = sum(1 for f in findings if f["severity"] == "HIGH")
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.22.1",
2
+ "version": "4.23.0",
3
3
  "components": {
4
4
  "agents": {
5
5
  "description": "44 specialized agents (orchestrator, backend, frontend, security, devops, etc.)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "4.22.1",
3
+ "version": "4.23.0",
4
4
  "description": "AI coding toolkit: 109 skills, 44 agents, 12 developer-tool integrations, recoverable native tool-output filtering, Claude Chat/Cowork export, safety constitution, SARIF audit, and signed npm provenance.",
5
5
  "keywords": [
6
6
  "claude",