@mmerterden/multi-agent-pipeline 16.18.0 → 16.20.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 (238) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +5 -5
  3. package/README.tr.md +2 -2
  4. package/docs/FIGMA_PIPELINE.md +1 -1
  5. package/docs/adr/0001-three-model-triage.md +4 -2
  6. package/docs/architecture.md +2 -2
  7. package/docs/ecosystem.md +13 -11
  8. package/docs/features.md +2 -2
  9. package/index.js +1 -1
  10. package/install/_codex-agents.mjs +2 -2
  11. package/install/_common.mjs +25 -1
  12. package/install/_dev-only-files.mjs +3 -2
  13. package/install/_mcp-register.mjs +4 -3
  14. package/install/_plugin-skills.mjs +1 -3
  15. package/install/copilot.mjs +18 -9
  16. package/install/index.mjs +2 -4
  17. package/install/templates/copilot-instructions.md +7 -7
  18. package/package.json +4 -3
  19. package/pipeline/agents/android-architect.md +1 -0
  20. package/pipeline/agents/backend-architect.md +1 -0
  21. package/pipeline/agents/code-reviewer.md +1 -0
  22. package/pipeline/agents/dev-critic.md +2 -1
  23. package/pipeline/agents/explorer.md +1 -0
  24. package/pipeline/agents/ios-architect.md +1 -0
  25. package/pipeline/agents/security-auditor.md +1 -0
  26. package/pipeline/agents/task-clarifier.md +1 -0
  27. package/pipeline/claude-md-template.md +2 -2
  28. package/pipeline/commands/multi-agent/SKILL.md +5 -5
  29. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +1 -1
  30. package/pipeline/commands/multi-agent/design-check/SKILL.md +1 -1
  31. package/pipeline/commands/multi-agent/help/SKILL.md +2 -2
  32. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -1
  33. package/pipeline/commands/multi-agent/manual-test/SKILL.md +5 -1
  34. package/pipeline/commands/multi-agent/resume/SKILL.md +1 -0
  35. package/pipeline/commands/multi-agent/review/SKILL.md +27 -14
  36. package/pipeline/commands/multi-agent/review-analysis/SKILL.md +1 -1
  37. package/pipeline/commands/multi-agent/store-ready/SKILL.md +24 -2
  38. package/pipeline/commands/multi-agent/sync/SKILL.md +2 -2
  39. package/pipeline/commands/sim-test.md +64 -20
  40. package/pipeline/lib/credential-inventory.sh +15 -2
  41. package/pipeline/lib/credential-store-resolver.sh +14 -4
  42. package/pipeline/lib/credential-store.sh +8 -2
  43. package/pipeline/lib/extract-conventions.sh +1 -14
  44. package/pipeline/lib/fetch-confluence.sh +12 -4
  45. package/pipeline/lib/fetch-crashlytics.sh +11 -8
  46. package/pipeline/lib/fetch-document.sh +1 -1
  47. package/pipeline/lib/fetch-figma-annotations.sh +7 -5
  48. package/pipeline/lib/fetch-fortify.sh +5 -3
  49. package/pipeline/lib/fetch-graylog.sh +5 -3
  50. package/pipeline/lib/figma-mcp-refresh.sh +1 -1
  51. package/pipeline/lib/figma-screenshot.sh +27 -24
  52. package/pipeline/lib/figma-token.sh +8 -4
  53. package/pipeline/lib/issue-fetcher.sh +0 -1
  54. package/pipeline/lib/jira-publish.sh +7 -5
  55. package/pipeline/lib/md2confluence-v3.py +13 -7
  56. package/pipeline/lib/multi-repo-pipeline.sh +18 -8
  57. package/pipeline/lib/plan-todos.sh +11 -0
  58. package/pipeline/lib/post-pr-review.sh +9 -2
  59. package/pipeline/lib/repo-cache.sh +18 -10
  60. package/pipeline/lib/review-watch.sh +60 -14
  61. package/pipeline/lib/shadow-git.sh +8 -4
  62. package/pipeline/lib/vercel-deploy.sh +2 -2
  63. package/pipeline/multi-agent-refs/_dev-context.md +5 -2
  64. package/pipeline/multi-agent-refs/analysis/locked.md +4 -4
  65. package/pipeline/multi-agent-refs/analysis/render.md +1 -1
  66. package/pipeline/multi-agent-refs/channels/pr.md +22 -4
  67. package/pipeline/multi-agent-refs/cross-cli-contract.md +1 -1
  68. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +15 -2
  69. package/pipeline/multi-agent-refs/features/review-delta.md +89 -0
  70. package/pipeline/multi-agent-refs/features/scope-check.md +41 -0
  71. package/pipeline/multi-agent-refs/features/verify-by-test.md +6 -5
  72. package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
  73. package/pipeline/multi-agent-refs/outside-the-pipeline.md +6 -6
  74. package/pipeline/multi-agent-refs/payload-contracts.md +1 -1
  75. package/pipeline/multi-agent-refs/phases/log-format.md +1 -1
  76. package/pipeline/multi-agent-refs/phases/modes.md +1 -1
  77. package/pipeline/multi-agent-refs/phases/phase-0-init.md +4 -2
  78. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +3 -3
  79. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +5 -5
  80. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +17 -2
  81. package/pipeline/multi-agent-refs/phases/phase-4-review.md +67 -24
  82. package/pipeline/multi-agent-refs/phases/phase-5-test.md +10 -0
  83. package/pipeline/multi-agent-refs/phases/phase-6-commit.md +2 -0
  84. package/pipeline/multi-agent-refs/phases/phase-7-report.md +4 -2
  85. package/pipeline/multi-agent-refs/rules.md +2 -2
  86. package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
  87. package/pipeline/rules/figma-pipeline.md +18 -72
  88. package/pipeline/rules/outside-the-pipeline.md +4 -3
  89. package/pipeline/schemas/agent-state.schema.json +130 -1
  90. package/pipeline/schemas/dev-critic-output.schema.json +5 -0
  91. package/pipeline/schemas/prefs.schema.json +47 -0
  92. package/pipeline/schemas/reviewer-output.schema.json +7 -2
  93. package/pipeline/schemas/scope-check.schema.json +55 -0
  94. package/pipeline/schemas/token-budget.json +3 -3
  95. package/pipeline/schemas/triage-output.schema.json +12 -2
  96. package/pipeline/scripts/README.md +3 -2
  97. package/pipeline/scripts/_fingerprint.mjs +173 -0
  98. package/pipeline/scripts/_stack-routing.mjs +1 -1
  99. package/pipeline/scripts/agent-guard.py +102 -21
  100. package/pipeline/scripts/anonymize-findings.mjs +7 -6
  101. package/pipeline/scripts/build-skills-index.mjs +14 -3
  102. package/pipeline/scripts/cost-budget-check.mjs +5 -3
  103. package/pipeline/scripts/cost-lib.sh +0 -15
  104. package/pipeline/scripts/diff-explain.mjs +22 -12
  105. package/pipeline/scripts/evidence-gate.mjs +73 -5
  106. package/pipeline/scripts/finding-fingerprint.mjs +101 -0
  107. package/pipeline/scripts/gc-refs.sh +6 -2
  108. package/pipeline/scripts/gc-tmp.sh +1 -1
  109. package/pipeline/scripts/gc-worktrees.sh +1 -1
  110. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
  111. package/pipeline/scripts/github-ssh-setup.sh +7 -2
  112. package/pipeline/scripts/graph-build.mjs +2 -2
  113. package/pipeline/scripts/jira-wiki-escape.mjs +2 -1
  114. package/pipeline/scripts/keychain.py +12 -11
  115. package/pipeline/scripts/learning-curve.mjs +1 -1
  116. package/pipeline/scripts/migrate-prefs.mjs +1 -1
  117. package/pipeline/scripts/output-quality-check.sh +3 -1
  118. package/pipeline/scripts/phase-tracker.sh +1 -1
  119. package/pipeline/scripts/phase0-exit-gate.mjs +2 -1
  120. package/pipeline/scripts/plan-coverage-gate.mjs +2 -1
  121. package/pipeline/scripts/pre-commit-check.sh +23 -13
  122. package/pipeline/scripts/prune-logs.sh +1 -1
  123. package/pipeline/scripts/render-agent-log-cost.sh +3 -1
  124. package/pipeline/scripts/render-cost-summary.sh +4 -2
  125. package/pipeline/scripts/render-work-summary.sh +5 -3
  126. package/pipeline/scripts/repo-map.mjs +3 -2
  127. package/pipeline/scripts/review-delta.mjs +217 -0
  128. package/pipeline/scripts/run-metrics.mjs +20 -0
  129. package/pipeline/scripts/scan-skills.sh +6 -2
  130. package/pipeline/scripts/scope-check-gate.mjs +90 -0
  131. package/pipeline/scripts/search-logs.sh +8 -6
  132. package/pipeline/scripts/sign-skills.sh +3 -1
  133. package/pipeline/scripts/smoke-cross-cli-behavior.sh +12 -5
  134. package/pipeline/scripts/triage-memory.mjs +25 -4
  135. package/pipeline/scripts/uninstall.mjs +20 -12
  136. package/pipeline/scripts/update-check.sh +2 -2
  137. package/pipeline/scripts/update-issue-progress.sh +6 -5
  138. package/pipeline/scripts/validate-analysis-doc.mjs +6 -6
  139. package/pipeline/scripts/validate-reviewer.mjs +6 -0
  140. package/pipeline/scripts/validate-triage.mjs +20 -0
  141. package/pipeline/scripts/verify-skills.sh +3 -1
  142. package/pipeline/scripts/worktree-finalize.sh +22 -10
  143. package/pipeline/skills/.skill-manifest.json +81 -57
  144. package/pipeline/skills/.skills-index.json +19 -19
  145. package/pipeline/skills/shared/README.md +8 -8
  146. package/pipeline/skills/shared/core/multi-agent/SKILL.md +7 -7
  147. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +1 -1
  148. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -2
  149. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +1 -1
  150. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +2 -2
  151. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +23 -14
  152. package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +5 -0
  153. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
  154. package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +56 -0
  155. package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +0 -4
  156. package/pipeline/skills/shared/external/api-patterns/SKILL.md +12 -24
  157. package/pipeline/skills/shared/external/app-store-changelog/references/release-notes-guidelines.md +34 -0
  158. package/pipeline/skills/shared/external/app-store-changelog/scripts/collect_release_changes.sh +33 -0
  159. package/pipeline/skills/shared/external/architecture/SKILL.md +7 -9
  160. package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +0 -4
  161. package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +0 -1
  162. package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +0 -14
  163. package/pipeline/skills/shared/external/hig-components-content/SKILL.md +13 -13
  164. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +16 -16
  165. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +6 -6
  166. package/pipeline/skills/shared/external/hig-components-system/SKILL.md +13 -13
  167. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -23
  168. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +18 -18
  169. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +30 -30
  170. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +11 -11
  171. package/pipeline/skills/shared/external/hig-technologies/SKILL.md +33 -33
  172. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +52 -52
  173. package/pipeline/skills/shared/external/ios-coding-standard/references/lint-local.sh +1 -1
  174. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +11 -11
  175. package/pipeline/skills/shared/external/ios-developer/SKILL.md +0 -1
  176. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +7 -3
  177. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -15
  178. package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +0 -5
  179. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Package.swift +17 -0
  180. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/Resources/.keep +0 -0
  181. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/main.swift +11 -0
  182. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/version.env +2 -0
  183. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/build_icon.sh +49 -0
  184. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/compile_and_run.sh +63 -0
  185. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/launch.sh +28 -0
  186. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/make_appcast.sh +82 -0
  187. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/package_app.sh +206 -0
  188. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh +52 -0
  189. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh +52 -0
  190. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/version.env +2 -0
  191. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/packaging.md +17 -0
  192. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/release.md +32 -0
  193. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/scaffold.md +79 -0
  194. package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +0 -1
  195. package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +0 -4
  196. package/pipeline/skills/shared/external/swift-concurrency-expert/references/approachable-concurrency.md +63 -0
  197. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swift-6-2-concurrency.md +272 -0
  198. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md +33 -0
  199. package/pipeline/skills/shared/external/swiftui-performance-audit/references/code-smells.md +150 -0
  200. package/pipeline/skills/shared/external/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
  201. package/pipeline/skills/shared/external/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
  202. package/pipeline/skills/shared/external/swiftui-performance-audit/references/profiling-intake.md +44 -0
  203. package/pipeline/skills/shared/external/swiftui-performance-audit/references/report-template.md +47 -0
  204. package/pipeline/skills/shared/external/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
  205. package/pipeline/skills/shared/external/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
  206. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/app-wiring.md +201 -0
  207. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/async-state.md +96 -0
  208. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +46 -0
  209. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/controls.md +57 -0
  210. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/deeplinks.md +66 -0
  211. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/focus.md +90 -0
  212. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/form.md +97 -0
  213. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/grids.md +71 -0
  214. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +71 -0
  215. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  216. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  217. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/list.md +86 -0
  218. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  219. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/macos-settings.md +71 -0
  220. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  221. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/media.md +73 -0
  222. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/menu-bar.md +101 -0
  223. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/navigationstack.md +159 -0
  224. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/overlay.md +45 -0
  225. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/performance.md +62 -0
  226. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/previews.md +48 -0
  227. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  228. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scrollview.md +87 -0
  229. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/searchable.md +71 -0
  230. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/sheets.md +155 -0
  231. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/split-views.md +72 -0
  232. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/tabview.md +114 -0
  233. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/theming.md +71 -0
  234. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/title-menus.md +93 -0
  235. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/top-bar.md +49 -0
  236. package/pipeline/skills/shared/external/swiftui-view-refactor/references/mv-patterns.md +161 -0
  237. package/pipeline/skills/skills-index.md +8 -8
  238. package/pipeline/skills/shared/external/help-skills/SKILL.md +0 -166
