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,494 @@
1
+ import { gitRun } from "./gitRun.js";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { configPath } from "./configPath.js";
5
+ import { parse as parseYaml } from "./vendorYaml.js";
6
+ import { redactText, scanText } from "./redactEngine.js";
7
+ import { fileStatKey, readMemoized, writeMemoized } from "./processMemo.js";
8
+ import { deriveCountTokensUrl, resolveActiveModel } from "./activeModel.js";
9
+ // Token measurement has two tiers. The structural tier approximates tokens from
10
+ // source size — the offline default, deterministic and never presented as
11
+ // precise. When an API key is available, foo measures `observe.sources` with
12
+ // Anthropic `count_tokens` instead (never tiktoken — tiktoken is OpenAI's
13
+ // tokenizer and undercounts Claude tokens). The cost model around the
14
+ // measurement stays heuristic in V0 (per-pass act/verify projections and
15
+ // iteration counts), so `basis` remains "heuristic"; `measured_method` records
16
+ // which tier produced the token counts.
17
+ const CHARS_PER_TOKEN = 4;
18
+ const ANTHROPIC_VERSION = "2023-06-01";
19
+ const DEFAULT_ACT_TOKENS = 2000;
20
+ const DEFAULT_VERIFY_TOKENS = 1000;
21
+ const DEFAULT_EXPECTED_ITERATIONS = 2;
22
+ const DEFAULT_CEILING_ITERATIONS = 5;
23
+ const BUDGET_KEYS = new Set([
24
+ "unit",
25
+ "limit",
26
+ "estimate",
27
+ "agreed",
28
+ "max_iterations",
29
+ "no_progress_after",
30
+ "on_exceed",
31
+ "warn_at",
32
+ ]);
33
+ const ESTIMATE_KEYS = new Set([
34
+ "expected",
35
+ "ceiling",
36
+ "basis",
37
+ "confidence",
38
+ "per_pass_tokens",
39
+ "model_mix",
40
+ "priced_with",
41
+ ]);
42
+ function isObject(value) {
43
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
44
+ }
45
+ function rejectUnknown(value, allowed, label) {
46
+ for (const key of Object.keys(value)) {
47
+ if (!allowed.has(key))
48
+ throw new Error(`${label} has unknown key: ${key}`);
49
+ }
50
+ }
51
+ function requireNumber(value, label) {
52
+ if (typeof value !== "number" || Number.isNaN(value))
53
+ throw new Error(`${label} must be a number`);
54
+ return value;
55
+ }
56
+ function requireString(value, label) {
57
+ if (typeof value !== "string" || value.length === 0)
58
+ throw new Error(`${label} must be a nonempty string`);
59
+ return value;
60
+ }
61
+ // Parse and validate a `budget` block. Unknown keys are rejected so the
62
+ // contract stays stable (R-001). `estimate` and `agreed` are optional: a budget
63
+ // may be authored before the handshake; its missing `agreed` is what
64
+ // `foo loop check --strict-budget` rejects (R-005).
65
+ export function parseBudget(raw, label) {
66
+ if (!isObject(raw))
67
+ throw new Error(`${label} must be an object`);
68
+ rejectUnknown(raw, BUDGET_KEYS, label);
69
+ const unit = "unit" in raw ? requireString(raw.unit, `${label}.unit`) : "usd";
70
+ if (unit !== "usd" && unit !== "tokens")
71
+ throw new Error(`${label}.unit must be usd or tokens`);
72
+ const budget = {
73
+ unit,
74
+ limit: requireNumber(raw.limit, `${label}.limit`),
75
+ };
76
+ if ("max_iterations" in raw)
77
+ budget.max_iterations = requireNumber(raw.max_iterations, `${label}.max_iterations`);
78
+ if ("no_progress_after" in raw)
79
+ budget.no_progress_after = requireNumber(raw.no_progress_after, `${label}.no_progress_after`);
80
+ if ("warn_at" in raw)
81
+ budget.warn_at = requireNumber(raw.warn_at, `${label}.warn_at`);
82
+ if ("on_exceed" in raw) {
83
+ const onExceed = requireString(raw.on_exceed, `${label}.on_exceed`);
84
+ if (onExceed !== "ask" && onExceed !== "stop" && onExceed !== "checkpoint") {
85
+ throw new Error(`${label}.on_exceed must be ask, stop, or checkpoint`);
86
+ }
87
+ budget.on_exceed = onExceed;
88
+ }
89
+ if ("estimate" in raw) {
90
+ const est = raw.estimate;
91
+ if (!isObject(est))
92
+ throw new Error(`${label}.estimate must be an object`);
93
+ rejectUnknown(est, ESTIMATE_KEYS, `${label}.estimate`);
94
+ budget.estimate = {
95
+ expected: requireNumber(est.expected, `${label}.estimate.expected`),
96
+ ceiling: requireNumber(est.ceiling, `${label}.estimate.ceiling`),
97
+ basis: requireString(est.basis, `${label}.estimate.basis`),
98
+ confidence: requireString(est.confidence, `${label}.estimate.confidence`),
99
+ per_pass_tokens: est.per_pass_tokens,
100
+ model_mix: est.model_mix,
101
+ priced_with: requireString(est.priced_with, `${label}.estimate.priced_with`),
102
+ };
103
+ }
104
+ if ("agreed" in raw) {
105
+ const agreed = raw.agreed;
106
+ if (!isObject(agreed))
107
+ throw new Error(`${label}.agreed must be an object`);
108
+ budget.agreed = {
109
+ by: requireString(agreed.by, `${label}.agreed.by`),
110
+ at: requireString(agreed.at, `${label}.agreed.at`),
111
+ event: requireString(agreed.event, `${label}.agreed.event`),
112
+ };
113
+ }
114
+ return budget;
115
+ }
116
+ // Load the dated price table. A missing table is not a throw and not a silent
117
+ // default (model-roster-data-v0 R-002): it returns a degraded, empty table with
118
+ // an explicit worded finding, so dollar estimates downgrade to token-only and
119
+ // every surface says why. A present-but-malformed table still throws — corrupt
120
+ // data is an error, absent data is a degraded mode.
121
+ // Memoized per process (cli_performance-v0 R-002): loop paths call loadPricing
122
+ // several times per invocation; pricing.yaml is committed data, and the
123
+ // (mtime, size) key keeps an out-of-band edit honest.
124
+ const pricingCache = new Map();
125
+ export function loadPricing(root) {
126
+ const pricingPath = configPath(root, "pricing.yaml");
127
+ const key = fileStatKey(pricingPath);
128
+ const cached = readMemoized(pricingCache, pricingPath, key);
129
+ if (cached !== undefined)
130
+ return cached;
131
+ const value = computePricingTable(pricingPath);
132
+ writeMemoized(pricingCache, pricingPath, key, value);
133
+ return value;
134
+ }
135
+ function computePricingTable(pricingPath) {
136
+ if (!existsSync(pricingPath)) {
137
+ return {
138
+ source: "pricing.yaml",
139
+ date: "",
140
+ staleAfterDays: 0,
141
+ currency: "usd",
142
+ models: {},
143
+ available: false,
144
+ findings: ["pricing degraded: pricing.yaml missing at repo root or in config/ — dollar estimates unavailable, token-only estimates only"],
145
+ };
146
+ }
147
+ const doc = parseYaml(readFileSync(pricingPath, "utf8"));
148
+ if (!isObject(doc))
149
+ throw new Error("pricing.yaml must be an object");
150
+ const models = doc.models;
151
+ if (!isObject(models))
152
+ throw new Error("pricing.yaml.models must be an object");
153
+ const parsedModels = {};
154
+ for (const [name, price] of Object.entries(models)) {
155
+ if (!isObject(price))
156
+ throw new Error(`pricing.yaml.models.${name} must be an object`);
157
+ parsedModels[name] = {
158
+ input_per_mtok: requireNumber(price.input_per_mtok, `pricing.yaml.models.${name}.input_per_mtok`),
159
+ output_per_mtok: requireNumber(price.output_per_mtok, `pricing.yaml.models.${name}.output_per_mtok`),
160
+ // Optional (spec-budget-v0 R-008): assumed agent throughput, used to derive
161
+ // runtime. Absent => the estimate reports no time rather than inventing one.
162
+ ...(typeof price.tokens_per_sec === "number" ? { tokens_per_sec: price.tokens_per_sec } : {}),
163
+ ...(typeof price.family === "string" ? { family: price.family } : {}),
164
+ };
165
+ }
166
+ return {
167
+ source: "pricing.yaml",
168
+ date: requireString(doc.date, "pricing.yaml.date"),
169
+ staleAfterDays: typeof doc.stale_after_days === "number" ? doc.stale_after_days : 30,
170
+ currency: typeof doc.currency === "string" ? doc.currency : "usd",
171
+ models: parsedModels,
172
+ available: true,
173
+ findings: [],
174
+ };
175
+ }
176
+ export function pricedWith(table) {
177
+ if (table.available === false)
178
+ return `unpriced (${table.source} missing)`;
179
+ return `${table.source}@${table.date}`;
180
+ }
181
+ /** Convert observed token categories with one model's declared dated rates. */
182
+ export function priceTokenUsage(table, model, usage) {
183
+ const rate = table.available === false ? undefined : table.models[model];
184
+ if (!rate)
185
+ return null;
186
+ const inputTokens = (usage.fresh_input ?? 0) + (usage.cache_write ?? 0) + (usage.cache_read ?? 0);
187
+ const outputTokens = usage.output ?? 0;
188
+ return {
189
+ usd: (inputTokens / 1_000_000) * rate.input_per_mtok + (outputTokens / 1_000_000) * rate.output_per_mtok,
190
+ priced_with: pricedWith(table),
191
+ };
192
+ }
193
+ // Days between the table date and `now`; > staleAfterDays means a staleness
194
+ // signal must surface (R-008).
195
+ export function pricingAgeDays(table, now = new Date()) {
196
+ const priced = new Date(`${table.date}T00:00:00Z`).getTime();
197
+ if (Number.isNaN(priced))
198
+ return Number.POSITIVE_INFINITY;
199
+ return Math.floor((now.getTime() - priced) / (1000 * 60 * 60 * 24));
200
+ }
201
+ export function pricingIsStale(table, now = new Date()) {
202
+ return pricingAgeDays(table, now) > table.staleAfterDays;
203
+ }
204
+ // A loop may observe the WEB, not just packet files (a scheduled research refresh
205
+ // is the motivating case). Such a source CANNOT be sized offline, and we refuse to
206
+ // pretend otherwise.
207
+ //
208
+ // Two wrong answers were considered and rejected:
209
+ // - 0 tokens (the original bug): the loop priced at ~$0, fell under the $0.50
210
+ // handshake floor, and AUTO-APPROVED itself — bypassing the human budget
211
+ // handshake for exactly the loops whose cost is least knowable.
212
+ // - A "conservative default" of N tokens: this is false precision. A fabricated
213
+ // token count flows straight into "$1.0100 expected .. $2.5250 ceiling", which
214
+ // reads as a prediction. Dressing a guess in four decimal places is not honesty.
215
+ //
216
+ // The honest answer is the one foo already uses for missing pricing data: the cost
217
+ // is UNKNOWN. An unmeasurable source contributes no invented tokens; it marks the
218
+ // estimate unmeasurable, which (a) forbids auto-approval and (b) forbids agreeing a
219
+ // dollar budget derived from the estimate. A human may still authorise the loop —
220
+ // but by setting an explicit spend CAP, which is a decision, not a prediction.
221
+ // A source that isn't a path we can stat is treated as external rather than
222
+ // missing: "https://…", "arXiv cs.SE (routing)", "provider pricing pages".
223
+ function looksExternal(source) {
224
+ return /^[a-z]+:\/\//i.test(source) || /\s/.test(source) || /\.(ai|com|dev|org|io)\b/i.test(source);
225
+ }
226
+ // Structural (offline) token approximation — the shared chars/token basis used
227
+ // by both the loop estimator and the spec estimator.
228
+ export function structuralTokens(text) {
229
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
230
+ }
231
+ export function measureSources(packetDir, sources) {
232
+ const measured = sources.map((source) => {
233
+ const sourcePath = path.join(packetDir, source);
234
+ if (existsSync(sourcePath))
235
+ return { source, found: true, tokens: structuralTokens(readFileSync(sourcePath, "utf8")) };
236
+ // Not a file we can read. A web/external source is UNMEASURABLE — it carries no
237
+ // invented token count (that would be false precision); it flags the estimate as
238
+ // resting on an unknown. A genuinely missing packet file stays 0: its absence is
239
+ // the finding, not its size.
240
+ if (looksExternal(source))
241
+ return { source, found: false, external: true, tokens: 0 };
242
+ return { source, found: false, tokens: 0 };
243
+ });
244
+ const total = measured.reduce((sum, item) => sum + item.tokens, 0);
245
+ return { total, measured };
246
+ }
247
+ /** Does this measurement contain a source whose size we could not actually determine? */
248
+ export function hasExternalSources(measured) {
249
+ return measured.some((item) => item.external === true);
250
+ }
251
+ // Count one source's tokens with Anthropic count_tokens. Single non-streaming
252
+ // call; raw fetch keeps this minimal CLI free of an SDK dependency for an
253
+ // optional, offline-first feature. Throws on any non-2xx so the caller can fall
254
+ // back to the structural tier.
255
+ // The count_tokens endpoint follows the active base URL (R-003): on a GLM-via-z.ai
256
+ // session it targets api.z.ai, not api.anthropic.com. Defaults to the native Anthropic
257
+ // endpoint when no URL is supplied so existing callers are unaffected.
258
+ export async function countTokensViaApi(model, text, apiKey, url = deriveCountTokensUrl("https://api.anthropic.com")) {
259
+ const res = await fetch(url, {
260
+ method: "POST",
261
+ headers: {
262
+ "x-api-key": apiKey,
263
+ "anthropic-version": ANTHROPIC_VERSION,
264
+ "content-type": "application/json",
265
+ },
266
+ body: JSON.stringify({ model, messages: [{ role: "user", content: text }] }),
267
+ });
268
+ if (!res.ok)
269
+ throw new Error(`count_tokens HTTP ${res.status}`);
270
+ const data = (await res.json());
271
+ if (typeof data.input_tokens !== "number")
272
+ throw new Error("count_tokens response missing input_tokens");
273
+ return data.input_tokens;
274
+ }
275
+ // --- Egress guard (security-hardening-followups-v0 R-002 / OQ-4) -------------
276
+ // The count_tokens path sends source content to api.anthropic.com. Never egress
277
+ // local-only/gitignored content or content carrying a high-confidence secret;
278
+ // otherwise send a REDACTED copy so no raw secret leaves the machine.
279
+ const LOCAL_ONLY = /(^|\/)integration-events\.jsonl$/; // local-only summary ledger (B1)
280
+ export function isLocalOnly(repoCwd, source, sourcePath) {
281
+ if (LOCAL_ONLY.test(source))
282
+ return true;
283
+ // exit 0 => the path is gitignored; non-zero (not ignored) or git
284
+ // unavailable => false — the secret scan still guards.
285
+ return gitRun(["check-ignore", "-q", "--", sourcePath], { cwd: repoCwd, stdio: ["ignore", "ignore", "ignore"] }).ok;
286
+ }
287
+ // Pure decision (testable without network or git): may this source's text go to
288
+ // the count_tokens API, and in what form?
289
+ export function guardSourceForEgress(text, localOnly) {
290
+ if (localOnly)
291
+ return { egress: false, reason: "local-only / gitignored source" };
292
+ if (scanText(text).counts.high > 0)
293
+ return { egress: false, reason: "high-confidence secret present" };
294
+ return { egress: true, text: redactText(text).redactedText };
295
+ }
296
+ // Measure sources, preferring count_tokens when a key is available and not
297
+ // disabled. Any failure (no key, network, auth, rate limit) falls back to the
298
+ // structural tier rather than blocking an estimate.
299
+ export async function measureSourcesCounted(packetDir, sources, opts) {
300
+ if (!opts.preferApi || !opts.apiKey) {
301
+ return { ...measureSources(packetDir, sources), method: "structural" };
302
+ }
303
+ // D-010 egress gate: on a non-native (routed) endpoint, never send source text to a
304
+ // third-party count_tokens — withhold the call and use the structural tier until the
305
+ // R-006a inference-egress boundary lands. The derived URL stays correct; only the
306
+ // *call* is gated, and the distinct method lets the surface fly ⚠️ (D-002 fail-loud).
307
+ if (opts.routedNonNative) {
308
+ return { ...measureSources(packetDir, sources), method: "structural_egress_gated" };
309
+ }
310
+ try {
311
+ const measured = [];
312
+ for (const source of sources) {
313
+ const sourcePath = path.join(packetDir, source);
314
+ if (!existsSync(sourcePath)) {
315
+ measured.push({ source, found: false, tokens: 0 });
316
+ continue;
317
+ }
318
+ const text = readFileSync(sourcePath, "utf8");
319
+ // R-002: never egress local-only or secret-bearing content — fall back to
320
+ // the structural tier for that source instead.
321
+ const decision = guardSourceForEgress(text, isLocalOnly(packetDir, source, sourcePath));
322
+ const tokens = decision.egress
323
+ ? await countTokensViaApi(opts.model, decision.text, opts.apiKey, opts.countTokensUrl)
324
+ : structuralTokens(text);
325
+ measured.push({ source, found: true, tokens });
326
+ }
327
+ const total = measured.reduce((sum, item) => sum + item.tokens, 0);
328
+ return { total, measured, method: "count_tokens" };
329
+ }
330
+ catch {
331
+ return { ...measureSources(packetDir, sources), method: "structural" };
332
+ }
333
+ }
334
+ function round(value, places) {
335
+ const factor = 10 ** places;
336
+ return Math.round(value * factor) / factor;
337
+ }
338
+ export function estimateLoopBudget(args) {
339
+ const unit = args.unit ?? args.budget?.unit ?? "usd";
340
+ const active = args.active ?? resolveActiveModel();
341
+ // An explicit budget model_mix still wins (a packet may pin its models); otherwise
342
+ // default to the *resolved active* model, not the hardcoded native one (R-002).
343
+ const worker = args.budget?.estimate?.model_mix?.worker ?? active.worker;
344
+ const verifier = args.budget?.estimate?.model_mix?.verifier ?? active.verifier;
345
+ const expectedIterations = args.budget?.no_progress_after ?? DEFAULT_EXPECTED_ITERATIONS;
346
+ const ceilingIterations = args.budget?.max_iterations ?? DEFAULT_CEILING_ITERATIONS;
347
+ const measurement = args.measured ?? { ...measureSources(args.packetDir, args.sources), method: "structural" };
348
+ const observeTokens = measurement.total;
349
+ const measured = measurement.measured;
350
+ const perPass = {
351
+ observe: observeTokens,
352
+ act: DEFAULT_ACT_TOKENS,
353
+ verify: DEFAULT_VERIFY_TOKENS,
354
+ };
355
+ // Degraded pricing (model-roster-data-v0 R-002/R-003): a missing table or an
356
+ // unknown model downgrades the dollar range to token-only with an explicit,
357
+ // worded finding — never a throw, and never another model's rates.
358
+ const findings = [...(args.pricing.findings ?? [])];
359
+ const workerPrice = args.pricing.models[worker];
360
+ const verifierPrice = args.pricing.models[verifier];
361
+ let priced = args.pricing.available !== false;
362
+ if (args.pricing.available !== false) {
363
+ if (!workerPrice)
364
+ findings.push(`unknown model: ${worker} has no price in ${args.pricing.source} — dollar estimate degraded to token-only, no default-model rates assumed`);
365
+ if (!verifierPrice && verifier !== worker)
366
+ findings.push(`unknown model: ${verifier} has no price in ${args.pricing.source} — dollar estimate degraded to token-only, no default-model rates assumed`);
367
+ if (!workerPrice || !verifierPrice)
368
+ priced = false;
369
+ }
370
+ // Worker reads the sources (input) and produces the action (output); the
371
+ // verifier re-reads the same sources (input) and emits its check (output).
372
+ const workerUsage = priceTokenUsage(args.pricing, worker, { fresh_input: perPass.observe, output: perPass.act });
373
+ const verifierUsage = priceTokenUsage(args.pricing, verifier, { fresh_input: perPass.observe, output: perPass.verify });
374
+ const perPassUsd = priced && workerUsage && verifierUsage ? workerUsage.usd + verifierUsage.usd : 0;
375
+ const perPassTokens = perPass.observe + perPass.act + perPass.observe + perPass.verify;
376
+ const usd = {
377
+ expected: round(perPassUsd * expectedIterations, 4),
378
+ ceiling: round(perPassUsd * ceilingIterations, 4),
379
+ };
380
+ const tokens = {
381
+ expected: perPassTokens * expectedIterations,
382
+ ceiling: perPassTokens * ceilingIterations,
383
+ };
384
+ const stale = priced && pricingIsStale(args.pricing, args.now);
385
+ if (stale) {
386
+ findings.push(`pricing stale: ${args.pricing.source}@${args.pricing.date} is ${pricingAgeDays(args.pricing, args.now)} days old (stale_after_days: ${args.pricing.staleAfterDays}) — dollar range priced on stale rates`);
387
+ }
388
+ // An unmeasurable source means the cost is UNKNOWN, not "probably about N". Say so
389
+ // in words and let every downstream surface refuse to print a fabricated range.
390
+ const unmeasurable = hasExternalSources(measured);
391
+ if (unmeasurable) {
392
+ const external = measured.filter((m) => m.external).map((m) => m.source);
393
+ findings.push(`cost unknown: ${external.length} observation source(s) cannot be sized offline (${external.slice(0, 3).join("; ")}${external.length > 3 ? "; …" : ""}) — this loop reads the web, so its cost cannot be estimated. Authorise it with an explicit cap: foo loop estimate <packet-dir> --agree --limit <usd>`);
394
+ }
395
+ return {
396
+ unit,
397
+ usd,
398
+ tokens,
399
+ expected_iterations: expectedIterations,
400
+ ceiling_iterations: ceilingIterations,
401
+ per_pass_tokens: perPass,
402
+ model_mix: { worker, verifier },
403
+ model_provenance: { provider: active.provider, family: active.family, confidence: active.confidence },
404
+ // Cold/structural tier: heuristic basis, low confidence. No fake precision.
405
+ basis: "heuristic",
406
+ confidence: "low",
407
+ priced_with: priced ? pricedWith(args.pricing) : args.pricing.available === false ? pricedWith(args.pricing) : "unpriced (unknown model)",
408
+ unmeasurable,
409
+ measured,
410
+ measured_method: measurement.method,
411
+ stale,
412
+ stale_age_days: priced ? pricingAgeDays(args.pricing, args.now) : 0,
413
+ priced,
414
+ findings,
415
+ };
416
+ }
417
+ // Build the budget block to record on agreement: the agreed ceiling becomes the
418
+ // limit, the estimate is preserved as provenance, and the agreed handshake is
419
+ // stamped (R-004).
420
+ //
421
+ // B4 carve-out (model-roster-data-v0 R-002): degraded pricing affects estimation,
422
+ // never the handshake itself — an *agreed dollar budget* still requires priced
423
+ // data, so an unpriced dollar agreement is refused rather than recorded at $0.00.
424
+ // A token-unit budget needs no price table and stays agreeable.
425
+ export function buildAgreedBudget(result, opts) {
426
+ // Cost is UNKNOWN (the loop reads sources productfoo cannot size). There is no honest
427
+ // estimate to agree to — so the only way to authorise it is for a human to name a
428
+ // spend CAP. That is a decision, not a prediction, and it is recorded as such: the
429
+ // budget carries the human's limit and NO fabricated expected/ceiling range.
430
+ if (result.unmeasurable) {
431
+ if (opts.limit === undefined) {
432
+ throw new Error(`cannot agree a budget for a loop whose cost is unknown (${result.findings.find((f) => f.startsWith("cost unknown")) ?? "unmeasurable sources"}) — ` +
433
+ "there is no estimate to agree to. Authorise it with an explicit cap you choose: --agree --limit <usd>.");
434
+ }
435
+ if (!(opts.limit > 0))
436
+ throw new Error("--limit must be a positive dollar amount");
437
+ return {
438
+ unit: "usd",
439
+ limit: opts.limit,
440
+ // basis `human-cap`: the number came from a person, not from an estimator.
441
+ estimate: {
442
+ expected: 0,
443
+ ceiling: 0,
444
+ basis: "heuristic",
445
+ confidence: "low",
446
+ per_pass_tokens: result.per_pass_tokens,
447
+ model_mix: result.model_mix,
448
+ priced_with: "unmeasurable — cost unknown; cap set by human",
449
+ },
450
+ agreed: { by: opts.by, at: opts.at, event: "com.adaptivespec.loop.budget_agreed" },
451
+ max_iterations: result.ceiling_iterations,
452
+ no_progress_after: result.expected_iterations,
453
+ on_exceed: "ask",
454
+ warn_at: 0.8,
455
+ };
456
+ }
457
+ if (result.unit === "usd" && !result.priced) {
458
+ throw new Error(`cannot agree a dollar budget on unpriced data (${result.findings.join("; ") || "pricing degraded"}) — ` +
459
+ "restore pricing.yaml (or add the model's rates), or agree a token budget with --unit tokens. " +
460
+ "Degraded pricing never waives the budget handshake (B4).");
461
+ }
462
+ const inUnit = (which) => result.unit === "tokens" ? result.tokens[which] : result.usd[which];
463
+ return {
464
+ unit: result.unit,
465
+ limit: inUnit("ceiling"),
466
+ estimate: {
467
+ expected: inUnit("expected"),
468
+ ceiling: inUnit("ceiling"),
469
+ basis: result.basis,
470
+ confidence: result.confidence,
471
+ per_pass_tokens: result.per_pass_tokens,
472
+ model_mix: result.model_mix,
473
+ priced_with: result.priced_with,
474
+ },
475
+ agreed: {
476
+ by: opts.by,
477
+ at: opts.at,
478
+ event: "com.adaptivespec.loop.budget_agreed",
479
+ },
480
+ max_iterations: result.ceiling_iterations,
481
+ no_progress_after: result.expected_iterations,
482
+ on_exceed: "ask",
483
+ warn_at: 0.8,
484
+ };
485
+ }
486
+ // Default dollar floor below which the handshake is skipped and the loop is
487
+ // auto-approved (R-006). Overridable via FOO_BUDGET_HANDSHAKE_FLOOR.
488
+ export function handshakeFloorUsd() {
489
+ const raw = process.env.FOO_BUDGET_HANDSHAKE_FLOOR;
490
+ if (raw === undefined)
491
+ return 0.5;
492
+ const parsed = Number(raw);
493
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0.5;
494
+ }
@@ -0,0 +1,35 @@
1
+ import { type BudgetBlock } from "./loopBudget.js";
2
+ type LoopContract = {
3
+ id: string;
4
+ title: string;
5
+ status: string;
6
+ owner: string;
7
+ refs: {
8
+ requirements: string[];
9
+ tasks: string[];
10
+ events: string[];
11
+ };
12
+ goal: string;
13
+ observe: {
14
+ sources: string[];
15
+ };
16
+ record: {
17
+ artifact: string;
18
+ };
19
+ ask_user_when: string[];
20
+ budget?: BudgetBlock;
21
+ };
22
+ export declare function renderLoopDesign(packetDir: string): string;
23
+ export declare function loadLoopContracts(packetDir: string): {
24
+ specId: string;
25
+ loops: LoopContract[];
26
+ };
27
+ /** Loop states that can act, and so spend. B4's budget handshake is due for
28
+ * these; a loop in any other state is a signal (AGENTS.md B4). */
29
+ export declare const ARMED_LOOP_STATES: ReadonlySet<string>;
30
+ export declare function checkLoopContracts(root: string, packetDir: string, options?: {
31
+ strictBudget?: boolean;
32
+ }): string;
33
+ export declare function renderLoopStatus(packetDir: string): unknown[];
34
+ export declare function writeAgreedBudget(packetDir: string, loopId: string, budget: BudgetBlock): void;
35
+ export {};