productfoo 0.0.0-stage → 1.0.1

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 (498) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +338 -2
  3. package/bin/foo +3 -0
  4. package/bin/foo.mjs +42 -0
  5. package/commands/bug.md +31 -0
  6. package/commands/discovery.md +28 -0
  7. package/commands/ideate.md +27 -0
  8. package/commands/init.md +28 -0
  9. package/commands/pitch.md +27 -0
  10. package/commands/preview.md +25 -0
  11. package/commands/research.md +31 -0
  12. package/commands/roast.md +27 -0
  13. package/commands/spec.md +30 -0
  14. package/config/pricing.yaml +198 -0
  15. package/config/routing-policy.yaml +171 -0
  16. package/dist-public/src/activeModel.d.ts +83 -0
  17. package/dist-public/src/activeModel.js +331 -0
  18. package/dist-public/src/agentAdapters.d.ts +122 -0
  19. package/dist-public/src/agentAdapters.js +178 -0
  20. package/dist-public/src/agentState.d.ts +43 -0
  21. package/dist-public/src/agentState.js +177 -0
  22. package/dist-public/src/bugCapsule.d.ts +94 -0
  23. package/dist-public/src/bugCapsule.js +227 -0
  24. package/dist-public/src/bugCapture.d.ts +26 -0
  25. package/dist-public/src/bugCapture.js +251 -0
  26. package/dist-public/src/bugOwnerLog.d.ts +93 -0
  27. package/dist-public/src/bugOwnerLog.js +304 -0
  28. package/dist-public/src/bugReportStore.d.ts +48 -0
  29. package/dist-public/src/bugReportStore.js +860 -0
  30. package/dist-public/src/bugSpecContext.d.ts +7 -0
  31. package/dist-public/src/bugSpecContext.js +129 -0
  32. package/dist-public/src/channelFreshness.d.ts +25 -0
  33. package/dist-public/src/channelFreshness.js +194 -0
  34. package/dist-public/src/cli.d.ts +30 -0
  35. package/dist-public/src/cli.js +1667 -0
  36. package/dist-public/src/cliArgs.d.ts +7 -0
  37. package/dist-public/src/cliArgs.js +44 -0
  38. package/dist-public/src/codeCatalog.d.ts +41 -0
  39. package/dist-public/src/codeCatalog.js +197 -0
  40. package/dist-public/src/commandContract.d.ts +84 -0
  41. package/dist-public/src/commandContract.js +390 -0
  42. package/dist-public/src/commandUsage.d.ts +3 -0
  43. package/dist-public/src/commandUsage.js +30 -0
  44. package/dist-public/src/commands/agent.d.ts +60 -0
  45. package/dist-public/src/commands/agent.js +1353 -0
  46. package/dist-public/src/commands/asp.d.ts +8 -0
  47. package/dist-public/src/commands/asp.js +669 -0
  48. package/dist-public/src/commands/evalCmd.d.ts +4 -0
  49. package/dist-public/src/commands/evalCmd.js +405 -0
  50. package/dist-public/src/commands/login.d.ts +25 -0
  51. package/dist-public/src/commands/login.js +94 -0
  52. package/dist-public/src/commands/loop.d.ts +16 -0
  53. package/dist-public/src/commands/loop.js +619 -0
  54. package/dist-public/src/commands/reconcile.d.ts +41 -0
  55. package/dist-public/src/commands/reconcile.js +292 -0
  56. package/dist-public/src/commands/runtime.d.ts +1 -0
  57. package/dist-public/src/commands/runtime.js +36 -0
  58. package/dist-public/src/commands/scanRedact.d.ts +8 -0
  59. package/dist-public/src/commands/scanRedact.js +266 -0
  60. package/dist-public/src/commands/setup.d.ts +28 -0
  61. package/dist-public/src/commands/setup.js +398 -0
  62. package/dist-public/src/commands/shared.d.ts +17 -0
  63. package/dist-public/src/commands/shared.js +147 -0
  64. package/dist-public/src/commands/source.d.ts +1 -0
  65. package/dist-public/src/commands/source.js +134 -0
  66. package/dist-public/src/commands/specCmd.d.ts +24 -0
  67. package/dist-public/src/commands/specCmd.js +794 -0
  68. package/dist-public/src/commands/update.d.ts +61 -0
  69. package/dist-public/src/commands/update.js +244 -0
  70. package/dist-public/src/comprehensionSignal.d.ts +20 -0
  71. package/dist-public/src/comprehensionSignal.js +166 -0
  72. package/dist-public/src/conductor/diversity.d.ts +48 -0
  73. package/dist-public/src/conductor/diversity.js +423 -0
  74. package/dist-public/src/conductor/engine.d.ts +7 -0
  75. package/dist-public/src/conductor/engine.js +295 -0
  76. package/dist-public/src/conductor/families/ideate.d.ts +2 -0
  77. package/dist-public/src/conductor/families/ideate.js +255 -0
  78. package/dist-public/src/conductor/families/interview.d.ts +2 -0
  79. package/dist-public/src/conductor/families/interview.js +39 -0
  80. package/dist-public/src/conductor/families/specSwarm.d.ts +2 -0
  81. package/dist-public/src/conductor/families/specSwarm.js +146 -0
  82. package/dist-public/src/conductor/familyDelivery.d.ts +6 -0
  83. package/dist-public/src/conductor/familyDelivery.js +37 -0
  84. package/dist-public/src/conductor/familyPackArtifact.d.ts +4 -0
  85. package/dist-public/src/conductor/familyPackArtifact.js +260 -0
  86. package/dist-public/src/conductor/ideateDecision.d.ts +6 -0
  87. package/dist-public/src/conductor/ideateDecision.js +237 -0
  88. package/dist-public/src/conductor/pack.d.ts +26 -0
  89. package/dist-public/src/conductor/pack.js +75 -0
  90. package/dist-public/src/conductor/types.d.ts +111 -0
  91. package/dist-public/src/conductor/types.js +7 -0
  92. package/dist-public/src/configPath.d.ts +2 -0
  93. package/dist-public/src/configPath.js +7 -0
  94. package/dist-public/src/discoveryArtifacts.d.ts +54 -0
  95. package/dist-public/src/discoveryArtifacts.js +467 -0
  96. package/dist-public/src/discoveryCommand.d.ts +127 -0
  97. package/dist-public/src/discoveryCommand.js +2148 -0
  98. package/dist-public/src/discoveryConnections.d.ts +339 -0
  99. package/dist-public/src/discoveryConnections.js +613 -0
  100. package/dist-public/src/discoveryDocs.d.ts +129 -0
  101. package/dist-public/src/discoveryDocs.js +577 -0
  102. package/dist-public/src/discoveryEvidence.d.ts +59 -0
  103. package/dist-public/src/discoveryEvidence.js +198 -0
  104. package/dist-public/src/discoveryFireflies.d.ts +25 -0
  105. package/dist-public/src/discoveryFireflies.js +551 -0
  106. package/dist-public/src/discoveryGmail.d.ts +12 -0
  107. package/dist-public/src/discoveryGmail.js +466 -0
  108. package/dist-public/src/discoveryGong.d.ts +12 -0
  109. package/dist-public/src/discoveryGong.js +491 -0
  110. package/dist-public/src/discoveryLearningRouting.d.ts +90 -0
  111. package/dist-public/src/discoveryLearningRouting.js +405 -0
  112. package/dist-public/src/discoveryMatching.d.ts +26 -0
  113. package/dist-public/src/discoveryMatching.js +73 -0
  114. package/dist-public/src/discoveryOutlook.d.ts +12 -0
  115. package/dist-public/src/discoveryOutlook.js +492 -0
  116. package/dist-public/src/discoveryProcess.d.ts +221 -0
  117. package/dist-public/src/discoveryProcess.js +703 -0
  118. package/dist-public/src/discoveryProof.d.ts +143 -0
  119. package/dist-public/src/discoveryProof.js +529 -0
  120. package/dist-public/src/discoveryProviders.d.ts +152 -0
  121. package/dist-public/src/discoveryProviders.js +756 -0
  122. package/dist-public/src/discoveryReadiness.d.ts +219 -0
  123. package/dist-public/src/discoveryReadiness.js +672 -0
  124. package/dist-public/src/discoveryReconcile.d.ts +57 -0
  125. package/dist-public/src/discoveryReconcile.js +424 -0
  126. package/dist-public/src/discoverySlack.d.ts +12 -0
  127. package/dist-public/src/discoverySlack.js +452 -0
  128. package/dist-public/src/discoverySources.d.ts +56 -0
  129. package/dist-public/src/discoverySources.js +322 -0
  130. package/dist-public/src/discoverySync.d.ts +52 -0
  131. package/dist-public/src/discoverySync.js +151 -0
  132. package/dist-public/src/discoverySynthesis.d.ts +57 -0
  133. package/dist-public/src/discoverySynthesis.js +215 -0
  134. package/dist-public/src/discoveryTeams.d.ts +12 -0
  135. package/dist-public/src/discoveryTeams.js +516 -0
  136. package/dist-public/src/discoveryZoom.d.ts +12 -0
  137. package/dist-public/src/discoveryZoom.js +523 -0
  138. package/dist-public/src/elicitationSurface.d.ts +13 -0
  139. package/dist-public/src/elicitationSurface.js +77 -0
  140. package/dist-public/src/errorReport.d.ts +99 -0
  141. package/dist-public/src/errorReport.js +443 -0
  142. package/dist-public/src/errorTaxonomy.d.ts +84 -0
  143. package/dist-public/src/errorTaxonomy.js +189 -0
  144. package/dist-public/src/evalCalibration.d.ts +82 -0
  145. package/dist-public/src/evalCalibration.js +169 -0
  146. package/dist-public/src/evalChecks.d.ts +21 -0
  147. package/dist-public/src/evalChecks.js +295 -0
  148. package/dist-public/src/evalCompile.d.ts +56 -0
  149. package/dist-public/src/evalCompile.js +58 -0
  150. package/dist-public/src/evalImprove.d.ts +60 -0
  151. package/dist-public/src/evalImprove.js +175 -0
  152. package/dist-public/src/evalJudge.d.ts +47 -0
  153. package/dist-public/src/evalJudge.js +143 -0
  154. package/dist-public/src/evalLabels.d.ts +100 -0
  155. package/dist-public/src/evalLabels.js +287 -0
  156. package/dist-public/src/evalRecord.d.ts +68 -0
  157. package/dist-public/src/evalRecord.js +165 -0
  158. package/dist-public/src/evalRoast.d.ts +88 -0
  159. package/dist-public/src/evalRoast.js +249 -0
  160. package/dist-public/src/evidenceIntake.d.ts +66 -0
  161. package/dist-public/src/evidenceIntake.js +356 -0
  162. package/dist-public/src/feedback.d.ts +78 -0
  163. package/dist-public/src/feedback.js +294 -0
  164. package/dist-public/src/firefliesClient.d.ts +106 -0
  165. package/dist-public/src/firefliesClient.js +258 -0
  166. package/dist-public/src/firstMoveTensions.d.ts +10 -0
  167. package/dist-public/src/firstMoveTensions.js +93 -0
  168. package/dist-public/src/gitRun.d.ts +21 -0
  169. package/dist-public/src/gitRun.js +50 -0
  170. package/dist-public/src/githubReconcile.d.ts +94 -0
  171. package/dist-public/src/githubReconcile.js +205 -0
  172. package/dist-public/src/gmailNango.d.ts +131 -0
  173. package/dist-public/src/gmailNango.js +329 -0
  174. package/dist-public/src/gongNango.d.ts +118 -0
  175. package/dist-public/src/gongNango.js +308 -0
  176. package/dist-public/src/grammarGate.d.ts +13 -0
  177. package/dist-public/src/grammarGate.js +72 -0
  178. package/dist-public/src/harnessGates.d.ts +65 -0
  179. package/dist-public/src/harnessGates.js +168 -0
  180. package/dist-public/src/humanSurfaceEval.d.ts +19 -0
  181. package/dist-public/src/humanSurfaceEval.js +156 -0
  182. package/dist-public/src/ideate.d.ts +43 -0
  183. package/dist-public/src/ideate.js +255 -0
  184. package/dist-public/src/ideateContext.d.ts +11 -0
  185. package/dist-public/src/ideateContext.js +83 -0
  186. package/dist-public/src/index.d.ts +2 -0
  187. package/dist-public/src/index.js +4 -0
  188. package/dist-public/src/initCommand.d.ts +25 -0
  189. package/dist-public/src/initCommand.js +558 -0
  190. package/dist-public/src/interviewGate.d.ts +25 -0
  191. package/dist-public/src/interviewGate.js +126 -0
  192. package/dist-public/src/jsonFile.d.ts +2 -0
  193. package/dist-public/src/jsonFile.js +22 -0
  194. package/dist-public/src/loopBudget.d.ts +182 -0
  195. package/dist-public/src/loopBudget.js +494 -0
  196. package/dist-public/src/loopContracts.d.ts +35 -0
  197. package/dist-public/src/loopContracts.js +364 -0
  198. package/dist-public/src/machineState.d.ts +1 -0
  199. package/dist-public/src/machineState.js +9 -0
  200. package/dist-public/src/notionNango.d.ts +93 -0
  201. package/dist-public/src/notionNango.js +279 -0
  202. package/dist-public/src/orchestrationResult.d.ts +11 -0
  203. package/dist-public/src/orchestrationResult.js +73 -0
  204. package/dist-public/src/outlookNango.d.ts +124 -0
  205. package/dist-public/src/outlookNango.js +327 -0
  206. package/dist-public/src/packetCorrections.d.ts +40 -0
  207. package/dist-public/src/packetCorrections.js +172 -0
  208. package/dist-public/src/packetGlossary.d.ts +6 -0
  209. package/dist-public/src/packetGlossary.js +42 -0
  210. package/dist-public/src/packetIndex.d.ts +54 -0
  211. package/dist-public/src/packetIndex.js +744 -0
  212. package/dist-public/src/packetIndexEntry.d.ts +3 -0
  213. package/dist-public/src/packetIndexEntry.js +128 -0
  214. package/dist-public/src/packetIndexServe.d.ts +13 -0
  215. package/dist-public/src/packetIndexServe.js +113 -0
  216. package/dist-public/src/packetIndexWorker.d.ts +7 -0
  217. package/dist-public/src/packetIndexWorker.js +45 -0
  218. package/dist-public/src/packetLifecycle.d.ts +30 -0
  219. package/dist-public/src/packetLifecycle.js +127 -0
  220. package/dist-public/src/packetReconcile.d.ts +73 -0
  221. package/dist-public/src/packetReconcile.js +386 -0
  222. package/dist-public/src/packetStatus.d.ts +125 -0
  223. package/dist-public/src/packetStatus.js +770 -0
  224. package/dist-public/src/packetValidation.d.ts +17 -0
  225. package/dist-public/src/packetValidation.js +333 -0
  226. package/dist-public/src/packetVerify.d.ts +73 -0
  227. package/dist-public/src/packetVerify.js +303 -0
  228. package/dist-public/src/packetWrite.d.ts +52 -0
  229. package/dist-public/src/packetWrite.js +448 -0
  230. package/dist-public/src/pitch.d.ts +38 -0
  231. package/dist-public/src/pitch.js +154 -0
  232. package/dist-public/src/processMemo.d.ts +8 -0
  233. package/dist-public/src/processMemo.js +23 -0
  234. package/dist-public/src/productDetect.d.ts +30 -0
  235. package/dist-public/src/productDetect.js +376 -0
  236. package/dist-public/src/profile.d.ts +62 -0
  237. package/dist-public/src/profile.js +334 -0
  238. package/dist-public/src/questionModel.d.ts +46 -0
  239. package/dist-public/src/questionModel.js +74 -0
  240. package/dist-public/src/questionText.d.ts +7 -0
  241. package/dist-public/src/questionText.js +38 -0
  242. package/dist-public/src/redactConfig.d.ts +2 -0
  243. package/dist-public/src/redactConfig.js +11 -0
  244. package/dist-public/src/redactEngine.d.ts +59 -0
  245. package/dist-public/src/redactEngine.js +509 -0
  246. package/dist-public/src/redactGate.d.ts +63 -0
  247. package/dist-public/src/redactGate.js +209 -0
  248. package/dist-public/src/repoRead.d.ts +29 -0
  249. package/dist-public/src/repoRead.js +160 -0
  250. package/dist-public/src/research.d.ts +242 -0
  251. package/dist-public/src/research.js +1314 -0
  252. package/dist-public/src/researchCompletion.d.ts +34 -0
  253. package/dist-public/src/researchCompletion.js +123 -0
  254. package/dist-public/src/researchExecution.d.ts +47 -0
  255. package/dist-public/src/researchExecution.js +309 -0
  256. package/dist-public/src/researchFormat.d.ts +21 -0
  257. package/dist-public/src/researchFormat.js +77 -0
  258. package/dist-public/src/researchGate.d.ts +86 -0
  259. package/dist-public/src/researchGate.js +1036 -0
  260. package/dist-public/src/researchReceipt.d.ts +76 -0
  261. package/dist-public/src/researchReceipt.js +148 -0
  262. package/dist-public/src/researchRender.d.ts +2 -0
  263. package/dist-public/src/researchRender.js +267 -0
  264. package/dist-public/src/researchSigningKey.d.ts +19 -0
  265. package/dist-public/src/researchSigningKey.js +95 -0
  266. package/dist-public/src/riskClassify.d.ts +42 -0
  267. package/dist-public/src/riskClassify.js +149 -0
  268. package/dist-public/src/routines.d.ts +42 -0
  269. package/dist-public/src/routines.js +269 -0
  270. package/dist-public/src/routingPolicy.d.ts +172 -0
  271. package/dist-public/src/routingPolicy.js +646 -0
  272. package/dist-public/src/secretInput.d.ts +8 -0
  273. package/dist-public/src/secretInput.js +89 -0
  274. package/dist-public/src/selfCheck.d.ts +7 -0
  275. package/dist-public/src/selfCheck.js +473 -0
  276. package/dist-public/src/selfCheckCache.d.ts +29 -0
  277. package/dist-public/src/selfCheckCache.js +147 -0
  278. package/dist-public/src/severityClass.d.ts +15 -0
  279. package/dist-public/src/severityClass.js +22 -0
  280. package/dist-public/src/skillClientPolicy.d.ts +8 -0
  281. package/dist-public/src/skillClientPolicy.js +16 -0
  282. package/dist-public/src/skillLocal.d.ts +98 -0
  283. package/dist-public/src/skillLocal.js +463 -0
  284. package/dist-public/src/skillTreeDigest.d.ts +9 -0
  285. package/dist-public/src/skillTreeDigest.js +64 -0
  286. package/dist-public/src/slackNango.d.ts +62 -0
  287. package/dist-public/src/slackNango.js +208 -0
  288. package/dist-public/src/social/arxiv.d.ts +19 -0
  289. package/dist-public/src/social/arxiv.js +96 -0
  290. package/dist-public/src/social/github.d.ts +2 -0
  291. package/dist-public/src/social/github.js +78 -0
  292. package/dist-public/src/social/hackernews.d.ts +2 -0
  293. package/dist-public/src/social/hackernews.js +67 -0
  294. package/dist-public/src/social/index.d.ts +10 -0
  295. package/dist-public/src/social/index.js +79 -0
  296. package/dist-public/src/social/reddit.d.ts +2 -0
  297. package/dist-public/src/social/reddit.js +125 -0
  298. package/dist-public/src/social/types.d.ts +73 -0
  299. package/dist-public/src/social/types.js +61 -0
  300. package/dist-public/src/social/xml.d.ts +10 -0
  301. package/dist-public/src/social/xml.js +49 -0
  302. package/dist-public/src/social/youtube.d.ts +7 -0
  303. package/dist-public/src/social/youtube.js +217 -0
  304. package/dist-public/src/sourceIngress.d.ts +488 -0
  305. package/dist-public/src/sourceIngress.js +1651 -0
  306. package/dist-public/src/specAfter.d.ts +187 -0
  307. package/dist-public/src/specAfter.js +814 -0
  308. package/dist-public/src/specBudget.d.ts +124 -0
  309. package/dist-public/src/specBudget.js +424 -0
  310. package/dist-public/src/specDecisionSignals.d.ts +38 -0
  311. package/dist-public/src/specDecisionSignals.js +121 -0
  312. package/dist-public/src/specLinkedCommits.d.ts +7 -0
  313. package/dist-public/src/specLinkedCommits.js +107 -0
  314. package/dist-public/src/specSections.d.ts +105 -0
  315. package/dist-public/src/specSections.js +718 -0
  316. package/dist-public/src/statusGlyphs.d.ts +18 -0
  317. package/dist-public/src/statusGlyphs.js +75 -0
  318. package/dist-public/src/swarmCommand.d.ts +10 -0
  319. package/dist-public/src/swarmCommand.js +96 -0
  320. package/dist-public/src/teamsNango.d.ts +114 -0
  321. package/dist-public/src/teamsNango.js +301 -0
  322. package/dist-public/src/tokenStore.d.ts +10 -0
  323. package/dist-public/src/tokenStore.js +77 -0
  324. package/dist-public/src/trustEnvelope.d.ts +78 -0
  325. package/dist-public/src/trustEnvelope.js +392 -0
  326. package/dist-public/src/untrustedText.d.ts +6 -0
  327. package/dist-public/src/untrustedText.js +20 -0
  328. package/dist-public/src/updateNotice.d.ts +41 -0
  329. package/dist-public/src/updateNotice.js +290 -0
  330. package/dist-public/src/vendorYaml.d.ts +1 -0
  331. package/dist-public/src/vendorYaml.js +7374 -0
  332. package/dist-public/src/workflowRunActuals.d.ts +67 -0
  333. package/dist-public/src/workflowRunActuals.js +184 -0
  334. package/dist-public/src/zendeskDiscovery.d.ts +12 -0
  335. package/dist-public/src/zendeskDiscovery.js +513 -0
  336. package/dist-public/src/zendeskNango.d.ts +131 -0
  337. package/dist-public/src/zendeskNango.js +344 -0
  338. package/dist-public/src/zoomNango.d.ts +106 -0
  339. package/dist-public/src/zoomNango.js +351 -0
  340. package/docs/CHANGELOG.md +32 -0
  341. package/docs/THIRD_PARTY_NOTICES.md +36 -0
  342. package/package.json +71 -4
  343. package/schemas/bug-capsule-receipt.schema.json +54 -0
  344. package/schemas/bug-capsule.schema.json +189 -0
  345. package/schemas/discovery-evidence-envelope.schema.json +79 -0
  346. package/schemas/discovery-processing-receipt.schema.json +71 -0
  347. package/schemas/discovery-service-contract.schema.json +189 -0
  348. package/skills/AGENTS.md +32 -0
  349. package/skills/README.md +287 -0
  350. package/skills/bug/SKILL.md +49 -0
  351. package/skills/build-preview-spec/SKILL.md +103 -0
  352. package/skills/capture-external-artifact/SKILL.md +23 -0
  353. package/skills/capture-external-artifact/agents/openai.yaml +4 -0
  354. package/skills/capture-external-artifact/references/extractor-libraries.md +27 -0
  355. package/skills/check-spec-health/SKILL.md +45 -0
  356. package/skills/check-spec-health/agents/openai.yaml +4 -0
  357. package/skills/competitive-positioning/SKILL.md +86 -0
  358. package/skills/competitive-positioning/agents/openai.yaml +4 -0
  359. package/skills/cpo-lens/SKILL.md +398 -0
  360. package/skills/cpo-lens/agents/openai.yaml +4 -0
  361. package/skills/create-prd/SKILL.md +26 -0
  362. package/skills/create-prd/agents/openai.yaml +4 -0
  363. package/skills/customer-discovery/SKILL.md +192 -0
  364. package/skills/design-system/SKILL.md +49 -0
  365. package/skills/design-system/agents/openai.yaml +4 -0
  366. package/skills/discussion-guide/SKILL.md +75 -0
  367. package/skills/discussion-guide/agents/openai.yaml +4 -0
  368. package/skills/firecrawl-research-papers/SKILL.md +96 -0
  369. package/skills/firecrawl-research-papers/agents/openai.yaml +4 -0
  370. package/skills/foo-roast/SKILL.md +325 -0
  371. package/skills/generate-gtm-brief/SKILL.md +41 -0
  372. package/skills/generate-gtm-brief/agents/openai.yaml +4 -0
  373. package/skills/generate-gtm-brief/references/gtm-brief-template.md +56 -0
  374. package/skills/gong-quotes/SKILL.md +43 -0
  375. package/skills/gong-quotes/agents/openai.yaml +4 -0
  376. package/skills/grill-me/SKILL.md +27 -0
  377. package/skills/grill-me/agents/openai.yaml +4 -0
  378. package/skills/ideate/SKILL.md +180 -0
  379. package/skills/ideate/references/family-packs.md +5 -0
  380. package/skills/initiative-tracker/SKILL.md +23 -0
  381. package/skills/initiative-tracker/agents/openai.yaml +4 -0
  382. package/skills/interop-review/SKILL.md +25 -0
  383. package/skills/interop-review/agents/openai.yaml +4 -0
  384. package/skills/interview-me/SKILL.md +404 -0
  385. package/skills/interview-me/agents/openai.yaml +4 -0
  386. package/skills/loop-library-audit/SKILL.md +123 -0
  387. package/skills/pitch/SKILL.md +110 -0
  388. package/skills/pitch/references/opportunity-assessment.md +136 -0
  389. package/skills/pitch/references/prfaq-template.md +54 -0
  390. package/skills/pmm-release-briefing/SKILL.md +27 -0
  391. package/skills/pmm-release-briefing/agents/openai.yaml +4 -0
  392. package/skills/pmm-release-briefing/references/variant-generalized.md +16 -0
  393. package/skills/positioning-messaging/SKILL.md +70 -0
  394. package/skills/positioning-messaging/agents/openai.yaml +4 -0
  395. package/skills/positioning-messaging/references/guest-insights.md +1400 -0
  396. package/skills/preview-swarm/SKILL.md +418 -0
  397. package/skills/preview-swarm/agents/openai.yaml +4 -0
  398. package/skills/preview-swarm/references/html-effectiveness-build-previews.md +168 -0
  399. package/skills/product-copywriting/SKILL.md +127 -0
  400. package/skills/product-copywriting/agents/openai.yaml +4 -0
  401. package/skills/product-copywriting/references/foo-voice.md +26 -0
  402. package/skills/product-shape-interview/SKILL.md +219 -0
  403. package/skills/product-shape-interview/agents/openai.yaml +4 -0
  404. package/skills/ready-for-release/SKILL.md +39 -0
  405. package/skills/ready-for-release/agents/openai.yaml +4 -0
  406. package/skills/redact-transcripts/SKILL.md +23 -0
  407. package/skills/redact-transcripts/agents/openai.yaml +4 -0
  408. package/skills/research-brief/SKILL.md +48 -0
  409. package/skills/research-brief/agents/openai.yaml +4 -0
  410. package/skills/research-refresh/SKILL.md +63 -0
  411. package/skills/research-refresh/references/worked-cases.md +44 -0
  412. package/skills/review-loop/SKILL.md +200 -0
  413. package/skills/review-spec/SKILL.md +32 -0
  414. package/skills/review-spec/agents/openai.yaml +4 -0
  415. package/skills/rfc-from-research/SKILL.md +42 -0
  416. package/skills/rfc-from-research/agents/openai.yaml +4 -0
  417. package/skills/rfc-from-research/references/rfc-pattern.md +69 -0
  418. package/skills/skill-registry.yaml +810 -0
  419. package/skills/spec-cleanup/SKILL.md +103 -0
  420. package/skills/spec-cleanup/agents/openai.yaml +4 -0
  421. package/skills/spec-swarm/SKILL.md +87 -0
  422. package/skills/spec-swarm/agents/openai.yaml +4 -0
  423. package/skills/spec-swarm/template/SKILL.template.md +94 -0
  424. package/skills/spec-swarm/template/personas/accessibility-inclusion.md +40 -0
  425. package/skills/spec-swarm/template/personas/ai-technical-pm.md +69 -0
  426. package/skills/spec-swarm/template/personas/analytics-engineering.md +39 -0
  427. package/skills/spec-swarm/template/personas/applied-ai-eval.md +64 -0
  428. package/skills/spec-swarm/template/personas/business-gm-pm.md +67 -0
  429. package/skills/spec-swarm/template/personas/consumer-craft-pm.md +68 -0
  430. package/skills/spec-swarm/template/personas/content-design.md +40 -0
  431. package/skills/spec-swarm/template/personas/cpo.md +42 -0
  432. package/skills/spec-swarm/template/personas/data-science.md +42 -0
  433. package/skills/spec-swarm/template/personas/developer-experience.md +65 -0
  434. package/skills/spec-swarm/template/personas/dx-product.md +70 -0
  435. package/skills/spec-swarm/template/personas/field.md +43 -0
  436. package/skills/spec-swarm/template/personas/finance-cfo.md +40 -0
  437. package/skills/spec-swarm/template/personas/growth-pm.md +67 -0
  438. package/skills/spec-swarm/template/personas/platform-pm.md +67 -0
  439. package/skills/spec-swarm/template/personas/pmm.md +43 -0
  440. package/skills/spec-swarm/template/personas/principal-product-designer.md +42 -0
  441. package/skills/spec-swarm/template/personas/product-ops-program.md +41 -0
  442. package/skills/spec-swarm/template/personas/qa-release.md +41 -0
  443. package/skills/spec-swarm/template/personas/responsible-ai-safety.md +64 -0
  444. package/skills/spec-swarm/template/personas/security-privacy-legal.md +40 -0
  445. package/skills/spec-swarm/template/personas/strategy-competitive.md +41 -0
  446. package/skills/spec-swarm/template/personas/technical-architect.md +42 -0
  447. package/skills/spec-swarm/template/personas/technical-lead.md +43 -0
  448. package/skills/spec-swarm/template/personas/user-research.md +41 -0
  449. package/skills/spec-swarm-accessibility-inclusion/SKILL.md +95 -0
  450. package/skills/spec-swarm-accessibility-inclusion/agents/openai.yaml +4 -0
  451. package/skills/spec-swarm-ai-technical-pm/SKILL.md +124 -0
  452. package/skills/spec-swarm-analytics-engineering/SKILL.md +94 -0
  453. package/skills/spec-swarm-analytics-engineering/agents/openai.yaml +4 -0
  454. package/skills/spec-swarm-applied-ai-eval/SKILL.md +120 -0
  455. package/skills/spec-swarm-business-gm-pm/SKILL.md +122 -0
  456. package/skills/spec-swarm-consumer-craft-pm/SKILL.md +122 -0
  457. package/skills/spec-swarm-content-design/SKILL.md +95 -0
  458. package/skills/spec-swarm-content-design/agents/openai.yaml +4 -0
  459. package/skills/spec-swarm-cpo/SKILL.md +94 -0
  460. package/skills/spec-swarm-cpo/agents/openai.yaml +4 -0
  461. package/skills/spec-swarm-data-science/SKILL.md +95 -0
  462. package/skills/spec-swarm-data-science/agents/openai.yaml +4 -0
  463. package/skills/spec-swarm-developer-experience/SKILL.md +121 -0
  464. package/skills/spec-swarm-dx-product/SKILL.md +126 -0
  465. package/skills/spec-swarm-field/SKILL.md +96 -0
  466. package/skills/spec-swarm-field/agents/openai.yaml +4 -0
  467. package/skills/spec-swarm-finance-cfo/SKILL.md +95 -0
  468. package/skills/spec-swarm-finance-cfo/agents/openai.yaml +4 -0
  469. package/skills/spec-swarm-growth-pm/SKILL.md +122 -0
  470. package/skills/spec-swarm-platform-pm/SKILL.md +122 -0
  471. package/skills/spec-swarm-pmm/SKILL.md +96 -0
  472. package/skills/spec-swarm-pmm/agents/openai.yaml +4 -0
  473. package/skills/spec-swarm-principal-product-designer/SKILL.md +95 -0
  474. package/skills/spec-swarm-principal-product-designer/agents/openai.yaml +4 -0
  475. package/skills/spec-swarm-product-ops-program/SKILL.md +96 -0
  476. package/skills/spec-swarm-product-ops-program/agents/openai.yaml +4 -0
  477. package/skills/spec-swarm-qa-release/SKILL.md +96 -0
  478. package/skills/spec-swarm-qa-release/agents/openai.yaml +4 -0
  479. package/skills/spec-swarm-responsible-ai-safety/SKILL.md +120 -0
  480. package/skills/spec-swarm-security-privacy-legal/SKILL.md +95 -0
  481. package/skills/spec-swarm-security-privacy-legal/agents/openai.yaml +4 -0
  482. package/skills/spec-swarm-strategy-competitive/SKILL.md +96 -0
  483. package/skills/spec-swarm-strategy-competitive/agents/openai.yaml +4 -0
  484. package/skills/spec-swarm-technical-architect/SKILL.md +95 -0
  485. package/skills/spec-swarm-technical-architect/agents/openai.yaml +4 -0
  486. package/skills/spec-swarm-technical-lead/SKILL.md +96 -0
  487. package/skills/spec-swarm-technical-lead/agents/openai.yaml +4 -0
  488. package/skills/spec-swarm-user-research/SKILL.md +95 -0
  489. package/skills/spec-swarm-user-research/agents/openai.yaml +4 -0
  490. package/skills/strategy-check/SKILL.md +170 -0
  491. package/skills/strategy-check/agents/openai.yaml +4 -0
  492. package/skills/study-digest/SKILL.md +41 -0
  493. package/skills/synthetic-customer/SKILL.md +68 -0
  494. package/skills/update-cli-spec/SKILL.md +69 -0
  495. package/skills/update-cli-spec/agents/openai.yaml +4 -0
  496. package/skills/voc-researcher/SKILL.md +45 -0
  497. package/skills/voc-researcher/agents/openai.yaml +4 -0
  498. package/skills-lock.json +35 -0
