@softspark/ai-toolkit 4.30.2 → 4.31.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +27 -28
  3. package/app/.claude-plugin/plugin.json +1 -1
  4. package/app/agents/code-reviewer.md +14 -0
  5. package/app/agents/frontend-specialist.md +27 -24
  6. package/app/agents/infrastructure-validator.md +2 -2
  7. package/app/claude-app/skills/ai-toolkit-rules/SKILL.md +44 -1
  8. package/app/personas/frontend-lead.md +20 -12
  9. package/app/rules/claude-toolkit-rules.md +4 -1
  10. package/app/rules/edit-discipline.md +37 -0
  11. package/app/skills/app-builder/SKILL.md +37 -6
  12. package/app/skills/claude-toolkit-rules/SKILL.md +37 -0
  13. package/app/skills/design-engineering/SKILL.md +87 -102
  14. package/app/skills/design-engineering/reference/animation-recipes.md +67 -0
  15. package/app/skills/design-engineering/reference/component-craft.md +28 -0
  16. package/app/skills/documentation-standards/SKILL.md +1 -1
  17. package/app/skills/edit-discipline/SKILL.md +50 -0
  18. package/app/skills/git-conventions/SKILL.md +18 -0
  19. package/app/skills/output-mode/SKILL.md +41 -0
  20. package/app/skills/quality-gates/SKILL.md +23 -0
  21. package/app/skills/review/SKILL.md +10 -0
  22. package/kb/procedures/{ecosystem-sync-sop.md → sop-ecosystem-sync.md} +2 -2
  23. package/kb/procedures/{post-release-testing-sop.md → sop-post-release-testing.md} +3 -3
  24. package/kb/procedures/sop-pre-commit.md +96 -0
  25. package/kb/procedures/{release-preparation-sop.md → sop-release.md} +87 -26
  26. package/kb/reference/architecture-overview.md +1 -1
  27. package/kb/reference/copilot-compatibility.md +1 -1
  28. package/kb/reference/licensing.md +2 -2
  29. package/kb/reference/quick-wins-implementation-summary.md +1 -1
  30. package/kb/reference/supported-tools-registry.md +2 -2
  31. package/kb/troubleshooting/plugin-double-load.md +1 -1
  32. package/llms-full.txt +974 -806
  33. package/llms.txt +6 -5
  34. package/manifest.json +1 -1
  35. package/package.json +2 -1
  36. package/scripts/doctor.py +1 -1
  37. package/scripts/generate_toolkit_rules_skills.py +189 -0
  38. package/scripts/validate.py +1 -1
  39. /package/kb/procedures/{maintenance-sop.md → sop-maintenance.md} +0 -0
  40. /package/kb/procedures/{release-verification-sop.md → sop-release-verification.md} +0 -0
package/llms.txt CHANGED
@@ -32,11 +32,12 @@
32
32
  - [Closed: Tool-Output Token Reduction](kb/history/completed/tool-output-token-reduction-closed-20260727.md)
33
33
  - [How-To Guides](kb/howto/README.md)
34
34
  - [Plan: Cloud Security Pack — Multi-Cloud Audit](kb/planning/cloud-security-pack-plan.md)
35
- - [SOP: Ecosystem Sync](kb/procedures/ecosystem-sync-sop.md)
36
- - [SOP: AI Toolkit Maintenance](kb/procedures/maintenance-sop.md)
37
- - [SOP: Post-Release Testing](kb/procedures/post-release-testing-sop.md)
38
- - [SOP: Release Preparation](kb/procedures/release-preparation-sop.md)
39
- - [SOP: Release Verification](kb/procedures/release-verification-sop.md)
35
+ - [SOP: Ecosystem Sync](kb/procedures/sop-ecosystem-sync.md)
36
+ - [SOP: AI Toolkit Maintenance](kb/procedures/sop-maintenance.md)
37
+ - [SOP: Post-Release Testing](kb/procedures/sop-post-release-testing.md)
38
+ - [SOP: Pre-Commit Gate](kb/procedures/sop-pre-commit.md)
39
+ - [SOP: Release Verification](kb/procedures/sop-release-verification.md)
40
+ - [SOP: Release Preparation](kb/procedures/sop-release.md)
40
41
  - [Agents Catalog](kb/reference/agents-catalog.md)
41
42
  - [Anti-Pattern Registry Format](kb/reference/anti-pattern-registry-format.md)
42
43
  - [AI Toolkit Architecture](kb/reference/architecture-overview.md)
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.30.2",
2
+ "version": "4.31.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.30.2",
3
+ "version": "4.31.0",
4
4
  "description": "AI coding toolkit: 109 skills, 44 agents, 14 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",
@@ -62,6 +62,7 @@
62
62
  "generate:roo": "python3 scripts/generate_roo_modes.py > .roomodes",
63
63
  "generate:aider": "python3 scripts/generate_aider_conf.py > .aider.conf.yml",
64
64
  "generate:language-rules": "python3 scripts/generate_language_rules_skills.py",
