@fro.bot/systematic 2.0.3 → 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 (32) hide show
  1. package/agents/research/learnings-researcher.md +27 -26
  2. package/agents/review/api-contract-reviewer.md +1 -1
  3. package/agents/review/correctness-reviewer.md +1 -1
  4. package/agents/review/data-migrations-reviewer.md +1 -1
  5. package/agents/review/dhh-rails-reviewer.md +31 -52
  6. package/agents/review/julik-frontend-races-reviewer.md +27 -200
  7. package/agents/review/kieran-python-reviewer.md +29 -116
  8. package/agents/review/kieran-rails-reviewer.md +29 -98
  9. package/agents/review/kieran-typescript-reviewer.md +29 -107
  10. package/agents/review/maintainability-reviewer.md +1 -1
  11. package/agents/review/performance-reviewer.md +1 -1
  12. package/agents/review/reliability-reviewer.md +1 -1
  13. package/agents/review/security-reviewer.md +1 -1
  14. package/agents/review/testing-reviewer.md +1 -1
  15. package/agents/workflow/pr-comment-resolver.md +99 -50
  16. package/dist/index.js +9 -0
  17. package/dist/lib/config-handler.d.ts +2 -0
  18. package/package.json +1 -1
  19. package/skills/ce-compound/SKILL.md +100 -27
  20. package/skills/ce-compound-refresh/SKILL.md +172 -74
  21. package/skills/ce-review/SKILL.md +379 -418
  22. package/skills/ce-work/SKILL.md +5 -4
  23. package/skills/ce-work-beta/SKILL.md +6 -5
  24. package/skills/claude-permissions-optimizer/scripts/extract-commands.mjs +9 -159
  25. package/skills/claude-permissions-optimizer/scripts/normalize.mjs +151 -0
  26. package/skills/git-worktree/scripts/worktree-manager.sh +163 -0
  27. package/skills/lfg/SKILL.md +2 -2
  28. package/skills/orchestrating-swarms/SKILL.md +1 -1
  29. package/skills/setup/SKILL.md +8 -137
  30. package/skills/slfg/SKILL.md +8 -4
  31. package/skills/test-browser/SKILL.md +2 -2
  32. package/skills/test-xcode/SKILL.md +2 -2
@@ -150,6 +150,7 @@ This command takes a work document (plan, specification, or todo file) and execu
150
150
 
151
151
  **When this matters most:** Any change that touches models with callbacks, error handling with fallback/retry, or functionality exposed through multiple interfaces.
152
152
 
153
+
153
154
  2. **Incremental Commits**
154
155
 
155
156
  After completing each task, evaluate whether to create an incremental commit:
@@ -234,11 +235,9 @@ This command takes a work document (plan, specification, or todo file) and execu
234
235
  # Use linting-agent before pushing to origin
235
236
  ```
236
237
 
237
- 2. **Consider Reviewer Agents** (Optional)
238
-
239
- Use for complex, risky, or large changes. Read agents from `systematic.local.md` frontmatter (`review_agents`). If no settings file, invoke the `setup` skill to create one.
238
+ 2. **Consider Code Review** (Optional)
240
239
 
241
- Run configured agents in parallel with task tool. Present findings and address critical issues.
240
+ Use for complex, risky, or large changes. Load the `ce:review` skill with `mode:autofix` to fix safe issues and flag the rest before shipping.
242
241
 
243
242
  3. **Final Validation**
244
243
  - All tasks marked completed
@@ -370,6 +369,7 @@ This command takes a work document (plan, specification, or todo file) and execu
370
369
 
371
370
  ---
372
371
 
372
+ [![Systematic v[VERSION]](https://img.shields.io/badge/Systematic-v[VERSION]-6366f1)](https://github.com/marcusrbrown/systematic)
373
373
  🤖 Generated with [MODEL] ([CONTEXT] context, [THINKING]) via [HARNESS](HARNESS_URL)
374
374
  EOF
375
375
  )"
@@ -487,3 +487,4 @@ For most features: tests + linting + following patterns is sufficient.
487
487
  - **Forgetting to track progress** - Update task status as you go or lose track of what's done
488
488
  - **80% done syndrome** - Finish the feature, don't move on early
489
489
  - **Over-reviewing simple changes** - Save reviewer agents for complex work
490
+
@@ -151,6 +151,7 @@ This command takes a work document (plan, specification, or todo file) and execu
151
151
 
152
152
  **When this matters most:** Any change that touches models with callbacks, error handling with fallback/retry, or functionality exposed through multiple interfaces.
153
153
 
154
+
154
155
  2. **Incremental Commits**
155
156
 
156
157
  After completing each task, evaluate whether to create an incremental commit:
@@ -243,11 +244,9 @@ This command takes a work document (plan, specification, or todo file) and execu
243
244
  # Use linting-agent before pushing to origin
244
245
  ```
