@fro.bot/systematic 2.0.2 → 2.1.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 (68) hide show
  1. package/agents/design/figma-design-sync.md +1 -1
  2. package/agents/document-review/coherence-reviewer.md +40 -0
  3. package/agents/document-review/design-lens-reviewer.md +46 -0
  4. package/agents/document-review/feasibility-reviewer.md +42 -0
  5. package/agents/document-review/product-lens-reviewer.md +50 -0
  6. package/agents/document-review/scope-guardian-reviewer.md +54 -0
  7. package/agents/document-review/security-lens-reviewer.md +38 -0
  8. package/agents/research/best-practices-researcher.md +2 -1
  9. package/agents/research/git-history-analyzer.md +1 -1
  10. package/agents/research/learnings-researcher.md +27 -26
  11. package/agents/research/repo-research-analyst.md +164 -9
  12. package/agents/review/api-contract-reviewer.md +49 -0
  13. package/agents/review/correctness-reviewer.md +49 -0
  14. package/agents/review/data-migrations-reviewer.md +53 -0
  15. package/agents/review/dhh-rails-reviewer.md +31 -52
  16. package/agents/review/julik-frontend-races-reviewer.md +27 -200
  17. package/agents/review/kieran-python-reviewer.md +29 -116
  18. package/agents/review/kieran-rails-reviewer.md +29 -98
  19. package/agents/review/kieran-typescript-reviewer.md +29 -107
  20. package/agents/review/maintainability-reviewer.md +49 -0
  21. package/agents/review/pattern-recognition-specialist.md +2 -1
  22. package/agents/review/performance-reviewer.md +51 -0
  23. package/agents/review/reliability-reviewer.md +49 -0
  24. package/agents/review/schema-drift-detector.md +12 -10
  25. package/agents/review/security-reviewer.md +51 -0
  26. package/agents/review/testing-reviewer.md +48 -0
  27. package/agents/workflow/pr-comment-resolver.md +99 -50
  28. package/agents/workflow/spec-flow-analyzer.md +60 -89
  29. package/dist/index.js +9 -0
  30. package/dist/lib/config-handler.d.ts +2 -0
  31. package/package.json +1 -1
  32. package/skills/agent-browser/SKILL.md +69 -48
  33. package/skills/ce-brainstorm/SKILL.md +2 -1
  34. package/skills/ce-compound/SKILL.md +126 -28
  35. package/skills/ce-compound-refresh/SKILL.md +181 -73
  36. package/skills/ce-ideate/SKILL.md +2 -1
  37. package/skills/ce-plan/SKILL.md +424 -414
  38. package/skills/ce-review/SKILL.md +379 -419
  39. package/skills/ce-review-beta/SKILL.md +506 -0
  40. package/skills/ce-review-beta/references/diff-scope.md +31 -0
  41. package/skills/ce-review-beta/references/findings-schema.json +128 -0
  42. package/skills/ce-review-beta/references/persona-catalog.md +50 -0
  43. package/skills/ce-review-beta/references/review-output-template.md +115 -0
  44. package/skills/ce-review-beta/references/subagent-template.md +56 -0
  45. package/skills/ce-work/SKILL.md +17 -8
  46. package/skills/ce-work-beta/SKILL.md +16 -9
  47. package/skills/claude-permissions-optimizer/SKILL.md +15 -14
  48. package/skills/claude-permissions-optimizer/scripts/extract-commands.mjs +9 -159
  49. package/skills/claude-permissions-optimizer/scripts/normalize.mjs +151 -0
  50. package/skills/deepen-plan/SKILL.md +348 -483
  51. package/skills/document-review/SKILL.md +160 -52
  52. package/skills/feature-video/SKILL.md +209 -178
  53. package/skills/file-todos/SKILL.md +72 -94
  54. package/skills/frontend-design/SKILL.md +243 -27
  55. package/skills/git-worktree/SKILL.md +37 -28
  56. package/skills/git-worktree/scripts/worktree-manager.sh +163 -0
  57. package/skills/lfg/SKILL.md +7 -7
  58. package/skills/orchestrating-swarms/SKILL.md +1 -1
  59. package/skills/reproduce-bug/SKILL.md +154 -60
  60. package/skills/resolve-pr-parallel/SKILL.md +19 -12
  61. package/skills/resolve-todo-parallel/SKILL.md +9 -6
  62. package/skills/setup/SKILL.md +8 -160
  63. package/skills/slfg/SKILL.md +11 -7
  64. package/skills/test-browser/SKILL.md +69 -145
  65. package/skills/test-xcode/SKILL.md +61 -183
  66. package/skills/triage/SKILL.md +10 -10
  67. package/skills/ce-plan-beta/SKILL.md +0 -571
  68. package/skills/deepen-plan-beta/SKILL.md +0 -323
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Extracts, normalizes, and pre-classifies Bash commands from OpenCode sessions.
3
+ // Extracts, normalizes, and pre-classifies Bash commands from Claude Code sessions.
4
4
  // Filters against the current allowlist, groups by normalized pattern, and classifies
