@mmerterden/multi-agent-pipeline 16.18.0 → 16.19.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 (200) hide show
  1. package/CHANGELOG.md +45 -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/_common.mjs +25 -1
  11. package/install/_dev-only-files.mjs +2 -2
  12. package/install/_mcp-register.mjs +4 -3
  13. package/install/_plugin-skills.mjs +1 -3
  14. package/install/copilot.mjs +18 -9
  15. package/install/index.mjs +2 -4
  16. package/install/templates/copilot-instructions.md +7 -7
  17. package/package.json +1 -1
  18. package/pipeline/claude-md-template.md +2 -2
  19. package/pipeline/commands/multi-agent/SKILL.md +5 -5
  20. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +1 -1
  21. package/pipeline/commands/multi-agent/design-check/SKILL.md +1 -1
  22. package/pipeline/commands/multi-agent/help/SKILL.md +2 -2
  23. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -1
  24. package/pipeline/commands/multi-agent/review/SKILL.md +27 -14
  25. package/pipeline/commands/multi-agent/review-analysis/SKILL.md +1 -1
  26. package/pipeline/commands/multi-agent/store-ready/SKILL.md +2 -2
  27. package/pipeline/commands/multi-agent/sync/SKILL.md +2 -2
  28. package/pipeline/commands/sim-test.md +54 -15
  29. package/pipeline/lib/credential-inventory.sh +15 -2
  30. package/pipeline/lib/credential-store-resolver.sh +14 -4
  31. package/pipeline/lib/credential-store.sh +8 -2
  32. package/pipeline/lib/extract-conventions.sh +1 -14
  33. package/pipeline/lib/fetch-confluence.sh +12 -4
  34. package/pipeline/lib/fetch-crashlytics.sh +11 -8
  35. package/pipeline/lib/fetch-document.sh +1 -1
  36. package/pipeline/lib/fetch-figma-annotations.sh +7 -5
  37. package/pipeline/lib/fetch-fortify.sh +5 -3
  38. package/pipeline/lib/fetch-graylog.sh +5 -3
  39. package/pipeline/lib/figma-mcp-refresh.sh +1 -1
  40. package/pipeline/lib/figma-screenshot.sh +27 -24
  41. package/pipeline/lib/figma-token.sh +8 -4
  42. package/pipeline/lib/issue-fetcher.sh +0 -1
  43. package/pipeline/lib/jira-publish.sh +7 -5
  44. package/pipeline/lib/md2confluence-v3.py +13 -7
  45. package/pipeline/lib/multi-repo-pipeline.sh +18 -8
  46. package/pipeline/lib/plan-todos.sh +11 -0
  47. package/pipeline/lib/post-pr-review.sh +9 -2
  48. package/pipeline/lib/repo-cache.sh +18 -10
  49. package/pipeline/lib/review-watch.sh +60 -14
  50. package/pipeline/lib/shadow-git.sh +8 -4
  51. package/pipeline/lib/vercel-deploy.sh +2 -2
  52. package/pipeline/multi-agent-refs/_dev-context.md +5 -2
  53. package/pipeline/multi-agent-refs/analysis/locked.md +4 -4
  54. package/pipeline/multi-agent-refs/analysis/render.md +1 -1
  55. package/pipeline/multi-agent-refs/cross-cli-contract.md +1 -1
  56. package/pipeline/multi-agent-refs/outside-the-pipeline.md +6 -6
  57. package/pipeline/multi-agent-refs/phases/log-format.md +1 -1
  58. package/pipeline/multi-agent-refs/phases/modes.md +1 -1
  59. package/pipeline/multi-agent-refs/phases/phase-0-init.md +4 -2
  60. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +3 -3
  61. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +5 -5
  62. package/pipeline/multi-agent-refs/phases/phase-4-review.md +21 -16
  63. package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
  64. package/pipeline/rules/figma-pipeline.md +18 -72
  65. package/pipeline/rules/outside-the-pipeline.md +4 -3
  66. package/pipeline/schemas/agent-state.schema.json +1 -1
  67. package/pipeline/schemas/token-budget.json +1 -1
  68. package/pipeline/scripts/_stack-routing.mjs +1 -1
  69. package/pipeline/scripts/agent-guard.py +102 -21
  70. package/pipeline/scripts/anonymize-findings.mjs +7 -6
  71. package/pipeline/scripts/build-skills-index.mjs +14 -3
  72. package/pipeline/scripts/cost-budget-check.mjs +5 -3
  73. package/pipeline/scripts/cost-lib.sh +0 -15
  74. package/pipeline/scripts/diff-explain.mjs +22 -12
  75. package/pipeline/scripts/gc-refs.sh +6 -2
  76. package/pipeline/scripts/gc-tmp.sh +1 -1
  77. package/pipeline/scripts/gc-worktrees.sh +1 -1
  78. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
  79. package/pipeline/scripts/github-ssh-setup.sh +7 -2
  80. package/pipeline/scripts/graph-build.mjs +2 -2
  81. package/pipeline/scripts/jira-wiki-escape.mjs +2 -1
  82. package/pipeline/scripts/keychain.py +12 -11
  83. package/pipeline/scripts/learning-curve.mjs +1 -1
  84. package/pipeline/scripts/migrate-prefs.mjs +1 -1
  85. package/pipeline/scripts/output-quality-check.sh +3 -1
  86. package/pipeline/scripts/phase-tracker.sh +1 -1
  87. package/pipeline/scripts/phase0-exit-gate.mjs +2 -1
  88. package/pipeline/scripts/plan-coverage-gate.mjs +2 -1
  89. package/pipeline/scripts/pre-commit-check.sh +23 -13
  90. package/pipeline/scripts/prune-logs.sh +1 -1
  91. package/pipeline/scripts/render-agent-log-cost.sh +3 -1
  92. package/pipeline/scripts/render-cost-summary.sh +4 -2
  93. package/pipeline/scripts/render-work-summary.sh +5 -3
  94. package/pipeline/scripts/repo-map.mjs +3 -2
  95. package/pipeline/scripts/scan-skills.sh +6 -2
  96. package/pipeline/scripts/search-logs.sh +8 -6
  97. package/pipeline/scripts/sign-skills.sh +3 -1
  98. package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
  99. package/pipeline/scripts/triage-memory.mjs +25 -4
  100. package/pipeline/scripts/uninstall.mjs +20 -12
  101. package/pipeline/scripts/update-check.sh +2 -2
  102. package/pipeline/scripts/update-issue-progress.sh +6 -5
  103. package/pipeline/scripts/validate-analysis-doc.mjs +6 -6
  104. package/pipeline/scripts/verify-skills.sh +3 -1
  105. package/pipeline/scripts/worktree-finalize.sh +22 -10
  106. package/pipeline/skills/.skill-manifest.json +81 -57
  107. package/pipeline/skills/.skills-index.json +19 -19
  108. package/pipeline/skills/shared/README.md +8 -8
  109. package/pipeline/skills/shared/core/multi-agent/SKILL.md +7 -7
  110. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +1 -1
  111. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -2
  112. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +1 -1
  113. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +2 -2
  114. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +23 -14
  115. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
  116. package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +56 -0
  117. package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +0 -4
  118. package/pipeline/skills/shared/external/api-patterns/SKILL.md +12 -24
  119. package/pipeline/skills/shared/external/app-store-changelog/references/release-notes-guidelines.md +34 -0
  120. package/pipeline/skills/shared/external/app-store-changelog/scripts/collect_release_changes.sh +33 -0
  121. package/pipeline/skills/shared/external/architecture/SKILL.md +7 -9
  122. package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +0 -4
  123. package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +0 -1
  124. package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +0 -14
  125. package/pipeline/skills/shared/external/hig-components-content/SKILL.md +13 -13
  126. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +16 -16
  127. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +6 -6
  128. package/pipeline/skills/shared/external/hig-components-system/SKILL.md +13 -13
  129. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -23
  130. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +18 -18
  131. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +30 -30
  132. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +11 -11
  133. package/pipeline/skills/shared/external/hig-technologies/SKILL.md +33 -33
  134. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +52 -52
  135. package/pipeline/skills/shared/external/ios-coding-standard/references/lint-local.sh +1 -1
  136. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +11 -11
  137. package/pipeline/skills/shared/external/ios-developer/SKILL.md +0 -1
  138. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +7 -3
  139. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -15
  140. package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +0 -5
  141. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Package.swift +17 -0
  142. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/Resources/.keep +0 -0
  143. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/main.swift +11 -0
  144. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/version.env +2 -0
  145. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/build_icon.sh +49 -0
  146. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/compile_and_run.sh +63 -0
  147. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/launch.sh +28 -0
  148. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/make_appcast.sh +82 -0
  149. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/package_app.sh +206 -0
  150. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh +52 -0
  151. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh +52 -0
  152. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/version.env +2 -0
  153. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/packaging.md +17 -0
  154. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/release.md +32 -0
  155. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/scaffold.md +79 -0
  156. package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +0 -1
  157. package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +0 -4
  158. package/pipeline/skills/shared/external/swift-concurrency-expert/references/approachable-concurrency.md +63 -0
  159. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swift-6-2-concurrency.md +272 -0
  160. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md +33 -0
  161. package/pipeline/skills/shared/external/swiftui-performance-audit/references/code-smells.md +150 -0
  162. package/pipeline/skills/shared/external/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
  163. package/pipeline/skills/shared/external/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
  164. package/pipeline/skills/shared/external/swiftui-performance-audit/references/profiling-intake.md +44 -0
  165. package/pipeline/skills/shared/external/swiftui-performance-audit/references/report-template.md +47 -0
  166. package/pipeline/skills/shared/external/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
  167. package/pipeline/skills/shared/external/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
  168. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/app-wiring.md +201 -0
  169. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/async-state.md +96 -0
  170. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +46 -0
  171. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/controls.md +57 -0
  172. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/deeplinks.md +66 -0
  173. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/focus.md +90 -0
  174. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/form.md +97 -0
  175. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/grids.md +71 -0
  176. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +71 -0
  177. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  178. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  179. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/list.md +86 -0
  180. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  181. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/macos-settings.md +71 -0
  182. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  183. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/media.md +73 -0
  184. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/menu-bar.md +101 -0
  185. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/navigationstack.md +159 -0
  186. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/overlay.md +45 -0
  187. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/performance.md +62 -0
  188. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/previews.md +48 -0
  189. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  190. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scrollview.md +87 -0
  191. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/searchable.md +71 -0
  192. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/sheets.md +155 -0
  193. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/split-views.md +72 -0
  194. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/tabview.md +114 -0
  195. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/theming.md +71 -0
  196. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/title-menus.md +93 -0
  197. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/top-bar.md +49 -0
  198. package/pipeline/skills/shared/external/swiftui-view-refactor/references/mv-patterns.md +161 -0
  199. package/pipeline/skills/skills-index.md +8 -8
  200. package/pipeline/skills/shared/external/help-skills/SKILL.md +0 -166
