@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
@@ -12,11 +12,11 @@
12
12
  // Pattern source: karpathy/llm-council backend/council.py stage 2, which labels
13
13
  // peer answers "Response A/B/C" and keeps label_to_model out of the prompt.
14
14
  //
15
- // A residual worth naming: with only two reviewers on Claude Code the label set
16
- // is {Source A, Source B} and the triage model is one of them, so it retains a
17
- // 50% prior on which findings are its own. Anonymization removes the signal, not
18
- // the guess - llm-council's four-model council has a stronger version of the
19
- // same property. The value here is that nothing TELLS the judge.
15
+ // A residual worth naming: on both hosts the triage model is one of the three
16
+ // reviewers, so with the label set {Source A, Source B, Source C} it retains a
17
+ // one-in-three prior on which findings are its own. Anonymization removes the
18
+ // signal, not the guess - llm-council's four-model council has the same property
19
+ // at one in four. The value here is that nothing TELLS the judge.
20
20
  //
21
21
  // What this does NOT do: scrub identity out of free text. A reviewer that writes
22
22
  // "as Sonnet would" in `issue`, or whose prose style is recognisable, is not
@@ -36,6 +36,7 @@
36
36
  // Exit codes: 0 ok, 64 usage, 65 input is not JSON.
37
37
 
38
38
  import { readFileSync, writeFileSync } from "node:fs";
39
+ import { pathToFileURL } from "node:url";
39
40
 
