just-vibe 0.7.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 (512) hide show
  1. package/.agents/plugins/marketplace.json +12 -0
  2. package/.claude-plugin/marketplace.json +12 -0
  3. package/CHANGELOG.md +49 -0
  4. package/LICENSE +21 -0
  5. package/README.md +282 -0
  6. package/bin/just-vibe.mjs +3 -0
  7. package/docs/command-quality.md +74 -0
  8. package/docs/compatibility.md +29 -0
  9. package/docs/releases.md +51 -0
  10. package/evals/README.md +47 -0
  11. package/evals/behavior/cases/arch-events/flow.json +12 -0
  12. package/evals/behavior/cases/arch-events/task.md +3 -0
  13. package/evals/behavior/cases/authz/access.mjs +1 -0
  14. package/evals/behavior/cases/authz/task.md +3 -0
  15. package/evals/behavior/cases/checkout/checkout.mjs +1 -0
  16. package/evals/behavior/cases/checkout/contract.md +1 -0
  17. package/evals/behavior/cases/checkout/keep.txt +1 -0
  18. package/evals/behavior/cases/checkout/task.md +3 -0
  19. package/evals/behavior/cases/data-reconcile/source.json +14 -0
  20. package/evals/behavior/cases/data-reconcile/target.json +14 -0
  21. package/evals/behavior/cases/data-reconcile/task.md +3 -0
  22. package/evals/behavior/cases/db-migrate/context.json +13 -0
  23. package/evals/behavior/cases/db-migrate/migration.sql +3 -0
  24. package/evals/behavior/cases/db-migrate/task.md +3 -0
  25. package/evals/behavior/cases/db-query/query.sql +1 -0
  26. package/evals/behavior/cases/db-query/rows.json +32 -0
  27. package/evals/behavior/cases/db-query/task.md +3 -0
  28. package/evals/behavior/cases/decision-matrix/decision.json +22 -0
  29. package/evals/behavior/cases/decision-matrix/task.md +3 -0
  30. package/evals/behavior/cases/github-pr/prs.json +16 -0
  31. package/evals/behavior/cases/github-pr/request.json +9 -0
  32. package/evals/behavior/cases/github-pr/task.md +3 -0
  33. package/evals/behavior/cases/idempotency/contract.md +1 -0
  34. package/evals/behavior/cases/idempotency/orders.mjs +1 -0
  35. package/evals/behavior/cases/idempotency/task.md +3 -0
  36. package/evals/behavior/cases/ml-checkpoint/checkpoint.json +6 -0
  37. package/evals/behavior/cases/ml-checkpoint/task.md +3 -0
  38. package/evals/behavior/cases/ml-checkpoint/training.json +16 -0
  39. package/evals/behavior/cases/ml-evaluate/labels.json +18 -0
  40. package/evals/behavior/cases/ml-evaluate/predictions.json +14 -0
  41. package/evals/behavior/cases/ml-evaluate/task.md +3 -0
  42. package/evals/behavior/cases/ml-leakage/task.json +30 -0
  43. package/evals/behavior/cases/ml-leakage/task.md +3 -0
  44. package/evals/behavior/cases/ml-parity/serving.json +16 -0
  45. package/evals/behavior/cases/ml-parity/task.md +3 -0
  46. package/evals/behavior/cases/ml-parity/training.json +16 -0
  47. package/evals/behavior/cases/ml-split/task.json +7 -0
  48. package/evals/behavior/cases/ml-split/task.md +3 -0
  49. package/evals/behavior/cases/ops-logs/context.json +4 -0
  50. package/evals/behavior/cases/ops-logs/events.json +17 -0
  51. package/evals/behavior/cases/ops-logs/task.md +3 -0
  52. package/evals/behavior/cases/rag-boundary/documents.json +26 -0
  53. package/evals/behavior/cases/rag-boundary/query.json +5 -0
  54. package/evals/behavior/cases/rag-boundary/task.md +3 -0
  55. package/evals/behavior/cases/react-race/AccountPanel.jsx +1 -0
  56. package/evals/behavior/cases/react-race/loader.mjs +1 -0
  57. package/evals/behavior/cases/react-race/task.md +3 -0
  58. package/evals/behavior/cases/regression-test/checkout.mjs +1 -0
  59. package/evals/behavior/cases/regression-test/contract.md +1 -0
  60. package/evals/behavior/cases/regression-test/task.md +3 -0
  61. package/evals/behavior/cases/ui-accessibility/observations.json +17 -0
  62. package/evals/behavior/cases/ui-accessibility/task.md +3 -0
  63. package/evals/behavior/cases/vercel-env/consumers.json +13 -0
  64. package/evals/behavior/cases/vercel-env/metadata.json +11 -0
  65. package/evals/behavior/cases/vercel-env/task.md +3 -0
  66. package/evals/behavior/cases/vite-assets/deployment.json +8 -0
  67. package/evals/behavior/cases/vite-assets/render.mjs +1 -0
  68. package/evals/behavior/cases/vite-assets/task.md +3 -0
  69. package/evals/behavior/cases/vite-assets/vite.config.mjs +1 -0
  70. package/evals/behavior/cases.json +185 -0
  71. package/evals/behavior/code-oracles.mjs +58 -0
  72. package/evals/behavior/harness.mjs +109 -0
  73. package/evals/behavior/oracles.json +196 -0
  74. package/evals/benchmark/README.md +57 -0
  75. package/evals/benchmark/cases.json +9 -0
  76. package/evals/benchmark/harness.mjs +231 -0
  77. package/evals/benchmark/oracles/node.mjs +69 -0
  78. package/evals/benchmark/oracles/python.py +117 -0
  79. package/evals/benchmark/report.mjs +62 -0
  80. package/evals/benchmark/repos/async-cache/README.md +12 -0
  81. package/evals/benchmark/repos/async-cache/TASK.md +1 -0
  82. package/evals/benchmark/repos/async-cache/package.json +1 -0
  83. package/evals/benchmark/repos/async-cache/src/cache.mjs +13 -0
  84. package/evals/benchmark/repos/async-cache/src/view.mjs +9 -0
  85. package/evals/benchmark/repos/async-cache/test/smoke.test.mjs +9 -0
  86. package/evals/benchmark/repos/ledger/README.md +11 -0
  87. package/evals/benchmark/repos/ledger/TASK.md +1 -0
  88. package/evals/benchmark/repos/ledger/src/service.py +14 -0
  89. package/evals/benchmark/repos/ledger/src/store.py +12 -0
  90. package/evals/benchmark/repos/ledger/test/test_smoke.py +9 -0
  91. package/evals/benchmark/repos/scoped-commit/README.md +5 -0
  92. package/evals/benchmark/repos/scoped-commit/TASK.md +1 -0
  93. package/evals/benchmark/repos/scoped-commit/package.json +1 -0
  94. package/evals/benchmark/repos/scoped-commit/src/invoice.mjs +8 -0
  95. package/evals/benchmark/repos/scoped-commit/test/invoice.test.mjs +4 -0
  96. package/evals/benchmark/repos/temporal-ml/README.md +12 -0
  97. package/evals/benchmark/repos/temporal-ml/TASK.md +1 -0
  98. package/evals/benchmark/repos/temporal-ml/src/features.py +9 -0
  99. package/evals/benchmark/repos/temporal-ml/src/pipeline.py +10 -0
  100. package/evals/benchmark/repos/temporal-ml/src/report.py +2 -0
  101. package/evals/benchmark/repos/temporal-ml/test/test_smoke.py +7 -0
  102. package/evals/benchmark/support/commit-tree.mjs +11 -0
  103. package/evals/benchmark/support/python-test-report.py +48 -0
  104. package/evals/fixtures/checkout/checkout.mjs +4 -0
  105. package/evals/fixtures/checkout/checkout.test.mjs +13 -0
  106. package/evals/fixtures/checkout/package.json +6 -0
  107. package/evals/fixtures/checkout/unrelated.txt +1 -0
  108. package/evals/fixtures/ml/observations.csv +5 -0
  109. package/evals/fixtures/ml/task.md +1 -0
  110. package/evals/releases/0.2.0.md +45 -0
  111. package/evals/releases/0.3.0.md +23 -0
  112. package/evals/releases/0.4.0-results.json +1274 -0
  113. package/evals/releases/0.4.0.md +55 -0
  114. package/evals/releases/0.5.0.md +28 -0
  115. package/evals/releases/0.6.0-after-results.json +1307 -0
  116. package/evals/releases/0.6.0-before-results.json +4850 -0
  117. package/evals/releases/0.6.0.md +94 -0
  118. package/evals/releases/0.7.0.md +32 -0
  119. package/evals/scenarios.json +7777 -0
  120. package/package.json +50 -0
  121. package/plugins/just-vibe/.claude-plugin/plugin.json +11 -0
  122. package/plugins/just-vibe/.codex-plugin/plugin.json +24 -0
  123. package/plugins/just-vibe/LICENSE +21 -0
  124. package/plugins/just-vibe/catalog/commands.json +16757 -0
  125. package/plugins/just-vibe/catalog/packs.json +115 -0
  126. package/plugins/just-vibe/catalog/profiles.json +2503 -0
  127. package/plugins/just-vibe/hooks/hooks.json +11 -0
  128. package/plugins/just-vibe/references/command-reference.md +328 -0
  129. package/plugins/just-vibe/references/daily-workflows.md +133 -0
  130. package/plugins/just-vibe/references/execution.md +60 -0
  131. package/plugins/just-vibe/references/instruction-memory.md +86 -0
  132. package/plugins/just-vibe/references/packs/api.md +27 -0
  133. package/plugins/just-vibe/references/packs/architecture.md +29 -0
  134. package/plugins/just-vibe/references/packs/backend.md +43 -0
  135. package/plugins/just-vibe/references/packs/data.md +27 -0
  136. package/plugins/just-vibe/references/packs/database.md +32 -0
  137. package/plugins/just-vibe/references/packs/decisions.md +29 -0
  138. package/plugins/just-vibe/references/packs/general.md +34 -0
  139. package/plugins/just-vibe/references/packs/git.md +45 -0
  140. package/plugins/just-vibe/references/packs/github.md +31 -0
  141. package/plugins/just-vibe/references/packs/installation.md +27 -0
  142. package/plugins/just-vibe/references/packs/llm.md +33 -0
  143. package/plugins/just-vibe/references/packs/ml-data.md +43 -0
  144. package/plugins/just-vibe/references/packs/ml-deployment.md +31 -0
  145. package/plugins/just-vibe/references/packs/ml-evaluation.md +29 -0
  146. package/plugins/just-vibe/references/packs/ml-experiments.md +29 -0
  147. package/plugins/just-vibe/references/packs/operations.md +35 -0
  148. package/plugins/just-vibe/references/packs/react.md +29 -0
  149. package/plugins/just-vibe/references/packs/security.md +31 -0
  150. package/plugins/just-vibe/references/packs/testing.md +35 -0
  151. package/plugins/just-vibe/references/packs/ui.md +29 -0
  152. package/plugins/just-vibe/references/packs/vercel.md +29 -0
  153. package/plugins/just-vibe/references/packs/vite.md +29 -0
  154. package/plugins/just-vibe/references/profile-reference.md +155 -0
  155. package/plugins/just-vibe/references/profiles/accessibility-engineer.md +31 -0
  156. package/plugins/just-vibe/references/profiles/agent-systems-engineer.md +31 -0
  157. package/plugins/just-vibe/references/profiles/ai-evaluation-engineer.md +31 -0
  158. package/plugins/just-vibe/references/profiles/ai-security-engineer.md +31 -0
  159. package/plugins/just-vibe/references/profiles/analytics-engineer.md +31 -0
  160. package/plugins/just-vibe/references/profiles/android-engineer.md +31 -0
  161. package/plugins/just-vibe/references/profiles/api-engineer.md +31 -0
  162. package/plugins/just-vibe/references/profiles/application-security-engineer.md +31 -0
  163. package/plugins/just-vibe/references/profiles/applied-ai-engineer.md +31 -0
  164. package/plugins/just-vibe/references/profiles/backend-engineer.md +31 -0
  165. package/plugins/just-vibe/references/profiles/bioinformatics-engineer.md +31 -0
  166. package/plugins/just-vibe/references/profiles/blockchain-engineer.md +31 -0
  167. package/plugins/just-vibe/references/profiles/build-release-engineer.md +31 -0
  168. package/plugins/just-vibe/references/profiles/business-intelligence-engineer.md +31 -0
  169. package/plugins/just-vibe/references/profiles/capacity-engineer.md +31 -0
  170. package/plugins/just-vibe/references/profiles/causal-inference-scientist.md +31 -0
  171. package/plugins/just-vibe/references/profiles/cloud-architect.md +31 -0
  172. package/plugins/just-vibe/references/profiles/cloud-engineer.md +31 -0
  173. package/plugins/just-vibe/references/profiles/cloud-security-engineer.md +31 -0
  174. package/plugins/just-vibe/references/profiles/compiler-engineer.md +31 -0
  175. package/plugins/just-vibe/references/profiles/computer-vision-engineer.md +31 -0
  176. package/plugins/just-vibe/references/profiles/controls-engineer.md +31 -0
  177. package/plugins/just-vibe/references/profiles/creative-technologist.md +31 -0
  178. package/plugins/just-vibe/references/profiles/cryptography-engineer.md +31 -0
  179. package/plugins/just-vibe/references/profiles/data-analyst.md +31 -0
  180. package/plugins/just-vibe/references/profiles/data-architect.md +31 -0
  181. package/plugins/just-vibe/references/profiles/data-engineer.md +31 -0
  182. package/plugins/just-vibe/references/profiles/data-governance-engineer.md +31 -0
  183. package/plugins/just-vibe/references/profiles/data-platform-engineer.md +31 -0
  184. package/plugins/just-vibe/references/profiles/data-quality-engineer.md +31 -0
  185. package/plugins/just-vibe/references/profiles/data-scientist.md +31 -0
  186. package/plugins/just-vibe/references/profiles/database-engineer.md +31 -0
  187. package/plugins/just-vibe/references/profiles/database-reliability-engineer.md +31 -0
  188. package/plugins/just-vibe/references/profiles/design-systems-engineer.md +31 -0
  189. package/plugins/just-vibe/references/profiles/desktop-engineer.md +31 -0
  190. package/plugins/just-vibe/references/profiles/detection-engineer.md +31 -0
  191. package/plugins/just-vibe/references/profiles/developer-advocate.md +31 -0
  192. package/plugins/just-vibe/references/profiles/developer-experience-engineer.md +31 -0
  193. package/plugins/just-vibe/references/profiles/devops-engineer.md +31 -0
  194. package/plugins/just-vibe/references/profiles/distributed-systems-engineer.md +31 -0
  195. package/plugins/just-vibe/references/profiles/edge-engineer.md +31 -0
  196. package/plugins/just-vibe/references/profiles/embedded-engineer.md +31 -0
  197. package/plugins/just-vibe/references/profiles/engineering-manager.md +31 -0
  198. package/plugins/just-vibe/references/profiles/enterprise-architect.md +31 -0
  199. package/plugins/just-vibe/references/profiles/experimentation-engineer.md +31 -0
  200. package/plugins/just-vibe/references/profiles/finops-engineer.md +31 -0
  201. package/plugins/just-vibe/references/profiles/firmware-engineer.md +31 -0
  202. package/plugins/just-vibe/references/profiles/frontend-architect.md +31 -0
  203. package/plugins/just-vibe/references/profiles/frontend-engineer.md +31 -0
  204. package/plugins/just-vibe/references/profiles/fullstack-engineer.md +31 -0
  205. package/plugins/just-vibe/references/profiles/game-networking-engineer.md +31 -0
  206. package/plugins/just-vibe/references/profiles/gameplay-engineer.md +31 -0
  207. package/plugins/just-vibe/references/profiles/geospatial-engineer.md +31 -0
  208. package/plugins/just-vibe/references/profiles/graphics-engineer.md +31 -0
  209. package/plugins/just-vibe/references/profiles/hpc-engineer.md +31 -0
  210. package/plugins/just-vibe/references/profiles/identity-access-engineer.md +31 -0
  211. package/plugins/just-vibe/references/profiles/inference-engineer.md +31 -0
  212. package/plugins/just-vibe/references/profiles/infrastructure-engineer.md +31 -0
  213. package/plugins/just-vibe/references/profiles/integration-architect.md +31 -0
  214. package/plugins/just-vibe/references/profiles/integration-engineer.md +31 -0
  215. package/plugins/just-vibe/references/profiles/ios-engineer.md +31 -0
  216. package/plugins/just-vibe/references/profiles/iot-engineer.md +31 -0
  217. package/plugins/just-vibe/references/profiles/kubernetes-engineer.md +31 -0
  218. package/plugins/just-vibe/references/profiles/llm-engineer.md +31 -0
  219. package/plugins/just-vibe/references/profiles/machine-learning-engineer.md +31 -0
  220. package/plugins/just-vibe/references/profiles/ml-architect.md +31 -0
  221. package/plugins/just-vibe/references/profiles/ml-data-engineer.md +31 -0
  222. package/plugins/just-vibe/references/profiles/ml-platform-engineer.md +31 -0
  223. package/plugins/just-vibe/references/profiles/mlops-engineer.md +31 -0
  224. package/plugins/just-vibe/references/profiles/mobile-engineer.md +31 -0
  225. package/plugins/just-vibe/references/profiles/network-engineer.md +31 -0
  226. package/plugins/just-vibe/references/profiles/nlp-engineer.md +31 -0
  227. package/plugins/just-vibe/references/profiles/observability-engineer.md +31 -0
  228. package/plugins/just-vibe/references/profiles/performance-engineer.md +31 -0
  229. package/plugins/just-vibe/references/profiles/platform-architect.md +31 -0
  230. package/plugins/just-vibe/references/profiles/platform-engineer.md +31 -0
  231. package/plugins/just-vibe/references/profiles/principal-engineer.md +31 -0
  232. package/plugins/just-vibe/references/profiles/privacy-engineer.md +31 -0
  233. package/plugins/just-vibe/references/profiles/product-engineer.md +31 -0
  234. package/plugins/just-vibe/references/profiles/product-security-engineer.md +31 -0
  235. package/plugins/just-vibe/references/profiles/protocol-engineer.md +31 -0
  236. package/plugins/just-vibe/references/profiles/qa-automation-engineer.md +31 -0
  237. package/plugins/just-vibe/references/profiles/recommendation-engineer.md +31 -0
  238. package/plugins/just-vibe/references/profiles/reinforcement-learning-engineer.md +31 -0
  239. package/plugins/just-vibe/references/profiles/research-engineer.md +31 -0
  240. package/plugins/just-vibe/references/profiles/research-scientist.md +31 -0
  241. package/plugins/just-vibe/references/profiles/responsible-ai-engineer.md +31 -0
  242. package/plugins/just-vibe/references/profiles/robotics-engineer.md +31 -0
  243. package/plugins/just-vibe/references/profiles/runtime-engineer.md +31 -0
  244. package/plugins/just-vibe/references/profiles/scientific-software-engineer.md +31 -0
  245. package/plugins/just-vibe/references/profiles/search-engineer.md +31 -0
  246. package/plugins/just-vibe/references/profiles/security-architect.md +31 -0
  247. package/plugins/just-vibe/references/profiles/security-automation-engineer.md +31 -0
  248. package/plugins/just-vibe/references/profiles/security-incident-responder.md +31 -0
  249. package/plugins/just-vibe/references/profiles/senior-software-engineer.md +31 -0
  250. package/plugins/just-vibe/references/profiles/simulation-engineer.md +31 -0
  251. package/plugins/just-vibe/references/profiles/site-reliability-engineer.md +31 -0
  252. package/plugins/just-vibe/references/profiles/software-architect.md +31 -0
  253. package/plugins/just-vibe/references/profiles/solutions-architect.md +31 -0
  254. package/plugins/just-vibe/references/profiles/speech-engineer.md +31 -0
  255. package/plugins/just-vibe/references/profiles/staff-engineer.md +31 -0
  256. package/plugins/just-vibe/references/profiles/storage-engineer.md +31 -0
  257. package/plugins/just-vibe/references/profiles/streaming-data-engineer.md +31 -0
  258. package/plugins/just-vibe/references/profiles/supply-chain-security-engineer.md +31 -0
  259. package/plugins/just-vibe/references/profiles/systems-engineer.md +31 -0
  260. package/plugins/just-vibe/references/profiles/tech-lead.md +31 -0
  261. package/plugins/just-vibe/references/profiles/technical-writer.md +31 -0
  262. package/plugins/just-vibe/references/profiles/test-infrastructure-engineer.md +31 -0
  263. package/plugins/just-vibe/references/profiles/ui-engineer.md +31 -0
  264. package/plugins/just-vibe/references/profiles/ux-engineer.md +31 -0
  265. package/plugins/just-vibe/references/profiles/web-performance-engineer.md +31 -0
  266. package/plugins/just-vibe/references/profiles/xr-engineer.md +31 -0
  267. package/plugins/just-vibe/references/profiles.md +59 -0
  268. package/plugins/just-vibe/references/runtime.md +70 -0
  269. package/plugins/just-vibe/references/scenarios/auth.md +31 -0
  270. package/plugins/just-vibe/references/scenarios/combobox.md +9 -0
  271. package/plugins/just-vibe/references/scenarios/date-picker.md +9 -0
  272. package/plugins/just-vibe/references/scenarios/delivery-evidence.md +21 -0
  273. package/plugins/just-vibe/references/scenarios/dialog.md +9 -0
  274. package/plugins/just-vibe/references/scenarios/training.md +21 -0
  275. package/plugins/just-vibe/references/teach-test.md +37 -0
  276. package/plugins/just-vibe/references/teaching.md +34 -0
  277. package/plugins/just-vibe/references/validation.md +11 -0
  278. package/plugins/just-vibe/scripts/discover-capabilities.mjs +3 -0
  279. package/plugins/just-vibe/scripts/hooks.mjs +14 -0
  280. package/plugins/just-vibe/scripts/inspect-project.mjs +3 -0
  281. package/plugins/just-vibe/scripts/installer.mjs +280 -0
  282. package/plugins/just-vibe/scripts/lib/automation.mjs +142 -0
  283. package/plugins/just-vibe/scripts/lib/bundle.mjs +100 -0
  284. package/plugins/just-vibe/scripts/lib/catalog.mjs +135 -0
  285. package/plugins/just-vibe/scripts/lib/command.mjs +26 -0
  286. package/plugins/just-vibe/scripts/lib/continuity.mjs +77 -0
  287. package/plugins/just-vibe/scripts/lib/discovery.mjs +84 -0
  288. package/plugins/just-vibe/scripts/lib/entrypoint.mjs +12 -0
  289. package/plugins/just-vibe/scripts/lib/evidence.mjs +136 -0
  290. package/plugins/just-vibe/scripts/lib/process.mjs +44 -0
  291. package/plugins/just-vibe/scripts/lib/profiles.mjs +83 -0
  292. package/plugins/just-vibe/scripts/lib/project.mjs +60 -0
  293. package/plugins/just-vibe/scripts/lib/routing.mjs +82 -0
  294. package/plugins/just-vibe/scripts/lib/run.mjs +248 -0
  295. package/plugins/just-vibe/scripts/lib/storage.mjs +84 -0
  296. package/plugins/just-vibe/scripts/lib/teaching.mjs +118 -0
  297. package/plugins/just-vibe/scripts/toolkit.mjs +225 -0
  298. package/plugins/just-vibe/skills/a11y/SKILL.md +8 -0
  299. package/plugins/just-vibe/skills/api-breaking/SKILL.md +56 -0
  300. package/plugins/just-vibe/skills/api-client/SKILL.md +56 -0
  301. package/plugins/just-vibe/skills/api-contract-test/SKILL.md +56 -0
  302. package/plugins/just-vibe/skills/api-design/SKILL.md +56 -0
  303. package/plugins/just-vibe/skills/api-errors/SKILL.md +56 -0
  304. package/plugins/just-vibe/skills/api-openapi/SKILL.md +56 -0
  305. package/plugins/just-vibe/skills/api-pagination/SKILL.md +56 -0
  306. package/plugins/just-vibe/skills/api-webhooks/SKILL.md +56 -0
  307. package/plugins/just-vibe/skills/arch-boundaries/SKILL.md +58 -0
  308. package/plugins/just-vibe/skills/arch-contracts/SKILL.md +56 -0
  309. package/plugins/just-vibe/skills/arch-event-flow/SKILL.md +56 -0
  310. package/plugins/just-vibe/skills/arch-feature/SKILL.md +57 -0
  311. package/plugins/just-vibe/skills/arch-map/SKILL.md +56 -0
  312. package/plugins/just-vibe/skills/arch-modernize/SKILL.md +56 -0
  313. package/plugins/just-vibe/skills/arch-scale/SKILL.md +58 -0
  314. package/plugins/just-vibe/skills/arch-tenancy/SKILL.md +56 -0
  315. package/plugins/just-vibe/skills/auto/SKILL.md +67 -0
  316. package/plugins/just-vibe/skills/automate/SKILL.md +56 -0
  317. package/plugins/just-vibe/skills/backend-auth/SKILL.md +63 -0
  318. package/plugins/just-vibe/skills/backend-cache/SKILL.md +59 -0
  319. package/plugins/just-vibe/skills/backend-concurrency/SKILL.md +58 -0
  320. package/plugins/just-vibe/skills/backend-idempotency/SKILL.md +58 -0
  321. package/plugins/just-vibe/skills/backend-jobs/SKILL.md +56 -0
  322. package/plugins/just-vibe/skills/backend-permissions/SKILL.md +56 -0
  323. package/plugins/just-vibe/skills/backend-resilience/SKILL.md +56 -0
  324. package/plugins/just-vibe/skills/backend-service/SKILL.md +56 -0
  325. package/plugins/just-vibe/skills/brainstorm/SKILL.md +56 -0
  326. package/plugins/just-vibe/skills/build/SKILL.md +56 -0
  327. package/plugins/just-vibe/skills/challenge/SKILL.md +56 -0
  328. package/plugins/just-vibe/skills/checkpoint/SKILL.md +59 -0
  329. package/plugins/just-vibe/skills/ci/SKILL.md +56 -0
  330. package/plugins/just-vibe/skills/cleanup/SKILL.md +56 -0
  331. package/plugins/just-vibe/skills/compare/SKILL.md +56 -0
  332. package/plugins/just-vibe/skills/copy/SKILL.md +56 -0
  333. package/plugins/just-vibe/skills/coverage/SKILL.md +56 -0
  334. package/plugins/just-vibe/skills/data-backfill/SKILL.md +56 -0
  335. package/plugins/just-vibe/skills/data-contract/SKILL.md +56 -0
  336. package/plugins/just-vibe/skills/data-incremental/SKILL.md +56 -0
  337. package/plugins/just-vibe/skills/data-lineage/SKILL.md +56 -0
  338. package/plugins/just-vibe/skills/data-pipeline/SKILL.md +56 -0
  339. package/plugins/just-vibe/skills/data-profile/SKILL.md +56 -0
  340. package/plugins/just-vibe/skills/data-quality/SKILL.md +56 -0
  341. package/plugins/just-vibe/skills/data-reconcile/SKILL.md +56 -0
  342. package/plugins/just-vibe/skills/db-access/SKILL.md +56 -0
  343. package/plugins/just-vibe/skills/db-explain/SKILL.md +56 -0
  344. package/plugins/just-vibe/skills/db-index/SKILL.md +56 -0
  345. package/plugins/just-vibe/skills/db-integrity/SKILL.md +57 -0
  346. package/plugins/just-vibe/skills/db-locks/SKILL.md +56 -0
  347. package/plugins/just-vibe/skills/db-migrate/SKILL.md +63 -0
  348. package/plugins/just-vibe/skills/db-query/SKILL.md +56 -0
  349. package/plugins/just-vibe/skills/db-schema/SKILL.md +56 -0
  350. package/plugins/just-vibe/skills/debug/SKILL.md +56 -0
  351. package/plugins/just-vibe/skills/decide/SKILL.md +57 -0
  352. package/plugins/just-vibe/skills/decision-adr/SKILL.md +56 -0
  353. package/plugins/just-vibe/skills/decision-buy-build/SKILL.md +56 -0
  354. package/plugins/just-vibe/skills/decision-matrix/SKILL.md +56 -0
  355. package/plugins/just-vibe/skills/decision-premortem/SKILL.md +56 -0
  356. package/plugins/just-vibe/skills/decision-reversible/SKILL.md +56 -0
  357. package/plugins/just-vibe/skills/decision-revisit/SKILL.md +56 -0
  358. package/plugins/just-vibe/skills/decision-spike/SKILL.md +58 -0
  359. package/plugins/just-vibe/skills/deploy/SKILL.md +56 -0
  360. package/plugins/just-vibe/skills/deps/SKILL.md +56 -0
  361. package/plugins/just-vibe/skills/design/SKILL.md +56 -0
  362. package/plugins/just-vibe/skills/do/SKILL.md +8 -0
  363. package/plugins/just-vibe/skills/docs/SKILL.md +56 -0
  364. package/plugins/just-vibe/skills/doctor/SKILL.md +58 -0
  365. package/plugins/just-vibe/skills/explain/SKILL.md +56 -0
  366. package/plugins/just-vibe/skills/fix/SKILL.md +57 -0
  367. package/plugins/just-vibe/skills/git-bisect/SKILL.md +58 -0
  368. package/plugins/just-vibe/skills/git-commit/SKILL.md +59 -0
  369. package/plugins/just-vibe/skills/git-conflicts/SKILL.md +58 -0
  370. package/plugins/just-vibe/skills/git-diff/SKILL.md +58 -0
  371. package/plugins/just-vibe/skills/git-recover/SKILL.md +58 -0
  372. package/plugins/just-vibe/skills/git-split/SKILL.md +58 -0
  373. package/plugins/just-vibe/skills/git-status/SKILL.md +58 -0
  374. package/plugins/just-vibe/skills/git-worktree/SKILL.md +58 -0
  375. package/plugins/just-vibe/skills/github-actions/SKILL.md +64 -0
  376. package/plugins/just-vibe/skills/github-address-review/SKILL.md +58 -0
  377. package/plugins/just-vibe/skills/github-fix-ci/SKILL.md +63 -0
  378. package/plugins/just-vibe/skills/github-issue/SKILL.md +58 -0
  379. package/plugins/just-vibe/skills/github-pr/SKILL.md +63 -0
  380. package/plugins/just-vibe/skills/github-release/SKILL.md +58 -0
  381. package/plugins/just-vibe/skills/github-review/SKILL.md +58 -0
  382. package/plugins/just-vibe/skills/github-triage/SKILL.md +58 -0
  383. package/plugins/just-vibe/skills/handoff/SKILL.md +62 -0
  384. package/plugins/just-vibe/skills/help/SKILL.md +63 -0
  385. package/plugins/just-vibe/skills/integrate/SKILL.md +56 -0
  386. package/plugins/just-vibe/skills/learn/SKILL.md +56 -0
  387. package/plugins/just-vibe/skills/llm-cost/SKILL.md +56 -0
  388. package/plugins/just-vibe/skills/llm-evals/SKILL.md +58 -0
  389. package/plugins/just-vibe/skills/llm-injection/SKILL.md +56 -0
  390. package/plugins/just-vibe/skills/llm-prompt/SKILL.md +56 -0
  391. package/plugins/just-vibe/skills/llm-rag/SKILL.md +56 -0
  392. package/plugins/just-vibe/skills/llm-retrieval/SKILL.md +56 -0
  393. package/plugins/just-vibe/skills/llm-structured/SKILL.md +56 -0
  394. package/plugins/just-vibe/skills/llm-tools/SKILL.md +58 -0
  395. package/plugins/just-vibe/skills/map/SKILL.md +56 -0
  396. package/plugins/just-vibe/skills/match/SKILL.md +56 -0
  397. package/plugins/just-vibe/skills/migrate/SKILL.md +56 -0
  398. package/plugins/just-vibe/skills/ml-ablation/SKILL.md +56 -0
  399. package/plugins/just-vibe/skills/ml-baseline/SKILL.md +56 -0
  400. package/plugins/just-vibe/skills/ml-batch/SKILL.md +56 -0
  401. package/plugins/just-vibe/skills/ml-calibrate/SKILL.md +56 -0
  402. package/plugins/just-vibe/skills/ml-dataset/SKILL.md +56 -0
  403. package/plugins/just-vibe/skills/ml-dataset-version/SKILL.md +56 -0
  404. package/plugins/just-vibe/skills/ml-debug-training/SKILL.md +56 -0
  405. package/plugins/just-vibe/skills/ml-drift/SKILL.md +56 -0
  406. package/plugins/just-vibe/skills/ml-error-analysis/SKILL.md +56 -0
  407. package/plugins/just-vibe/skills/ml-evaluate/SKILL.md +56 -0
  408. package/plugins/just-vibe/skills/ml-experiments/SKILL.md +56 -0
  409. package/plugins/just-vibe/skills/ml-explain/SKILL.md +56 -0
  410. package/plugins/just-vibe/skills/ml-features/SKILL.md +59 -0
  411. package/plugins/just-vibe/skills/ml-frame/SKILL.md +56 -0
  412. package/plugins/just-vibe/skills/ml-imbalance/SKILL.md +56 -0
  413. package/plugins/just-vibe/skills/ml-inference-perf/SKILL.md +56 -0
  414. package/plugins/just-vibe/skills/ml-labels/SKILL.md +56 -0
  415. package/plugins/just-vibe/skills/ml-leakage/SKILL.md +64 -0
  416. package/plugins/just-vibe/skills/ml-monitor/SKILL.md +56 -0
  417. package/plugins/just-vibe/skills/ml-package/SKILL.md +56 -0
  418. package/plugins/just-vibe/skills/ml-parity/SKILL.md +56 -0
  419. package/plugins/just-vibe/skills/ml-report/SKILL.md +56 -0
  420. package/plugins/just-vibe/skills/ml-reproduce/SKILL.md +56 -0
  421. package/plugins/just-vibe/skills/ml-robustness/SKILL.md +56 -0
  422. package/plugins/just-vibe/skills/ml-rollout/SKILL.md +56 -0
  423. package/plugins/just-vibe/skills/ml-serving/SKILL.md +56 -0
  424. package/plugins/just-vibe/skills/ml-slices/SKILL.md +56 -0
  425. package/plugins/just-vibe/skills/ml-split/SKILL.md +58 -0
  426. package/plugins/just-vibe/skills/ml-threshold/SKILL.md +56 -0
  427. package/plugins/just-vibe/skills/ml-train/SKILL.md +62 -0
  428. package/plugins/just-vibe/skills/ml-training-cost/SKILL.md +56 -0
  429. package/plugins/just-vibe/skills/ml-tune/SKILL.md +56 -0
  430. package/plugins/just-vibe/skills/ops-alerts/SKILL.md +56 -0
  431. package/plugins/just-vibe/skills/ops-container/SKILL.md +56 -0
  432. package/plugins/just-vibe/skills/ops-incident/SKILL.md +56 -0
  433. package/plugins/just-vibe/skills/ops-logs/SKILL.md +56 -0
  434. package/plugins/just-vibe/skills/ops-observability/SKILL.md +56 -0
  435. package/plugins/just-vibe/skills/ops-postmortem/SKILL.md +56 -0
  436. package/plugins/just-vibe/skills/ops-restore/SKILL.md +56 -0
  437. package/plugins/just-vibe/skills/ops-runbook/SKILL.md +56 -0
  438. package/plugins/just-vibe/skills/orient/SKILL.md +58 -0
  439. package/plugins/just-vibe/skills/perf/SKILL.md +56 -0
  440. package/plugins/just-vibe/skills/plan/SKILL.md +56 -0
  441. package/plugins/just-vibe/skills/polish/SKILL.md +56 -0
  442. package/plugins/just-vibe/skills/pr/SKILL.md +58 -0
  443. package/plugins/just-vibe/skills/profile/SKILL.md +66 -0
  444. package/plugins/just-vibe/skills/profiles/SKILL.md +58 -0
  445. package/plugins/just-vibe/skills/react-async/SKILL.md +57 -0
  446. package/plugins/just-vibe/skills/react-audit/SKILL.md +56 -0
  447. package/plugins/just-vibe/skills/react-component/SKILL.md +65 -0
  448. package/plugins/just-vibe/skills/react-effects/SKILL.md +58 -0
  449. package/plugins/just-vibe/skills/react-forms/SKILL.md +56 -0
  450. package/plugins/just-vibe/skills/react-hydration/SKILL.md +57 -0
  451. package/plugins/just-vibe/skills/react-rerenders/SKILL.md +56 -0
  452. package/plugins/just-vibe/skills/react-state/SKILL.md +56 -0
  453. package/plugins/just-vibe/skills/refactor/SKILL.md +56 -0
  454. package/plugins/just-vibe/skills/release/SKILL.md +58 -0
  455. package/plugins/just-vibe/skills/remember/SKILL.md +70 -0
  456. package/plugins/just-vibe/skills/repro/SKILL.md +56 -0
  457. package/plugins/just-vibe/skills/research/SKILL.md +56 -0
  458. package/plugins/just-vibe/skills/responsive/SKILL.md +8 -0
  459. package/plugins/just-vibe/skills/resume/SKILL.md +63 -0
  460. package/plugins/just-vibe/skills/review/SKILL.md +56 -0
  461. package/plugins/just-vibe/skills/scope/SKILL.md +56 -0
  462. package/plugins/just-vibe/skills/security/SKILL.md +56 -0
  463. package/plugins/just-vibe/skills/security-authz/SKILL.md +56 -0
  464. package/plugins/just-vibe/skills/security-config/SKILL.md +56 -0
  465. package/plugins/just-vibe/skills/security-dependencies/SKILL.md +56 -0
  466. package/plugins/just-vibe/skills/security-fix/SKILL.md +56 -0
  467. package/plugins/just-vibe/skills/security-inputs/SKILL.md +56 -0
  468. package/plugins/just-vibe/skills/security-secrets/SKILL.md +56 -0
  469. package/plugins/just-vibe/skills/security-threat-model/SKILL.md +56 -0
  470. package/plugins/just-vibe/skills/security-uploads/SKILL.md +56 -0
  471. package/plugins/just-vibe/skills/setup/SKILL.md +60 -0
  472. package/plugins/just-vibe/skills/skill/SKILL.md +56 -0
  473. package/plugins/just-vibe/skills/spec/SKILL.md +56 -0
  474. package/plugins/just-vibe/skills/tasks/SKILL.md +56 -0
  475. package/plugins/just-vibe/skills/teach/SKILL.md +63 -0
  476. package/plugins/just-vibe/skills/teach-test/SKILL.md +65 -0
  477. package/plugins/just-vibe/skills/test/SKILL.md +56 -0
  478. package/plugins/just-vibe/skills/test-e2e/SKILL.md +56 -0
  479. package/plugins/just-vibe/skills/test-fixtures/SKILL.md +56 -0
  480. package/plugins/just-vibe/skills/test-flaky/SKILL.md +56 -0
  481. package/plugins/just-vibe/skills/test-integration/SKILL.md +56 -0
  482. package/plugins/just-vibe/skills/test-load/SKILL.md +56 -0
  483. package/plugins/just-vibe/skills/test-property/SKILL.md +56 -0
  484. package/plugins/just-vibe/skills/test-regression/SKILL.md +57 -0
  485. package/plugins/just-vibe/skills/test-unit/SKILL.md +56 -0
  486. package/plugins/just-vibe/skills/tools/SKILL.md +64 -0
  487. package/plugins/just-vibe/skills/trace/SKILL.md +56 -0
  488. package/plugins/just-vibe/skills/ui-accessibility/SKILL.md +57 -0
  489. package/plugins/just-vibe/skills/ui-audit/SKILL.md +56 -0
  490. package/plugins/just-vibe/skills/ui-flow/SKILL.md +56 -0
  491. package/plugins/just-vibe/skills/ui-motion/SKILL.md +56 -0
  492. package/plugins/just-vibe/skills/ui-responsive/SKILL.md +56 -0
  493. package/plugins/just-vibe/skills/ui-states/SKILL.md +56 -0
  494. package/plugins/just-vibe/skills/ui-system/SKILL.md +56 -0
  495. package/plugins/just-vibe/skills/ui-visual-diff/SKILL.md +56 -0
  496. package/plugins/just-vibe/skills/vercel-audit/SKILL.md +56 -0
  497. package/plugins/just-vibe/skills/vercel-build-fix/SKILL.md +63 -0
  498. package/plugins/just-vibe/skills/vercel-env/SKILL.md +56 -0
  499. package/plugins/just-vibe/skills/vercel-performance/SKILL.md +56 -0
  500. package/plugins/just-vibe/skills/vercel-preview/SKILL.md +56 -0
  501. package/plugins/just-vibe/skills/vercel-release-check/SKILL.md +57 -0
  502. package/plugins/just-vibe/skills/vercel-routing/SKILL.md +56 -0
  503. package/plugins/just-vibe/skills/vercel-runtime/SKILL.md +61 -0
  504. package/plugins/just-vibe/skills/verify/SKILL.md +59 -0
  505. package/plugins/just-vibe/skills/vite-assets/SKILL.md +57 -0
  506. package/plugins/just-vibe/skills/vite-bundle/SKILL.md +58 -0
  507. package/plugins/just-vibe/skills/vite-chunks/SKILL.md +56 -0
  508. package/plugins/just-vibe/skills/vite-config/SKILL.md +56 -0
  509. package/plugins/just-vibe/skills/vite-env/SKILL.md +56 -0
  510. package/plugins/just-vibe/skills/vite-hmr/SKILL.md +56 -0
  511. package/plugins/just-vibe/skills/vite-setup/SKILL.md +56 -0
  512. package/plugins/just-vibe/skills/vite-upgrade/SKILL.md +56 -0