@@ -57,11 +57,22 @@ function parseFrontmatter(raw) {
57
57
  if (end < 0) return {};
58
58
  const body = raw.slice(3, end).trim();
59
59
  const out = {};
60
- for (const line of body.split("\n")) {
61
- const m = line.match(/^([\w-]+):\s*(.*)$/);
60
+ const lines = body.split("\n");
61
+ for (let i = 0; i < lines.length; i++) {
62
+ const m = lines[i].match(/^([\w-]+):\s*(.*)$/);
62
63
  if (!m) continue;
63
64
  let value = m[2].trim();
64
- if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
65
+ if (/^[|>][+-]?$/.test(value)) {
66
+ // YAML block scalar: the text is the indented lines that follow, and the
67
+ // indicator itself is not a description.
68
+ const parts = [];
69
+ while (i + 1 < lines.length && (/^\s/.test(lines[i + 1]) || lines[i + 1].trim() === "")) {
70
+ parts.push(lines[++i].trim());
71
+ }
72
+ value = parts.join(" ").replace(/\s+/g, " ").trim();
73
+ } else if (value.startsWith('"') && value.endsWith('"')) {
74
+ value = value.slice(1, -1);
75
+ }
65
76
  out[m[1]] = value;
66
77
  }
67
78
  return out;
@@ -13,12 +13,14 @@
13
13
  * coding tool; this turns "will this burn my tokens?" into a visible number.
14
14
  *
15
15
  * Inputs (CLI overrides prefs):
16
- * --task-id <id> Locate .worktrees/<id>/phase-tracker.json
16
+ * --task-id <id> Locate $HOME/.claude/logs/multi-agent/<id>/tracker-state.json
17
+ * (also tried: <id> without a leading #, task-<id>)
17
18
  * --tracker <path> Explicit tracker JSON (skips lookup)
18
19
  * --prefs <path> Preferences JSON; reads .global.costBudget
19
20
  * --max-usd <n> Hard ceiling (USD)
20
21
  * --warn-pct <n> Warn threshold as percent of max-usd (1..99)
21
- * --pricing-model <m> opus | sonnet | haiku (rate to price tokens at)
22
+ * --pricing-model <m> fable | opus | sonnet | haiku (any key under cost-table.json
23
+ * prices; default fable, the most expensive tier)
22
24
  * --enabled Force-enable even if prefs say otherwise
23
25
  *
24
26
  * Output: one human-readable status line on stdout.
@@ -62,7 +64,7 @@ for (let i = 0; i < argv.length; i++) {
62
64
  if (flags.help || flags.h) {
63
65
  process.stdout.write(
64
66
  "Usage: cost-budget-check.mjs [--task-id <id> | --tracker <path>] [--prefs <path>]\n" +
65
- " [--max-usd <n>] [--warn-pct <n>] [--pricing-model opus|sonnet|haiku] [--enabled]\n",
67
+ " [--max-usd <n>] [--warn-pct <n>] [--pricing-model fable|opus|sonnet|haiku] [--enabled]\n",
66
68
  );
67
69
  process.exit(0);
68
70
  }
@@ -15,9 +15,6 @@
15
15
  # absent). Returns null when $rate is null.
16
16
  # cost_floor_cents($u)
17
17
  # -> floor USD to whole cents; null passes through.
18
- #
19
- # cost_usd <rate-json> <tin> <tout> [tcached] - scalar convenience wrapper
20
- # that prints the raw (unrounded) USD via jq. Prints nothing when jq is absent.
21
18
 
22
19
  COST_JQ_DEFS='
23
20
  def cost_usd_of($rate; $tin; $tout; $tcached):
@@ -29,15 +26,3 @@ COST_JQ_DEFS='
29
26
  def cost_floor_cents($u):
30
27
  if $u == null then null else (($u * 100) | floor) / 100 end;
31
28
  '
32
-
33
- cost_usd() {
34
- local rate="$1" tin="$2" tout="$3" tcached="${4:-0}"
35
- command -v jq >/dev/null 2>&1 || return 0
36
- jq -n \
37
- --argjson rate "$rate" \
38
- --argjson tin "$tin" \
39
- --argjson tout "$tout" \
40
- --argjson tcached "$tcached" \
41
- "$COST_JQ_DEFS"'
42
- cost_usd_of($rate; $tin; $tout; $tcached)'
43
- }
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Inputs (any one):
13
13
  * --triage <path> Path to triage-output.json
14
- * --task-id <#N|JIRA> Resolve via $HOME/.claude/logs/multi-agent/<project>/<id>/
14
+ * --task-id <#N|JIRA> Resolve $HOME/.claude/logs/multi-agent/<id>/agent-state.json
15
15
  * --state <path> Path to agent-state.json with embedded triage
16
16
  *
17
17
  * Diff source:
@@ -31,6 +31,8 @@
31
31
  */
32
32
 
33
33
  import { readFileSync, existsSync } from "fs";
34
+ import { join } from "path";
35
+ import { homedir } from "os";
34
36
  import { execFileSync } from "child_process";
35
37
 
36
38
  const argv = process.argv.slice(2);
@@ -55,7 +57,7 @@ if (flags.help || flags.h) {
55
57
  Inputs (one required):
56
58
  --triage <path> Path to triage-output.json
57
59
  --state <path> agent-state.json with embedded triage
58
- --task-id <id> Look up by task ID
60
+ --task-id <id> Look up $HOME/.claude/logs/multi-agent/<id>/agent-state.json
59
61
 
60
62
  Diff source:
61
63
  --branch <name> Branch to diff (default: current)
@@ -68,24 +70,32 @@ Output:
68
70
  process.exit(0);
69
71
  }
70
72
 
73
+ function resolveStateByTaskId(id) {
74
+ const bare = String(id).replace(/^#/, "");
75
+ const logRoot = join(homedir(), ".claude", "logs", "multi-agent");
76
+ const candidates = [...new Set([String(id), bare, `task-${bare}`])].map((d) =>
77
+ join(logRoot, d, "agent-state.json"),
78
+ );
79
+ const found = candidates.find((p) => existsSync(p));
80
+ if (!found)
81
+ die(`no agent-state.json for task ${id} under ${logRoot} (tried ${candidates.join(", ")})`);
82
+ return found;
83
+ }
84
+
71
85
  function loadTriage() {
72
86
  if (flags.triage) {
73
87
  if (!existsSync(flags.triage)) die(`triage file not found: ${flags.triage}`);
74
88
  return JSON.parse(readFileSync(flags.triage, "utf-8"));
75
89
  }
76
- if (flags.state) {
77
- if (!existsSync(flags.state)) die(`state file not found: ${flags.state}`);
78
- const state = JSON.parse(readFileSync(flags.state, "utf-8"));
90
+ const statePath =
91
+ flags.state || (flags["task-id"] ? resolveStateByTaskId(flags["task-id"]) : null);
92
+ if (statePath) {
93
+ if (!existsSync(statePath)) die(`state file not found: ${statePath}`);
94
+ const state = JSON.parse(readFileSync(statePath, "utf-8"));
79
95
  const triage = state?.phases?.["4"]?.triage ?? state?.triage;
80
- if (!triage) die(`no triage block found in ${flags.state}`);
96
+ if (!triage) die(`no triage block found in ${statePath}`);
81
97
  return triage;
82
98
  }
83
- if (flags["task-id"]) {
84
- die(
85
- "--task-id resolution not implemented in this script - use --triage or --state directly. " +
86
- "The slash command resolves task IDs via the orchestrator and passes --state to this script.",
87
- );
88
- }
89
99
  die("one of --triage, --state, or --task-id is required");
90
100
  }
91
101
 
@@ -111,8 +111,11 @@ if [ -z "$REFS_DIRS" ]; then
111
111
  fi
112
112
 
113
113
  # Match the node-id shape offload-ref.sh writes (p<phase>-<slug>-<8 hex>.md).
114
- # Anything else in that directory belongs to someone else.
115
- NODE_GLOB='p*-*-*.md'
114
+ # Anything else in that directory belongs to someone else. The glob is a cheap
115
+ # pre-filter for find; the regex is the actual shape (a plain `p*-*-*.md`
116
+ # also matched pr-body-final.md).
117
+ NODE_GLOB='p[0-9]*-*-????????.md'
118
+ NODE_RE='^p[0-9][0-9a-z.]*-[a-z0-9-]+-[0-9a-f]{8}\.md$'
116
119
 
117
120
  MATCHES=""
118
121
  while IFS= read -r dir; do
@@ -128,6 +131,7 @@ while IFS= read -r dir; do
128
131
  [ -n "$found" ] || continue
129
132
  while IFS= read -r f; do
130
133
  [ -n "$f" ] || continue
134
+ printf '%s\n' "$(basename "$f")" | grep -Eq "$NODE_RE" || continue
131
135
  # Grace: skip anything touched inside the window.
132
136
  if [ "$GRACE_MIN" -gt 0 ] && [ -n "$(find "$f" -mmin -"$GRACE_MIN" 2>/dev/null)" ]; then
133
137
  continue
@@ -110,7 +110,7 @@ while IFS= read -r -d '' p; do
110
110
  # Fresh-scratch grace: spare items an in-flight run touched recently
111
111
  # (checks the item and, for dirs, anything inside it).
112
112
  if [ "$GRACE_MIN" -gt 0 ] &&
113
- find "$p" -mmin "-$GRACE_MIN" -print 2>/dev/null | head -1 | grep -q .; then
113
+ [ -n "$(find "$p" -mmin "-$GRACE_MIN" -print -quit 2>/dev/null)" ]; then
114
114
  skipped_active=$((skipped_active + 1))
115
115
  continue
116
116
  fi
@@ -108,7 +108,7 @@ EOF
108
108
  if printf '%s\n' "$registered" | grep -qxF "$dir"; then
109
109
  continue # healthy registered worktree - kill/finish territory
110
110
  fi
111
- if [ "$OLDER_MIN" -gt 0 ] && find "$dir" -type f -mmin "-$OLDER_MIN" 2>/dev/null | head -1 | grep -q .; then
111
+ if [ "$OLDER_MIN" -gt 0 ] && [ -n "$(find "$dir" -type f -mmin "-$OLDER_MIN" -print -quit 2>/dev/null)" ]; then
112
112
  continue # recently touched - spare it
113
113
  fi
114
114
  orphans+=("$dir")
@@ -32,7 +32,7 @@ const args = Object.fromEntries(
32
32
  }),
33
33
  );
34
34
 
35
- const MODE = args.mode ?? "dev";
35
+ const MODE = args.mode;
36
36
 
37
37
  const PHASE_NAMES = [
38
38
  "0:Init",
@@ -74,9 +74,9 @@ const MODES = {
74
74
  },
75
75
  };
76
76
 
77
- const spec = MODES[MODE];
77
+ const spec = MODE ? MODES[MODE] : null;
78
78
  if (!spec) {
79
- console.error(`unknown mode: ${MODE}`);
79
+ console.error(MODE ? `unknown mode: ${MODE}` : "usage: gen-mode-dispatch.mjs --mode=<mode>");
80
80
  console.error(`accepted: ${Object.keys(MODES).join(", ")}`);
81
81
  process.exit(2);
82
82
  }
@@ -44,8 +44,13 @@ echo "SSH key başarıyla oluşturuldu!"
44
44
  # SSH agent'a ekle
45
45
  eval "$(ssh-agent -s)" > /dev/null 2>&1
46
46
 
47
- # macOS Keychain entegrasyonu için SSH config ayarla
47
+ # SSH config ayarla (UseKeychain yalnızca macOS'ta geçerli bir seçenek;
48
+ # Linux'ta ssh bilinmeyen seçenek diye config'i reddeder)
48
49
  SSH_CONFIG="$HOME/.ssh/config"
50
+ USE_KEYCHAIN_LINE=""
51
+ if [ "$(uname -s 2>/dev/null)" = "Darwin" ]; then
52
+ USE_KEYCHAIN_LINE=" UseKeychain yes"
53
+ fi
49
54
  if ! grep -q "github.com" "$SSH_CONFIG" 2>/dev/null; then
50
55
  cat >> "$SSH_CONFIG" <<EOF
51
56
 
@@ -54,7 +59,7 @@ Host github.com
54
59
  User git
55
60
  IdentityFile $KEY_PATH
56
61
  AddKeysToAgent yes
57
- UseKeychain yes
62
+ ${USE_KEYCHAIN_LINE}
58
63
  EOF
59
64
  chmod 600 "$SSH_CONFIG"
60
65
  echo "SSH config güncellendi."
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * Inputs:
14
14
  * --root <path> Repo root. Default: cwd
15
- * --stack <id> ios | android | node | python. Required.
15
+ * --stack <id> ios | android | node | python | go. Required.
16
16
  * --out <path> Graph output. Default: ~/.claude/knowledge/<project>/code-graph.json
17
17
  * --max-nodes N Refuse to write a graph larger than this. Default: 200000.
18
18
  * Callers pass prefs.global.codeGraph.maxNodes. A tree that
@@ -88,7 +88,7 @@ function main() {
88
88
  const { flags } = parseFlags(process.argv.slice(2));
89
89
  const stack = flags.stack;
90
90
  if (!stack || stack === true) {
91
- process.stderr.write("graph-build: --stack <ios|android|node|python> required\n");
91
+ process.stderr.write("graph-build: --stack <ios|android|node|python|go> required\n");
92
92
  process.exit(64);
93
93
  }
94
94
 
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
+ import { pathToFileURL } from "node:url";
3
4
 
4
5
  const SEQUENCES = [
5
6
  ":)",
@@ -77,7 +78,7 @@ async function readStdin() {
77
78
  return Buffer.concat(chunks).toString("utf8");
78
79
  }
79
80
 
80
- const isMain = process.argv[1] && import.meta.url === `file://${process.argv[1]}`;
81
+ const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
81
82
  if (isMain) {
82
83
  const args = process.argv.slice(2);
83
84
  const check = args.includes("--check");
@@ -51,7 +51,8 @@ Behaviour
51
51
  - Windows / unknown platforms: backend unavailable; `doctor` prints remediation.
52
52
  - Never logs the secret value. `set` accepts the value via argv (CLI) or via
53
53
  stdin when the value is exactly `-` (preferred for scripts to avoid shell
54
- history leak).
54
+ history leak). On macOS the write goes through `security -i` with the
55
+ command on stdin, so the value never appears on a child process argv.
55
56
  - get returns the raw value to stdout with no trailing newline manipulation
56
57
  beyond stripping the OS-level final \\n that `security -w` appends.
57
58
 
@@ -166,17 +167,17 @@ def set_(label: str, value: str, account: Optional[str] = None) -> None:
166
167
  pf = _ensure_backend()
167
168
  acct = _account_default(account)
168
169
  if pf == "macos":
169
- rc, _, err = _run(
170
- [
171
- "/usr/bin/security",
172
- "add-generic-password",
173
- "-a", acct,
174
- "-l", label,
175
- "-s", label,
176
- "-w", value,
177
- "-U",
178
- ]
170
+ # `security -i` reads the add command from stdin, so the secret never
171
+ # lands on the security argv (visible to ps). Backslashes and double
172
+ # quotes are escaped for security's interactive tokenizer.
173
+ def quote(text: str) -> str:
174
+ return text.replace("\\", "\\\\").replace('"', '\\"')
175
+
176
+ command = (
177
+ f'add-generic-password -U -a "{quote(acct)}" -l "{quote(label)}" '
178
+ f'-s "{quote(label)}" -w "{quote(value)}"\n'
179
179
  )
180
+ rc, _, err = _run(["/usr/bin/security", "-i"], input_value=command.encode("utf-8"))
180
181
  if rc != 0:
181
182
  sys.stderr.write(err.decode("utf-8", errors="replace"))
182
183
  sys.exit(4)
@@ -141,7 +141,7 @@ const buckets = [...byBucket.keys()].sort().map((start) => {
141
141
  : null,
142
142
  reworkPerTask: round(list.reduce((s, t) => s + t.rework, 0) / n),
143
143
  tokensPerTask: Math.round((totIn + totOut) / n),
144
- cacheRatio: totIn ? round(totCached / totIn) : null,
144
+ cacheRatio: totIn + totCached ? round(totCached / (totIn + totCached)) : null,
145
145
  memoryPrecision: totInjected ? round(totUsed / totInjected) : null,
146
146
  };
147
147
  });
@@ -248,7 +248,7 @@ function migrate(prefs) {
248
248
  changes.push("initialized empty identities array");
249
249
  }
250
250
 
251
- // 4. keychainMapping - expand to 12 services
251
+ // 4. keychainMapping - add every default service the file does not name yet
252
252
  const km = out.global.keychainMapping || {};
253
253
  const kmDefaults = defaultKeychainMapping();
254
254
  let kmChanged = false;
@@ -23,7 +23,9 @@ TASK=""
23
23
  while [ "$#" -gt 0 ]; do
24
24
  case "$1" in
25
25
  --strict) STRICT=true; shift ;;
26
- --task) TASK="$2"; shift 2 ;;
26
+ --task)
27
+ [ "$#" -ge 2 ] || { echo "output-quality-check: --task needs a value" >&2; exit 1; }
28
+ TASK="$2"; shift 2 ;;
27
29
  *) shift ;;
28
30
  esac
29
31
  done
@@ -265,7 +265,7 @@ emit_otel_span() {
265
265
  local event="${1:-unknown}"
266
266
  local phase_id="${2:-?}"
267
267
  local subject="${3:-}"
268
- local extra_attrs="${4:-{\}}"
268
+ local extra_attrs="${4:-"{}"}"
269
269
  local task_id
270
270
  task_id=$(basename "$TRACKER_DIR")
271
271
  local now_ns
@@ -36,6 +36,7 @@
36
36
  import { existsSync, readFileSync } from "fs";
37
37
  import { join } from "path";
38
38
  import { homedir } from "os";
39
+ import { pathToFileURL } from "url";
39
40
 
40
41
  const FIGMA_URL = /figma\.com\/(design|make|file)\//i;
41
42
 
@@ -233,6 +234,6 @@ function main(argv) {
233
234
  return result.ok ? 0 : 1;
234
235
  }
235
236
 
236
- if (import.meta.url === `file://${process.argv[1]}`) {
237
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
237
238
  process.exit(main(process.argv));
238
239
  }
@@ -31,6 +31,7 @@
31
31
 
32
32
  import { readFileSync, existsSync } from "node:fs";
33
33
  import { join, isAbsolute } from "node:path";
34
+ import { pathToFileURL } from "node:url";
34
35
 
35
36
  export function auditTodos(todos) {
36
37
  const gaps = [];
@@ -89,7 +90,7 @@ function readJson(path) {
89
90
  return JSON.parse(readFileSync(path, "utf8"));
90
91
  }
91
92
 
92
- const isMain = process.argv[1] && import.meta.url === `file://${process.argv[1]}`;
93
+ const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
93
94
  if (isMain) {
94
95
  const args = process.argv.slice(2);
95
96
  const flag = (name) => {
@@ -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
  }
@@ -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
@@ -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)