@@ -1,7 +1,9 @@
1
1
  #!/bin/bash
2
2
  # Pre-commit secret detection for multi-agent pipeline
3
3
  # Scans staged git changes for accidentally committed secrets
4
- # Exit 0 = clean, Exit 1 = secrets found (blocks commit)
4
+ # Exit 0 = clean, Exit 2 = secrets found. The PreToolUse hook contract
5
+ # (install/templates/claude-hooks.json, agent-guard.sh) blocks the tool call
6
+ # only on exit 2, with the reason on stderr.
5
7
 
6
8
  set -uo pipefail
7
9
 
@@ -56,6 +58,14 @@ process.stdin.on("end", () => {
56
58
  fi
57
59
  fi
58
60
 
61
+ # Staged paths from `git diff --cached --name-only` are repo-root-relative, so
62
+ # the `-f` tests below only see them from the repo root. Hooks fire with the
63
+ # caller's cwd, which is often a subdirectory.
64
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
65
+ if [ -n "$REPO_ROOT" ]; then
66
+ cd "$REPO_ROOT" || exit 0
67
+ fi
68
+
59
69
  FOUND=0
60
70
 
61
71
  # `file "$f" | grep -q "binary"` never matches real `file` output (it names a
@@ -74,14 +84,14 @@ scan_file() {
74
84
 
75
85
  # .env files
76
86
  if echo "$file" | grep -qE '\.env($|\.)'; then
77
- echo "BLOCKED: .env file staged: $file"
87
+ echo "BLOCKED: .env file staged: $file" >&2
78
88
  FOUND=1
79
89
  return 0
80
90
  fi
81
91
 
82
92
  # Credentials files
83
93
  if echo "$file" | grep -qiE '(credentials|secrets|tokens)\.(json|yaml|yml|plist)$'; then
84
- echo "BLOCKED: Credentials file staged: $file"
94
+ echo "BLOCKED: Credentials file staged: $file" >&2
85
95
  FOUND=1
86
96
  return 0
87
97
  fi
@@ -90,37 +100,37 @@ scan_file() {
90
100
 
91
101
  # API keys / tokens (key = "long_string" pattern)
92
102
  if echo "$content" | grep -qiE '(api[_-]?key|api[_-]?secret|access[_-]?token|auth[_-]?token|secret[_-]?key)\s*[:=]\s*["'"'"'][A-Za-z0-9+/=_-]{20,}'; then
93
- echo "BLOCKED: Possible API key/token in $file"
103
+ echo "BLOCKED: Possible API key/token in $file" >&2
94
104
  FOUND=1
95
105
  fi
96
106
 
97
107
  # AWS access keys
98
108
  if echo "$content" | grep -qE 'AKIA[0-9A-Z]{16}'; then
99
- echo "BLOCKED: AWS access key in $file"
109
+ echo "BLOCKED: AWS access key in $file" >&2
100
110
  FOUND=1
101
111
  fi
102
112
 
103
113
  # Private keys
104
114
  if echo "$content" | grep -qE 'BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY'; then
105
- echo "BLOCKED: Private key in $file"
115
+ echo "BLOCKED: Private key in $file" >&2
106
116
  FOUND=1
107
117
  fi
108
118
 
109
119
  # Firebase/GCP service account JSON
110
120
  if echo "$content" | grep -qE '"type"\s*:\s*"service_account"'; then
111
- echo "BLOCKED: Service account JSON in $file"
121
+ echo "BLOCKED: Service account JSON in $file" >&2
112
122
  FOUND=1
113
123
  fi
114
124
 
115
125
  # High-signal provider token prefixes (low false-positive rate)
116
126
  if echo "$content" | grep -qE '(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{60,}|xox[baprs]-[A-Za-z0-9-]{12,}|sk_live_[A-Za-z0-9]{20,}|rk_live_[A-Za-z0-9]{20,}|AIza[0-9A-Za-z_-]{35}|npm_[A-Za-z0-9]{36}|glpat-[A-Za-z0-9_-]{20,}'; then
117
- echo "BLOCKED: Provider access token in $file"
127
+ echo "BLOCKED: Provider access token in $file" >&2
118
128
  FOUND=1
119
129
  fi
120
130
 
121
131
  # JWT (three base64url segments - header.payload.signature)
122
132
  if echo "$content" | grep -qE 'eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}'; then
123
- echo "BLOCKED: JWT in $file"
133
+ echo "BLOCKED: JWT in $file" >&2
124
134
  FOUND=1
125
135
  fi
126
136
 
@@ -191,7 +201,7 @@ scan_file() {
191
201
  if (H >= 4.2) { print s; exit }
192
202
  }')
193
203
  if [ -n "$entropy_hit" ]; then
194
- echo "BLOCKED: High-entropy string (possible secret) in $file"
204
+ echo "BLOCKED: High-entropy string (possible secret) in $file" >&2
195
205
  FOUND=1
196
206
  fi
197
207
  fi
@@ -234,9 +244,9 @@ if [ "$SCAN_UNSTAGED" = "1" ]; then
234
244
  fi
235
245
 
236
246
  if [ $FOUND -eq 1 ]; then
237
- echo ""
238
- echo "Secret check failed. Remove secrets from staged files before committing."
239
- exit 1
247
+ echo "" >&2
248
+ echo "Secret check failed. Remove secrets from staged files before committing." >&2
249
+ exit 2
240
250
  fi
241
251
 
242
252
  exit 0
@@ -151,7 +151,7 @@ while IFS= read -r -d '' d; do
151
151
  # so a concurrent run keeps a resumable log dir. --task is an explicit,
152
152
  # deliberate target and bypasses the grace.
153
153
  if [ "$GRACE_MIN" -gt 0 ] && [ -z "$TASK" ] &&
154
- find "$d" -mmin "-$GRACE_MIN" -print 2>/dev/null | head -1 | grep -q .; then
154
+ [ -n "$(find "$d" -mmin "-$GRACE_MIN" -print -quit 2>/dev/null)" ]; then
155
155
  skipped_active=$((skipped_active + 1))
156
156
  continue
157
157
  fi
@@ -25,7 +25,9 @@ shift || true
25
25
  SPANS_FILE=""
26
26
  while [ $# -gt 0 ]; do
27
27
  case "$1" in
28
- --otel-spans) SPANS_FILE="${2:-}"; shift 2 ;;
28
+ --otel-spans)
29
+ [ $# -ge 2 ] || { echo "render-agent-log-cost: --otel-spans needs a path" >&2; exit 1; }
30
+ SPANS_FILE="$2"; shift 2 ;;
29
31
  *) shift ;;
30
32
  esac
31
33
  done
@@ -19,7 +19,9 @@ shift || true
19
19
  SPANS_FILE=""
20
20
  while [ $# -gt 0 ]; do
21
21
  case "$1" in
22
- --otel-spans) SPANS_FILE="${2:-}"; shift 2 ;;
22
+ --otel-spans)
23
+ [ $# -ge 2 ] || { echo "render-cost-summary: --otel-spans needs a path" >&2; exit 1; }
24
+ SPANS_FILE="$2"; shift 2 ;;
23
25
  *) shift ;;
24
26
  esac
25
27
  done
@@ -71,7 +73,7 @@ if [ -n "$tracker_file" ]; then
71
73
  (.phases // {} | to_entries)
72
74
  end)
73
75
  | [ .[] |
74
- select(.value.tokens_in // 0 > 0 or .value.tokens_out // 0 > 0) |
76
+ select(((.value.tokens_in // 0) > 0) or ((.value.tokens_out // 0) > 0)) |
75
77
  {
76
78
  phase: .key,
77
79
  phase_name: (.value.name // .key),
@@ -44,11 +44,13 @@ TASK_ID="${1:?usage: render-work-summary.sh <task-id>}"
44
44
  shift || true
45
45
 
46
46
  WORKTREE=""; BRANCH=""; BASE=""
47
+ need_value() { [ $# -ge 2 ] || { echo "render-work-summary: $1 needs a value" >&2; exit 1; }; }
48
+
47
49
  while [ $# -gt 0 ]; do
48
50
  case "$1" in
49
- --worktree) WORKTREE="$2"; shift 2 ;;
50
- --branch) BRANCH="$2"; shift 2 ;;
51
- --base-branch) BASE="$2"; shift 2 ;;
51
+ --worktree) need_value "$@"; WORKTREE="$2"; shift 2 ;;
52
+ --branch) need_value "$@"; BRANCH="$2"; shift 2 ;;
53
+ --base-branch) need_value "$@"; BASE="$2"; shift 2 ;;
52
54
  *) shift ;;
53
55
  esac
54
56
  done
@@ -30,6 +30,7 @@ import { promises as fs } from "node:fs";
30
30
  import { existsSync, statSync } from "node:fs";
31
31
  import path from "node:path";
32
32
  import process from "node:process";
33
+ import { matchesGlob } from "./_path-match.mjs";
33
34
 
34
35
  // ---- CLI args --------------------------------------------------------------
35
36
 
@@ -142,8 +143,8 @@ async function walk(root, args) {
142
143
  continue;
143
144
  }
144
145
  const rel = path.relative(root, full);
145
- if (args.include && !args.include.some((g) => rel.includes(g))) continue;
146
- if (args.exclude && args.exclude.some((g) => rel.includes(g))) continue;
146
+ if (args.include && !args.include.some((g) => matchesGlob(rel, g))) continue;
147
+ if (args.exclude && args.exclude.some((g) => matchesGlob(rel, g))) continue;
147
148
  out.push({ path: rel, lang: LANG_EXT[ext], abs: full });
148
149
  }
149
150
  }
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * review-delta.mjs - classify one Phase 4 round against the round before it.
4
+ *
5
+ * Reviewers re-read the whole diff every round and return findings with no
6
+ * memory of the previous round. This script gives the loop that memory: it
7
+ * fingerprints both rounds (see _fingerprint.mjs) and reports which accepted
8
+ * findings are still present after the fix, which were resolved, which triage
9
+ * downgraded, and which are new. A finding that survives `--trip-cycles`
10
+ * consecutive rework cycles is the autopilot circuit-breaker's trigger 2.
11
+ *
12
+ * Usage:
13
+ * review-delta.mjs --rounds-dir <dir> --iteration N [options]
14
+ * review-delta.mjs --previous <round-N-1.json> --current <round-N.json> [--prior <round-N-2.json> ...] [options]
15
+ *
16
+ * Options:
17
+ * --kind triage|reviewer|dev-critic document shape (default triage)
18
+ * --trip-cycles N recurrence that trips (default 2)
19
+ * --severities a,b accepted severities that count as the
20
+ * rework mandate (default blocking,important)
21
+ *
22
+ * Output (stdout, JSON): {iteration, previousIteration, counts, new[], stillPresent[],
23
+ * resolved[], downgraded[], stillPresentBlocking[], recurrence{}, tripped, plateau}
24
+ * Each list entry is {fingerprint, severity, file, issue} so a log line reads
25
+ * without a lookup. With no previous round: {"skipped":"no-previous-round"}.
26
+ *
27
+ * Exit codes:
28
+ * 0 progress, or nothing to compare (fail-open like evidence-gate)
29
+ * 1 unparseable input or wrong shape
30
+ * 3 trigger-2 signal: a mandate finding recurred >= --trip-cycles times
31
+ * 64 usage
32
+ */
33
+
34
+ import { existsSync, readFileSync } from "node:fs";
35
+ import { join } from "node:path";
36
+
37
+ import { annotate, collectFingerprints, KINDS } from "./_fingerprint.mjs";
38
+
39
+ const USAGE = `usage:
40
+ review-delta.mjs --rounds-dir <dir> --iteration N [--kind ${KINDS.join("|")}] [--trip-cycles N] [--severities a,b]
41
+ review-delta.mjs --previous <json> --current <json> [--prior <json> ...] [same options]`;
42
+
43
+ const ROUND_FILE = (n) => `triage-round-${n}.json`;
44
+
45
+ function parseArgs(argv) {
46
+ const opts = { prior: [], kind: "triage", tripCycles: 2, severities: ["blocking", "important"] };
47
+ for (let i = 0; i < argv.length; i++) {
48
+ const a = argv[i];
49
+ const next = () => argv[++i];
50
+ if (a === "--rounds-dir") opts.roundsDir = next();
51
+ else if (a === "--iteration") opts.iteration = Number(next());
52
+ else if (a === "--previous") opts.previous = next();
53
+ else if (a === "--current") opts.current = next();
54
+ else if (a === "--prior") opts.prior.push(next());
55
+ else if (a === "--kind") opts.kind = next();
56
+ else if (a === "--trip-cycles") opts.tripCycles = Number(next());
57
+ else if (a === "--severities")
58
+ opts.severities = next()
59
+ .split(",")
60
+ .map((s) => s.trim());
61
+ else return null;
62
+ }
63
+ return opts;
64
+ }
65
+
66
+ function loadDoc(path, kind) {
67
+ const doc = JSON.parse(readFileSync(path, "utf-8"));
68
+ return annotate(doc, kind).doc;
69
+ }
70
+
71
+ /**
72
+ * Resolve the ordered list of round files, newest first: [current, previous, prior...].
73
+ * Returns null when there is no previous round to compare against.
74
+ */
75
+ function resolveRounds(opts) {
76
+ if (opts.roundsDir) {
77
+ if (!Number.isInteger(opts.iteration) || opts.iteration < 1) {
78
+ throw new Error("--iteration must be a positive integer");
79
+ }
80
+ const current = join(opts.roundsDir, ROUND_FILE(opts.iteration));
81
+ if (!existsSync(current)) throw new Error(`current round file missing: ${current}`);
82
+ const chain = [current];
83
+ for (let n = opts.iteration - 1; n >= 1; n--) {
84
+ const p = join(opts.roundsDir, ROUND_FILE(n));
85
+ if (!existsSync(p)) break;
86
+ chain.push(p);
87
+ }
88
+ return { chain, iteration: opts.iteration };
89
+ }
90
+ if (!opts.current) throw new Error("--current is required without --rounds-dir");
91
+ const chain = [opts.current];
92
+ if (opts.previous) chain.push(opts.previous, ...opts.prior);
93
+ return { chain, iteration: chain.length };
94
+ }
95
+
96
+ function entry(fp, finding) {
97
+ return {
98
+ fingerprint: fp,
99
+ severity: finding.severity ?? null,
100
+ file: finding.file ?? null,
101
+ issue: finding.issue ?? null,
102
+ };
103
+ }
104
+
105
+ function buckets(kind) {
106
+ return kind === "triage"
107
+ ? { accepted: ["accepted"], elsewhere: ["deferred", "rejected"] }
108
+ : { accepted: ["findings"], elsewhere: [] };
109
+ }
110
+
111
+ function mandate(doc, kind, severities) {
112
+ return collectFingerprints(doc, { kind, buckets: buckets(kind).accepted, severities });
113
+ }
114
+
115
+ function setEqual(a, b) {
116
+ if (a.size !== b.size) return false;
117
+ for (const x of a) if (!b.has(x)) return false;
118
+ return true;
119
+ }
120
+
121
+ export function computeDelta(docs, { kind, severities, tripCycles }) {
122
+ const [current, previous, ...priors] = docs;
123
+ const b = buckets(kind);
124
+ const prevMandate = mandate(previous, kind, severities);
125
+ const prevAll = collectFingerprints(previous, { kind, buckets: b.accepted });
126
+ const curAccepted = collectFingerprints(current, { kind, buckets: b.accepted });
127
+ const curElsewhere = collectFingerprints(current, { kind, buckets: b.elsewhere });
128
+
129
+ const stillPresent = [];
130
+ const resolved = [];
131
+ const downgraded = [];
132
+ for (const [fp, f] of prevMandate) {
133
+ if (curAccepted.has(fp)) stillPresent.push(entry(fp, curAccepted.get(fp)));
134
+ else if (curElsewhere.has(fp)) downgraded.push(entry(fp, f));
135
+ else resolved.push(entry(fp, f));
136
+ }
137
+ const fresh = [];
138
+ for (const [fp, f] of curAccepted) if (!prevAll.has(fp)) fresh.push(entry(fp, f));
139
+
140
+ const priorMandates = priors.map((d) => mandate(d, kind, severities));
141
+ const recurrence = {};
142
+ for (const { fingerprint } of stillPresent) {
143
+ let n = 1;
144
+ for (const m of priorMandates) {
145
+ if (!m.has(fingerprint)) break;
146
+ n += 1;
147
+ }
148
+ recurrence[fingerprint] = n;
149
+ }
150
+
151
+ let plateau = false;
152
+ if (priors.length > 0 && stillPresent.length > 0) {
153
+ const prevStill = new Set();
154
+ for (const fp of priorMandates[0].keys()) if (prevMandate.has(fp)) prevStill.add(fp);
155
+ plateau = setEqual(prevStill, new Set(stillPresent.map((e) => e.fingerprint)));
156
+ }
157
+
158
+ const tripped = stillPresent.some((e) => recurrence[e.fingerprint] >= tripCycles);
159
+ return {
160
+ counts: {
161
+ new: fresh.length,
162
+ stillPresent: stillPresent.length,
163
+ resolved: resolved.length,
164
+ downgraded: downgraded.length,
165
+ },
166
+ new: fresh,
167
+ stillPresent,
168
+ resolved,
169
+ downgraded,
170
+ stillPresentBlocking: stillPresent.filter((e) => e.severity === "blocking"),
171
+ recurrence,
172
+ tripped,
173
+ plateau,
174
+ };
175
+ }
176
+
177
+ function main() {
178
+ const opts = parseArgs(process.argv.slice(2));
179
+ if (!opts || !KINDS.includes(opts.kind) || !(opts.tripCycles >= 1)) {
180
+ console.error(USAGE);
181
+ return 64;
182
+ }
183
+
184
+ let rounds;
185
+ try {
186
+ rounds = resolveRounds(opts);
187
+ } catch (err) {
188
+ console.error(`error: ${err.message}`);
189
+ return opts.roundsDir && /missing/.test(err.message) ? 1 : 64;
190
+ }
191
+
192
+ if (rounds.chain.length < 2) {
193
+ process.stdout.write(JSON.stringify({ skipped: "no-previous-round" }) + "\n");
194
+ return 0;
195
+ }
196
+
197
+ let docs;
198
+ try {
199
+ docs = rounds.chain.map((p) => loadDoc(p, opts.kind));
200
+ } catch (err) {
201
+ console.error(`error: ${err.message}`);
202
+ return 1;
203
+ }
204
+
205
+ const delta = computeDelta(docs, opts);
206
+ const out = {
207
+ iteration: rounds.iteration,
208
+ previousIteration: rounds.iteration - 1,
209
+ ...delta,
210
+ };
211
+ process.stdout.write(JSON.stringify(out, null, 2) + "\n");
212
+ return delta.tripped ? 3 : 0;
213
+ }
214
+
215
+ if (process.argv[1] && /review-delta\.mjs$/.test(process.argv[1])) {
216
+ process.exit(main());
217
+ }
@@ -23,6 +23,11 @@
23
23
  * attribution reports "unavailable".
24
24
  * consensusVerdict - unanimous-* / split / unverified
25
25
  * buildPassed - per-repo build outcome
26
+ * diff.filesTouched / locAdded / - size of the change, from state.diffRisk
27
+ * locRemoved / maxScore (Phase 4 Step 1.75); null when the
28
+ * run predates the persisted totals
29
+ * reviewDelta.stillPresentFinal / - cross-round classification of the
30
+ * resolvedTotal / tripped last iteration (state.reviewIterations[].delta)
26
31
  *
27
32
  * Usage:
28
33
  * node pipeline/scripts/run-metrics.mjs --state path/to/agent-state.json
@@ -147,6 +152,10 @@ if (projects.length) {
147
152
  buildPassed = typeof b === "string" ? b === "passed" : b && b.ok === true;
148
153
  }
149
154
 
155
+ const diffRisk = state.diffRisk && typeof state.diffRisk === "object" ? state.diffRisk : {};
156
+ const deltas = iterations.map((it) => it.delta).filter((d) => d && typeof d === "object");
157
+ const lastDelta = deltas.length ? deltas[deltas.length - 1] : null;
158
+
150
159
  const metrics = {
151
160
  taskId: state.taskId ?? null,
152
161
  status: state.status ?? null,
@@ -193,6 +202,17 @@ const metrics = {
193
202
  },
194
203
  consensusVerdict: lastTriage.consensus ? lastTriage.consensus.verdict : null,
195
204
  buildPassed,
205
+ diff: {
206
+ filesTouched: Number.isInteger(diffRisk.files) ? diffRisk.files : null,
207
+ locAdded: Number.isInteger(diffRisk.loc_added) ? diffRisk.loc_added : null,
208
+ locRemoved: Number.isInteger(diffRisk.loc_removed) ? diffRisk.loc_removed : null,
209
+ maxScore: typeof diffRisk.max_score === "number" ? diffRisk.max_score : null,
210
+ },
211
+ reviewDelta: {
212
+ stillPresentFinal: lastDelta ? (lastDelta.stillPresent || []).length : null,
213
+ resolvedTotal: deltas.length ? deltas.reduce((n, d) => n + (d.resolved || []).length, 0) : null,
214
+ tripped: deltas.some((d) => d.tripped === true),
215
+ },
196
216
  };
197
217
 
198
218
  console.log(JSON.stringify(metrics, null, 2));
@@ -34,10 +34,14 @@ THRESHOLD="medium"
34
34
 
35
35
  while [ $# -gt 0 ]; do
36
36
  case "$1" in
37
- --root) ROOT="$2"; shift 2 ;;
37
+ --root)
38
+ [ $# -ge 2 ] || { echo "scan-skills: --root needs a value" >&2; exit 64; }
39
+ ROOT="$2"; shift 2 ;;
38
40
  --strict) STRICT=1; shift ;;
39
41
  --json) JSON=1; shift ;;
40
- --threshold) THRESHOLD="$2"; shift 2 ;;
42
+ --threshold)
43
+ [ $# -ge 2 ] || { echo "scan-skills: --threshold needs a value" >&2; exit 64; }
44
+ THRESHOLD="$2"; shift 2 ;;
41
45
  --help|-h)
