ma-agents 3.2.0 → 3.3.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 (86) hide show
  1. package/.opencode/skills/.ma-agents.json +99 -99
  2. package/.roo/rules/00-ma-agents.md +13 -0
  3. package/.roo/skills/.ma-agents.json +241 -0
  4. package/.roo/skills/MANIFEST.yaml +254 -0
  5. package/.roo/skills/ai-audit-trail/SKILL.md +23 -0
  6. package/.roo/skills/auto-bug-detection/SKILL.md +169 -0
  7. package/.roo/skills/cmake-best-practices/SKILL.md +64 -0
  8. package/.roo/skills/cmake-best-practices/examples/cmake.md +59 -0
  9. package/.roo/skills/code-documentation/SKILL.md +57 -0
  10. package/.roo/skills/code-documentation/examples/cpp.md +29 -0
  11. package/.roo/skills/code-documentation/examples/csharp.md +28 -0
  12. package/.roo/skills/code-documentation/examples/javascript_typescript.md +28 -0
  13. package/.roo/skills/code-documentation/examples/python.md +57 -0
  14. package/.roo/skills/code-review/SKILL.md +43 -0
  15. package/.roo/skills/commit-message/SKILL.md +79 -0
  16. package/.roo/skills/cpp-best-practices/SKILL.md +234 -0
  17. package/.roo/skills/cpp-best-practices/examples/modern-idioms.md +189 -0
  18. package/.roo/skills/cpp-best-practices/examples/naming-and-organization.md +102 -0
  19. package/.roo/skills/cpp-concurrency-safety/SKILL.md +60 -0
  20. package/.roo/skills/cpp-concurrency-safety/examples/concurrency.md +73 -0
  21. package/.roo/skills/cpp-const-correctness/SKILL.md +63 -0
  22. package/.roo/skills/cpp-const-correctness/examples/const_correctness.md +54 -0
  23. package/.roo/skills/cpp-memory-handling/SKILL.md +42 -0
  24. package/.roo/skills/cpp-memory-handling/examples/modern-cpp.md +49 -0
  25. package/.roo/skills/cpp-memory-handling/examples/smart-pointers.md +46 -0
  26. package/.roo/skills/cpp-modern-composition/SKILL.md +64 -0
  27. package/.roo/skills/cpp-modern-composition/examples/composition.md +51 -0
  28. package/.roo/skills/cpp-robust-interfaces/SKILL.md +55 -0
  29. package/.roo/skills/cpp-robust-interfaces/examples/interfaces.md +56 -0
  30. package/.roo/skills/create-hardened-docker-skill/SKILL.md +637 -0
  31. package/.roo/skills/create-hardened-docker-skill/scripts/create-all.sh +489 -0
  32. package/.roo/skills/csharp-best-practices/SKILL.md +278 -0
  33. package/.roo/skills/docker-hardening-verification/SKILL.md +28 -0
  34. package/.roo/skills/docker-hardening-verification/scripts/verify-hardening.sh +39 -0
  35. package/.roo/skills/docker-image-signing/SKILL.md +28 -0
  36. package/.roo/skills/docker-image-signing/scripts/sign-image.sh +33 -0
  37. package/.roo/skills/document-revision-history/SKILL.md +104 -0
  38. package/.roo/skills/git-workflow-skill/SKILL.md +194 -0
  39. package/.roo/skills/git-workflow-skill/hooks/commit-msg +61 -0
  40. package/.roo/skills/git-workflow-skill/hooks/pre-commit +38 -0
  41. package/.roo/skills/git-workflow-skill/hooks/prepare-commit-msg +56 -0
  42. package/.roo/skills/git-workflow-skill/scripts/finish-feature.sh +192 -0
  43. package/.roo/skills/git-workflow-skill/scripts/install-hooks.sh +55 -0
  44. package/.roo/skills/git-workflow-skill/scripts/start-feature.sh +110 -0
  45. package/.roo/skills/git-workflow-skill/scripts/validate-workflow.sh +229 -0
  46. package/.roo/skills/js-ts-dependency-mgmt/SKILL.md +49 -0
  47. package/.roo/skills/js-ts-dependency-mgmt/examples/dependency_mgmt.md +60 -0
  48. package/.roo/skills/js-ts-security-skill/SKILL.md +64 -0
  49. package/.roo/skills/js-ts-security-skill/scripts/verify-security.sh +136 -0
  50. package/.roo/skills/logging-best-practices/SKILL.md +50 -0
  51. package/.roo/skills/logging-best-practices/examples/cpp.md +36 -0
  52. package/.roo/skills/logging-best-practices/examples/csharp.md +49 -0
  53. package/.roo/skills/logging-best-practices/examples/javascript.md +77 -0
  54. package/.roo/skills/logging-best-practices/examples/python.md +57 -0
  55. package/.roo/skills/logging-best-practices/references/logging-standards.md +29 -0
  56. package/.roo/skills/open-presentation/SKILL.md +35 -0
  57. package/.roo/skills/opentelemetry-best-practices/SKILL.md +34 -0
  58. package/.roo/skills/opentelemetry-best-practices/examples/go.md +32 -0
  59. package/.roo/skills/opentelemetry-best-practices/examples/javascript.md +58 -0
  60. package/.roo/skills/opentelemetry-best-practices/examples/python.md +37 -0
  61. package/.roo/skills/opentelemetry-best-practices/references/otel-standards.md +37 -0
  62. package/.roo/skills/python-best-practices/SKILL.md +385 -0
  63. package/.roo/skills/python-dependency-mgmt/SKILL.md +42 -0
  64. package/.roo/skills/python-dependency-mgmt/examples/dependency_mgmt.md +67 -0
  65. package/.roo/skills/python-security-skill/SKILL.md +56 -0
  66. package/.roo/skills/python-security-skill/examples/security.md +56 -0
  67. package/.roo/skills/self-signed-cert/SKILL.md +42 -0
  68. package/.roo/skills/self-signed-cert/scripts/generate-cert.ps1 +45 -0
  69. package/.roo/skills/self-signed-cert/scripts/generate-cert.sh +43 -0
  70. package/.roo/skills/skill-creator/SKILL.md +196 -0
  71. package/.roo/skills/skill-creator/references/output-patterns.md +82 -0
  72. package/.roo/skills/skill-creator/references/workflows.md +28 -0
  73. package/.roo/skills/skill-creator/scripts/init_skill.py +208 -0
  74. package/.roo/skills/skill-creator/scripts/package_skill.py +99 -0
  75. package/.roo/skills/skill-creator/scripts/quick_validate.py +113 -0
  76. package/.roo/skills/story-status-lookup/SKILL.md +78 -0
  77. package/.roo/skills/test-accompanied-development/SKILL.md +50 -0
  78. package/.roo/skills/test-generator/SKILL.md +65 -0
  79. package/.roo/skills/vercel-react-best-practices/SKILL.md +109 -0
  80. package/.roo/skills/verify-hardened-docker-skill/SKILL.md +442 -0
  81. package/.roo/skills/verify-hardened-docker-skill/scripts/verify-docker-hardening.sh +439 -0
  82. package/README.md +2 -1
  83. package/lib/agents.js +23 -0
  84. package/package.json +4 -3
  85. package/test/roo-code-agent.test.js +166 -0
  86. package/test/roo-code-injection.test.js +172 -0
