@soleur/opencode 0.0.0-stage → 1.0.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 (812) hide show
  1. package/.claude-plugin/agents.manifest.json +476 -0
  2. package/.claude-plugin/plugin.json +42 -0
  3. package/README.md +44 -2
  4. package/agents/community/.gitkeep +0 -0
  5. package/agents/engineering/cto.md +87 -0
  6. package/agents/engineering/design/ddd-architect.md +95 -0
  7. package/agents/engineering/discovery/agent-finder.md +214 -0
  8. package/agents/engineering/discovery/functional-discovery.md +182 -0
  9. package/agents/engineering/infra/infra-security.md +151 -0
  10. package/agents/engineering/infra/platform-strategist.md +109 -0
  11. package/agents/engineering/infra/terraform-architect.md +171 -0
  12. package/agents/engineering/prompt-engineer.md +37 -0
  13. package/agents/engineering/research/best-practices-researcher.md +115 -0
  14. package/agents/engineering/research/framework-docs-researcher.md +93 -0
  15. package/agents/engineering/research/git-history-analyzer.md +53 -0
  16. package/agents/engineering/research/learnings-researcher.md +285 -0
  17. package/agents/engineering/research/repo-research-analyst.md +118 -0
  18. package/agents/engineering/review/agent-native-reviewer.md +279 -0
  19. package/agents/engineering/review/architecture-strategist.md +61 -0
  20. package/agents/engineering/review/code-quality-analyst.md +92 -0
  21. package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
  22. package/agents/engineering/review/data-integrity-guardian.md +82 -0
  23. package/agents/engineering/review/data-migration-expert.md +98 -0
  24. package/agents/engineering/review/deployment-verification-agent.md +183 -0
  25. package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
  26. package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
  27. package/agents/engineering/review/legacy-code-expert.md +55 -0
  28. package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
  29. package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
  30. package/agents/engineering/review/performance-oracle.md +130 -0
  31. package/agents/engineering/review/security-sentinel.md +167 -0
  32. package/agents/engineering/review/semgrep-sast.md +36 -0
  33. package/agents/engineering/review/test-design-reviewer.md +150 -0
  34. package/agents/engineering/review/user-impact-reviewer.md +70 -0
  35. package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
  36. package/agents/finance/budget-analyst.md +29 -0
  37. package/agents/finance/cfo.md +44 -0
  38. package/agents/finance/financial-reporter.md +30 -0
  39. package/agents/finance/revenue-analyst.md +30 -0
  40. package/agents/legal/clo.md +115 -0
  41. package/agents/legal/legal-compliance-auditor.md +110 -0
  42. package/agents/legal/legal-document-generator.md +134 -0
  43. package/agents/marketing/analytics-analyst.md +25 -0
  44. package/agents/marketing/brand-architect.md +181 -0
  45. package/agents/marketing/cmo.md +76 -0
  46. package/agents/marketing/conversion-optimizer.md +19 -0
  47. package/agents/marketing/copywriter.md +20 -0
  48. package/agents/marketing/fact-checker.md +70 -0
  49. package/agents/marketing/growth-strategist.md +145 -0
  50. package/agents/marketing/paid-media-strategist.md +28 -0
  51. package/agents/marketing/pricing-strategist.md +17 -0
  52. package/agents/marketing/programmatic-seo-specialist.md +25 -0
  53. package/agents/marketing/retention-strategist.md +23 -0
  54. package/agents/marketing/seo-aeo-analyst.md +164 -0
  55. package/agents/operations/coo.md +50 -0
  56. package/agents/operations/ops-advisor.md +78 -0
  57. package/agents/operations/ops-provisioner.md +101 -0
  58. package/agents/operations/ops-research.md +52 -0
  59. package/agents/operations/service-automator.md +278 -0
  60. package/agents/product/business-validator.md +206 -0
  61. package/agents/product/competitive-intelligence.md +89 -0
  62. package/agents/product/cpo.md +63 -0
  63. package/agents/product/design/ux-design-lead.md +209 -0
  64. package/agents/product/spec-flow-analyzer.md +122 -0
  65. package/agents/sales/cro.md +44 -0
  66. package/agents/sales/deal-architect.md +29 -0
  67. package/agents/sales/outbound-strategist.md +29 -0
  68. package/agents/sales/pipeline-analyst.md +29 -0
  69. package/agents/support/cco.md +48 -0
  70. package/agents/support/community-manager.md +570 -0
  71. package/agents/support/ticket-triage.md +70 -0
  72. package/bin/soleur-opencode.js +29 -0
  73. package/commands/go.md +659 -0
  74. package/commands/help.md +273 -0
  75. package/commands/sync.md +977 -0
  76. package/commands/version.md +103 -0
  77. package/hooks/antigravity-hooks.json +84 -0
  78. package/hooks/antigravity-session-start.sh +15 -0
  79. package/hooks/bare-root-cwd-guard.sh +131 -0
  80. package/hooks/browser-snapshot-credential-guard.sh +200 -0
  81. package/hooks/codex-session-start.sh +9 -0
  82. package/hooks/compaction-state.sh +525 -0
  83. package/hooks/destructive-command-guard.sh +1387 -0
  84. package/hooks/devin-session-start.sh +90 -0
  85. package/hooks/ext-filter-detect.sh +171 -0
  86. package/hooks/hooks.json +133 -0
  87. package/hooks/lib/hook-tool-kind.sh +24 -0
  88. package/hooks/lib/log-rotation.sh +176 -0
  89. package/hooks/lib/shell-argv.pl +576 -0
  90. package/hooks/memory-backstop.sh +1114 -0
  91. package/hooks/operator-stage-approval.sh +391 -0
  92. package/hooks/stop-hook.sh +367 -0
  93. package/hooks/unkept-promise-hook.sh +383 -0
  94. package/hooks/version-banner.sh +127 -0
  95. package/hooks/welcome-hook.sh +50 -0
  96. package/install.js +108 -0
  97. package/lib/agent-registry.ts +154 -0
  98. package/lib/c4-canonical-cli.mjs +34 -0
  99. package/lib/c4-canonical.mjs +46 -0
  100. package/lib/c4-from-components.ts +554 -0
  101. package/lib/go-routing.ts +81 -0
  102. package/lib/grok-inspect-contract.ts +311 -0
  103. package/lib/harness-model-map.ts +101 -0
  104. package/lib/harness-parity.ts +726 -0
  105. package/lib/harness-tool-map.ts +180 -0
  106. package/lib/harness.ts +716 -0
  107. package/lib/heartbeat-live-reconcile.ts +1153 -0
  108. package/lib/heartbeat-manifest.ts +342 -0
  109. package/lib/kb-coverage.ts +252 -0
  110. package/lib/pr-merge-poll.ts +103 -0
  111. package/lib/workflow-fidelity.ts +388 -0
  112. package/opencode/INSTRUCTIONS.md +152 -0
  113. package/opencode/agents/engineering/cto.md +10 -0
  114. package/opencode/agents/engineering/design/ddd-architect.md +10 -0
  115. package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
  116. package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
  117. package/opencode/agents/engineering/infra/infra-security.md +10 -0
  118. package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
  119. package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
  120. package/opencode/agents/engineering/prompt-engineer.md +10 -0
  121. package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
  122. package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
  123. package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
  124. package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
  125. package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
  126. package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
  127. package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
  128. package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
  129. package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
  130. package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
  131. package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
  132. package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
  133. package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
  134. package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
  135. package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
  136. package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
  137. package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
  138. package/opencode/agents/engineering/review/performance-oracle.md +10 -0
  139. package/opencode/agents/engineering/review/security-sentinel.md +10 -0
  140. package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
  141. package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
  142. package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
  143. package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
  144. package/opencode/agents/finance/budget-analyst.md +10 -0
  145. package/opencode/agents/finance/cfo.md +10 -0
  146. package/opencode/agents/finance/financial-reporter.md +10 -0
  147. package/opencode/agents/finance/revenue-analyst.md +10 -0
  148. package/opencode/agents/legal/clo.md +10 -0
  149. package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
  150. package/opencode/agents/legal/legal-document-generator.md +10 -0
  151. package/opencode/agents/marketing/analytics-analyst.md +10 -0
  152. package/opencode/agents/marketing/brand-architect.md +10 -0
  153. package/opencode/agents/marketing/cmo.md +10 -0
  154. package/opencode/agents/marketing/conversion-optimizer.md +10 -0
  155. package/opencode/agents/marketing/copywriter.md +10 -0
  156. package/opencode/agents/marketing/fact-checker.md +10 -0
  157. package/opencode/agents/marketing/growth-strategist.md +10 -0
  158. package/opencode/agents/marketing/paid-media-strategist.md +10 -0
  159. package/opencode/agents/marketing/pricing-strategist.md +10 -0
  160. package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
  161. package/opencode/agents/marketing/retention-strategist.md +10 -0
  162. package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
  163. package/opencode/agents/operations/coo.md +10 -0
  164. package/opencode/agents/operations/ops-advisor.md +10 -0
  165. package/opencode/agents/operations/ops-provisioner.md +10 -0
  166. package/opencode/agents/operations/ops-research.md +10 -0
  167. package/opencode/agents/operations/service-automator.md +10 -0
  168. package/opencode/agents/product/business-validator.md +10 -0
  169. package/opencode/agents/product/competitive-intelligence.md +10 -0
  170. package/opencode/agents/product/cpo.md +10 -0
  171. package/opencode/agents/product/design/ux-design-lead.md +10 -0
  172. package/opencode/agents/product/spec-flow-analyzer.md +10 -0
  173. package/opencode/agents/sales/cro.md +10 -0
  174. package/opencode/agents/sales/deal-architect.md +10 -0
  175. package/opencode/agents/sales/outbound-strategist.md +10 -0
  176. package/opencode/agents/sales/pipeline-analyst.md +10 -0
  177. package/opencode/agents/support/cco.md +10 -0
  178. package/opencode/agents/support/community-manager.md +10 -0
  179. package/opencode/agents/support/ticket-triage.md +10 -0
  180. package/opencode/commands/go.md +10 -0
  181. package/opencode/commands/help.md +10 -0
  182. package/opencode/commands/sync.md +10 -0
  183. package/opencode/commands/version.md +11 -0
  184. package/opencode/opencode.json +8 -0
  185. package/opencode/plugin.ts +493 -0
  186. package/package.json +36 -4
  187. package/scripts/admin-merge-ready.sh +545 -0
  188. package/scripts/admin-merge-ready.test.sh +753 -0
  189. package/scripts/affected-tests.sh +908 -0
  190. package/scripts/affected-tests.test.sh +1797 -0
  191. package/scripts/alpha-metrics.sh +78 -0
  192. package/scripts/alpha-metrics.test.sh +115 -0
  193. package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
  194. package/scripts/audit-flag-flip.sh +107 -0
  195. package/scripts/check-red-on-main.sh +316 -0
  196. package/scripts/ci-head-verdict.sh +363 -0
  197. package/scripts/cloud-detect.sh +193 -0
  198. package/scripts/delegation-verify.sh +657 -0
  199. package/scripts/delegation-verify.test.sh +1424 -0
  200. package/scripts/deploy-arm.sh +475 -0
  201. package/scripts/domain-model-drift.sh +419 -0
  202. package/scripts/emit-decision.sh +144 -0
  203. package/scripts/emit-decision.test.sh +144 -0
  204. package/scripts/ext-filter-divergence-corpus.txt +15 -0
  205. package/scripts/ext-filter-divergence.sh +189 -0
  206. package/scripts/generate-c4-from-components.ts +442 -0
  207. package/scripts/grok-fidelity-gate.sh +68 -0
  208. package/scripts/grok-pre-push-gate.sh +232 -0
  209. package/scripts/harness-discovery-smoke.ts +906 -0
  210. package/scripts/harness-parity-census.ts +92 -0
  211. package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
  212. package/scripts/install-opencode-adapter.ts +520 -0
  213. package/scripts/lib/affected-tests/common.sh +365 -0
  214. package/scripts/lib/affected-tests/go.sh +157 -0
  215. package/scripts/lib/affected-tests/js.sh +188 -0
  216. package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
  217. package/scripts/lib/affected-tests/python.sh +185 -0
  218. package/scripts/lib/context-sanitize.sh +49 -0
  219. package/scripts/lib/domain-model-lib.sh +180 -0
  220. package/scripts/lib/ext-filter-grammar.tsv +34 -0
  221. package/scripts/lib/operator-script.sh +1236 -0
  222. package/scripts/lib/proc.sh +580 -0
  223. package/scripts/lib/session-state.sh +748 -0
  224. package/scripts/lib/tmp-classify.sh +843 -0
  225. package/scripts/lib/wrapper-boundary.sh +47 -0
  226. package/scripts/lint-ratchet.py +1547 -0
  227. package/scripts/monitor-pr-checks.sh +441 -0
  228. package/scripts/pipeline-tally.sh +756 -0
  229. package/scripts/pipeline-tally.test.sh +740 -0
  230. package/scripts/plugin-version.sh +528 -0
  231. package/scripts/precommit-guard.sh +113 -0
  232. package/scripts/precommit-guard.test.sh +254 -0
  233. package/scripts/reconcile-live-heartbeats.ts +705 -0
  234. package/scripts/render-c4-model.sh +217 -0
  235. package/scripts/resolve-git-root.sh +55 -0
  236. package/scripts/resolve-regenerable-conflicts.sh +363 -0
  237. package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
  238. package/scripts/ruff-requirements.txt +22 -0
  239. package/scripts/run-in-pid-namespace.sh +98 -0
  240. package/scripts/run-in-pid-namespace.test.sh +290 -0
  241. package/scripts/scan-ancestor-signal-helpers.py +556 -0
  242. package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
  243. package/scripts/setup-ralph-loop.sh +238 -0
  244. package/scripts/sync-cursor-name-map.ts +316 -0
  245. package/scripts/sync-grok-agent-compat.ts +188 -0
  246. package/scripts/sync-npm-package.ts +178 -0
  247. package/scripts/sync-opencode-adapter.ts +479 -0
  248. package/scripts/sync-pr-behind.sh +534 -0
  249. package/scripts/sync-pr-behind.test.sh +252 -0
  250. package/scripts/taste-profile-update.sh +180 -0
  251. package/scripts/taste-profile-update.test.sh +167 -0
  252. package/scripts/write-budget-marker.sh +23 -0
  253. package/scripts/write-kb-coverage.ts +113 -0
  254. package/skills/admin-ip-refresh/SKILL.md +81 -0
  255. package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
  256. package/skills/agent-browser/SKILL.md +698 -0
  257. package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
  258. package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
  259. package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
  260. package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
  261. package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
  262. package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
  263. package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
  264. package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
  265. package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
  266. package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
  267. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
  268. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
  269. package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
  270. package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
  271. package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
  272. package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
  273. package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
  274. package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
  275. package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
  276. package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
  277. package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
  278. package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
  279. package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
  280. package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
  281. package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
  282. package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
  283. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
  284. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
  285. package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
  286. package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
  287. package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
  288. package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
  289. package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
  290. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
  291. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
  292. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
  293. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
  294. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
  295. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
  296. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
  297. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
  298. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
  299. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
  300. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
  301. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
  302. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
  303. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
  304. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
  305. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
  306. package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
  307. package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
  308. package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
  309. package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
  310. package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
  311. package/skills/agent-native-architecture/SKILL.md +479 -0
  312. package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
  313. package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
  314. package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
  315. package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
  316. package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
  317. package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
  318. package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
  319. package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
  320. package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
  321. package/skills/agent-native-architecture/references/product-implications.md +478 -0
  322. package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
  323. package/skills/agent-native-architecture/references/self-modification.md +294 -0
  324. package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
  325. package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
  326. package/skills/agent-native-audit/SKILL.md +300 -0
  327. package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
  328. package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
  329. package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
  330. package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
  331. package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
  332. package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
  333. package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
  334. package/skills/architecture/SKILL.md +436 -0
  335. package/skills/architecture/references/adr-template.md +154 -0
  336. package/skills/architecture/references/likec4-reference.md +136 -0
  337. package/skills/architecture/references/nfr-reference.md +126 -0
  338. package/skills/archive-kb/SKILL.md +86 -0
  339. package/skills/archive-kb/scripts/archive-kb.sh +300 -0
  340. package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
  341. package/skills/atdd-developer/SKILL.md +65 -0
  342. package/skills/brainstorm/SKILL.md +701 -0
  343. package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
  344. package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
  345. package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
  346. package/skills/brainstorm/references/ui-surface-terms.md +42 -0
  347. package/skills/brainstorm-techniques/SKILL.md +231 -0
  348. package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
  349. package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
  350. package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
  351. package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
  352. package/skills/campaign-calendar/SKILL.md +145 -0
  353. package/skills/cf-token-scope/SKILL.md +164 -0
  354. package/skills/cf-token-scope/references/widen-playbook.md +114 -0
  355. package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
  356. package/skills/changelog/SKILL.md +142 -0
  357. package/skills/code-to-prd/SKILL.md +66 -0
  358. package/skills/code-to-prd/references/banner-template.md +12 -0
  359. package/skills/code-to-prd/references/prd-template.md +41 -0
  360. package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
  361. package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
  362. package/skills/code-to-prd/test/fixture/.env.example +7 -0
  363. package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
  364. package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
  365. package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
  366. package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
  367. package/skills/code-to-prd/test/fixture/package.json +6 -0
  368. package/skills/cohort-status/SKILL.md +62 -0
  369. package/skills/community/SKILL.md +210 -0
  370. package/skills/community/scripts/bsky-community.sh +644 -0
  371. package/skills/community/scripts/bsky-setup.sh +631 -0
  372. package/skills/community/scripts/community-router.sh +93 -0
  373. package/skills/community/scripts/discord-community.sh +487 -0
  374. package/skills/community/scripts/discord-setup.sh +784 -0
  375. package/skills/community/scripts/github-community.sh +706 -0
  376. package/skills/community/scripts/hn-community.sh +233 -0
  377. package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
  378. package/skills/community/scripts/linkedin-community.sh +750 -0
  379. package/skills/community/scripts/linkedin-setup.sh +758 -0
  380. package/skills/community/scripts/x-community.sh +890 -0
  381. package/skills/community/scripts/x-setup.sh +713 -0
  382. package/skills/community/test/community-argv.test.sh +2922 -0
  383. package/skills/community/test/github-community.test.sh +981 -0
  384. package/skills/competitive-analysis/SKILL.md +69 -0
  385. package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
  386. package/skills/compound/SKILL.md +673 -0
  387. package/skills/compound/scripts/token-efficiency-report.sh +334 -0
  388. package/skills/compound/test/phase-16.test.sh +379 -0
  389. package/skills/compound-capture/SKILL.md +791 -0
  390. package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
  391. package/skills/compound-capture/assets/resolution-template.md +116 -0
  392. package/skills/compound-capture/references/yaml-schema.md +67 -0
  393. package/skills/compound-capture/schema.yaml +181 -0
  394. package/skills/constraint-scaffold/SKILL.md +175 -0
  395. package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
  396. package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
  397. package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
  398. package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
  399. package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
  400. package/skills/constraint-scaffold/references/shared-runner.template +135 -0
  401. package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
  402. package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
  403. package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
  404. package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
  405. package/skills/constraint-scaffold/test/generator.test.sh +93 -0
  406. package/skills/constraint-scaffold/test/parity.test.sh +285 -0
  407. package/skills/content-writer/SKILL.md +242 -0
  408. package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
  409. package/skills/cron-delete/SKILL.md +56 -0
  410. package/skills/cron-list/SKILL.md +54 -0
  411. package/skills/deepen-plan/SKILL.md +937 -0
  412. package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
  413. package/skills/deploy/SKILL.md +115 -0
  414. package/skills/deploy/references/hetzner-setup.md +65 -0
  415. package/skills/deploy/scripts/deploy.sh +37 -0
  416. package/skills/deploy-docs/SKILL.md +95 -0
  417. package/skills/dhh-rails-style/SKILL.md +203 -0
  418. package/skills/dhh-rails-style/references/architecture.md +717 -0
  419. package/skills/dhh-rails-style/references/controllers.md +323 -0
  420. package/skills/dhh-rails-style/references/frontend.md +556 -0
  421. package/skills/dhh-rails-style/references/gems.md +316 -0
  422. package/skills/dhh-rails-style/references/models.md +388 -0
  423. package/skills/dhh-rails-style/references/testing.md +354 -0
  424. package/skills/discord-content/SKILL.md +126 -0
  425. package/skills/docs-site/SKILL.md +214 -0
  426. package/skills/drain-labeled-backlog/SKILL.md +203 -0
  427. package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
  428. package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
  429. package/skills/drain-prs/SKILL.md +142 -0
  430. package/skills/drain-prs/scripts/triage-prs.sh +192 -0
  431. package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
  432. package/skills/dspy-ruby/SKILL.md +634 -0
  433. package/skills/dspy-ruby/assets/config-template.rb +359 -0
  434. package/skills/dspy-ruby/assets/module-template.rb +326 -0
  435. package/skills/dspy-ruby/assets/signature-template.rb +143 -0
  436. package/skills/dspy-ruby/references/core-concepts.md +268 -0
  437. package/skills/dspy-ruby/references/optimization.md +623 -0
  438. package/skills/dspy-ruby/references/providers.md +335 -0
  439. package/skills/eval-harness/README.md +194 -0
  440. package/skills/eval-harness/SKILL.md +157 -0
  441. package/skills/eval-harness/enums/go-routes.json +13 -0
  442. package/skills/eval-harness/enums/incident-threshold.json +5 -0
  443. package/skills/eval-harness/enums/lane.json +5 -0
  444. package/skills/eval-harness/enums/tool-selection.json +20 -0
  445. package/skills/eval-harness/enums/triage-levels.json +5 -0
  446. package/skills/eval-harness/gated-skills.json +34 -0
  447. package/skills/eval-harness/models.generated.json +5 -0
  448. package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
  449. package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
  450. package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
  451. package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
  452. package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
  453. package/skills/eval-harness/prompts/go-baseline.txt +6 -0
  454. package/skills/eval-harness/prompts/go-skill.txt +21 -0
  455. package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
  456. package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
  457. package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
  458. package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
  459. package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
  460. package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
  461. package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
  462. package/skills/eval-harness/prompts/triage-skill.txt +10 -0
  463. package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
  464. package/skills/eval-harness/scripts/extract-block.cjs +87 -0
  465. package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
  466. package/skills/eval-harness/scripts/gen-models.sh +41 -0
  467. package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
  468. package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
  469. package/skills/eval-harness/scripts/parse-label.cjs +93 -0
  470. package/skills/eval-harness/scripts/verdict.cjs +132 -0
  471. package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
  472. package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
  473. package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
  474. package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
  475. package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
  476. package/skills/eval-harness/test/eval-gate.test.sh +132 -0
  477. package/skills/eval-harness/test/extract-block.test.sh +87 -0
  478. package/skills/eval-harness/test/gate-classification.test.sh +77 -0
  479. package/skills/eval-harness/test/gen-models.test.sh +75 -0
  480. package/skills/eval-harness/test/measure-classification.test.sh +74 -0
  481. package/skills/eval-harness/test/parse-label.test.sh +129 -0
  482. package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
  483. package/skills/eval-harness/test/verdict.test.sh +123 -0
  484. package/skills/every-style-editor/SKILL.md +142 -0
  485. package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
  486. package/skills/feature-tweet/SKILL.md +199 -0
  487. package/skills/feature-video/SKILL.md +412 -0
  488. package/skills/feature-video/scripts/check_deps.sh +281 -0
  489. package/skills/file-todos/SKILL.md +267 -0
  490. package/skills/file-todos/assets/todo-template.md +166 -0
  491. package/skills/fix-issue/SKILL.md +278 -0
  492. package/skills/fix-issue/references/agent-authored-exclusion.md +118 -0
  493. package/skills/fix-issue/references/exclude-label-jq-snippet.md +96 -0
  494. package/skills/flag-bootstrap/SETUP.md +142 -0
  495. package/skills/flag-create/SKILL.md +145 -0
  496. package/skills/flag-create/scripts/create.sh +273 -0
  497. package/skills/flag-delete/SKILL.md +167 -0
  498. package/skills/flag-delete/scripts/delete.sh +288 -0
  499. package/skills/flag-list/SKILL.md +99 -0
  500. package/skills/flag-list/scripts/list.sh +275 -0
  501. package/skills/flag-set-role/SKILL.md +229 -0
  502. package/skills/flag-set-role/scripts/flip.sh +774 -0
  503. package/skills/frontend-anti-slop/SKILL.md +106 -0
  504. package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
  505. package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
  506. package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
  507. package/skills/frontend-design/SKILL.md +77 -0
  508. package/skills/gdpr-gate/NOTICE +136 -0
  509. package/skills/gdpr-gate/SKILL.md +341 -0
  510. package/skills/gdpr-gate/references/fields.md +160 -0
  511. package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
  512. package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
  513. package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
  514. package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
  515. package/skills/gdpr-gate/references/layers/frontend.md +249 -0
  516. package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
  517. package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
  518. package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
  519. package/skills/gdpr-gate/references/legal-consent.md +140 -0
  520. package/skills/gdpr-gate/references/non-negotiables.md +60 -0
  521. package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
  522. package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
  523. package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
  524. package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
  525. package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
  526. package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
  527. package/skills/gemini-imagegen/SKILL.md +283 -0
  528. package/skills/gemini-imagegen/requirements.txt +5 -0
  529. package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
  530. package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
  531. package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
  532. package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
  533. package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
  534. package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
  535. package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
  536. package/skills/git-worktree/SKILL.md +534 -0
  537. package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
  538. package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
  539. package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
  540. package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
  541. package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
  542. package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
  543. package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
  544. package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
  545. package/skills/go/SKILL.md +54 -0
  546. package/skills/growth/SKILL.md +164 -0
  547. package/skills/harvest-debt/SKILL.md +78 -0
  548. package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
  549. package/skills/heal-skill/SKILL.md +173 -0
  550. package/skills/help/SKILL.md +46 -0
  551. package/skills/incident/NOTICE +39 -0
  552. package/skills/incident/SKILL.md +395 -0
  553. package/skills/incident/scripts/dry-run.sh +448 -0
  554. package/skills/incident/scripts/redact-engine.py +412 -0
  555. package/skills/incident/scripts/redact-sentinel.sh +44 -0
  556. package/skills/incident/templates/pir.md +139 -0
  557. package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
  558. package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
  559. package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
  560. package/skills/incident/test/fixtures/positive-corpus.md +87 -0
  561. package/skills/incident/test/redact-sentinel.test.sh +1420 -0
  562. package/skills/invoice/SKILL.md +206 -0
  563. package/skills/kb-glossary/SKILL.md +71 -0
  564. package/skills/kb-glossary/references/glossary-format.md +111 -0
  565. package/skills/kb-glossary/references/rejected-request-register.md +115 -0
  566. package/skills/kb-search/SKILL.md +276 -0
  567. package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
  568. package/skills/legal-audit/SKILL.md +161 -0
  569. package/skills/legal-generate/NOTICE +242 -0
  570. package/skills/legal-generate/SKILL.md +244 -0
  571. package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
  572. package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
  573. package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
  574. package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
  575. package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
  576. package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
  577. package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
  578. package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
  579. package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
  580. package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
  581. package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
  582. package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
  583. package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
  584. package/skills/linear-fetch/SKILL.md +247 -0
  585. package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
  586. package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
  587. package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
  588. package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
  589. package/skills/linear-fetch/test/parity.test.sh +112 -0
  590. package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
  591. package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
  592. package/skills/merge-pr/SKILL.md +668 -0
  593. package/skills/model-launch-review/SKILL.md +255 -0
  594. package/skills/model-launch-review/scripts/audit-models.sh +501 -0
  595. package/skills/one-shot/SKILL.md +316 -0
  596. package/skills/one-shot/references/token-discipline.md +74 -0
  597. package/skills/operator-bootstrap/SKILL.md +412 -0
  598. package/skills/operator-bootstrap/template.sh +439 -0
  599. package/skills/operator-digest/SKILL.md +287 -0
  600. package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
  601. package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
  602. package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
  603. package/skills/operator-rephrase/SKILL.md +98 -0
  604. package/skills/pencil-setup/SKILL.md +201 -0
  605. package/skills/pencil-setup/scripts/check_deps.sh +556 -0
  606. package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
  607. package/skills/pencil-setup/scripts/package-lock.json +1172 -0
  608. package/skills/pencil-setup/scripts/package.json +14 -0
  609. package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
  610. package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
  611. package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
  612. package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
  613. package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
  614. package/skills/plan/SKILL.md +1140 -0
  615. package/skills/plan/references/differential-migration-testing.md +185 -0
  616. package/skills/plan/references/plan-community-discovery.md +42 -0
  617. package/skills/plan/references/plan-founder-check.md +137 -0
  618. package/skills/plan/references/plan-functional-overlap.md +25 -0
  619. package/skills/plan/references/plan-host-fanout.md +51 -0
  620. package/skills/plan/references/plan-issue-templates.md +779 -0
  621. package/skills/plan/references/plan-network-outage-checklist.md +131 -0
  622. package/skills/plan/references/plan-scope-check.md +126 -0
  623. package/skills/plan/references/plan-sharp-edges.md +325 -0
  624. package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
  625. package/skills/plan/references/webapp-cwv-observability.md +143 -0
  626. package/skills/plan-review/SKILL.md +91 -0
  627. package/skills/plan-review/lib/named-panel.mjs +58 -0
  628. package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
  629. package/skills/postmerge/SKILL.md +642 -0
  630. package/skills/postmerge/references/deploy-status-debugging.md +138 -0
  631. package/skills/postmerge/references/graceful-degradation.md +20 -0
  632. package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
  633. package/skills/preflight/SKILL.md +1553 -0
  634. package/skills/preflight/references/check-13-founder-check.md +286 -0
  635. package/skills/preflight/scripts/founder-check.py +1640 -0
  636. package/skills/preflight/scripts/parse-form-a.awk +107 -0
  637. package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
  638. package/skills/product-roadmap/SKILL.md +357 -0
  639. package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
  640. package/skills/provision-cloudflare/SKILL.md +67 -0
  641. package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
  642. package/skills/provision-doppler/SKILL.md +58 -0
  643. package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
  644. package/skills/provision-github/SKILL.md +59 -0
  645. package/skills/provision-github/scripts/provision-github.sh +300 -0
  646. package/skills/provision-hetzner/SKILL.md +76 -0
  647. package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
  648. package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
  649. package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
  650. package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
  651. package/skills/qa/SKILL.md +263 -0
  652. package/skills/qa/references/browser-preflight.md +22 -0
  653. package/skills/questionnaire-generate/SKILL.md +328 -0
  654. package/skills/questionnaire-generate/references/questionnaire.template +60 -0
  655. package/skills/rclone/SKILL.md +162 -0
  656. package/skills/rclone/scripts/check_setup.sh +63 -0
  657. package/skills/release-announce/SKILL.md +65 -0
  658. package/skills/release-docs/SKILL.md +92 -0
  659. package/skills/reproduce-bug/SKILL.md +358 -0
  660. package/skills/resolve-debt/SKILL.md +85 -0
  661. package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
  662. package/skills/resolve-parallel/SKILL.md +45 -0
  663. package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
  664. package/skills/resolve-pr-parallel/SKILL.md +68 -0
  665. package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
  666. package/skills/resolve-todo-parallel/SKILL.md +57 -0
  667. package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
  668. package/skills/review/SKILL.md +1621 -0
  669. package/skills/review/references/anti-slop-scanner-hook.md +40 -0
  670. package/skills/review/references/defect-classes-migrations.md +9 -0
  671. package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
  672. package/skills/review/references/review-e2e-testing.md +116 -0
  673. package/skills/review/references/review-todo-structure.md +165 -0
  674. package/skills/review/references/risk-tier-and-fix-rounds.md +95 -0
  675. package/skills/review/references/semgrep-custom-rules.yaml +174 -0
  676. package/skills/review/references/ultrathink-phases.md +98 -0
  677. package/skills/review/references/wfs.md +6 -0
  678. package/skills/review/scripts/emit-review-trailer.sh +499 -0
  679. package/skills/review/scripts/ensure-semgrep.sh +99 -0
  680. package/skills/review/scripts/fix-round-seats.sh +197 -0
  681. package/skills/review/test/emit-review-trailer.test.sh +387 -0
  682. package/skills/review/test/ensure-semgrep.test.sh +111 -0
  683. package/skills/review/test/fix-round-seats.test.sh +237 -0
  684. package/skills/review/workflows/README.md +147 -0
  685. package/skills/review/workflows/review.workflow.js +667 -0
  686. package/skills/schedule/SKILL.md +796 -0
  687. package/skills/seo-aeo/SKILL.md +139 -0
  688. package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
  689. package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
  690. package/skills/ship/SKILL.md +3022 -0
  691. package/skills/ship/references/adr-ordinal-collision.md +47 -0
  692. package/skills/ship/references/affected-gate-contract.md +115 -0
  693. package/skills/ship/references/ci-run-diagnosis.md +29 -0
  694. package/skills/ship/references/ci-workflow-authoring.md +68 -0
  695. package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
  696. package/skills/ship/references/cto-ruling-armed-push.md +5 -0
  697. package/skills/ship/references/decision-challenges-render.md +71 -0
  698. package/skills/ship/references/detached-battery-verdict.md +13 -0
  699. package/skills/ship/references/feature-tweet-draft.md +42 -0
  700. package/skills/ship/references/followthrough-issue-body-template.md +47 -0
  701. package/skills/ship/references/followthrough-stub-template.sh +92 -0
  702. package/skills/ship/references/merge-queue-dequeue.md +39 -0
  703. package/skills/ship/references/operator-holds.md +149 -0
  704. package/skills/ship/references/pipeline-tally-render.md +43 -0
  705. package/skills/ship/references/pr-overlap.md +153 -0
  706. package/skills/ship/references/queue-mode.md +7 -0
  707. package/skills/ship/references/ready-run-wait.md +36 -0
  708. package/skills/ship/references/red-on-main-quarantine.md +110 -0
  709. package/skills/ship/references/register-update-pr-pattern.md +88 -0
  710. package/skills/ship/references/settle-then-admin-merge.md +130 -0
  711. package/skills/ship/references/supabase-column-probe.md +13 -0
  712. package/skills/ship/scripts/auto-close-scan.sh +76 -0
  713. package/skills/ship/scripts/battery-owed.sh +403 -0
  714. package/skills/ship/scripts/hold-gate-eval.sh +410 -0
  715. package/skills/ship/scripts/net-issue-flow.sh +665 -0
  716. package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
  717. package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
  718. package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
  719. package/skills/skill-creator/SKILL.md +299 -0
  720. package/skills/skill-creator/references/api-security.md +248 -0
  721. package/skills/skill-creator/references/authoring-levers.md +110 -0
  722. package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
  723. package/skills/skill-creator/references/best-practices.md +431 -0
  724. package/skills/skill-creator/references/common-patterns.md +639 -0
  725. package/skills/skill-creator/references/core-principles.md +193 -0
  726. package/skills/skill-creator/references/executable-code.md +188 -0
  727. package/skills/skill-creator/references/iteration-and-testing.md +509 -0
  728. package/skills/skill-creator/references/official-spec.md +193 -0
  729. package/skills/skill-creator/references/recommended-structure.md +180 -0
  730. package/skills/skill-creator/references/skill-structure.md +396 -0
  731. package/skills/skill-creator/references/using-scripts.md +122 -0
  732. package/skills/skill-creator/references/using-templates.md +120 -0
  733. package/skills/skill-creator/references/workflows-and-validation.md +541 -0
  734. package/skills/skill-creator/scripts/init_skill.py +322 -0
  735. package/skills/skill-creator/scripts/package_skill.py +110 -0
  736. package/skills/skill-creator/scripts/quick_validate.py +64 -0
  737. package/skills/skill-creator/templates/router-skill.md +73 -0
  738. package/skills/skill-creator/templates/simple-skill.md +34 -0
  739. package/skills/skill-creator/workflows/add-reference.md +96 -0
  740. package/skills/skill-creator/workflows/add-script.md +104 -0
  741. package/skills/skill-creator/workflows/add-template.md +83 -0
  742. package/skills/skill-creator/workflows/add-workflow.md +128 -0
  743. package/skills/skill-creator/workflows/audit-skill.md +159 -0
  744. package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
  745. package/skills/skill-creator/workflows/create-new-skill.md +207 -0
  746. package/skills/skill-creator/workflows/get-guidance.md +134 -0
  747. package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
  748. package/skills/skill-creator/workflows/verify-skill.md +226 -0
  749. package/skills/skill-security-scan/SKILL.md +145 -0
  750. package/skills/skill-security-scan/references/disclaimer.md +21 -0
  751. package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
  752. package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
  753. package/skills/skill-security-scan/references/override-mechanism.md +171 -0
  754. package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
  755. package/skills/skill-security-scan/references/regex-patterns.md +136 -0
  756. package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
  757. package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
  758. package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
  759. package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
  760. package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
  761. package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
  762. package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
  763. package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
  764. package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
  765. package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
  766. package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
  767. package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
  768. package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
  769. package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
  770. package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
  771. package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
  772. package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
  773. package/skills/skill-security-scan/scripts/lib.sh +197 -0
  774. package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
  775. package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
  776. package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
  777. package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
  778. package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
  779. package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
  780. package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
  781. package/skills/social-distribute/SKILL.md +485 -0
  782. package/skills/spec-templates/SKILL.md +274 -0
  783. package/skills/sync/SKILL.md +49 -0
  784. package/skills/test-browser/SKILL.md +464 -0
  785. package/skills/test-browser/references/feature-map.md +161 -0
  786. package/skills/test-fix-loop/SKILL.md +159 -0
  787. package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
  788. package/skills/triage/SKILL.md +402 -0
  789. package/skills/trigger-cron/SKILL.md +136 -0
  790. package/skills/trigger-cron/scripts/trigger.sh +198 -0
  791. package/skills/user-set-role/SKILL.md +147 -0
  792. package/skills/user-set-role/scripts/set-role.sh +218 -0
  793. package/skills/user-story-writer/SKILL.md +77 -0
  794. package/skills/ux-audit/SKILL.md +251 -0
  795. package/skills/ux-audit/references/finding.schema.json +62 -0
  796. package/skills/ux-audit/references/route-list.yaml +95 -0
  797. package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
  798. package/skills/ux-audit/scripts/bot-signin.ts +135 -0
  799. package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
  800. package/skills/work/SKILL.md +1479 -0
  801. package/skills/work/references/end-of-work-resume.md +7 -0
  802. package/skills/work/references/hook-test-main-cwd.md +24 -0
  803. package/skills/work/references/key-principles.md +83 -0
  804. package/skills/work/references/property-testing.md +122 -0
  805. package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
  806. package/skills/work/references/work-agent-teams.md +97 -0
  807. package/skills/work/references/work-lifecycle-parallel.md +147 -0
  808. package/skills/work/references/work-lockfile-bumps.md +97 -0
  809. package/skills/work/references/work-scratch-sandboxes.md +212 -0
  810. package/skills/work/references/work-subagent-fanout.md +61 -0
  811. package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
  812. package/skills/xcode-test/SKILL.md +360 -0