42
46
  sed -n '1,30p' "$0" | sed 's/^# \{0,1\}//'
43
47
  exit 0
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scope-check-gate.mjs - every file in the diff needs a stated reason.
4
+ *
5
+ * Phase 3 writes `.pipeline/scope-check.json` before the handoff: one reason
6
+ * per touched file, the things it deliberately did not do, and the simplifier
7
+ * rationales that used to be discarded. This gate compares that record with
8
+ * the real diff so an unjustified file cannot ride into review unnoticed.
9
+ *
10
+ * Usage:
11
+ * git diff --name-only <base>...HEAD | node scope-check-gate.mjs --check .pipeline/scope-check.json --diff-files -
12
+ * node scope-check-gate.mjs --check scope-check.json --diff-files files.txt [--advisory]
13
+ *
14
+ * Output (stdout, JSON): {ok, justified, unjustified[], unlisted[], notDone}
15
+ * unjustified = in the diff, missing from files[] (or with an empty reason)
16
+ * unlisted = in files[], not in the diff (stale entry; reported, never fatal)
17
+ *
18
+ * Exit codes: 0 every diff file justified (or --advisory), 1 otherwise or on
19
+ * unreadable input, 64 usage.
20
+ */
21
+
22
+ import { readFileSync } from "node:fs";
23
+
24
+ import { normalizeFile } from "./_fingerprint.mjs";
25
+
26
+ const USAGE =
27
+ "usage: scope-check-gate.mjs --check <scope-check.json> --diff-files <path|-> [--advisory]";
28
+
29
+ function parseArgs(argv) {
30
+ const opts = {};
31
+ for (let i = 0; i < argv.length; i++) {
32
+ const a = argv[i];
33
+ if (a === "--check") opts.check = argv[++i];
34
+ else if (a === "--diff-files") opts.diffFiles = argv[++i];
35
+ else if (a === "--advisory") opts.advisory = true;
36
+ else return null;
37
+ }
38
+ return opts.check && opts.diffFiles ? opts : null;
39
+ }
40
+
41
+ function readText(path) {
42
+ return path === "-" ? readFileSync(0, "utf-8") : readFileSync(path, "utf-8");
43
+ }
44
+
45
+ export function evaluate(check, diffFiles) {
46
+ const files = Array.isArray(check?.files) ? check.files : [];
47
+ const reasons = new Map();
48
+ for (const f of files) {
49
+ if (!f || typeof f.path !== "string") continue;
50
+ const reason = typeof f.reason === "string" ? f.reason.trim() : "";
51
+ reasons.set(normalizeFile(f.path), reason);
52
+ }
53
+ const diff = [...new Set(diffFiles.map(normalizeFile).filter(Boolean))];
54
+ const unjustified = diff.filter((p) => !(reasons.get(p) ?? "").length);
55
+ const diffSet = new Set(diff);
56
+ const unlisted = [...reasons.keys()].filter((p) => !diffSet.has(p));
57
+ return {
58
+ ok: unjustified.length === 0,
59
+ justified: diff.length - unjustified.length,
60
+ unjustified,
61
+ unlisted,
62
+ notDone: Array.isArray(check?.notDone) ? check.notDone.length : 0,
63
+ };
64
+ }
65
+
66
+ function main() {
67
+ const opts = parseArgs(process.argv.slice(2));
68
+ if (!opts) {
69
+ console.error(USAGE);
70
+ return 64;
71
+ }
72
+ const fail = opts.advisory ? 0 : 1;
73
+ let check;
74
+ let diffFiles;
75
+ try {
76
+ check = JSON.parse(readText(opts.check));
77
+ diffFiles = readText(opts.diffFiles).split("\n");
78
+ } catch (err) {
79
+ console.error(`scope-check-gate: ${err.message}`);
80
+ process.stdout.write(JSON.stringify({ ok: false, error: err.message }) + "\n");
81
+ return fail;
82
+ }
83
+ const result = evaluate(check, diffFiles);
84
+ process.stdout.write(JSON.stringify(result) + "\n");
85
+ return result.ok ? 0 : fail;
86
+ }
87
+
88
+ if (process.argv[1] && /scope-check-gate\.mjs$/.test(process.argv[1])) {
89
+ process.exit(main());
90
+ }
@@ -42,16 +42,18 @@ LIMIT=20
42
42
  QUERY=""