@@ -0,0 +1,439 @@
1
+ #!/bin/bash
2
+ #
3
+ # verify-docker-hardening.sh
4
+ # Comprehensive Docker security verification script
5
+ # Checks Dockerfile, docker-compose.yml, and running containers
6
+ # against CIS, OWASP, and NIST standards
7
+ #
8
+
9
+ set -e
10
+
11
+ # Colors for output
12
+ RED='\033[0;31m'
13
+ GREEN='\033[0;32m'
14
+ YELLOW='\033[1;33m'
15
+ BLUE='\033[0;34m'
16
+ NC='\033[0m' # No Color
17
+
18
+ # Configuration
19
+ IMAGE_NAME="${1:-contacts-app}"
20
+ CONTAINER_NAME="${2:-contacts-app}"
21
+ EXIT_CODE=0
22
+
23
+ # Counters
24
+ TOTAL_CHECKS=0
25
+ PASSED_CHECKS=0
26
+ FAILED_CHECKS=0
27
+ WARNING_CHECKS=0
28
+
29
+ # Helper functions
30
+ print_header() {
31
+ echo ""
32
+ echo -e "${BLUE}========================================${NC}"
33
+ echo -e "${BLUE}$1${NC}"
34
+ echo -e "${BLUE}========================================${NC}"
35
+ }
36
+
37
+ print_check() {
38
+ TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
39
+ echo -ne " [$TOTAL_CHECKS] $1... "
40
+ }
41
+
42
+ pass() {
43
+ PASSED_CHECKS=$((PASSED_CHECKS + 1))
44
+ echo -e "${GREEN}✅ PASS${NC}"
45
+ }
46
+
47
+ fail() {
48
+ FAILED_CHECKS=$((FAILED_CHECKS + 1))
49
+ EXIT_CODE=2
50
+ echo -e "${RED}❌ FAIL${NC}"
51
+ [ -n "$1" ] && echo -e " ${RED}→ $1${NC}"
52
+ }
53
+
54
+ warn() {
55
+ WARNING_CHECKS=$((WARNING_CHECKS + 1))
56
+ echo -e "${YELLOW}⚠️ WARN${NC}"
57
+ [ -n "$1" ] && echo -e " ${YELLOW}→ $1${NC}"
58
+ }
59
+
60
+ critical() {
61
+ FAILED_CHECKS=$((FAILED_CHECKS + 1))
62
+ EXIT_CODE=1
63
+ echo -e "${RED}🚨 CRITICAL${NC}"
64
+ [ -n "$1" ] && echo -e " ${RED}→ $1${NC}"
65
+ }
66
+
67
+ # Start verification
68
+ echo -e "${BLUE}🔍 Docker Security Verification${NC}"
69
+ echo -e "${BLUE}================================${NC}"
70
+ echo "Image: $IMAGE_NAME"
71
+ echo "Container: $CONTAINER_NAME"
72
+
73
+ # ============================================================================
74
+ # 1. Dockerfile Verification
75
+ # ============================================================================
76
+ print_header "1. Dockerfile Security"
77
+
78
+ if [ ! -f "Dockerfile" ]; then
79
+ print_check "Dockerfile exists"
80
+ critical "Dockerfile not found in current directory"
81
+ EXIT_CODE=5
82
+ exit $EXIT_CODE
83
+ fi
84
+
85
+ # Check for specific version tags
86
+ print_check "Specific version tags (no :latest)"
87
+ if grep -qE "^FROM.*:(latest|alpine)$" Dockerfile; then
88
+ fail "Using :latest or unversioned :alpine tag"
89
+ else
90
+ pass
91
+ fi
92
+
93
+ # Check for non-root user
94
+ print_check "Non-root user configured"
95
+ if grep -qE "^USER (root|[0-9]+)" Dockerfile; then
96
+ if grep -qE "^USER root" Dockerfile; then
97
+ fail "Running as root user"
98
+ else
99
+ pass
100
+ fi
101
+ elif grep -qE "^USER [a-zA-Z]" Dockerfile; then
102
+ pass
103
+ else
104
+ fail "No USER directive found"
105
+ fi
106
+
107
+ # Check for HEALTHCHECK
108
+ print_check "HEALTHCHECK instruction"
109
+ if grep -q "^HEALTHCHECK" Dockerfile; then
110
+ pass
111
+ else
112
+ warn "Missing HEALTHCHECK instruction"
113
+ fi
114
+
115
+ # Check for hardcoded secrets
116
+ print_check "No hardcoded secrets in ENV/ARG"
117
+ if grep -iE "^(ENV|ARG).*(SECRET|PASSWORD|KEY|TOKEN|CLIENT_ID)=" Dockerfile | grep -v "REACT_APP_API_BASE_URL" > /dev/null; then
118
+ fail "Potential hardcoded secrets found"
119
+ else
120
+ pass
121
+ fi
122
+
123
+ # Check for multi-stage build
124
+ print_check "Multi-stage build pattern"
125
+ if [ $(grep -c "^FROM" Dockerfile) -ge 2 ]; then
126
+ pass
127
+ else
128
+ warn "Not using multi-stage build"
129
+ fi
130
+
131
+ # Check for Alpine base images
132
+ print_check "Minimal Alpine base images"
133
+ if grep -qE "^FROM.*alpine" Dockerfile; then
134
+ pass
135
+ else
136
+ warn "Not using Alpine base images"
137
+ fi
138
+
139
+ # ============================================================================
140
+ # 2. docker-compose.yml Verification
141
+ # ============================================================================
142
+ print_header "2. docker-compose.yml Security"
143
+
144
+ if [ ! -f "docker-compose.yml" ]; then
145
+ print_check "docker-compose.yml exists"
146
+ warn "docker-compose.yml not found (optional)"
147
+ else
148
+ # Check for read-only filesystem
149
+ print_check "Read-only root filesystem"
150
+ if grep -q "read_only: true" docker-compose.yml; then
151
+ pass
152
+ else
153
+ fail "Missing read_only: true"
154
+ fi
155
+
156
+ # Check for no-new-privileges
157
+ print_check "No new privileges"
158
+ if grep -q "no-new-privileges:true" docker-compose.yml; then
159
+ pass
160
+ else
161
+ fail "Missing security_opt: no-new-privileges:true"
162
+ fi
163
+
164
+ # Check for capability dropping
165
+ print_check "Capabilities dropped"
166
+ if grep -q "cap_drop:" docker-compose.yml; then
167
+ pass
168
+ else
169
+ fail "Missing cap_drop configuration"
170
+ fi
171
+
172
+ # Check for tmpfs mounts
173
+ print_check "Tmpfs mounts for writable dirs"
174
+ if grep -q "tmpfs:" docker-compose.yml; then
175
+ pass
176
+ else
177
+ fail "Missing tmpfs mounts"
178
+ fi
179
+
180
+ # Check for resource limits
181
+ print_check "Memory limits configured"
182
+ if grep -q "memory:" docker-compose.yml; then
183
+ pass
184
+ else
185
+ warn "Missing memory limits"
186
+ fi
187
+
188
+ print_check "CPU limits configured"
189
+ if grep -q "cpus:" docker-compose.yml; then
190
+ pass
191
+ else
192
+ warn "Missing CPU limits"
193
+ fi
194
+
195
+ # Check for health check
196
+ print_check "Healthcheck configured"
197
+ if grep -q "healthcheck:" docker-compose.yml; then
198
+ pass
199
+ else
200
+ warn "Missing healthcheck configuration"
201
+ fi
202
+
203
+ # Check for privileged mode
204
+ print_check "Not running in privileged mode"
205
+ if grep -q "privileged: true" docker-compose.yml; then
206
+ critical "Running in privileged mode"
207
+ else
208
+ pass
209
+ fi
210
+ fi
211
+
212
+ # ============================================================================
213
+ # 3. .dockerignore Verification
214
+ # ============================================================================
215
+ print_header "3. .dockerignore Configuration"
216
+
217
+ if [ ! -f ".dockerignore" ]; then
218
+ print_check ".dockerignore exists"
219
+ warn ".dockerignore not found"
220
+ else
221
+ print_check ".env excluded from Docker context"
222
+ if grep -qE "^\.env$" .dockerignore; then
223
+ pass
224
+ else
225
+ critical ".env not in .dockerignore - secret leakage risk!"
226
+ fi
227
+
228
+ print_check "node_modules excluded"
229
+ if grep -q "node_modules" .dockerignore; then
230
+ pass
231
+ else
232
+ warn "node_modules not excluded"
233
+ fi
234
+
235
+ print_check ".git excluded"
236
+ if grep -qE "^\.git" .dockerignore; then
237
+ pass
238
+ else
239
+ warn ".git not excluded"
240
+ fi
241
+ fi
242
+
243
+ # ============================================================================
244
+ # 4. Image Security Scanning
245
+ # ============================================================================
246
+ print_header "4. Image Vulnerability Scanning"
247
+
248
+ # Check if image exists
249
+ if ! docker images --format "{{.Repository}}" | grep -q "^${IMAGE_NAME}$"; then
250
+ print_check "Docker image exists"
251
+ warn "Image '$IMAGE_NAME' not found locally. Skipping image scans."
252
+ echo " Run 'docker build -t $IMAGE_NAME .' to build the image."
253
+ else
254
+ # Check if trivy is installed
255
+ if ! command -v trivy &> /dev/null; then
256
+ print_check "Trivy scanner installed"
257
+ warn "Trivy not installed. Skipping vulnerability scans."
258
+ echo " Install: brew install aquasecurity/trivy/trivy (macOS)"
259
+ echo " : apt-get install trivy (Linux)"
260
+ else
261
+ # Scan for CRITICAL vulnerabilities
262
+ print_check "No CRITICAL vulnerabilities"
263
+ if trivy image --quiet --severity CRITICAL --exit-code 1 "$IMAGE_NAME" > /dev/null 2>&1; then
264
+ pass
265
+ else
266
+ critical "CRITICAL vulnerabilities found. Run: trivy image --severity CRITICAL $IMAGE_NAME"
267
+ fi
268
+
269
+ # Scan for HIGH vulnerabilities
270
+ print_check "No HIGH vulnerabilities"
271
+ if trivy image --quiet --severity HIGH --exit-code 1 "$IMAGE_NAME" > /dev/null 2>&1; then
272
+ pass
273
+ else
274
+ fail "HIGH vulnerabilities found. Run: trivy image --severity HIGH $IMAGE_NAME"
275
+ fi
276
+
277
+ # Scan for leaked secrets
278
+ print_check "No leaked secrets in image"
279
+ if trivy image --quiet --scanners secret --exit-code 1 "$IMAGE_NAME" > /dev/null 2>&1; then
280
+ pass
281
+ else
282
+ critical "Secrets detected in image! Run: trivy image --scanners secret $IMAGE_NAME"
283
+ fi
284
+ fi
285
+
286
+ # Check image size
287
+ print_check "Optimized image size (< 100MB)"
288
+ IMAGE_SIZE=$(docker images --format "{{.Size}}" "$IMAGE_NAME" | head -1 | sed 's/MB//' | sed 's/GB/*1024/' | bc 2>/dev/null || echo "0")
289
+ if [ -n "$IMAGE_SIZE" ] && [ "$IMAGE_SIZE" != "0" ]; then
290
+ if (( $(echo "$IMAGE_SIZE < 100" | bc -l) )); then
291
+ pass
292
+ else
293
+ warn "Image size is ${IMAGE_SIZE}MB (recommended < 100MB)"
294
+ fi
295
+ else
296
+ warn "Could not determine image size"
297
+ fi
298
+
299
+ # Check for .env in image
300
+ print_check ".env file not baked into image"
301
+ if docker run --rm "$IMAGE_NAME" sh -c "ls -la / 2>/dev/null | grep -q .env"; then
302
+ critical ".env file found in image! Secrets leaked!"
303
+ else
304
+ pass
305
+ fi
306
+ fi
307
+
308
+ # ============================================================================
309
+ # 5. Runtime Security (if container is running)
310
+ # ============================================================================
311
+ print_header "5. Runtime Security Verification"
312
+
313
+ if ! docker ps --filter "name=$CONTAINER_NAME" --format "{{.Names}}" | grep -q "^$CONTAINER_NAME$"; then
314
+ echo -e "${YELLOW} Container '$CONTAINER_NAME' is not running.${NC}"
315
+ echo -e "${YELLOW} Run 'docker-compose up -d' to enable runtime checks.${NC}"
316
+ else
317
+ # Check container runs as non-root
318
+ print_check "Container runs as non-root"
319
+ CONTAINER_USER=$(docker exec "$CONTAINER_NAME" whoami 2>/dev/null || echo "root")
320
+ if [ "$CONTAINER_USER" != "root" ]; then
321
+ pass
322
+ else
323
+ critical "Container running as root user!"
324
+ fi
325
+
326
+ # Check user ID is not 0
327
+ print_check "User ID is not 0 (root)"
328
+ USER_ID=$(docker exec "$CONTAINER_NAME" id -u 2>/dev/null || echo "0")
329
+ if [ "$USER_ID" != "0" ]; then
330
+ pass
331
+ else
332
+ critical "Container running with UID 0 (root)!"
333
+ fi
334
+
335
+ # Check read-only filesystem
336
+ print_check "Root filesystem is read-only"
337
+ if docker exec "$CONTAINER_NAME" sh -c "touch /test 2>/dev/null"; then
338
+ fail "Root filesystem is writable"
339
+ docker exec "$CONTAINER_NAME" sh -c "rm /test 2>/dev/null" || true
340
+ else
341
+ pass
342
+ fi
343
+
344
+ # Check tmpfs is writable
345
+ print_check "Tmpfs mount is writable"
346
+ if docker exec "$CONTAINER_NAME" sh -c "touch /tmp/test 2>/dev/null && rm /tmp/test 2>/dev/null"; then
347
+ pass
348
+ else
349
+ warn "Tmpfs mount /tmp is not writable"
350
+ fi
351
+
352
+ # Check health status
353
+ print_check "Container is healthy"
354
+ HEALTH_STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "none")
355
+ if [ "$HEALTH_STATUS" = "healthy" ]; then
356
+ pass
357
+ elif [ "$HEALTH_STATUS" = "none" ]; then
358
+ warn "No health check configured"
359
+ else
360
+ fail "Container health status: $HEALTH_STATUS"
361
+ fi
362
+
363
+ # Check capabilities
364
+ print_check "Capabilities dropped"
365
+ CAPS_DROPPED=$(docker inspect --format='{{.HostConfig.CapDrop}}' "$CONTAINER_NAME" 2>/dev/null || echo "[]")
366
+ if echo "$CAPS_DROPPED" | grep -q "ALL"; then
367
+ pass
368
+ else
369
+ warn "Not all capabilities dropped"
370
+ fi
371
+
372
+ # Check memory limit
373
+ print_check "Memory limit enforced"
374
+ MEMORY_LIMIT=$(docker inspect --format='{{.HostConfig.Memory}}' "$CONTAINER_NAME" 2>/dev/null || echo "0")
375
+ if [ "$MEMORY_LIMIT" != "0" ]; then
376
+ pass
377
+ else
378
+ warn "No memory limit set"
379
+ fi
380
+ fi
381
+
382
+ # ============================================================================
383
+ # 6. Git Secret Protection
384
+ # ============================================================================
385
+ print_header "6. Git Secret Protection"
386
+
387
+ if [ -d ".git" ]; then
388
+ # Check .env in .gitignore
389
+ print_check ".env in .gitignore"
390
+ if [ -f ".gitignore" ] && grep -qE "^\.env$" .gitignore; then
391
+ pass
392
+ else
393
+ critical ".env not in .gitignore! Secrets may be committed!"
394
+ fi
395
+
396
+ # Check .env.example exists
397
+ print_check ".env.example exists (template)"
398
+ if [ -f ".env.example" ]; then
399
+ pass
400
+ else
401
+ warn ".env.example not found"
402
+ fi
403
+
404
+ # Check if .env is committed
405
+ print_check ".env not committed to git"
406
+ if git ls-files --error-unmatch .env > /dev/null 2>&1; then
407
+ critical ".env is tracked by git! Remove immediately!"
408
+ else
409
+ pass
410
+ fi
411
+ else
412
+ echo -e "${YELLOW} Not a git repository. Skipping git checks.${NC}"
413
+ fi
414
+
415
+ # ============================================================================
416
+ # Summary
417
+ # ============================================================================
418
+ print_header "Verification Summary"
419
+
420
+ echo ""
421
+ echo " Total checks: $TOTAL_CHECKS"
422
+ echo -e " ${GREEN}Passed: $PASSED_CHECKS${NC}"
423
+ echo -e " ${YELLOW}Warnings: $WARNING_CHECKS${NC}"
424
+ echo -e " ${RED}Failed: $FAILED_CHECKS${NC}"
425
+ echo ""
426
+
427
+ if [ $FAILED_CHECKS -eq 0 ]; then
428
+ echo -e "${GREEN}✅ All critical security checks passed!${NC}"
429
+ if [ $WARNING_CHECKS -gt 0 ]; then
430
+ echo -e "${YELLOW}⚠️ $WARNING_CHECKS warning(s) - consider addressing these.${NC}"
431
+ fi
432
+ else
433
+ echo -e "${RED}❌ $FAILED_CHECKS security check(s) failed!${NC}"
434
+ echo -e "${RED} Please fix the issues above before deploying.${NC}"
435
+ fi
436
+
437
+ echo ""
438
+
439
+ exit $EXIT_CODE
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ma-agents
2
2
 