245
246
 
246
- 2. **Consider Reviewer Agents** (Optional)
247
-
248
- Use for complex, risky, or large changes. Read agents from `systematic.local.md` frontmatter (`review_agents`). If no settings file, invoke the `setup` skill to create one.
247
+ 2. **Consider Code Review** (Optional)
249
248
 
250
- Run configured agents in parallel with task tool. Present findings and address critical issues.
249
+ Use for complex, risky, or large changes. Load the `ce:review` skill with `mode:autofix` to fix safe issues and flag the rest before shipping.
251
250
 
252
251
  3. **Final Validation**
253
252
  - All tasks marked completed
@@ -379,6 +378,7 @@ This command takes a work document (plan, specification, or todo file) and execu
379
378
 
380
379
  ---
381
380
 
381
+ [![Systematic v[VERSION]](https://img.shields.io/badge/Systematic-v[VERSION]-6366f1)](https://github.com/marcusrbrown/systematic)
382
382
  🤖 Generated with [MODEL] ([CONTEXT] context, [THINKING]) via [HARNESS](HARNESS_URL)
383
383
  EOF
384
384
  )"
@@ -468,7 +468,7 @@ When external delegation is active, follow this workflow for each tagged task. D
468
468
 
469
469
  Verify the delegate CLI is installed. If not found, print "Delegate CLI not installed - continuing with standard mode." and proceed normally.
470
470
 
471
- 2. **Build prompt** — For each task, assemble a prompt from the plan's implementation unit (Goal, Files, Approach, Conventions from `systematic.local.md`). Include rules: no git commits, no PRs, run `git status` and `git diff --stat` when done. Never embed credentials or tokens in the prompt - pass auth through environment variables.
471
+ 2. **Build prompt** — For each task, assemble a prompt from the plan's implementation unit (Goal, Files, Approach, Conventions from project AGENTS.md/AGENTS.md). Include rules: no git commits, no PRs, run `git status` and `git diff --stat` when done. Never embed credentials or tokens in the prompt - pass auth through environment variables.
472
472
 
473
473
  3. **Write prompt to file** — Save the assembled prompt to a unique temporary file to avoid shell quoting issues and cross-task races. Use a unique filename per task.
474
474
 
@@ -560,3 +560,4 @@ For most features: tests + linting + following patterns is sufficient.
560
560
  - **Forgetting to track progress** - Update task status as you go or lose track of what's done
561
561
  - **80% done syndrome** - Finish the feature, don't move on early
562
562
  - **Over-reviewing simple changes** - Save reviewer agents for complex work
563
+
@@ -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
+ }
@@ -65,6 +65,137 @@ copy_env_files() {
65
65
  echo -e " ${GREEN}✓ Copied $copied environment file(s)${NC}"
66
66
  }
67
67
 