40
41
  const IDENTITY_KEYS = [
41
42
  "reviewer",
@@ -136,7 +137,7 @@ export function anonymize(input, { seed } = {}) {
136
137
  return { findings: tagged, map: { seed: seedText, labelToModel, malformed } };
137
138
  }
138
139
 
139
- const isMain = process.argv[1] && import.meta.url === `file://${process.argv[1]}`;
140
+ const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
140
141
  if (isMain) {
141
142
  const args = process.argv.slice(2);
142
143
  const flag = (name) => {
@@ -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
 
@@ -11,13 +11,21 @@
11
11
  // node pipeline/scripts/evidence-gate.mjs --claim build --status passed --evidence build.log
12
12
  // node pipeline/scripts/evidence-gate.mjs --claim test --status passed --evidence test.log \
13
13
  // --success-pattern 'All tests passed' --failure-pattern 'FAILED'
14
+ // node pipeline/scripts/evidence-gate.mjs --claim manual --status passed --evidence manual-test.json
14
15
  //
15
16
  // Flags:
16
- // --claim build | test | review (selects default success/failure markers)
17
+ // --claim build | test | review | manual
18
+ // build/test/review select default success/failure markers;
19
+ // manual is JSON-based (Phase 5 manual-test.json, see below)
17
20
  // --status the claimed status (only "passed" is gated; others pass through)
18
21
  // --evidence <path> the log / artifact that must substantiate a "passed" claim
19
- // --success-pattern override the default success regex for the claim type
20
- // --failure-pattern override the default failure regex for the claim type
22
+ // --success-pattern override the default success regex for the claim type (marker claims only)
23
+ // --failure-pattern override the default failure regex for the claim type (marker claims only)
24
+ //
25
+ // The manual claim reads a JSON document instead of scanning for markers:
26
+ // { "criteria": [ { "spec", "observed", "verdict": "pass|fail|not-tested", "reason"?, ... } ], ... }
27
+ // criteria must be a non-empty array; every item needs spec, observed and a
28
+ // valid verdict; any "fail" is decisive; "not-tested" needs a non-empty reason.
21
29
  //
22
30
  // Exit codes:
23
31
  // 0 - status is not "passed" (nothing to prove), OR evidence substantiates the pass
@@ -47,6 +55,10 @@ const DEFAULT_MARKERS = {
47
55
  },
48
56
  };
49
57
 
58
+ const JSON_CLAIMS = new Set(["manual"]);
59
+ const CLAIMS = [...Object.keys(DEFAULT_MARKERS), ...JSON_CLAIMS];
60
+ const MANUAL_VERDICTS = new Set(["pass", "fail", "not-tested"]);
61
+
50
62
  const MAX_PATTERN_LEN = 200;
51
63
 
52
64
  // Compile a caller-supplied pattern safely: cap length and catch a bad regex so
@@ -101,12 +113,64 @@ function usage(msg) {
101
113
  process.exit(2);
102
114
  }
103
115
 
116
+ function nonEmptyString(v) {
117
+ return typeof v === "string" && v.trim().length > 0;
118
+ }
119
+
120
+ // Manual-test evidence is a structured document, not a log: every acceptance
121
+ // criterion must say what was observed and how it came out. A "fail" anywhere
122
+ // is decisive; an untested criterion is only acceptable when it says why.
123
+ function gateManual(claim, evidence, body) {
124
+ let doc;
125
+ try {
126
+ doc = JSON.parse(body);
127
+ } catch (err) {
128
+ fail(`${claim} evidence is not valid JSON: ${evidence} (${err.message}) (default-FAIL)`);
129
+ }
130
+ const criteria = doc && typeof doc === "object" ? doc.criteria : undefined;
131
+ if (!Array.isArray(criteria) || criteria.length === 0) {
132
+ fail(`${claim} claims passed but evidence has no criteria: ${evidence} (default-FAIL)`);
133
+ }
134
+ criteria.forEach((item, i) => {
135
+ const label = `criteria[${i}]`;
136
+ if (!item || typeof item !== "object") {
137
+ fail(`${claim} evidence ${label} is not an object: ${evidence} (default-FAIL)`);
138
+ }
139
+ if (!nonEmptyString(item.spec)) {
140
+ fail(`${claim} evidence ${label} has no spec: ${evidence} (default-FAIL)`);
141
+ }
142
+ if (!nonEmptyString(item.observed)) {
143
+ fail(`${claim} evidence ${label} has no observed value: ${evidence} (default-FAIL)`);
144
+ }
145
+ if (!MANUAL_VERDICTS.has(item.verdict)) {
146
+ fail(
147
+ `${claim} evidence ${label} verdict must be one of pass|fail|not-tested: ${evidence} (default-FAIL)`,
148
+ );
149
+ }
150
+ });
151
+ const failed = criteria.find((item) => item.verdict === "fail");
152
+ if (failed) {
153
+ fail(
154
+ `${claim} evidence shows a failed criterion "${failed.spec}" - pass claim contradicted by ${evidence}`,
155
+ );
156
+ }
157
+ const untested = criteria.find(
158
+ (item) => item.verdict === "not-tested" && !nonEmptyString(item.reason),
159
+ );
160
+ if (untested) {
161
+ fail(
162
+ `${claim} evidence has not-tested criterion "${untested.spec}" without a reason - pass claim unverified by ${evidence} (default-FAIL)`,
163
+ );
164
+ }
165
+ ok(`${claim} pass claim substantiated by ${criteria.length} criteria in ${evidence}`);
166
+ }
167
+
104
168
  const args = parseArgs(process.argv.slice(2));
105
169
  const claim = args.claim;
106
170
  const status = args.status;
107
171
 
108
- if (!claim || !DEFAULT_MARKERS[claim]) {
109
- usage(`--claim must be one of: ${Object.keys(DEFAULT_MARKERS).join(", ")}`);
172
+ if (!claim || !CLAIMS.includes(claim)) {
173
+ usage(`--claim must be one of: ${CLAIMS.join(", ")}`);
110
174
  }
111
175
  if (typeof status !== "string") {
112
176
  usage("--status is required");
@@ -138,6 +202,10 @@ if (body.trim().length === 0) {
138
202
  fail(`${claim} claims passed but evidence file is blank: ${evidence} (default-FAIL)`);
139
203
  }
140
204
 
205
+ if (JSON_CLAIMS.has(claim)) {
206
+ gateManual(claim, evidence, body);
207
+ }
208
+
141
209
  const successRe = userRegExp(args["success-pattern"], DEFAULT_MARKERS[claim].success, "success");
142
210
  const failureRe = userRegExp(args["failure-pattern"], DEFAULT_MARKERS[claim].failure, "failure");
143
211
 
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * finding-fingerprint.mjs - stamp every review finding with its stable id.
4
+ *
5
+ * Usage:
6
+ * node finding-fingerprint.mjs annotate <path|-> [--kind reviewer|triage|dev-critic|auto] [--in-place]
7
+ * node finding-fingerprint.mjs compute --file <path> --issue <text> [--rule-id <id>]
8
+ *
9
+ * `annotate` prints the annotated JSON (or rewrites the file with --in-place)
10
+ * and reports counts on stderr. `compute` prints {"fingerprint":"F:..."}.
11
+ *
12
+ * Exit codes: 0 ok, 1 unparseable input or unknown shape, 64 usage.
13
+ * Zero dependencies. Library half lives in _fingerprint.mjs.
14
+ */
15
+
16
+ import { readFileSync, writeFileSync } from "node:fs";
17
+
18
+ import { annotate, findingFingerprint, KINDS } from "./_fingerprint.mjs";
19
+
20
+ const USAGE = `usage:
21
+ finding-fingerprint.mjs annotate <path|-> [--kind ${KINDS.join("|")}|auto] [--in-place]
22
+ finding-fingerprint.mjs compute --file <path> --issue <text> [--rule-id <id>]`;
23
+
24
+ function parseArgs(argv) {
25
+ const opts = { _: [] };
26
+ for (let i = 0; i < argv.length; i++) {
27
+ const a = argv[i];
28
+ if (a === "--in-place") opts.inPlace = true;
29
+ else if (a === "--kind") opts.kind = argv[++i];
30
+ else if (a === "--file") opts.file = argv[++i];
31
+ else if (a === "--issue") opts.issue = argv[++i];
32
+ else if (a === "--rule-id") opts.ruleId = argv[++i];
33
+ else if (a.startsWith("--")) return null;
34
+ else opts._.push(a);
35
+ }
36
+ return opts;
37
+ }
38
+
39
+ function readSource(path) {
40
+ return path === "-" ? readFileSync(0, "utf-8") : readFileSync(path, "utf-8");
41
+ }
42
+
43
+ function main() {
44
+ const [command, ...rest] = process.argv.slice(2);
45
+ const opts = parseArgs(rest);
46
+ if (!opts) {
47
+ console.error(USAGE);
48
+ return 64;
49
+ }
50
+
51
+ if (command === "compute") {
52
+ if (!opts.file || !opts.issue) {
53
+ console.error(USAGE);
54
+ return 64;
55
+ }
56
+ const fingerprint = findingFingerprint({
57
+ file: opts.file,
58
+ issue: opts.issue,
59
+ ruleId: opts.ruleId,
60
+ });
61
+ process.stdout.write(JSON.stringify({ fingerprint }) + "\n");
62
+ return 0;
63
+ }
64
+
65
+ if (command !== "annotate" || opts._.length !== 1) {
66
+ console.error(USAGE);
67
+ return 64;
68
+ }
69
+
70
+ const path = opts._[0];
71
+ if (opts.inPlace && path === "-") {
72
+ console.error("error: --in-place needs a file path, not STDIN");
73
+ return 64;
74
+ }
75
+
76
+ let doc;
77
+ try {
78
+ doc = JSON.parse(readSource(path));
79
+ } catch (err) {
80
+ console.error(`error: cannot read ${path}: ${err.message}`);
81
+ return 1;
82
+ }
83
+
84
+ let result;
85
+ try {
86
+ result = annotate(doc, opts.kind ?? "auto");
87
+ } catch (err) {
88
+ console.error(`error: ${err.message}`);
89
+ return 1;
90
+ }
91
+
92
+ const text = JSON.stringify(result.doc, null, 2) + "\n";
93
+ if (opts.inPlace) writeFileSync(path, text);
94
+ else process.stdout.write(text);
95
+ console.error(
96
+ `finding-fingerprint: ${result.kind}, ${result.computed} computed, ${result.kept} kept`,
97
+ );
98
+ return 0;
99
+ }
100
+
101
+ process.exit(main());
@@ -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) => {