43
43
  SEMANTIC=0
44
44
 
45
+ need_value() { [ $# -ge 2 ] || { echo "search-logs: $1 needs a value" >&2; exit 64; }; }
46
+
45
47
  while [ $# -gt 0 ]; do
46
48
  case "$1" in
47
- --root) ROOT="$2"; shift 2 ;;
48
- --project) PROJECT="$2"; shift 2 ;;
49
- --since) SINCE="$2"; shift 2 ;;
50
- --phase) PHASE="$2"; shift 2 ;;
49
+ --root) need_value "$@"; ROOT="$2"; shift 2 ;;
50
+ --project) need_value "$@"; PROJECT="$2"; shift 2 ;;
51
+ --since) need_value "$@"; SINCE="$2"; shift 2 ;;
52
+ --phase) need_value "$@"; PHASE="$2"; shift 2 ;;
51
53
  --json) JSON=1; shift ;;
52
54
  --tsv) TSV=1; shift ;;
53
55
  --semantic) SEMANTIC=1; shift ;;
54
- --limit) LIMIT="$2"; shift 2 ;;
56
+ --limit) need_value "$@"; LIMIT="$2"; shift 2 ;;
55
57
  --help|-h)
56
58
  awk 'NR==1{next} /^[^#]/{exit} {sub(/^# ?/,""); print}' "$0"
