@clubmatto/ai-kit 0.0.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +65 -0
  3. package/dist/scripts/fetch-playwright-skills.js +63 -0
  4. package/dist/src/cmd/sync.js +109 -0
  5. package/dist/src/commands/sync.js +111 -0
  6. package/dist/src/content.js +99 -0
  7. package/dist/src/index.js +19 -0
  8. package/dist/src/logger.js +2 -0
  9. package/dist/src/manifest.js +24 -0
  10. package/dist/src/output.js +46 -0
  11. package/dist/src/reader.js +99 -0
  12. package/dist/src/template.js +10 -0
  13. package/dist/tests/content.test.js +141 -0
  14. package/dist/tests/integration/cli.test.js +43 -0
  15. package/dist/tests/output.js +36 -0
  16. package/dist/tests/reader.test.js +141 -0
  17. package/dist/tests/sync.test.js +90 -0
  18. package/dist/tests/utils.js +20 -0
  19. package/dist/vitest.config.js +9 -0
  20. package/docs/roadmap.md +16 -0
  21. package/eslint.config.mjs +38 -0
  22. package/package.json +78 -0
  23. package/scripts/fetch-playwright-skills.ts +79 -0
  24. package/src/agents/monorepo.md +30 -0
  25. package/src/agents/opencode.json +31 -0
  26. package/src/cmd/sync.ts +158 -0
  27. package/src/commands/commit.md +43 -0
  28. package/src/commands/interview.md +92 -0
  29. package/src/commands/synth.md +45 -0
  30. package/src/index.ts +24 -0
  31. package/src/logger.ts +10 -0
  32. package/src/manifest.ts +29 -0
  33. package/src/output.ts +66 -0
  34. package/src/reader.ts +114 -0
  35. package/src/rules/go.md +306 -0
  36. package/src/rules/kotlin.md +177 -0
  37. package/src/rules/plan-mode.md +7 -0
  38. package/src/rules/spring-boot.md +549 -0
  39. package/src/rules/typescript.md +302 -0
  40. package/src/rules/unsure.md +9 -0
  41. package/src/skills/image-gen/SKILL.md +50 -0
  42. package/src/skills/image-gen/scripts/generate.js +166 -0
  43. package/src/skills/playwright-cli/SKILL.md +279 -0
  44. package/src/skills/playwright-cli/references/request-mocking.md +87 -0
  45. package/src/skills/playwright-cli/references/running-code.md +232 -0
  46. package/src/skills/playwright-cli/references/session-management.md +170 -0
  47. package/src/skills/playwright-cli/references/storage-state.md +275 -0
  48. package/src/skills/playwright-cli/references/test-generation.md +88 -0
  49. package/src/skills/playwright-cli/references/tracing.md +142 -0
  50. package/src/skills/playwright-cli/references/video-recording.md +43 -0
  51. package/src/template.ts +14 -0
  52. package/tests/fixtures/agents/another.json +4 -0
  53. package/tests/fixtures/agents/monorepo.md +5 -0
  54. package/tests/fixtures/agents/opencode.json +4 -0
  55. package/tests/fixtures/commands/another.md +5 -0
  56. package/tests/fixtures/commands/commit.md +7 -0
  57. package/tests/fixtures/commands/test.md +13 -0
  58. package/tests/fixtures/rules/nested/nested-rule.md +3 -0
  59. package/tests/fixtures/rules/test-rule.md +5 -0
  60. package/tests/fixtures/rules/typescript.md +5 -0
  61. package/tests/fixtures/skills/test-skill/SKILL.md +7 -0
  62. package/tests/fixtures/skills/test-skill/nested-refs/doc.md +3 -0
  63. package/tests/fixtures/skills/test-skill/skill-details.md +7 -0
  64. package/tests/integration/cli.test.ts +55 -0
  65. package/tests/output.ts +37 -0
  66. package/tests/reader.test.ts +193 -0
  67. package/tests/sync.test.ts +136 -0
  68. package/tests/utils.ts +17 -0
  69. package/tsconfig.json +23 -0
  70. package/vitest.config.ts +8 -0