5
5
  // each pattern as green/yellow/red so the model can review rather than classify from scratch.
6
6
  //
@@ -15,6 +15,7 @@
15
15
  import { readdir, readFile, stat } from 'node:fs/promises'
16
16
  import { homedir } from 'node:os'
17
17
  import { join } from 'node:path'
18
+ import { normalize } from './normalize.mjs'
18
19
 
19
20
  const args = process.argv.slice(2)
20
21
 
@@ -42,9 +43,8 @@ const maxSessions = parseInt(flag('max-sessions', '500'), 10)
42
43
  const minCount = parseInt(flag('min-count', '5'), 10)
43
44
  const projectSlugFilter = flag('project-slug', null)
44
45
  const settingsPaths = flagAll('settings')
45
- const opencodeDir =
46
- process.env.OPENCODE_CONFIG_DIR || join(homedir(), '.config', 'opencode')
47
- const projectsDir = join(opencodeDir, 'projects')
46
+ const claudeDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude')
47
+ const projectsDir = join(claudeDir, 'projects')
48
48
  const cutoff = Date.now() - days * 24 * 60 * 60 * 1000
49
49
 
50
50
  // ── Allowlist loading ──────────────────────────────────────────────────────
@@ -70,9 +70,9 @@ async function loadAllowlist(filePath) {
70
70
  }
71
71
 
72
72
  if (settingsPaths.length === 0) {
73
- settingsPaths.push(join(opencodeDir, 'settings.json'))
74
- settingsPaths.push(join(process.cwd(), '.opencode', 'settings.json'))
75
- settingsPaths.push(join(process.cwd(), '.opencode', 'settings.local.json'))
73
+ settingsPaths.push(join(claudeDir, 'settings.json'))
74
+ settingsPaths.push(join(process.cwd(), '.claude', 'settings.json'))
75
+ settingsPaths.push(join(process.cwd(), '.claude', 'settings.local.json'))
76
76
  }
77
77
 