57
59
  exit 0
@@ -78,7 +80,7 @@ fi
78
80
  # --limit (passed as --top N). --since/--phase have no meaning here.
79
81
  if [ "$SEMANTIC" -eq 1 ]; then
80
82
  TM_SCRIPT="$SCRIPT_DIR/triage-memory.mjs"
81
- if [ ! -x "$TM_SCRIPT" ] && ! command -v node >/dev/null 2>&1; then
83
+ if [ ! -f "$TM_SCRIPT" ] || ! command -v node >/dev/null 2>&1; then
82
84
  echo "search-logs: --semantic requires node + triage-memory.mjs" >&2
83
85
  exit 1
84
86
  fi
@@ -18,7 +18,9 @@ set -euo pipefail
18
18
  ROOT_OVERRIDE=""
19
19
  while [ $# -gt 0 ]; do
20
20
  case "$1" in
21
- --root) ROOT_OVERRIDE="$2"; shift 2 ;;
21
+ --root)
22
+ [ $# -ge 2 ] || { echo "sign-skills: --root needs a value" >&2; exit 1; }
23
+ ROOT_OVERRIDE="$2"; shift 2 ;;
22
24
  *) shift ;;
23
25
  esac
24
26
  done
@@ -95,7 +95,7 @@ echo ""
95
95
  echo "→ 3. No personal-data hardcodes in generic docs"
