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.
- package/LICENSE +201 -0
- package/README.md +338 -2
- package/bin/foo +3 -0
- package/bin/foo.mjs +42 -0
- package/commands/bug.md +31 -0
- package/commands/discovery.md +28 -0
- package/commands/ideate.md +27 -0
- package/commands/init.md +28 -0
- package/commands/pitch.md +27 -0
- package/commands/preview.md +25 -0
- package/commands/research.md +31 -0
- package/commands/roast.md +27 -0
- package/commands/spec.md +30 -0
- package/config/pricing.yaml +198 -0
- package/config/routing-policy.yaml +171 -0
- package/dist-public/src/activeModel.d.ts +83 -0
- package/dist-public/src/activeModel.js +331 -0
- package/dist-public/src/agentAdapters.d.ts +122 -0
- package/dist-public/src/agentAdapters.js +178 -0
- package/dist-public/src/agentState.d.ts +43 -0
- package/dist-public/src/agentState.js +177 -0
- package/dist-public/src/bugCapsule.d.ts +94 -0
- package/dist-public/src/bugCapsule.js +227 -0
- package/dist-public/src/bugCapture.d.ts +26 -0
- package/dist-public/src/bugCapture.js +251 -0
- package/dist-public/src/bugOwnerLog.d.ts +93 -0
- package/dist-public/src/bugOwnerLog.js +304 -0
- package/dist-public/src/bugReportStore.d.ts +48 -0
- package/dist-public/src/bugReportStore.js +860 -0
- package/dist-public/src/bugSpecContext.d.ts +7 -0
- package/dist-public/src/bugSpecContext.js +129 -0
- package/dist-public/src/channelFreshness.d.ts +25 -0
- package/dist-public/src/channelFreshness.js +194 -0
- package/dist-public/src/cli.d.ts +30 -0
- package/dist-public/src/cli.js +1667 -0
- package/dist-public/src/cliArgs.d.ts +7 -0
- package/dist-public/src/cliArgs.js +44 -0
- package/dist-public/src/codeCatalog.d.ts +41 -0
- package/dist-public/src/codeCatalog.js +197 -0
- package/dist-public/src/commandContract.d.ts +84 -0
- package/dist-public/src/commandContract.js +390 -0
- package/dist-public/src/commandUsage.d.ts +3 -0
- package/dist-public/src/commandUsage.js +30 -0
- package/dist-public/src/commands/agent.d.ts +60 -0
- package/dist-public/src/commands/agent.js +1353 -0
- package/dist-public/src/commands/asp.d.ts +8 -0
- package/dist-public/src/commands/asp.js +669 -0
- package/dist-public/src/commands/evalCmd.d.ts +4 -0
- package/dist-public/src/commands/evalCmd.js +405 -0
- package/dist-public/src/commands/login.d.ts +25 -0
- package/dist-public/src/commands/login.js +94 -0
- package/dist-public/src/commands/loop.d.ts +16 -0
- package/dist-public/src/commands/loop.js +619 -0
- package/dist-public/src/commands/reconcile.d.ts +41 -0
- package/dist-public/src/commands/reconcile.js +292 -0
- package/dist-public/src/commands/runtime.d.ts +1 -0
- package/dist-public/src/commands/runtime.js +36 -0
- package/dist-public/src/commands/scanRedact.d.ts +8 -0
- package/dist-public/src/commands/scanRedact.js +266 -0
- package/dist-public/src/commands/setup.d.ts +28 -0
- package/dist-public/src/commands/setup.js +398 -0
- package/dist-public/src/commands/shared.d.ts +17 -0
- package/dist-public/src/commands/shared.js +147 -0
- package/dist-public/src/commands/source.d.ts +1 -0
- package/dist-public/src/commands/source.js +134 -0
- package/dist-public/src/commands/specCmd.d.ts +24 -0
- package/dist-public/src/commands/specCmd.js +794 -0
- package/dist-public/src/commands/update.d.ts +61 -0
- package/dist-public/src/commands/update.js +244 -0
- package/dist-public/src/comprehensionSignal.d.ts +20 -0
- package/dist-public/src/comprehensionSignal.js +166 -0
- package/dist-public/src/conductor/diversity.d.ts +48 -0
- package/dist-public/src/conductor/diversity.js +423 -0
- package/dist-public/src/conductor/engine.d.ts +7 -0
- package/dist-public/src/conductor/engine.js +295 -0
- package/dist-public/src/conductor/families/ideate.d.ts +2 -0
- package/dist-public/src/conductor/families/ideate.js +255 -0
- package/dist-public/src/conductor/families/interview.d.ts +2 -0
- package/dist-public/src/conductor/families/interview.js +39 -0
- package/dist-public/src/conductor/families/specSwarm.d.ts +2 -0
- package/dist-public/src/conductor/families/specSwarm.js +146 -0
- package/dist-public/src/conductor/familyDelivery.d.ts +6 -0
- package/dist-public/src/conductor/familyDelivery.js +37 -0
- package/dist-public/src/conductor/familyPackArtifact.d.ts +4 -0
- package/dist-public/src/conductor/familyPackArtifact.js +260 -0
- package/dist-public/src/conductor/ideateDecision.d.ts +6 -0
- package/dist-public/src/conductor/ideateDecision.js +237 -0
- package/dist-public/src/conductor/pack.d.ts +26 -0
- package/dist-public/src/conductor/pack.js +75 -0
- package/dist-public/src/conductor/types.d.ts +111 -0
- package/dist-public/src/conductor/types.js +7 -0
- package/dist-public/src/configPath.d.ts +2 -0
- package/dist-public/src/configPath.js +7 -0
- package/dist-public/src/discoveryArtifacts.d.ts +54 -0
- package/dist-public/src/discoveryArtifacts.js +467 -0
- package/dist-public/src/discoveryCommand.d.ts +127 -0
- package/dist-public/src/discoveryCommand.js +2148 -0
- package/dist-public/src/discoveryConnections.d.ts +339 -0
- package/dist-public/src/discoveryConnections.js +613 -0
- package/dist-public/src/discoveryDocs.d.ts +129 -0
- package/dist-public/src/discoveryDocs.js +577 -0
- package/dist-public/src/discoveryEvidence.d.ts +59 -0
- package/dist-public/src/discoveryEvidence.js +198 -0
- package/dist-public/src/discoveryFireflies.d.ts +25 -0
- package/dist-public/src/discoveryFireflies.js +551 -0
- package/dist-public/src/discoveryGmail.d.ts +12 -0
- package/dist-public/src/discoveryGmail.js +466 -0
- package/dist-public/src/discoveryGong.d.ts +12 -0
- package/dist-public/src/discoveryGong.js +491 -0
- package/dist-public/src/discoveryLearningRouting.d.ts +90 -0
- package/dist-public/src/discoveryLearningRouting.js +405 -0
- package/dist-public/src/discoveryMatching.d.ts +26 -0
- package/dist-public/src/discoveryMatching.js +73 -0
- package/dist-public/src/discoveryOutlook.d.ts +12 -0
- package/dist-public/src/discoveryOutlook.js +492 -0
- package/dist-public/src/discoveryProcess.d.ts +221 -0
- package/dist-public/src/discoveryProcess.js +703 -0
- package/dist-public/src/discoveryProof.d.ts +143 -0
- package/dist-public/src/discoveryProof.js +529 -0
- package/dist-public/src/discoveryProviders.d.ts +152 -0
- package/dist-public/src/discoveryProviders.js +756 -0
- package/dist-public/src/discoveryReadiness.d.ts +219 -0
- package/dist-public/src/discoveryReadiness.js +672 -0
- package/dist-public/src/discoveryReconcile.d.ts +57 -0
- package/dist-public/src/discoveryReconcile.js +424 -0
- package/dist-public/src/discoverySlack.d.ts +12 -0
- package/dist-public/src/discoverySlack.js +452 -0
- package/dist-public/src/discoverySources.d.ts +56 -0
- package/dist-public/src/discoverySources.js +322 -0
- package/dist-public/src/discoverySync.d.ts +52 -0
- package/dist-public/src/discoverySync.js +151 -0
- package/dist-public/src/discoverySynthesis.d.ts +57 -0
- package/dist-public/src/discoverySynthesis.js +215 -0
- package/dist-public/src/discoveryTeams.d.ts +12 -0
- package/dist-public/src/discoveryTeams.js +516 -0
- package/dist-public/src/discoveryZoom.d.ts +12 -0
- package/dist-public/src/discoveryZoom.js +523 -0
- package/dist-public/src/elicitationSurface.d.ts +13 -0
- package/dist-public/src/elicitationSurface.js +77 -0
- package/dist-public/src/errorReport.d.ts +99 -0
- package/dist-public/src/errorReport.js +443 -0
- package/dist-public/src/errorTaxonomy.d.ts +84 -0
- package/dist-public/src/errorTaxonomy.js +189 -0
- package/dist-public/src/evalCalibration.d.ts +82 -0
- package/dist-public/src/evalCalibration.js +169 -0
- package/dist-public/src/evalChecks.d.ts +21 -0
- package/dist-public/src/evalChecks.js +295 -0
- package/dist-public/src/evalCompile.d.ts +56 -0
- package/dist-public/src/evalCompile.js +58 -0
- package/dist-public/src/evalImprove.d.ts +60 -0
- package/dist-public/src/evalImprove.js +175 -0
- package/dist-public/src/evalJudge.d.ts +47 -0
- package/dist-public/src/evalJudge.js +143 -0
- package/dist-public/src/evalLabels.d.ts +100 -0
- package/dist-public/src/evalLabels.js +287 -0
- package/dist-public/src/evalRecord.d.ts +68 -0
- package/dist-public/src/evalRecord.js +165 -0
- package/dist-public/src/evalRoast.d.ts +88 -0
- package/dist-public/src/evalRoast.js +249 -0
- package/dist-public/src/evidenceIntake.d.ts +66 -0
- package/dist-public/src/evidenceIntake.js +356 -0
- package/dist-public/src/feedback.d.ts +78 -0
- package/dist-public/src/feedback.js +294 -0
- package/dist-public/src/firefliesClient.d.ts +106 -0
- package/dist-public/src/firefliesClient.js +258 -0
- package/dist-public/src/firstMoveTensions.d.ts +10 -0
- package/dist-public/src/firstMoveTensions.js +93 -0
- package/dist-public/src/gitRun.d.ts +21 -0
- package/dist-public/src/gitRun.js +50 -0
- package/dist-public/src/githubReconcile.d.ts +94 -0
- package/dist-public/src/githubReconcile.js +205 -0
- package/dist-public/src/gmailNango.d.ts +131 -0
- package/dist-public/src/gmailNango.js +329 -0
- package/dist-public/src/gongNango.d.ts +118 -0
- package/dist-public/src/gongNango.js +308 -0
- package/dist-public/src/grammarGate.d.ts +13 -0
- package/dist-public/src/grammarGate.js +72 -0
- package/dist-public/src/harnessGates.d.ts +65 -0
- package/dist-public/src/harnessGates.js +168 -0
- package/dist-public/src/humanSurfaceEval.d.ts +19 -0
- package/dist-public/src/humanSurfaceEval.js +156 -0
- package/dist-public/src/ideate.d.ts +43 -0
- package/dist-public/src/ideate.js +255 -0
- package/dist-public/src/ideateContext.d.ts +11 -0
- package/dist-public/src/ideateContext.js +83 -0
- package/dist-public/src/index.d.ts +2 -0
- package/dist-public/src/index.js +4 -0
- package/dist-public/src/initCommand.d.ts +25 -0
- package/dist-public/src/initCommand.js +558 -0
- package/dist-public/src/interviewGate.d.ts +25 -0
- package/dist-public/src/interviewGate.js +126 -0
- package/dist-public/src/jsonFile.d.ts +2 -0
- package/dist-public/src/jsonFile.js +22 -0
- package/dist-public/src/loopBudget.d.ts +182 -0
- package/dist-public/src/loopBudget.js +494 -0
- package/dist-public/src/loopContracts.d.ts +35 -0
- package/dist-public/src/loopContracts.js +364 -0
- package/dist-public/src/machineState.d.ts +1 -0
- package/dist-public/src/machineState.js +9 -0
- package/dist-public/src/notionNango.d.ts +93 -0
- package/dist-public/src/notionNango.js +279 -0
- package/dist-public/src/orchestrationResult.d.ts +11 -0
- package/dist-public/src/orchestrationResult.js +73 -0
- package/dist-public/src/outlookNango.d.ts +124 -0
- package/dist-public/src/outlookNango.js +327 -0
- package/dist-public/src/packetCorrections.d.ts +40 -0
- package/dist-public/src/packetCorrections.js +172 -0
- package/dist-public/src/packetGlossary.d.ts +6 -0
- package/dist-public/src/packetGlossary.js +42 -0
- package/dist-public/src/packetIndex.d.ts +54 -0
- package/dist-public/src/packetIndex.js +744 -0
- package/dist-public/src/packetIndexEntry.d.ts +3 -0
- package/dist-public/src/packetIndexEntry.js +128 -0
- package/dist-public/src/packetIndexServe.d.ts +13 -0
- package/dist-public/src/packetIndexServe.js +113 -0
- package/dist-public/src/packetIndexWorker.d.ts +7 -0
- package/dist-public/src/packetIndexWorker.js +45 -0
- package/dist-public/src/packetLifecycle.d.ts +30 -0
- package/dist-public/src/packetLifecycle.js +127 -0
- package/dist-public/src/packetReconcile.d.ts +73 -0
- package/dist-public/src/packetReconcile.js +386 -0
- package/dist-public/src/packetStatus.d.ts +125 -0
- package/dist-public/src/packetStatus.js +770 -0
- package/dist-public/src/packetValidation.d.ts +17 -0
- package/dist-public/src/packetValidation.js +333 -0
- package/dist-public/src/packetVerify.d.ts +73 -0
- package/dist-public/src/packetVerify.js +303 -0
- package/dist-public/src/packetWrite.d.ts +52 -0
- package/dist-public/src/packetWrite.js +448 -0
- package/dist-public/src/pitch.d.ts +38 -0
- package/dist-public/src/pitch.js +154 -0
- package/dist-public/src/processMemo.d.ts +8 -0
- package/dist-public/src/processMemo.js +23 -0
- package/dist-public/src/productDetect.d.ts +30 -0
- package/dist-public/src/productDetect.js +376 -0
- package/dist-public/src/profile.d.ts +62 -0
- package/dist-public/src/profile.js +334 -0
- package/dist-public/src/questionModel.d.ts +46 -0
- package/dist-public/src/questionModel.js +74 -0
- package/dist-public/src/questionText.d.ts +7 -0
- package/dist-public/src/questionText.js +38 -0
- package/dist-public/src/redactConfig.d.ts +2 -0
- package/dist-public/src/redactConfig.js +11 -0
- package/dist-public/src/redactEngine.d.ts +59 -0
- package/dist-public/src/redactEngine.js +509 -0
- package/dist-public/src/redactGate.d.ts +63 -0
- package/dist-public/src/redactGate.js +209 -0
- package/dist-public/src/repoRead.d.ts +29 -0
- package/dist-public/src/repoRead.js +160 -0
- package/dist-public/src/research.d.ts +242 -0
- package/dist-public/src/research.js +1314 -0
- package/dist-public/src/researchCompletion.d.ts +34 -0
- package/dist-public/src/researchCompletion.js +123 -0
- package/dist-public/src/researchExecution.d.ts +47 -0
- package/dist-public/src/researchExecution.js +309 -0
- package/dist-public/src/researchFormat.d.ts +21 -0
- package/dist-public/src/researchFormat.js +77 -0
- package/dist-public/src/researchGate.d.ts +86 -0
- package/dist-public/src/researchGate.js +1036 -0
- package/dist-public/src/researchReceipt.d.ts +76 -0
- package/dist-public/src/researchReceipt.js +148 -0
- package/dist-public/src/researchRender.d.ts +2 -0
- package/dist-public/src/researchRender.js +267 -0
- package/dist-public/src/researchSigningKey.d.ts +19 -0
- package/dist-public/src/researchSigningKey.js +95 -0
- package/dist-public/src/riskClassify.d.ts +42 -0
- package/dist-public/src/riskClassify.js +149 -0
- package/dist-public/src/routines.d.ts +42 -0
- package/dist-public/src/routines.js +269 -0
- package/dist-public/src/routingPolicy.d.ts +172 -0
- package/dist-public/src/routingPolicy.js +646 -0
- package/dist-public/src/secretInput.d.ts +8 -0
- package/dist-public/src/secretInput.js +89 -0
- package/dist-public/src/selfCheck.d.ts +7 -0
- package/dist-public/src/selfCheck.js +473 -0
- package/dist-public/src/selfCheckCache.d.ts +29 -0
- package/dist-public/src/selfCheckCache.js +147 -0
- package/dist-public/src/severityClass.d.ts +15 -0
- package/dist-public/src/severityClass.js +22 -0
- package/dist-public/src/skillClientPolicy.d.ts +8 -0
- package/dist-public/src/skillClientPolicy.js +16 -0
- package/dist-public/src/skillLocal.d.ts +98 -0
- package/dist-public/src/skillLocal.js +463 -0
- package/dist-public/src/skillTreeDigest.d.ts +9 -0
- package/dist-public/src/skillTreeDigest.js +64 -0
- package/dist-public/src/slackNango.d.ts +62 -0
- package/dist-public/src/slackNango.js +208 -0
- package/dist-public/src/social/arxiv.d.ts +19 -0
- package/dist-public/src/social/arxiv.js +96 -0
- package/dist-public/src/social/github.d.ts +2 -0
- package/dist-public/src/social/github.js +78 -0
- package/dist-public/src/social/hackernews.d.ts +2 -0
- package/dist-public/src/social/hackernews.js +67 -0
- package/dist-public/src/social/index.d.ts +10 -0
- package/dist-public/src/social/index.js +79 -0
- package/dist-public/src/social/reddit.d.ts +2 -0
- package/dist-public/src/social/reddit.js +125 -0
- package/dist-public/src/social/types.d.ts +73 -0
- package/dist-public/src/social/types.js +61 -0
- package/dist-public/src/social/xml.d.ts +10 -0
- package/dist-public/src/social/xml.js +49 -0
- package/dist-public/src/social/youtube.d.ts +7 -0
- package/dist-public/src/social/youtube.js +217 -0
- package/dist-public/src/sourceIngress.d.ts +488 -0
- package/dist-public/src/sourceIngress.js +1651 -0
- package/dist-public/src/specAfter.d.ts +187 -0
- package/dist-public/src/specAfter.js +814 -0
- package/dist-public/src/specBudget.d.ts +124 -0
- package/dist-public/src/specBudget.js +424 -0
- package/dist-public/src/specDecisionSignals.d.ts +38 -0
- package/dist-public/src/specDecisionSignals.js +121 -0
- package/dist-public/src/specLinkedCommits.d.ts +7 -0
- package/dist-public/src/specLinkedCommits.js +107 -0
- package/dist-public/src/specSections.d.ts +105 -0
- package/dist-public/src/specSections.js +718 -0
- package/dist-public/src/statusGlyphs.d.ts +18 -0
- package/dist-public/src/statusGlyphs.js +75 -0
- package/dist-public/src/swarmCommand.d.ts +10 -0
- package/dist-public/src/swarmCommand.js +96 -0
- package/dist-public/src/teamsNango.d.ts +114 -0
- package/dist-public/src/teamsNango.js +301 -0
- package/dist-public/src/tokenStore.d.ts +10 -0
- package/dist-public/src/tokenStore.js +77 -0
- package/dist-public/src/trustEnvelope.d.ts +78 -0
- package/dist-public/src/trustEnvelope.js +392 -0
- package/dist-public/src/untrustedText.d.ts +6 -0
- package/dist-public/src/untrustedText.js +20 -0
- package/dist-public/src/updateNotice.d.ts +41 -0
- package/dist-public/src/updateNotice.js +290 -0
- package/dist-public/src/vendorYaml.d.ts +1 -0
- package/dist-public/src/vendorYaml.js +7374 -0
- package/dist-public/src/workflowRunActuals.d.ts +67 -0
- package/dist-public/src/workflowRunActuals.js +184 -0
- package/dist-public/src/zendeskDiscovery.d.ts +12 -0
- package/dist-public/src/zendeskDiscovery.js +513 -0
- package/dist-public/src/zendeskNango.d.ts +131 -0
- package/dist-public/src/zendeskNango.js +344 -0
- package/dist-public/src/zoomNango.d.ts +106 -0
- package/dist-public/src/zoomNango.js +351 -0
- package/docs/CHANGELOG.md +32 -0
- package/docs/THIRD_PARTY_NOTICES.md +36 -0
- package/package.json +71 -4
- package/schemas/bug-capsule-receipt.schema.json +54 -0
- package/schemas/bug-capsule.schema.json +189 -0
- package/schemas/discovery-evidence-envelope.schema.json +79 -0
- package/schemas/discovery-processing-receipt.schema.json +71 -0
- package/schemas/discovery-service-contract.schema.json +189 -0
- package/skills/AGENTS.md +32 -0
- package/skills/README.md +287 -0
- package/skills/bug/SKILL.md +49 -0
- package/skills/build-preview-spec/SKILL.md +103 -0
- package/skills/capture-external-artifact/SKILL.md +23 -0
- package/skills/capture-external-artifact/agents/openai.yaml +4 -0
- package/skills/capture-external-artifact/references/extractor-libraries.md +27 -0
- package/skills/check-spec-health/SKILL.md +45 -0
- package/skills/check-spec-health/agents/openai.yaml +4 -0
- package/skills/competitive-positioning/SKILL.md +86 -0
- package/skills/competitive-positioning/agents/openai.yaml +4 -0
- package/skills/cpo-lens/SKILL.md +398 -0
- package/skills/cpo-lens/agents/openai.yaml +4 -0
- package/skills/create-prd/SKILL.md +26 -0
- package/skills/create-prd/agents/openai.yaml +4 -0
- package/skills/customer-discovery/SKILL.md +192 -0
- package/skills/design-system/SKILL.md +49 -0
- package/skills/design-system/agents/openai.yaml +4 -0
- package/skills/discussion-guide/SKILL.md +75 -0
- package/skills/discussion-guide/agents/openai.yaml +4 -0
- package/skills/firecrawl-research-papers/SKILL.md +96 -0
- package/skills/firecrawl-research-papers/agents/openai.yaml +4 -0
- package/skills/foo-roast/SKILL.md +325 -0
- package/skills/generate-gtm-brief/SKILL.md +41 -0
- package/skills/generate-gtm-brief/agents/openai.yaml +4 -0
- package/skills/generate-gtm-brief/references/gtm-brief-template.md +56 -0
- package/skills/gong-quotes/SKILL.md +43 -0
- package/skills/gong-quotes/agents/openai.yaml +4 -0
- package/skills/grill-me/SKILL.md +27 -0
- package/skills/grill-me/agents/openai.yaml +4 -0
- package/skills/ideate/SKILL.md +180 -0
- package/skills/ideate/references/family-packs.md +5 -0
- package/skills/initiative-tracker/SKILL.md +23 -0
- package/skills/initiative-tracker/agents/openai.yaml +4 -0
- package/skills/interop-review/SKILL.md +25 -0
- package/skills/interop-review/agents/openai.yaml +4 -0
- package/skills/interview-me/SKILL.md +404 -0
- package/skills/interview-me/agents/openai.yaml +4 -0
- package/skills/loop-library-audit/SKILL.md +123 -0
- package/skills/pitch/SKILL.md +110 -0
- package/skills/pitch/references/opportunity-assessment.md +136 -0
- package/skills/pitch/references/prfaq-template.md +54 -0
- package/skills/pmm-release-briefing/SKILL.md +27 -0
- package/skills/pmm-release-briefing/agents/openai.yaml +4 -0
- package/skills/pmm-release-briefing/references/variant-generalized.md +16 -0
- package/skills/positioning-messaging/SKILL.md +70 -0
- package/skills/positioning-messaging/agents/openai.yaml +4 -0
- package/skills/positioning-messaging/references/guest-insights.md +1400 -0
- package/skills/preview-swarm/SKILL.md +418 -0
- package/skills/preview-swarm/agents/openai.yaml +4 -0
- package/skills/preview-swarm/references/html-effectiveness-build-previews.md +168 -0
- package/skills/product-copywriting/SKILL.md +127 -0
- package/skills/product-copywriting/agents/openai.yaml +4 -0
- package/skills/product-copywriting/references/foo-voice.md +26 -0
- package/skills/product-shape-interview/SKILL.md +219 -0
- package/skills/product-shape-interview/agents/openai.yaml +4 -0
- package/skills/ready-for-release/SKILL.md +39 -0
- package/skills/ready-for-release/agents/openai.yaml +4 -0
- package/skills/redact-transcripts/SKILL.md +23 -0
- package/skills/redact-transcripts/agents/openai.yaml +4 -0
- package/skills/research-brief/SKILL.md +48 -0
- package/skills/research-brief/agents/openai.yaml +4 -0
- package/skills/research-refresh/SKILL.md +63 -0
- package/skills/research-refresh/references/worked-cases.md +44 -0
- package/skills/review-loop/SKILL.md +200 -0
- package/skills/review-spec/SKILL.md +32 -0
- package/skills/review-spec/agents/openai.yaml +4 -0
- package/skills/rfc-from-research/SKILL.md +42 -0
- package/skills/rfc-from-research/agents/openai.yaml +4 -0
- package/skills/rfc-from-research/references/rfc-pattern.md +69 -0
- package/skills/skill-registry.yaml +810 -0
- package/skills/spec-cleanup/SKILL.md +103 -0
- package/skills/spec-cleanup/agents/openai.yaml +4 -0
- package/skills/spec-swarm/SKILL.md +87 -0
- package/skills/spec-swarm/agents/openai.yaml +4 -0
- package/skills/spec-swarm/template/SKILL.template.md +94 -0
- package/skills/spec-swarm/template/personas/accessibility-inclusion.md +40 -0
- package/skills/spec-swarm/template/personas/ai-technical-pm.md +69 -0
- package/skills/spec-swarm/template/personas/analytics-engineering.md +39 -0
- package/skills/spec-swarm/template/personas/applied-ai-eval.md +64 -0
- package/skills/spec-swarm/template/personas/business-gm-pm.md +67 -0
- package/skills/spec-swarm/template/personas/consumer-craft-pm.md +68 -0
- package/skills/spec-swarm/template/personas/content-design.md +40 -0
- package/skills/spec-swarm/template/personas/cpo.md +42 -0
- package/skills/spec-swarm/template/personas/data-science.md +42 -0
- package/skills/spec-swarm/template/personas/developer-experience.md +65 -0
- package/skills/spec-swarm/template/personas/dx-product.md +70 -0
- package/skills/spec-swarm/template/personas/field.md +43 -0
- package/skills/spec-swarm/template/personas/finance-cfo.md +40 -0
- package/skills/spec-swarm/template/personas/growth-pm.md +67 -0
- package/skills/spec-swarm/template/personas/platform-pm.md +67 -0
- package/skills/spec-swarm/template/personas/pmm.md +43 -0
- package/skills/spec-swarm/template/personas/principal-product-designer.md +42 -0
- package/skills/spec-swarm/template/personas/product-ops-program.md +41 -0
- package/skills/spec-swarm/template/personas/qa-release.md +41 -0
- package/skills/spec-swarm/template/personas/responsible-ai-safety.md +64 -0
- package/skills/spec-swarm/template/personas/security-privacy-legal.md +40 -0
- package/skills/spec-swarm/template/personas/strategy-competitive.md +41 -0
- package/skills/spec-swarm/template/personas/technical-architect.md +42 -0
- package/skills/spec-swarm/template/personas/technical-lead.md +43 -0
- package/skills/spec-swarm/template/personas/user-research.md +41 -0
- package/skills/spec-swarm-accessibility-inclusion/SKILL.md +95 -0
- package/skills/spec-swarm-accessibility-inclusion/agents/openai.yaml +4 -0
- package/skills/spec-swarm-ai-technical-pm/SKILL.md +124 -0
- package/skills/spec-swarm-analytics-engineering/SKILL.md +94 -0
- package/skills/spec-swarm-analytics-engineering/agents/openai.yaml +4 -0
- package/skills/spec-swarm-applied-ai-eval/SKILL.md +120 -0
- package/skills/spec-swarm-business-gm-pm/SKILL.md +122 -0
- package/skills/spec-swarm-consumer-craft-pm/SKILL.md +122 -0
- package/skills/spec-swarm-content-design/SKILL.md +95 -0
- package/skills/spec-swarm-content-design/agents/openai.yaml +4 -0
- package/skills/spec-swarm-cpo/SKILL.md +94 -0
- package/skills/spec-swarm-cpo/agents/openai.yaml +4 -0
- package/skills/spec-swarm-data-science/SKILL.md +95 -0
- package/skills/spec-swarm-data-science/agents/openai.yaml +4 -0
- package/skills/spec-swarm-developer-experience/SKILL.md +121 -0
- package/skills/spec-swarm-dx-product/SKILL.md +126 -0
- package/skills/spec-swarm-field/SKILL.md +96 -0
- package/skills/spec-swarm-field/agents/openai.yaml +4 -0
- package/skills/spec-swarm-finance-cfo/SKILL.md +95 -0
- package/skills/spec-swarm-finance-cfo/agents/openai.yaml +4 -0
- package/skills/spec-swarm-growth-pm/SKILL.md +122 -0
- package/skills/spec-swarm-platform-pm/SKILL.md +122 -0
- package/skills/spec-swarm-pmm/SKILL.md +96 -0
- package/skills/spec-swarm-pmm/agents/openai.yaml +4 -0
- package/skills/spec-swarm-principal-product-designer/SKILL.md +95 -0
- package/skills/spec-swarm-principal-product-designer/agents/openai.yaml +4 -0
- package/skills/spec-swarm-product-ops-program/SKILL.md +96 -0
- package/skills/spec-swarm-product-ops-program/agents/openai.yaml +4 -0
- package/skills/spec-swarm-qa-release/SKILL.md +96 -0
- package/skills/spec-swarm-qa-release/agents/openai.yaml +4 -0
- package/skills/spec-swarm-responsible-ai-safety/SKILL.md +120 -0
- package/skills/spec-swarm-security-privacy-legal/SKILL.md +95 -0
- package/skills/spec-swarm-security-privacy-legal/agents/openai.yaml +4 -0
- package/skills/spec-swarm-strategy-competitive/SKILL.md +96 -0
- package/skills/spec-swarm-strategy-competitive/agents/openai.yaml +4 -0
- package/skills/spec-swarm-technical-architect/SKILL.md +95 -0
- package/skills/spec-swarm-technical-architect/agents/openai.yaml +4 -0
- package/skills/spec-swarm-technical-lead/SKILL.md +96 -0
- package/skills/spec-swarm-technical-lead/agents/openai.yaml +4 -0
- package/skills/spec-swarm-user-research/SKILL.md +95 -0
- package/skills/spec-swarm-user-research/agents/openai.yaml +4 -0
- package/skills/strategy-check/SKILL.md +170 -0
- package/skills/strategy-check/agents/openai.yaml +4 -0
- package/skills/study-digest/SKILL.md +41 -0
- package/skills/synthetic-customer/SKILL.md +68 -0
- package/skills/update-cli-spec/SKILL.md +69 -0
- package/skills/update-cli-spec/agents/openai.yaml +4 -0
- package/skills/voc-researcher/SKILL.md +45 -0
- package/skills/voc-researcher/agents/openai.yaml +4 -0
- 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 {};
|