@northbridge-security/secureai 0.1.13

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 (50) hide show
  1. package/.claude/README.md +122 -0
  2. package/.claude/commands/architect/clean.md +978 -0
  3. package/.claude/commands/architect/kiss.md +762 -0
  4. package/.claude/commands/architect/review.md +704 -0
  5. package/.claude/commands/catchup.md +90 -0
  6. package/.claude/commands/code.md +115 -0
  7. package/.claude/commands/commit.md +1218 -0
  8. package/.claude/commands/cover.md +1298 -0
  9. package/.claude/commands/fmea.md +275 -0
  10. package/.claude/commands/kaizen.md +312 -0
  11. package/.claude/commands/pr.md +503 -0
  12. package/.claude/commands/todo.md +99 -0
  13. package/.claude/commands/worktree.md +738 -0
  14. package/.claude/commands/wrapup.md +103 -0
  15. package/LICENSE +183 -0
  16. package/README.md +108 -0
  17. package/dist/cli.js +75634 -0
  18. package/docs/agents/devops-reviewer.md +889 -0
  19. package/docs/agents/kiss-simplifier.md +1088 -0
  20. package/docs/agents/typescript.md +8 -0
  21. package/docs/guides/README.md +109 -0
  22. package/docs/guides/agents.clean.arch.md +244 -0
  23. package/docs/guides/agents.clean.arch.ts.md +1314 -0
  24. package/docs/guides/agents.gotask.md +1037 -0
  25. package/docs/guides/agents.markdown.md +1209 -0
  26. package/docs/guides/agents.onepassword.md +285 -0
  27. package/docs/guides/agents.sonar.md +857 -0
  28. package/docs/guides/agents.tdd.md +838 -0
  29. package/docs/guides/agents.tdd.ts.md +1062 -0
  30. package/docs/guides/agents.typesript.md +1389 -0
  31. package/docs/guides/github-mcp.md +1075 -0
  32. package/package.json +130 -0
  33. package/packages/secureai-cli/src/cli.ts +21 -0
  34. package/tasks/README.md +880 -0
  35. package/tasks/aws.yml +64 -0
  36. package/tasks/bash.yml +118 -0
  37. package/tasks/bun.yml +738 -0
  38. package/tasks/claude.yml +183 -0
  39. package/tasks/docker.yml +420 -0
  40. package/tasks/docs.yml +127 -0
  41. package/tasks/git.yml +1336 -0
  42. package/tasks/gotask.yml +132 -0
  43. package/tasks/json.yml +77 -0
  44. package/tasks/markdown.yml +95 -0
  45. package/tasks/onepassword.yml +350 -0
  46. package/tasks/security.yml +102 -0
  47. package/tasks/sonar.yml +437 -0
  48. package/tasks/template.yml +74 -0
  49. package/tasks/vscode.yml +103 -0
  50. package/tasks/yaml.yml +121 -0