3
- A universal NPX tool to install AI coding agent skills. Write skills once, install them across Claude Code, Gemini, Copilot, Cline, Cursor, and Kilocode.
3
+ A universal NPX tool to install AI coding agent skills. Write skills once, install them across Claude Code, Gemini, Copilot, Cline, Cursor, Kilocode, and Roo Code.
4
4
 
5
5
  ## Installation & Usage
6
6
 
@@ -115,6 +115,7 @@ The file is version-controlled as part of `_bmad-output/` project knowledge. Com
115
115
  | Cursor | `.cursor/skills/` | `generic` | `.cursor/cursor.md` |
116
116
  | Kilocode | `.kilocode/skills/` | `generic` | `.kilocode/kilocode.md` |
117
117
  | Cline | `.cline/skills/` | `cline` | `.cline/clinerules.md` |
118
+ | Roo Code | `.roo/skills/` | `generic` | `.roo/rules/00-ma-agents.md` |
118
119
  | SRE Agent (Alex) | `_bmad/skills/` | `generic` | `_bmad/bmm/agents/sre.md` |
119
120
  | DevOps Agent (Amit) | `_bmad/skills/` | `generic` | `_bmad/bmm/agents/devops.md` |
120
121
  | Cyber Analyst (Yael) | `_bmad/skills/` | `generic` | `_bmad/bmm/agents/cyber.md` |