@@ -0,0 +1,705 @@
1
+ #!/usr/bin/env bun
2
+ // CLI wrapper for the source-vs-live Better Stack reconcile (#6549 item 2; monitors arm and
3
+ // unmanaged-live class #7884; logs_alert arm #8097).
4
+ //
5
+ // Reads the live payload from Better Stack, reconciles it against the executable MANIFEST +
6
+ // resolved `.tf` declarations (pure logic in ../lib/heartbeat-live-reconcile.ts), prints structured
7
+ // `SOLEUR_HEARTBEAT_RECONCILE_*` markers to stdout, and exits with a tri-state contract that the
8
+ // drift workflow branches on. It only READS; it never unpauses, creates or deletes anything.
9
+ //
10
+ // Better Stack API contract (list endpoints, measured 2026-09-15 with the READONLY token):
11
+ // GET https://uptime.betterstack.com/api/v2/heartbeats
12
+ // GET https://uptime.betterstack.com/api/v2/monitors
13
+ // Authorization: Bearer <BETTERSTACK_API_TOKEN>
14
+ // 200 -> { data: [ { id: "<digits>", attributes: { … } } ],
15
+ // pagination: { first, last, prev, next: "https://uptime.betterstack.com/api/v2/<ep>?page=2" | null } }
16
+ //
17
+ // Arms (each prints its markers; the exit code is derived from the emitted markers at the end —
18
+ // any ERROR line -> 1, else any MISMATCH line -> 2, else 0):
19
+ // declarations `for_each`/`count` resolved exactly from literal variable defaults (source defaults
20
+ // only — TF_VAR_* overrides are not seen); a shape it cannot resolve, or an
21
+ // override / *.tf.json file in the root:
22
+ // SOLEUR_HEARTBEAT_RECONCILE_ERROR surface=declarations reason=unresolvable-declaration resource=<type.name|file>
23
+ // any other declaration failure (unbalanced block, unreadable dir), contained to its arm:
24
+ // SOLEUR_HEARTBEAT_RECONCILE_ERROR surface=declarations reason=parse-error detail="<file>: <msg>"
25
+ // heartbeats (a) fed-but-paused / (b) absent-live per resolved instance, plus unmanaged-live:
26
+ // SOLEUR_HEARTBEAT_RECONCILE_MISMATCH name=<n> live=absent|paused reason=absent-live|fed-but-paused resource=<type.name> route=<reason>~resource.<type>.<name>[.<key>]
27
+ // SOLEUR_HEARTBEAT_RECONCILE_MISMATCH surface=heartbeats reason=unmanaged-live id=<id> [dup=name] route=unmanaged-live~id.<id> name="<n>"
28
+ // SOLEUR_HEARTBEAT_RECONCILE_OK surface=heartbeats checked=<n> live=<n>
29
+ // monitors always runs (#7884):
30
+ // SOLEUR_HEARTBEAT_RECONCILE_MISMATCH surface=monitors reason=unmanaged-live id=<id> [dup=url] route=unmanaged-live~id.<id> url="<url>" name="<n>"
31
+ // SOLEUR_HEARTBEAT_RECONCILE_MISMATCH surface=monitors reason=absent-live resource=<type.name> route=absent-live~resource.<type>.<name>[.<key>] url="<url>"
32
+ // SOLEUR_HEARTBEAT_RECONCILE_MISMATCH surface=monitors reason=monitor-config-drift id=<id> resource=<type.name> field=<f> route=monitor-config-drift~id.<id>.<f> detail="declared=<v> live=<v>"
33
+ // SOLEUR_HEARTBEAT_RECONCILE_OK surface=monitors declared=<n> live=<n> matched=<id,id,…> (arm summary:
34
+ // printed whenever the read + declarations resolved, beside any MISMATCH rows)
35
+ // logs_alert (#8097 / ADR-218) reads GET https://telemetry.betterstack.com/api/v2/alerts (same
36
+ // global token) only when the root declares a `logtail_exploration_alert`:
37
+ // SOLEUR_HEARTBEAT_RECONCILE_MISMATCH name=<n> live=logs_alert reason=logs-alert-paused|logs-alert-absent resource=<type.name> route=<reason>~resource.<type>.<name> detail="<paused_reason>"
38
+ // inventory only when BOTH uptime reads succeeded:
39
+ // SOLEUR_HEARTBEAT_RECONCILE_INVENTORY monitors=<n> heartbeats=<n> total=<n>
40
+ //
41
+ // Per-read outcomes: 5xx/429/timeout (the per-request timeout covers the body read) after retries,
42
+ // or the overall run deadline passing -> `UNREACHABLE surface=<arm> detail="…"` (rc 0, no page);
43
+ // 401/403, a malformed body, a non-numeric row id, a non-boolean `paused` / alarm leaf, a row id
44
+ // already read, a monitors row without a string url, an off-endpoint / non-page-param / repeated
45
+ // `pagination.next`, or more than 50 pages -> `ERROR … reason=<kind> detail="…"` (rc 1). The arms are
46
+ // independent: one arm's ERROR never suppresses another arm's markers.
47
+ //
48
+ // Marker grammar: machine fields first; every MISMATCH line carries exactly one `route=` token
49
+ // (`ROUTE_TOKEN_RE`), unique per violation within a run; every vendor-sourced field (`url`, `name`,
50
+ // `detail`) last and double-quoted, with `"` percent-encoded as %22, line breaks / control / format
51
+ // (Cf) / private-use (Co) characters stripped and each vendor field capped at 200 characters.
52
+ // Routing tokens (`reason=`, `resource=`, `id=`, `surface=`, `route=`) are only ever read from the
53
+ // text before a line's first `"`, so vendor text cannot forge them.
54
+
55
+ import { readFileSync } from "node:fs";
56
+ import { join } from "node:path";
57
+
58
+ import { MANIFEST, type ManifestEntry } from "../lib/heartbeat-manifest";
59
+ import {
60
+ assertNever,
61
+ assertUniqueMonitorUrls,
62
+ DeclarationParseError,
63
+ type DiscoveredHeartbeat,
64
+ type DiscoveredLogsAlert,
65
+ type DiscoveredMonitor,
66
+ findUnmanagedHeartbeats,
67
+ type InfraVariables,
68
+ type LiveHeartbeat,
69
+ type LiveLogsAlert,
70
+ type LiveMonitor,
71
+ listTfFiles,
72
+ matchedMonitorIds,
73
+ parseHeartbeatBlocks,
74
+ parseLogsAlertBlocks,
75
+ parseMonitorBlocks,
76
+ reconcileHeartbeats,
77
+ reconcileLogsAlerts,
78
+ reconcileMonitors,
79
+ resolveInfraVariables,
80
+ resolveInfraVariablesPerFile,
81
+ routeToken,
82
+ UnresolvableDeclaration,
83
+ type Violation,
84
+ } from "../lib/heartbeat-live-reconcile";
85
+
86
+ export { ROUTE_TOKEN_RE, VIOLATION_REASONS, type ViolationReason } from "../lib/heartbeat-live-reconcile";
87
+
88
+ const UPTIME_HOST = "uptime.betterstack.com";
89
+ const HEARTBEATS_PATH = "/api/v2/heartbeats";
90
+ const MONITORS_PATH = "/api/v2/monitors";
91
+ const HEARTBEATS_URL = `https://${UPTIME_HOST}${HEARTBEATS_PATH}`;
92
+ export const MONITORS_URL = `https://${UPTIME_HOST}${MONITORS_PATH}`;
93
+ // (#8097) The Telemetry (Logs) API lives on a SECOND host. A second EXACT constant, deliberately —
94
+ // never a suffix match on `betterstack.com`: `telemetry.betterstack.com.evil.example` must be refused.
95
+ const LOGS_ALERTS_HOST = "telemetry.betterstack.com";
96
+ const LOGS_ALERTS_PATH = "/api/v2/alerts";
97
+ const LOGS_ALERTS_URL = `https://${LOGS_ALERTS_HOST}${LOGS_ALERTS_PATH}`;
98
+
99
+ /** A reader never follows more pages than this; hitting it is an ERROR, not a truncated OK. */
100
+ export const MAX_PAGES = 50;
101
+
102
+ /** Default overall run deadline (all arms, all pages, all retries). Past it, an arm is UNREACHABLE. */
103
+ export const RUN_DEADLINE_MS = 240_000;
104
+
105
+ /** The only query params a vendor `pagination.next` may carry. */
106
+ const PAGE_PARAMS = new Set(["page", "per_page"]);
107
+
108
+ /**
109
+ * Only ever attach the Bearer token to the trusted Better Stack host over HTTPS, and only to the
110
+ * arm's own endpoint. `pagination.next` comes from the RESPONSE BODY, so a MITM or a
111
+ * malicious/compromised response could point it at an attacker host to exfiltrate the token, or at
112
+ * another endpoint to substitute its payload — pin host, default port, no userinfo, the exact
113
+ * pathname, and only `page` / `per_page` query params (SSRF / credential-exfiltration /
114
+ * endpoint-switch / filtered-view guard). A `#fragment` is ignored (never sent, never part of a
115
+ * page's identity).
116
+ */
117
+ function isAllowedUrlFor(host: string, pathname: string, u: string): boolean {
118
+ try {
119
+ const parsed = new URL(u);
120
+ return (
121
+ parsed.protocol === "https:" &&
122
+ parsed.hostname === host &&
123
+ parsed.port === "" &&
124
+ parsed.username === "" &&
125
+ parsed.password === "" &&
126
+ parsed.pathname === pathname &&
127
+ [...parsed.searchParams.keys()].every((k) => PAGE_PARAMS.has(k))
128
+ );
129
+ } catch {
130
+ return false;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * A page's identity: origin + path + sorted page params, with `page` defaulting to 1 and numeric
136
+ * values canonicalized (`01` -> `1`), fragment dropped — so equivalent spellings of one page collide.
137
+ */
138
+ function pageKey(u: string): string {
139
+ const parsed = new URL(u);
140
+ const params = new Map<string, string>([["page", "1"]]);
141
+ for (const [k, v] of parsed.searchParams) params.set(k, /^[0-9]+$/.test(v) ? String(Number(v)) : v);
142
+ const query = [...params]
143
+ .sort(([a], [b]) => a.localeCompare(b))
144
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`);
145
+ return `${parsed.origin}${parsed.pathname}?${query.join("&")}`;
146
+ }
147
+
148
+ function withoutHash(u: string): string {
149
+ const parsed = new URL(u);
150
+ parsed.hash = "";
151
+ return parsed.href;
152
+ }
153
+
154
+ type PagedResult<T> =
155
+ | { ok: true; live: T[] }
156
+ | { ok: false; kind: "unreachable" | "auth" | "error"; detail?: string };
157
+ export type FetchResult = PagedResult<LiveHeartbeat>;
158
+ export type MonitorsFetchResult = PagedResult<LiveMonitor>;
159
+ export type LogsAlertsFetchResult = PagedResult<LiveLogsAlert>;
160
+
161
+ export interface FetchOptions {
162
+ token: string;
163
+ fetchImpl?: (url: string, init?: RequestInit) => Promise<Response>;
164
+ sleepImpl?: (ms: number) => Promise<void>;
165
+ /** Clock seam for the run deadline (ms). Default `Date.now`. */
166
+ nowImpl?: () => number;
167
+ /** Max attempts per page on a transient failure (5xx/429/network/timeout). Default 3. */
168
+ maxAttempts?: number;
169
+ /** Per-request timeout in ms, covering the response headers AND the body read. Default 10_000. */
170
+ timeoutMs?: number;
171
+ /** Overall run deadline in ms, from the start of the run (or of a direct fetch call). Default 240_000. */
172
+ runDeadlineMs?: number;
173
+ /** Absolute deadline (`nowImpl()` scale); set once by `runReconcile` so every arm shares one budget. */
174
+ deadlineAt?: number;
175
+ }
176
+
177
+ const defaultSleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
178
+
179
+ /** A list row the reader refuses (never skips): surfaces as `error`, rc 1. */
180
+ class RowError extends Error {}
181
+
182
+ /** A request (headers or body) that outlived its per-request timeout — transient. */
183
+ class RequestTimeout extends Error {}
184
+
185
+ /** Vendor row id as a numeric string, or a `RowError`. */
186
+ function numericId(id: unknown): string {
187
+ if (typeof id === "string" && /^[0-9]+$/.test(id)) return id;
188
+ if (typeof id === "number" && Number.isSafeInteger(id) && id >= 0) return String(id);
189
+ throw new RowError("row id is not numeric");
190
+ }
191
+
192
+ /** A boolean leaf the reconcile compares: anything but a real boolean is refused (fail closed). */
193
+ function boolField(attrs: Record<string, unknown>, key: string, what: string): boolean {
194
+ const v = attrs[key];
195
+ if (typeof v !== "boolean") throw new RowError(`${what} has a non-boolean ${key}`);
196
+ return v;
197
+ }
198
+
199
+ /** A numeric leaf: a finite number or null; anything else is refused. */
200
+ function numberOrNull(attrs: Record<string, unknown>, key: string, what: string): number | null {
201
+ const v = attrs[key];
202
+ if (v === null || v === undefined) return null;
203
+ if (typeof v !== "number" || !Number.isFinite(v)) throw new RowError(`${what} has a non-numeric ${key}`);
204
+ return v;
205
+ }
206
+
207
+ /** A string leaf: a string or null; anything else is refused. */
208
+ function stringOrNull(attrs: Record<string, unknown>, key: string, what: string): string | null {
209
+ const v = attrs[key];
210
+ if (v === null || v === undefined) return null;
211
+ if (typeof v !== "string") throw new RowError(`${what} has a non-string ${key}`);
212
+ return v;
213
+ }
214
+
215
+ /**
216
+ * Fetch every live heartbeat (following `pagination.next`) with depth-bounded retry.
217
+ *
218
+ * - transient (5xx / 429 / thrown network/abort error / request timeout): retry up to
219
+ * `maxAttempts` with exponential backoff (1s/2s/4s); exhausted, or the run deadline passed ->
220
+ * `unreachable` (the caller must NOT page — Sentry stays ok).
221
+ * - auth (401/403): NOT transient -> `auth` immediately, no retry.
222
+ * - other non-2xx, a malformed 200 body, a non-numeric id, a non-string name or a non-boolean
223
+ * `paused`: `error`.
224
+ *
225
+ * Network is injected (`fetchImpl`/`sleepImpl`/`nowImpl`) so the retry/auth/pagination/deadline
226
+ * branches are unit-testable without a token or the live API.
227
+ */
228
+ export async function fetchLiveHeartbeats(opts: FetchOptions): Promise<FetchResult> {
229
+ return fetchPaged(HEARTBEATS_URL, (u) => isAllowedUrlFor(UPTIME_HOST, HEARTBEATS_PATH, u), opts, (attrs, id) => {
230
+ const rowId = numericId(id);
231
+ const what = `heartbeat ${rowId}`;
232
+ if (!attrs || typeof attrs.name !== "string") throw new RowError(`${what} has no string name`);
233
+ return { id: rowId, name: attrs.name, paused: boolField(attrs, "paused", what) };
234
+ });
235
+ }
236
+
237
+ /** (#7884) Every live uptime monitor (`GET /api/v2/monitors`), same retry/auth/pagination contract. */
238
+ export async function fetchLiveMonitors(opts: FetchOptions): Promise<MonitorsFetchResult> {
239
+ return fetchPaged(MONITORS_URL, (u) => isAllowedUrlFor(UPTIME_HOST, MONITORS_PATH, u), opts, (attrs, id) => {
240
+ const rowId = numericId(id);
241
+ const what = `monitor ${rowId}`;
242
+ if (!attrs || typeof attrs.url !== "string") throw new RowError(`${what} has no string url`);
243
+ const days = attrs.maintenance_days;
244
+ if (days !== null && days !== undefined && !(Array.isArray(days) && days.every((d) => typeof d === "string"))) {
245
+ throw new RowError(`${what} has a malformed maintenance_days`);
246
+ }
247
+ return {
248
+ id: rowId,
249
+ url: attrs.url,
250
+ name: typeof attrs.pronounceable_name === "string" ? attrs.pronounceable_name : "",
251
+ monitorType: typeof attrs.monitor_type === "string" ? attrs.monitor_type : "",
252
+ requiredKeyword: typeof attrs.required_keyword === "string" ? attrs.required_keyword : null,
253
+ paused: boolField(attrs, "paused", what),
254
+ email: boolField(attrs, "email", what),
255
+ call: boolField(attrs, "call", what),
256
+ sms: boolField(attrs, "sms", what),
257
+ push: boolField(attrs, "push", what),
258
+ verifySsl: boolField(attrs, "verify_ssl", what),
259
+ followRedirects: boolField(attrs, "follow_redirects", what),
260
+ confirmationPeriod: numberOrNull(attrs, "confirmation_period", what),
261
+ checkFrequency: numberOrNull(attrs, "check_frequency", what),
262
+ requestTimeout: numberOrNull(attrs, "request_timeout", what),
263
+ recoveryPeriod: numberOrNull(attrs, "recovery_period", what),
264
+ maintenanceFrom: stringOrNull(attrs, "maintenance_from", what),
265
+ maintenanceTo: stringOrNull(attrs, "maintenance_to", what),
266
+ maintenanceDays: (days as string[] | null | undefined) ?? null,
267
+ };
268
+ });
269
+ }
270
+
271
+ /** (#8097) Every live Logs alert (`GET /api/v2/alerts`), same retry/auth/pagination contract. */
272
+ export async function fetchLiveLogsAlerts(opts: FetchOptions): Promise<LogsAlertsFetchResult> {
273
+ return fetchPaged(LOGS_ALERTS_URL, (u) => isAllowedUrlFor(LOGS_ALERTS_HOST, LOGS_ALERTS_PATH, u), opts, (attrs) => {
274
+ if (attrs && typeof attrs.name === "string") {
275
+ // Coalesced ONCE, here: null/absent/non-string -> "" so every consumer reads a plain string.
276
+ return {
277
+ name: attrs.name,
278
+ paused: boolField(attrs, "paused", "logs alert"),
279
+ pausedReason: typeof attrs.paused_reason === "string" ? attrs.paused_reason : "",
280
+ };
281
+ }
282
+ return null;
283
+ });
284
+ }
285
+
286
+ type Attempt =
287
+ | { type: "retry"; detail: string }
288
+ | { type: "final"; result: { ok: false; kind: "auth" | "error"; detail: string } }
289
+ | { type: "body"; body: unknown };
290
+
291
+ /**
292
+ * Fetch every page of a Better Stack list endpoint (following `pagination.next`) with
293
+ * depth-bounded retry. The retry/auth/pagination contract documented on the heartbeat wrapper's
294
+ * header lives here:
295
+ * - transient (5xx / 429 / thrown network/abort error / a request — headers or body — outliving
296
+ * `timeoutMs`): retry up to `maxAttempts` with exponential backoff (1s/2s/4s); exhausted ->
297
+ * `unreachable` (the caller must NOT page). The run deadline is checked before every attempt;
298
+ * past it -> `unreachable` too.
299
+ * - auth (401/403): NOT transient -> `auth` immediately, no retry.
300
+ * - other non-2xx, an HTTP 3xx (never followed), a malformed 200 body, a row `mapAttrs` refuses
301
+ * (throws `RowError`), a row id already read (on this or an earlier page), a `pagination.next`
302
+ * that fails `isAllowedUrl` or names an already-read page (`pageKey`), or more than `MAX_PAGES`
303
+ * pages: `error`.
304
+ */
305
+ async function fetchPaged<T>(
306
+ startUrl: string,
307
+ isAllowedUrl: (u: string) => boolean,
308
+ opts: FetchOptions,
309
+ mapAttrs: (attrs: Record<string, unknown> | undefined, id: unknown) => T | null,
310
+ ): Promise<PagedResult<T>> {
311
+ const fetchImpl = opts.fetchImpl ?? fetch;
312
+ const sleepImpl = opts.sleepImpl ?? defaultSleep;
313
+ const now = opts.nowImpl ?? Date.now;
314
+ const maxAttempts = opts.maxAttempts ?? 3;
315
+ const timeoutMs = opts.timeoutMs ?? 10_000;
316
+ const deadlineAt = opts.deadlineAt ?? now() + (opts.runDeadlineMs ?? RUN_DEADLINE_MS);
317
+
318
+ const attemptOnce = async (target: string): Promise<Attempt> => {
319
+ const controller = new AbortController();
320
+ let timer: ReturnType<typeof setTimeout> | undefined;
321
+ const budget = Math.max(1, Math.min(timeoutMs, deadlineAt - now()));
322
+ // ONE timer per attempt, raced against both the headers and the body read.
323
+ const expired = new Promise<never>((_, reject) => {
324
+ timer = setTimeout(() => {
325
+ controller.abort();
326
+ reject(new RequestTimeout(`request timed out after ${budget}ms`));
327
+ }, budget);
328
+ });
329
+ expired.catch(() => {});
330
+ try {
331
+ let resp: Response;
332
+ try {
333
+ resp = await Promise.race([
334
+ fetchImpl(target, {
335
+ headers: { Authorization: `Bearer ${opts.token}`, Accept: "application/json" },
336
+ signal: controller.signal,
337
+ // Do NOT auto-follow HTTP redirects: `fetch`'s default `redirect: "follow"` would re-issue
338
+ // the request — with the Bearer token attached — to a `Location` we never validate (a
339
+ // MITM/DNS-takeover/compromised-edge 3xx could exfiltrate the token). We inspect any 3xx
340
+ // ourselves below and refuse it. The API paginates via the response body (`pagination.next`,
341
+ // pinned by `isAllowedUrl`), never via HTTP redirects.
342
+ redirect: "manual",
343
+ }),
344
+ expired,
345
+ ]);
346
+ } catch (err) {
347
+ // Thrown network/abort/timeout error — transient.
348
+ return { type: "retry", detail: String((err as Error)?.message ?? err) };
349
+ }
350
+ if (resp.status === 401 || resp.status === 403) {
351
+ return { type: "final", result: { ok: false, kind: "auth", detail: `HTTP ${resp.status}` } };
352
+ }
353
+ // `redirect: "manual"` surfaces 3xx here instead of auto-following. The API never legitimately
354
+ // redirects, so refuse fail-closed — the token is never re-sent to a redirect target.
355
+ if (resp.status >= 300 && resp.status < 400) {
356
+ return { type: "final", result: { ok: false, kind: "error", detail: `unexpected redirect (HTTP ${resp.status})` } };
357
+ }
358
+ if (resp.status === 429 || resp.status >= 500) return { type: "retry", detail: `HTTP ${resp.status}` };
359
+ if (!resp.ok) return { type: "final", result: { ok: false, kind: "error", detail: `HTTP ${resp.status}` } };
360
+ try {
361
+ // The same timer covers the body: a server that sends headers then stalls cannot hang the run.
362
+ return { type: "body", body: await Promise.race([resp.json(), expired]) };
363
+ } catch (err) {
364
+ if (err instanceof RequestTimeout) return { type: "retry", detail: err.message };
365
+ return { type: "final", result: { ok: false, kind: "error", detail: `invalid JSON: ${String((err as Error)?.message ?? err)}` } };
366
+ }
367
+ } finally {
368
+ clearTimeout(timer);
369
+ }
370
+ };
371
+
372
+ const live: T[] = [];
373
+ const seenPages = new Set<string>();
374
+ const seenIds = new Set<string>();
375
+ let url: string | null = startUrl;
376
+ let pages = 0;
377
+
378
+ while (url) {
379
+ if (pages >= MAX_PAGES) {
380
+ return { ok: false, kind: "error", detail: `pagination exceeded ${MAX_PAGES} pages` };
381
+ }
382
+ pages++;
383
+ seenPages.add(pageKey(url));
384
+ let body: unknown;
385
+ let fetched = false;
386
+ let lastDetail = "retries exhausted";
387
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
388
+ if (now() >= deadlineAt) return { ok: false, kind: "unreachable", detail: "run deadline exceeded" };
389
+ const outcome = await attemptOnce(url);
390
+ if (outcome.type === "final") return outcome.result;
391
+ if (outcome.type === "body") {
392
+ body = outcome.body;
393
+ fetched = true;
394
+ break;
395
+ }
396
+ lastDetail = outcome.detail;
397
+ if (attempt < maxAttempts) await sleepImpl(1000 * 2 ** (attempt - 1));
398
+ }
399
+ if (!fetched) return { ok: false, kind: "unreachable", detail: lastDetail };
400
+
401
+ const data = (body as { data?: unknown })?.data;
402
+ if (!Array.isArray(data)) {
403
+ return { ok: false, kind: "error", detail: "response has no `data` array" };
404
+ }
405
+ for (const row of data) {
406
+ const r = row as { id?: unknown; attributes?: Record<string, unknown> } | null;
407
+ if (typeof r?.id === "string" || typeof r?.id === "number") {
408
+ const key = String(r.id);
409
+ if (seenIds.has(key)) return { ok: false, kind: "error", detail: `row id ${key} was already read (pages overlap)` };
410
+ seenIds.add(key);
411
+ }
412
+ let mapped: T | null;
413
+ try {
414
+ mapped = mapAttrs(r?.attributes, r?.id);
415
+ } catch (err) {
416
+ if (err instanceof RowError) return { ok: false, kind: "error", detail: err.message };
417
+ throw err;
418
+ }
419
+ if (mapped !== null) live.push(mapped);
420
+ }
421
+ const next = (body as { pagination?: { next?: unknown } })?.pagination?.next;
422
+ if (typeof next === "string" && next.length > 0) {
423
+ if (!isAllowedUrl(next)) {
424
+ // Fail loud rather than either following the token off-host / off-endpoint OR silently
425
+ // reconciling against a truncated or filtered payload (which would false-OK a real mismatch).
426
+ let where = "unparseable";
427
+ try {
428
+ const p = new URL(next);
429
+ where = `${p.host}${p.pathname}`;
430
+ } catch {
431
+ /* keep placeholder */
432
+ }
433
+ return { ok: false, kind: "error", detail: `refusing off-endpoint pagination.next (${where})` };
434
+ }
435
+ if (seenPages.has(pageKey(next))) {
436
+ return { ok: false, kind: "error", detail: "pagination.next repeats an already-read page" };
437
+ }
438
+ url = withoutHash(next);
439
+ } else {
440
+ url = null;
441
+ }
442
+ }
443
+
444
+ return { ok: true, live };
445
+ }
446
+
447
+ /**
448
+ * Read every `.tf` file in an infra directory whose text mentions `needle`, and parse it. A parse
449
+ * failure that is not an `UnresolvableDeclaration` is rethrown as a `DeclarationParseError` naming
450
+ * the file, so the marker says where to look.
451
+ */
452
+ function discoverFromInfra<T>(infraDir: string, needle: string, parse: (tf: string) => T[]): T[] {
453
+ const discovered: T[] = [];
454
+ for (const file of listTfFiles(infraDir)) {
455
+ try {
456
+ const text = readFileSync(join(infraDir, file), "utf8");
457
+ if (!text.includes(needle)) continue;
458
+ discovered.push(...parse(text));
459
+ } catch (err) {
460
+ if (err instanceof UnresolvableDeclaration) throw err;
461
+ throw new DeclarationParseError(file, String((err as Error)?.message ?? err));
462
+ }
463
+ }
464
+ return discovered;
465
+ }
466
+ /** Every resolved heartbeat instance in the root. Throws `UnresolvableDeclaration` / `DeclarationParseError`. */
467
+ export function discoverHeartbeatsFromInfra(infraDir: string, vars: InfraVariables = resolveInfraVariables(infraDir)): DiscoveredHeartbeat[] {
468
+ return discoverFromInfra(infraDir, "betteruptime_heartbeat", (t) => parseHeartbeatBlocks(t, vars));
469
+ }
470
+ /** (#7884) Every resolved monitor instance in the root, URL-unique. Throws `UnresolvableDeclaration` / `DeclarationParseError`. */
471
+ export function discoverMonitorsFromInfra(infraDir: string, vars: InfraVariables = resolveInfraVariables(infraDir)): DiscoveredMonitor[] {
472
+ const declared = discoverFromInfra(infraDir, "betteruptime_monitor", (t) => parseMonitorBlocks(t, vars));
473
+ assertUniqueMonitorUrls(declared);
474
+ return declared;
475
+ }
476
+ /** (#8097) Every `logtail_exploration_alert` block in the root — the `logs_alert` arm's expected set. */
477
+ export function discoverLogsAlertsFromInfra(
478
+ infraDir: string,
479
+ vars: InfraVariables = resolveInfraVariables(infraDir),
480
+ ): DiscoveredLogsAlert[] {
481
+ return discoverFromInfra(infraDir, "logtail_exploration_alert", (t) => parseLogsAlertBlocks(t, vars));
482
+ }
483
+
484
+ /**
485
+ * Sanitize a marker line: turn line breaks, C0 controls, DEL and backticks into a space (so a value
486
+ * can never inject a GitHub Actions `::annotation::`, start a forged marker line, or break out of the
487
+ * code fence in the auto-filed issue body), and remove C1 controls (U+0080-U+009F, incl. NEL
488
+ * U+0085), every format character (`\p{Cf}`: zero-width U+200B-U+200F, soft hyphen U+00AD, bidi
489
+ * embeddings/overrides/isolates U+202A-U+202E / U+2066-U+2069, word joiner U+2060, BOM U+FEFF, tag
490
+ * characters U+E0001…), private-use characters (`\p{Co}`) and lone surrogates (`\p{Cs}`), so nothing
491
+ * invisible can visually reorder or hide a routing token. Vendor free text (`paused_reason`, monitor
492
+ * `url`/`pronounceable_name`, heartbeat `name`) flows through here.
493
+ * Escapes only — cq-regex-unicode-separators-escape-only.
494
+ */
495
+ const INVISIBLE = /[\p{Cf}\p{Co}\p{Cs}\u0080-\u009f]+/gu;
496
+ const BREAKING = /[\r\n\u2028\u2029\u0000-\u001f\u007f`]+/gu;
497
+ const oneLine = (s: string) => s.replace(INVISIBLE, "").replace(BREAKING, " ");
498
+
499
+ /** Max characters (code points) of any one vendor-sourced field in a marker, counted after sanitizing. */
500
+ export const VENDOR_FIELD_CAP = 200;
501
+
502
+ /** A vendor-sourced value for a quoted marker field: sanitized, capped, `"` -> `%22`. */
503
+ const quoted = (s: string) => Array.from(oneLine(s)).slice(0, VENDOR_FIELD_CAP).join("").replace(/"/g, "%22");
504
+
505
+ /** A source-derived value printed UNQUOTED before the first `"`: no quote or whitespace can split it. */
506
+ const bare = (s: string) => oneLine(s).replace(/"/g, "%22").replace(/\s/g, "%20");
507
+
508
+ interface RunResult {
509
+ code: number;
510
+ markers: string[];
511
+ }
512
+
513
+ export interface RunDeps {
514
+ /** Injectable seam so harness rows can prove the arm's assertions are load-bearing. */
515
+ reconcileMonitors?: typeof reconcileMonitors;
516
+ }
517
+
518
+ /** The one MISMATCH line for a violation. Exhaustive over the union (a new variant fails to compile). */
519
+ function mismatchMarker(v: Violation): string {
520
+ const route = routeToken(v);
521
+ const P = "SOLEUR_HEARTBEAT_RECONCILE_MISMATCH";
522
+ switch (v.kind) {
523
+ case "heartbeat":
524
+ return `${P} name=${bare(v.liveName)} live=${v.live} reason=${v.reason} resource=betteruptime_heartbeat.${v.resourceName} ${route}`;
525
+ case "logs_alert":
526
+ // Prefix through `reason=` unchanged from ADR-218 / monitor-send-failed-alert.md; `resource=`
527
+ // and `route=` follow, and the quoted vendor text stays last.
528
+ return `${P} name=${bare(v.liveName)} live=${v.live} reason=${v.reason} resource=logtail_exploration_alert.${v.resourceName} ${route} detail="${quoted(v.detail ?? "")}"`;
529
+ case "monitor":
530
+ switch (v.reason) {
531
+ case "absent-live":
532
+ return `${P} surface=monitors reason=absent-live resource=betteruptime_monitor.${v.resourceName} ${route} url="${quoted(v.url)}"`;
533
+ case "monitor-config-drift":
534
+ return `${P} surface=monitors reason=monitor-config-drift id=${v.id} resource=betteruptime_monitor.${v.resourceName} field=${v.field} ${route} detail="${quoted(`declared=${v.declared} live=${v.live}`)}"`;
535
+ default:
536
+ return assertNever(v);
537
+ }
538
+ case "unmanaged":
539
+ switch (v.surface) {
540
+ case "monitors":
541
+ return `${P} surface=monitors reason=unmanaged-live id=${v.id}${v.dup ? " dup=url" : ""} ${route} url="${quoted(v.url)}" name="${quoted(v.name)}"`;
542
+ case "heartbeats":
543
+ return `${P} surface=heartbeats reason=unmanaged-live id=${v.id}${v.dup ? " dup=name" : ""} ${route} name="${quoted(v.name)}"`;
544
+ default:
545
+ return assertNever(v);
546
+ }
547
+ default:
548
+ return assertNever(v);
549
+ }
550
+ }
551
+
552
+ type Discovery<T> = { ok: true; declared: T } | { ok: false; marker: string };
553
+
554
+ /** A declarations ERROR marker for any error: unresolvable shape, per-file parse failure, or anything else. */
555
+ function declarationErrorMarker(err: unknown, fallbackWhere: string): string {
556
+ if (err instanceof UnresolvableDeclaration) {
557
+ return oneLine(`SOLEUR_HEARTBEAT_RECONCILE_ERROR surface=declarations reason=unresolvable-declaration resource=${bare(err.resource)}`);
558
+ }
559
+ const detail =
560
+ err instanceof DeclarationParseError ? `${err.file}: ${err.detail}` : `${fallbackWhere}: ${String((err as Error)?.message ?? err)}`;
561
+ return oneLine(`SOLEUR_HEARTBEAT_RECONCILE_ERROR surface=declarations reason=parse-error detail="${quoted(detail)}"`);
562
+ }
563
+
564
+ function discover<T>(fn: () => T, where: string): Discovery<T> {
565
+ try {
566
+ return { ok: true, declared: fn() };
567
+ } catch (err) {
568
+ return { ok: false, marker: declarationErrorMarker(err, where) };
569
+ }
570
+ }
571
+
572
+ export async function runReconcile(
573
+ infraDir: string,
574
+ opts: FetchOptions,
575
+ manifest: readonly Pick<ManifestEntry, "name" | "feeder" | "arming_pending">[] = MANIFEST,
576
+ deps: RunDeps = {},
577
+ ): Promise<RunResult> {
578
+ if (!opts.token) {
579
+ return {
580
+ code: 1,
581
+ markers: ["SOLEUR_HEARTBEAT_RECONCILE_ERROR reason=token-absent"],
582
+ };
583
+ }
584
+ const reconcileMonitorsImpl = deps.reconcileMonitors ?? reconcileMonitors;
585
+ // One deadline shared by every arm, page and retry.
586
+ const now = opts.nowImpl ?? Date.now;
587
+ const fetchOpts: FetchOptions = { ...opts, deadlineAt: opts.deadlineAt ?? now() + (opts.runDeadlineMs ?? RUN_DEADLINE_MS) };
588
+
589
+ // ── Declarations: resolved once, per arm, and every failure contained to the arm it blinds ──
590
+ const declMarkers = new Set<string>(); // a root-wide failure (override file) is reported once, not per arm
591
+ let vars: InfraVariables = new Map();
592
+ try {
593
+ const resolution = resolveInfraVariablesPerFile(infraDir);
594
+ vars = resolution.vars;
595
+ for (const e of resolution.errors) declMarkers.add(declarationErrorMarker(e, infraDir));
596
+ } catch (err) {
597
+ declMarkers.add(declarationErrorMarker(err, infraDir));
598
+ }
599
+ const hbDecl = discover(() => discoverHeartbeatsFromInfra(infraDir, vars), infraDir);
600
+ const monDecl = discover(() => discoverMonitorsFromInfra(infraDir, vars), infraDir);
601
+ const alertDecl = discover(() => discoverLogsAlertsFromInfra(infraDir, vars), infraDir);
602
+ for (const d of [hbDecl, monDecl, alertDecl]) if (!d.ok) declMarkers.add(d.marker);
603
+
604
+ // ── Arm 1: heartbeats (the original #6549 contract + #7884 unmanaged-live) ──
605
+ const result = await fetchLiveHeartbeats(fetchOpts);
606
+ const hbMarkers: string[] = [];
607
+ if (!result.ok) {
608
+ if (result.kind === "unreachable") {
609
+ hbMarkers.push(oneLine(`SOLEUR_HEARTBEAT_RECONCILE_UNREACHABLE surface=heartbeats detail="${quoted(result.detail ?? "n/a")}"`));
610
+ } else {
611
+ // auth or malformed -> hard error (exit 1)
612
+ hbMarkers.push(oneLine(`SOLEUR_HEARTBEAT_RECONCILE_ERROR reason=${result.kind} detail="${quoted(result.detail ?? "n/a")}"`));
613
+ }
614
+ } else if (hbDecl.ok) {
615
+ const discovered = hbDecl.declared;
616
+ const violations: Violation[] = [
617
+ ...reconcileHeartbeats(manifest, discovered, result.live),
618
+ ...findUnmanagedHeartbeats(discovered, result.live),
619
+ ];
620
+ for (const v of violations) hbMarkers.push(oneLine(mismatchMarker(v)));
621
+ if (violations.length === 0) {
622
+ hbMarkers.push(`SOLEUR_HEARTBEAT_RECONCILE_OK surface=heartbeats checked=${discovered.length} live=${result.live.length}`);
623
+ }
624
+ }
625
+
626
+ // ── Arm 2 (#7884): uptime monitors — always runs, independent of arm 1's outcome ──
627
+ const monitors = await fetchLiveMonitors(fetchOpts);
628
+ const monMarkers: string[] = [];
629
+ if (!monitors.ok) {
630
+ if (monitors.kind === "unreachable") {
631
+ monMarkers.push(oneLine(`SOLEUR_HEARTBEAT_RECONCILE_UNREACHABLE surface=monitors detail="${quoted(monitors.detail ?? "n/a")}"`));
632
+ } else {
633
+ monMarkers.push(oneLine(`SOLEUR_HEARTBEAT_RECONCILE_ERROR surface=monitors reason=${monitors.kind} detail="${quoted(monitors.detail ?? "n/a")}"`));
634
+ }
635
+ } else if (monDecl.ok) {
636
+ const declared = monDecl.declared;
637
+ for (const v of reconcileMonitorsImpl(declared, monitors.live)) monMarkers.push(oneLine(mismatchMarker(v)));
638
+ // The monitors OK line is the arm's completion summary, printed whenever the read and the
639
+ // declarations both resolved — even beside MISMATCH rows — so `matched=` is a positive control
640
+ // that the arm saw each declared object (plan AC8: drift rows for 4226366 AND it in matched=).
641
+ // It never lowers the combined code: the code is derived from the MISMATCH lines themselves.
642
+ const ids = matchedMonitorIds(declared, monitors.live).join(",");
643
+ monMarkers.push(`SOLEUR_HEARTBEAT_RECONCILE_OK surface=monitors declared=${declared.length} live=${monitors.live.length} matched=${ids}`);
644
+ }
645
+
646
+ // ── Arm 3 (#8097): declared Logs alerts — INDEPENDENT of the uptime reads (different host), and
647
+ // only when the root declares one, so a root without Logs alerts makes no telemetry read.
648
+ const armMarkers: string[] = [];
649
+ if (alertDecl.ok && alertDecl.declared.length > 0) {
650
+ const declaredAlerts = alertDecl.declared;
651
+ const alerts = await fetchLiveLogsAlerts(fetchOpts);
652
+ if (!alerts.ok) {
653
+ if (alerts.kind === "unreachable") {
654
+ armMarkers.push(oneLine(`SOLEUR_HEARTBEAT_RECONCILE_UNREACHABLE surface=logs_alert detail="${quoted(alerts.detail ?? "n/a")}"`));
655
+ } else {
656
+ armMarkers.push(
657
+ oneLine(`SOLEUR_HEARTBEAT_RECONCILE_ERROR surface=logs_alert reason=${alerts.kind} detail="${quoted(alerts.detail ?? "n/a")}"`),
658
+ );
659
+ }
660
+ } else {
661
+ const violations = reconcileLogsAlerts(declaredAlerts, alerts.live);
662
+ if (violations.length === 0) {
663
+ armMarkers.push(`SOLEUR_HEARTBEAT_RECONCILE_OK surface=logs_alert declared=${declaredAlerts.length} live=${alerts.live.length}`);
664
+ }
665
+ for (const v of violations) armMarkers.push(oneLine(mismatchMarker(v)));
666
+ }
667
+ }
668
+
669
+ // ── Inventory: a measured object count, only when both uptime lists were read in full ──
670
+ const inventory: string[] = [];
671
+ if (result.ok && monitors.ok) {
672
+ const m = monitors.live.length;
673
+ const h = result.live.length;
674
+ inventory.push(`SOLEUR_HEARTBEAT_RECONCILE_INVENTORY monitors=${m} heartbeats=${h} total=${m + h}`);
675
+ }
676
+
677
+ // ERROR(1) > MISMATCH(2) > OK(0), derived from the rows actually emitted — combined only after
678
+ // every arm has printed, so no code path can disagree with the markers.
679
+ const markers = [...declMarkers, ...hbMarkers, ...monMarkers, ...armMarkers, ...inventory];
680
+ const code = markers.some((m) => m.startsWith("SOLEUR_HEARTBEAT_RECONCILE_ERROR "))
681
+ ? 1
682
+ : markers.some((m) => m.startsWith("SOLEUR_HEARTBEAT_RECONCILE_MISMATCH "))
683
+ ? 2
684
+ : 0;
685
+ return { code, markers };
686
+ }
687
+
688
+ async function main(): Promise<number> {
689
+ const token = process.env.BETTERSTACK_API_TOKEN ?? "";
690
+ // Repo-root-relative default; overridable for local dry-runs.
691
+ const infraDir = process.env.RECONCILE_INFRA_DIR ?? "apps/web-platform/infra";
692
+ const { code, markers } = await runReconcile(infraDir, { token });
693
+ for (const m of markers) console.log(m);
694
+ return code;
695
+ }
696
+
697
+ // Only auto-run as a CLI; importing for tests must not execute (bun `import.meta.main`).
698
+ if (import.meta.main) {
699
+ main()
700
+ .then((code) => process.exit(code))
701
+ .catch((err) => {
702
+ console.log(`SOLEUR_HEARTBEAT_RECONCILE_ERROR reason=uncaught detail="${quoted(String(err?.message ?? err))}"`);
703
+ process.exit(1);
704
+ });
705
+ }