package/tasks/bun.yml ADDED
@@ -0,0 +1,738 @@
1
+ # https://taskfile.dev
2
+
3
+ version: "3"
4
+
5
+ tasks:
6
+ core:info:
7
+ silent: true
8
+ cmds:
9
+ - |
10
+ # Color codes
11
+ GREEN='\033[0;32m'
12
+ YELLOW='\033[0;33m'
13
+ BLUE='\033[0;34m'
14
+ BOLD='\033[1m'
15
+ NC='\033[0m' # No Color
16
+
17
+ echo -e "${BOLD}Development:${NC}"
18
+ echo -e " ${GREEN}task install${NC} ${YELLOW}i${NC} Clean install (frozen lockfile, no scripts)"
19
+ echo -e " ${GREEN}task install:quick${NC} ${YELLOW}iq${NC} Quick install (allows lockfile updates)"
20
+ echo -e " ${GREEN}task build${NC} ${YELLOW}b${NC} Build the project"
21
+ echo -e " ${GREEN}task test${NC} ${YELLOW}t${NC} Run unit tests"
22
+ echo -e " ${GREEN}task test:integration${NC} ${YELLOW}ti${NC} Run integration tests"
23
+ echo -e " ${GREEN}task test:coverage${NC} ${YELLOW}cov${NC} Run tests with coverage FILES=\"...\" or DIFF=true"
24
+ echo -e " ${GREEN}task test:coverage:report${NC} ${YELLOW}covr${NC} Show coverage report with low files THRESHOLD=80 (default)"
25
+ echo -e " ${GREEN}task lint${NC} ${YELLOW}l${NC} Run linter FILES=\"...\""
26
+ echo -e " ${GREEN}task lint:fix${NC} ${YELLOW}lf${NC} Run linter with auto-fix FILES=\"...\""
27
+ echo -e " ${GREEN}task dev${NC} ${YELLOW}d${NC} Run in development mode"
28
+ echo ""
29
+
30
+ install:
31
+ desc: Clean install dependencies (removes caches, uses frozen lockfile, ignores scripts)
32
+ aliases: [i]
33
+ silent: true
34
+ cmds:
35
+ - |
36
+ GREEN='\033[0;32m'
37
+ YELLOW='\033[0;33m'
38
+ NC='\033[0m'
39
+
40
+ # Clean local caches first for reproducible builds
41
+ # See: https://www.lekman.com/blog/2025/11/securing-your-javascript-dependencies-what-every-organisation-needs-to-know/
42
+
43
+ # Remove node_modules
44
+ if [ -d "node_modules" ]; then
45
+ rm -rf node_modules
46
+ echo -e "${GREEN}✓${NC} Removed node_modules/"
47
+ fi
48
+
49
+ # Remove bun's local cache
50
+ if [ -d ".bun" ]; then
51
+ rm -rf .bun
52
+ echo -e "${GREEN}✓${NC} Removed .bun/"
53
+ fi
54
+
55
+ # Remove SST cache (serverless framework)
56
+ if [ -d ".sst" ]; then
57
+ rm -rf .sst
58
+ echo -e "${GREEN}✓${NC} Removed .sst/"
59
+ fi
60
+
61
+ # Remove SST output directory
62
+ if [ -d ".open-next" ]; then
63
+ rm -rf .open-next
64
+ echo -e "${GREEN}✓${NC} Removed .open-next/"
65
+ fi
66
+
67
+ # Secure install with frozen lockfile and no lifecycle scripts
68
+ # --frozen-lockfile: Ensures exact versions from bun.lockb (fails if lockfile needs update)
69
+ # --ignore-scripts: Prevents execution of potentially malicious postinstall scripts
70
+ echo -e "${GREEN}▶${NC} Installing dependencies (frozen lockfile, no scripts)..."
71
+ bun install --frozen-lockfile --ignore-scripts
72
+
73
+ echo -e "${GREEN}✓${NC} Dependencies installed securely"
74
+
75
+ install:quick:
76
+ desc: Quick install (no clean, updates lockfile if needed)
77
+ aliases: [iq]
78
+ silent: true
79
+ cmds:
80
+ - bun install
81
+
82
+ build:
83
+ desc: Build the project
84
+ aliases: [b]
85
+ silent: true
86
+ cmds:
87
+ - bun turbo build
88
+ sources:
89
+ - src/**/*.ts
90
+ generates:
91
+ - dist/**/*.js
92
+
93
+ rebuild:
94
+ aliases: [rb]
95
+ desc: First cleans project build artifacts, logs, and temporary files, then rebuilds the project
96
+ silent: true
97
+ cmds:
98
+ - task clean
99
+ - task install
100
+ - task build
101
+
102
+ clean:
103
+ desc: Clean build artifacts, logs, and temporary files (use venv=true to also delete venv)
104
+ aliases: [c]
105
+ silent: true
106
+ vars:
107
+ DELETE_VENV: '{{.venv | default "false"}}'
108
+ cmds:
109
+ - |
110
+ # Color codes
111
+ GREEN='\033[0;32m'
112
+ YELLOW='\033[0;33m'
113
+ NC='\033[0m'
114
+
115
+ # Get repository root
116
+ REPO_ROOT=$(task git:repo:root)
117
+
118
+ # Clean build artifacts and dependency caches
119
+ rm -rf dist
120
+ rm -rf node_modules
121
+ rm -rf .bun
122
+ rm -rf .sst
123
+ rm -rf .open-next
124
+ echo -e "${GREEN}✓${NC} Removed dist/, node_modules/, .bun/, .sst/, .open-next/"
125
+
126
+ # Clean .logs contents (keep the directory)
127
+ if [ -d .logs ]; then
128
+ find .logs -mindepth 1 -delete
129
+ echo -e "${GREEN}✓${NC} Cleaned .logs/ contents"
130
+ fi
131
+
132
+ # Delete *.local.md files in root folder only (not subfolders)
133
+ # EXCEPT: .bugs.local.md and *.report.local.md
134
+ LOCAL_MD_COUNT=$(find . -maxdepth 1 -name "*.local.md" -type f ! -name ".bugs.local.md" ! -name "*.report.local.md" | wc -l | tr -d ' ')
135
+ if [ "$LOCAL_MD_COUNT" -gt 0 ]; then
136
+ find . -maxdepth 1 -name "*.local.md" -type f ! -name ".bugs.local.md" ! -name "*.report.local.md" -delete
137
+ echo -e "${GREEN}✓${NC} Deleted $LOCAL_MD_COUNT *.local.md file(s) from root (preserved .bugs.local.md and *.report.local.md)"
138
+ fi
139
+
140
+ # Delete *.txt files in root folder only (not subfolders)
141
+ TXT_COUNT=$(find . -maxdepth 1 -name "*.txt" -type f | wc -l | tr -d ' ')
142
+ if [ "$TXT_COUNT" -gt 0 ]; then
143
+ find . -maxdepth 1 -name "*.txt" -type f -delete
144
+ echo -e "${GREEN}✓${NC} Deleted $TXT_COUNT *.txt file(s) from root"
145
+ fi
146
+
147
+ # Delete *.log files in root folder only (not subfolders)
148
+ LOG_COUNT=$(find . -maxdepth 1 -name "*.log" -type f | wc -l | tr -d ' ')
149
+ if [ "$LOG_COUNT" -gt 0 ]; then
150
+ find . -maxdepth 1 -name "*.log" -type f -delete
151
+ echo -e "${GREEN}✓${NC} Deleted $LOG_COUNT *.log file(s) from root"
152
+ fi
153
+
154
+ # Delete TypeScript build cache files (recursive)
155
+ TSBUILDINFO_COUNT=$(find . -name "*.tsbuildinfo" -type f | wc -l | tr -d ' ')
156
+ if [ "$TSBUILDINFO_COUNT" -gt 0 ]; then
157
+ find . -name "*.tsbuildinfo" -type f -delete
158
+ echo -e "${GREEN}✓${NC} Deleted $TSBUILDINFO_COUNT *.tsbuildinfo file(s)"
159
+ fi
160
+
161
+ # Delete .DS_Store files (macOS metadata, recursive)
162
+ DS_STORE_COUNT=$(find . -name ".DS_Store" -type f | wc -l | tr -d ' ')
163
+ if [ "$DS_STORE_COUNT" -gt 0 ]; then
164
+ find . -name ".DS_Store" -type f -delete
165
+ echo -e "${GREEN}✓${NC} Deleted $DS_STORE_COUNT .DS_Store file(s)"
166
+ fi
167
+
168
+ # Optionally delete venv
169
+ {{if eq .DELETE_VENV "true"}}
170
+ if [ -d venv ]; then
171
+ rm -rf venv
172
+ echo -e "${GREEN}✓${NC} Deleted venv/"
173
+ fi
174
+ {{end}}
175
+
176
+ # Delete all git worktrees (except main worktree)
177
+ WORKTREE_COUNT=$(git worktree list --porcelain | grep -c "^worktree " || echo 0)
178
+ # Subtract 1 for the main worktree
179
+ if [ "$WORKTREE_COUNT" -gt 1 ]; then
180
+ REMOVED_COUNT=0
181
+ git worktree list --porcelain | grep "^worktree " | cut -d' ' -f2 | while read -r worktree_path; do
182
+ # Skip main worktree (current directory)
183
+ if [ "$worktree_path" != "$REPO_ROOT" ] && [ "$worktree_path" != "." ]; then
184
+ git worktree remove "$worktree_path" --force 2>/dev/null || true
185
+ REMOVED_COUNT=$((REMOVED_COUNT + 1))
186
+ fi
187
+ done
188
+
189
+ # Prune stale worktree administrative files
190
+ git worktree prune 2>/dev/null || true
191
+
192
+ ACTUAL_COUNT=$((WORKTREE_COUNT - 1))
193
+ echo -e "${GREEN}✓${NC} Removed $ACTUAL_COUNT git worktree(s)"
194
+ fi
195
+
196
+ dev:
197
+ desc: Run in development mode
198
+ aliases: [d]
199
+ silent: true
200
+ cmds:
201
+ - bun run dev
202
+
203
+ lint:
204
+ desc: Run linter
205
+ aliases: [l]
206
+ silent: true
207
+ vars:
208
+ FILE_PATTERN: '{{.FILES | default "."}}'
209
+ deps:
210
+ - task: yaml:lint
211
+ vars: { FILES: "{{.FILES}}" }
212
+ - task: markdown:lint
213
+ vars: { FILES: "{{.FILES}}" }
214
+ cmds:
215
+ - |
216
+ GREEN='\033[0;32m'
217
+ YELLOW='\033[0;33m'
218
+ RED='\033[0;31m'
219
+ NC='\033[0m'
220
+
221
+ # Run typecheck only if FILES not specified (needs full project)
222
+ {{if not .FILES}}
223
+ task typecheck
224
+ {{end}}
225
+
226
+ # Detect ESLint
227
+ HAS_ESLINT=false
228
+ if [ -f "eslint.config.js" ] || [ -f "eslint.config.mjs" ] || [ -f "eslint.config.cjs" ] || \
229
+ [ -f ".eslintrc.js" ] || [ -f ".eslintrc.cjs" ] || [ -f ".eslintrc.json" ] || [ -f ".eslintrc" ] || \
230
+ grep -q '"eslintConfig"' package.json 2>/dev/null; then
231
+ HAS_ESLINT=true
232
+ fi
233
+
234
+ # Detect Biome
235
+ HAS_BIOME=false
236
+ if [ -f "biome.json" ] || [ -f "biome.jsonc" ]; then
237
+ HAS_BIOME=true
238
+ fi
239
+
240
+ # Track overall exit code
241
+ FINAL_EXIT=0
242
+
243
+ # Run ESLint - exits 0 for warnings, 1 for errors
244
+ if [ "$HAS_ESLINT" = true ]; then
245
+ echo -e "${GREEN}▶${NC} Running ESLint..."
246
+ bunx eslint {{.FILE_PATTERN}} || FINAL_EXIT=$?
247
+ fi
248
+
249
+ # Run Biome lint
250
+ if [ "$HAS_BIOME" = true ]; then
251
+ echo -e "${GREEN}▶${NC} Running Biome lint..."
252
+ bunx biome lint {{.FILE_PATTERN}} || FINAL_EXIT=$?
253
+ fi
254
+
255
+ if [ "$HAS_ESLINT" = false ] && [ "$HAS_BIOME" = false ]; then
256
+ echo -e "${YELLOW}⚠${NC} No linter configuration found (ESLint or Biome)"
257
+ fi
258
+
259
+ exit $FINAL_EXIT
260
+
261
+ lint:fix:
262
+ desc: Run linter with auto-fix
263
+ aliases: [lf]
264
+ silent: true
265
+ vars:
266
+ FILE_PATTERN: '{{.FILES | default "."}}'
267
+ deps:
268
+ - task: yaml:lint:fix
269
+ vars: { FILES: "{{.FILES}}" }
270
+ - task: markdown:lint:fix
271
+ vars: { FILES: "{{.FILES}}" }
272
+ cmds:
273
+ - |
274
+ GREEN='\033[0;32m'
275
+ YELLOW='\033[0;33m'
276
+ NC='\033[0m'
277
+
278
+ # Detect ESLint
279
+ HAS_ESLINT=false
280
+ if [ -f "eslint.config.js" ] || [ -f "eslint.config.mjs" ] || [ -f "eslint.config.cjs" ] || \
281
+ [ -f ".eslintrc.js" ] || [ -f ".eslintrc.cjs" ] || [ -f ".eslintrc.json" ] || [ -f ".eslintrc" ] || \
282
+ grep -q '"eslintConfig"' package.json 2>/dev/null; then
283
+ HAS_ESLINT=true
284
+ fi
285
+
286
+ # Detect Biome
287
+ HAS_BIOME=false
288
+ if [ -f "biome.json" ] || [ -f "biome.jsonc" ]; then
289
+ HAS_BIOME=true
290
+ fi
291
+
292
+ # Detect Prettier
293
+ HAS_PRETTIER=false
294
+ if [ -f ".prettierrc" ] || [ -f ".prettierrc.json" ] || [ -f ".prettierrc.js" ] || \
295
+ [ -f ".prettierrc.cjs" ] || [ -f ".prettierrc.mjs" ] || [ -f "prettier.config.js" ] || \
296
+ [ -f "prettier.config.cjs" ] || [ -f "prettier.config.mjs" ] || \
297
+ grep -q '"prettier"' package.json 2>/dev/null; then
298
+ HAS_PRETTIER=true
299
+ fi
300
+
301
+ # Run linters and formatters
302
+ if [ "$HAS_ESLINT" = true ]; then
303
+ echo -e "${GREEN}▶${NC} Running ESLint with auto-fix..."
304
+ bunx eslint --fix {{.FILE_PATTERN}}
305
+ fi
306
+
307
+ if [ "$HAS_BIOME" = true ]; then
308
+ echo -e "${GREEN}▶${NC} Running Biome lint with auto-fix..."
309
+ bunx biome lint --write {{.FILE_PATTERN}}
310
+ fi
311
+
312
+ if [ "$HAS_PRETTIER" = true ]; then
313
+ echo -e "${GREEN}▶${NC} Running Prettier..."
314
+ bunx prettier --write --log-level=warn {{.FILE_PATTERN}}
315
+ fi
316
+
317
+ if [ "$HAS_ESLINT" = false ] && [ "$HAS_BIOME" = false ] && [ "$HAS_PRETTIER" = false ]; then
318
+ echo -e "${YELLOW}⚠${NC} No linter/formatter configuration found (ESLint, Biome, or Prettier)"
319
+ fi
320
+
321
+ format:
322
+ desc: Format code
323
+ aliases: [f]
324
+ silent: true
325
+ cmds:
326
+ - |
327
+ GREEN='\033[0;32m'
328
+ YELLOW='\033[0;33m'
329
+ NC='\033[0m'
330
+
331
+ # Detect Biome
332
+ HAS_BIOME=false
333
+ if [ -f "biome.json" ] || [ -f "biome.jsonc" ]; then
334
+ HAS_BIOME=true
335
+ fi
336
+
337
+ # Detect Prettier
338
+ HAS_PRETTIER=false
339
+ if [ -f ".prettierrc" ] || [ -f ".prettierrc.json" ] || [ -f ".prettierrc.js" ] || \
340
+ [ -f ".prettierrc.cjs" ] || [ -f ".prettierrc.mjs" ] || [ -f "prettier.config.js" ] || \
341
+ [ -f "prettier.config.cjs" ] || [ -f "prettier.config.mjs" ] || \
342
+ grep -q '"prettier"' package.json 2>/dev/null; then
343
+ HAS_PRETTIER=true
344
+ fi
345
+
346
+ # Run formatters (Biome first if both exist, as it's faster)
347
+ if [ "$HAS_BIOME" = true ]; then
348
+ echo -e "${GREEN}▶${NC} Running Biome format..."
349
+ bunx biome format --write .
350
+ fi
351
+
352
+ if [ "$HAS_PRETTIER" = true ]; then
353
+ echo -e "${GREEN}▶${NC} Running Prettier..."
354
+ bunx prettier --write --log-level=warn .
355
+ fi
356
+
357
+ if [ "$HAS_BIOME" = false ] && [ "$HAS_PRETTIER" = false ]; then
358
+ echo -e "${YELLOW}⚠${NC} No formatter configuration found (Biome or Prettier)"
359
+ fi
360
+
361
+ typecheck:
362
+ desc: Type check TypeScript
363
+ aliases: [tc]
364
+ silent: true
365
+ cmds:
366
+ - bun turbo typecheck
367
+
368
+ test:setup-dirs:
369
+ desc: Create test output directories in root and all packages
370
+ internal: true
371
+ silent: true
372
+ cmds:
373
+ - |
374
+ # Create .logs directories in root and each package (turbo runs in package dirs)
375
+ mkdir -p .logs/test-results .logs/coverage
376
+ for pkg in packages/*/; do
377
+ mkdir -p "$pkg/.logs/test-results" "$pkg/coverage"
378
+ done
379
+
380
+ test:
381
+ desc: Run unit tests (excludes integration tests)
382
+ aliases: [t]
383
+ silent: true
384
+ deps: [test:setup-dirs]
385
+ cmds:
386
+ - |
387
+ source "$(task op:export)"
388
+ bun turbo test
389
+
390
+ test:integration:
391
+ desc: Run integration tests (tests that interact with real external systems)
392
+ aliases: [ti]
393
+ silent: true
394
+ deps: [test:setup-dirs]
395
+ cmds:
396
+ - |
397
+ source "$(task op:export)"
398
+ bun turbo test:integration
399
+
400
+ test:coverage:
401
+ desc: Run tests with coverage (use FILES="pattern" or DIFF=true for staged files)
402
+ aliases: [cov]
403
+ silent: true
404
+ deps: [test:setup-dirs]
405
+ vars:
406
+ FILE_PATTERN: '{{.FILES | default ""}}'
407
+ DIFF_MODE: '{{.DIFF | default "false"}}'
408
+ cmds:
409
+ - |
410
+ # Export 1Password secrets
411
+ source "$(task op:export)"
412
+
413
+ EXIT_CODE=0
414
+
415
+ if [ "{{.DIFF_MODE}}" = "true" ]; then
416
+ FILE_LIST=$(git diff --staged --name-only --diff-filter=d '*.ts' '*.tsx' | tr '\n' ' ')
417
+ if [ -z "$FILE_LIST" ]; then
418
+ echo "No TypeScript files staged"
419
+ exit 0
420
+ fi
421
+ export FILES="$FILE_LIST"
422
+ bun run test:coverage || EXIT_CODE=$?
423
+ elif [ -n "{{.FILE_PATTERN}}" ]; then
424
+ export FILES="{{.FILE_PATTERN}}"
425
+ bun run test:coverage || EXIT_CODE=$?
426
+ else
427
+ # Use turbo for full coverage run (caching, parallelism)
428
+ bunx turbo test:coverage || EXIT_CODE=$?
429
+ fi
430
+
431
+ # Always show report, then exit with original exit code
432
+ task test:coverage:report
433
+ exit $EXIT_CODE
434
+
435
+ test:coverage:report:
436
+ desc: Display coverage report summary from existing test results
437
+ aliases: [covr]
438
+ silent: true
439
+ vars:
440
+ THRESHOLD: '{{.THRESHOLD | default "80"}}'
441
+ cmds:
442
+ - |
443
+ # Color codes
444
+ RED='\033[0;31m'
445
+ YELLOW='\033[0;33m'
446
+ GREEN='\033[0;32m'
447
+ CYAN='\033[0;36m'
448
+ BOLD='\033[1m'
449
+ NC='\033[0m'
450
+
451
+ # Ensure .tmp directory exists for temp files
452
+ mkdir -p .tmp
453
+
454
+ # Find all lcov files in packages (monorepo) or root
455
+ # Excludes .logs/ directory (old debug runs)
456
+ LCOV_FILES=$(find . -path "./packages/*/coverage/lcov.info" -o -path "./coverage/lcov.info" 2>/dev/null | grep -v node_modules | grep -v "\.logs/")
457
+
458
+ echo ""
459
+ echo -e "${BOLD}Coverage Report${NC}"
460
+ echo ""
461
+
462
+ if [ -n "$LCOV_FILES" ]; then
463
+ # Aggregate coverage from all lcov files
464
+ TOTAL_HIT=0
465
+ TOTAL_FOUND=0
466
+
467
+ for LCOV_FILE in $LCOV_FILES; do
468
+ HIT=$(grep "^LH:" "$LCOV_FILE" 2>/dev/null | awk -F: '{sum+=$2} END {print sum+0}')
469
+ FOUND=$(grep "^LF:" "$LCOV_FILE" 2>/dev/null | awk -F: '{sum+=$2} END {print sum+0}')
470
+ TOTAL_HIT=$((TOTAL_HIT + HIT))
471
+ TOTAL_FOUND=$((TOTAL_FOUND + FOUND))
472
+
473
+ # Extract package name from path (handle root ./coverage/lcov.info)
474
+ # Check for root patterns first, before removing suffixes
475
+ PKG_NAME=$(echo "$LCOV_FILE" | sed -e 's|^\./coverage/lcov\.info$|root|' -e 's|^\./\.logs/coverage/lcov\.info$|root|' -e 's|^\./packages/||' -e 's|/coverage/lcov\.info$||')
476
+ if [ "$FOUND" -gt 0 ]; then
477
+ PKG_PCT=$(awk "BEGIN {printf \"%.1f\", ($HIT/$FOUND)*100}")
478
+ # Color code: green >= 80%, yellow >= 50%, red < 50%
479
+ if awk "BEGIN {exit !($PKG_PCT >= 80)}"; then
480
+ PKG_COLOR="$GREEN"
481
+ elif awk "BEGIN {exit !($PKG_PCT >= 50)}"; then
482
+ PKG_COLOR="$YELLOW"
483
+ else
484
+ PKG_COLOR="$RED"
485
+ fi
486
+ echo -e " ${CYAN}${PKG_NAME}:${NC} ${HIT}/${FOUND} (${PKG_COLOR}${PKG_PCT}%${NC})"
487
+ fi
488
+ done
489
+
490
+ if [ "$TOTAL_FOUND" -gt 0 ]; then
491
+ COVERAGE_PCT=$(awk "BEGIN {printf \"%.1f\", ($TOTAL_HIT/$TOTAL_FOUND)*100}")
492
+ # Color code total
493
+ if awk "BEGIN {exit !($COVERAGE_PCT >= 80)}"; then
494
+ TOTAL_COLOR="$GREEN"
495
+ elif awk "BEGIN {exit !($COVERAGE_PCT >= 50)}"; then
496
+ TOTAL_COLOR="$YELLOW"
497
+ else
498
+ TOTAL_COLOR="$RED"
499
+ fi
500
+ echo ""
501
+ echo -e " ${BOLD}Total:${NC} ${TOTAL_HIT}/${TOTAL_FOUND} (${TOTAL_COLOR}${COVERAGE_PCT}%${NC})"
502
+ fi
503
+
504
+ # Parse all lcov files to find files below threshold
505
+ # Process each lcov file separately to preserve package context
506
+ > .tmp/low_coverage_raw.txt
507
+ for LCOV_FILE in $LCOV_FILES; do
508
+ # Extract package prefix from lcov path (e.g., packages/secureai-core/)
509
+ PKG_PREFIX=$(echo "$LCOV_FILE" | sed -n 's|\./\(packages/[^/]*/\)coverage/lcov.info|\1|p')
510
+
511
+ awk -v threshold="{{.THRESHOLD}}" -v pkg_prefix="$PKG_PREFIX" '
512
+ /^SF:/ {
513
+ file = substr($0, 4)
514
+ # Skip cross-package references (files starting with ../)
515
+ if (file ~ /^\.\.\//) {
516
+ skip = 1
517
+ next
518
+ }
519
+ # Prepend package prefix to relative paths (src/... or tests/...)
520
+ if (pkg_prefix != "" && file ~ /^src\//) {
521
+ file = pkg_prefix file
522
+ } else if (pkg_prefix != "" && file ~ /^tests\//) {
523
+ file = pkg_prefix file
524
+ }
525
+ # Check exclusion patterns (matching bunfig.toml)
526
+ skip = 0
527
+ if (file ~ /\.system\.ts$/) skip = 1
528
+ if (file ~ /systems\.ts$/) skip = 1
529
+ if (file ~ /\.cli\.ts$/) skip = 1
530
+ if (file ~ /\/index\.ts$/) skip = 1
531
+ if (file ~ /\/interfaces\.ts$/) skip = 1
532
+ if (file ~ /\/mock\.ts$/) skip = 1
533
+ if (file ~ /\/mocks\.ts$/) skip = 1
534
+ if (file ~ /\/mocks\//) skip = 1
535
+ if (file ~ /test-doubles\.ts$/) skip = 1
536
+ if (file ~ /^tests\//) skip = 1
537
+ if (file ~ /\.test\.ts$/) skip = 1
538
+ if (file ~ /\/setup\.ts$/) skip = 1
539
+ if (file ~ /\/tools\/plan-/) skip = 1
540
+ if (file ~ /\/tools\/pii-/) skip = 1
541
+ if (file ~ /\/tools\/secret-/) skip = 1
542
+ if (file ~ /in-memory/) skip = 1
543
+ if (file ~ /\.system\.js$/) skip = 1
544
+ }
545
+ /^LF:/ { lines_found = substr($0, 4) }
546
+ /^LH:/ { lines_hit = substr($0, 4) }
547
+ /^end_of_record/ {
548
+ if (lines_found > 0 && skip == 0) {
549
+ coverage = (lines_hit / lines_found) * 100
550
+ if (coverage < threshold) {
551
+ printf "%s|%.1f\n", file, coverage
552
+ }
553
+ }
554
+ file = ""; lines_found = 0; lines_hit = 0; skip = 0
555
+ }
556
+ ' "$LCOV_FILE" >> .tmp/low_coverage_raw.txt
557
+ done
558
+
559
+ # Deduplicate by file (keep first occurrence - lowest will be sorted first)
560
+ sort -t'|' -k2 -n .tmp/low_coverage_raw.txt 2>/dev/null | \
561
+ awk -F'|' '!seen[$1]++ { print $1 "|" $2 }' | \
562
+ sort -t'|' -k2 -n > .tmp/low_coverage_files.txt
563
+ rm -f .tmp/low_coverage_raw.txt
564
+
565
+ echo ""
566
+ if [ -s .tmp/low_coverage_files.txt ]; then
567
+ FILE_COUNT=$(wc -l < .tmp/low_coverage_files.txt | tr -d ' ')
568
+ echo -e "${BOLD}Files Below {{.THRESHOLD}}%${NC} (${FILE_COUNT} files)"
569
+ echo ""
570
+ echo -e " ${CYAN}Coverage File${NC}"
571
+ echo -e " ${CYAN}───────── ────────────────────────────────────────────────────${NC}"
572
+ awk -F'|' -v red="$RED" -v yellow="$YELLOW" -v nc="$NC" '{
573
+ color = ($2 < 50) ? red : yellow
574
+ printf " %s%7.1f%% %s%s\n", color, $2, nc, $1
575
+ }' .tmp/low_coverage_files.txt
576
+ echo ""
577
+ echo -e " ${RED}$FILE_COUNT file(s) below threshold${NC}"
578
+ else
579
+ echo -e " ${GREEN}✓ All files meet {{.THRESHOLD}}% coverage${NC}"
580
+ fi
581
+ rm -f .tmp/low_coverage_files.txt
582
+ else
583
+ echo -e " ${YELLOW}No coverage files found${NC}"
584
+ echo -e " ${CYAN}Run:${NC} task test:coverage"
585
+ fi
586
+
587
+ # Find all junit files in packages (monorepo) or root
588
+ JUNIT_FILES=$(find . -path "./packages/*/.logs/test-results/junit.xml" -o -path "./.logs/test-results/junit.xml" 2>/dev/null | grep -v node_modules)
589
+
590
+ echo ""
591
+ echo -e "${BOLD}Test Results${NC}"
592
+ echo ""
593
+
594
+ if [ -n "$JUNIT_FILES" ]; then
595
+ TOTAL_TESTS=0
596
+ TOTAL_FAILURES=0
597
+
598
+ for JUNIT_FILE in $JUNIT_FILES; do
599
+ TESTS=$(grep -o 'tests="[0-9]*"' "$JUNIT_FILE" 2>/dev/null | head -1 | grep -o '[0-9]*' || echo "0")
600
+ FAILURES=$(grep -o 'failures="[0-9]*"' "$JUNIT_FILE" 2>/dev/null | head -1 | grep -o '[0-9]*' || echo "0")
601
+ TOTAL_TESTS=$((TOTAL_TESTS + TESTS))
602
+ TOTAL_FAILURES=$((TOTAL_FAILURES + FAILURES))
603
+
604
+ # Extract package name from path
605
+ PKG_NAME=$(echo "$JUNIT_FILE" | sed 's|^\./packages/||' | sed 's|/\.logs/test-results/junit.xml||' | sed 's|^\./\.logs/test-results/junit.xml|root|')
606
+ if [ "$FAILURES" = "0" ]; then
607
+ echo -e " ${GREEN}✓${NC} ${PKG_NAME}: ${TESTS} tests"
608
+ else
609
+ echo -e " ${RED}✗${NC} ${PKG_NAME}: ${FAILURES}/${TESTS} failed"
610
+ fi
611
+ done
612
+
613
+ echo ""
614
+ if [ "$TOTAL_FAILURES" = "0" ]; then
615
+ echo -e " ${GREEN}✓ ${TOTAL_TESTS} tests passed${NC}"
616
+ else
617
+ echo -e " ${RED}✗ ${TOTAL_FAILURES} failures${NC} out of ${TOTAL_TESTS} tests"
618
+ fi
619
+ else
620
+ echo -e " ${YELLOW}No test result files found${NC}"
621
+ echo -e " ${CYAN}Run:${NC} task test:coverage"
622
+ fi
623
+ echo ""
624
+
625
+ test:clear:
626
+ desc: Clear test caches, coverage reports, and result files
627
+ aliases: [tcl]
628
+ silent: true
629
+ cmds:
630
+ - bun run packages/secureai-cli/src/cli.ts test-clear {{.CLI_ARGS}}
631
+
632
+ ci:
633
+ desc: Run CI checks (lint, typecheck, test, build)
634
+ silent: true
635
+ deps:
636
+ - lint
637
+ - typecheck
638
+ - test
639
+ - build
640
+
641
+ # Settings shortcuts (flattened from claude namespace for convenience)
642
+ so:
643
+ desc: Open Claude settings.json in detected IDE
644
+ silent: true
645
+ cmds:
646
+ - task claude:settings:open
647
+
648
+ ss:
649
+ desc: Show current Claude settings
650
+ silent: true
651
+ cmds:
652
+ - task claude:settings:show
653
+
654
+ sp:
655
+ desc: Print Claude settings file path
656
+ silent: true
657
+ cmds:
658
+ - task claude:settings:path
659
+
660
+ # Cross-platform IDE file opener utility
661
+ # Opens file in the IDE that invoked the task (detects from environment/process)
662
+ ide:open:
663
+ desc: Open file in current IDE (detects invoking IDE from environment)
664
+ aliases: [io]
665
+ silent: true
666
+ vars:
667
+ FILE_PATH: '{{.FILE | default ""}}'
668
+ cmds:
669
+ - |
670
+ FILE_PATH="{{.FILE_PATH}}"
671
+ if [ -z "$FILE_PATH" ]; then
672
+ echo "Usage: task ide:open FILE=\"path/to/file\""
673
+ exit 1
674
+ fi
675
+
676
+ # Expand ~ to home directory
677
+ FILE_PATH="${FILE_PATH/#\~/$HOME}"
678
+
679
+ # Detect which IDE invoked us by checking environment variables
680
+ # VSCODE_CODE_CACHE_PATH contains the IDE name in the path
681
+ IDE_CMD=""
682
+
683
+ if [ -n "$VSCODE_CODE_CACHE_PATH" ]; then
684
+ if [[ "$VSCODE_CODE_CACHE_PATH" == *"Cursor"* ]]; then
685
+ IDE_CMD="cursor"
686
+ elif [[ "$VSCODE_CODE_CACHE_PATH" == *"Code - Insiders"* ]]; then
687
+ IDE_CMD="code-insiders"
688
+ elif [[ "$VSCODE_CODE_CACHE_PATH" == *"Code"* ]]; then
689
+ IDE_CMD="code"
690
+ fi
691
+ fi
692
+
693
+ # Fallback: check __CFBundleIdentifier on macOS
694
+ if [ -z "$IDE_CMD" ] && [ -n "$__CFBundleIdentifier" ]; then
695
+ case "$__CFBundleIdentifier" in
696
+ com.todesktop.230313mzl4w4u92)
697
+ IDE_CMD="cursor"
698
+ ;;
699
+ com.microsoft.VSCodeInsiders)
700
+ IDE_CMD="code-insiders"
701
+ ;;
702
+ com.microsoft.VSCode)
703
+ IDE_CMD="code"
704
+ ;;
705
+ esac
706
+ fi
707
+
708
+ # External terminal fallback: code > code-insiders > cursor
709
+ if [ -z "$IDE_CMD" ]; then
710
+ if command -v code &> /dev/null; then
711
+ IDE_CMD="code"
712
+ elif command -v code-insiders &> /dev/null; then
713
+ IDE_CMD="code-insiders"
714
+ elif command -v cursor &> /dev/null; then
715
+ IDE_CMD="cursor"
716
+ fi
717
+ fi
718
+
719
+ if [ -z "$IDE_CMD" ]; then
720
+ # Fallback: try platform-specific open
721
+ case "$(uname -s)" in
722
+ Darwin)
723
+ open "$FILE_PATH"
724
+ ;;
725
+ Linux)
726
+ xdg-open "$FILE_PATH" 2>/dev/null || echo "No IDE found"
727
+ ;;
728
+ MINGW*|MSYS*|CYGWIN*)
729
+ start "" "$FILE_PATH"
730
+ ;;
731
+ *)
732
+ echo "No IDE found. Install cursor, code, or code-insiders"
733
+ exit 1
734
+ ;;
735
+ esac
736
+ else
737
+ "$IDE_CMD" "$FILE_PATH"
738
+ fi