@@ -0,0 +1,89 @@
1
+ const MAX_SECRET_BYTES = 8192;
2
+ export async function readSecretFromStream(input, maxBytes = MAX_SECRET_BYTES) {
3
+ const chunks = [];
4
+ let total = 0;
5
+ for await (const chunkValue of input) {
6
+ const chunk = Buffer.isBuffer(chunkValue) ? chunkValue : Buffer.from(String(chunkValue));
7
+ total += chunk.length;
8
+ if (total > maxBytes)
9
+ throw new Error("secret input is too large");
10
+ chunks.push(chunk);
11
+ }
12
+ const bytes = Buffer.concat(chunks);
13
+ const text = bytes.toString("utf8");
14
+ if (!Buffer.from(text, "utf8").equals(bytes))
15
+ throw new Error("secret input is not valid UTF-8");
16
+ bytes.fill(0);
17
+ const trimmed = text.trim();
18
+ if (!trimmed)
19
+ throw new Error("secret input is empty");
20
+ return trimmed;
21
+ }
22
+ export function readMaskedSecret(io) {
23
+ const input = io.input;
24
+ const output = io.output;
25
+ if (!input.isTTY || typeof input.setRawMode !== "function") {
26
+ return Promise.reject(new Error("masked secret input requires an interactive terminal"));
27
+ }
28
+ output.write(io.prompt);
29
+ return new Promise((resolve, reject) => {
30
+ let secret = "";
31
+ let settled = false;
32
+ const finish = (error) => {
33
+ if (settled)
34
+ return;
35
+ settled = true;
36
+ input.off("data", onData);
37
+ try {
38
+ input.setRawMode(false);
39
+ }
40
+ catch {
41
+ // Best effort terminal restoration after the descriptor disappears.
42
+ }
43
+ input.pause();
44
+ output.write("\n");
45
+ if (error)
46
+ reject(error);
47
+ else if (!secret.trim())
48
+ reject(new Error("secret input is empty"));
49
+ else
50
+ resolve(secret.trim());
51
+ };
52
+ const onData = (chunkValue) => {
53
+ const chunk = Buffer.isBuffer(chunkValue) ? chunkValue.toString("utf8") : chunkValue;
54
+ for (const character of chunk) {
55
+ if (character === "\u0003" || character === "\u0004") {
56
+ finish(new Error("secret input cancelled"));
57
+ return;
58
+ }
59
+ if (character === "\r" || character === "\n") {
60
+ finish();
61
+ return;
62
+ }
63
+ if (character === "\u007f" || character === "\b") {
64
+ if (secret.length > 0) {
65
+ secret = secret.slice(0, -1);
66
+ output.write("\b \b");
67
+ }
68
+ continue;
69
+ }
70
+ if (character >= " " && character <= "~") {
71
+ if (Buffer.byteLength(secret) >= MAX_SECRET_BYTES) {
72
+ finish(new Error("secret input is too large"));
73
+ return;
74
+ }
75
+ secret += character;
76
+ output.write("*");
77
+ }
78
+ }
79
+ };
80
+ try {
81
+ input.setRawMode(true);
82
+ input.resume();
83
+ input.on("data", onData);
84
+ }
85
+ catch (error) {
86
+ finish(error instanceof Error ? error : new Error("could not read masked secret"));
87
+ }
88
+ });
89
+ }
@@ -0,0 +1,7 @@
1
+ export type SelfCheckResult = {
2
+ output: string;
3
+ exitCode: number;
4
+ };
5
+ /** Supported checks for this build. */
6
+ export declare function selfUsage(): string;
7
+ export declare function runSelfCheck(root: string, command: string | undefined, args: string[]): SelfCheckResult;
@@ -0,0 +1,473 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { UsageError } from "./errorTaxonomy.js";
4
+ import { FOO_COMMAND_CONTRACT, validateCommandDocs, validateCommittedLaunchers } from "./commandContract.js";
5
+ import { gitRunOrNull } from "./gitRun.js";
6
+ import { commitTrailerSignal } from "./specLinkedCommits.js";
7
+ import { PII_PROFILE, scanText } from "./redactEngine.js";
8
+ import { loadRepoAllowlist, REDACT_ALLOW_FILE } from "./redactConfig.js";
9
+ import { ARMED_LOOP_STATES, loadLoopContracts } from "./loopContracts.js";
10
+ import { estimateLoopBudget, handshakeFloorUsd, hasExternalSources, loadPricing } from "./loopBudget.js";
11
+ import { allowlistDigest, loadScanCache, saveScanCache, SCAN_DETECTOR_VERSION, } from "./selfCheckCache.js";
12
+ const SECRET_NAME = /(^|[._-])(secret|secrets|credential|credentials|transcript|transcripts)([._-]|$)/i;
13
+ const SECRET_EXT = /\.(env|key|pem|pfx|p12)$/i;
14
+ // B1 for feedback content (feedback_triage_v0 RISK-001). A feedback document is
15
+ // raw private material about a named person, so it may not be tracked until it
16
+ // has been through the `pii` profile.
17
+ //
18
+ // Recognising feedback content is deliberately NARROW, and it is the whole
19
+ // design decision here. Matching on the word "feedback" anywhere would fire on
20
+ // src/feedback.ts and every skill that mentions the word — a gate that fires on
21
+ // the mention of a risk rather than the risk, which is the failure this repo has
22
+ // now learned four times. So a tracked file is feedback content only when it
23
+ // says so, or when it sits in a directory that exists to hold it:
24
+ // 1. it carries an explicit `foo-feedback: true` marker line (what
25
+ // `foo triage-feedback` will write), or
26
+ // 2. it is a .md/.txt document inside a directory named `feedback`.
27
+ // Everything else — including source files about feedback — is untouched.
28
+ const FEEDBACK_DOC_EXT = /\.(md|txt)$/i;
29
+ const FEEDBACK_MARKER = /^\s*(?:#\s*)?foo-feedback:\s*true\s*$/im;
30
+ function isFeedbackContent(file, text) {
31
+ if (FEEDBACK_MARKER.test(text))
32
+ return true;
33
+ return FEEDBACK_DOC_EXT.test(file) && file.split("/").slice(0, -1).includes("feedback");
34
+ }
35
+ function trackedFiles(root) {
36
+ const out = gitRunOrNull(["ls-files"], { cwd: root });
37
+ if (out === null)
38
+ return null;
39
+ return out
40
+ .split(/\r?\n/)
41
+ .filter((line) => line.trim().length > 0);
42
+ }
43
+ // Crude binary guard: a NUL byte in the first chunk means skip (not text).
44
+ function isProbablyText(buf) {
45
+ const n = Math.min(buf.length, 4096);
46
+ for (let i = 0; i < n; i++)
47
+ if (buf[i] === 0)
48
+ return false;
49
+ return true;
50
+ }
51
+ // The two B1 messages, built in exactly one place so a cached finding and a
52
+ // live scan produce byte-identical output.
53
+ function secretFindingMessage(file, f) {
54
+ return `high-confidence ${f.detector} in ${file}:${f.line} — redact (\`foo redact\`) or mark the line with a foo-redact:allow pragma`;
55
+ }
56
+ function piiFindingMessage(file, f) {
57
+ return `unredacted personal detail (${f.detector}) in tracked feedback content ${file}:${f.line} — run \`foo redact ${file} --profile secrets,pii --write\` before tracking it`;
58
+ }
59
+ function pushFileFindings(findings, file, scan) {
60
+ for (const f of scan.findings) {
61
+ findings.push({ id: "B1", message: secretFindingMessage(file, f) });
62
+ }
63
+ if (scan.pii === null)
64
+ return;
65
+ for (const f of scan.pii) {
66
+ findings.push({ id: "B1", message: piiFindingMessage(file, f) });
67
+ }
68
+ }
69
+ function scanTrackedFile(absPath, relPath, allowlist) {
70
+ let buf;
71
+ try {
72
+ buf = readFileSync(absPath);
73
+ }
74
+ catch {
75
+ return null; // tracked but unreadable (deleted, permissions) — same skip as before
76
+ }
77
+ if (buf.length > 512 * 1024)
78
+ return { skipped: "large", findings: [], pii: null };
79
+ if (!isProbablyText(buf))
80
+ return { skipped: "binary", findings: [], pii: null };
81
+ const text = buf.toString("utf8");
82
+ const findings = [];
83
+ for (const f of scanText(text, { allowlist }).findings) {
84
+ if (f.confidence === "high")
85
+ findings.push({ detector: f.detector, line: f.line });
86
+ }
87
+ // Feedback content gets the pii profile on top of the secrets scan (only
88
+ // high-confidence personal identifiers block — see the isFeedbackContent
89
+ // note above). `pii: null` records "not feedback content" so the warm path
90
+ // reproduces the exact decision without re-reading.
91
+ let pii = null;
92
+ if (isFeedbackContent(relPath, text)) {
93
+ pii = [];
94
+ for (const f of scanText(text, { allowlist, profiles: [PII_PROFILE] }).findings) {
95
+ if (f.confidence === "high")
96
+ pii.push({ detector: f.detector, line: f.line });
97
+ }
98
+ }
99
+ return { findings, pii };
100
+ }
101
+ function statSafe(absPath) {
102
+ try {
103
+ // statSync (not lstat): readFileSync follows symlinks, so the content key
104
+ // must describe the target, not the link.
105
+ const st = statSync(absPath);
106
+ return { mtimeMs: st.mtimeMs, size: st.size };
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
112
+ // A cache entry is a hit only when the file it describes is unchanged on disk:
113
+ // same mtime, same size. Anything else reads as cold — a rescan.
114
+ function cacheHit(absPath, entry) {
115
+ const st = statSafe(absPath);
116
+ return st !== null && st.mtimeMs === entry.mtimeMs && st.size === entry.size;
117
+ }
118
+ function sameEntries(a, b) {
119
+ const keysA = Object.keys(a);
120
+ if (keysA.length !== Object.keys(b).length)
121
+ return false;
122
+ for (const key of keysA) {
123
+ if (JSON.stringify(a[key]) !== JSON.stringify(b[key]))
124
+ return false;
125
+ }
126
+ return true;
127
+ }
128
+ function checkSafety(root, tracked, full) {
129
+ if (!tracked)
130
+ return { findings: [], verified: false };
131
+ const findings = [];
132
+ for (const file of tracked) {
133
+ const base = path.basename(file);
134
+ if (file === ".foo" || file.startsWith(".foo/")) {
135
+ findings.push({ id: "B1", message: `tracked file under local productfoo state: ${file}` });
136
+ }
137
+ else if (["docs/pitch", "docs/playbooks", "docs/research"].some((prefix) => file === prefix || file.startsWith(`${prefix}/`))) {
138
+ findings.push({ id: "B1", message: `tracked file under legacy private scratch: ${file}` });
139
+ }
140
+ else if (SECRET_NAME.test(base) || SECRET_EXT.test(base)) {
141
+ findings.push({ id: "B1", message: `tracked file looks like raw private material: ${file}` });
142
+ }
143
+ }
144
+ const allowlist = loadRepoAllowlist(root);
145
+ const head = gitRunOrNull(["rev-parse", "HEAD"], { cwd: root })?.trim() ?? null;
146
+ const allowDigest = allowlistDigest(root, REDACT_ALLOW_FILE);
147
+ // --full rescans every tracked file (the first-run/CI path, on demand) — but
148
+ // it still records results, so it doubles as a cache refresh.
149
+ const cache = full ? null : loadScanCache(root, head, SCAN_DETECTOR_VERSION, allowDigest);
150
+ // Caching is on whenever the run is keyable (HEAD known) — a cold run records
151
+ // entries too, that is how the cache warms. `cache` being null only means no
152
+ // prior results to reuse.
153
+ const caching = head !== null;
154
+ const entries = {};
155
+ for (const file of tracked) {
156
+ if (file === REDACT_ALLOW_FILE)
157
+ continue; // the allowlist itself lists secret-shaped values
158
+ const abs = path.join(root, file);
159
+ if (cache) {
160
+ const cached = cache.entries[file];
161
+ if (cached && cacheHit(abs, cached)) {
162
+ entries[file] = cached;
163
+ pushFileFindings(findings, file, cached);
164
+ continue;
165
+ }
166
+ }
167
+ const scan = scanTrackedFile(abs, file, allowlist);
168
+ if (scan === null)
169
+ continue; // unreadable — same skip as before, nothing to cache
170
+ if (caching) {
171
+ const st = statSafe(abs);
172
+ if (st)
173
+ entries[file] = { ...scan, mtimeMs: st.mtimeMs, size: st.size };
174
+ }
175
+ pushFileFindings(findings, file, scan);
176
+ }
177
+ // Skip the rewrite when nothing changed: a warm run over an unchanged tree
178
+ // stays read-only. `repo` is resolved so the same repo reached as "." and as
179
+ // an absolute path shares one cache file and one validity check.
180
+ if (caching && (cache === null || !sameEntries(entries, cache.entries))) {
181
+ saveScanCache({
182
+ schema_version: 1,
183
+ repo: path.resolve(root),
184
+ head,
185
+ detector_version: SCAN_DETECTOR_VERSION,
186
+ allowlist_digest: allowDigest,
187
+ entries,
188
+ });
189
+ }
190
+ return { findings, verified: true };
191
+ }
192
+ function specHomes(root) {
193
+ const homes = [];
194
+ for (const dir of ["specs", "examples"]) {
195
+ const base = path.join(root, dir);
196
+ if (!existsSync(base))
197
+ continue;
198
+ for (const entry of readdirSync(base, { withFileTypes: true })) {
199
+ if (!entry.isDirectory())
200
+ continue;
201
+ const specPath = path.join(base, entry.name, "spec.md");
202
+ if (existsSync(specPath))
203
+ homes.push(specPath);
204
+ }
205
+ }
206
+ return homes;
207
+ }
208
+ // B2 — every committed spec.md has a present, unique Spec ID, read from YAML
209
+ // frontmatter (`spec_id`, canonical per T-013) or the transitional `Spec ID:`
210
+ // line.
211
+ function specIdFor(specText) {
212
+ if (specText.startsWith("---")) {
213
+ const end = specText.indexOf("\n---", 3);
214
+ if (end !== -1) {
215
+ const fm = specText.slice(3, end).match(/^spec_id:\s*(\S+)\s*$/m);
216
+ if (fm)
217
+ return fm[1];
218
+ }
219
+ }
220
+ const line = specText.match(/^Spec ID:\s*(\S+)\s*$/m);
221
+ return line ? line[1] : null;
222
+ }
223
+ function checkSpecIdentity(root) {
224
+ const findings = [];
225
+ const seen = new Map();
226
+ for (const specPath of specHomes(root)) {
227
+ const rel = path.relative(root, specPath);
228
+ const id = specIdFor(readFileSync(specPath, "utf8"));
229
+ if (!id) {
230
+ findings.push({ id: "B2", message: `${rel} is missing a Spec ID` });
231
+ continue;
232
+ }
233
+ const prior = seen.get(id);
234
+ if (prior)
235
+ findings.push({ id: "B2", message: `duplicate Spec ID ${id}: ${rel} and ${prior}` });
236
+ else
237
+ seen.set(id, rel);
238
+ }
239
+ return findings;
240
+ }
241
+ // B4 — autonomy. An autonomous loop needs a well-formed contract AND an agreed
242
+ // budget. This boundary used to be honor-system: `foo loop check` enforced it,
243
+ // but nothing invoked `foo loop check` — no hook, no skill, and `self check`
244
+ // itself only covered B1/B2. So a packet could carry a scheduled, acting,
245
+ // unbudgeted loop and every automated gate in the repo would pass it. B1 gets a
246
+ // fail-closed pre-commit hook; B4 got a command you had to remember to type.
247
+ //
248
+ // Now B4 is checked wherever B1 and B2 are. Strictly READ-ONLY: `foo loop check`
249
+ // may WRITE an auto-approved budget, but a checker must never mutate the thing it
250
+ // is checking — so this re-derives the same decision purely and reports it.
251
+ //
252
+ // Two things keep this from being merely strict:
253
+ // 1. Only an ARMED loop blocks. B4 governs "any loop or autonomous action" — a
254
+ // `draft` contract takes no action, so drafting one must not make the repo
255
+ // un-committable. A draft is a signal; the moment it is armed (ready/active)
256
+ // the budget handshake becomes a hard boundary. That is exactly when the
257
+ // money can start being spent.
258
+ // 2. An unbudgeted loop that provably prices BELOW the handshake floor is a
259
+ // signal, not a block — cheap work keeps its momentum (B4's size-aware rule).
260
+ // Otherwise it blocks: malformed contract, or armed + unbudgeted + unable to prove
261
+ // it is cheap (unpriced, unmeasurable sources, or genuinely at/above the floor).
262
+ function checkAutonomy(root) {
263
+ const boundaries = [];
264
+ const signals = [];
265
+ const specsDir = path.join(root, "specs");
266
+ if (!existsSync(specsDir))
267
+ return { boundaries, signals };
268
+ const floor = handshakeFloorUsd();
269
+ const pricing = loadPricing(root);
270
+ for (const entry of readdirSync(specsDir, { withFileTypes: true })) {
271
+ if (!entry.isDirectory())
272
+ continue;
273
+ const packetDir = path.join(specsDir, entry.name);
274
+ if (!existsSync(path.join(packetDir, "loops.yaml")))
275
+ continue;
276
+ let loops;
277
+ try {
278
+ loops = loadLoopContracts(packetDir).loops;
279
+ }
280
+ catch (error) {
281
+ boundaries.push({
282
+ id: "B4",
283
+ message: `specs/${entry.name}/loops.yaml is a malformed loop contract: ${error instanceof Error ? error.message : String(error)}`,
284
+ });
285
+ continue;
286
+ }
287
+ for (const loop of loops) {
288
+ if (loop.budget?.agreed)
289
+ continue; // handshake already done
290
+ const estimate = estimateLoopBudget({
291
+ packetDir,
292
+ sources: loop.observe.sources,
293
+ pricing,
294
+ unit: "usd",
295
+ budget: loop.budget,
296
+ });
297
+ const unmeasurable = hasExternalSources(estimate.measured);
298
+ const belowFloor = estimate.priced && !unmeasurable && estimate.usd.expected < floor;
299
+ if (belowFloor) {
300
+ signals.push({
301
+ id: "signal",
302
+ message: `specs/${entry.name} loop ${loop.id} has no agreed budget but prices below the $${floor.toFixed(2)} floor ($${estimate.usd.expected.toFixed(2)} expected) — foo loop check will auto-approve it${ARMED_LOOP_STATES.has(loop.status) ? "" : " once it is armed"}`,
303
+ });
304
+ continue;
305
+ }
306
+ // Never quote a dollar figure for an unmeasurable loop — its cost is unknown,
307
+ // and a number here would be invented.
308
+ const why = unmeasurable
309
+ ? "its cost is unknown (it reads sources productfoo cannot size), so it can never prove it is cheap"
310
+ : !estimate.priced
311
+ ? "its cost could not be priced"
312
+ : `it prices at $${estimate.usd.expected.toFixed(2)} expected, at or above the $${floor.toFixed(2)} floor`;
313
+ const fix = unmeasurable
314
+ ? `run foo loop estimate specs/${entry.name} --agree --limit <usd> (you set the cap; productfoo will not invent one)`
315
+ : `run foo loop estimate specs/${entry.name} --agree`;
316
+ // A draft loop cannot act, so it cannot spend — surface it, don't block the
317
+ // repo. Arming it (status: ready|active) is what makes the handshake due.
318
+ if (!ARMED_LOOP_STATES.has(loop.status)) {
319
+ signals.push({
320
+ id: "signal",
321
+ message: `specs/${entry.name} loop ${loop.id} (${loop.status}) has no agreed budget — ${why}; budget it before arming it (${fix})`,
322
+ });
323
+ continue;
324
+ }
325
+ boundaries.push({
326
+ id: "B4",
327
+ message: `specs/${entry.name} loop ${loop.id} is ${loop.status} (armed) with no agreed budget and ${why} — ${fix}`,
328
+ });
329
+ }
330
+ }
331
+ return { boundaries, signals };
332
+ }
333
+ // Signal — skill structure and runtime-neutral wording. Surfaced, never blocking.
334
+ // The command surface is what a user reads before they run anything, so a
335
+ // description that no longer matches the contract is a live lie in every
336
+ // harness's picker. Both halves are checked: `commands/*.md` (the docs) and the
337
+ // committed launchers (`.claude/commands/foo/*`, plugin skills).
338
+ //
339
+ // A signal, not a boundary — AGENTS.md keeps boundaries narrow (B1/B2/B4), and
340
+ // stale copy does not cross one. `foo commands validate` is the gating check;
341
+ // this makes the drift visible in the run people actually do.
342
+ function commandSurfaceSignals(root, tracked) {
343
+ const findings = [];
344
+ const docs = validateCommandDocs(root);
345
+ for (const d of docs.drift) {
346
+ findings.push({
347
+ id: "signal",
348
+ message: `command contract drift: ${d.id}.${d.field} in ${FOO_COMMAND_CONTRACT.find((c) => c.id === d.id)?.docs_path ?? "docs"} — run \`foo commands validate\` for the detail.`,
349
+ });
350
+ }
351
+ if (tracked === null)
352
+ return findings; // no git: the same posture B1 takes.
353
+ const launchers = validateCommittedLaunchers(root, tracked);
354
+ for (const d of launchers.drift) {
355
+ findings.push({
356
+ id: "signal",
357
+ message: `committed launcher drift: ${d.file} advertises ${d.field} "${d.launcher ?? "(missing)"}" but the contract says "${d.contract}" — re-run \`foo agent setup\` and commit the result.`,
358
+ });
359
+ }
360
+ return findings;
361
+ }
362
+ function skillSignals(root) {
363
+ const skillsDir = path.join(root, "skills");
364
+ if (!existsSync(skillsDir))
365
+ return [];
366
+ const registry = existsSync(path.join(skillsDir, "skill-registry.yaml"))
367
+ ? readFileSync(path.join(skillsDir, "skill-registry.yaml"), "utf8")
368
+ : "";
369
+ const forbidden = [
370
+ "AskUserQuestion",
371
+ "TodoWrite",
372
+ "Task tool",
373
+ "Skill tool",
374
+ "WebFetch",
375
+ "ToolSearch",
376
+ "mcp__",
377
+ "Claude Code",
378
+ ];
379
+ const findings = [];
380
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
381
+ if (!entry.isDirectory())
382
+ continue;
383
+ const name = entry.name;
384
+ const skillPath = path.join(skillsDir, name, "SKILL.md");
385
+ if (!existsSync(skillPath))
386
+ continue;
387
+ const text = readFileSync(skillPath, "utf8");
388
+ if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name))
389
+ findings.push({ id: "signal", message: `skill folder name not kebab-case: ${name}` });
390
+ if (!text.startsWith("---\n"))
391
+ findings.push({ id: "signal", message: `${name}/SKILL.md missing frontmatter` });
392
+ if (!text.includes(`name: ${name}`))
393
+ findings.push({ id: "signal", message: `${name}/SKILL.md frontmatter name mismatch` });
394
+ if (!text.includes("## Usage"))
395
+ findings.push({ id: "signal", message: `${name}/SKILL.md missing ## Usage` });
396
+ if (!text.includes("## Error Handling"))
397
+ findings.push({ id: "signal", message: `${name}/SKILL.md missing ## Error Handling` });
398
+ if (registry && !registry.includes(`id: "${name}"`))
399
+ findings.push({ id: "signal", message: `${name} missing from skill-registry.yaml` });
400
+ text.split(/\r?\n/).forEach((line, index) => {
401
+ for (const token of forbidden) {
402
+ if (line.includes(token))
403
+ findings.push({ id: "signal", message: `${name}/SKILL.md:${index + 1} runtime-specific wording: ${token}` });
404
+ }
405
+ });
406
+ }
407
+ return findings;
408
+ }
409
+ /** Supported checks for this build. */
410
+ export function selfUsage() {
411
+ return [
412
+ "foo self — check foo against its own boundaries.",
413
+ "",
414
+ " foo self check [--json] [--full]",
415
+ " run the boundary checks (B1 safety, B2 spec identity, B4 autonomy).",
416
+ " Results are cached per file (mtime, size) under the machine state dir;",
417
+ " --full rescans every tracked file and refreshes the cache.",
418
+ " foo self gate fail-closed secret gate (used as the pre-commit hook)",
419
+ " foo self which which build is running, and from where",
420
+ " foo self help this",
421
+ "",
422
+ "",
423
+ ].join("\n");
424
+ }
425
+ export function runSelfCheck(root, command, args) {
426
+ // A typo is not a crash. Throwing a bare Error here classified as command_error, which
427
+ // `pages()` treats as report-worthy — so `foo self help` manufactured a bug report about
428
+ // itself. UsageError classifies as usage_error: told to the human, never filed.
429
+ if (command && command !== "check") {
430
+ throw new UsageError(`unknown self command: ${command}\n\n${selfUsage()}`, "E_BAD_FLAG");
431
+ }
432
+ const asJson = args.includes("--json");
433
+ const full = args.includes("--full");
434
+ // One `git ls-files` per run, shared by the B1 scan and the launcher-surface
435
+ // signals — the scan used to spawn it twice (cli_performance_v0 R-003).
436
+ const tracked = trackedFiles(root);
437
+ const safety = checkSafety(root, tracked, full);
438
+ const autonomy = checkAutonomy(root);
439
+ const boundaries = [...safety.findings, ...checkSpecIdentity(root), ...autonomy.boundaries];
440
+ const signals = [
441
+ ...autonomy.signals,
442
+ ...skillSignals(root),
443
+ ...commandSurfaceSignals(root, tracked),
444
+ ...commitTrailerSignal(root).map((message) => ({ id: "signal", message })),
445
+ ];
446
+ const exitCode = boundaries.length > 0 ? 1 : 0;
447
+ if (asJson) {
448
+ const payload = {
449
+ schema_version: "0.1",
450
+ blocked: exitCode !== 0,
451
+ safety_verified: safety.verified,
452
+ boundaries,
453
+ signals,
454
+ };
455
+ return { output: `${JSON.stringify(payload, null, 2)}\n`, exitCode };
456
+ }
457
+ const lines = [
458
+ "foo self check",
459
+ "",
460
+ "Boundaries (block):",
461
+ ...(boundaries.length === 0
462
+ ? [" none — B1 safety, B2 spec identity, and B4 autonomy hold."]
463
+ : boundaries.map((f) => ` [${f.id}] ${f.message}`)),
464
+ ...(safety.verified ? [] : [" note: B1 could not be verified (git unavailable); treating as advisory."]),
465
+ "",
466
+ "Signals (surface, do not block):",
467
+ ...(signals.length === 0 ? [" none."] : signals.map((f) => ` - ${f.message}`)),
468
+ "",
469
+ exitCode === 0 ? "Result: boundaries hold." : "Result: boundary violation — resolve before committing.",
470
+ "",
471
+ ];
472
+ return { output: lines.join("\n"), exitCode };
473
+ }
@@ -0,0 +1,29 @@
1
+ export declare const SCAN_DETECTOR_VERSION = "self-check-scan-cache-v1";
2
+ export type CachedFinding = {
3
+ detector: string;
4
+ line: number;
5
+ };
6
+ export type ScanCacheEntry = {
7
+ mtimeMs: number;
8
+ size: number;
9
+ skipped?: "large" | "binary";
10
+ findings: CachedFinding[];
11
+ pii: CachedFinding[] | null;
12
+ };
13
+ export type ScanCache = {
14
+ schema_version: 1;
15
+ repo: string;
16
+ head: string;
17
+ detector_version: string;
18
+ allowlist_digest: string | null;
19
+ entries: Record<string, ScanCacheEntry>;
20
+ };
21
+ export declare const MAX_CACHE_ENTRIES = 50000;
22
+ export declare const MAX_CACHE_BYTES: number;
23
+ export declare const MAX_CACHE_FILES = 128;
24
+ export declare function scanCachePath(root: string): string;
25
+ export declare function allowlistDigest(root: string, allowFile: string): string | null;
26
+ export declare function loadScanCache(root: string, head: string | null, detectorVersion: string, allowlistDigestValue: string | null): ScanCache | null;
27
+ export declare function capScanCacheEntries(entries: Record<string, ScanCacheEntry>, maxEntries: number): Record<string, ScanCacheEntry>;
28
+ export declare function pruneScanCacheDir(dir: string, maxFiles: number): void;
29
+ export declare function saveScanCache(cache: ScanCache): boolean;