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,770 @@
1
+ // Derived packet status (R-019 / D-019 / D-020).
2
+ //
3
+ // Progress is never stored in spec.md — it is derived here from tasks.yaml on
4
+ // every call. The default path is fast: it reads only tasks.yaml and spec.md,
5
+ // never git or the network, and never emits percent-complete. Source-authority,
6
+ // work-evidence (R-011), and last-meaningful-learning require git or event
7
+ // replay and are deferred to an explicit --deep flag (T-015); the fast view
8
+ // marks them "not yet checked" rather than faking them.
9
+ //
10
+ // derivePacketStatus() is the shared reader: the text view, the agent --json
11
+ // view, and any future cross-packet index all render from this one object.
12
+ import { existsSync, readFileSync } from "node:fs";
13
+ import path from "node:path";
14
+ import { parse as parseYaml } from "./vendorYaml.js";
15
+ import { isRetired, isTerminal, latestVerdict } from "./packetLifecycle.js";
16
+ import { glyphFor, STATUS_GLYPHS, withGlyph } from "./statusGlyphs.js";
17
+ import { parseDecisionRequests, parseOpenQuestions, requirementIds, } from "./specSections.js";
18
+ export const PACKET_STATUS_SCHEMA_VERSION = "1.1";
19
+ const CLAIMED_HUMAN_LABEL = STATUS_GLYPHS.claimed.label;
20
+ function asRecord(value) {
21
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
22
+ }
23
+ function str(value) {
24
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
25
+ }
26
+ // Header identity is best-effort: packets mid-migration (T-013) still carry a
27
+ // flat `Spec ID:` / `Status:` header; restamped packets carry YAML frontmatter
28
+ // (D-017). Identity drives display only — counts never come from the header.
29
+ function parseHeader(specText) {
30
+ let title = null;
31
+ let lifecycle = null;
32
+ let stage = null;
33
+ let mode = null;
34
+ if (specText.startsWith("---")) {
35
+ const end = specText.indexOf("\n---", 3);
36
+ if (end !== -1) {
37
+ try {
38
+ const front = asRecord(parseYaml(specText.slice(3, end)));
39
+ title = str(front.title);
40
+ lifecycle = str(front.lifecycle) ?? str(front.status);
41
+ stage = str(front.stage);
42
+ mode = str(front.mode);
43
+ }
44
+ catch {
45
+ // fall through to flat parsing
46
+ }
47
+ }
48
+ }
49
+ if (!title) {
50
+ const m = specText.match(/^#\s+(.+?)\s*$/m);
51
+ title = m ? m[1].trim() : null;
52
+ }
53
+ if (!lifecycle)
54
+ lifecycle = specText.match(/^Status:\s*(\S+)/m)?.[1] ?? null;
55
+ if (!stage)
56
+ stage = specText.match(/^Stage:\s*(\S+)/m)?.[1] ?? null;
57
+ if (!mode)
58
+ mode = specText.match(/^Mode:\s*(\S+)/m)?.[1] ?? null;
59
+ return { title, lifecycle, stage, mode };
60
+ }
61
+ function openQuestionTexts(specText) {
62
+ // Defensive de-dupe: a derived "needs attention" surface must never show the
63
+ // same question twice (duplicate content reads as a broken derivation).
64
+ return [...new Set(parseOpenQuestions(specText).map((q) => q.text))];
65
+ }
66
+ // Single-packet derivation from already-read inputs (cli-performance-v0 R-001):
67
+ // the index reads and parses each packet's tasks.yaml and spec.md exactly once
68
+ // and threads the results through here, instead of every derivation re-reading
69
+ // the files. Does no fs I/O of its own except the retired-packet verdict tail.
70
+ export function derivePacketStatusFromInputs(packetDir, inputs, options = {}) {
71
+ const now = options.now ?? new Date();
72
+ const doc = asRecord(inputs.tasksDoc);
73
+ const rawTasks = doc.tasks;
74
+ if (!Array.isArray(rawTasks))
75
+ throw new Error("tasks.yaml.tasks must be an array");
76
+ const specText = inputs.specText;
77
+ const specRequirements = new Set(requirementIds(specText));
78
+ const requirementPriorities = {};
79
+ for (const m of specText.matchAll(/^###\s+(R-?\d+)[^\n]*\r?\n(?:[ \t]*\r?\n)*-\s*Priority:\s*(P\d)/gm)) {
80
+ requirementPriorities[m[1]] = m[2];
81
+ }
82
+ const header = parseHeader(specText);
83
+ const counts = { total: 0, done: 0, doing: 0, blocked: 0, todo: 0, dropped: 0 };
84
+ const proof = { checks_total: 0, checks_done: 0 };
85
+ // Checks on live (non-dropped) tasks that are not yet done WITH a proof —
86
+ // the receipt gap that keeps lifecycle_effective at in_flight (R-001).
87
+ let unprovenChecks = 0;
88
+ const completed = [];
89
+ const blocked = [];
90
+ const stale = [];
91
+ const coveredReqs = new Set();
92
+ const statusById = new Map();
93
+ const tasks = [];
94
+ for (const raw of rawTasks) {
95
+ const t = asRecord(raw);
96
+ const id = str(t.id);
97
+ if (!id)
98
+ continue;
99
+ const status = (str(t.status) ?? "todo");
100
+ const title = str(t.title) ?? "";
101
+ const agreement = str(t.agreement) ?? "agreed";
102
+ const owner = str(t.owner);
103
+ const refs = Array.isArray(t.refs) ? t.refs.map(str).filter((x) => x !== null) : [];
104
+ const dependsOn = Array.isArray(t.depends_on) ? t.depends_on.map(str).filter((x) => x !== null) : [];
105
+ statusById.set(id, status);
106
+ // Per-task proof receipt: how many done_when checks are met. The packet
107
+ // total is their sum; the index uses the per-task counts to tell a proven
108
+ // task (every check met) from a merely done one.
109
+ const doneWhen = Array.isArray(t.done_when) ? t.done_when : [];
110
+ const checksTotal = doneWhen.length;
111
+ const checksDone = doneWhen.filter((dw) => asRecord(dw).done === true).length;
112
+ proof.checks_total += checksTotal;
113
+ proof.checks_done += checksDone;
114
+ if (status !== "dropped") {
115
+ // Done without a proof string is a claim, not a receipt — it does not
116
+ // count toward effective-done. Dropped work is excluded: its unmet
117
+ // checks were retired with the task, not left open.
118
+ unprovenChecks += doneWhen.filter((dw) => {
119
+ const check = asRecord(dw);
120
+ return check.done !== true || str(check.proof) === null;
121
+ }).length;
122
+ }
123
+ tasks.push({ id, title, status, agreement, owner, refs, dependsOn, checksDone, checksTotal });
124
+ counts.total += 1;
125
+ if (status === "done")
126
+ counts.done += 1;
127
+ else if (status === "doing")
128
+ counts.doing += 1;
129
+ else if (status === "blocked")
130
+ counts.blocked += 1;
131
+ else if (status === "dropped")
132
+ counts.dropped += 1;
133
+ else
134
+ counts.todo += 1;
135
+ // Requirement coverage counts requirements referenced by at least one
136
+ // non-rejected task (agreement !== "rejected"), intersected with the
137
+ // requirements that actually exist in spec.md.
138
+ if (agreement !== "rejected") {
139
+ for (const ref of refs)
140
+ if (specRequirements.has(ref))
141
+ coveredReqs.add(ref);
142
+ }
143
+ if (status === "done")
144
+ completed.push(id);
145
+ if (status === "blocked") {
146
+ blocked.push({ id, title, reason: str(t.blocked) });
147
+ }
148
+ if (agreement === "suggested" || agreement === "discovered") {
149
+ const due = str(asRecord(t.review).due);
150
+ const overdue = due !== null && !Number.isNaN(Date.parse(due)) && Date.parse(due) < now.getTime();
151
+ stale.push({ id, title, agreement, overdue });
152
+ }
153
+ }
154
+ // Next decision: the first todo/doing task whose dependencies are all done.
155
+ // A retired packet (parked/killed) has no next decision by definition — its
156
+ // remaining tasks are not backlog, they are covered by the verdict.
157
+ const retired = isRetired(header.lifecycle);
158
+ let next = null;
159
+ for (const t of tasks) {
160
+ if (retired)
161
+ break;
162
+ if (t.status !== "todo" && t.status !== "doing")
163
+ continue;
164
+ const ready = t.dependsOn.every((dep) => statusById.get(dep) === "done");
165
+ if (ready) {
166
+ next = { id: t.id, title: t.title, kind: t.status };
167
+ break;
168
+ }
169
+ }
170
+ // The verdict reason is a bounded ledger-tail read, paid only by retired
171
+ // packets so the common path stays tasks.yaml + spec.md only.
172
+ const verdict = retired
173
+ ? { state: header.lifecycle, ...verdictReason(packetDir, header.lifecycle) }
174
+ : null;
175
+ // Drift (foo-status-glyphs-v0 T-005, resolves OQ1): the header still claims an
176
+ // unfinished lifecycle while every task is done — a restamp nudge. Computed in
177
+ // the shared reader so asp status and asp index read the same signal. Mirrors
178
+ // the existing packetIndex restamp flag.
179
+ const restamp = header.lifecycle === "draft" && counts.total > 0 && counts.done === counts.total;
180
+ // Effective lifecycle (packet-lifecycle-truth-v0 R-001): derived purely from
181
+ // tasks.yaml, never from the header. Effectively done means every task is
182
+ // done (dropped work excluded, at least one done) and every done_when check
183
+ // on live tasks is done with a proof present; any open/doing/blocked task or
184
+ // missing receipt keeps the packet in flight. Read-side only — nothing here
185
+ // mutates the header; `foo asp verdict <packet> done` is the governed
186
+ // transition (R-003).
187
+ const effectivelyDone = counts.total > 0 &&
188
+ counts.done > 0 &&
189
+ counts.done + counts.dropped === counts.total &&
190
+ unprovenChecks === 0;
191
+ // Drift is a contradiction between claim and receipt (R-002): a non-terminal
192
+ // header over effectively-done work, or a completion stamp (done/built) over
193
+ // work that is not proofs-complete. Terminal retirement (parked/killed) is a
194
+ // recorded decision, never drift — even at 0% tasks.
195
+ const lifecycleDrift = effectivelyDone
196
+ ? !isTerminal(header.lifecycle)
197
+ : header.lifecycle === "done" || header.lifecycle === "built";
198
+ const decisionParse = parseDecisionRequests(specText, { taskIds: statusById.keys() });
199
+ const blockedTask = tasks.find((task) => task.status === "blocked") ?? null;
200
+ const selectedDecision = (() => {
201
+ for (const task of tasks) {
202
+ if (task.status !== "blocked")
203
+ continue;
204
+ const request = decisionParse.requests.find((candidate) => candidate.unblocks.kind === "task" && candidate.unblocks.id === task.id);
205
+ if (request)
206
+ return request;
207
+ }
208
+ return decisionParse.requests.find((candidate) => candidate.unblocks.kind === "packet") ?? null;
209
+ })();
210
+ // One source of action truth. A retirement verdict wins over every stale
211
+ // question below it; lifecycle contradictions win over completion labels;
212
+ // only a complete canonical request can become a decision.
213
+ const nextAction = isRetired(header.lifecycle)
214
+ ? { kind: "none", reason: header.lifecycle === "parked" ? "This work is paused." : "This work was stopped." }
215
+ : lifecycleDrift
216
+ ? { kind: "reconcile", reason: lifecycleDriftText({ lifecycle_effective: effectivelyDone ? "done" : "in_flight", lifecycle_stored: header.lifecycle }) }
217
+ : header.lifecycle === "done" || header.lifecycle === "built"
218
+ ? { kind: "none", reason: "This work is complete." }
219
+ : selectedDecision
220
+ ? { kind: "decide", decision: selectedDecision }
221
+ : blockedTask
222
+ ? { kind: "unblock", task: { id: blockedTask.id, title: blockedTask.title, reason: blocked.find((item) => item.id === blockedTask.id)?.reason ?? null } }
223
+ : next
224
+ ? { kind: "task", task: { id: next.id, title: next.title, status: next.kind } }
225
+ : { kind: "none", reason: "No action is needed." };
226
+ return {
227
+ packet: path.basename(packetDir),
228
+ spec_id: str(doc.spec_id) ?? path.basename(packetDir),
229
+ title: header.title,
230
+ lifecycle: header.lifecycle,
231
+ lifecycle_stored: header.lifecycle,
232
+ lifecycle_effective: effectivelyDone ? "done" : "in_flight",
233
+ lifecycle_drift: lifecycleDrift,
234
+ stage: header.stage,
235
+ mode: header.mode,
236
+ tasks: counts,
237
+ proof,
238
+ requirements: { total: specRequirements.size, covered: coveredReqs.size },
239
+ requirement_priorities: requirementPriorities,
240
+ completed,
241
+ task_list: tasks.map((t) => ({ id: t.id, title: t.title, status: t.status, checksDone: t.checksDone, checksTotal: t.checksTotal, agreement: t.agreement, owner: t.owner, deps: t.dependsOn, refs: t.refs })),
242
+ blocked,
243
+ stale,
244
+ open_questions: openQuestionTexts(specText),
245
+ next_action: nextAction,
246
+ decision_findings: decisionParse.findings,
247
+ next,
248
+ drift: { restamp },
249
+ verdict,
250
+ deep_checked: false,
251
+ };
252
+ }
253
+ // File-reading entry point: the shared reader for single-packet views (asp
254
+ // status). The index (packetIndex.ts) prefers derivePacketStatusFromInputs so
255
+ // each packet's files are read and parsed once per run (cli-performance-v0 R-001).
256
+ export function derivePacketStatus(packetDir, options = {}) {
257
+ const tasksPath = path.join(packetDir, "tasks.yaml");
258
+ const specPath = path.join(packetDir, "spec.md");
259
+ if (!existsSync(tasksPath))
260
+ throw new Error(`tasks.yaml missing in ${packetDir}`);
261
+ if (!existsSync(specPath))
262
+ throw new Error(`spec.md missing in ${packetDir}`);
263
+ return derivePacketStatusFromInputs(packetDir, {
264
+ tasksDoc: parseYaml(readFileSync(tasksPath, "utf8")),
265
+ specText: readFileSync(specPath, "utf8"),
266
+ }, options);
267
+ }
268
+ // The frontmatter stamp is canonical; the ledger event supplies the reason.
269
+ // A hand-stamped packet with no event still renders (reason null) rather than
270
+ // erroring — the missing trail is itself legible.
271
+ function verdictReason(packetDir, state) {
272
+ const record = latestVerdict(packetDir);
273
+ if (record && record.state === state)
274
+ return { reason: record.reason, at: record.at };
275
+ return { reason: null, at: null };
276
+ }
277
+ function pad(label) {
278
+ return (label + " ").slice(0, 11);
279
+ }
280
+ // Exported so the initiative brief (initiative-brief-v0 R-001) renders the same
281
+ // sentence as the CLI card instead of writing a second one. A projection that
282
+ // re-phrases the action is a second reader with extra steps.
283
+ export function nextActionText(action) {
284
+ switch (action.kind) {
285
+ case "none":
286
+ return `none — ${action.reason}`;
287
+ case "reconcile":
288
+ return action.reason;
289
+ case "decide":
290
+ return `decide — ${action.decision.question.prompt}`;
291
+ case "unblock":
292
+ return `unblock ${action.task.title}${action.task.reason ? ` (${action.task.reason})` : ""} · ${action.task.id}`;
293
+ case "task":
294
+ return `${action.task.title} · ${action.task.id}`;
295
+ }
296
+ }
297
+ // One sentence naming both sides of a lifecycle contradiction (R-002). The
298
+ // word "drift" leads so the line is greppable with or without the glyph.
299
+ function lifecycleDriftText(s) {
300
+ return s.lifecycle_effective === "done"
301
+ ? `drift — lifecycle says ${s.lifecycle_stored ?? "(unset)"}, but every task is done and proven; record it: foo asp verdict <packet> done --reason <text>`
302
+ : `drift — lifecycle says ${s.lifecycle_stored}, but the work is not proofs-complete; finish it or reopen with foo asp verdict <packet> active --reason <text>`;
303
+ }
304
+ const DEEP_SCAN_LIMIT = 2000;
305
+ const SECRET_HINT = /(-----BEGIN [A-Z ]*PRIVATE KEY-----|\bAKIA[0-9A-Z]{16}\b|\bgh[pousr]_[A-Za-z0-9]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b|(?:password|secret|api[_-]?key|token|bearer)\s*[:=]\s*\S{8,})/i;
306
+ function redact(text) {
307
+ return SECRET_HINT.test(text) ? "[redacted: looked like a secret]" : text;
308
+ }
309
+ function deriveDeep(packetDir) {
310
+ const eventsPath = path.join(packetDir, "events.jsonl");
311
+ if (!existsSync(eventsPath)) {
312
+ return { source_authority: "no event ledger", work_evidence: "no event ledger", last_learning: null };
313
+ }
314
+ const lines = readFileSync(eventsPath, "utf8").split(/\r?\n/).filter((l) => l.trim());
315
+ const scanned = lines.slice(-DEEP_SCAN_LIMIT);
316
+ const events = [];
317
+ for (const line of scanned) {
318
+ try {
319
+ const e = JSON.parse(line);
320
+ if (e && typeof e === "object")
321
+ events.push(e);
322
+ }
323
+ catch {
324
+ // A malformed ledger line is skipped, not fatal — this is a read.
325
+ }
326
+ }
327
+ const providers = new Set();
328
+ let sourceSignals = 0;
329
+ let workEvidence = 0;
330
+ let latest = null;
331
+ for (const e of events) {
332
+ const type = String(e.type ?? "");
333
+ const subject = String(e.subject ?? "");
334
+ const data = asRecord(e.data);
335
+ if (/source|external|record|observ|synced/i.test(type) || "provider" in data || "synced_at" in data || "observed_at" in data) {
336
+ sourceSignals += 1;
337
+ const provider = str(data.provider) ?? str(data.source);
338
+ if (provider)
339
+ providers.add(provider);
340
+ }
341
+ if (/code\.changed|pr_merged|status_changed|work\./i.test(type))
342
+ workEvidence += 1;
343
+ const summary = str(data.summary);
344
+ if (summary && type !== "cli_usage") {
345
+ const t = Date.parse(String(e.time ?? "")) || 0;
346
+ if (!latest || t >= latest.time)
347
+ latest = { time: t, type, subject, summary };
348
+ }
349
+ }
350
+ const source_authority = sourceSignals === 0
351
+ ? "no source-authority signals in the ledger"
352
+ : `${sourceSignals} source signal(s)${providers.size > 0 ? ` from: ${[...providers].sort().join(", ")}` : ""}`;
353
+ const work_evidence = workEvidence === 0 ? "no work-evidence events in the ledger" : `${workEvidence} work-evidence event(s) (status changes, code changes, merged work)`;
354
+ const last_learning = latest
355
+ ? `${latest.type}${latest.subject ? ` (${latest.subject})` : ""}: ${redact(latest.summary).slice(0, 140)}`
356
+ : null;
357
+ return { source_authority, work_evidence, last_learning };
358
+ }
359
+ function renderText(s, deepRequested, deep, glyphs) {
360
+ const lines = [];
361
+ lines.push(`${s.packet} · ${s.spec_id}`);
362
+ const meta = [s.lifecycle && `lifecycle ${s.lifecycle}`, s.stage && `stage ${s.stage}`, s.mode && `mode ${s.mode}`]
363
+ .filter(Boolean)
364
+ .join(" · ");
365
+ if (s.title || meta)
366
+ lines.push(`${s.title ?? ""}${s.title && meta ? " — " : ""}${meta}`.trim());
367
+ lines.push("");
368
+ // A retirement verdict leads the card: the packet's headline truth is "we
369
+ // decided not to do this (yet)", not its task counts.
370
+ if (s.verdict) {
371
+ const state = s.verdict.state === "killed" ? "killed" : "parked";
372
+ const when = s.verdict.at ? ` (${s.verdict.at.slice(0, 10)})` : "";
373
+ lines.push(`${pad("Verdict")}${withGlyph(state, `${state}${when} — ${s.verdict.reason ?? "no reason recorded in the ledger"}`, glyphs)}`);
374
+ lines.push("");
375
+ }
376
+ // Task-level and spec-level progress stay on separate lines (R-019); no percentages.
377
+ lines.push(`${pad("Tasks")}${withGlyph("claimed", `${s.tasks.done} ${CLAIMED_HUMAN_LABEL}`, glyphs)} · ${withGlyph("doing", `${s.tasks.doing} doing`, glyphs)} · ${withGlyph("blocked", `${s.tasks.blocked} blocked`, glyphs)} · ${withGlyph("todo", `${s.tasks.todo} todo`, glyphs)} (${s.tasks.total} total)`);
378
+ lines.push(`${pad("Proof")}${withGlyph("proven", `${s.proof.checks_done} of ${s.proof.checks_total} checks proven`, glyphs)}`);
379
+ // 0-of-N renders loudly (packet-grammar-v1 R-009): a packet whose tasks
380
+ // reference none of its requirements is drift, not a quiet statistic. The
381
+ // silent "0 of 0" this line used to show for unparsed formats is extinct —
382
+ // the grammar's tolerant read gives every packet a real denominator.
383
+ lines.push(s.requirements.total > 0
384
+ ? s.requirements.covered === 0
385
+ ? `${pad("Spec")}${withGlyph("drift", `0 of ${s.requirements.total} requirements covered — no task references any requirement yet`, glyphs)}`
386
+ : `${pad("Spec")}${s.requirements.covered} of ${s.requirements.total} requirements covered by a task`
387
+ : `${pad("Spec")}no R-numbered requirements found`);
388
+ lines.push("");
389
+ lines.push(`${pad("Completed")}${s.completed.length > 0 ? s.completed.join(" ") : "none yet"}`);
390
+ if (s.blocked.length === 0) {
391
+ lines.push(`${pad("Blocked")}none`);
392
+ }
393
+ else {
394
+ s.blocked.forEach((b, i) => {
395
+ const text = `${b.id} ${b.title}${b.reason ? ` — ${b.reason}` : ""}`;
396
+ lines.push(`${pad(i === 0 ? "Blocked" : "")}${withGlyph("blocked", text, glyphs)}`);
397
+ });
398
+ }
399
+ if (s.stale.length === 0) {
400
+ lines.push(`${pad("Review")}no stale suggested/discovered tasks`);
401
+ }
402
+ else {
403
+ lines.push(`${pad("Review")}${s.stale.length} ${s.stale.length === 1 ? "task" : "tasks"} awaiting review:`);
404
+ for (const r of s.stale) {
405
+ const text = `${r.id} (${r.agreement})${r.overdue ? " — overdue" : ""} ${r.title}`;
406
+ lines.push(`${pad("")}${r.overdue ? withGlyph("drift", text, glyphs) : text}`);
407
+ }
408
+ }
409
+ // Lifecycle drift (packet-lifecycle-truth-v0 R-002): the header contradicts
410
+ // the derived truth. The restamp nudge (draft + all done) keeps its original
411
+ // wording; other contradictions name both states and the governed way out.
412
+ lines.push(s.drift.restamp
413
+ ? `${pad("Drift")}${withGlyph("drift", "header says draft, but all tasks are done — restamp the lifecycle", glyphs)}`
414
+ : s.lifecycle_drift
415
+ ? `${pad("Drift")}${withGlyph("drift", lifecycleDriftText(s), glyphs)}`
416
+ : `${pad("Drift")}none`);
417
+ if (s.open_questions.length > 0) {
418
+ const actionable = s.next_action.kind === "decide";
419
+ const label = actionable
420
+ ? `${s.open_questions.length} saved · 1 decision needs your input`
421
+ : `${s.open_questions.length} saved for reference · not blocking`;
422
+ lines.push(`${pad("Questions")}${actionable ? withGlyph("needs_input", label, glyphs) : label}`);
423
+ for (const q of s.open_questions)
424
+ lines.push(`${pad("")}- ${q}`);
425
+ }
426
+ else {
427
+ lines.push(`${pad("Questions")}none saved`);
428
+ }
429
+ lines.push("");
430
+ lines.push(`${pad("Next")}${nextActionText(s.next_action)}`);
431
+ if (deepRequested && deep) {
432
+ lines.push(`${pad("Source")}${deep.source_authority}`);
433
+ lines.push(`${pad("Evidence")}${deep.work_evidence}`);
434
+ lines.push(`${pad("Learning")}${deep.last_learning ?? "no meaningful learning recorded yet"}`);
435
+ }
436
+ else {
437
+ lines.push(`${pad("Deep")}source-authority, work-evidence, last learning — not yet checked (run with --deep)`);
438
+ }
439
+ lines.push("");
440
+ return lines.join("\n");
441
+ }
442
+ // Glyphs are chosen for screen-reader and terminal-font portability (✓/✗ are
443
+ // widely spoken and have braille mappings; ●/○ are a filled/empty in-progress
444
+ // pair; × reads as "times"). The word is always load-bearing — color and glyph
445
+ // are additive, never the sole carrier of a status.
446
+ const STATUS_META = {
447
+ done: { glyph: "✓", label: CLAIMED_HUMAN_LABEL, tone: "ok" },
448
+ doing: { glyph: "●", label: "doing", tone: "active" },
449
+ blocked: { glyph: "✗", label: "blocked", tone: "bad" },
450
+ todo: { glyph: "○", label: "todo", tone: "muted" },
451
+ dropped: { glyph: "×", label: "dropped", tone: "muted" },
452
+ };
453
+ function makePaint(color) {
454
+ const wrap = (code) => (s) => (color ? `\x1b[${code}m${s}\x1b[0m` : s);
455
+ return {
456
+ ok: wrap("32"),
457
+ active: wrap("36"),
458
+ bad: wrap("31"),
459
+ warn: wrap("33"),
460
+ muted: wrap("2"),
461
+ bold: wrap("1"),
462
+ accent: wrap("1;36"),
463
+ };
464
+ }
465
+ function bar(done, total, p, tone, width = 16) {
466
+ if (total <= 0)
467
+ return p.muted("░".repeat(width));
468
+ let filled = Math.round((done / total) * width);
469
+ if (done > 0 && filled === 0)
470
+ filled = 1;
471
+ if (done < total && filled === width)
472
+ filled = width - 1;
473
+ filled = Math.max(0, Math.min(width, filled));
474
+ const empty = width - filled;
475
+ return `${p[tone]("█".repeat(filled))}${empty > 0 ? p.muted("░".repeat(empty)) : ""}`;
476
+ }
477
+ function renderRich(s, opts) {
478
+ const p = makePaint(opts.color);
479
+ const out = [];
480
+ // One fixed-width left label column so every value lands on the same edge.
481
+ const W = 10;
482
+ const field = (label, tone = "bold") => p[tone](label.padEnd(W));
483
+ // Bars are decorative (R-019 forbids a number) and read as repeated noise to a
484
+ // screen reader, so they render only on a real terminal; a pipe / --plain omits
485
+ // them. Truncation hides text behind an off-screen ellipsis, so it too applies
486
+ // only on a TTY where width is bounded — piped output stays whole.
487
+ const barCell = (done, total, tone) => (opts.bars ? `${bar(done, total, p, tone)} ` : "");
488
+ const clip = (text, n = 84) => (opts.bars && text.length > n ? `${text.slice(0, n - 1)}…` : text);
489
+ // Plain-first (jargon-supply-chain-v0 R-002): the human name leads the card;
490
+ // machine identifiers ride muted at the end of the line, never bold-first.
491
+ out.push(`${p.bold(s.title ?? s.packet)} ${p.muted("·")} ${p.muted(`${s.packet} · ${s.spec_id}`)}`);
492
+ const meta = [s.lifecycle && `lifecycle ${s.lifecycle}`, s.stage && `stage ${s.stage}`, s.mode && `mode ${s.mode}`]
493
+ .filter(Boolean)
494
+ .join(" · ");
495
+ if (meta)
496
+ out.push(p.muted(meta));
497
+ out.push("");
498
+ // Summary drops zero-count categories (so an at-risk packet never reads as
499
+ // complete) and surfaces blocked in red with the `!` token asp index uses.
500
+ const counts = [`${s.tasks.done} ${CLAIMED_HUMAN_LABEL}`];
501
+ if (s.tasks.doing)
502
+ counts.push(`${s.tasks.doing} doing`);
503
+ if (s.tasks.blocked)
504
+ counts.push(p.bad(`! ${s.tasks.blocked} blocked`));
505
+ if (s.tasks.todo)
506
+ counts.push(`${s.tasks.todo} todo`);
507
+ if (s.tasks.dropped)
508
+ counts.push(p.muted(`${s.tasks.dropped} dropped`));
509
+ out.push(` ${field("Tasks")}${barCell(s.tasks.done, s.tasks.total, "ok")}${counts.join(" · ")} ${p.muted(`(${s.tasks.total} total)`)}`);
510
+ out.push(` ${field("Proof")}${barCell(s.proof.checks_done, s.proof.checks_total, "active")}${s.proof.checks_done} of ${s.proof.checks_total} checks proven`);
511
+ out.push(s.requirements.total > 0
512
+ ? ` ${field("Spec")}${barCell(s.requirements.covered, s.requirements.total, "active")}${s.requirements.covered} of ${s.requirements.total} requirements covered`
513
+ : ` ${field("Spec")}${p.muted("no R-numbered requirements found")}`);
514
+ out.push("");
515
+ // The decision this card exists to drive sits directly under the summary, in
516
+ // the only accent treatment, above the task detail (principal-designer P0).
517
+ // A retired packet's decision is already made — the verdict replaces Next.
518
+ if (s.verdict) {
519
+ const state = s.verdict.state === "killed" ? "killed" : "parked";
520
+ const glyph = glyphFor(state, true);
521
+ out.push(` ${field("Verdict", "warn")}${p.bold(`${glyph} ${state}`)} ${p.muted("—")} ${clip(s.verdict.reason ?? "no reason recorded in the ledger")}`);
522
+ }
523
+ else {
524
+ const tone = s.next_action.kind === "none" ? "muted" : s.next_action.kind === "unblock" || s.next_action.kind === "reconcile" ? "warn" : "accent";
525
+ out.push(` ${field("Next", tone)}${p[tone](`→ ${clip(nextActionText(s.next_action))}`)}`);
526
+ }
527
+ out.push("");
528
+ if (s.task_list.length === 0) {
529
+ out.push(` ${p.muted("no tasks in tasks.yaml")}`);
530
+ }
531
+ else {
532
+ for (const t of s.task_list) {
533
+ const m = STATUS_META[t.status] ?? STATUS_META.todo;
534
+ const title = m.tone === "muted" ? p.muted(t.title) : t.title;
535
+ out.push(` ${p[m.tone](m.glyph)} ${p[m.tone](m.label.padEnd(7))} ${title} ${p.muted(`· ${t.id}`)}`.trimEnd());
536
+ }
537
+ }
538
+ // Exceptions the table can't carry — one line each, same aligned column, shown
539
+ // only when non-empty. Full text always lives in --plain / --json and spec.md.
540
+ const exceptions = [];
541
+ for (const b of s.blocked) {
542
+ exceptions.push(` ${field("Blocked", "bad")}${clip(`${b.title}${b.reason ? ` — ${b.reason}` : ""}`)} ${p.muted(`· ${b.id}`)}`);
543
+ }
544
+ if (s.stale.length > 0) {
545
+ const tail = s.stale.map((r) => `${r.id} (${r.agreement}${r.overdue ? ", overdue" : ""})`).join(", ");
546
+ exceptions.push(` ${field("Review", "warn")}${s.stale.length} awaiting review ${p.muted(`· ${tail}`)}`);
547
+ }
548
+ if (s.open_questions.length > 0) {
549
+ const word = s.open_questions.length === 1 ? "question" : "questions";
550
+ const actionable = s.next_action.kind === "decide";
551
+ exceptions.push(` ${field("Questions", actionable ? "warn" : "muted")}${s.open_questions.length} saved ${word} ${p.muted(actionable ? "· 1 needs your input" : "· reference only")}`);
552
+ }
553
+ // Lifecycle drift rides the exceptions column on the rich card too (R-002) —
554
+ // the quick view must not read healthier than the text/plain view.
555
+ if (s.lifecycle_drift) {
556
+ exceptions.push(` ${field("Drift", "warn")}${clip(s.lifecycle_effective === "done"
557
+ ? `lifecycle says ${s.lifecycle_stored ?? "(unset)"}, work proven done — foo asp verdict <packet> done`
558
+ : `lifecycle says ${s.lifecycle_stored}, work not proofs-complete`)}`);
559
+ }
560
+ if (exceptions.length > 0) {
561
+ out.push("");
562
+ out.push(...exceptions);
563
+ }
564
+ out.push("");
565
+ // The rich card is the quick view; --deep renders the detailed text audit.
566
+ out.push(` ${field("Deep", "muted")}${p.muted("source-authority, work-evidence, last learning — run asp status --deep")}`);
567
+ out.push("");
568
+ return out.join("\n");
569
+ }
570
+ export function renderAspStatus(packetDir, options = {}) {
571
+ const status = derivePacketStatus(packetDir, { now: options.now });
572
+ const deep = options.deep === true ? deriveDeep(packetDir) : null;
573
+ if (deep)
574
+ status.deep_checked = true;
575
+ if (options.json) {
576
+ return `${JSON.stringify({ schema_version: PACKET_STATUS_SCHEMA_VERSION, ...status, ...(deep ? { deep } : {}) }, null, 2)}\n`;
577
+ }
578
+ if (options.html) {
579
+ return renderStatusHtml(status);
580
+ }
581
+ // --deep is the detailed audit; render it as text (with the deep findings)
582
+ // rather than the quick rich card.
583
+ if (options.rich && !deep) {
584
+ return renderRich(status, {
585
+ color: options.color === true,
586
+ deep: options.deep === true,
587
+ // Bars and truncation are TTY-only; default to the color decision (a real
588
+ // terminal) but let an explicit caller force them for capture.
589
+ bars: options.bars ?? options.color === true,
590
+ });
591
+ }
592
+ return renderText(status, options.deep === true, deep, options.glyphs === true);
593
+ }
594
+ // Single-packet HTML progress card (the visual sibling of the rich TTY card and
595
+ // the cross-packet `asp index --html`). Server-rendered and self-contained: no
596
+ // client JS, so it degrades to readable semantic HTML, reuses the asp-index
597
+ // design tokens (Atkinson Hyperlegible, OKLCH), and carries the design-swarm +
598
+ // Impeccable findings — Next is the dominant call-out linked to its list row,
599
+ // every status shows a word (never color/glyph alone), the task list is a fixed
600
+ // grid, accent side-bars are dropped (an AI tell), and `--muted` is darkened for
601
+ // ≥4.5:1 contrast. No percent-complete (R-019): bars encode proportion only.
602
+ const HTML_STATUS_META = {
603
+ done: { glyph: "✓", word: CLAIMED_HUMAN_LABEL, cls: "d" },
604
+ doing: { glyph: "●", word: "doing", cls: "wip" },
605
+ blocked: { glyph: "✗", word: "blocked", cls: "b" },
606
+ todo: { glyph: "○", word: "todo", cls: "o" },
607
+ dropped: { glyph: "×", word: "dropped", cls: "x" },
608
+ };
609
+ function escHtml(value) {
610
+ return value.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
611
+ }
612
+ // One filled segment over the track background — no abutting zero-flex sliver to
613
+ // read as a glitch near 100%. aria-hidden: the adjacent value text is the number.
614
+ function pbarHtml(done, total, colorVar) {
615
+ // The card refuses a percent-complete number by design; this bar is a coarse
616
+ // proportion of discrete counts, not a precise metric. Round to one decimal so
617
+ // the markup never ships a long binary float (e.g. 82.35294117647058%).
618
+ const raw = total > 0 ? Math.max(0, Math.min(100, (done / total) * 100)) : 0;
619
+ const fill = Math.round(raw * 10) / 10;
620
+ return `<div class="pbar" aria-hidden="true"><i style="width:${fill}%;background:var(${colorVar})"></i></div>`;
621
+ }
622
+ export function renderStatusHtml(s) {
623
+ const title = s.title ?? s.packet;
624
+ // Summary line drops zero-count categories and leads with in-flight work.
625
+ const counts = [];
626
+ if (s.tasks.doing)
627
+ counts.push(`${s.tasks.doing} doing`);
628
+ counts.push(`${s.tasks.done} ${CLAIMED_HUMAN_LABEL}`);
629
+ if (s.tasks.blocked)
630
+ counts.push(`${s.tasks.blocked} blocked`);
631
+ if (s.tasks.todo)
632
+ counts.push(`${s.tasks.todo} todo`);
633
+ if (s.tasks.dropped)
634
+ counts.push(`${s.tasks.dropped} dropped`);
635
+ const countLine = counts.join(" · ");
636
+ const tags = [s.lifecycle && `lifecycle ${s.lifecycle}`, s.stage && `stage ${s.stage}`, s.mode && `mode ${s.mode}`]
637
+ .filter((x) => Boolean(x))
638
+ .map((t) => `<span class="tag">${escHtml(t)}</span>`)
639
+ .join("");
640
+ const nextId = s.next?.id;
641
+ const tasks = s.task_list
642
+ .map((t) => {
643
+ const m = HTML_STATUS_META[t.status] ?? HTML_STATUS_META.todo;
644
+ // Mark the row the Next call-out points at, so the two read as one object.
645
+ const mark = t.id === nextId ? " next" : "";
646
+ return `<li class="tk ${m.cls}${mark}"><span class="g ${m.cls}" aria-hidden="true">${m.glyph}</span><span class="tid">${escHtml(t.id)}</span><span class="tst">${m.word}</span><span class="tt">${escHtml(t.title)}</span></li>`;
647
+ })
648
+ .join("");
649
+ const nextValue = s.next_action.kind === "task"
650
+ ? `<span class="next-t">${escHtml(s.next_action.task.title)}</span><span class="next-id">${escHtml(s.next_action.task.id)}</span>`
651
+ : `<span class="next-t">${escHtml(nextActionText(s.next_action))}</span>`;
652
+ const next = `<h2 class="next-k">Next</h2><p class="next-v">${nextValue}</p>`;
653
+ const exc = [];
654
+ if (s.blocked.length > 0) {
655
+ const items = s.blocked
656
+ .map((b) => `<li><b>${escHtml(b.id)}</b> ${escHtml(b.title)}${b.reason ? ` <span class="muted">(${escHtml(b.reason)})</span>` : ""}</li>`)
657
+ .join("");
658
+ exc.push(`<div class="exc b"><h3 class="exc-k">Blocked</h3><ul class="exc-l">${items}</ul></div>`);
659
+ }
660
+ if (s.stale.length > 0) {
661
+ const items = s.stale
662
+ .map((r) => `<li><b>${escHtml(r.id)}</b> ${escHtml(r.title)} <span class="muted">(${escHtml(r.agreement)}${r.overdue ? ", overdue" : ""})</span></li>`)
663
+ .join("");
664
+ exc.push(`<div class="exc w"><h3 class="exc-k">Review</h3><ul class="exc-l">${items}</ul></div>`);
665
+ }
666
+ if (s.open_questions.length > 0) {
667
+ const items = s.open_questions.map((q) => `<li>${escHtml(q)}</li>`).join("");
668
+ const actionable = s.next_action.kind === "decide";
669
+ exc.push(`<div class="exc ${actionable ? "w" : ""}"><h3 class="exc-k">Saved questions${actionable ? " · 1 needs your input" : " · reference only"}</h3><ul class="exc-l">${items}</ul></div>`);
670
+ }
671
+ const excHtml = exc.length > 0 ? `<section class="excs"><h2 class="sr">Needs attention</h2>${exc.join("")}</section>` : "";
672
+ const spec = s.requirements.total > 0
673
+ ? `<div class="chip"><div class="k">requirements</div><div class="v">${s.requirements.covered}<small>/${s.requirements.total}</small></div>${pbarHtml(s.requirements.covered, s.requirements.total, "--proof")}</div>`
674
+ : `<div class="chip"><div class="k">requirements</div><div class="v">—<small> none</small></div></div>`;
675
+ return `<!doctype html>
676
+ <html lang="en">
677
+ <head>
678
+ <meta charset="utf-8" />
679
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
680
+ <title>foo · ${escHtml(s.packet)} progress</title>
681
+ <style>
682
+ @import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:wght@400;700&display=swap');
683
+ :root{--ink:oklch(0.205 0.012 25);--muted:oklch(0.418 0.014 25);--line:oklch(0.875 0.006 25);--line2:oklch(0.705 0.012 25);--bg:oklch(1 0 0);--surf:oklch(0.985 0.001 25);--surf2:oklch(0.967 0.002 25);--att:oklch(0.36 0.12 0);--att-soft:oklch(0.967 0.015 8);--proof:oklch(0.47 0.095 190);--wip:oklch(0.5 0.12 255);--bad:oklch(0.52 0.18 25);--warn:oklch(0.55 0.1 70);--warn-soft:oklch(0.96 0.03 82);--bad-soft:oklch(0.96 0.025 25);--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--ui:"Atkinson Hyperlegible",ui-sans-serif,system-ui,sans-serif;}
684
+ *{box-sizing:border-box;}
685
+ body{margin:0;background:var(--surf);color:var(--ink);font-family:var(--ui);line-height:1.5;-webkit-font-smoothing:antialiased;}
686
+ .wrap{max-width:760px;margin:0 auto;padding:40px 24px 56px;}
687
+ .sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0;}
688
+ .muted{color:var(--muted);}
689
+ .mast{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap;}
690
+ .wm{font-size:13px;font-weight:700;color:var(--muted);letter-spacing:.02em;}
691
+ .wm b{color:var(--att);}
692
+ h1.ttl{font-size:25px;font-weight:700;letter-spacing:-0.01em;margin:5px 0 4px;line-height:1.2;}
693
+ .sub{margin:0;font-size:12.5px;color:var(--muted);font-family:var(--mono);}
694
+ .tags{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end;}
695
+ .tag{font-family:var(--mono);font-size:10.5px;font-weight:700;border:0.5px solid var(--line2);border-radius:5px;padding:3px 7px;color:var(--muted);white-space:nowrap;}
696
+ .chips{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;margin:22px 0 0;}
697
+ .chip{background:var(--bg);border:0.5px solid var(--line);border-radius:9px;padding:12px 14px;}
698
+ .chip .k{font-family:var(--mono);font-size:10.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.03em;}
699
+ .chip .v{font-size:22px;font-weight:700;margin:3px 0 9px;}
700
+ .chip .v small{font-size:12px;font-weight:400;color:var(--muted);}
701
+ .pbar{height:8px;border-radius:3px;overflow:hidden;background:var(--line);margin-top:1px;}
702
+ .pbar i{display:block;height:100%;}
703
+ .nextcard{display:block;margin:22px 0 0;background:var(--att-soft);border:0.5px solid var(--line2);border-radius:9px;padding:15px 17px;}
704
+ .next-k{font-family:var(--mono);font-size:10.5px;font-weight:700;color:var(--att);text-transform:uppercase;letter-spacing:.05em;margin:0 0 5px;}
705
+ .next-v{margin:0;font-size:18px;font-weight:700;display:flex;gap:10px;align-items:baseline;flex-wrap:wrap;}
706
+ .next-id{font-family:var(--mono);font-size:13px;color:var(--att);}
707
+ .next-t{font-weight:400;}
708
+ .seclabel{font-family:var(--mono);font-size:10.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:26px 0 8px;}
709
+ .tks{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;}
710
+ .tk{display:grid;grid-template-columns:1.4rem 6ch 8ch 1fr;align-items:center;gap:11px;padding:8px 6px;border-top:0.5px solid var(--line);font-size:14px;}
711
+ .tk:first-child{border-top:0;}
712
+ .tk.next{background:var(--att-soft);border-radius:7px;}
713
+ .g{width:19px;height:19px;border-radius:5px;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:oklch(0.99 0.004 190);}
714
+ .g.d{background:var(--proof);}
715
+ .g.wip{background:var(--wip);}
716
+ .g.b{background:var(--bad);}
717
+ .g.o{background:transparent;border:1.5px solid var(--line2);color:var(--muted);}
718
+ .g.x{background:transparent;border:1.5px solid var(--line2);color:var(--muted);}
719
+ .tid{font-family:var(--mono);font-size:11.5px;font-weight:700;color:var(--muted);}
720
+ .tst{font-family:var(--mono);font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;}
721
+ .tk.d .tst{color:var(--proof);}
722
+ .tk.wip .tst{color:var(--wip);}
723
+ .tk.b .tst{color:var(--bad);}
724
+ .tk.o .tst,.tk.x .tst{color:var(--muted);}
725
+ .tt{color:var(--ink);}
726
+ .tk.d .tt,.tk.x .tt{color:var(--muted);}
727
+ .tk.x .tt{text-decoration:line-through;}
728
+ .excs{margin-top:24px;display:flex;flex-direction:column;gap:10px;}
729
+ .exc{background:var(--bg);border:0.5px solid var(--line);border-radius:9px;padding:12px 15px;}
730
+ .exc.b{background:var(--bad-soft);}
731
+ .exc.w{background:var(--warn-soft);}
732
+ .exc-k{font-family:var(--mono);font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;margin:0 0 7px;color:var(--ink);}
733
+ .exc-l{margin:0;padding-left:18px;font-size:13.5px;display:flex;flex-direction:column;gap:4px;}
734
+ .deep{margin:26px 0 0;font-size:12.5px;color:var(--muted);font-style:italic;}
735
+ .foot{margin:18px 0 0;font-family:var(--mono);font-size:10.5px;color:var(--muted);border-top:0.5px solid var(--line);padding-top:12px;}
736
+ @media (max-width:520px){.wrap{padding:28px 16px 40px;}.tags{justify-content:flex-start;}}
737
+ </style>
738
+ </head>
739
+ <body>
740
+ <main class="wrap">
741
+ <header class="mast">
742
+ <div>
743
+ <div class="wm">foo<b>.</b> packet progress</div>
744
+ <h1 class="ttl">${escHtml(title)}</h1>
745
+ <p class="sub">${escHtml(s.spec_id)} · derived from tasks.yaml</p>
746
+ </div>
747
+ <div class="tags">${tags || '<span class="tag">no header</span>'}</div>
748
+ </header>
749
+
750
+ <section class="chips">
751
+ <h2 class="sr">Progress</h2>
752
+ <div class="chip"><div class="k">tasks</div><div class="v">${s.tasks.done}<small>/${s.tasks.total} ${CLAIMED_HUMAN_LABEL}</small></div>${pbarHtml(s.tasks.done, s.tasks.total, "--proof")}</div>
753
+ <div class="chip"><div class="k">proof</div><div class="v">${s.proof.checks_done}<small>/${s.proof.checks_total} checks</small></div>${pbarHtml(s.proof.checks_done, s.proof.checks_total, "--proof")}</div>
754
+ ${spec}
755
+ </section>
756
+
757
+ <section class="nextcard">${next}</section>
758
+
759
+ <h2 class="seclabel">Tasks <span class="muted">· ${countLine}</span></h2>
760
+ <ul class="tks">${tasks || '<li class="tk o"><span class="tt muted">no tasks in tasks.yaml</span></li>'}</ul>
761
+
762
+ ${excHtml}
763
+
764
+ <p class="deep">Deep checks not yet run: source-authority, work-evidence, last meaningful learning.</p>
765
+ <p class="foot">generated by foo asp status · read-only, derived from ${escHtml(s.packet)}/tasks.yaml · no percent-complete by design</p>
766
+ </main>
767
+ </body>
768
+ </html>
769
+ `;
770
+ }