package/src/reader.ts ADDED
@@ -0,0 +1,114 @@
1
+ import { readdirSync, readFileSync } from "fs";
2
+ import { join } from "path";
3
+
4
+ type SyncType = "commands" | "rules" | "skills" | "config";
5
+
6
+ export interface SyncItem {
7
+ type: SyncType;
8
+ name: string;
9
+ content: string;
10
+ }
11
+
12
+ interface CommandConfig {
13
+ template: string;
14
+ description: string;
15
+ }
16
+
17
+ function parseFrontmatter(content: string): Record<string, string> {
18
+ const result: Record<string, string> = {};
19
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
20
+ if (!match) return result;
21
+
22
+ const frontmatter = match[1];
23
+ for (const line of frontmatter.split("\n")) {
24
+ const colonIndex = line.indexOf(":");
25
+ if (colonIndex === -1) continue;
26
+ const key = line.slice(0, colonIndex).trim();
27
+ result[key] = line.slice(colonIndex + 1).trim();
28
+ }
29
+ return result;
30
+ }
31
+
32
+ export function getCommandConfig(
33
+ commandsDir: string,
34
+ ): Record<string, CommandConfig> {
35
+ const config: Record<string, CommandConfig> = {};
36
+
37
+ try {
38
+ const files = readdirSync(commandsDir);
39
+ for (const file of files) {
40
+ if (!file.endsWith(".md")) continue;
41
+ const filePath = join(commandsDir, file);
42
+ const content = readFileSync(filePath, "utf-8");
43
+ const frontmatter = parseFrontmatter(content);
44
+ const name = file.replace(/\.md$/, "");
45
+ const body = content.replace(/^---[\s\S]*?---\n/, "").trim();
46
+ config[name] = {
47
+ description: frontmatter.description || "",
48
+ template: body,
49
+ };
50
+ }
51
+ } catch {
52
+ return config;
53
+ }
54
+
55
+ return config;
56
+ }
57
+
58
+ function readFiles(dir: string, type: SyncType, baseDir?: string): SyncItem[] {
59
+ if (!readdirSync(dir, { withFileTypes: true }).length) {
60
+ return [];
61
+ }
62
+
63
+ const base = baseDir || dir;
64
+
65
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
66
+ const path = join(dir, entry.name);
67
+ const relativePath = path.slice(base.length + 1);
68
+ if (entry.isDirectory()) {
69
+ return readFiles(path, type, base);
70
+ }
71
+ if (entry.name.endsWith(".md")) {
72
+ return [
73
+ {
74
+ type,
75
+ name: relativePath,
76
+ content: readFileSync(path, "utf-8"),
77
+ },
78
+ ];
79
+ }
80
+ return [];
81
+ });
82
+ }
83
+
84
+ export function readContent(rulesDir: string, skillsDir: string): SyncItem[] {
85
+ return [...readFiles(rulesDir, "rules"), ...readFiles(skillsDir, "skills")];
86
+ }
87
+
88
+ export function readConfigs(agentsDir: string): SyncItem[] {
89
+ try {
90
+ return readdirSync(agentsDir, { withFileTypes: true })
91
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
92
+ .map((entry) => ({
93
+ type: "config",
94
+ name: entry.name,
95
+ content: readFileSync(join(agentsDir, entry.name), "utf-8"),
96
+ }));
97
+ } catch {
98
+ return [];
99
+ }
100
+ }
101
+
102
+ export function readAgents(agentsDir: string): SyncItem | null {
103
+ const sourcePath = join(agentsDir, "monorepo.md");
104
+
105
+ try {
106
+ return {
107
+ type: "config",
108
+ name: "AGENTS.md",
109
+ content: readFileSync(sourcePath, "utf-8"),
110
+ };
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
@@ -0,0 +1,306 @@
1
+ # 🦫 Go Specialist Agent Rules
2
+
3
+ ## 🎯 Your Go Persona
4
+
5
+ You are a senior Go engineer with expertise in:
6
+
7
+ - Clean, idiomatic Go (following **Effective Go** and Go Proverbs)
8
+ - Building maintainable services with **minimal dependencies**
9
+ - Concurrency patterns (`goroutines`, `channels`, `sync` primitives)
10
+ - Performance optimization and memory management
11
+
12
+ **Your primary values**: Simplicity, readability, and explicit error handling.
13
+
14
+ ## 📁 Go Project Structure
15
+
16
+ Follow this exact structure for all Go projects:
17
+
18
+ [service-or-lib-name]/
19
+ ├── cmd/ # Application entry points (one per binary)
20
+ │ └── [app-name]/
21
+ │ └── main.go # Minimal main - just parse flags and run
22
+ ├── internal/ # Private application code (cannot be imported outside)
23
+ │ ├── handlers/ # HTTP/gRPC handlers
24
+ │ ├── models/ # Domain models/structs
25
+ │ ├── repository/ # Data access layer
26
+ │ └── service/ # Business logic
27
+ ├── pkg/ # Public library code (can be imported by other projects)
28
+ │ └── [package-name]/ # Well-documented, stable APIs
29
+ ├── api/ # Protocol definitions (gRPC)
30
+ ├── scripts/ # Build/deployment scripts
31
+ ├── configs/ # Configuration files
32
+ ├── deployments/ # Docker, k8s manifests
33
+ ├── go.mod # MODULE DECLARATION (must be present)
34
+ ├── go.sum # Dependency checksums
35
+ ├── Makefile # Common build commands
36
+ └── README.md # Project documentation
37
+
38
+ ## 🛠️ Development Commands
39
+
40
+ ### Essential Workflow Commands
41
+
42
+ ```bash
43
+
44
+ # ALWAYS run before making changes
45
+ go mod tidy
46
+
47
+ # Run tests
48
+ go test ./... -v # All tests with verbose output
49
+ go test ./... -race # With race detector (for concurrent code)
50
+ go test -run TestSpecific # Run specific test
51
+
52
+ # Build
53
+ go build ./cmd/[app-name]
54
+
55
+ # Linting & Static Analysis (MUST PASS)
56
+ golangci-lint run # If configured
57
+ go vet ./... # Built-in checks
58
+ ```
59
+
60
+ ### Code Generation (if applicable)
61
+
62
+ ```bash
63
+ # Protocol buffers
64
+ protoc --go_out=. --go-grpc_out=. api/proto/*.proto
65
+ ```
66
+
67
+ ## 📝 Go Code Standards
68
+
69
+ ### Imports & Organization
70
+
71
+ ```go
72
+ package myproject
73
+ // ✅ GOOD: Grouped with stdlib, external, internal
74
+ import (
75
+ // Standard library
76
+ "context"
77
+ "fmt"
78
+ "time"
79
+
80
+ // External dependencies
81
+ "github.com/pkg/errors"
82
+ "go.uber.org/zap"
83
+
84
+ // Internal modules
85
+ "myproject/internal/models"
86
+ )
87
+
88
+ // ❌ BAD: Mixed, ungrouped imports
89
+ import "fmt"
90
+ import "myproject/internal/models"
91
+ import "context"
92
+ import "github.com/pkg/errors"
93
+ ```
94
+
95
+ ## Error Handling (CRITICAL)
96
+
97
+ ```go
98
+ package myproject
99
+
100
+ // ✅ GOOD: Explicit, wrapped errors with context
101
+ func ProcessData(ctx context.Context, input string) (Result, error) {
102
+ data, err := parseInput(input)
103
+ if err != nil {
104
+ return Result{}, fmt.Errorf("parse input: %w", err)
105
+ }
106
+
107
+ result, err := calculate(data)
108
+ if err != nil {
109
+ return Result{}, fmt.Errorf("calculate: %w", err)
110
+ }
111
+
112
+ return result, nil
113
+ }
114
+
115
+ // ✅ GOOD: Custom error types for API consumers
116
+ type ValidationError struct {
117
+ Field string
118
+ Message string
119
+ }
120
+
121
+ func (e ValidationError) Error() string {
122
+ return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
123
+ }
124
+
125
+ // ❌ BAD: Ignoring errors or generic messages
126
+ _, err = doSomething()
127
+ if err != nil {
128
+ return err // No context!
129
+ }
130
+ ```
131
+
132
+ ### Struct Design & Methods
133
+
134
+ ```go
135
+ package myproject
136
+
137
+ // ✅ GOOD: Constructor functions for complex initialisation
138
+ type Config struct {
139
+ Addr string
140
+ Timeout time.Duration
141
+ Logger *zap.Logger
142
+ }
143
+
144
+ func NewConfig(addr string) (*Config, error) {
145
+ if addr == "" {
146
+ return nil, errors.New("addr cannot be empty")
147
+ }
148
+
149
+ logger, _ := zap.NewProduction()
150
+
151
+ return &Config{
152
+ Addr: addr,
153
+ Timeout: 30 * time.Second,
154
+ Logger: logger,
155
+ }, nil
156
+ }
157
+
158
+ // ✅ GOOD: Pointer vs value receiver decision
159
+ // Use pointer receiver when:
160
+ // 1. Method needs to modify the receiver
161
+ // 2. Struct is large (to avoid copying)
162
+ // 3. Consistency with other methods
163
+
164
+ type User struct {
165
+ ID int
166
+ Name string
167
+ }
168
+
169
+ func (u *User) UpdateName(name string) { // Pointer receiver - modifies
170
+ u.Name = name
171
+ }
172
+
173
+ func (u User) DisplayName() string { // Value receiver - read-only
174
+ return fmt.Sprintf("User: %s", u.Name)
175
+ }
176
+ ```
177
+
178
+ ### Concurrency Patterns
179
+
180
+ ```go
181
+ package myproject
182
+ // ✅ GOOD: Context-aware goroutines with proper cleanup
183
+ func ProcessConcurrently(ctx context.Context, items []Item) ([]Result, error) {
184
+ var wg sync.WaitGroup
185
+ results := make([]Result, len(items))
186
+ errCh := make(chan error, 1)
187
+
188
+ for i, item := range items {
189
+ wg.Add(1)
190
+ go func(idx int, it Item) {
191
+ defer wg.Done()
192
+
193
+ select {
194
+ case <-ctx.Done():
195
+ return // Respect cancellation
196
+ default:
197
+ res, err := processItem(ctx, it)
198
+ if err != nil {
199
+ select {
200
+ case errCh <- fmt.Errorf("item %d: %w", idx, err):
201
+ default:
202
+ }
203
+ return
204
+ }
205
+ results[idx] = res
206
+ }
207
+ }(i, item)
208
+ }
209
+
210
+ wg.Wait()
211
+ close(errCh)
212
+
213
+ if err := <-errCh; err != nil {
214
+ return nil, err
215
+ }
216
+
217
+ return results, nil
218
+ }
219
+ ```
220
+
221
+ ## 🧪 Testing Standards
222
+
223
+ - Use `testify/assert` for assertions
224
+ - Use `testify/mock` for mocking
225
+ - Use `testify/require` for preconditions
226
+ - Always `require.NoError(t, err)` for errors
227
+
228
+ ### Table-Driven Tests (PREFERRED)
229
+
230
+ ```go
231
+ package myproject_test
232
+
233
+ func TestCalculate(t *testing.T) {
234
+ tests := []struct {
235
+ name string
236
+ input int
237
+ expected int
238
+ hasError bool
239
+ }{
240
+ {"positive number", 5, 25, false},
241
+ {"zero", 0, 0, false},
242
+ {"negative number", -3, 0, true},
243
+ }
244
+
245
+ for _, tt := range tests {
246
+ t.Run(tt.name, func(t *testing.T) {
247
+ result, err := Calculate(tt.input)
248
+
249
+ if tt.hasError {
250
+ require.Error(t, err)
251
+ return
252
+ }
253
+
254
+ require.NoError(t, err)
255
+ assert.Equal(t, tt.expected, result)
256
+ })
257
+ }
258
+ }
259
+ ```
260
+
261
+ ## 📦 Dependency Management
262
+
263
+ ### Module Rules
264
+
265
+ - Always use Go modules (go.mod must be present)
266
+ - Pin specific versions – no floating dependencies
267
+ - Minimize external dependencies - stdlib first
268
+ - Upgrade systematically – test thoroughly after upgrades
269
+
270
+ ### Version Guidelines
271
+
272
+ ```go
273
+ # go.mod example
274
+ module github.com/company/service-name
275
+
276
+ go 1.21 # Minimum version
277
+
278
+ require (
279
+ github.com/pkg/errors v0.9.1
280
+ github.com/stretchr/testify v1.8.4
281
+ go.uber.org/zap v1.26.0
282
+ )
283
+
284
+ # ❌ AVOID: Indirect dependencies for direct functionality
285
+ # github.com/some-transitive-dependency v1.2.3
286
+ ```
287
+
288
+ ## 🚫 Go-Specific Restrictions
289
+
290
+ ### Never Do These:
291
+
292
+ - ❌ Never use panic() in production code (except in main() or during initialization)
293
+ - ❌ Never ignore errors (\_ = functionThatReturnsError())
294
+ - ❌ Never use global variables for application state
295
+ - ❌ Never write if err != nil { return nil } (always return the error)
296
+
297
+ ## 🔍 Context Usage (IMPORTANT)
298
+
299
+ Always pass context.Context as the first parameter to functions that:
300
+
301
+ - Make network calls
302
+ - Do I/O operations
303
+ - Could be long-running
304
+ - Should respect cancellation/timeout
305
+
306
+ {{FOOTER}}
@@ -0,0 +1,177 @@
1
+ # ☕ Kotlin Specialist Agent Rules
2
+
3
+ ## 🎯 Your Kotlin Persona
4
+
5
+ You are a senior Kotlin engineer with expertise in:
6
+
7
+ - Modern Kotlin idioms and best practices (following Kotlin Coding Conventions)
8
+ - Functional programming with Kotlin's rich standard library
9
+ - Coroutines and structured concurrency
10
+ - Building type-safe, expressive APIs
11
+ - Android development (if applicable to this project)
12
+
13
+ **Your primary values**: Expressiveness, null safety, and pragmatic functional programming.
14
+
15
+ ## 📝 Kotlin Code Standards
16
+
17
+ ### Null Safety (CRITICAL)
18
+
19
+ ```kotlin
20
+ // ✅ GOOD: Use nullable types explicitly
21
+ fun processUser(user: User?) {
22
+ user?.let {
23
+ // Safe access with let
24
+ println("Processing ${it.name}")
25
+ } ?: run {
26
+ // Handle null case
27
+ println("User is null")
28
+ }
29
+ }
30
+
31
+ // ✅ GOOD: Use safe calls and elvis operator
32
+ val length: Int = text?.length ?: 0
33
+
34
+ // ✅ GOOD: Require non-null when appropriate
35
+ fun requireNonNull(input: String?) {
36
+ val nonNullInput = requireNotNull(input) { "Input cannot be null" }
37
+ // Now safely use nonNullInput
38
+ }
39
+
40
+ // ❌ BAD: Using !! operator (avoid unless absolutely necessary)
41
+ val dangerous = potentiallyNullValue!! // Throws NPE if null
42
+ ```
43
+
44
+ ### Immutability & Data Classes
45
+
46
+ ```kotlin
47
+ // ✅ GOOD: Prefer val over var, data classes for models
48
+ data class User(
49
+ val id: Long,
50
+ val name: String,
51
+ val email: String,
52
+ val createdAt: Instant = Instant.now()
53
+ ) {
54
+ // Secondary constructor for validation
55
+ constructor(name: String, email: String) : this(
56
+ id = 0L,
57
+ name = name.validateName(),
58
+ email = email.validateEmail()
59
+ )
60
+ }
61
+
62
+ // ✅ GOOD: Use copy for updates
63
+ val updatedUser = user.copy(name = "New Name")
64
+
65
+ // ✅ GOOD: Sealed classes for state representation
66
+ sealed class Result<out T> {
67
+ data class Success<out T>(val data: T) : Result<T>()
68
+ data class Error(val exception: Throwable) : Result<Nothing>()
69
+ object Loading : Result<Nothing>()
70
+ }
71
+ ```
72
+
73
+ ### Extension Functions & DSLs
74
+
75
+ ```kotlin
76
+ // ✅ GOOD: Meaningful extension functions
77
+ fun String.isValidEmail(): Boolean {
78
+ return Patterns.EMAIL_ADDRESS.matcher(this).matches()
79
+ }
80
+
81
+ fun List<User>.filterActive(): List<User> {
82
+ return filter { it.isActive }
83
+ }
84
+
85
+ // ✅ GOOD: Type-safe builders/DSLs when appropriate
86
+ fun createUser(block: UserBuilder.() -> Unit): User {
87
+ return UserBuilder().apply(block).build()
88
+ }
89
+
90
+ class UserBuilder {
91
+ var name: String = ""
92
+ var email: String = ""
93
+
94
+ fun build(): User {
95
+ require(name.isNotBlank()) { "Name cannot be blank" }
96
+ require(email.isValidEmail()) { "Invalid email" }
97
+ return User(name = name, email = email)
98
+ }
99
+ }
100
+ ```
101
+
102
+ ### Coroutines & Structured Concurrency
103
+
104
+ ```kotlin
105
+ // ✅ GOOD: Proper coroutine scoping
106
+ class UserService(
107
+ private val userRepository: UserRepository,
108
+ private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
109
+ ) {
110
+ private val scope = CoroutineScope(SupervisorJob() + ioDispatcher)
111
+
112
+ suspend fun getUser(id: Long): Result<User> = withContext(ioDispatcher) {
113
+ return@withContext try {
114
+ Result.success(userRepository.findById(id))
115
+ } catch (e: Exception) {
116
+ Result.failure(e)
117
+ }
118
+ }
119
+
120
+ fun processUsersInParallel(ids: List<Long>) {
121
+ scope.launch {
122
+ val deferredResults = ids.map { id ->
123
+ async { getUser(id) }
124
+ }
125
+ val results = deferredResults.awaitAll()
126
+ // Process results
127
+ }
128
+ }
129
+
130
+ fun cleanup() {
131
+ scope.cancel() // Proper cleanup
132
+ }
133
+ }
134
+
135
+ // ✅ GOOD: Flow for streams
136
+ fun getUserUpdates(userId: Long): Flow<UserUpdate> = callbackFlow {
137
+ val callback = object : UserUpdateCallback {
138
+ override fun onUpdate(update: UserUpdate) {
139
+ trySend(update)
140
+ }
141
+ override fun onComplete() {
142
+ close()
143
+ }
144
+ }
145
+
146
+ userRepository.registerUpdateCallback(userId, callback)
147
+
148
+ awaitClose {
149
+ userRepository.unregisterUpdateCallback(userId, callback)
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### Good Practices
155
+
156
+ - Use version catalogs (libs.versions.toml) for centralized dependency management
157
+ - Prefer platform BOMs when available (e.g., Kotlin BOM, Spring Boot BOM)
158
+ - Avoid dynamic versions (use exact versions: 1.9.0, not 1.9.+)
159
+
160
+ ## 🚫 Kotlin-Specific Restrictions
161
+
162
+ ### Never do these:
163
+
164
+ - ❌ Never use !! (non-null assertion operator) without explicit justification.
165
+ - ❌ Never create mutable collections when immutable will suffice.
166
+ - ❌ Never ignore suspend modifier when calling suspending functions.
167
+ - ❌ Never leak coroutines (always use structured concurrency with proper scopes).
168
+ - ❌ Never use Java-style getters/setters for Kotlin properties.
169
+
170
+ ## 📚 Recommended Reading
171
+
172
+ - Kotlin Coding Conventions: https://kotlinlang.org/docs/coding-conventions.html
173
+ - Kotlin Koans: https://play.kotlinlang.org/koans
174
+ - Kotlin Coroutines Guide: https://kotlinlang.org/docs/coroutines-guide.html
175
+ - Effective Kotlin: https://kt.academy/article/ek-effective-kotlin
176
+
177
+ {{FOOTER}}
@@ -0,0 +1,7 @@
1
+ # Plan Mode
2
+
3
+ - Make the plan extremely concise. Sacrifice grammar for the sake of concision.
4
+ - Never make time estimates, focus on what/how/why.
5
+ - At the end of each plan, give me a list of unresolved questions to answer, if any.
6
+
7
+ \_{{FOOTER}}