65
+ "generate:toolkit-rules": "python3 scripts/generate_toolkit_rules_skills.py",
65
66
  "generate:opencode-agents": "python3 scripts/generate_opencode_agents.py .",
66
67
  "generate:opencode-commands": "python3 scripts/generate_opencode_commands.py .",
67
68
  "generate:opencode-skills": "python3 scripts/generate_opencode_skills.py .",
package/scripts/doctor.py CHANGED
@@ -124,7 +124,7 @@ SEMVER_OUTPUT = re.compile(
124
124
  rf"(?:\.{_SEMVER_PRERELEASE_IDENTIFIER})*)?"
125
125
  rf"(?:\+{_SEMVER_BUILD_IDENTIFIER}"
126
126
  rf"(?:\.{_SEMVER_BUILD_IDENTIFIER})*)?)"
127
- r"(?![0-9A-Za-z.+-])"
127
+ r"(?![0-9A-Za-z+-]|\.(?!\s|$))"
128
128
  )
129
129
  VERSION_LIKE_OUTPUT = re.compile(r"(?<![0-9])[0-9]+\.[0-9]+\.[^\s,;()]+")
130
130
 
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ # Copyright 2024-2026 Lukasz Krzemien (biuro@softspark.eu)
4
+ # Source: https://github.com/softspark/ai-toolkit
5
+
6
+ """Generate knowledge skills from the top-level rule files in ``app/rules/``.
7
+
8
+ Each ``app/rules/<stem>.md`` becomes ``app/skills/<stem>/SKILL.md``. The
9
+ per-language directories are not touched here; ``generate_language_rules_skills``
10
+ owns those.
11
+
12
+ Why this exists
13
+ ---------------
14
+ The rules were reachable only as Claude Code user-level files under
15
+ ``~/.claude/rules/``, with the global ``CLAUDE.md`` carrying a pointer to them
16
+ rather than their content. That pointer is a Claude Code idiom. DeepSeek Harness
17
+ reads ``AGENTS.md``, ``AGENTS.local.md``, ``CLAUDE.md`` and ``CLAUDE.local.md``
18
+ and has no rules-directory support at all, so under DSH every one of those rules
19
+ was inert: named in an injected file, never loaded, never applied.
20
+
21
+ Shipping them as skills fixes that without changing the Claude Code surface. A
22
+ skill is discovered by every editor that reads the shared skill catalogue, and
23
+ the catalogue injects each skill's *description* into the session. So the
24
+ description here is written as the rule itself in one imperative line — it is in
25
+ context whether or not the model chooses to load the body.
26
+
27
+ Idempotent: rerunning overwrites the generated ``SKILL.md`` and leaves anything
28
+ else in the skill directory alone.
29
+
30
+ Usage:
31
+ python3 scripts/generate_toolkit_rules_skills.py # write all
32
+ python3 scripts/generate_toolkit_rules_skills.py --check # dry-run
33
+ python3 scripts/generate_toolkit_rules_skills.py --rules git-conventions
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import argparse
38
+ import sys
39
+ from pathlib import Path
40
+ from textwrap import dedent
41
+
42
+ ROOT = Path(__file__).resolve().parent.parent
43
+ RULES_DIR = ROOT / "app" / "rules"
44
+ SKILLS_DIR = ROOT / "app" / "skills"
45
+
46
+ # The description is the rule. It is injected into every session through the
47
+ # skill catalogue, so it has to state the obligation on its own — a reader who
48
+ # never opens the body should still know what is required. Keep it imperative,
49
+ # keep it one line, and put the reasoning in the file under app/rules/.
50
+ DESCRIPTIONS: dict[str, str] = {
51
+ "edit-discipline": (
52
+ "Change files with the edit and write tools, never by rewriting them "
53
+ "through bash (sed, awk, tee, heredoc, redirection), and show `git diff` "
54
+ "before reporting a file-changing task as done. "
55
+ "Triggers: edit, write, modify, refactor, patch, fix, diff, review changes."
56
+ ),
57
+ "git-conventions": (
58
+ "Conventional Commits only (feat, fix, docs, refactor, test, chore); no "
59
+ "AI co-authorship trailer and no AI signature in a commit message. "
60
+ "Triggers: commit, git, message, changelog, release, pull request."
61
+ ),
62
+ "output-mode": (
63
+ "Answer concisely: lead with the result, no preamble, tables over prose, "
64
+ "no trailing restatement of a diff the reader can already see. "
65
+ "Triggers: response style, verbosity, summary, explanation, report."
66
+ ),
67
+ "quality-gates": (
68
+ "Plan before work over an hour, and hold the gates: ruff clean, mypy "
69
+ "--strict clean, pytest coverage above 70 percent, no secrets in code. "
70
+ "Triggers: quality, lint, mypy, pytest, coverage, gate, definition of done."
71
+ ),
72
+ "claude-toolkit-rules": (
73
+ "SoftSpark working agreement: never guess a home directory path, give at "
74
+ "least three alternatives, and apply a devil's advocate critique to "
75
+ "decisions. Triggers: toolkit, conventions, workflow, alternatives, review."
76
+ ),
77
+ }
78
+
79
+
80
+ def _strip_frontmatter(text: str) -> str:
81
+ """Remove YAML frontmatter (--- ... ---) if present."""
82
+ if not text.startswith("---\n"):
83
+ return text
84
+ end = text.find("\n---\n", 4)
85
+ if end == -1:
86
+ return text
87
+ return text[end + len("\n---\n"):]
88
+
89
+
90
+ def _title(stem: str) -> str:
91
+ return stem.replace("-", " ").title()
92
+
93
+
94
+ def _build_skill_md(stem: str, rule_file: Path) -> str:
95
+ description = DESCRIPTIONS[stem]
96
+ body = _strip_frontmatter(rule_file.read_text(encoding="utf-8")).strip()
97
+ frontmatter = dedent(
98
+ f"""\
99
+ ---
100
+ name: {stem}
101
+ description: "{description}"
102
+ effort: low
103
+ user-invocable: false
104
+ allowed-tools: Read
105
+ ---
106
+
107
+ # {_title(stem)}
108
+
109
+ This rule comes from `app/rules/{stem}.md` in ai-toolkit. It applies to
110
+ every task in this workspace, not only when it is loaded.
111
+
112
+ """
113
+ )
114
+ return frontmatter + body + "\n"
115
+
116
+
117
+ def discover_rules() -> list[str]:
118
+ """List top-level rule files that have a description to ship them under.
119
+
120
+ A rule with no entry in DESCRIPTIONS is skipped loudly rather than shipped
121
+ with a generated summary: the description is the part that reaches every
122
+ session, so it is written by a person.
123
+ """
124
+ if not RULES_DIR.is_dir():
125
+ return []
126
+ found: list[str] = []
127
+ for path in sorted(RULES_DIR.glob("*.md")):
128
+ if path.stem in DESCRIPTIONS:
129
+ found.append(path.stem)
130
+ else:
131
+ print(
132
+ f" SKIP: {path.name} has no entry in DESCRIPTIONS; add one to ship it",
133
+ file=sys.stderr,
134
+ )
135
+ return found
136
+
137
+
138
+ def generate(rules: list[str] | None = None, check: bool = False) -> int:
139
+ """Generate skills. Returns the number written, or that would be written."""
140
+ selected = rules if rules else discover_rules()
141
+ written = 0
142
+ for stem in selected:
143
+ rule_file = RULES_DIR / f"{stem}.md"
144
+ if not rule_file.is_file():
145
+ print(f" SKIP: {stem} (app/rules/{stem}.md missing)", file=sys.stderr)
146
+ continue
147
+ if stem not in DESCRIPTIONS:
148
+ print(f" SKIP: {stem} (no description)", file=sys.stderr)
149
+ continue
150
+
151
+ skill_dir = SKILLS_DIR / stem
152
+ skill_md = skill_dir / "SKILL.md"
153
+ content = _build_skill_md(stem, rule_file)
154
+
155
+ if check:
156
+ existing = skill_md.read_text(encoding="utf-8") if skill_md.is_file() else ""
157
+ status = "OK" if existing == content else "DIFF"
158
+ print(f" [{status}] {skill_md.relative_to(ROOT)}")
159
+ if existing != content:
160
+ written += 1
161
+ continue
162
+
163
+ skill_dir.mkdir(parents=True, exist_ok=True)
164
+ skill_md.write_text(content, encoding="utf-8")
165
+ print(f" wrote {skill_md.relative_to(ROOT)}")
166
+ written += 1
167
+ return written
168
+
169
+
170
+ def main() -> int:
171
+ parser = argparse.ArgumentParser(description=__doc__)
172
+ parser.add_argument("--check", action="store_true", help="report drift, write nothing")
173
+ parser.add_argument("--rules", help="comma-separated rule stems")
174
+ args = parser.parse_args()
175
+
176
+ rules = [r.strip() for r in args.rules.split(",")] if args.rules else None
177
+ written = generate(rules, check=args.check)
178
+ if args.check:
179
+ if written:
180
+ print(f"{written} generated skill(s) differ from app/rules/", file=sys.stderr)
181
+ return 1
182
+ print("toolkit rule skills are up to date")
183
+ return 0
184
+ print(f"{written} toolkit rule skill(s) written")
185
+ return 0
186
+
187
+
188
+ if __name__ == "__main__":
189
+ raise SystemExit(main())
@@ -147,7 +147,7 @@ MAX_EMITTED_SKILL_NODES = 10_000
147
147
  #
148
148
  # Ratchet: lower WARN by 2_000 each release until it reaches 12_000. Never lower a
149
149
  # threshold in the same change that something violates it — split the skill first,
150
- # then tighten. The step lives in kb/procedures/release-preparation-sop.md so it
150
+ # then tighten. The step lives in kb/procedures/sop-release.md so it
151
151
  # does not rot as a comment nobody reads.
152
152
  SKILL_BODY_BUDGET_ERROR = 20_000
153
153
  SKILL_BODY_BUDGET_WARN = 18_000