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,646 @@
|
|
|
1
|
+
// The routing policy loader and tier resolver (model-orchestrator-v0 T-001,
|
|
2
|
+
// R-001 / R-011 / R-013).
|
|
3
|
+
//
|
|
4
|
+
// The whole point of this module is what is NOT in it. Tier names, the
|
|
5
|
+
// task-type map, difficulty thresholds, escalation limits, and per-model tier
|
|
6
|
+
// membership are all committed data — `routing-policy.yaml` for the policy and
|
|
7
|
+
// `pricing.yaml` for which model sits in which tier. This file holds the
|
|
8
|
+
// decision logic and zero routing constants, so adding a provider, renaming a
|
|
9
|
+
// model, or moving a tier boundary is a data-only diff (C-001 / V-003). A test
|
|
10
|
+
// reads this source back and asserts no tier name, task-type name, or threshold
|
|
11
|
+
// number appears in it.
|
|
12
|
+
//
|
|
13
|
+
// It also has no defaults. A threshold missing from the data file is reported as
|
|
14
|
+
// missing, not quietly replaced with a number invented here — a default is a
|
|
15
|
+
// policy choice, and policy choices belong in the data file where a human can
|
|
16
|
+
// see and change them.
|
|
17
|
+
//
|
|
18
|
+
// Honest degradation (R-013) is the second rule. An unknown model, a model the
|
|
19
|
+
// roster does not place, an unmapped task type, a stale price table, or a stale
|
|
20
|
+
// policy all resolve to the policy's own `conservative_tier` with a worded
|
|
21
|
+
// finding. Never a silent guess, and never downward to the cheap end — the
|
|
22
|
+
// conservative direction is the strong one. When the policy file itself is
|
|
23
|
+
// missing there is no conservative tier to fall back to, so resolution returns
|
|
24
|
+
// no tier at all and says so; productfoo does not invent a tier name in code.
|
|
25
|
+
//
|
|
26
|
+
// R-011 is enforced structurally rather than by convention: usage share encodes
|
|
27
|
+
// price and free tiers, not capability, so the data file lists the keys that may
|
|
28
|
+
// never become routing inputs and the loader strips any it finds, with a
|
|
29
|
+
// finding. A rule nobody can add data against is stronger than a rule written
|
|
30
|
+
// down.
|
|
31
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
32
|
+
import { configPath } from "./configPath.js";
|
|
33
|
+
import { fileStatKey, readMemoized, writeMemoized } from "./processMemo.js";
|
|
34
|
+
import { pricingIsStale } from "./loopBudget.js";
|
|
35
|
+
import { parse as parseYaml } from "./vendorYaml.js";
|
|
36
|
+
const POLICY_SOURCE = "routing-policy.yaml";
|
|
37
|
+
/** Key of the data node that lists forbidden inputs — never itself a finding. */
|
|
38
|
+
const FORBIDDEN_INPUTS_KEY = "forbidden_inputs";
|
|
39
|
+
// --- parsing helpers ---------------------------------------------------------
|
|
40
|
+
function isObj(value) {
|
|
41
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
function strOf(value) {
|
|
44
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
45
|
+
}
|
|
46
|
+
function numOf(value) {
|
|
47
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
48
|
+
}
|
|
49
|
+
function boolOf(value) {
|
|
50
|
+
return value === true;
|
|
51
|
+
}
|
|
52
|
+
function strArray(value) {
|
|
53
|
+
if (!Array.isArray(value))
|
|
54
|
+
return [];
|
|
55
|
+
return value.filter((item) => typeof item === "string" && item.length > 0);
|
|
56
|
+
}
|
|
57
|
+
function numberMap(value) {
|
|
58
|
+
if (!isObj(value))
|
|
59
|
+
return {};
|
|
60
|
+
const out = {};
|
|
61
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
62
|
+
const parsed = numOf(raw);
|
|
63
|
+
if (parsed !== null)
|
|
64
|
+
out[key] = parsed;
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function stringMap(value) {
|
|
69
|
+
if (!isObj(value))
|
|
70
|
+
return {};
|
|
71
|
+
const out = {};
|
|
72
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
73
|
+
const parsed = strOf(raw);
|
|
74
|
+
if (parsed !== null)
|
|
75
|
+
out[key] = parsed;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
const CANONICAL_WORKFLOW_LANE_ORDER = [
|
|
80
|
+
"direct",
|
|
81
|
+
"shape-fast",
|
|
82
|
+
"review-targeted",
|
|
83
|
+
"deep-explicit",
|
|
84
|
+
];
|
|
85
|
+
const COMPILED_WORKFLOW_SAFETY_FLOOR = "shape-fast";
|
|
86
|
+
const WORKFLOW_LANES = new Set(CANONICAL_WORKFLOW_LANE_ORDER);
|
|
87
|
+
function workflowLaneOf(value) {
|
|
88
|
+
return typeof value === "string" && WORKFLOW_LANES.has(value) ? value : null;
|
|
89
|
+
}
|
|
90
|
+
function workflowLaneArray(value) {
|
|
91
|
+
return strArray(value).map(workflowLaneOf).filter((lane) => lane !== null);
|
|
92
|
+
}
|
|
93
|
+
function workflowLaneMap(value) {
|
|
94
|
+
if (!isObj(value))
|
|
95
|
+
return {};
|
|
96
|
+
const out = {};
|
|
97
|
+
for (const depth of ["fast", "standard", "deep"]) {
|
|
98
|
+
const lane = workflowLaneOf(value[depth]);
|
|
99
|
+
if (lane)
|
|
100
|
+
out[depth] = lane;
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function stringArrayMap(value) {
|
|
105
|
+
if (!isObj(value))
|
|
106
|
+
return {};
|
|
107
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, strArray(child)]));
|
|
108
|
+
}
|
|
109
|
+
function workflowLimitations(value) {
|
|
110
|
+
if (!isObj(value))
|
|
111
|
+
return {};
|
|
112
|
+
const out = {};
|
|
113
|
+
for (const [key, child] of Object.entries(value)) {
|
|
114
|
+
const lane = workflowLaneOf(key);
|
|
115
|
+
const limitation = strOf(child);
|
|
116
|
+
if (lane && limitation)
|
|
117
|
+
out[lane] = limitation;
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
function unavailableWorkflow() {
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
basis: null,
|
|
125
|
+
rollout: null,
|
|
126
|
+
defaultLane: null,
|
|
127
|
+
conservativeLane: null,
|
|
128
|
+
laneOrder: [],
|
|
129
|
+
executableLanes: [],
|
|
130
|
+
targetedMinDomains: null,
|
|
131
|
+
depthLanes: {},
|
|
132
|
+
ambiguityTerms: [],
|
|
133
|
+
domainSignals: {},
|
|
134
|
+
limitations: {},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Every path in `value` whose key is a forbidden routing input (R-011). Exported
|
|
139
|
+
* so any surface that accepts routing data — the policy file, the roster, a
|
|
140
|
+
* future refresh proposal — can ask the same question of it. The node that
|
|
141
|
+
* *declares* the forbidden list is skipped; naming a thing is not using it.
|
|
142
|
+
*/
|
|
143
|
+
export function findForbiddenInputs(value, forbidden) {
|
|
144
|
+
const banned = new Set(forbidden);
|
|
145
|
+
const hits = [];
|
|
146
|
+
const walk = (node, trail) => {
|
|
147
|
+
if (Array.isArray(node)) {
|
|
148
|
+
node.forEach((item) => walk(item, trail));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (!isObj(node))
|
|
152
|
+
return;
|
|
153
|
+
for (const [key, child] of Object.entries(node)) {
|
|
154
|
+
if (key === FORBIDDEN_INPUTS_KEY)
|
|
155
|
+
continue;
|
|
156
|
+
const here = trail ? `${trail}.${key}` : key;
|
|
157
|
+
if (banned.has(key))
|
|
158
|
+
hits.push(here);
|
|
159
|
+
walk(child, here);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
walk(value, "");
|
|
163
|
+
return hits;
|
|
164
|
+
}
|
|
165
|
+
function unavailablePolicy(finding) {
|
|
166
|
+
return {
|
|
167
|
+
source: POLICY_SOURCE,
|
|
168
|
+
available: false,
|
|
169
|
+
date: null,
|
|
170
|
+
staleAfterDays: null,
|
|
171
|
+
evidence: null,
|
|
172
|
+
ladder: [],
|
|
173
|
+
conservativeTier: null,
|
|
174
|
+
tiers: {},
|
|
175
|
+
taskTypes: {},
|
|
176
|
+
difficulty: { basis: null, signals: {}, escalateAt: null, downgradeAt: null, maxTierMoves: null, thinkingBudgetTokens: {} },
|
|
177
|
+
escalation: {
|
|
178
|
+
basis: null,
|
|
179
|
+
quality: { retriesSameModel: null, then: null, maxTierMoves: null },
|
|
180
|
+
resilience: { trigger: null, direction: null, maxTierMoves: null },
|
|
181
|
+
},
|
|
182
|
+
review: { requireDifferentFamily: false, onSameFamily: null },
|
|
183
|
+
panel: { default: null, requireComparableCandidates: false, comparabilityMaxRankGap: null, onIncomparable: null, adjudication: null },
|
|
184
|
+
workflow: unavailableWorkflow(),
|
|
185
|
+
forbiddenInputs: [],
|
|
186
|
+
findings: [finding],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Load the routing policy from the committed data file. Missing or unreadable
|
|
191
|
+
* data degrades to an unavailable policy with a worded finding; it never throws,
|
|
192
|
+
* because a routing *decision* must be able to say "I cannot route this" without
|
|
193
|
+
* taking the caller down with it.
|
|
194
|
+
*
|
|
195
|
+
* Memoized per process (cli_performance-v0 R-002): the policy file is committed
|
|
196
|
+
* data read from several surfaces per invocation; the (mtime, size) key keeps
|
|
197
|
+
* an out-of-band edit honest.
|
|
198
|
+
*/
|
|
199
|
+
const routingPolicyCache = new Map();
|
|
200
|
+
export function loadRoutingPolicy(root = process.cwd()) {
|
|
201
|
+
const file = configPath(root, POLICY_SOURCE);
|
|
202
|
+
const key = fileStatKey(file);
|
|
203
|
+
const cached = readMemoized(routingPolicyCache, file, key);
|
|
204
|
+
if (cached !== undefined)
|
|
205
|
+
return cached;
|
|
206
|
+
const value = computeRoutingPolicy(file);
|
|
207
|
+
writeMemoized(routingPolicyCache, file, key, value);
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
function computeRoutingPolicy(file) {
|
|
211
|
+
if (!existsSync(file)) {
|
|
212
|
+
return unavailablePolicy(`routing policy unavailable: ${POLICY_SOURCE} missing at repo root or in config/ — no tier can be resolved`);
|
|
213
|
+
}
|
|
214
|
+
let doc;
|
|
215
|
+
try {
|
|
216
|
+
doc = parseYaml(readFileSync(file, "utf8"));
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
220
|
+
return unavailablePolicy(`routing policy unavailable: ${POLICY_SOURCE} unreadable (${detail}) — no tier can be resolved`);
|
|
221
|
+
}
|
|
222
|
+
if (!isObj(doc)) {
|
|
223
|
+
return unavailablePolicy(`routing policy unavailable: ${POLICY_SOURCE} is not a mapping — no tier can be resolved`);
|
|
224
|
+
}
|
|
225
|
+
const findings = [];
|
|
226
|
+
const forbiddenInputs = strArray(doc[FORBIDDEN_INPUTS_KEY]);
|
|
227
|
+
for (const hit of findForbiddenInputs(doc, forbiddenInputs)) {
|
|
228
|
+
findings.push(`routing input refused: ${POLICY_SOURCE} carries ${hit}, which measures use rather than capability — it is ignored, not routed on`);
|
|
229
|
+
}
|
|
230
|
+
const tiersNode = isObj(doc.tiers) ? doc.tiers : {};
|
|
231
|
+
const ladder = strArray(tiersNode.ladder);
|
|
232
|
+
const definitionsNode = isObj(tiersNode.definitions) ? tiersNode.definitions : {};
|
|
233
|
+
const tiers = {};
|
|
234
|
+
for (const [name, raw] of Object.entries(definitionsNode)) {
|
|
235
|
+
const def = isObj(raw) ? raw : {};
|
|
236
|
+
tiers[name] = { name, purpose: strOf(def.purpose) ?? "", taskTypes: strArray(def.task_types) };
|
|
237
|
+
}
|
|
238
|
+
const conservativeTier = strOf(doc.conservative_tier);
|
|
239
|
+
if (!conservativeTier) {
|
|
240
|
+
findings.push(`routing policy degraded: ${POLICY_SOURCE} names no conservative tier — an unresolvable task has nowhere safe to land`);
|
|
241
|
+
}
|
|
242
|
+
else if (!tiers[conservativeTier]) {
|
|
243
|
+
findings.push(`routing policy degraded: ${POLICY_SOURCE} names a conservative tier (${conservativeTier}) it does not define`);
|
|
244
|
+
}
|
|
245
|
+
if (Object.keys(tiers).length === 0) {
|
|
246
|
+
findings.push(`routing policy degraded: ${POLICY_SOURCE} defines no tiers — every task resolves conservatively`);
|
|
247
|
+
}
|
|
248
|
+
const taskTypes = stringMap(doc.task_types);
|
|
249
|
+
for (const [taskType, tier] of Object.entries(taskTypes)) {
|
|
250
|
+
if (!tiers[tier])
|
|
251
|
+
findings.push(`routing policy degraded: task type ${taskType} maps to an undefined tier (${tier})`);
|
|
252
|
+
}
|
|
253
|
+
const difficultyNode = isObj(doc.difficulty) ? doc.difficulty : {};
|
|
254
|
+
const difficulty = {
|
|
255
|
+
basis: strOf(difficultyNode.basis),
|
|
256
|
+
signals: numberMap(difficultyNode.signals),
|
|
257
|
+
escalateAt: numOf(difficultyNode.escalate_at),
|
|
258
|
+
downgradeAt: numOf(difficultyNode.downgrade_at),
|
|
259
|
+
maxTierMoves: numOf(difficultyNode.max_tier_moves),
|
|
260
|
+
thinkingBudgetTokens: numberMap(difficultyNode.thinking_budget_tokens),
|
|
261
|
+
};
|
|
262
|
+
// No defaults here on purpose: a missing threshold is reported, never invented.
|
|
263
|
+
if (difficulty.escalateAt === null || difficulty.downgradeAt === null) {
|
|
264
|
+
findings.push(`routing policy degraded: ${POLICY_SOURCE} omits a difficulty threshold — difficulty cannot move a task off its type default`);
|
|
265
|
+
}
|
|
266
|
+
const escalationNode = isObj(doc.escalation) ? doc.escalation : {};
|
|
267
|
+
const qualityNode = isObj(escalationNode.quality) ? escalationNode.quality : {};
|
|
268
|
+
const resilienceNode = isObj(escalationNode.resilience) ? escalationNode.resilience : {};
|
|
269
|
+
const escalation = {
|
|
270
|
+
basis: strOf(escalationNode.basis),
|
|
271
|
+
quality: {
|
|
272
|
+
retriesSameModel: numOf(qualityNode.retries_same_model),
|
|
273
|
+
then: strOf(qualityNode.then),
|
|
274
|
+
maxTierMoves: numOf(qualityNode.max_tier_moves),
|
|
275
|
+
},
|
|
276
|
+
resilience: {
|
|
277
|
+
trigger: strOf(resilienceNode.trigger),
|
|
278
|
+
direction: strOf(resilienceNode.direction),
|
|
279
|
+
maxTierMoves: numOf(resilienceNode.max_tier_moves),
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
const reviewNode = isObj(doc.review) ? doc.review : {};
|
|
283
|
+
const panelNode = isObj(doc.panel) ? doc.panel : {};
|
|
284
|
+
const workflowNode = isObj(doc.workflow) ? doc.workflow : {};
|
|
285
|
+
const workflowRollout = strOf(workflowNode.rollout);
|
|
286
|
+
const laneOrder = workflowLaneArray(workflowNode.lane_order);
|
|
287
|
+
const executableLanes = workflowLaneArray(workflowNode.executable_lanes);
|
|
288
|
+
const defaultLane = workflowLaneOf(workflowNode.default_lane);
|
|
289
|
+
const conservativeLane = workflowLaneOf(workflowNode.conservative_lane);
|
|
290
|
+
const laneOrderCanonical = laneOrder.length === CANONICAL_WORKFLOW_LANE_ORDER.length
|
|
291
|
+
&& laneOrder.every((lane, index) => lane === CANONICAL_WORKFLOW_LANE_ORDER[index]);
|
|
292
|
+
const rolloutProven = workflowRollout === "observe_only";
|
|
293
|
+
const safetyFloorHeld = defaultLane === COMPILED_WORKFLOW_SAFETY_FLOOR
|
|
294
|
+
&& conservativeLane === COMPILED_WORKFLOW_SAFETY_FLOOR;
|
|
295
|
+
const executableLanesComplete = executableLanes.length > 0
|
|
296
|
+
&& defaultLane !== null
|
|
297
|
+
&& conservativeLane !== null
|
|
298
|
+
&& executableLanes.includes(defaultLane)
|
|
299
|
+
&& executableLanes.includes(conservativeLane);
|
|
300
|
+
const workflowValid = rolloutProven
|
|
301
|
+
&& defaultLane !== null
|
|
302
|
+
&& conservativeLane !== null
|
|
303
|
+
&& laneOrderCanonical
|
|
304
|
+
&& safetyFloorHeld
|
|
305
|
+
&& executableLanesComplete;
|
|
306
|
+
const workflow = {
|
|
307
|
+
valid: workflowValid,
|
|
308
|
+
basis: strOf(workflowNode.basis),
|
|
309
|
+
// Enabling direct-by-default requires dependency-owned paired release proof.
|
|
310
|
+
// No proof receipt/API exists yet, so a data-only flip must fail closed.
|
|
311
|
+
rollout: rolloutProven ? workflowRollout : null,
|
|
312
|
+
defaultLane,
|
|
313
|
+
conservativeLane,
|
|
314
|
+
laneOrder,
|
|
315
|
+
executableLanes,
|
|
316
|
+
targetedMinDomains: numOf(workflowNode.targeted_min_domains),
|
|
317
|
+
depthLanes: workflowLaneMap(workflowNode.depth_lanes),
|
|
318
|
+
ambiguityTerms: strArray(workflowNode.ambiguity_terms),
|
|
319
|
+
domainSignals: stringArrayMap(workflowNode.domain_signals),
|
|
320
|
+
limitations: workflowLimitations(workflowNode.limitations),
|
|
321
|
+
};
|
|
322
|
+
if (workflowRollout === "enabled") {
|
|
323
|
+
findings.push(`routing policy degraded: enabled rollout refused because dependency-owned paired release proof is unavailable`);
|
|
324
|
+
}
|
|
325
|
+
else if (workflowRollout !== "observe_only") {
|
|
326
|
+
findings.push(`routing policy degraded: ${POLICY_SOURCE} has an unsupported workflow rollout (${workflowRollout ?? "missing"})`);
|
|
327
|
+
}
|
|
328
|
+
if (!laneOrderCanonical) {
|
|
329
|
+
findings.push(`routing policy degraded: workflow lane_order must match the canonical safety order (${CANONICAL_WORKFLOW_LANE_ORDER.join(", ")})`);
|
|
330
|
+
}
|
|
331
|
+
if (!safetyFloorHeld) {
|
|
332
|
+
findings.push(`routing policy degraded: observe-only default_lane and conservative_lane must both remain ${COMPILED_WORKFLOW_SAFETY_FLOOR} until dependency-owned release proof exists`);
|
|
333
|
+
}
|
|
334
|
+
if (!executableLanesComplete) {
|
|
335
|
+
findings.push(`routing policy degraded: workflow executable_lanes must be non-empty and include its default and conservative lanes`);
|
|
336
|
+
}
|
|
337
|
+
if (!workflow.valid) {
|
|
338
|
+
findings.push(`routing policy degraded: ${POLICY_SOURCE} has an incomplete workflow policy — workflow routing will escalate conservatively`);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
source: POLICY_SOURCE,
|
|
342
|
+
available: true,
|
|
343
|
+
date: strOf(doc.date),
|
|
344
|
+
staleAfterDays: numOf(doc.stale_after_days),
|
|
345
|
+
evidence: strOf(doc.evidence),
|
|
346
|
+
ladder,
|
|
347
|
+
conservativeTier,
|
|
348
|
+
tiers,
|
|
349
|
+
taskTypes,
|
|
350
|
+
difficulty,
|
|
351
|
+
escalation,
|
|
352
|
+
review: {
|
|
353
|
+
requireDifferentFamily: boolOf(reviewNode.require_different_family),
|
|
354
|
+
onSameFamily: strOf(reviewNode.on_same_family),
|
|
355
|
+
},
|
|
356
|
+
panel: {
|
|
357
|
+
default: strOf(panelNode.default),
|
|
358
|
+
requireComparableCandidates: boolOf(panelNode.require_comparable_candidates),
|
|
359
|
+
comparabilityMaxRankGap: numOf(panelNode.comparability_max_rank_gap),
|
|
360
|
+
onIncomparable: strOf(panelNode.on_incomparable),
|
|
361
|
+
adjudication: strOf(panelNode.adjudication),
|
|
362
|
+
},
|
|
363
|
+
workflow,
|
|
364
|
+
forbiddenInputs,
|
|
365
|
+
findings,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
// --- staleness ---------------------------------------------------------------
|
|
369
|
+
/** Days between the policy date and `now`; Infinity when the date is unusable. */
|
|
370
|
+
export function routingPolicyAgeDays(policy, now = new Date()) {
|
|
371
|
+
if (!policy.date)
|
|
372
|
+
return Number.POSITIVE_INFINITY;
|
|
373
|
+
const dated = new Date(`${policy.date}T00:00:00Z`).getTime();
|
|
374
|
+
if (Number.isNaN(dated))
|
|
375
|
+
return Number.POSITIVE_INFINITY;
|
|
376
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
377
|
+
return Math.floor((now.getTime() - dated) / dayMs);
|
|
378
|
+
}
|
|
379
|
+
/** True when the policy is older than its own declared refresh window (RISK-002). */
|
|
380
|
+
export function routingPolicyIsStale(policy, now = new Date()) {
|
|
381
|
+
if (policy.staleAfterDays === null)
|
|
382
|
+
return true;
|
|
383
|
+
return routingPolicyAgeDays(policy, now) > policy.staleAfterDays;
|
|
384
|
+
}
|
|
385
|
+
// A stale price table or a stale policy means the data behind a routing decision
|
|
386
|
+
// can no longer be vouched for, so the decision degrades (R-013). Both are worded
|
|
387
|
+
// for a reader who did not watch the run.
|
|
388
|
+
function stalenessFindings(ctx) {
|
|
389
|
+
const now = ctx.now ?? new Date();
|
|
390
|
+
const out = [];
|
|
391
|
+
if (ctx.policy.available && routingPolicyIsStale(ctx.policy, now)) {
|
|
392
|
+
out.push(`routing policy stale: ${ctx.policy.source} was last refreshed ${ctx.policy.date ?? "on an unstated date"} — routing conservatively until it is refreshed`);
|
|
393
|
+
}
|
|
394
|
+
if (ctx.pricing.available === false) {
|
|
395
|
+
out.push(`price table unavailable: ${ctx.pricing.source} is missing — routing conservatively, no model costs assumed`);
|
|
396
|
+
}
|
|
397
|
+
else if (pricingIsStale(ctx.pricing, now)) {
|
|
398
|
+
out.push(`price table stale: ${ctx.pricing.source} is dated ${ctx.pricing.date} — routing conservatively until it is refreshed`);
|
|
399
|
+
}
|
|
400
|
+
return out;
|
|
401
|
+
}
|
|
402
|
+
// --- resolution --------------------------------------------------------------
|
|
403
|
+
function conservative(policy, reason, findings) {
|
|
404
|
+
if (!policy.conservativeTier) {
|
|
405
|
+
return {
|
|
406
|
+
tier: null,
|
|
407
|
+
reason: `${reason}, and the policy names no safe tier to fall back to`,
|
|
408
|
+
degraded: true,
|
|
409
|
+
findings: [...policy.findings, ...findings],
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
tier: policy.conservativeTier,
|
|
414
|
+
reason: `${reason}, so the stronger of the available tiers was used`,
|
|
415
|
+
degraded: true,
|
|
416
|
+
findings: [...policy.findings, ...findings],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* The tier a task type maps to, before difficulty adjusts it (R-002 step 1). An
|
|
421
|
+
* unmapped task type lands conservatively and says so — never on the cheap end
|
|
422
|
+
* by default.
|
|
423
|
+
*/
|
|
424
|
+
export function tierForTaskType(ctx, taskType) {
|
|
425
|
+
const { policy } = ctx;
|
|
426
|
+
const stale = stalenessFindings(ctx);
|
|
427
|
+
if (!policy.available) {
|
|
428
|
+
return { tier: null, reason: `no routing policy is available, so no tier was chosen`, degraded: true, findings: [...policy.findings, ...stale] };
|
|
429
|
+
}
|
|
430
|
+
if (stale.length > 0) {
|
|
431
|
+
return conservative(policy, `the data behind this routing decision could not be vouched for`, stale);
|
|
432
|
+
}
|
|
433
|
+
const mapped = policy.taskTypes[taskType];
|
|
434
|
+
if (!mapped) {
|
|
435
|
+
return conservative(policy, `${policy.source} maps no tier for work of this kind (${taskType})`, []);
|
|
436
|
+
}
|
|
437
|
+
if (!policy.tiers[mapped]) {
|
|
438
|
+
return conservative(policy, `work of this kind (${taskType}) maps to a tier ${policy.source} does not define (${mapped})`, []);
|
|
439
|
+
}
|
|
440
|
+
return {
|
|
441
|
+
tier: mapped,
|
|
442
|
+
reason: `${policy.source} routes work of this kind (${taskType}) to the ${mapped} tier`,
|
|
443
|
+
degraded: false,
|
|
444
|
+
findings: [...policy.findings],
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* The tier a specific model sits in. Tier membership is roster data: a model the
|
|
449
|
+
* roster does not place, or does not know at all, resolves conservatively with a
|
|
450
|
+
* worded finding rather than being guessed into a tier (R-013).
|
|
451
|
+
*/
|
|
452
|
+
export function tierForModel(ctx, modelId) {
|
|
453
|
+
const { policy, roster } = ctx;
|
|
454
|
+
const stale = stalenessFindings(ctx);
|
|
455
|
+
if (!policy.available) {
|
|
456
|
+
return { tier: null, reason: `no routing policy is available, so no tier was chosen`, degraded: true, findings: [...policy.findings, ...stale] };
|
|
457
|
+
}
|
|
458
|
+
const entry = roster.entries.find((candidate) => candidate.id === modelId);
|
|
459
|
+
if (!entry) {
|
|
460
|
+
return conservative(policy, `${modelId} is not in ${roster.source}, so nothing vouches for what it can do`, [
|
|
461
|
+
...stale,
|
|
462
|
+
`unplaced model: ${modelId} is not in ${roster.source} — routed conservatively, no capability assumed`,
|
|
463
|
+
]);
|
|
464
|
+
}
|
|
465
|
+
if (!entry.routingTier) {
|
|
466
|
+
return conservative(policy, `${roster.source} does not place ${modelId} in a tier`, [
|
|
467
|
+
...stale,
|
|
468
|
+
`unplaced model: ${roster.source} carries no tier for ${modelId} — routed conservatively until the roster places it with evidence`,
|
|
469
|
+
]);
|
|
470
|
+
}
|
|
471
|
+
if (!policy.tiers[entry.routingTier]) {
|
|
472
|
+
return conservative(policy, `${roster.source} places ${modelId} in a tier ${policy.source} does not define (${entry.routingTier})`, stale);
|
|
473
|
+
}
|
|
474
|
+
if (stale.length > 0) {
|
|
475
|
+
return conservative(policy, `the data behind this routing decision could not be vouched for`, stale);
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
tier: entry.routingTier,
|
|
479
|
+
reason: `${roster.source} places ${modelId} in the ${entry.routingTier} tier`,
|
|
480
|
+
degraded: false,
|
|
481
|
+
findings: [...policy.findings],
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
/** Roster models placed in `tier`, in roster order. Empty is a real answer, not an error. */
|
|
485
|
+
export function modelsForTier(ctx, tier) {
|
|
486
|
+
return ctx.roster.entries.filter((entry) => entry.routingTier === tier).map((entry) => entry.id);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Move `tier` `steps` rungs along the ladder (R-003 allows at most one, and the
|
|
490
|
+
* policy data says how many). A tier that is off the ladder — a different axis
|
|
491
|
+
* rather than a stronger rung — does not move, and says why. Movement stops at
|
|
492
|
+
* the ends rather than wrapping.
|
|
493
|
+
*/
|
|
494
|
+
export function adjustTier(policy, tier, steps) {
|
|
495
|
+
const at = policy.ladder.indexOf(tier);
|
|
496
|
+
if (at < 0) {
|
|
497
|
+
return { tier, reason: `the ${tier} tier is not on the ladder, so difficulty does not move it`, degraded: false, findings: [] };
|
|
498
|
+
}
|
|
499
|
+
const cap = policy.difficulty.maxTierMoves;
|
|
500
|
+
const allowed = cap === null ? steps : Math.max(-cap, Math.min(cap, steps));
|
|
501
|
+
const target = Math.max(0, Math.min(policy.ladder.length - 1, at + allowed));
|
|
502
|
+
const moved = policy.ladder[target];
|
|
503
|
+
if (moved === tier) {
|
|
504
|
+
return { tier, reason: `the ${tier} tier is already at the end of the ladder in that direction`, degraded: false, findings: [] };
|
|
505
|
+
}
|
|
506
|
+
return { tier: moved, reason: `difficulty moved this from the ${tier} tier to the ${moved} tier`, degraded: false, findings: [] };
|
|
507
|
+
}
|
|
508
|
+
// --- workflow-lane resolution ------------------------------------------------
|
|
509
|
+
function phraseMatches(text, phrase) {
|
|
510
|
+
const escaped = phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\s+/g, "\\s+");
|
|
511
|
+
return new RegExp(`\\b${escaped}\\b`, "i").test(text);
|
|
512
|
+
}
|
|
513
|
+
function laneRank(policy, lane) {
|
|
514
|
+
return policy.laneOrder.indexOf(lane);
|
|
515
|
+
}
|
|
516
|
+
function higherLane(policy, requested, floor) {
|
|
517
|
+
const requestedRank = laneRank(policy, requested);
|
|
518
|
+
const floorRank = laneRank(policy, floor);
|
|
519
|
+
if (requestedRank < 0 || floorRank < 0)
|
|
520
|
+
return floor;
|
|
521
|
+
return requestedRank >= floorRank ? requested : floor;
|
|
522
|
+
}
|
|
523
|
+
function workflowFallback(policy) {
|
|
524
|
+
// The policy is untrusted on this path. Do not let malformed data choose the
|
|
525
|
+
// fallback or claim executability; shape-fast is productfoo's compiled safety floor.
|
|
526
|
+
const lane = COMPILED_WORKFLOW_SAFETY_FLOOR;
|
|
527
|
+
return {
|
|
528
|
+
lane,
|
|
529
|
+
recommended_lane: lane,
|
|
530
|
+
reason: "Workflow policy unavailable or incomplete, so productfoo chose the conservative bounded-shaping lane.",
|
|
531
|
+
signals: [...policy.findings, "workflow-policy-unavailable"],
|
|
532
|
+
confidence: "low",
|
|
533
|
+
conservative_escalation: true,
|
|
534
|
+
explicit: false,
|
|
535
|
+
executable: true,
|
|
536
|
+
rollout: "observe_only",
|
|
537
|
+
routing_model_calls: 0,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Choose how much orchestration a request earns. This is a pure, deterministic
|
|
542
|
+
* decision over committed policy data plus the existing B3 classifier: it never
|
|
543
|
+
* calls a model merely to decide that no extra model call is needed.
|
|
544
|
+
*/
|
|
545
|
+
export function resolveWorkflowLane(policy, input) {
|
|
546
|
+
const workflow = policy.workflow;
|
|
547
|
+
if (!policy.available
|
|
548
|
+
|| !workflow.valid
|
|
549
|
+
|| !workflow.rollout
|
|
550
|
+
|| !workflow.defaultLane
|
|
551
|
+
|| !workflow.conservativeLane
|
|
552
|
+
|| workflow.laneOrder.length === 0) {
|
|
553
|
+
return workflowFallback(policy);
|
|
554
|
+
}
|
|
555
|
+
const riskDimensions = Object.entries(input.risk.dimensions)
|
|
556
|
+
.filter(([, matches]) => matches.length > 0)
|
|
557
|
+
.map(([dimension]) => dimension);
|
|
558
|
+
const domainMatches = Object.entries(workflow.domainSignals)
|
|
559
|
+
.filter(([, terms]) => terms.some((term) => phraseMatches(input.work, term)))
|
|
560
|
+
.map(([domain]) => domain);
|
|
561
|
+
const ambiguityMatches = workflow.ambiguityTerms.filter((term) => phraseMatches(input.work, term));
|
|
562
|
+
const domains = [...new Set([...riskDimensions, ...domainMatches])];
|
|
563
|
+
const targeted = workflow.targetedMinDomains !== null && domains.length >= workflow.targetedMinDomains;
|
|
564
|
+
const explicitLane = input.explicitLane ?? (input.explicitDepth ? workflow.depthLanes[input.explicitDepth] : undefined);
|
|
565
|
+
const explicit = explicitLane !== undefined;
|
|
566
|
+
let recommended;
|
|
567
|
+
let reason;
|
|
568
|
+
let confidence = input.risk.confidence;
|
|
569
|
+
let conservativeEscalation = false;
|
|
570
|
+
if (input.researchRequested) {
|
|
571
|
+
recommended = "shape-fast";
|
|
572
|
+
reason = "B5 explicit research must use productfoo's governed research front door; a simpler lane cannot bypass that boundary.";
|
|
573
|
+
conservativeEscalation = explicitLane !== undefined && explicitLane !== "shape-fast";
|
|
574
|
+
}
|
|
575
|
+
else if (explicitLane) {
|
|
576
|
+
recommended = explicitLane;
|
|
577
|
+
reason = `The user explicitly selected the ${explicitLane} lane.`;
|
|
578
|
+
}
|
|
579
|
+
else if (input.risk.risky && targeted) {
|
|
580
|
+
recommended = "review-targeted";
|
|
581
|
+
reason = `Material risk crosses ${domains.length} domains, so bounded cross-domain analysis is the smallest adequate lane.`;
|
|
582
|
+
}
|
|
583
|
+
else if (input.risk.risky) {
|
|
584
|
+
recommended = "shape-fast";
|
|
585
|
+
reason = "B3 risk requires an agreed shape before build, so direct execution is not eligible.";
|
|
586
|
+
}
|
|
587
|
+
else if (targeted) {
|
|
588
|
+
recommended = "review-targeted";
|
|
589
|
+
reason = `The request crosses ${domains.length} domains, so bounded cross-domain analysis is the smallest adequate lane.`;
|
|
590
|
+
}
|
|
591
|
+
else if (ambiguityMatches.length > 0) {
|
|
592
|
+
recommended = "shape-fast";
|
|
593
|
+
reason = "The requested outcome is ambiguous, so one bounded interviewer is the smallest adequate lane.";
|
|
594
|
+
confidence = "medium";
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
recommended = "direct";
|
|
598
|
+
reason = "The request is clear, routine, and reversible, so the current agent plus deterministic checks is sufficient.";
|
|
599
|
+
confidence = "high";
|
|
600
|
+
}
|
|
601
|
+
if (input.risk.risky) {
|
|
602
|
+
const safetyFloor = targeted ? "review-targeted" : "shape-fast";
|
|
603
|
+
const safe = higherLane(workflow, recommended, safetyFloor);
|
|
604
|
+
if (safe !== recommended) {
|
|
605
|
+
recommended = safe;
|
|
606
|
+
conservativeEscalation = true;
|
|
607
|
+
reason = `An explicit simpler lane cannot bypass B3 risk; productfoo escalated to ${safe}.`;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const rollout = workflow.rollout;
|
|
611
|
+
const observedOnly = rollout === "observe_only" && recommended === "direct" && !explicit;
|
|
612
|
+
const recommendedExecutable = workflow.executableLanes.includes(recommended);
|
|
613
|
+
const adapterFallback = !recommendedExecutable;
|
|
614
|
+
const lane = observedOnly
|
|
615
|
+
? workflow.defaultLane
|
|
616
|
+
: adapterFallback
|
|
617
|
+
? workflow.conservativeLane
|
|
618
|
+
: recommended;
|
|
619
|
+
if (observedOnly) {
|
|
620
|
+
reason += ` The direct recommendation is observe-only until paired same-harness evaluation clears the release gate; ${lane} remains executable.`;
|
|
621
|
+
}
|
|
622
|
+
else if (adapterFallback) {
|
|
623
|
+
conservativeEscalation = true;
|
|
624
|
+
reason += ` ${workflow.limitations[recommended] ?? "That adapter is unavailable"}; ${lane} is the smallest safe implemented fallback.`;
|
|
625
|
+
}
|
|
626
|
+
const executable = workflow.executableLanes.includes(lane);
|
|
627
|
+
const limitation = workflow.limitations[recommended];
|
|
628
|
+
return {
|
|
629
|
+
lane,
|
|
630
|
+
recommended_lane: recommended,
|
|
631
|
+
reason,
|
|
632
|
+
signals: [
|
|
633
|
+
...riskDimensions.map((dimension) => `risk:${dimension}`),
|
|
634
|
+
...domainMatches.map((domain) => `domain:${domain}`),
|
|
635
|
+
...ambiguityMatches.map((term) => `ambiguity:${term}`),
|
|
636
|
+
],
|
|
637
|
+
confidence,
|
|
638
|
+
conservative_escalation: conservativeEscalation,
|
|
639
|
+
explicit,
|
|
640
|
+
executable,
|
|
641
|
+
rollout,
|
|
642
|
+
routing_model_calls: 0,
|
|
643
|
+
...(input.researchRequested ? { boundary: "B5", required_capability: "research" } : {}),
|
|
644
|
+
...(limitation ? { limitation } : {}),
|
|
645
|
+
};
|
|
646
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Readable } from "node:stream";
|
|
2
|
+
export interface MaskedSecretIO {
|
|
3
|
+
input: NodeJS.ReadStream;
|
|
4
|
+
output: NodeJS.WritableStream;
|
|
5
|
+
prompt: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function readSecretFromStream(input: Readable, maxBytes?: number): Promise<string>;
|
|
8
|
+
export declare function readMaskedSecret(io: MaskedSecretIO): Promise<string>;
|