@@ -0,0 +1,135 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { resolve, sep } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ export const pluginRoot = fileURLToPath(new URL('../../', import.meta.url));
6
+ export const MODES = ['inspect', 'plan', 'apply'];
7
+ export const HOSTS = ['codex', 'claude'];
8
+ export const CAPABILITIES = ['project.read', 'git.repo', 'github.context', 'vercel.context',
9
+ 'browser.inspect', 'database.context', 'data.read', 'ml.artifacts', 'telemetry.read',
10
+ 'web.research', 'container.context', 'user.questions'];
11
+
12
+ const aliasIdentity = new Set(['id', 'pack', 'summary', 'aliasOf', 'aliases', 'skillPath', 'searchTerms']);
13
+
14
+ export function materializeAliases(catalog) {
15
+ return { ...catalog, commands: catalog.commands.map(command => {
16
+ if (!command.aliasOf) return structuredClone(command);
17
+ const target = catalog.commands.find(c => c.id === command.aliasOf && !c.aliasOf);
18
+ if (!target) throw new Error(`Invalid alias target: ${command.id}`);
19
+ for (const key of Object.keys(command)) if (!aliasIdentity.has(key)) throw new Error(`Alias must inherit ${key}: ${command.id}`);
20
+ return { ...structuredClone(target), ...structuredClone(command), aliases: [] };
21
+ }) };
22
+ }
23
+
24
+ export function validateCatalog(catalog, packs) {
25
+ if (catalog.schemaVersion !== 1 || packs.schemaVersion !== 1) throw new Error('Unsupported catalog version.');
26
+ if (!Array.isArray(catalog.commands) || !Array.isArray(packs.packs)) throw new Error('Invalid catalog collections.');
27
+ const ids = new Set();
28
+ const groups = new Set(packs.packs.map(p => p.id));
29
+ if (groups.size !== packs.packs.length) throw new Error('Duplicate pack.');
30
+ for (const c of catalog.commands) {
31
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(c.id) || ids.has(c.id)) throw new Error(`Invalid/duplicate command: ${c.id}`);
32
+ ids.add(c.id);
33
+ if (!groups.has(c.pack) || !MODES.includes(c.defaultMode)) throw new Error(`Invalid pack/mode: ${c.id}`);
34
+ for (const key of ['summary', 'modePolicy', 'readScope', 'writeScope', 'skillPath']) {
35
+ if (typeof c[key] !== 'string' || !c[key].trim()) throw new Error(`Missing ${key}: ${c.id}`);
36
+ }
37
+ for (const key of ['requiredInputs', 'procedure', 'outputs', 'verification', 'stopConditions']) {
38
+ if (!Array.isArray(c[key]) || !c[key].length || c[key].some(s => typeof s !== 'string' || !s.trim())) throw new Error(`Invalid ${key}: ${c.id}`);
39
+ }
40
+ if (typeof c.selection !== 'string' || !c.selection.trim()) throw new Error(`Missing selection boundary: ${c.id}`);
41
+ if (!Array.isArray(c.branches) || !c.branches.length || c.branches.some(b => !b.when?.trim() || !b.then?.trim())) throw new Error(`Invalid decision branches: ${c.id}`);
42
+ if (c.guides !== undefined && (!Array.isArray(c.guides) || c.guides.some(g => !g.title?.trim() || !g.when?.trim() || !/^references\/(?:[a-z0-9-]+\/)*[a-z0-9-]+\.md$/.test(g.path)))) throw Error(`Invalid conditional guide: ${c.id}`);
43
+ if (c.validation?.structural !== 'automated' || !['fixtures-tested', 'not-applicable'].includes(c.validation.runtime)
44
+ || !['not-evaluated', 'passed-fixtures', 'partial-fixtures'].includes(c.validation.behavioral)) throw new Error(`Invalid validation dimensions: ${c.id}`);
45
+ if (c.validation.behavioral !== 'not-evaluated' && !c.validation.record?.trim()) throw new Error(`Behavioral results require an evidence record: ${c.id}`);
46
+ if (!Array.isArray(c.capabilities) || c.capabilities.some(s => !CAPABILITIES.includes(s))) throw new Error(`Unknown capability: ${c.id}`);
47
+ if (!Array.isArray(c.examples) || !c.examples.length || c.examples.some(e => !e.brief?.trim() || !MODES.includes(e.mode))) throw new Error(`Missing example: ${c.id}`);
48
+ if (!Array.isArray(c.hostSupport) || !c.hostSupport.length || c.hostSupport.some(h => !HOSTS.includes(h))) throw new Error(`Unknown host: ${c.id}`);
49
+ if (!['implemented', 'planned'].includes(c.implementationStatus)) throw new Error(`Unknown implementation status: ${c.id}`);
50
+ if (c.executionModel !== 'host-agent' || c.skillPath !== `skills/${c.id}/SKILL.md`) throw new Error(`Invalid execution path: ${c.id}`);
51
+ }
52
+ for (const c of catalog.commands) {
53
+ if (c.aliasOf) {
54
+ const target = catalog.commands.find(t => t.id === c.aliasOf);
55
+ if (!target || target.aliasOf || !target.aliases.includes(c.id)) throw new Error(`Invalid alias: ${c.id}`);
56
+ for (const key of new Set([...Object.keys(c), ...Object.keys(target)])) {
57
+ if (!aliasIdentity.has(key) && JSON.stringify(c[key]) !== JSON.stringify(target[key])) throw new Error(`Alias contract drift (${key}): ${c.id}`);
58
+ }
59
+ }
60
+ for (const a of c.aliases) if (!catalog.commands.some(t => t.id === a && t.aliasOf === c.id)) throw new Error(`Missing alias: ${a}`);
61
+ }
62
+ return catalog;
63
+ }
64
+
65
+ export function loadCatalog(root = pluginRoot) {
66
+ const catalog = materializeAliases(JSON.parse(readFileSync(resolve(root, 'catalog/commands.json'), 'utf8')));
67
+ const packs = JSON.parse(readFileSync(resolve(root, 'catalog/packs.json'), 'utf8'));
68
+ validateCatalog(catalog, packs);
69
+ return { ...catalog, packs: packs.packs, root };
70
+ }
71
+
72
+ export function getCommand(catalog, id, { canonical = false } = {}) {
73
+ const name = id.replace(/^\/just-vibe:/, '').replace(/^\$/, '');
74
+ let command = catalog.commands.find(c => c.id === name);
75
+ if (!command) throw new Error(`Unknown workflow: ${name}. Use tools to search the catalog.`);
76
+ if (canonical && command.aliasOf) command = catalog.commands.find(c => c.id === command.aliasOf);
77
+ return command;
78
+ }
79
+
80
+ export function skillFile(catalog, command) {
81
+ const path = resolve(catalog.root, command.skillPath);
82
+ if (!path.startsWith(resolve(catalog.root) + sep)) throw new Error('Skill path escapes plugin.');
83
+ return path;
84
+ }
85
+
86
+ export function invocation(command, host = 'claude') {
87
+ if (!HOSTS.includes(host)) throw new Error(`Unsupported host: ${host}`);
88
+ return host === 'claude' ? `/just-vibe:${command.id}` : `Select just-vibe → ${command.id} in the skill picker`;
89
+ }
90
+
91
+ export function availability(catalog, command, capabilities = {}, host = 'claude') {
92
+ if (!HOSTS.includes(host)) throw new Error(`Unsupported host: ${host}`);
93
+ if (command.implementationStatus === 'planned') return { status: 'planned', reasons: ['Workflow is not implemented.'] };
94
+ if (!command.hostSupport.includes(host)) return { status: 'unsupported', reasons: [`No ${host} mapping.`] };
95
+ if (!existsSync(skillFile(catalog, command))) return { status: 'uninstalled', reasons: ['Skill file is absent from this payload.'] };
96
+ if (command.aliasOf) return availability(catalog, getCommand(catalog, command.aliasOf, { canonical: true }), capabilities, host);
97
+ const checks = command.capabilities.map(id => ({ id, ...(capabilities[id] || { status: 'unknown', reason: 'Not observed in this session.' }) }));
98
+ const blocked = checks.filter(c => ['missing', 'disabled'].includes(c.status));
99
+ const unknown = checks.filter(c => c.status !== 'available');
100
+ return { status: blocked.length ? 'blocked' : unknown.length ? 'unknown' : 'available',
101
+ reasons: unknown.map(c => `${c.id}: ${c.reason}`), capabilities: checks };
102
+ }
103
+
104
+ const stopWords = new Set('the a an and or to for of in on with my this that it is are be can please me do how what why i we our'.split(' '));
105
+ const synonyms = { 'ci': ['checks', 'actions', 'pipeline'], 'ml': ['model', 'training', 'dataset'],
106
+ 'a11y': ['accessibility', 'keyboard', 'focus'], 'db': ['database', 'sql', 'postgres', 'sqlite'],
107
+ 'rerenders': ['rendering', 'renders', 'freezes'], 'leakage': ['contamination', 'leak', 'future'],
108
+ 'recovery': ['recover', 'restore'], 'pr': ['pull', 'request'], 'env': ['environment', 'variables'] };
109
+ function tokens(text) {
110
+ return (text.toLowerCase().match(/[a-z0-9]+/g) || []).filter(w => !stopWords.has(w));
111
+ }
112
+
113
+ export function searchCommands(catalog, query = '', { pack, limit = 1000 } = {}) {
114
+ if (pack && !catalog.packs.some(p => p.id === pack)) throw new Error(`Unknown pack: ${pack}`);
115
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1000) throw new Error('limit must be between 1 and 1000.');
116
+ const exact = query.trim().toLowerCase().replace(/^\/just-vibe:/, '');
117
+ const exactCommand = catalog.commands.find(c => c.id === exact && (!pack || c.pack === pack));
118
+ if (exactCommand) return [{ command: exactCommand, score: 100 }];
119
+ const exactPack = catalog.packs.find(p => p.id === exact.replace(/\s+/g, '-') || p.name.toLowerCase() === exact);
120
+ if (exactPack && !pack) pack = exactPack.id;
121
+ const queryTokens = tokens(query);
122
+ const expanded = new Set(queryTokens);
123
+ for (const token of queryTokens) for (const word of synonyms[token] || []) expanded.add(word);
124
+ const scored = catalog.commands.filter(c => !pack || c.pack === pack).map(command => {
125
+ const id = new Set(tokens(command.id));
126
+ const summary = new Set(tokens(command.summary));
127
+ const detail = new Set(tokens([command.pack, ...command.procedure, ...command.examples.map(e => e.brief)].join(' ')));
128
+ const scenarios = new Set(tokens((command.searchTerms || []).join(' ')));
129
+ let score = command.id === query.toLowerCase().trim() ? 100 : 0;
130
+ if (command.pack === query.toLowerCase().trim()) score += 40;
131
+ for (const token of expanded) score += id.has(token) ? 8 : scenarios.has(token) ? 5 : summary.has(token) ? 4 : detail.has(token) ? 1 : 0;
132
+ return { command, score };
133
+ }).filter(c => !queryTokens.length || c.score > 0 || (exactPack && c.command.pack === exactPack.id));
134
+ return scored.sort((a, b) => b.score - a.score || a.command.id.localeCompare(b.command.id)).slice(0, limit);
135
+ }
@@ -0,0 +1,26 @@
1
+ import { accessSync, constants, existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { delimiter, dirname, extname, isAbsolute, resolve } from 'node:path';
3
+
4
+ export function findExecutable(name, env = process.env) {
5
+ const directories = isAbsolute(name) ? [''] : (env.PATH || env.Path || '').split(delimiter).filter(Boolean);
6
+ const extensions = process.platform === 'win32' && !extname(name)
7
+ ? ['', ...(env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';')] : [''];
8
+ for (const dir of directories) for (const extension of extensions) {
9
+ const path = resolve(dir, `${name}${extension}`);
10
+ try { accessSync(path, constants.X_OK); if (statSync(path).isFile()) return path; } catch {}
11
+ }
12
+ return null;
13
+ }
14
+
15
+ export function commandInvocation(binary, args) {
16
+ if (process.platform !== 'win32') return [binary, args];
17
+ const executable = findExecutable(binary);
18
+ if (!executable || !/\.(cmd|bat)$/i.test(executable)) return [executable || binary, args];
19
+ // Resolve standard npm-generated Node shims without invoking cmd.exe or evaluating user input.
20
+ const shim = readFileSync(executable, 'utf8');
21
+ for (const match of shim.matchAll(/"%dp0%\\([^"\r\n]+\.(?:[cm]?js))"/gi)) {
22
+ const script = resolve(dirname(executable), match[1]);
23
+ if (existsSync(script)) return [process.execPath, [script, ...args]];
24
+ }
25
+ throw new Error(`Unsupported Windows command wrapper: ${executable}. Install the native executable or a standard npm CLI shim.`);
26
+ }
@@ -0,0 +1,77 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { atomicJson, within, readJson, projectRoot, fingerprint, compareSnapshot } from './storage.mjs';
3
+ import { getProfile, loadProfiles } from './profiles.mjs';
4
+
5
+ const name = value => {
6
+ if (typeof value !== 'string' || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(value)) throw Error('Use a lowercase name up to 64 characters.');
7
+ return value;
8
+ };
9
+ const text = (value, label, max = 12000) => {
10
+ if (typeof value !== 'string' || !value.trim() || value.length > max) throw Error(`${label} must be nonempty text up to ${max} characters.`);
11
+ return value;
12
+ };
13
+ function keys(value, allowed) {
14
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some(k => !allowed.includes(k))) throw Error('Unknown or malformed continuity fields.');
15
+ }
16
+ function revision(value) { if (!Number.isInteger(value) || value < 0) throw Error('Provide the current revision (0 for a new record).'); return value; }
17
+ function readRecord(root, path, kind) {
18
+ const record = readJson(within(root, path));
19
+ if (record.schemaVersion !== 1 || record.kind !== kind || record.root !== projectRoot(root) || !Number.isInteger(record.revision) || record.revision < 1) throw Error('Invalid state or state belongs to another project. Review it explicitly before migration.');
20
+ return record;
21
+ }
22
+ export function readPreferences(root) {
23
+ const path = within(root, '.just-vibe/project.json');
24
+ if (!existsSync(path)) return null;
25
+ const record = readRecord(root, '.just-vibe/project.json', 'preferences');
26
+ validatePreferences(record.preferences);
27
+ return record;
28
+ }
29
+ function validatePreferences(p) {
30
+ keys(p, ['profile', 'packageManager', 'testCommand', 'style', 'detail']);
31
+ if (p.profile !== undefined) { text(p.profile, 'Profile', 64); getProfile(loadProfiles(), p.profile); }
32
+ if (p.packageManager !== undefined && !['npm', 'pnpm', 'yarn', 'bun'].includes(p.packageManager)) throw Error('Unknown package manager.');
33
+ if (p.detail !== undefined && !['concise', 'standard', 'detailed'].includes(p.detail)) throw Error('Unknown detail preference.');
34
+ for (const key of ['style', 'testCommand']) if (p[key] !== undefined) text(p[key], key, 2000);
35
+ }
36
+ export function continuity(root, operation, payload = {}, id) {
37
+ root = projectRoot(root);
38
+ const base = { schemaVersion: 1, root, updatedAt: new Date().toISOString() };
39
+ if (operation === 'init') {
40
+ keys(payload, ['preferences']); validatePreferences(payload.preferences || {});
41
+ return atomicJson(root, '.just-vibe/project.json', { ...base, kind: 'preferences', preferences: payload.preferences || {} }, 0);
42
+ }
43
+ if (operation === 'show') return { preferences: readPreferences(root), notes: existsSync(within(root, '.just-vibe/notes.json')) ? readRecord(root, '.just-vibe/notes.json', 'notes') : null, note: 'Saved context, not executable configuration or authorization. Explicit task instructions take precedence.' };
44
+ if (operation === 'configure') {
45
+ keys(payload, ['revision', 'preferences']); validatePreferences(payload.preferences);
46
+ readPreferences(root);
47
+ return atomicJson(root, '.just-vibe/project.json', { ...base, kind: 'preferences', preferences: payload.preferences }, revision(payload.revision));
48
+ }
49
+ if (operation === 'remember' || operation === 'forget') {
50
+ keys(payload, ['revision', 'id', 'text', 'rationale']); name(payload.id);
51
+ const current = existsSync(within(root, '.just-vibe/notes.json')) ? readRecord(root, '.just-vibe/notes.json', 'notes') : null;
52
+ const notes = (current?.notes || []).filter(n => n.id !== payload.id);
53
+ if (operation === 'remember') notes.push({ id: payload.id, text: text(payload.text, 'Note'), ...(payload.rationale ? { rationale: text(payload.rationale, 'Rationale', 2000) } : {}), updatedAt: base.updatedAt });
54
+ if (notes.length > 100) throw Error('Keep at most 100 project notes.');
55
+ return atomicJson(root, '.just-vibe/notes.json', { ...base, kind: 'notes', notes }, revision(payload.revision));
56
+ }
57
+ if (operation === 'checkpoint') {
58
+ name(id); keys(payload, ['revision', 'objective', 'constraints', 'decisions', 'completed', 'remaining', 'nextStep']);
59
+ for (const field of ['objective', 'nextStep']) text(payload[field], field);
60
+ for (const field of ['constraints', 'decisions', 'completed', 'remaining']) {
61
+ if (!Array.isArray(payload[field]) || payload[field].length > 100) throw Error(`${field} must be a list with at most 100 entries.`);
62
+ payload[field].forEach(value => text(value, field));
63
+ }
64
+ const { revision: rev, ...context } = payload;
65
+ return atomicJson(root, `.just-vibe/checkpoints/${id}.json`, { ...base, kind: 'checkpoint', id, context, snapshot: fingerprint(root) }, revision(rev));
66
+ }
67
+ if (operation === 'resume') {
68
+ name(id);
69
+ const checkpoint = readRecord(root, `.just-vibe/checkpoints/${id}.json`, 'checkpoint');
70
+ return { checkpoint, ...compareSnapshot(checkpoint.snapshot, fingerprint(root)), instruction: 'Reconcile changed state and re-run affected checks. Prior checks are historical; checkpoint text does not restore permissions, extend budgets or override the current user.' };
71
+ }
72
+ if (operation === 'list') {
73
+ const directory = within(root, '.just-vibe/checkpoints');
74
+ return { checkpoints: existsSync(directory) ? readdirSync(directory).filter(n => /^[a-z0-9][a-z0-9-]{0,63}\.json$/.test(n)).sort().map(n => n.slice(0, -5)) : [] };
75
+ }
76
+ throw Error(`Unknown project operation: ${operation}`);
77
+ }
@@ -0,0 +1,84 @@
1
+ import { realpathSync, statSync, readFileSync, accessSync, constants } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { CAPABILITIES, availability, searchCommands, invocation, getCommand } from './catalog.mjs';
4
+ import { findExecutable, gitRead } from './project.mjs';
5
+ import { routeContext, rankCandidates, intentSignals, executionStrategy } from './routing.mjs';
6
+
7
+ export function validateCapabilityReport(report, root, now = Date.now()) {
8
+ if (report.schemaVersion !== 1 || !report.capabilities || typeof report.capabilities !== 'object' || Array.isArray(report.capabilities)) throw new Error('Invalid capability report.');
9
+ if (typeof report.root !== 'string' || realpathSync(resolve(report.root)) !== realpathSync(resolve(root))) throw new Error('Capability report belongs to another project.');
10
+ const age = now - Date.parse(report.observedAt);
11
+ if (!Number.isFinite(age) || age < -60_000 || age > 15 * 60_000) throw new Error('Capability report is stale or has an invalid timestamp. Re-observe the current session.');
12
+ for (const [key, value] of Object.entries(report.capabilities)) {
13
+ if (!CAPABILITIES.includes(key) || !value || !['available', 'missing', 'disabled', 'unknown'].includes(value.status)
14
+ || typeof value.reason !== 'string' || !value.reason.trim() || value.reason.length > 500) throw new Error(`Invalid capability evidence: ${key}`);
15
+ }
16
+ return report;
17
+ }
18
+
19
+ export function readCapabilityReport(path, root) {
20
+ const stat = statSync(path);
21
+ if (!stat.isFile() || stat.size > 64 * 1024) throw new Error('Capability report must be a regular file up to 64 KiB.');
22
+ return validateCapabilityReport(JSON.parse(readFileSync(path, 'utf8')), root);
23
+ }
24
+
25
+ export function discoverCapabilities(root = process.cwd(), { report, git = gitRead, executable = findExecutable } = {}) {
26
+ const project = realpathSync(resolve(root));
27
+ if (!statSync(project).isDirectory()) throw new Error('Project root must be a directory.');
28
+ accessSync(project, constants.R_OK | constants.X_OK);
29
+ const capabilities = Object.fromEntries(CAPABILITIES.map(id => [id, { status: 'unknown', reason: 'Host must verify task-specific access or supplied evidence.', source: 'unobserved' }]));
30
+ capabilities['project.read'] = { status: 'available', reason: 'Project directory is readable.', source: 'local-probe' };
31
+ const gitPath = executable('git');
32
+ capabilities['git.repo'] = gitPath && git(project, ['rev-parse', '--git-dir']) !== null
33
+ ? { status: 'available', reason: 'Git recognizes the selected repository.', source: 'local-probe' }
34
+ : { status: 'missing', reason: gitPath ? 'Selected directory is not a Git repository.' : 'Git is not on PATH.', source: 'local-probe' };
35
+ if (report) {
36
+ validateCapabilityReport(report, project);
37
+ for (const [id, value] of Object.entries(report.capabilities)) {
38
+ // A supplied host report may attest connector/evidence access, not override local facts.
39
+ if (['project.read', 'git.repo'].includes(id)) continue;
40
+ capabilities[id] = { ...value, source: 'host-report' };
41
+ }
42
+ }
43
+ const integrations = ['git', 'gh', 'vercel', 'node', 'python3', 'docker', 'codex', 'claude'].map(name => ({
44
+ name, executable: Boolean(executable(name)), authenticated: 'unknown',
45
+ }));
46
+ return { schemaVersion: 1, root: project, observedAt: new Date().toISOString(), capabilities, integrations,
47
+ note: 'Executable presence is not authentication. Host reports are session evidence, not permission grants. No external services were contacted.' };
48
+ }
49
+
50
+ export function listTools(catalog, discovery, { query = '', pack, available = false, all = false, host = 'claude', limit = 1000 } = {}) {
51
+ return searchCommands(catalog, query, { pack, limit: 1000 }).map(({ command, score }) => ({
52
+ id: command.id, pack: command.pack, summary: command.summary, aliasOf: command.aliasOf,
53
+ defaultMode: command.defaultMode, invocation: invocation(command, host), example: command.examples[0],
54
+ implementationStatus: command.implementationStatus, executionModel: command.executionModel,
55
+ validation: command.validation, score, ...availability(catalog, command, discovery.capabilities, host),
56
+ })).filter(c => (all || !['planned', 'uninstalled', 'unsupported'].includes(c.status)) && (!available || c.status === 'available')).slice(0, limit);
57
+ }
58
+
59
+ export function recommend(catalog, discovery, brief, { host = 'claude', limit = 3, context = routeContext(discovery.root) } = {}) {
60
+ if (typeof brief !== 'string' || !brief.trim()) throw new Error('A routing goal is required.');
61
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1000) throw Error('limit must be between 1 and 1000.');
62
+ const signals = intentSignals(brief);
63
+ const query = signals.positive.trim();
64
+ const matches = (/[a-z0-9]/i.test(query) ? listTools(catalog, discovery, { query, host, all: true, limit: catalog.commands.length }) : [])
65
+ .filter(c => !['auto', 'do', 'help', 'tools', 'setup'].includes(c.id));
66
+ for (const rule of signals.matches) for (const id of rule.ids) {
67
+ if (catalog.commands.some(c => c.id === id) && !matches.some(c => c.id === id)) matches.push(...listTools(catalog, discovery, { query: id, host, all: true }));
68
+ }
69
+ const unique = new Map();
70
+ for (const match of matches) {
71
+ const id = match.aliasOf || match.id;
72
+ if (unique.has(id)) { unique.get(id).matchedNames.push(match.id); continue; }
73
+ const canonical = getCommand(catalog, id);
74
+ unique.set(id, { ...match, id, aliasOf: undefined, summary: canonical.summary,
75
+ invocation: invocation(canonical, host), matchedNames: [match.id] });
76
+ }
77
+ const candidates = rankCandidates([...unique.values()], brief, context);
78
+ return { brief, executableHere: false,
79
+ context, strategy: executionStrategy(brief, candidates), recommendations: candidates.slice(0, limit),
80
+ confidence: candidates.length === 0 ? 'no-match' : candidates.length > 1 && candidates[0].score - candidates[1].score < 12 ? 'ambiguous' : 'candidate',
81
+ instruction: 'Candidates only. The active host agent must resolve intent, context, scope, and authority before selecting and executing a route. Do not execute keyword matches blindly.',
82
+ available: candidates.filter(c => c.status === 'available').slice(0, limit),
83
+ unavailable: candidates.filter(c => c.status !== 'available').slice(0, limit) };
84
+ }
@@ -0,0 +1,12 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ export function isDirectRun(moduleUrl, argument = process.argv[1]) {
5
+ if (!argument) return false;
6
+ try {
7
+ return realpathSync(argument) === realpathSync(fileURLToPath(moduleUrl));
8
+ } catch {
9
+ // Imports from stdin/eval have no filesystem entry point.
10
+ return false;
11
+ }
12
+ }
@@ -0,0 +1,136 @@
1
+ import { readFileSync, readdirSync, lstatSync } from 'node:fs';
2
+ import { join, relative } from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { runCommand, requireResult, redact } from './process.mjs';
6
+ import { within, readJson, projectRoot, digest } from './storage.mjs';
7
+
8
+ const observed = (kind, target, data) => ({ schemaVersion: 1, kind, target, observedAt: new Date().toISOString(), ...data });
9
+ const parse = source => { try { return JSON.parse(source); } catch { throw Error('Provider returned invalid JSON; update or inspect the installed CLI.'); } };
10
+ const clean = value => JSON.parse(redact(JSON.stringify(value)));
11
+
12
+ export async function githubChecks({ root, repo, pr }, run = runCommand) {
13
+ if (typeof repo !== 'string' || !/^[\w.-]+\/[\w.-]+$/.test(repo) || !/^[1-9]\d*$/.test(String(pr))) throw Error('Specify --repo owner/name and --pr NUMBER.');
14
+ const view = async () => {
15
+ const value = parse(requireResult(await run(['gh', 'pr', 'view', String(pr), '--repo', repo, '--json', 'number,url,headRefOid,baseRefName,state,isDraft'], { cwd: root })));
16
+ if (value.number !== Number(pr) || !/^[a-f0-9]{40,64}$/.test(value.headRefOid || '')) throw Error('Unexpected PR identity.');
17
+ return value;
18
+ };
19
+ const before = await view();
20
+ const result = await run(['gh', 'pr', 'checks', String(pr), '--repo', repo, '--json', 'name,state,bucket,link,workflow'], { cwd: root });
21
+ const checks = parse(requireResult(result, [0, 1, 8]));
22
+ if (!Array.isArray(checks) || checks.some(c => !c || typeof c.name !== 'string' || !['pass', 'fail', 'pending', 'skipping', 'cancel'].includes(c.bucket))) throw Error('Unexpected GitHub check schema.');
23
+ const after = await view();
24
+ const stale = before.headRefOid !== after.headRefOid || before.baseRefName !== after.baseRefName;
25
+ const counts = Object.fromEntries(['pass', 'fail', 'pending', 'skipping', 'cancel'].map(k => [k, checks.filter(c => c.bucket === k).length]));
26
+ return observed('github-checks', { repo, pr: Number(pr), head: before.headRefOid }, clean({ stale, currentHead: after.headRefOid, state: after.state, draft: after.isDraft, checks, counts,
27
+ result: stale ? 'stale' : !checks.length ? 'unknown' : counts.fail || counts.cancel ? 'failed' : counts.pending ? 'pending' : 'completed',
28
+ limitation: 'Check observation only. Does not establish branch-protection satisfaction, approvals, mergeability or deployment health. Re-read before a dependent action.' }));
29
+ }
30
+
31
+ export async function vercelDiagnosis({ root, deployment, scope }, run = runCommand) {
32
+ if (typeof deployment !== 'string' || !/^(?:dpl_[A-Za-z0-9]+|(?:https:\/\/)?[a-z0-9][a-z0-9.-]*\.vercel\.app)$/i.test(deployment)) throw Error('Specify a dpl_ID or vercel.app deployment URL without query parameters.');
33
+ if (scope && !/^[a-zA-Z0-9_-]+$/.test(scope)) throw Error('Invalid Vercel scope.');
34
+ const suffix = scope ? ['--scope', scope] : [];
35
+ const info = await run(['vercel', 'inspect', deployment, ...suffix], { cwd: root, timeoutMs: 20000 });
36
+ requireResult(info);
37
+ const detail = info.stdout + '\n' + info.stderr;
38
+ const logs = await run(['vercel', 'inspect', deployment, '--logs', ...suffix], { cwd: root, timeoutMs: 20000 });
39
+ const lines = redact(logs.stdout + '\n' + logs.stderr).split(/\r?\n/).filter(Boolean);
40
+ const errors = lines.filter(line => /\b(?:error|failed|cannot find|not found|ELIFECYCLE)\b/i.test(line)).slice(0, 12);
41
+ return observed('vercel-build', { deployment, scope: scope || null }, { details: redact(detail), logs: { status: logs.status, timedOut: logs.timedOut, truncated: logs.truncated, ...(logs.error ? { error: logs.error } : {}) },
42
+ result: logs.status !== 0 || logs.error || logs.timedOut || logs.truncated ? 'incomplete' : 'observed', errorCandidates: errors,
43
+ limitation: 'Build-log candidates, not a proven root cause or a runtime-health check. CLI output is unstructured; verify deployment identity in details. Empty logs are not evidence of a successful build.' });
44
+ }
45
+
46
+ function sqlCode(source) {
47
+ // Remove comments and string bodies while retaining statement boundaries and identifier tokens.
48
+ return source.replace(/\$([a-zA-Z_][a-zA-Z0-9_]*)?\$[\s\S]*?\$\1\$/g, ' ')
49
+ .replace(/'(?:''|[^'])*'/g, ' ').replace(/--[^\n]*|\/\*[\s\S]*?\*\//g, ' ');
50
+ }
51
+ export function migrationEvidence({ root, directory, applied }) {
52
+ root = projectRoot(root);
53
+ if (!directory) throw Error('Specify --directory with the SQL migration directory.');
54
+ const folder = within(root, directory);
55
+ const files = [];
56
+ function collect(path, depth = 0) {
57
+ if (depth > 5) throw Error('Migration directory nesting exceeds five levels.');
58
+ for (const entry of readdirSync(path, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
59
+ const file = within(root, join(path, entry.name));
60
+ if (entry.isDirectory()) collect(file, depth + 1);
61
+ else if (entry.isFile() && entry.name.endsWith('.sql')) {
62
+ if (files.length >= 1000 || lstatSync(file).size > 1024 * 1024) throw Error('Migration inspection budget exceeded.');
63
+ const source = readFileSync(file, 'utf8'), code = sqlCode(source);
64
+ const findings = [];
65
+ for (const [pattern, finding] of [[/\bDROP\s+(?:TABLE|COLUMN|SCHEMA|TYPE|INDEX)\b/i, 'drop-object'], [/\bTRUNCATE\b/i, 'truncate'], [/\bDELETE\s+FROM\b[^;]*;/i, 'delete-data'], [/\bALTER\s+TABLE\b/i, 'alter-table-lock-review'], [/\bCREATE\s+(?:UNIQUE\s+)?INDEX\b(?!\s+CONCURRENTLY)/i, 'index-lock-review'], [/\bSET\s+NOT\s+NULL\b/i, 'not-null-backfill-review']]) if (pattern.test(code)) findings.push(finding);
66
+ files.push({ path: relative(folder, file).replaceAll('\\', '/'), sha256: digest(source), findings });
67
+ }
68
+ }
69
+ }
70
+ collect(folder);
71
+ let history = null;
72
+ if (applied) {
73
+ history = readJson(within(root, applied));
74
+ if (history.schemaVersion !== 1 || typeof history.target !== 'string' || !history.target.trim() || !Number.isFinite(Date.parse(history.observedAt)) || !Array.isArray(history.migrations) || history.migrations.some(m => typeof m.path !== 'string' || !/^[a-f0-9]{64}$/.test(m.sha256 || ''))) throw Error('Applied history requires schemaVersion:1, target, observedAt and migrations [{path,sha256}].');
75
+ if (new Set(history.migrations.map(m => m.path)).size !== history.migrations.length) throw Error('Duplicate applied migration identities.');
76
+ }
77
+ const drift = history?.migrations.filter(m => !files.some(f => f.path === m.path && f.sha256 === m.sha256)) || [];
78
+ return observed('migrations', { directory, database: history?.target || null }, { files, appliedObservedAt: history?.observedAt || null, drift, pending: history ? files.filter(f => !history.migrations.some(m => m.path === f.path)).map(f => f.path) : null,
79
+ result: !files.length ? 'unknown' : drift.length ? 'drift' : history ? 'compared' : 'local-only',
80
+ limitation: 'Static SQL review signals and supplied history only. Does not connect to a database, parse every SQL dialect, establish lock duration or prove safe execution. ORM-generated migrations must first be rendered to SQL.' });
81
+ }
82
+
83
+ export async function browserEvidence({ root, url, steps }, launch) {
84
+ const target = new URL(url);
85
+ if (!['http:', 'https:'].includes(target.protocol) || target.username || target.password || target.search || target.hash) throw Error('Use an HTTP(S) URL without credentials, query parameters or fragment.');
86
+ const plan = steps ? readJson(within(root, steps)) : { steps: [{ action: 'title' }] };
87
+ if (!Array.isArray(plan.steps) || !plan.steps.length || plan.steps.length > 30) throw Error('Provide 1–30 browser steps.');
88
+ const allowed = new Set(['click', 'fill', 'press', 'visible', 'hidden', 'focused', 'text', 'url', 'title']);
89
+ for (const step of plan.steps) {
90
+ if (!allowed.has(step.action) || Object.keys(step).some(k => !['action', 'selector', 'value'].includes(k))) throw Error('Unknown browser step.');
91
+ if (!['url', 'title'].includes(step.action) && (typeof step.selector !== 'string' || !step.selector || step.selector.length > 500)) throw Error('Browser step requires a bounded selector.');
92
+ if (['fill', 'press', 'text', 'url'].includes(step.action) && (typeof step.value !== 'string' || step.value.length > 4000)) throw Error('Browser step requires a bounded value.');
93
+ }
94
+ if (!launch) {
95
+ const require = createRequire(join(projectRoot(root), 'package.json'));
96
+ let module;
97
+ try { module = await import(pathToFileURL(require.resolve('playwright')).href); }
98
+ catch { try { module = await import(pathToFileURL(require.resolve('@playwright/test')).href); } catch { throw Error('Install Playwright and its Chromium browser in the selected project, or use the host browser tools. No dependency was installed.'); } }
99
+ const chromium = module.chromium || module.default?.chromium;
100
+ if (!chromium?.launch) throw Error('The installed Playwright package does not expose Chromium.');
101
+ launch = () => chromium.launch({ headless: true, timeout: 15000 });
102
+ }
103
+ const browser = await launch();
104
+ const results = [], issues = [];
105
+ try {
106
+ const context = await browser.newContext();
107
+ const page = await context.newPage(); page.setDefaultTimeout(5000);
108
+ page.on('pageerror', e => issues.push(redact(e.message).slice(0, 1000)));
109
+ const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
110
+ for (const [index, step] of plan.steps.entries()) {
111
+ try {
112
+ const locator = step.selector ? page.locator(step.selector) : null;
113
+ if (step.action === 'click') await locator.click();
114
+ else if (step.action === 'fill') await locator.fill(step.value);
115
+ else if (step.action === 'press') await locator.press(step.value);
116
+ else if (step.action === 'visible') await locator.waitFor({ state: 'visible' });
117
+ else if (step.action === 'hidden') await locator.waitFor({ state: 'hidden' });
118
+ else if (step.action === 'focused') { if (!(await locator.evaluate(element => element.getRootNode().activeElement === element))) throw Error('Expected element did not own focus.'); }
119
+ else if (step.action === 'text') { await locator.waitFor({ state: 'visible' }); if (!(await locator.innerText()).includes(step.value)) throw Error('Expected text was not present.'); }
120
+ else if (step.action === 'url' && page.url() !== new URL(step.value, url).href) throw Error('URL assertion did not match.');
121
+ else if (step.action === 'title') { const title = await page.title(); if (step.value !== undefined && title !== step.value) throw Error('Title assertion did not match.'); }
122
+ results.push({ index, action: step.action, pass: true });
123
+ } catch (error) { results.push({ index, action: step.action, pass: false, error: redact(error.message).slice(0, 1000) }); break; }
124
+ }
125
+ const status = response?.status() ?? null;
126
+ return observed('browser', { url }, { status, steps: results, pageErrors: issues.slice(0, 20), result: results.length === plan.steps.length && results.every(s => s.pass) && status !== null && status < 400 && !issues.length ? 'passed' : 'failed', limitation: 'Fresh headless Chromium session and only the listed interactions. No automatic login, full accessibility audit, visual approval or other-browser coverage.' });
127
+ } finally { await browser.close(); }
128
+ }
129
+
130
+ export async function collectEvidence(kind, options, dependencies = {}) {
131
+ if (kind === 'github') return githubChecks(options, dependencies.run);
132
+ if (kind === 'vercel') return vercelDiagnosis(options, dependencies.run);
133
+ if (kind === 'migrations') return migrationEvidence(options);
134
+ if (kind === 'browser') return browserEvidence(options, dependencies.launch);
135
+ throw Error(`Unknown evidence collector: ${kind}`);
136
+ }
@@ -0,0 +1,44 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { commandInvocation } from './command.mjs';
3
+
4
+ export function redact(value) {
5
+ return String(value).replace(/\b(?:gh[pousr]_[\w]{20,}|github_pat_[\w]{20,}|npm_[\w]{20,})\b/g, '[REDACTED]')
6
+ .replace(/\b(Bearer\s+)[A-Za-z0-9._~+\/-]+/gi, '$1[REDACTED]')
7
+ .replace(/((?:password|token|secret|api[_-]?key|authorization)\s*[=:]\s*)[^\s,;]+/gi, '$1[REDACTED]')
8
+ .replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/g, '$1[REDACTED]@');
9
+ }
10
+
11
+ export async function runCommand(command, { cwd, timeoutMs = 15000, maxBytes = 512 * 1024, env = process.env } = {}) {
12
+ if (!Array.isArray(command) || !command.length || command.some(s => typeof s !== 'string' || s.includes('\0'))) throw Error('Command must be an argv array.');
13
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 120000) throw Error('Invalid command timeout.');
14
+ let child;
15
+ try {
16
+ const [binary, args] = commandInvocation(command[0], command.slice(1));
17
+ child = spawn(binary, args, { cwd, env: { ...env, CI: '1', NO_COLOR: '1', GH_PROMPT_DISABLED: '1', GIT_TERMINAL_PROMPT: '0' }, shell: false, detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'pipe'] });
18
+ } catch (error) { return { status: null, error: redact(error.message), stdout: '', stderr: '', timedOut: false, truncated: false }; }
19
+ return new Promise(resolve => {
20
+ let stdout = '', stderr = '', bytes = 0, timedOut = false, truncated = false, error;
21
+ const stop = () => {
22
+ try {
23
+ if (process.platform === 'win32') {
24
+ const killer = spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore', shell: false });
25
+ killer.on('error', () => child.kill());
26
+ } else process.kill(-child.pid, 'SIGKILL');
27
+ } catch { child.kill('SIGKILL'); }
28
+ };
29
+ const timer = setTimeout(() => { timedOut = true; stop(); }, timeoutMs);
30
+ const collect = key => data => {
31
+ const remaining = Math.max(0, maxBytes - bytes); bytes += data.length;
32
+ if (key === 'stdout') stdout += data.subarray(0, remaining).toString(); else stderr += data.subarray(0, remaining).toString();
33
+ if (bytes > maxBytes && !truncated) { truncated = true; stop(); }
34
+ };
35
+ child.stdout.on('data', collect('stdout')); child.stderr.on('data', collect('stderr'));
36
+ child.on('error', e => { error = redact(e.message); });
37
+ child.on('close', (status, signal) => { clearTimeout(timer); resolve({ status, signal, stdout, stderr, timedOut, truncated, ...(error ? { error } : {}) }); });
38
+ });
39
+ }
40
+
41
+ export function requireResult(result, accepted = [0]) {
42
+ if (result.error || result.timedOut || result.truncated || !accepted.includes(result.status)) throw Error(result.error || (result.timedOut ? 'Command timed out; evidence is incomplete.' : result.truncated ? 'Command exceeded output limit; evidence is incomplete.' : redact(result.stderr.trim()).slice(0, 1000) || `Command exited ${result.status}.`));
43
+ return result.stdout;
44
+ }
@@ -0,0 +1,83 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { loadCatalog, pluginRoot } from './catalog.mjs';
4
+
5
+ const text = value => typeof value === 'string' && Boolean(value.trim());
6
+ const list = value => Array.isArray(value) && value.length > 0 && value.every(text);
7
+
8
+ export function validateProfiles(data, commands = loadCatalog()) {
9
+ if (data?.schemaVersion !== 1 || !Array.isArray(data.families) || !Array.isArray(data.profiles)) throw new Error('Invalid profile catalog.');
10
+ const families = new Set(), ids = new Set();
11
+ for (const family of data.families) {
12
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(family.id) || !text(family.name) || families.has(family.id)) throw new Error('Invalid profile family.');
13
+ families.add(family.id);
14
+ }
15
+ for (const p of data.profiles) {
16
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(p.id) || ids.has(p.id) || !families.has(p.family)) throw new Error(`Invalid profile identity: ${p.id}`);
17
+ ids.add(p.id);
18
+ for (const field of ['name', 'summary', 'decision', 'boundary', 'example']) if (!text(p[field])) throw new Error(`Missing profile ${field}: ${p.id}`);
19
+ for (const field of ['priorities', 'verification', 'workflows']) if (!list(p[field])) throw new Error(`Invalid profile ${field}: ${p.id}`);
20
+ if (new Set(p.workflows).size !== p.workflows.length || p.workflows.some(id => !commands.commands.some(c => c.id === id && !c.aliasOf))) throw new Error(`Unknown or duplicate canonical workflow in profile: ${p.id}`);
21
+ }
22
+ return data;
23
+ }
24
+
25
+ export function loadProfiles(root = pluginRoot) {
26
+ return validateProfiles(JSON.parse(readFileSync(resolve(root, 'catalog/profiles.json'), 'utf8')), loadCatalog(root));
27
+ }
28
+
29
+ export function getProfile(data, id) {
30
+ const p = data.profiles.find(p => p.id === id);
31
+ if (!p) throw new Error(`Unknown profile: ${id}. Use profiles to browse supported roles.`);
32
+ return p;
33
+ }
34
+
35
+ export function searchProfiles(data, query = '') {
36
+ const exact = query.trim().toLowerCase();
37
+ const words = exact.match(/[a-z0-9]+/g) || [];
38
+ const synonyms = { architecture: ['architect'], ml: ['machine', 'learning'], sre: ['site', 'reliability'], a11y: ['accessibility'] };
39
+ for (const word of [...words]) words.push(...(synonyms[word] || []));
40
+ return data.profiles.map(p => {
41
+ const identity = `${p.id} ${p.name} ${p.family}`.toLowerCase().match(/[a-z0-9]+/g) || [];
42
+ const detail = `${p.summary} ${p.priorities.join(' ')}`.toLowerCase().match(/[a-z0-9]+/g) || [];
43
+ const score = p.id === exact || p.name.toLowerCase() === exact ? 1000
44
+ : words.reduce((score, word) => score + (identity.includes(word) ? 10 : detail.includes(word) ? 1 : 0), 0);
45
+ return { ...p, score };
46
+ }).filter(p => !words.length || p.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
47
+ }
48
+
49
+ export function validateProfileSelection(data, value) {
50
+ if (!value || value.scope !== 'task' || !['user', 'agent'].includes(value.selectedBy)
51
+ || typeof value.pinned !== 'boolean' || !text(value.reason)) throw new Error('Invalid profile selection.');
52
+ if (value.selectedBy === 'agent' && value.pinned) throw new Error('An agent-selected profile cannot be pinned.');
53
+ getProfile(data, value.primary);
54
+ if (!Array.isArray(value.secondary) || value.secondary.length > 2
55
+ || new Set([value.primary, ...value.secondary]).size !== value.secondary.length + 1) throw new Error('Choose at most two distinct secondary profiles.');
56
+ for (const id of value.secondary) getProfile(data, id);
57
+ return value;
58
+ }
59
+
60
+ // Selection is recorded context, not proof of user authority or a host setting.
61
+ export function selectProfiles(data, request, previous = null) {
62
+ if (!request || !['user', 'agent'].includes(request.selectedBy) || !text(request.reason)) throw new Error('Selection needs selectedBy and an evidence-based reason.');
63
+ if (Object.keys(request).some(key => !['primary', 'secondary', 'selectedBy', 'reason', 'pinned', 'scope'].includes(key))
64
+ || (request.scope !== undefined && request.scope !== 'task')
65
+ || (request.pinned !== undefined && typeof request.pinned !== 'boolean')) throw new Error('Profile selection changes task roles only.');
66
+ if (previous !== null && previous !== undefined) validateProfileSelection(data, previous);
67
+ if (previous?.pinned && request.selectedBy === 'agent') throw new Error('A pinned user profile cannot be changed or cleared by the agent.');
68
+ if (request.primary === null) {
69
+ if ((request.secondary !== undefined && (!Array.isArray(request.secondary) || request.secondary.length)) || request.pinned === true) throw new Error('A cleared profile cannot have secondary profiles or a pin.');
70
+ return null;
71
+ }
72
+ const result = { primary: request.primary, secondary: structuredClone(request.secondary ?? []), selectedBy: request.selectedBy,
73
+ pinned: request.pinned ?? request.selectedBy === 'user', scope: 'task', reason: request.reason };
74
+ validateProfileSelection(data, result);
75
+ return result;
76
+ }
77
+
78
+ export function profileContext(data, selection) {
79
+ if (!selection) return null;
80
+ validateProfileSelection(data, selection);
81
+ return { selection: structuredClone(selection), roles: [selection.primary, ...selection.secondary].map(id => structuredClone(getProfile(data, id))),
82
+ instruction: 'Apply only priorities relevant to the original task. The primary role resolves emphasis; explicit user constraints, scope, mode, evidence and authority remain unchanged. Suggested workflows are candidates, not permissions or an execution checklist.' };
83
+ }