@softspark/ai-toolkit 2.11.0 → 2.12.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/AGENTS.md +21 -21
- package/CHANGELOG.md +24 -0
- package/README.md +6 -9
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/agents/meta-architect.md +44 -0
- package/app/skills/agent-creator/SKILL.md +22 -6
- package/app/skills/analyze/SKILL.md +21 -1
- package/app/skills/api-patterns/SKILL.md +27 -0
- package/app/skills/app-builder/SKILL.md +24 -0
- package/app/skills/architecture-audit/SKILL.md +24 -0
- package/app/skills/architecture-decision/SKILL.md +24 -0
- package/app/skills/biz-scan/SKILL.md +24 -0
- package/app/skills/brand-voice/SKILL.md +36 -0
- package/app/skills/briefing/SKILL.md +41 -1
- package/app/skills/build/SKILL.md +24 -1
- package/app/skills/chaos/SKILL.md +22 -1
- package/app/skills/ci/SKILL.md +22 -1
- package/app/skills/ci-cd-patterns/SKILL.md +25 -0
- package/app/skills/command-creator/SKILL.md +31 -2
- package/app/skills/content-moderation-patterns/SKILL.md +25 -0
- package/app/skills/database-patterns/SKILL.md +25 -0
- package/app/skills/debug/SKILL.md +23 -1
- package/app/skills/design-engineering/SKILL.md +25 -0
- package/app/skills/docker-devops/SKILL.md +26 -0
- package/app/skills/ecommerce-patterns/SKILL.md +26 -0
- package/app/skills/evaluate/SKILL.md +22 -1
- package/app/skills/evolve/SKILL.md +25 -0
- package/app/skills/explain/SKILL.md +26 -1
- package/app/skills/explore/SKILL.md +25 -0
- package/app/skills/fix/SKILL.md +26 -1
- package/app/skills/git-mastery/SKILL.md +26 -0
- package/app/skills/grill-me/SKILL.md +22 -7
- package/app/skills/health/SKILL.md +22 -1
- package/app/skills/hipaa-validate/SKILL.md +25 -8
- package/app/skills/hook-creator/SKILL.md +23 -6
- package/app/skills/index/SKILL.md +22 -1
- package/app/skills/instinct-review/SKILL.md +25 -1
- package/app/skills/introspect/SKILL.md +26 -1
- package/app/skills/lint/SKILL.md +27 -1
- package/app/skills/mcp-builder/SKILL.md +27 -0
- package/app/skills/mem-search/SKILL.md +40 -0
- package/app/skills/migrate/SKILL.md +27 -1
- package/app/skills/migration-patterns/SKILL.md +26 -0
- package/app/skills/night-watch/SKILL.md +22 -1
- package/app/skills/observability-patterns/SKILL.md +26 -0
- package/app/skills/onboard/SKILL.md +22 -1
- package/app/skills/panic/SKILL.md +21 -1
- package/app/skills/performance-profiling/SKILL.md +34 -0
- package/app/skills/persona/SKILL.md +21 -4
- package/app/skills/plan/SKILL.md +26 -0
- package/app/skills/plan-writing/SKILL.md +26 -0
- package/app/skills/plugin-creator/SKILL.md +23 -6
- package/app/skills/pr/SKILL.md +24 -1
- package/app/skills/prd-to-issues/SKILL.md +22 -6
- package/app/skills/prd-to-plan/SKILL.md +22 -4
- package/app/skills/predict/SKILL.md +25 -0
- package/app/skills/qa-session/SKILL.md +23 -7
- package/app/skills/rag-patterns/SKILL.md +26 -0
- package/app/skills/refactor/SKILL.md +26 -0
- package/app/skills/refactor-plan/SKILL.md +23 -6
- package/app/skills/rollback/SKILL.md +27 -1
- package/app/skills/security-patterns/SKILL.md +27 -0
- package/app/skills/skill-audit/SKILL.md +22 -5
- package/app/skills/skill-creator/SKILL.md +50 -9
- package/app/skills/swift-patterns/SKILL.md +27 -107
- package/app/skills/swift-patterns/reference/frameworks.md +111 -0
- package/app/skills/test/SKILL.md +23 -1
- package/app/skills/testing-patterns/SKILL.md +26 -0
- package/app/skills/triage-issue/SKILL.md +22 -4
- package/app/skills/ubiquitous-language/SKILL.md +23 -8
- package/kb/procedures/release-preparation-sop.md +11 -5
- package/kb/procedures/release-verification-sop.md +24 -6
- package/llms-full.txt +56 -32
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/evaluate_skills.py +84 -0
|
@@ -280,113 +280,7 @@ func testLoginFlow() {
|
|
|
280
280
|
|
|
281
281
|
## Common Frameworks
|
|
282
282
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
```swift
|
|
286
|
-
@Observable
|
|
287
|
-
final class UserViewModel {
|
|
288
|
-
var users: [User] = []
|
|
289
|
-
var isLoading = false
|
|
290
|
-
private let service: UserService
|
|
291
|
-
|
|
292
|
-
init(service: UserService) { self.service = service }
|
|
293
|
-
|
|
294
|
-
func load() async {
|
|
295
|
-
isLoading = true
|
|
296
|
-
defer { isLoading = false }
|
|
297
|
-
users = (try? await service.fetchAll()) ?? []
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
struct UserListView: View {
|
|
302
|
-
@State private var vm: UserViewModel
|
|
303
|
-
|
|
304
|
-
init(service: UserService) {
|
|
305
|
-
_vm = State(initialValue: UserViewModel(service: service))
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
var body: some View {
|
|
309
|
-
NavigationStack {
|
|
310
|
-
List(vm.users) { user in
|
|
311
|
-
NavigationLink(value: user) { Text(user.name) }
|
|
312
|
-
}
|
|
313
|
-
.navigationTitle("Users")
|
|
314
|
-
.navigationDestination(for: User.self) { UserDetailView(user: $0) }
|
|
315
|
-
.task { await vm.load() }
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
### Combine
|
|
322
|
-
|
|
323
|
-
```swift
|
|
324
|
-
class SearchVM: ObservableObject {
|
|
325
|
-
@Published var query = ""
|
|
326
|
-
@Published var results: [Item] = []
|
|
327
|
-
private var cancellables = Set<AnyCancellable>()
|
|
328
|
-
|
|
329
|
-
init(service: SearchService) {
|
|
330
|
-
$query
|
|
331
|
-
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
|
|
332
|
-
.removeDuplicates()
|
|
333
|
-
.filter { !$0.isEmpty }
|
|
334
|
-
.flatMap { service.search(query: $0) }
|
|
335
|
-
.receive(on: DispatchQueue.main)
|
|
336
|
-
.sink(receiveCompletion: { _ in },
|
|
337
|
-
receiveValue: { [weak self] in self?.results = $0 })
|
|
338
|
-
.store(in: &cancellables)
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Structured Concurrency
|
|
344
|
-
|
|
345
|
-
```swift
|
|
346
|
-
func fetchAllProfiles(ids: [String]) async throws -> [Profile] {
|
|
347
|
-
try await withThrowingTaskGroup(of: Profile.self) { group in
|
|
348
|
-
for id in ids { group.addTask { try await fetchProfile(id: id) } }
|
|
349
|
-
return try await group.reduce(into: []) { $0.append($1) }
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// AsyncStream for bridging callbacks
|
|
354
|
-
let locations = AsyncStream<Location> { continuation in
|
|
355
|
-
manager.onUpdate = { continuation.yield($0) }
|
|
356
|
-
continuation.onTermination = { _ in manager.stop() }
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### SwiftData
|
|
361
|
-
|
|
362
|
-
```swift
|
|
363
|
-
@Model
|
|
364
|
-
final class Item {
|
|
365
|
-
var title: String
|
|
366
|
-
var timestamp: Date
|
|
367
|
-
@Relationship(deleteRule: .cascade) var tags: [Tag]
|
|
368
|
-
init(title: String) { self.title = title; self.timestamp = .now; self.tags = [] }
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
struct ItemListView: View {
|
|
372
|
-
@Query(sort: \Item.timestamp, order: .reverse) private var items: [Item]
|
|
373
|
-
@Environment(\.modelContext) private var context
|
|
374
|
-
|
|
375
|
-
var body: some View {
|
|
376
|
-
List(items) { Text($0.title) }
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### Vapor (Server-Side)
|
|
382
|
-
|
|
383
|
-
```swift
|
|
384
|
-
app.get("users", ":id") { req async throws -> User in
|
|
385
|
-
guard let id = req.parameters.get("id", as: UUID.self) else { throw Abort(.badRequest) }
|
|
386
|
-
guard let user = try await User.find(id, on: req.db) else { throw Abort(.notFound) }
|
|
387
|
-
return user
|
|
388
|
-
}
|
|
389
|
-
```
|
|
283
|
+
For SwiftUI + `@Observable`, Combine, Structured Concurrency, SwiftData, and Vapor framework patterns with complete code examples, see [reference/frameworks.md](reference/frameworks.md).
|
|
390
284
|
|
|
391
285
|
---
|
|
392
286
|
|
|
@@ -498,3 +392,29 @@ Schemes: separate Debug/Release/Testing. Enable ASan + TSan in test schemes.
|
|
|
498
392
|
| Blocking main thread | UI freezes | `async/await`, `Task { }` |
|
|
499
393
|
| `UserDefaults` for secrets | Insecure | Keychain (`SecItemAdd`) |
|
|
500
394
|
| `@ObservedObject` for owned state | Object recreated | `@StateObject` or `@State` + `@Observable` |
|
|
395
|
+
|
|
396
|
+
## Rules
|
|
397
|
+
|
|
398
|
+
- **MUST** use Swift concurrency primitives (`async/await`, actors, `Task`) for new code — GCD is legacy and mixes poorly with the new model
|
|
399
|
+
- **MUST** annotate view models with `@MainActor` when they touch UI state — off-main mutations cause runtime warnings and flaky UI
|
|
400
|
+
- **NEVER** force-unwrap (`!`) without a documented invariant in a comment; runtime crashes from unwrap are the top iOS crash category
|
|
401
|
+
- **NEVER** store secrets in `UserDefaults` or plist — use Keychain APIs (`SecItemAdd`, `SecItemCopyMatching`)
|
|
402
|
+
- **CRITICAL**: SwiftUI state flows downward; mutations flow through `@State`, `@Binding`, or `@Observable`. Never mutate a parent's state from a child via a captured reference — it breaks dependency tracking.
|
|
403
|
+
- **MANDATORY**: every closure that captures `self` inside a reference type uses `[weak self]` or `[unowned self]` — retain cycles are the top memory-leak cause
|
|
404
|
+
|
|
405
|
+
## Gotchas
|
|
406
|
+
|
|
407
|
+
- `@StateObject` and `@ObservedObject` look similar but behave oppositely on parent re-render: `@StateObject` persists, `@ObservedObject` may re-initialize. Using `@ObservedObject` for view-owned state recreates the object on every render — state loss without error.
|
|
408
|
+
- `Task { @MainActor in ... }` inside a non-`@MainActor` context does **not** synchronously return to main; it schedules. Code between the `await` and `Task` boundary runs on whatever actor you came from, which can race with UI updates.
|
|
409
|
+
- `AsyncStream` continuations without `onTermination` leak: if the consumer cancels, the producer keeps yielding forever. Always install a termination handler.
|
|
410
|
+
- SwiftData `@Query` with `@Environment(\.modelContext)` invalidates on every write; heavy reads in a watched view cause perf drops. Use `@FetchRequest`-style fetch descriptors with explicit refresh, not ambient `@Query`, for large datasets.
|
|
411
|
+
- Combine's `.receive(on: DispatchQueue.main)` schedules asynchronously — if the next operator expects sync execution, order matters. Prefer moving `.receive(on:)` to just before the sink, not mid-pipeline.
|
|
412
|
+
- Swift Concurrency does not compose cleanly with Objective-C completion handlers; `withCheckedContinuation` bridges but a continuation that is never resumed hangs the Task forever. Always pair resumes with all control-flow paths, including errors.
|
|
413
|
+
|
|
414
|
+
## When NOT to Load
|
|
415
|
+
|
|
416
|
+
- For **Flutter or React Native** cross-platform code — use `/flutter-patterns` or JS patterns; this skill is Swift-only
|
|
417
|
+
- For generic iOS architecture decisions (MVC vs MVVM vs VIPER) — use `/architecture-decision`
|
|
418
|
+
- For Kotlin-based cross-platform mobile (KMP) — use `/kotlin-patterns`
|
|
419
|
+
- For mobile CI/CD specifics (TestFlight, Fastlane) — use `/ci-cd-patterns`
|
|
420
|
+
- For Objective-C interop deep dives — outside scope; this skill focuses on modern Swift
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Swift Framework Patterns
|
|
2
|
+
|
|
3
|
+
Detailed patterns for SwiftUI, Combine, Structured Concurrency, SwiftData, and Vapor. Loaded on demand from `swift-patterns/SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## SwiftUI + @Observable (iOS 17+)
|
|
6
|
+
|
|
7
|
+
```swift
|
|
8
|
+
@Observable
|
|
9
|
+
final class UserViewModel {
|
|
10
|
+
var users: [User] = []
|
|
11
|
+
var isLoading = false
|
|
12
|
+
private let service: UserService
|
|
13
|
+
|
|
14
|
+
init(service: UserService) { self.service = service }
|
|
15
|
+
|
|
16
|
+
func load() async {
|
|
17
|
+
isLoading = true
|
|
18
|
+
defer { isLoading = false }
|
|
19
|
+
users = (try? await service.fetchAll()) ?? []
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
struct UserListView: View {
|
|
24
|
+
@State private var vm: UserViewModel
|
|
25
|
+
|
|
26
|
+
init(service: UserService) {
|
|
27
|
+
_vm = State(initialValue: UserViewModel(service: service))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var body: some View {
|
|
31
|
+
NavigationStack {
|
|
32
|
+
List(vm.users) { user in
|
|
33
|
+
NavigationLink(value: user) { Text(user.name) }
|
|
34
|
+
}
|
|
35
|
+
.navigationTitle("Users")
|
|
36
|
+
.navigationDestination(for: User.self) { UserDetailView(user: $0) }
|
|
37
|
+
.task { await vm.load() }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Combine
|
|
44
|
+
|
|
45
|
+
```swift
|
|
46
|
+
class SearchVM: ObservableObject {
|
|
47
|
+
@Published var query = ""
|
|
48
|
+
@Published var results: [Item] = []
|
|
49
|
+
private var cancellables = Set<AnyCancellable>()
|
|
50
|
+
|
|
51
|
+
init(service: SearchService) {
|
|
52
|
+
$query
|
|
53
|
+
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
|
|
54
|
+
.removeDuplicates()
|
|
55
|
+
.filter { !$0.isEmpty }
|
|
56
|
+
.flatMap { service.search(query: $0) }
|
|
57
|
+
.receive(on: DispatchQueue.main)
|
|
58
|
+
.sink(receiveCompletion: { _ in },
|
|
59
|
+
receiveValue: { [weak self] in self?.results = $0 })
|
|
60
|
+
.store(in: &cancellables)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Structured Concurrency
|
|
66
|
+
|
|
67
|
+
```swift
|
|
68
|
+
func fetchAllProfiles(ids: [String]) async throws -> [Profile] {
|
|
69
|
+
try await withThrowingTaskGroup(of: Profile.self) { group in
|
|
70
|
+
for id in ids { group.addTask { try await fetchProfile(id: id) } }
|
|
71
|
+
return try await group.reduce(into: []) { $0.append($1) }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// AsyncStream for bridging callbacks
|
|
76
|
+
let locations = AsyncStream<Location> { continuation in
|
|
77
|
+
manager.onUpdate = { continuation.yield($0) }
|
|
78
|
+
continuation.onTermination = { _ in manager.stop() }
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## SwiftData
|
|
83
|
+
|
|
84
|
+
```swift
|
|
85
|
+
@Model
|
|
86
|
+
final class Item {
|
|
87
|
+
var title: String
|
|
88
|
+
var timestamp: Date
|
|
89
|
+
@Relationship(deleteRule: .cascade) var tags: [Tag]
|
|
90
|
+
init(title: String) { self.title = title; self.timestamp = .now; self.tags = [] }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
struct ItemListView: View {
|
|
94
|
+
@Query(sort: \Item.timestamp, order: .reverse) private var items: [Item]
|
|
95
|
+
@Environment(\.modelContext) private var context
|
|
96
|
+
|
|
97
|
+
var body: some View {
|
|
98
|
+
List(items) { Text($0.title) }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Vapor (Server-Side)
|
|
104
|
+
|
|
105
|
+
```swift
|
|
106
|
+
app.get("users", ":id") { req async throws -> User in
|
|
107
|
+
guard let id = req.parameters.get("id", as: UUID.self) else { throw Abort(.badRequest) }
|
|
108
|
+
guard let user = try await User.find(id, on: req.db) else { throw Abort(.notFound) }
|
|
109
|
+
return user
|
|
110
|
+
}
|
|
111
|
+
```
|
package/app/skills/test/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: test
|
|
3
|
-
description: "Run
|
|
3
|
+
description: "Run the project's test suite with coverage reporting, auto-detecting the framework (pytest, vitest, jest, flutter test, go test, cargo test, phpunit). Use when the user asks to run existing tests — not to author new ones test-first."
|
|
4
4
|
effort: medium
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
argument-hint: "[file or pattern]"
|
|
@@ -105,3 +105,25 @@ tests/ # or test/, spec/, __tests__/
|
|
|
105
105
|
├── integration/ # Tests with external dependencies
|
|
106
106
|
└── e2e/ # End-to-end tests
|
|
107
107
|
```
|
|
108
|
+
|
|
109
|
+
## Rules
|
|
110
|
+
|
|
111
|
+
- **MUST** detect the framework automatically via `detect-runner.py` — do not assume
|
|
112
|
+
- **NEVER** modify tests to make them pass
|
|
113
|
+
- **CRITICAL**: coverage reporting must use the project's configured tool (`--cov=src`, `--coverage`, etc.) — do not invent flags
|
|
114
|
+
- **MANDATORY**: when a test fails, report the failure exactly; do not paraphrase
|
|
115
|
+
|
|
116
|
+
## Gotchas
|
|
117
|
+
|
|
118
|
+
- `pytest --cov=src` inflates coverage when `tests/` lives under `src/` — test files count toward covered lines. Either move `tests/` out, or use `--cov=src --cov-branch --cov-report=term-missing` with an explicit `[tool.coverage.run] omit = ["tests/*"]` in `pyproject.toml`.
|
|
119
|
+
- `go test ./...` runs packages in parallel by default; a test depending on shared global state may pass alone and fail in the suite. If flakiness appears only under `./...`, suspect shared state, not a bug in the test.
|
|
120
|
+
- `flutter test` without an emulator falls back to the headless "null platform" — widget tests that require a render surface are **silently skipped**. CI without a display must add `flutter test --platform vm` or a virtual framebuffer.
|
|
121
|
+
- `vitest run` and `jest` interpret glob patterns differently: `*.test.ts` in vitest matches filenames, in jest matches paths. Passing the same CLI arg to both produces different test sets — use the framework-native config file when possible.
|
|
122
|
+
- `pytest --lf` (last-failed) silently runs **all** tests if there is no cache from a prior run. First-time runs in CI therefore ignore `--lf` and re-run everything, which can hide "only failed tests" bugs in local runs.
|
|
123
|
+
|
|
124
|
+
## When NOT to Use
|
|
125
|
+
|
|
126
|
+
- To write new tests test-first — use `/tdd`
|
|
127
|
+
- To author test design patterns — use `/testing-patterns` (knowledge skill)
|
|
128
|
+
- To debug a failing test — use `/debug` after `/test` surfaces the failure
|
|
129
|
+
- For performance/load testing — use dedicated tooling, not `/test`
|
|
@@ -82,3 +82,29 @@ For Flutter/Dart testing patterns, see [reference/flutter-testing.md](reference/
|
|
|
82
82
|
| "We'll add tests later" | Untested code accumulates — later means never, and coverage gaps compound |
|
|
83
83
|
| "Mocking everything is fine" | Over-mocking tests the mocks, not the code — mock at boundaries only |
|
|
84
84
|
| "100% coverage means no bugs" | Coverage measures execution, not correctness — focus on behavior assertions |
|
|
85
|
+
|
|
86
|
+
## Rules
|
|
87
|
+
|
|
88
|
+
- **MUST** follow Arrange-Act-Assert (AAA) structure in every test — unstructured tests degrade into procedural smoke tests
|
|
89
|
+
- **MUST** test behavior through the public interface, not internal implementation — tests coupled to internals break on every refactor
|
|
90
|
+
- **NEVER** test implementation details (private method return values, internal state flags) — they are not the contract
|
|
91
|
+
- **NEVER** hit real external services in unit tests — use fakes/stubs for boundaries; save real integration for integration tests
|
|
92
|
+
- **CRITICAL**: integration tests must hit real dependencies (database, message queue, external API) when mock-vs-prod divergence is a real risk. Mocked integration tests create false confidence.
|
|
93
|
+
- **MANDATORY**: flaky tests are bugs, not noise. Quarantine or delete them — a tolerated flaky test erodes the suite's credibility.
|
|
94
|
+
|
|
95
|
+
## Gotchas
|
|
96
|
+
|
|
97
|
+
- Coverage numbers are easy to game: include generated code, test files that import but do not assert, or wide `# pragma: no cover` usage. A 95% reported coverage with 60% real behavior assertion is common.
|
|
98
|
+
- Snapshot tests (Jest `.toMatchSnapshot()`, pytest-regressions) accept any output as "correct" on first run. An incorrect initial snapshot becomes the accepted baseline — review snapshots as carefully as code.
|
|
99
|
+
- Mocks configured with `any` matchers (e.g., `.mock.calls[0][0]` without a schema) pass even when the production call shape changes. Assert on specific arguments, not just "was called".
|
|
100
|
+
- Test isolation fails when globals leak (module-level mutable state, module-scoped fixtures, env vars set in one test). Flakiness that appears only under `pytest -n auto` or `jest --parallel` is usually shared state.
|
|
101
|
+
- Property-based tests (Hypothesis, fast-check) shrink failing examples to minimal reproducers, but shrinking time can dominate the run. For complex generators, cap shrink deadlines or seed the failing example for next-run reproducibility.
|
|
102
|
+
- Test pyramid vs trophy: the "right" ratio depends on stack. Frontend apps with rendering concerns benefit from more integration tests (trophy); pure backend services align better with pyramid. Don't cargo-cult one model.
|
|
103
|
+
|
|
104
|
+
## When NOT to Load
|
|
105
|
+
|
|
106
|
+
- For **running** the test suite — use `/test`
|
|
107
|
+
- For test-first development workflow — use `/tdd`
|
|
108
|
+
- For debugging a specific test failure — use `/debug` on the failure output
|
|
109
|
+
- For test framework choice in a new project — use `/app-builder`
|
|
110
|
+
- For performance/load testing — this skill covers correctness tests, not load
|
|
@@ -114,7 +114,25 @@ Do NOT include file paths, line numbers, or implementation details. Describe mod
|
|
|
114
114
|
|
|
115
115
|
## Rules
|
|
116
116
|
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
117
|
+
- **MUST** explore the codebase for root cause before filing — symptoms masquerade as causes often
|
|
118
|
+
- **MUST** propose a TDD fix plan with ordered RED→GREEN cycles, each a vertical slice
|
|
119
|
+
- **NEVER** ask follow-up clarifying questions; one initial question maximum, then investigate autonomously
|
|
120
|
+
- **NEVER** include file paths, function names, or line numbers in the issue body — they go stale before the issue is picked up
|
|
121
|
+
- **CRITICAL**: the issue must be reproducible. If reproduction steps cannot be determined from the investigation, say so explicitly in the Problem section — do not fabricate them.
|
|
122
|
+
- **MANDATORY**: file the issue immediately via `gh issue create` and share the URL — do not ask the user to review a draft first
|
|
123
|
+
|
|
124
|
+
## Gotchas
|
|
125
|
+
|
|
126
|
+
- `gh issue create` without `--body` opens `$EDITOR`. In automated flows the skill hangs — always pass the body file or inline text.
|
|
127
|
+
- "Root cause" often turns out to be two concurrent issues. If the investigation keeps branching, file the most-probable primary cause and note the secondary as a follow-up in the same issue.
|
|
128
|
+
- TDD plans with more than ~5 RED→GREEN cycles usually conceal a deeper design issue. Short plans (2-3 cycles) reflect confident root-cause identification; long plans reflect fishing.
|
|
129
|
+
- The `debugger` agent explored autonomously but returns a narrative. Parse it for: confirmed hypothesis, code paths, and recent changes. Discard speculation.
|
|
130
|
+
- Bugs in framework-adjacent code (middleware, ORM hooks) require test setup that mirrors the framework's call context. A TDD plan that writes the test "like a unit test" may not actually reproduce the framework bug.
|
|
131
|
+
|
|
132
|
+
## When NOT to Use
|
|
133
|
+
|
|
134
|
+
- For a conversational bug report from a non-engineer — use `/qa-session` first, which returns refined reports this skill can then process
|
|
135
|
+
- For a specific reproducible error with known root cause — use `/fix` directly
|
|
136
|
+
- For architectural-scale problems — use `/architecture-audit`
|
|
137
|
+
- For creating issues from a PRD — use `/prd-to-issues`
|
|
138
|
+
- For debugging without filing an issue — use `/debug`
|
|
@@ -64,11 +64,26 @@ Extract and formalize domain terminology into a consistent glossary.
|
|
|
64
64
|
|
|
65
65
|
## Rules
|
|
66
66
|
|
|
67
|
-
- **
|
|
68
|
-
- **
|
|
69
|
-
- **
|
|
70
|
-
- **
|
|
71
|
-
- **
|
|
72
|
-
- **
|
|
73
|
-
- **
|
|
74
|
-
|
|
67
|
+
- **MUST** be opinionated — pick the best term and list alternatives as aliases to avoid
|
|
68
|
+
- **MUST** flag every ambiguity (one word → two concepts) and every synonym (two words → one concept) with an explicit recommendation
|
|
69
|
+
- **MUST** write tight definitions (one sentence) that define what the term **is**, not what it does
|
|
70
|
+
- **NEVER** include generic programming concepts (array, function, endpoint, class) — this glossary is for the **domain**, not the toolchain
|
|
71
|
+
- **NEVER** silently overwrite an existing `UBIQUITOUS_LANGUAGE.md` — read the current file first, then update incrementally preserving prior decisions
|
|
72
|
+
- **CRITICAL**: show relationships with cardinality ("an Order belongs to exactly one Customer"). Vocabulary without relationships is a word list, not a language.
|
|
73
|
+
- **MANDATORY**: include 3-5 example dialogue exchanges showing correct usage. Abstract glossaries without dialogue rarely get adopted.
|
|
74
|
+
|
|
75
|
+
## Gotchas
|
|
76
|
+
|
|
77
|
+
- Domain terms often **overlap** with framework jargon (e.g., "Service" in DDD vs "Service" in Angular). If the framework already claims a term, prefer a domain-specific alternative to avoid collision.
|
|
78
|
+
- Stakeholders resist terminology change even when their current terms are ambiguous. "Account" replacing "User" triggers more discussion than expected — plan for negotiation in the example dialogue section.
|
|
79
|
+
- Glossaries rot when features ship without updating them. A `UBIQUITOUS_LANGUAGE.md` last updated 6 months ago is a snapshot, not a source of truth. Flag staleness in the header and re-run this skill periodically.
|
|
80
|
+
- Relationships between terms are easy to hand-wave with "related to". Pin down the cardinality (`0..1`, `1..*`, `1..1`) — vague relationships produce schema ambiguity later.
|
|
81
|
+
- The opinionated canonical choice may contradict marketing or legal language. Note conflicts explicitly rather than hiding them; the domain model and the marketing site can legitimately diverge.
|
|
82
|
+
|
|
83
|
+
## When NOT to Use
|
|
84
|
+
|
|
85
|
+
- For writing a **PRD** (full requirements) — use `/write-a-prd`
|
|
86
|
+
- For generating user-facing documentation — use `/docs`
|
|
87
|
+
- For implementation planning — use `/plan` or `/prd-to-plan`
|
|
88
|
+
- For enforcing language in commit messages or code reviews — use `/brand-voice` (distinct concern: writing style, not domain terms)
|
|
89
|
+
- When the project is a single-developer throwaway — glossary overhead is not justified
|
|
@@ -3,10 +3,10 @@ title: "SOP: Release Preparation"
|
|
|
3
3
|
category: procedures
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [sop, release, version, publish, changelog, semver, provenance, sarif]
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.8.0"
|
|
7
7
|
created: "2026-04-10"
|
|
8
|
-
last_updated: "2026-04-
|
|
9
|
-
description: "Step-by-step checklist for preparing a new ai-toolkit release — version sync, changelog, artifact regeneration, validation, and tagging. Run BEFORE every git tag. Includes mandatory Provenance, SARIF, and checksum-pin checks added in v2.8.0."
|
|
8
|
+
last_updated: "2026-04-21"
|
|
9
|
+
description: "Step-by-step checklist for preparing a new ai-toolkit release — version sync, changelog, artifact regeneration, validation, and tagging. Run BEFORE every git tag. Includes mandatory Provenance, SARIF, and checksum-pin checks added in v2.8.0, and the single-run npm test discipline added in v1.8.0."
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# SOP: Release Preparation
|
|
@@ -207,7 +207,13 @@ python3 scripts/validate.py --strict
|
|
|
207
207
|
python3 scripts/audit_skills.py --ci
|
|
208
208
|
python3 scripts/audit_skills.py --sarif > audit.sarif # MANDATORY — GHAS ingest
|
|
209
209
|
python3 scripts/audit_skills.py --permissions # review Bash/Write/Edit footprint
|
|
210
|
-
|
|
210
|
+
|
|
211
|
+
# Run npm test ONCE, cache output, parse from file. The suite is 669+ bats
|
|
212
|
+
# cases — rerunning it per check wastes minutes. Do not pipe npm test into
|
|
213
|
+
# tail/grep multiple times in the same session.
|
|
214
|
+
npm test > /tmp/npm-test.log 2>&1
|
|
215
|
+
tail -3 /tmp/npm-test.log
|
|
216
|
+
echo "ok: $(grep -c '^ok ' /tmp/npm-test.log) | not ok: $(grep -c '^not ok' /tmp/npm-test.log)"
|
|
211
217
|
```
|
|
212
218
|
|
|
213
219
|
**Expected results:**
|
|
@@ -215,7 +221,7 @@ npm test
|
|
|
215
221
|
- `audit_skills.py --ci`: `HIGH: 0 | WARN: 0` (INFO is acceptable)
|
|
216
222
|
- `audit_skills.py --sarif`: valid JSON, non-empty `runs[0].tool.driver.rules`
|
|
217
223
|
- `audit_skills.py --permissions`: review `Skills with Bash + Write + Edit` list — any newly-added skill with broad access MUST be justified in the CHANGELOG entry
|
|
218
|
-
- `npm test`: `1..N` with zero `not ok`
|
|
224
|
+
- `npm test`: `1..N` with zero `not ok` (read from the cached `/tmp/npm-test.log`, do not rerun)
|
|
219
225
|
|
|
220
226
|
**One-liner:**
|
|
221
227
|
```bash
|
|
@@ -3,10 +3,10 @@ title: "SOP: Release Verification"
|
|
|
3
3
|
category: procedures
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [sop, verification, release, smoke-test, install, update, qa, provenance, sarif]
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.3.0"
|
|
7
7
|
created: "2026-04-08"
|
|
8
|
-
last_updated: "2026-04-
|
|
9
|
-
description: "End-to-end smoke test after installing or updating @softspark/ai-toolkit — verifies CLI, install, doctor, validation, tests, eject, npm provenance attestation, SARIF audit, and per-skill permissions. Reflects the v2.8.0 supply-chain standard."
|
|
8
|
+
last_updated: "2026-04-21"
|
|
9
|
+
description: "End-to-end smoke test after installing or updating @softspark/ai-toolkit — verifies CLI, install, doctor, validation, tests, eject, npm provenance attestation, SARIF audit, and per-skill permissions. Reflects the v2.8.0 supply-chain standard. v1.3.0 adds the single-run npm test discipline (cache to file, parse ok/not-ok once)."
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# SOP: Release Verification
|
|
@@ -39,7 +39,7 @@ python3 scripts/generate_agents_md.py > AGENTS.md # 1. Regenerate AGEN
|
|
|
39
39
|
python3 scripts/generate_codex_rules.py . # 2. Refresh standard Codex rules
|
|
40
40
|
python3 scripts/generate_llms_txt.py > llms.txt # 3. Regenerate llms.txt
|
|
41
41
|
python3 scripts/validate.py --strict # 4. Validation passed?
|
|
42
|
-
npm test
|
|
42
|
+
npm test > /tmp/npm-test.log 2>&1 && grep -c '^ok ' /tmp/npm-test.log && ! grep -q '^not ok' /tmp/npm-test.log # 5. All tests passed? (single run, cached)
|
|
43
43
|
|
|
44
44
|
# Post-install verification (Phases 1-7)
|
|
45
45
|
ai-toolkit --version # 6. Version OK?
|
|
@@ -204,15 +204,33 @@ python3 scripts/audit_skills.py --ci
|
|
|
204
204
|
## Phase 6: Tests (3-5 min)
|
|
205
205
|
|
|
206
206
|
```bash
|
|
207
|
-
|
|
207
|
+
# Run ONCE, capture to file, then parse. Full suite is 669+ bats cases —
|
|
208
|
+
# re-running it per check (tail / grep ok / grep not ok piped separately)
|
|
209
|
+
# wastes minutes every release. Always cache the output.
|
|
210
|
+
npm test > /tmp/npm-test.log 2>&1
|
|
211
|
+
exit=$?
|
|
212
|
+
tail -3 /tmp/npm-test.log
|
|
213
|
+
echo "ok: $(grep -c '^ok ' /tmp/npm-test.log)"
|
|
214
|
+
echo "not ok: $(grep -c '^not ok' /tmp/npm-test.log)"
|
|
215
|
+
echo "exit: $exit"
|
|
208
216
|
```
|
|
209
217
|
|
|
210
218
|
**Verify:**
|
|
219
|
+
- [ ] `exit == 0`
|
|
220
|
+
- [ ] `ok == expected test count` (e.g., 669)
|
|
221
|
+
- [ ] `not ok == 0`
|
|
211
222
|
- [ ] Bats runs tests in parallel (4 jobs)
|
|
212
|
-
- [ ] All `ok` — zero `not ok`
|
|
213
223
|
- [ ] Groups: agents, autodetect, cli, generators, guards, hooks, inject,
|
|
214
224
|
install, kb, mcp, readme, profiles, uninstall, validate
|
|
215
225
|
|
|
226
|
+
**Anti-pattern — do NOT do this:**
|
|
227
|
+
```bash
|
|
228
|
+
# Runs the full suite THREE times. Adds 1-3 min and pressures CI capacity.
|
|
229
|
+
npm test 2>&1 | tail -3
|
|
230
|
+
npm test 2>&1 | grep -c '^ok '
|
|
231
|
+
npm test 2>&1 | grep -c '^not ok'
|
|
232
|
+
```
|
|
233
|
+
|
|
216
234
|
**Key test areas:**
|
|
217
235
|
- Guards: rm -rf, DROP TABLE, git push --force blocked
|
|
218
236
|
- Install: idempotent, profiles, --only/--skip, orphan cleanup
|