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,619 @@
|
|
|
1
|
+
// `foo loop design|check|status|estimate|routine` plus the top-level
|
|
2
|
+
// `foo estimate` (spec-budget-v0), which reuses the same pricing primitives.
|
|
3
|
+
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { checkLoopContracts, loadLoopContracts, renderLoopDesign, renderLoopStatus, writeAgreedBudget } from "../loopContracts.js";
|
|
6
|
+
import { buildAgreedBudget, estimateLoopBudget, handshakeFloorUsd, loadPricing, measureSourcesCounted } from "../loopBudget.js";
|
|
7
|
+
import { autonomyDenied, loadModelRoster, resolveActiveModel } from "../activeModel.js";
|
|
8
|
+
import { estimateSpecBudget } from "../specBudget.js";
|
|
9
|
+
import { parse as parseYaml } from "../vendorYaml.js";
|
|
10
|
+
import { parseRoutines, validateRoutine, emitBinding } from "../routines.js";
|
|
11
|
+
import { supportedRuntimes } from "../agentAdapters.js";
|
|
12
|
+
import { glyphsEnabled, withGlyph } from "../statusGlyphs.js";
|
|
13
|
+
import { flagValue, hasFlag, resolvePacketDir } from "./shared.js";
|
|
14
|
+
import { UsageError } from "../errorTaxonomy.js";
|
|
15
|
+
export async function runLoopCommand(root, command, packetArg, args) {
|
|
16
|
+
if (!command)
|
|
17
|
+
throw new UsageError("loop command missing", "E_COMMAND_MISSING");
|
|
18
|
+
const { packetDir } = resolvePacketDir(root, packetArg);
|
|
19
|
+
if (command === "design")
|
|
20
|
+
return renderLoopDesign(packetDir);
|
|
21
|
+
if (command === "check") {
|
|
22
|
+
const strictBudget = hasFlag(args, "--strict-budget") || process.env.FOO_BUDGET_REQUIRED === "1";
|
|
23
|
+
return `${checkLoopContracts(root, packetDir, { strictBudget })}\n`;
|
|
24
|
+
}
|
|
25
|
+
if (command === "status")
|
|
26
|
+
return `${JSON.stringify(renderLoopStatus(packetDir), null, 2)}\n`;
|
|
27
|
+
if (command === "estimate")
|
|
28
|
+
return runLoopEstimate(root, packetDir, args);
|
|
29
|
+
throw new UsageError(`unknown loop command: ${command}`, "E_BAD_FLAG");
|
|
30
|
+
}
|
|
31
|
+
// foo loop routine <packet> [--emit <runtime>] [--json] [--strict]
|
|
32
|
+
// (portable-routines-v0): validate the routines in loops.yaml, resolve the
|
|
33
|
+
// runtimes they are bound to, and optionally emit a per-runtime binding. The
|
|
34
|
+
// contract is canonical; only the binding is per-runtime. Advisory by default; a
|
|
35
|
+
// B4 autonomy finding blocks only under --strict.
|
|
36
|
+
export function runLoopRoutine(root, packetDir, args) {
|
|
37
|
+
const { specId, routines } = parseRoutines(packetDir);
|
|
38
|
+
const packetRel = path.relative(root, packetDir).split(path.sep).join("/");
|
|
39
|
+
const json = hasFlag(args, "--json");
|
|
40
|
+
const emit = flagValue(args, "--emit");
|
|
41
|
+
if (emit) {
|
|
42
|
+
const routineId = flagValue(args, "--routine");
|
|
43
|
+
const routine = routineId ? routines.find((r) => r.id === routineId) : routines[0];
|
|
44
|
+
if (!routine)
|
|
45
|
+
throw new Error(routineId ? `routine ${routineId} not found` : "no routines in loops.yaml");
|
|
46
|
+
return { output: emitBinding(routine, emit, packetRel), exitCode: 0 };
|
|
47
|
+
}
|
|
48
|
+
const report = routines.map((routine) => ({
|
|
49
|
+
routine: routine.id,
|
|
50
|
+
title: routine.title,
|
|
51
|
+
trigger: routine.trigger,
|
|
52
|
+
unattended: routine.unattended,
|
|
53
|
+
runtimes: routine.runtimes,
|
|
54
|
+
findings: validateRoutine(routine),
|
|
55
|
+
}));
|
|
56
|
+
const b4 = report.flatMap((r) => r.findings).filter((f) => f.boundary === "B4");
|
|
57
|
+
// B4 is the autonomy boundary: it blocks only under --strict (signals-over-gates).
|
|
58
|
+
const exitCode = hasFlag(args, "--strict") && b4.length > 0 ? 1 : 0;
|
|
59
|
+
if (json) {
|
|
60
|
+
return {
|
|
61
|
+
output: `${JSON.stringify({ spec_id: specId, packet: packetRel, routines: report, supported_runtimes: supportedRuntimes() }, null, 2)}\n`,
|
|
62
|
+
exitCode,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const glyphs = glyphsEnabled({ isTTY: Boolean(process.stdout.isTTY), env: process.env, args });
|
|
66
|
+
const lines = [];
|
|
67
|
+
if (routines.length === 0) {
|
|
68
|
+
lines.push("No routines in loops.yaml. A routine is a loop with a schedule/event trigger and an adapters block.");
|
|
69
|
+
lines.push("Next: foo loop design <packet-dir> > <packet-dir>/loops.yaml, then add trigger + adapters.");
|
|
70
|
+
return { output: `${lines.join("\n")}\n`, exitCode: 0 };
|
|
71
|
+
}
|
|
72
|
+
lines.push(withGlyph(b4.length > 0 ? "drift" : "proven", `routines in ${packetRel} (${routines.length})`, glyphs));
|
|
73
|
+
for (const r of report) {
|
|
74
|
+
const triggerDesc = r.trigger.kind === "schedule" ? `schedule ${r.trigger.cron ?? "(no cron)"}` : r.trigger.kind;
|
|
75
|
+
lines.push(` ${r.routine} ${r.title} — ${triggerDesc}${r.unattended ? " · unattended" : ""}`);
|
|
76
|
+
lines.push(` runtimes: ${r.runtimes.join(", ") || "(none bound)"}`);
|
|
77
|
+
for (const f of r.findings) {
|
|
78
|
+
lines.push(` [${f.boundary ?? f.class}/${f.severity}] ${f.summary}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
lines.push("");
|
|
82
|
+
lines.push(` Supported runtimes: ${supportedRuntimes().join(", ")}. Emit a binding: foo loop routine ${packetRel} --emit <runtime>`);
|
|
83
|
+
lines.push(b4.length > 0
|
|
84
|
+
? "\n[boundary] B4 autonomy — an unattended routine missing verify/terminal/approval is unbounded; run with --strict to block."
|
|
85
|
+
: "\n[signal] advisory only — routine validation surfaces findings, it does not block.");
|
|
86
|
+
return { output: `${lines.join("\n")}\n`, exitCode };
|
|
87
|
+
}
|
|
88
|
+
// foo loop estimate: pre-flight cost estimate for a loop, as a range, never a
|
|
89
|
+
// single number (R-003). Dry-run by default — it writes nothing. With --agree
|
|
90
|
+
// it records the agreed budget into loops.yaml and appends a budget_agreed
|
|
91
|
+
// event as provenance (R-004), reusing the dry-run-before-writes pattern.
|
|
92
|
+
async function runLoopEstimate(root, packetDir, args) {
|
|
93
|
+
const asJson = hasFlag(args, "--json");
|
|
94
|
+
const agree = hasFlag(args, "--agree");
|
|
95
|
+
const loopId = flagValue(args, "--loop");
|
|
96
|
+
const unitArg = flagValue(args, "--unit");
|
|
97
|
+
if (unitArg && unitArg !== "usd" && unitArg !== "tokens")
|
|
98
|
+
throw new UsageError("--unit must be usd or tokens", "E_BAD_FLAG");
|
|
99
|
+
const { loops } = loadLoopContracts(packetDir);
|
|
100
|
+
const loop = loopId ? loops.find((entry) => entry.id === loopId) : loops[0];
|
|
101
|
+
if (!loop)
|
|
102
|
+
throw new Error(loopId ? `loop ${loopId} not found` : "no loops in loops.yaml");
|
|
103
|
+
const pricing = loadPricing(root);
|
|
104
|
+
const unit = unitArg ?? loop.budget?.unit ?? "usd";
|
|
105
|
+
// Measure sources with count_tokens when a key is available (the accurate
|
|
106
|
+
// tier); --structural forces the offline approximation. Either way the cost
|
|
107
|
+
// model stays heuristic in V0.
|
|
108
|
+
// Resolve the active model once (glm-model-awareness-v0 R-001/D-003 single source):
|
|
109
|
+
// a budget model_mix still wins, else default to the resolved worker (GLM behind the
|
|
110
|
+
// harness prices at GLM rates, not hardcoded Opus). The roster is data, loaded from
|
|
111
|
+
// the same root as the price table (model-roster-data-v0 R-001).
|
|
112
|
+
const active = resolveActiveModel(process.env, loadModelRoster(root));
|
|
113
|
+
const worker = loop.budget?.estimate?.model_mix?.worker ?? active.worker;
|
|
114
|
+
const apiKey = process.env.ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_AUTH_TOKEN;
|
|
115
|
+
// R-002 (OQ-4): egress is opt-in. Default to the structural tier; only send
|
|
116
|
+
// source content to the count_tokens API when explicitly asked, and even then
|
|
117
|
+
// the egress guard redacts/refuses secret-bearing or local-only sources. The
|
|
118
|
+
// endpoint follows the active base URL (R-003), not the hardcoded Anthropic one.
|
|
119
|
+
const preferApi = hasFlag(args, "--count-tokens") && !hasFlag(args, "--structural");
|
|
120
|
+
const measured = await measureSourcesCounted(packetDir, loop.observe.sources, { model: worker, apiKey, preferApi, countTokensUrl: active.countTokensUrl, routedNonNative: active.routed });
|
|
121
|
+
const result = estimateLoopBudget({ packetDir, sources: loop.observe.sources, pricing, unit, budget: loop.budget, measured, active });
|
|
122
|
+
const floor = handshakeFloorUsd();
|
|
123
|
+
// B4 fail-closed: an unpriced estimate (degraded pricing / unknown model) can
|
|
124
|
+
// never claim to be below the handshake floor (model-roster-data-v0 R-002).
|
|
125
|
+
const belowThreshold = result.priced && result.usd.expected < floor;
|
|
126
|
+
const glyphs = glyphsEnabled({ isTTY: Boolean(process.stdout.isTTY), env: process.env, args });
|
|
127
|
+
if (!agree) {
|
|
128
|
+
if (asJson) {
|
|
129
|
+
return `${JSON.stringify({ loop: loop.id, dry_run: true, below_threshold: belowThreshold, handshake_floor_usd: floor, estimate: result }, null, 2)}\n`;
|
|
130
|
+
}
|
|
131
|
+
return renderEstimateCard(loop.id, result, { agreed: false, belowThreshold, floor, glyphs });
|
|
132
|
+
}
|
|
133
|
+
// T-005 / R-005: autonomy on GLM is default-deny — the one place GLM-awareness
|
|
134
|
+
// BLOCKS (a B4 boundary, not a signal). Estimation above still works (you can price
|
|
135
|
+
// a GLM loop); only the --agree approval handshake is gated, until both follow-ons
|
|
136
|
+
// land. Override is deliberate and logged, mirroring FOO_ALLOW_MAIN_EDITS.
|
|
137
|
+
const glmAutonomyOverride = process.env.FOO_ALLOW_GLM_AUTONOMY === "1";
|
|
138
|
+
if (autonomyDenied(active.family) && !glmAutonomyOverride) {
|
|
139
|
+
throw new Error("autonomy on GLM is default-deny (glm-model-awareness-v0 T-005 / R-005).\n" +
|
|
140
|
+
"GLM's weaker self-correction makes it a degraded verifier of its own loop, and the\n" +
|
|
141
|
+
"inference-egress boundary is not yet in place. Unblocks when BOTH land:\n" +
|
|
142
|
+
" - glm-inference-egress-boundary-v0 (R-006a)\n" +
|
|
143
|
+
" - glm-eval-goldenset-v0 (R-006b)\n" +
|
|
144
|
+
"Run on a native (Anthropic) model, or override deliberately with FOO_ALLOW_GLM_AUTONOMY=1.");
|
|
145
|
+
}
|
|
146
|
+
// --agree: write the agreed budget and record provenance. For an unmeasurable loop
|
|
147
|
+
// there is no estimate to agree to — the human names a cap instead (--limit).
|
|
148
|
+
const limitRaw = flagValue(args, "--limit");
|
|
149
|
+
const limit = limitRaw === undefined ? undefined : Number(limitRaw);
|
|
150
|
+
if (limitRaw !== undefined && !Number.isFinite(limit))
|
|
151
|
+
throw new UsageError("--limit requires a dollar amount, e.g. --limit 2.00", "E_BAD_FLAG");
|
|
152
|
+
const by = flagValue(args, "--by") ?? process.env.USER ?? "unknown";
|
|
153
|
+
const at = new Date().toISOString();
|
|
154
|
+
const budget = buildAgreedBudget(result, { by, at, limit });
|
|
155
|
+
writeAgreedBudget(packetDir, loop.id, budget);
|
|
156
|
+
appendBudgetAgreedEvent(packetDir, loop, result, { by, at });
|
|
157
|
+
// A deliberate GLM-autonomy override is auditable: record who bypassed the boundary.
|
|
158
|
+
if (active.family === "glm" && glmAutonomyOverride) {
|
|
159
|
+
appendAutonomyOverrideEvent(packetDir, loop, active, { by, at });
|
|
160
|
+
}
|
|
161
|
+
if (asJson) {
|
|
162
|
+
return `${JSON.stringify({ loop: loop.id, agreed: true, by, at, budget }, null, 2)}\n`;
|
|
163
|
+
}
|
|
164
|
+
return renderEstimateCard(loop.id, result, { agreed: true, belowThreshold, floor, by, glyphs });
|
|
165
|
+
}
|
|
166
|
+
function renderEstimateCard(loopId, result, meta) {
|
|
167
|
+
const lines = [];
|
|
168
|
+
lines.push(`Loop ${loopId} budget estimate (basis: ${result.basis}, confidence: ${result.confidence})`);
|
|
169
|
+
// R-004 / T-004: when the active model is only assumed (inferred from a base-URL
|
|
170
|
+
// override), fly the ⚠️ tag so the estimate is never read as a confident,
|
|
171
|
+
// model-confirmed number. Glyph is human-surface-only; the word carries the meaning
|
|
172
|
+
// when glyphs are off. Never reaches --json (early return above).
|
|
173
|
+
const active = result.model_provenance;
|
|
174
|
+
const egressGated = result.measured_method === "structural_egress_gated";
|
|
175
|
+
if (active.confidence === "assumed" || egressGated) {
|
|
176
|
+
const pricedFamily = /glm/i.test(result.model_mix.worker) ? "glm" : /claude/i.test(result.model_mix.worker) ? "claude" : "unknown";
|
|
177
|
+
let tag;
|
|
178
|
+
if (egressGated) {
|
|
179
|
+
// D-010: count_tokens was withheld because the endpoint is a routed third party,
|
|
180
|
+
// so the estimate is structural even though a key was available — say so (D-002).
|
|
181
|
+
tag = ` estimated · count_tokens withheld (egress-gated: routed to ${active.provider}) — structural until the inference-egress boundary lands`;
|
|
182
|
+
}
|
|
183
|
+
else if (pricedFamily !== active.family) {
|
|
184
|
+
// The budget was pinned for one family but a different model is live now —
|
|
185
|
+
// honor the recorded pin, but surface the conflict instead of silently re-pricing.
|
|
186
|
+
tag = ` estimated · budget pinned for ${pricedFamily}, but ${active.family} (${active.provider}) is active — re-estimate to price the live model`;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
tag = ` estimated · model: unconfirmed (assumed ${active.family} via ${active.provider} base URL)`;
|
|
190
|
+
}
|
|
191
|
+
lines.push(withGlyph("drift", tag, meta.glyphs));
|
|
192
|
+
}
|
|
193
|
+
// Degraded/unknown-model findings (model-roster-data-v0 R-002/R-003): always
|
|
194
|
+
// worded; the ⚠️ drift glyph rides along only on a human TTY (glyphsEnabled
|
|
195
|
+
// gating upstream), never into --json (early return above).
|
|
196
|
+
for (const finding of result.findings) {
|
|
197
|
+
lines.push(withGlyph("drift", ` finding: ${finding}`, meta.glyphs));
|
|
198
|
+
}
|
|
199
|
+
// No fabricated range for a loop whose cost we genuinely cannot know. Printing
|
|
200
|
+
// "$1.0100 expected .. $2.5250 ceiling" for a loop that reads the web is false
|
|
201
|
+
// precision: four decimal places of confidence on a guess. Say "unknown" instead.
|
|
202
|
+
lines.push(result.unmeasurable
|
|
203
|
+
? " Dollars: unknown — this loop reads sources productfoo cannot size (see findings above)"
|
|
204
|
+
: result.priced
|
|
205
|
+
? ` Dollars: $${result.usd.expected.toFixed(2)} expected .. $${result.usd.ceiling.toFixed(2)} ceiling`
|
|
206
|
+
: " Dollars: unavailable — token-only estimate (see findings above)");
|
|
207
|
+
lines.push(result.unmeasurable
|
|
208
|
+
? " Tokens: unknown — observation size is not knowable offline"
|
|
209
|
+
: ` Tokens: ${result.tokens.expected} expected .. ${result.tokens.ceiling} ceiling`);
|
|
210
|
+
lines.push(` Unit agreed in: ${result.unit}`);
|
|
211
|
+
lines.push(` Iterations: ${result.expected_iterations} expected .. ${result.ceiling_iterations} ceiling`);
|
|
212
|
+
lines.push(` Models: worker=${result.model_mix.worker}, verifier=${result.model_mix.verifier}`);
|
|
213
|
+
lines.push(` Token source: ${result.measured_method === "count_tokens" ? `count_tokens API (${result.model_provenance.provider})` : result.measured_method === "structural_egress_gated" ? "structural (count_tokens withheld — egress-gated)" : "structural approximation"}`);
|
|
214
|
+
lines.push(` Priced with: ${result.priced_with}`);
|
|
215
|
+
if (result.stale)
|
|
216
|
+
lines.push(` WARNING: price table is ${result.stale_age_days} days old (stale).`);
|
|
217
|
+
lines.push(" Measured sources:");
|
|
218
|
+
for (const source of result.measured) {
|
|
219
|
+
lines.push(` - ${source.source}: ${source.found ? `${source.tokens} tokens` : "MISSING (counted as 0)"}`);
|
|
220
|
+
}
|
|
221
|
+
if (meta.agreed) {
|
|
222
|
+
lines.push(` AGREED by ${meta.by}; budget written to loops.yaml and budget_agreed event recorded.`);
|
|
223
|
+
}
|
|
224
|
+
else if (meta.belowThreshold) {
|
|
225
|
+
lines.push(` Below the $${meta.floor.toFixed(2)} handshake floor — would auto-approve. Re-run with --agree to record it.`);
|
|
226
|
+
}
|
|
227
|
+
else if (!result.priced) {
|
|
228
|
+
lines.push(` Unpriced — cannot compare to the $${meta.floor.toFixed(2)} handshake floor; treated as at/above it (fail-closed, B4). Restore pricing data, or agree a token budget with --unit tokens --agree.`);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
lines.push(` Above the $${meta.floor.toFixed(2)} handshake floor — agreement required. Re-run with --agree to accept.`);
|
|
232
|
+
}
|
|
233
|
+
return `${lines.join("\n")}\n`;
|
|
234
|
+
}
|
|
235
|
+
function appendBudgetAgreedEvent(packetDir, loop, result, who) {
|
|
236
|
+
const eventsPath = path.join(packetDir, "events.jsonl");
|
|
237
|
+
const event = {
|
|
238
|
+
specversion: "0.1",
|
|
239
|
+
id: `evt_loop_budget_agreed_${Date.now()}`,
|
|
240
|
+
source: "agent://foo/loop-estimate",
|
|
241
|
+
type: "com.adaptivespec.loop.budget_agreed",
|
|
242
|
+
subject: `loop:${loop.id}`,
|
|
243
|
+
time: who.at,
|
|
244
|
+
actor: { type: "human", id: who.by, mode: "human-in-loop" },
|
|
245
|
+
data: {
|
|
246
|
+
summary: `Agreed a budget for loop ${loop.id}: $${result.usd.expected.toFixed(4)} expected, $${result.usd.ceiling.toFixed(4)} ceiling.`,
|
|
247
|
+
unit: result.unit,
|
|
248
|
+
expected_usd: result.usd.expected,
|
|
249
|
+
ceiling_usd: result.usd.ceiling,
|
|
250
|
+
expected_tokens: result.tokens.expected,
|
|
251
|
+
ceiling_tokens: result.tokens.ceiling,
|
|
252
|
+
basis: result.basis,
|
|
253
|
+
confidence: result.confidence,
|
|
254
|
+
priced_with: result.priced_with,
|
|
255
|
+
},
|
|
256
|
+
extensions: {},
|
|
257
|
+
};
|
|
258
|
+
appendFileSync(eventsPath, `${JSON.stringify(event)}\n`);
|
|
259
|
+
}
|
|
260
|
+
// Record a deliberate GLM-autonomy override (T-005). The boundary is default-deny;
|
|
261
|
+
// when a human sets FOO_ALLOW_GLM_AUTONOMY=1 to bypass it, that bypass is auditable —
|
|
262
|
+
// who, when, and which model family it waived.
|
|
263
|
+
function appendAutonomyOverrideEvent(packetDir, loop, active, who) {
|
|
264
|
+
const eventsPath = path.join(packetDir, "events.jsonl");
|
|
265
|
+
const event = {
|
|
266
|
+
specversion: "0.1",
|
|
267
|
+
id: `evt_glm_autonomy_override_${Date.now()}`,
|
|
268
|
+
source: "agent://foo/loop-estimate",
|
|
269
|
+
type: "com.adaptivespec.loop.glm_autonomy_override",
|
|
270
|
+
subject: `loop:${loop.id}`,
|
|
271
|
+
time: who.at,
|
|
272
|
+
actor: { type: "human", id: who.by, mode: "human-in-loop" },
|
|
273
|
+
data: {
|
|
274
|
+
summary: `GLM autonomy default-deny overridden via FOO_ALLOW_GLM_AUTONOMY for loop ${loop.id}.`,
|
|
275
|
+
provider: active.provider,
|
|
276
|
+
model: active.worker,
|
|
277
|
+
family: active.family,
|
|
278
|
+
},
|
|
279
|
+
extensions: {},
|
|
280
|
+
};
|
|
281
|
+
appendFileSync(eventsPath, `${JSON.stringify(event)}\n`);
|
|
282
|
+
}
|
|
283
|
+
// foo estimate <packet>: per-release-tier token/dollar cost ranges for a spec
|
|
284
|
+
// packet (spec-budget-v0). Read-only; reuses the loop-budget price/count_tokens
|
|
285
|
+
// primitives. Tokens-only — engineer-effort and calendar-time are out of scope.
|
|
286
|
+
export async function runSpecEstimate(root, packetDir, packetLabel, args) {
|
|
287
|
+
const asJson = hasFlag(args, "--json");
|
|
288
|
+
const unitArg = flagValue(args, "--unit");
|
|
289
|
+
if (unitArg && unitArg !== "usd" && unitArg !== "tokens")
|
|
290
|
+
throw new UsageError("--unit must be usd or tokens", "E_BAD_FLAG");
|
|
291
|
+
const tierArg = flagValue(args, "--tier");
|
|
292
|
+
if (tierArg && !["core", "next", "deferred", "all"].includes(tierArg)) {
|
|
293
|
+
throw new UsageError("--tier must be core, next, deferred, or all", "E_BAD_FLAG");
|
|
294
|
+
}
|
|
295
|
+
const passesArg = flagValue(args, "--passes");
|
|
296
|
+
const pricing = loadPricing(root);
|
|
297
|
+
const apiKey = process.env.ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_AUTH_TOKEN;
|
|
298
|
+
// R-002 (OQ-4): egress is opt-in. Default to the structural tier; only send
|
|
299
|
+
// source content to the count_tokens API when explicitly asked, and even then
|
|
300
|
+
// the egress guard redacts/refuses secret-bearing or local-only sources.
|
|
301
|
+
const preferApi = hasFlag(args, "--count-tokens") && !hasFlag(args, "--structural");
|
|
302
|
+
const estimate = await estimateSpecBudget({
|
|
303
|
+
packetDir,
|
|
304
|
+
packetLabel,
|
|
305
|
+
pricing,
|
|
306
|
+
unit: unitArg ?? "usd",
|
|
307
|
+
buildPasses: passesArg ? Number(passesArg) : undefined,
|
|
308
|
+
apiKey,
|
|
309
|
+
preferApi,
|
|
310
|
+
repoRoot: root,
|
|
311
|
+
// Roster and price table load from the same root (model-roster-data-v0 R-001).
|
|
312
|
+
active: resolveActiveModel(process.env, loadModelRoster(root)),
|
|
313
|
+
});
|
|
314
|
+
const tiers = tierArg && tierArg !== "all" ? estimate.tiers.filter((tier) => tier.tier === tierArg) : estimate.tiers;
|
|
315
|
+
if (asJson) {
|
|
316
|
+
// --tier filters the tiers, so `total` MUST be recomputed over the filtered
|
|
317
|
+
// set. Shipping the full-packet total next to a filtered tier list made the
|
|
318
|
+
// human and JSON surfaces disagree — the human card recomputed, the JSON
|
|
319
|
+
// did not, so an agent reading --json --tier core got the core+next number.
|
|
320
|
+
const total = {
|
|
321
|
+
expected_tokens: tiers.reduce((s, t) => s + t.expected_tokens, 0),
|
|
322
|
+
ceiling_tokens: tiers.reduce((s, t) => s + t.ceiling_tokens, 0),
|
|
323
|
+
expected_usd: sumExpected(tiers),
|
|
324
|
+
ceiling_usd: sumCeiling(tiers),
|
|
325
|
+
expected_minutes: sumMinutes(tiers, "expected_minutes"),
|
|
326
|
+
ceiling_minutes: sumMinutes(tiers, "ceiling_minutes"),
|
|
327
|
+
};
|
|
328
|
+
return `${JSON.stringify({ ...estimate, tiers, total }, null, 2)}\n`;
|
|
329
|
+
}
|
|
330
|
+
const glyphs = glyphsEnabled({ isTTY: Boolean(process.stdout.isTTY), env: process.env, args });
|
|
331
|
+
let card = renderSpecEstimateCard(estimate, tiers, glyphs);
|
|
332
|
+
if (hasFlag(args, "--per-task")) {
|
|
333
|
+
const tasksPath = path.join(packetDir, "tasks.yaml");
|
|
334
|
+
if (existsSync(tasksPath)) {
|
|
335
|
+
const doc = parseYaml(readFileSync(tasksPath, "utf8"));
|
|
336
|
+
const tasks = (doc.tasks ?? []).map((t) => ({
|
|
337
|
+
id: String(t.id ?? "?"),
|
|
338
|
+
title: String(t.title ?? ""),
|
|
339
|
+
status: String(t.status ?? "?"),
|
|
340
|
+
refs: Array.isArray(t.refs) ? t.refs.map(String) : [],
|
|
341
|
+
}));
|
|
342
|
+
card = `${card}${renderPerTask(estimate, tasks)}\n`;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return card;
|
|
346
|
+
}
|
|
347
|
+
// Money, rounded to what the estimate can actually support. A `heuristic` /
|
|
348
|
+
// `low confidence` number whose tokens come from `text.length / 4` has no
|
|
349
|
+
// business rendering to a hundredth of a cent — `$0.0714` is the fake precision
|
|
350
|
+
// R-004 forbids. Cents below a dollar, two decimals above (D-014).
|
|
351
|
+
//
|
|
352
|
+
// The `<1¢` case is load-bearing and was a bug here: rounding a real-but-tiny
|
|
353
|
+
// cost to "0¢" reprints the exact lie D-014 exists to kill — `0` reads as FREE,
|
|
354
|
+
// not as "too small to show". Only a genuinely zero cost may render as zero.
|
|
355
|
+
function money(usd) {
|
|
356
|
+
if (usd === null)
|
|
357
|
+
return null;
|
|
358
|
+
if (usd === 0)
|
|
359
|
+
return "$0";
|
|
360
|
+
if (usd < 0.01)
|
|
361
|
+
return "<1¢";
|
|
362
|
+
if (usd < 1)
|
|
363
|
+
return `${Math.round(usd * 100)}¢`;
|
|
364
|
+
return `$${usd.toFixed(2)}`;
|
|
365
|
+
}
|
|
366
|
+
// A range, always (R-004: never a single number). Collapses to one figure only
|
|
367
|
+
// when the two ends round to the same thing — printing "7¢–7¢" is noise.
|
|
368
|
+
function moneyRange(lo, hi) {
|
|
369
|
+
const a = money(lo);
|
|
370
|
+
const b = money(hi);
|
|
371
|
+
if (a === null || b === null)
|
|
372
|
+
return null;
|
|
373
|
+
return a === b ? a : `${a}–${b}`;
|
|
374
|
+
}
|
|
375
|
+
// When there are no dollars, TOKENS are the estimate — so the cost column falls
|
|
376
|
+
// back to them. An unpriced run that shows an empty column has told the user
|
|
377
|
+
// nothing, which is worse than the $0.0000 it replaced.
|
|
378
|
+
function tokensRange(lo, hi) {
|
|
379
|
+
const fmt = (t) => (t >= 1000 ? `${(t / 1000).toFixed(1)}k` : String(t));
|
|
380
|
+
const a = fmt(lo);
|
|
381
|
+
const b = fmt(hi);
|
|
382
|
+
return a === b ? `${a} tok` : `${a}–${b} tok`;
|
|
383
|
+
}
|
|
384
|
+
function minutesRange(lo, hi) {
|
|
385
|
+
if (lo === null || hi === null)
|
|
386
|
+
return null;
|
|
387
|
+
const fmt = (m) => (m < 1 ? "<1" : String(Math.round(m)));
|
|
388
|
+
const a = fmt(lo);
|
|
389
|
+
const b = fmt(hi);
|
|
390
|
+
return a === b ? `${a} min` : `${a}–${b} min`;
|
|
391
|
+
}
|
|
392
|
+
function pctLabel(pct) {
|
|
393
|
+
if (pct === null)
|
|
394
|
+
return "";
|
|
395
|
+
const sign = pct > 0 ? "+" : "";
|
|
396
|
+
return ` (${sign}${pct}%)`;
|
|
397
|
+
}
|
|
398
|
+
function renderSpecEstimateCard(estimate, tiers, glyphs) {
|
|
399
|
+
const lines = [];
|
|
400
|
+
const cols = (a, b, c) => {
|
|
401
|
+
const parts = [a.padEnd(26)];
|
|
402
|
+
if (b !== null)
|
|
403
|
+
parts.push(b.padEnd(12));
|
|
404
|
+
if (c !== null)
|
|
405
|
+
parts.push(c);
|
|
406
|
+
return parts.join("").trimEnd();
|
|
407
|
+
};
|
|
408
|
+
lines.push(`${estimate.packet} — what this costs to build, on your current model`);
|
|
409
|
+
for (const finding of estimate.findings) {
|
|
410
|
+
lines.push(withGlyph("drift", ` finding: ${finding}`, glyphs));
|
|
411
|
+
}
|
|
412
|
+
if (!estimate.priced) {
|
|
413
|
+
lines.push(" No price for this model — the token and time ranges below ARE the estimate.");
|
|
414
|
+
}
|
|
415
|
+
lines.push("");
|
|
416
|
+
// --- What it costs, by tier -----------------------------------------------
|
|
417
|
+
lines.push(` Right now · ${estimate.worker_model}`);
|
|
418
|
+
for (const tier of tiers) {
|
|
419
|
+
const cost = moneyRange(tier.expected_usd, tier.ceiling_usd) ?? tokensRange(tier.expected_tokens, tier.ceiling_tokens);
|
|
420
|
+
lines.push(` ${cols(tier.tier, cost, minutesRange(tier.expected_minutes, tier.ceiling_minutes))}`);
|
|
421
|
+
}
|
|
422
|
+
if (tiers.length > 1) {
|
|
423
|
+
const label = `all (${tiers.map((t) => t.tier).join("+")})`;
|
|
424
|
+
const allCost = moneyRange(sumExpected(tiers), sumCeiling(tiers)) ??
|
|
425
|
+
tokensRange(tiers.reduce((s2, t) => s2 + t.expected_tokens, 0), tiers.reduce((s2, t) => s2 + t.ceiling_tokens, 0));
|
|
426
|
+
lines.push(` ${cols(label, allCost, minutesRange(sumMinutes(tiers, "expected_minutes"), sumMinutes(tiers, "ceiling_minutes")))}`);
|
|
427
|
+
}
|
|
428
|
+
// --- Where the cost actually is (the relative signal) ----------------------
|
|
429
|
+
const heavy = tiers
|
|
430
|
+
.flatMap((t) => t.requirements)
|
|
431
|
+
.sort((a, b) => b.share_of_packet - a.share_of_packet)
|
|
432
|
+
.slice(0, 3)
|
|
433
|
+
.filter((r) => r.share_of_packet > 0);
|
|
434
|
+
if (heavy.length > 0) {
|
|
435
|
+
lines.push("");
|
|
436
|
+
lines.push(` Where the cost is: ${heavy.map((r) => `${r.id} (${Math.round(r.share_of_packet * 100)}%)`).join(" · ")}`);
|
|
437
|
+
}
|
|
438
|
+
// --- Trade-offs ------------------------------------------------------------
|
|
439
|
+
if (estimate.tradeoffs.length > 0) {
|
|
440
|
+
lines.push("");
|
|
441
|
+
lines.push(" Trade-offs");
|
|
442
|
+
estimate.tradeoffs.forEach((t, i) => {
|
|
443
|
+
const cost = moneyRange(t.expected_usd, t.expected_usd) ?? tokensRange(t.expected_tokens, t.expected_tokens);
|
|
444
|
+
const price = `${cost}${pctLabel(t.delta_usd_pct)}`;
|
|
445
|
+
const time = t.expected_minutes === null ? "" : `${minutesRange(t.expected_minutes, t.expected_minutes)}${pctLabel(t.delta_minutes_pct)}`;
|
|
446
|
+
lines.push(` ${i + 1}. ${t.label}`);
|
|
447
|
+
if (price || time)
|
|
448
|
+
lines.push(` ${[price, time].filter(Boolean).join(" · ")}`);
|
|
449
|
+
lines.push(` costs you: ${t.costs_you}`);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
// --- The honesty block -----------------------------------------------------
|
|
453
|
+
lines.push("");
|
|
454
|
+
lines.push(` ${estimate.basis}, ${estimate.confidence} confidence — a relative read on which parts are heavy, not a price tag.`);
|
|
455
|
+
lines.push(` Prices agent tokens${estimate.worker_tokens_per_sec !== null ? " and agent runtime" : ""}. Does NOT price: ${estimate.excludes.join(", ")}.`);
|
|
456
|
+
lines.push(` Assumptions: build_passes=${estimate.build_passes}${estimate.worker_tokens_per_sec !== null ? `, ${estimate.worker_tokens_per_sec} tok/sec` : ""}, tier source=${estimate.tier_source}`);
|
|
457
|
+
lines.push(` Token source: ${estimate.measured_method === "count_tokens" ? "count_tokens API (redacted, egress-guarded)" : estimate.measured_method === "structural_egress_gated" ? "structural (count_tokens withheld — egress-gated)" : "structural approximation"}`);
|
|
458
|
+
lines.push(` Priced with: ${estimate.priced_with}`);
|
|
459
|
+
if (estimate.stale)
|
|
460
|
+
lines.push(withGlyph("drift", ` WARNING: price table is ${estimate.stale_age_days} days old (stale).`, glyphs));
|
|
461
|
+
if (tiers.length === 0) {
|
|
462
|
+
// Nothing parsed. $0 would be a lie — it is not free, it is UNKNOWN. (theirs, kept)
|
|
463
|
+
lines.push(" Cost UNKNOWN — no requirements were parsed from this spec (expected `### R-001 Title` headings).");
|
|
464
|
+
lines.push(" This is not an estimate of zero; it is a failure to estimate.");
|
|
465
|
+
}
|
|
466
|
+
if (estimate.excluded_requirements.length > 0) {
|
|
467
|
+
lines.push(` Not costed (must-not or unparsed): ${estimate.excluded_requirements.join(", ")}`);
|
|
468
|
+
}
|
|
469
|
+
lines.push("");
|
|
470
|
+
lines.push(" Read this as a FLOOR, not a forecast: it sizes requirement text × build passes.");
|
|
471
|
+
lines.push(" It does not model what an agent actually burns executing the work.");
|
|
472
|
+
return `${lines.join("\n")}\n`;
|
|
473
|
+
}
|
|
474
|
+
function sumExpected(tiers) {
|
|
475
|
+
if (tiers.some((t) => t.expected_usd === null))
|
|
476
|
+
return null;
|
|
477
|
+
return Math.round(tiers.reduce((s, t) => s + (t.expected_usd ?? 0), 0) * 10000) / 10000;
|
|
478
|
+
}
|
|
479
|
+
// --- The hook (spec-budget-v0 R-010) -----------------------------------------
|
|
480
|
+
//
|
|
481
|
+
// `foo estimate` had ZERO routes in: nothing in foo ever sent a user to it. That
|
|
482
|
+
// gap was load-bearing while the number was wrong — it kept a misleading figure
|
|
483
|
+
// away from people. Now that the number is honest, cost shows up where scope is
|
|
484
|
+
// actually CHOSEN, rather than behind a verb you have to remember.
|
|
485
|
+
//
|
|
486
|
+
// One front door: this is a LINE on a surface the user already looks at, not a
|
|
487
|
+
// second command. The deep view stays `foo estimate <packet>`.
|
|
488
|
+
//
|
|
489
|
+
// Two rules this helper must never break:
|
|
490
|
+
// 1. STRUCTURAL ONLY. A hook fires on surfaces the user did not ask to spend
|
|
491
|
+
// on — it must never egress spec text to count_tokens as a side effect.
|
|
492
|
+
// preferApi is hard-false here; --count-tokens remains opt-in on `foo
|
|
493
|
+
// estimate` itself.
|
|
494
|
+
// 2. NEVER BLOCK. A packet with no spec.md, an unparseable spec, or a missing
|
|
495
|
+
// price table returns null and the host surface renders exactly as before.
|
|
496
|
+
// A cost line is a signal, never a gate.
|
|
497
|
+
export async function costHeadline(root, packetDir, opts = {}) {
|
|
498
|
+
// Point at the packet the way the user typed it, not an absolute machine path.
|
|
499
|
+
const label = path.relative(root, packetDir) || packetDir;
|
|
500
|
+
try {
|
|
501
|
+
if (!existsSync(path.join(packetDir, "spec.md")))
|
|
502
|
+
return null;
|
|
503
|
+
const estimate = await estimateSpecBudget({
|
|
504
|
+
packetDir,
|
|
505
|
+
packetLabel: packetDir,
|
|
506
|
+
pricing: loadPricing(root),
|
|
507
|
+
preferApi: false, // rule 1: a hook never egresses
|
|
508
|
+
repoRoot: root,
|
|
509
|
+
active: resolveActiveModel(process.env, loadModelRoster(root)),
|
|
510
|
+
});
|
|
511
|
+
if (estimate.tiers.length === 0)
|
|
512
|
+
return null; // nothing parsed => say nothing, not "$0"
|
|
513
|
+
// "Remaining" is the honest number on a status card: a packet that is 80% done
|
|
514
|
+
// does not still cost its full build price. Scale by the share of task-linked
|
|
515
|
+
// requirements not yet done; fall back to the full packet when tasks.yaml has
|
|
516
|
+
// no usable refs (and say "to build", not "remaining", so the words stay true).
|
|
517
|
+
let scale = 1;
|
|
518
|
+
let scopeWord = "to build";
|
|
519
|
+
if (opts.remainingOnly) {
|
|
520
|
+
const tasksPath = path.join(packetDir, "tasks.yaml");
|
|
521
|
+
if (existsSync(tasksPath)) {
|
|
522
|
+
const doc = parseYaml(readFileSync(tasksPath, "utf8"));
|
|
523
|
+
const tasks = doc.tasks ?? [];
|
|
524
|
+
const costed = tasks.filter((t) => Array.isArray(t.refs) && t.refs.length > 0);
|
|
525
|
+
if (costed.length > 0) {
|
|
526
|
+
const open = costed.filter((t) => String(t.status ?? "") !== "done" && String(t.status ?? "") !== "dropped");
|
|
527
|
+
if (open.length === 0)
|
|
528
|
+
return null; // nothing left to build — no cost line at all
|
|
529
|
+
scale = open.length / costed.length;
|
|
530
|
+
scopeWord = "left to build";
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
const lo = estimate.total.expected_usd === null ? null : estimate.total.expected_usd * scale;
|
|
535
|
+
const hi = estimate.total.ceiling_usd === null ? null : estimate.total.ceiling_usd * scale;
|
|
536
|
+
const mLo = estimate.total.expected_minutes === null ? null : estimate.total.expected_minutes * scale;
|
|
537
|
+
const mHi = estimate.total.ceiling_minutes === null ? null : estimate.total.ceiling_minutes * scale;
|
|
538
|
+
const cost = moneyRange(lo, hi) ??
|
|
539
|
+
tokensRange(Math.round(estimate.total.expected_tokens * scale), Math.round(estimate.total.ceiling_tokens * scale));
|
|
540
|
+
const time = minutesRange(mLo, mHi);
|
|
541
|
+
// "floor" is not decoration: the estimate omits agent-execution overhead, which
|
|
542
|
+
// is usually the dominant cost. Saying so here keeps the hook from becoming the
|
|
543
|
+
// very price tag the command stopped pretending to be.
|
|
544
|
+
return `${cost}${time ? ` · ${time}` : ""} ${scopeWord} on ${estimate.worker_model} — a floor, not a forecast. Options: foo estimate ${label}`;
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
return null; // rule 2: never block the host surface
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Dollars at a precision the estimate can actually support. "$0.0057" implies a
|
|
551
|
+
// confidence this heuristic does not have; below a cent, say so in words.
|
|
552
|
+
function usd(value) {
|
|
553
|
+
if (value === 0)
|
|
554
|
+
return "$0";
|
|
555
|
+
if (value < 0.01)
|
|
556
|
+
return "<$0.01";
|
|
557
|
+
return `$${value.toFixed(2)}`;
|
|
558
|
+
}
|
|
559
|
+
// Per-task cost (R: "an estimate per task in a spec"). A task's cost is the cost of
|
|
560
|
+
// the requirements it implements, split across every task that shares them — so the
|
|
561
|
+
// per-task column sums back to the tier total instead of double-counting shared work.
|
|
562
|
+
export function renderPerTask(estimate, tasks) {
|
|
563
|
+
const reqCost = new Map();
|
|
564
|
+
for (const tier of estimate.tiers) {
|
|
565
|
+
// D-014: a null (unpriced) requirement is UNKNOWN, not free — it must not
|
|
566
|
+
// silently contribute 0 to a task's cost. Skip it; it surfaces as uncosted.
|
|
567
|
+
for (const req of tier.requirements) {
|
|
568
|
+
if (req.expected_usd === null || req.ceiling_usd === null)
|
|
569
|
+
continue;
|
|
570
|
+
reqCost.set(req.id, { expected: req.expected_usd, ceiling: req.ceiling_usd });
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const sharers = new Map();
|
|
574
|
+
for (const task of tasks) {
|
|
575
|
+
for (const ref of task.refs)
|
|
576
|
+
sharers.set(ref, (sharers.get(ref) ?? 0) + 1);
|
|
577
|
+
}
|
|
578
|
+
const lines = ["", " Per task (requirement cost split across the tasks that share it):"];
|
|
579
|
+
let totalE = 0;
|
|
580
|
+
let totalC = 0;
|
|
581
|
+
const uncosted = [];
|
|
582
|
+
for (const task of tasks) {
|
|
583
|
+
let e = 0;
|
|
584
|
+
let c = 0;
|
|
585
|
+
let costed = false;
|
|
586
|
+
for (const ref of task.refs) {
|
|
587
|
+
const cost = reqCost.get(ref);
|
|
588
|
+
if (!cost)
|
|
589
|
+
continue;
|
|
590
|
+
const share = sharers.get(ref) ?? 1;
|
|
591
|
+
e += cost.expected / share;
|
|
592
|
+
c += cost.ceiling / share;
|
|
593
|
+
costed = true;
|
|
594
|
+
}
|
|
595
|
+
if (!costed) {
|
|
596
|
+
uncosted.push(task.id);
|
|
597
|
+
lines.push(` ${task.id} ${task.status.padEnd(7)} cost unknown — no costed requirement · ${task.title}`);
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
totalE += e;
|
|
601
|
+
totalC += c;
|
|
602
|
+
lines.push(` ${task.id} ${task.status.padEnd(7)} ${usd(e)} .. ${usd(c)} · ${task.title}`);
|
|
603
|
+
}
|
|
604
|
+
lines.push(` ${"".padEnd(4)} ${"".padEnd(7)} ${usd(totalE)} .. ${usd(totalC)} · TOTAL across costed tasks`);
|
|
605
|
+
if (uncosted.length > 0) {
|
|
606
|
+
lines.push(` ${uncosted.length} task(s) carry no costed requirement (refs missing or unparsed): ${uncosted.join(", ")}`);
|
|
607
|
+
}
|
|
608
|
+
return lines.join("\n");
|
|
609
|
+
}
|
|
610
|
+
function sumCeiling(tiers) {
|
|
611
|
+
if (tiers.some((t) => t.ceiling_usd === null))
|
|
612
|
+
return null;
|
|
613
|
+
return Math.round(tiers.reduce((s, t) => s + (t.ceiling_usd ?? 0), 0) * 10000) / 10000;
|
|
614
|
+
}
|
|
615
|
+
function sumMinutes(tiers, key) {
|
|
616
|
+
if (tiers.some((t) => t[key] === null))
|
|
617
|
+
return null;
|
|
618
|
+
return Math.round(tiers.reduce((s, t) => s + (t[key] ?? 0), 0) * 10) / 10;
|
|
619
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { BehaviorStory, ExtractionCoverage } from "../codeCatalog.js";
|
|
2
|
+
import { type GithubFixture } from "../sourceIngress.js";
|
|
3
|
+
import { type DriftItem, type RecoveredIntent } from "../specAfter.js";
|
|
4
|
+
export type ReconcileFixture = GithubFixture & {
|
|
5
|
+
diff?: string;
|
|
6
|
+
files?: Array<{
|
|
7
|
+
path: string;
|
|
8
|
+
additions?: number;
|
|
9
|
+
deletions?: number;
|
|
10
|
+
}>;
|
|
11
|
+
linked_issue?: {
|
|
12
|
+
key?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
body?: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export type CheckSummary = "pass" | "fail" | "absent" | "mixed";
|
|
18
|
+
export interface ProofSummary {
|
|
19
|
+
merged: boolean | null;
|
|
20
|
+
checks: CheckSummary;
|
|
21
|
+
declared_packet: string | null;
|
|
22
|
+
claimed: string[];
|
|
23
|
+
proven: string[];
|
|
24
|
+
contradicted: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface ReconcileReport {
|
|
27
|
+
schema_version: "0.1";
|
|
28
|
+
source: string;
|
|
29
|
+
packet_free: true;
|
|
30
|
+
recovered_intent: RecoveredIntent;
|
|
31
|
+
behavior: BehaviorStory[];
|
|
32
|
+
coverage: ExtractionCoverage;
|
|
33
|
+
drift: DriftItem[];
|
|
34
|
+
proof: ProofSummary;
|
|
35
|
+
}
|
|
36
|
+
export declare function buildReconcileReport(source: string, fixture: ReconcileFixture): ReconcileReport;
|
|
37
|
+
export declare function renderReconcileReport(report: ReconcileReport, glyphsOn: boolean): string;
|
|
38
|
+
export declare function runReconcileCommand(root: string, args: string[], glyphsOn: boolean): {
|
|
39
|
+
output: string;
|
|
40
|
+
exitCode: number;
|
|
41
|
+
};
|