package/lib/agents.js CHANGED
@@ -135,6 +135,29 @@ const agents = [
135
135
  instructionFiles: ['.cline/clinerules.md', '.clinerules'],
136
136
  injectionStrategy: { position: 'top', skipPatterns: ['---'] }
137
137
  },
138
+ {
139
+ id: 'roo-code',
140
+ name: 'Roo Code',
141
+ version: '1.0.0',
142
+ category: 'ide',
143
+ description: 'Roo Code AI Assistant (Enhanced Cline fork)',
144
+ skillsDir: '.roo/skills',
145
+ getProjectPath: () => path.join(process.cwd(), '.roo', 'skills'),
146
+ getGlobalPath: () => {
147
+ const platform = os.platform();
148
+ if (platform === 'win32') {
149
+ return path.join(os.homedir(), 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'skills');
150
+ } else if (platform === 'darwin') {
151
+ return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'skills');
152
+ } else {
153
+ return path.join(os.homedir(), '.config', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'skills');
154
+ }
155
+ },
156
+ fileExtension: '.md',
157
+ template: 'generic',
158
+ instructionFiles: ['.roo/rules/00-ma-agents.md'],
159
+ injectionStrategy: { position: 'top', skipPatterns: ['---'] }
160
+ },
138
161
  {
139
162
  id: 'cursor',
140
163
  name: 'Cursor',
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "ma-agents",
3
- "version": "3.2.0",
4
- "description": "NPX tool to install skills for AI coding agents (Claude Code, Gemini, Copilot, Kilocode, Cline, Cursor)",
3
+ "version": "3.3.0",
4
+ "description": "NPX tool to install skills for AI coding agents (Claude Code, Gemini, Copilot, Kilocode, Cline, Cursor, Roo Code)",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "ma-agents": "bin/cli.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node bin/cli.js",
11
- "test": "node test/yes-flag.test.js && node test/skill-authoring.test.js && node test/skill-validation.test.js && node test/skill-mandatory.test.js && node test/skill-customize-agent.test.js && node test/create-agent.test.js && node test/generate-project-context.test.js && node test/build-bmad-args.test.js && node test/bmad-version-bump.test.js && node test/extension-module-restructure.test.js && node test/convert-agents-to-skills.test.js && node test/migration.test.js && node test/migration-validation.test.js && node test/story-15-5-workflow-skills.test.js && node test/repo-layout.test.js && node test/config-storage.test.js && node test/cross-repo-validation.test.js && node test/config-lost-on-update.test.js && node test/portable-paths.test.js && node test/cicd-remote-mode.test.js && node test/config-layout.test.js",
11
+ "test": "node test/yes-flag.test.js && node test/skill-authoring.test.js && node test/skill-validation.test.js && node test/skill-mandatory.test.js && node test/skill-customize-agent.test.js && node test/create-agent.test.js && node test/generate-project-context.test.js && node test/build-bmad-args.test.js && node test/bmad-version-bump.test.js && node test/extension-module-restructure.test.js && node test/convert-agents-to-skills.test.js && node test/migration.test.js && node test/migration-validation.test.js && node test/story-15-5-workflow-skills.test.js && node test/repo-layout.test.js && node test/config-storage.test.js && node test/cross-repo-validation.test.js && node test/config-lost-on-update.test.js && node test/portable-paths.test.js && node test/cicd-remote-mode.test.js && node test/config-layout.test.js && node test/roo-code-agent.test.js && node test/roo-code-injection.test.js",
12
12
  "build:bmad-cache": "node scripts/build-bmad-cache.js"
13
13
  },