68
+ # Resolve the repository default branch, falling back to main when origin/HEAD
69
+ # is unavailable (for example in single-branch clones).
70
+ get_default_branch() {
71
+ local head_ref
72
+ head_ref=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)
73
+
74
+ if [[ -n "$head_ref" ]]; then
75
+ echo "${head_ref#refs/remotes/origin/}"
76
+ else
77
+ echo "main"
78
+ fi
79
+ }
80
+
81
+ # Auto-trust is only safe when the worktree is created from a long-lived branch
82
+ # the developer already controls. Review/PR branches should fall back to the
83
+ # default branch baseline and require manual direnv approval.
84
+ is_trusted_base_branch() {
85
+ local branch="$1"
86
+ local default_branch="$2"
87
+
88
+ [[ "$branch" == "$default_branch" ]] && return 0
89
+
90
+ case "$branch" in
91
+ develop|dev|trunk|staging|release/*)
92
+ return 0
93
+ ;;
94
+ *)
95
+ return 1
96
+ ;;
97
+ esac
98
+ }
99
+
100
+ # Trust development tool configs in a new worktree.
101
+ # Worktrees get a new filesystem path that tools like mise and direnv
102
+ # have never seen. Without trusting, these tools block with interactive
103
+ # prompts or refuse to load configs, which breaks hooks and scripts.
104
+ #
105
+ # Safety: auto-trusts only configs unchanged from a trusted baseline branch.
106
+ # Review/PR branches fall back to the default-branch baseline, and direnv
107
+ # auto-allow is limited to trusted base branches because .envrc can source
108
+ # additional files that direnv does not validate.
109
+ #
110
+ # TOCTOU between hash-check and trust is acceptable for local dev use.
111
+ trust_dev_tools() {
112
+ local worktree_path="$1"
113
+ local base_ref="$2"
114
+ local allow_direnv_auto="$3"
115
+ local trusted=0
116
+ local skipped_messages=()
117
+ local manual_commands=()
118
+
119
+ # mise: trust the specific config file if present and unchanged
120
+ if command -v mise &>/dev/null; then
121
+ for f in .mise.toml mise.toml .tool-versions; do
122
+ if [[ -f "$worktree_path/$f" ]]; then
123
+ if _config_unchanged "$f" "$base_ref" "$worktree_path"; then
124
+ if (cd "$worktree_path" && mise trust "$f" --quiet); then
125
+ trusted=$((trusted + 1))
126
+ else
127
+ echo -e " ${YELLOW}Warning: 'mise trust $f' failed -- run manually in $worktree_path${NC}"
128
+ fi
129
+ else
130
+ skipped_messages+=("mise trust $f (config differs from $base_ref)")
131
+ manual_commands+=("mise trust $f")
132
+ fi
133
+ break
134
+ fi
135
+ done
136
+ fi
137
+
138
+ # direnv: allow .envrc
139
+ if command -v direnv &>/dev/null; then
140
+ if [[ -f "$worktree_path/.envrc" ]]; then
141
+ if [[ "$allow_direnv_auto" != "true" ]]; then
142
+ skipped_messages+=("direnv allow (.envrc auto-allow is disabled for non-trusted base branches)")
143
+ manual_commands+=("direnv allow")
144
+ elif _config_unchanged ".envrc" "$base_ref" "$worktree_path"; then
145
+ if (cd "$worktree_path" && direnv allow); then
146
+ trusted=$((trusted + 1))
147
+ else
148
+ echo -e " ${YELLOW}Warning: 'direnv allow' failed -- run manually in $worktree_path${NC}"
149
+ fi
150
+ else
151
+ skipped_messages+=("direnv allow (.envrc differs from $base_ref)")
152
+ manual_commands+=("direnv allow")
153
+ fi
154
+ fi
155
+ fi
156
+
157
+ if [[ $trusted -gt 0 ]]; then
158
+ echo -e " ${GREEN}✓ Trusted $trusted dev tool config(s)${NC}"
159
+ fi
160
+
161
+ if [[ ${#skipped_messages[@]} -gt 0 ]]; then
162
+ echo -e " ${YELLOW}Skipped auto-trust for config(s) requiring manual review:${NC}"
163
+ for item in "${skipped_messages[@]}"; do
164
+ echo -e " - $item"
165
+ done
166
+ if [[ ${#manual_commands[@]} -gt 0 ]]; then
167
+ local joined
168
+ joined=$(printf ' && %s' "${manual_commands[@]}")
169
+ echo -e " ${BLUE}Review the diff, then run manually: cd $worktree_path${joined}${NC}"
170
+ fi
171
+ fi
172
+ }
173
+
174
+ # Check if a config file is unchanged from the base branch.
175
+ # Returns 0 (true) if the file is identical to the base branch version.
176
+ # Returns 1 (false) if the file was added or modified by this branch.
177
+ #
178
+ # Note: rev-parse returns the stored blob hash; hash-object on a path applies
179
+ # gitattributes filters. A mismatch causes a false negative (trust skipped),
180
+ # which is the safe direction.
181
+ _config_unchanged() {
182
+ local file="$1"
183
+ local base_ref="$2"
184
+ local worktree_path="$3"
185
+
186
+ # Reject symlinks -- trust only regular files with verifiable content
187
+ [[ -L "$worktree_path/$file" ]] && return 1
188
+
189
+ # Get the blob hash directly from git's object database via rev-parse
190
+ local base_hash
191
+ base_hash=$(git rev-parse "$base_ref:$file" 2>/dev/null) || return 1
192
+
193
+ local worktree_hash
194
+ worktree_hash=$(git hash-object "$worktree_path/$file") || return 1
195
+
196
+ [[ "$base_hash" == "$worktree_hash" ]]
197
+ }
198
+
68
199
  # Create a new worktree
69
200
  create_worktree() {
70
201
  local branch_name="$1"
@@ -107,6 +238,29 @@ create_worktree() {
107
238
  # Copy environment files
108
239
  copy_env_files "$worktree_path"
109
240
 
241
+ # Trust dev tool configs (mise, direnv) so hooks and scripts work immediately.
242
+ # Long-lived integration branches can use themselves as the trust baseline,
243
+ # while review/PR branches fall back to the default branch and require manual
244
+ # direnv approval.
245
+ local default_branch
246
+ default_branch=$(get_default_branch)
247
+ local trust_branch="$default_branch"
248
+ local allow_direnv_auto="false"
249
+ if is_trusted_base_branch "$from_branch" "$default_branch"; then
250
+ trust_branch="$from_branch"
251
+ allow_direnv_auto="true"
252
+ fi
253
+
254
+ if ! git fetch origin "$trust_branch" --quiet; then
255
+ echo -e " ${YELLOW}Warning: could not fetch origin/$trust_branch -- trust check may use stale data${NC}"
256
+ fi
257
+ # Skip trust entirely if the baseline ref doesn't exist locally.
258
+ if git rev-parse --verify "origin/$trust_branch" &>/dev/null; then
259
+ trust_dev_tools "$worktree_path" "origin/$trust_branch" "$allow_direnv_auto"
260
+ else
261
+ echo -e " ${YELLOW}Skipping dev tool trust -- origin/$trust_branch not found locally${NC}"
262
+ fi
263
+
110
264
  echo -e "${GREEN}✓ Worktree created successfully!${NC}"
111
265
  echo ""
112
266
  echo "To switch to this worktree:"
@@ -321,6 +475,15 @@ Environment Files:
321
475
  - Creates .backup files if destination already exists
322
476
  - Use 'copy-env' to refresh env files after main repo changes
323
477
 
478
+ Dev Tool Trust:
479
+ - Trusts mise config (.mise.toml, mise.toml, .tool-versions) and direnv (.envrc)
480
+ - Uses trusted base branches directly (main, develop, dev, trunk, staging, release/*)
481
+ - Other branches fall back to the default branch as the trust baseline
482
+ - direnv auto-allow is skipped on non-trusted base branches; review manually first
483
+ - Modified configs are flagged for manual review
484
+ - Only runs if the tool is installed and config exists
485
+ - Prevents hooks/scripts from hanging on interactive trust prompts
486
+
324
487
  Examples:
325
488
  worktree-manager.sh create feature-login
326
489
  worktree-manager.sh create feature-auth develop
@@ -23,9 +23,9 @@ CRITICAL: You MUST execute every step below IN ORDER. Do NOT skip any required s
23
23
 
24
24
  GATE: STOP. Verify that implementation work was performed - files were created or modified beyond the plan. Do NOT proceed to step 5 if no code changes were made.
25
25
 
26
- 5. `/ce:review`
26
+ 5. `/ce:review mode:autofix`
27
27
 
28
- 6. `/systematic:resolve-todo-parallel`
28
+ 6. `/systematic:todo-resolve`
29
29
 
30
30
  7. `/systematic:test-browser`
31
31
 
@@ -314,7 +314,7 @@ task({
314
314
 
315
315
  ## Plugin Agent Types
316
316
 
317
- From the `compound-engineering` plugin (examples):
317
+ From the Systematic plugin (examples):
318
318
 
319
319
  ### Review Agents
320
320
  ```javascript