96
96
  # These are patterns that must be placeholders, never literals
97
97
  PERSONAL_HARDCODES=(
98
- "${USER}_tkgithub"
98
+ "${USER:-$(id -un)}_tkgithub"
99
99
  )
100
100
  for p in "${PERSONAL_HARDCODES[@]}"; do
101
101
  FOUND=$(find "$CLAUDE_CMDS" -name SKILL.md -exec grep -l -F -- "$p" {} + 2>/dev/null || true)
@@ -159,15 +159,22 @@ echo "→ 7. Agent personas deployed to both CLIs"
159
159
 
160
160
  CLAUDE_AGENTS="$HOME/.claude/agents"
161
161
  COPILOT_AGENTS="$HOME/.copilot/agents"
162
- AGENT_FILES=(code-reviewer.md explorer.md ios-architect.md android-architect.md backend-architect.md security-auditor.md)
162
+ AGENT_FILES=()
163
+ if [ -d "$CLAUDE_AGENTS" ]; then
164
+ for f in "$CLAUDE_AGENTS"/*.md; do
165
+ [ -f "$f" ] && AGENT_FILES+=("$(basename "$f")")
166
+ done
167
+ fi
163
168
 
164
169
  if [ -d "$CLAUDE_AGENTS" ] && [ -d "$COPILOT_AGENTS" ]; then
165
170
  missing_on_copilot=0
166
- for a in "${AGENT_FILES[@]}"; do
171
+ for a in ${AGENT_FILES[@]+"${AGENT_FILES[@]}"}; do
167
172
  [ -f "$COPILOT_AGENTS/$a" ] || missing_on_copilot=$((missing_on_copilot+1))
168
173
  done
169
- if [ "$missing_on_copilot" -eq 0 ]; then
170
- pass "all 6 reviewer/explorer personas present on both CLIs"
174
+ if [ "$missing_on_copilot" -eq 0 ] && [ "${#AGENT_FILES[@]}" -gt 0 ]; then
175
+ pass "all ${#AGENT_FILES[@]} personas present on both CLIs"
176
+ elif [ "${#AGENT_FILES[@]}" -eq 0 ]; then
177
+ fail "no persona files under $CLAUDE_AGENTS"
171
178
  else
172
179
  fail "$missing_on_copilot persona file(s) missing from Copilot side"
173
180
  fi