14
14
  "keywords": [
@@ -21,6 +21,7 @@
21
21
  "kilocode",
22
22
  "cline",
23
23
  "cursor",
24
+ "roo-code",
24
25
  "npx"
25
26
  ],
26
27
  "author": "",
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Tests for Story 18.1: Register Roo Code Agent in Registry
4
+ *
5
+ * Validates that the roo-code agent entry conforms to the required schema
6
+ * and resolves correctly through the existing config-driven registry pattern.
7
+ */
8
+ 'use strict';
9
+
10
+ const assert = require('assert');
11
+
12
+ let passed = 0;
13
+ let failed = 0;
14
+ const errors = [];
15
+
16
+ function test(name, fn) {
17
+ try {
18
+ fn();
19
+ console.log(` \u2713 ${name}`);
20
+ passed++;
21
+ } catch (err) {
22
+ console.error(` \u2717 ${name}: ${err.message}`);
23
+ failed++;
24
+ errors.push({ name, error: err.message });
25
+ }
26
+ }
27
+
28
+ const { getAllAgents, getAgent, getAgentsByCategory } = require('../lib/agents');
29
+ const allAgents = getAllAgents();
30
+ const rooCode = getAgent('roo-code');
31
+
32
+ // --- Schema validation ---
33
+
34
+ console.log('\nRoo Code agent schema validation');
35
+
36
+ test('roo-code agent exists in registry', () => {
37
+ assert.ok(rooCode, 'roo-code agent should exist in the registry');
38
+ });
39
+
40
+ test('id is "roo-code"', () => {
41
+ assert.strictEqual(rooCode.id, 'roo-code');
42
+ });
43
+
44
+ test('name is "Roo Code"', () => {
45
+ assert.strictEqual(rooCode.name, 'Roo Code');
46
+ });
47
+
48
+ test('category is "ide"', () => {
49
+ assert.strictEqual(rooCode.category, 'ide');
50
+ });
51
+
52
+ test('getProjectPath is a function', () => {
53
+ assert.strictEqual(typeof rooCode.getProjectPath, 'function',
54
+ 'getProjectPath should be a function');
55
+ });
56
+
57
+ test('getGlobalPath is a function', () => {
58
+ assert.strictEqual(typeof rooCode.getGlobalPath, 'function',
59
+ 'getGlobalPath should be a function');
60
+ });
61
+
62
+ test('getProjectPath returns path ending in .roo/skills', () => {
63
+ const projectPath = rooCode.getProjectPath();
64
+ assert.ok(
65
+ projectPath.endsWith('.roo/skills') || projectPath.endsWith('.roo\\skills'),
66
+ `Expected path ending in .roo/skills, got: ${projectPath}`
67
+ );
68
+ });
69
+
70
+ test('getGlobalPath returns a non-empty string', () => {
71
+ const globalPath = rooCode.getGlobalPath();
72
+ assert.strictEqual(typeof globalPath, 'string');
73
+ assert.ok(globalPath.length > 0, 'getGlobalPath should return a non-empty string');
74
+ });
75
+
76
+ test('getGlobalPath contains roo-cline (VS Code extension storage convention)', () => {
77
+ const globalPath = rooCode.getGlobalPath();
78
+ assert.ok(
79
+ globalPath.includes('roo-cline'),
80
+ `Expected global path to contain 'roo-cline', got: ${globalPath}`
81
+ );
82
+ });
83
+
84
+ test('fileExtension is ".md"', () => {
85
+ assert.strictEqual(rooCode.fileExtension, '.md');
86
+ });
87
+
88
+ test('description is a non-empty string', () => {
89
+ assert.strictEqual(typeof rooCode.description, 'string',
90
+ 'description should be a string');
91
+ assert.ok(rooCode.description.length > 0, 'description should not be empty');
92
+ });
93
+
94
+ test('description mentions Cline fork lineage', () => {
95
+ assert.ok(rooCode.description.includes('Cline'),
96
+ `description should mention Cline fork lineage, got: ${rooCode.description}`);
97
+ });
98
+
99
+ test('template is "generic"', () => {
100
+ assert.strictEqual(rooCode.template, 'generic');
101
+ });
102
+
103
+ test('skillsDir is ".roo/skills"', () => {
104
+ assert.strictEqual(rooCode.skillsDir, '.roo/skills');
105
+ });
106
+
107
+ test('instructionFiles is a non-empty array', () => {
108
+ assert.ok(Array.isArray(rooCode.instructionFiles),
109
+ 'instructionFiles should be an array');
110
+ assert.ok(rooCode.instructionFiles.length > 0,
111
+ 'instructionFiles should not be empty');
112
+ });
113
+
114
+ test('instructionFiles targets .roo/rules/ directory', () => {
115
+ const hasRooRules = rooCode.instructionFiles.some(f =>
116
+ f.includes('.roo/rules/') || f.includes('.roo\\rules\\')
117
+ );
118
+ assert.ok(hasRooRules,
119
+ `instructionFiles should target .roo/rules/ directory, got: ${rooCode.instructionFiles}`);
120
+ });
121
+
122
+ test('injectionStrategy is an object', () => {
123
+ assert.ok(rooCode.injectionStrategy && typeof rooCode.injectionStrategy === 'object',
124
+ 'injectionStrategy should be an object');
125
+ });
126
+
127
+ test('injectionStrategy.position is "top"', () => {
128
+ assert.strictEqual(rooCode.injectionStrategy.position, 'top');
129
+ });
130
+
131
+ // --- Registry resolution ---
132
+
133
+ console.log('\nRoo Code agent resolution');
134
+
135
+ test('getAgent("roo-code") returns the agent', () => {
136
+ const resolved = getAgent('roo-code');
137
+ assert.ok(resolved, 'getAgent("roo-code") should return an agent object');
138
+ assert.strictEqual(resolved.id, 'roo-code');
139
+ });
140
+
141
+ test('roo-code appears in getAllAgents()', () => {
142
+ const ids = allAgents.map(a => a.id);
143
+ assert.ok(ids.includes('roo-code'), 'roo-code should appear in getAllAgents()');
144
+ });
145
+
146
+ test('roo-code is in the ide category', () => {
147
+ const ideAgents = getAgentsByCategory('ide');
148
+ const ids = ideAgents.map(a => a.id);
149
+ assert.ok(ids.includes('roo-code'), 'roo-code should be in ide category');
150
+ });
151
+
152
+ test('roo-code is distinct from cline', () => {
153
+ const cline = getAgent('cline');
154
+ assert.ok(cline, 'cline agent should still exist');
155
+ assert.notStrictEqual(cline.id, rooCode.id, 'roo-code and cline should be different agents');
156
+ assert.notStrictEqual(cline.skillsDir, rooCode.skillsDir,
157
+ 'roo-code and cline should use different skills directories');
158
+ });
159
+
160
+ // Print summary
161
+ console.log(`\n${passed} passed, ${failed} failed`);
162
+ if (errors.length > 0) {
163
+ console.log('\nFailed tests:');
164
+ errors.forEach(e => console.log(` - ${e.name}: ${e.error}`));
165
+ }
166
+ if (failed > 0) process.exit(1);