78
78
  for (const p of settingsPaths) {
@@ -320,7 +320,7 @@ const GREEN_COMPOUND = [
320
320
  /\b--dry-run\b/,
321
321
  /^git\s+clean\s+.*(-[a-z]*n|--dry-run)\b/, // git clean dry run
322
322
  // NOTE: find is intentionally NOT green. Bash(find *) would also match
323
- // find -delete and find -exec rm in OpenCode's allowlist glob matching.
323
+ // find -delete and find -exec rm in Claude Code's allowlist glob matching.
324
324
  // Commands with mode-switching flags: only green when the normalized pattern
325
325
  // is narrow enough that the allowlist glob can't match the destructive form.
326
326
  // Bash(sed -n *) is safe; Bash(sed *) would also match sed -i.
@@ -410,156 +410,7 @@ function classify(command) {
410
410
  return { tier: 'unknown' }
411
411
  }
412
412
 
413
- // ── Normalization ──────────────────────────────────────────────────────────
414
-
415
- // Risk-modifying flags that must NOT be collapsed into wildcards.
416
- // Global flags are always preserved; context-specific flags only matter
417
- // for certain base commands.
418
- const GLOBAL_RISK_FLAGS = new Set([
419
- '--force',
420
- '--hard',
421
- '-rf',
422
- '--privileged',
423
- '--no-verify',
424
- '--system',
425
- '--force-with-lease',
426
- '-D',
427
- '--force-if-includes',
428
- '--volumes',
429
- '--rmi',
430
- '--rewrite',
431
- '--delete',
432
- ])
433
-
434
- // Flags that are only risky for specific base commands.
435
- // -f means force-push in git, force-remove in docker, but pattern-file in grep.
436
- // -v means remove-volumes in docker-compose, but verbose everywhere else.
437
- const CONTEXTUAL_RISK_FLAGS = {
438
- '-f': new Set(['git', 'docker', 'rm']),
439
- '-v': new Set(['docker', 'docker-compose']),
440
- }
441
-
442
- function isRiskFlag(token, base) {
443
- if (GLOBAL_RISK_FLAGS.has(token)) return true
444
- // Check context-specific flags
445
- const contexts = CONTEXTUAL_RISK_FLAGS[token]
446
- if (contexts && base && contexts.has(base)) return true
447
- // Combined short flags containing risk chars: -rf, -fr, -fR, etc.
448
- if (/^-[a-zA-Z]*[rf][a-zA-Z]*$/.test(token) && token.length <= 4) return true
449
- return false
450
- }
451
-
452
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: command normalization intentionally centralizes risk checks and pattern shaping.
453
- function normalize(command) {
454
- // Don't normalize shell injection patterns
455
- if (/\|\s*(sh|bash|zsh)\b/.test(command)) return command
456
- // Don't normalize sudo -- keep as-is
457
- if (/^sudo\s/.test(command)) return 'sudo *'
458
-
459
- // Handle pnpm --filter <pkg> <subcommand> specially
460
- const pnpmFilter = command.match(/^pnpm\s+--filter\s+\S+\s+(\S+)/)
461
- if (pnpmFilter) return `pnpm --filter * ${pnpmFilter[1]} *`
462
-
463
- // Handle sed specially -- preserve the mode flag to keep safe patterns narrow.
464
- // sed -i (in-place) is destructive; sed -n, sed -e, bare sed are read-only.
465
- if (/^sed\s/.test(command)) {
466
- if (/\s-i\b/.test(command)) return 'sed -i *'
467
- const sedFlag = command.match(/^sed\s+(-[a-zA-Z])\s/)
468
- return sedFlag ? `sed ${sedFlag[1]} *` : 'sed *'
469
- }
470
-
471
- // Handle ast-grep specially -- preserve --rewrite flag.
472
- if (/^(ast-grep|sg)\s/.test(command)) {
473
- const base = command.startsWith('sg') ? 'sg' : 'ast-grep'
474
- return /\s--rewrite\b/.test(command) ? `${base} --rewrite *` : `${base} *`
475
- }
476
-
477
- // Handle find specially -- preserve key action flags.
478
- // find -delete and find -exec rm are destructive; find -name/-type are safe.
479
- if (/^find\s/.test(command)) {
480
- if (/\s-delete\b/.test(command)) return 'find -delete *'
481
- if (/\s-exec\s/.test(command)) return 'find -exec *'
482
- // Extract the first predicate flag for a narrower safe pattern
483
- const findFlag = command.match(/\s(-(?:name|type|path|iname))\s/)
484
- return findFlag ? `find ${findFlag[1]} *` : 'find *'
485
- }
486
-
487
- // Handle git -C <dir> <subcommand> -- strip the -C <dir> and normalize the git subcommand
488
- const gitC = command.match(/^git\s+-C\s+\S+\s+(.+)$/)
489
- if (gitC) return normalize(`git ${gitC[1]}`)
490
-
491
- // Split on compound operators -- normalize the first command only
492
- const compoundMatch = command.match(/^(.+?)\s*(&&|\|\||;)\s*(.+)$/)
493
- if (compoundMatch) {
494
- return normalize(compoundMatch[1].trim())
495
- }
496
-
497
- // Strip trailing pipe chains for normalization (e.g., `cmd | tail -5`)
498
- // but preserve pipe-to-shell (already handled by shell injection check above)
499
- const pipeMatch = command.match(/^(.+?)\s*\|\s*(.+)$/)
500
- if (pipeMatch) {
501
- return normalize(pipeMatch[1].trim())
502
- }
503
-
504
- // Strip trailing redirections (2>&1, > file, >> file)
505
- const cleaned = command
506
- .replace(/\s*[12]?>>?\s*\S+\s*$/, '')
507
- .replace(/\s*2>&1\s*$/, '')
508
- .trim()
509
-
510
- const parts = cleaned.split(/\s+/)
511
- if (parts.length === 0) return command
512
-
513
- const base = parts[0]
514
-
515
- // For git/docker/gh/npm etc, include the subcommand
516
- const multiWordBases = [
517
- 'git',
518
- 'docker',
519
- 'docker-compose',
520
- 'gh',
521
- 'npm',
522
- 'bun',
523
- 'pnpm',
524
- 'yarn',
525
- 'cargo',
526
- 'pip',
527
- 'pip3',
528
- 'bundle',
529
- 'systemctl',
530
- 'kubectl',
531
- ]
532
-
533
- let prefix = base
534
- let argStart = 1
535
-
536
- if (multiWordBases.includes(base) && parts.length > 1) {
537
- prefix = `${base} ${parts[1]}`
538
- argStart = 2
539
- }
540
-
541
- // Preserve risk-modifying flags in the remaining args
542
- const preservedFlags = []
543
- for (let i = argStart; i < parts.length; i++) {
544
- if (isRiskFlag(parts[i], base)) {
545
- preservedFlags.push(parts[i])
546
- }
547
- }
548
-
549
- // Build the normalized pattern
550
- if (parts.length <= argStart && preservedFlags.length === 0) {
551
- return prefix // no args, no flags: e.g., "git status"
552
- }
553
-
554
- const flagStr =
555
- preservedFlags.length > 0 ? ` ${preservedFlags.join(' ')}` : ''
556
- const hasVaryingArgs = parts.length > argStart + preservedFlags.length
557
-
558
- if (hasVaryingArgs) {
559
- return `${prefix + flagStr} *`
560
- }
561
- return prefix + flagStr
562
- }
413
+ // ── Normalization (see ./normalize.mjs) ────────────────────────────────────
563
414
 
564
415
  // ── Session file scanning ──────────────────────────────────────────────────
565
416
 
@@ -587,7 +438,6 @@ async function listJsonlFiles(dir) {
587
438
  }
588
439
  }
589
440
 
590
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: transcript parsing requires defensive guards for heterogeneous session data.
591
441
  async function processFile(filePath, sessionId) {
592
442
  try {
593
443
  filesScanned++
@@ -0,0 +1,151 @@
1
+ // Normalization helpers extracted from extract-commands.mjs for testability.
2
+
3
+ // Risk-modifying flags that must NOT be collapsed into wildcards.
4
+ // Global flags are always preserved; context-specific flags only matter
5
+ // for certain base commands.
6
+ const GLOBAL_RISK_FLAGS = new Set([
7
+ '--force',
8
+ '--hard',
9
+ '-rf',
10
+ '--privileged',
11
+ '--no-verify',
12
+ '--system',
13
+ '--force-with-lease',
14
+ '-D',
15
+ '--force-if-includes',
16
+ '--volumes',
17
+ '--rmi',
18
+ '--rewrite',
19
+ '--delete',
20
+ ])
21
+
22
+ // Flags that are only risky for specific base commands.
23
+ // -f means force-push in git, force-remove in docker, but pattern-file in grep.
24
+ // -v means remove-volumes in docker-compose, but verbose everywhere else.
25
+ const CONTEXTUAL_RISK_FLAGS = {
26
+ '-f': new Set(['git', 'docker', 'rm']),
27
+ '-v': new Set(['docker', 'docker-compose']),
28
+ }
29
+
30
+ export function isRiskFlag(token, base) {
31
+ if (GLOBAL_RISK_FLAGS.has(token)) return true
32
+ // Check context-specific flags
33
+ const contexts = Object.hasOwn(CONTEXTUAL_RISK_FLAGS, token)
34
+ ? CONTEXTUAL_RISK_FLAGS[token]
35
+ : undefined
36
+ if (contexts && base && contexts.has(base)) return true
37
+ // Combined short flags containing risk chars: -rf, -fr, -fR, etc.
38
+ if (/^-[a-zA-Z]*[rf][a-zA-Z]*$/.test(token) && token.length <= 4) return true
39
+ return false
40
+ }
41
+
42
+ export function normalize(command) {
43
+ // Don't normalize shell injection patterns
44
+ if (/\|\s*(sh|bash|zsh)\b/.test(command)) return command
45
+ // Don't normalize sudo -- keep as-is
46
+ if (/^sudo\s/.test(command)) return 'sudo *'
47
+
48
+ // Handle pnpm --filter <pkg> <subcommand> specially
49
+ const pnpmFilter = command.match(/^pnpm\s+--filter\s+\S+\s+(\S+)/)
50
+ if (pnpmFilter) return `pnpm --filter * ${pnpmFilter[1]} *`
51
+
52
+ // Handle sed specially -- preserve the mode flag to keep safe patterns narrow.
53
+ // sed -i (in-place) is destructive; sed -n, sed -e, bare sed are read-only.
54
+ if (/^sed\s/.test(command)) {
55
+ if (/\s-i\b/.test(command)) return 'sed -i *'
56
+ const sedFlag = command.match(/^sed\s+(-[a-zA-Z])\s/)
57
+ return sedFlag ? `sed ${sedFlag[1]} *` : 'sed *'
58
+ }
59
+
60
+ // Handle ast-grep specially -- preserve --rewrite flag.
61
+ if (/^(ast-grep|sg)\s/.test(command)) {
62
+ const base = command.startsWith('sg') ? 'sg' : 'ast-grep'
63
+ return /\s--rewrite\b/.test(command) ? `${base} --rewrite *` : `${base} *`
64
+ }
65
+
66
+ // Handle find specially -- preserve key action flags.
67
+ // find -delete and find -exec rm are destructive; find -name/-type are safe.
68
+ if (/^find\s/.test(command)) {
69
+ if (/\s-delete\b/.test(command)) return 'find -delete *'
70
+ if (/\s-exec\s/.test(command)) return 'find -exec *'
71
+ // Extract the first predicate flag for a narrower safe pattern
72
+ const findFlag = command.match(/\s(-(?:name|type|path|iname))\s/)
73
+ return findFlag ? `find ${findFlag[1]} *` : 'find *'
74
+ }
75
+
76
+ // Handle git -C <dir> <subcommand> -- strip the -C <dir> and normalize the git subcommand
77
+ const gitC = command.match(/^git\s+-C\s+\S+\s+(.+)$/)
78
+ if (gitC) return normalize(`git ${gitC[1]}`)
79
+
80
+ // Split on compound operators -- normalize the first command only
81
+ const compoundMatch = command.match(/^(.+?)\s*(&&|\|\||;)\s*(.+)$/)
82
+ if (compoundMatch) {
83
+ return normalize(compoundMatch[1].trim())
84
+ }
85
+
86
+ // Strip trailing pipe chains for normalization (e.g., `cmd | tail -5`)
87
+ // but preserve pipe-to-shell (already handled by shell injection check above)
88
+ const pipeMatch = command.match(/^(.+?)\s*\|\s*(.+)$/)
89
+ if (pipeMatch) {
90
+ return normalize(pipeMatch[1].trim())
91
+ }
92
+
93
+ // Strip trailing redirections (2>&1, > file, >> file)
94
+ const cleaned = command
95
+ .replace(/\s*[12]?>>?\s*\S+\s*$/, '')
96
+ .replace(/\s*2>&1\s*$/, '')
97
+ .trim()
98
+
99
+ const parts = cleaned.split(/\s+/)
100
+ if (parts.length === 0) return command
101
+
102
+ const base = parts[0]
103
+
104
+ // For git/docker/gh/npm etc, include the subcommand
105
+ const multiWordBases = [
106
+ 'git',
107
+ 'docker',
108
+ 'docker-compose',
109
+ 'gh',
110
+ 'npm',
111
+ 'bun',
112
+ 'pnpm',
113
+ 'yarn',
114
+ 'cargo',
115
+ 'pip',
116
+ 'pip3',
117
+ 'bundle',
118
+ 'systemctl',
119
+ 'kubectl',
120
+ ]
121
+
122
+ let prefix = base
123
+ let argStart = 1
124
+
125
+ if (multiWordBases.includes(base) && parts.length > 1) {
126
+ prefix = `${base} ${parts[1]}`
127
+ argStart = 2
128
+ }
129
+
130
+ // Preserve risk-modifying flags in the remaining args
131
+ const preservedFlags = []
132
+ for (let i = argStart; i < parts.length; i++) {
133
+ if (isRiskFlag(parts[i], base)) {
134
+ preservedFlags.push(parts[i])
135
+ }
136
+ }
137
+
138
+ // Build the normalized pattern
139
+ if (parts.length <= argStart && preservedFlags.length === 0) {
140
+ return prefix // no args, no flags: e.g., "git status"
141
+ }
142
+
143
+ const flagStr =
144
+ preservedFlags.length > 0 ? ` ${preservedFlags.join(' ')}` : ''
145
+ const hasVaryingArgs = parts.length > argStart + preservedFlags.length
146
+
147
+ if (hasVaryingArgs) {
148
+ return `${prefix + flagStr} *`
149
+ }
150
+ return prefix + flagStr
151
+ }