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,516 @@
|
|
|
1
|
+
// `foo discovery teams …` — the local worker for a tenant-owned Microsoft
|
|
2
|
+
// Teams connection (microsoft-teams-discovery-connector-v0, T-006 local
|
|
3
|
+
// worker mode; the gong worker port with Graph deltas, R-011).
|
|
4
|
+
//
|
|
5
|
+
// This is the runtime adapter the discovery core was designed to be driven by:
|
|
6
|
+
// it lists bounded meeting metadata inside an approved study or inbox scope,
|
|
7
|
+
// normalizes it through the Teams adapter, hands the closed session shape to
|
|
8
|
+
// `foo discovery match`, opens exactly one proof-authorized transcript into
|
|
9
|
+
// process memory, synthesizes the sanitized envelope in one model request,
|
|
10
|
+
// and hands the envelope to `foo discovery apply`. The transcript never
|
|
11
|
+
// reaches argv, stdin, a file, Git, a log, an error, or the host agent's
|
|
12
|
+
// context; every surface this module writes is content-free.
|
|
13
|
+
//
|
|
14
|
+
// Graph deltas from the gong worker (D-001): the connection is tenant-owned
|
|
15
|
+
// through Nango only (R-002 — no direct-credential fallback), listing filters
|
|
16
|
+
// what the approved scope selects with internal-only meetings excluded by
|
|
17
|
+
// default (the contracts pin `include_internal_only: false`), and the
|
|
18
|
+
// transcript is observed per meeting through its transcript metadata before
|
|
19
|
+
// the single content read (R-005/R-007). Support state is `contract_only`
|
|
20
|
+
// until the feasibility task (T-001) and the dogfood run (T-008) produce
|
|
21
|
+
// receipt-backed evidence.
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { hasFlag } from "./cliArgs.js";
|
|
25
|
+
import { loadDiscoveryRun } from "./discoveryArtifacts.js";
|
|
26
|
+
import { discoveryTargetDir, inboxScope, isInboxTarget, localRoot, localTargetName, runDiscoveryCommand, teamsScopeValidationError, } from "./discoveryCommand.js";
|
|
27
|
+
import { openAuthorizedTranscript } from "./discoveryProof.js";
|
|
28
|
+
import { teamsDiscoveryAdapter } from "./discoveryProviders.js";
|
|
29
|
+
import { planDiscoverySync } from "./discoverySync.js";
|
|
30
|
+
import { anthropicSynthesisTransport, defaultSynthesisModel, synthesisCredentialPresent, synthesizeDiscoveryEnvelope } from "./discoverySynthesis.js";
|
|
31
|
+
import { resolveTeamsConnection, TEAMS_POLICY, TeamsGraphClient, TeamsRuntimeError, teamsConnectionConfigured, teamsCredentialBroker } from "./teamsNango.js";
|
|
32
|
+
const MATCHABLE_STUDY_STATES = new Set(["awaiting_match", "ambiguous_match", "needs_source_approval", "needs_provider_access", "needs_provider_connector", "needs_research_proof"]);
|
|
33
|
+
const DEFAULT_LOOKBACK_DAYS = 14;
|
|
34
|
+
const SUPPORT_STATE = "contract_only";
|
|
35
|
+
const MODE = "local_worker";
|
|
36
|
+
function result(status, options = {}) {
|
|
37
|
+
return { schema_version: "0.1", status, writes: [], errors: [], handoff: null, next_actions: [], coverage: {}, ...options };
|
|
38
|
+
}
|
|
39
|
+
function exitCodeFor(status) {
|
|
40
|
+
return ["ambiguous_match", "needs_source_approval", "needs_provider_access", "needs_provider_connector", "needs_research_proof", "invalid", "budget_exhausted"].includes(status) ? 1 : 0;
|
|
41
|
+
}
|
|
42
|
+
function outcome(value, json) {
|
|
43
|
+
const exitCode = exitCodeFor(value.status);
|
|
44
|
+
if (json)
|
|
45
|
+
return { output: `${JSON.stringify(value, null, 2)}\n`, exitCode, result: value };
|
|
46
|
+
const lines = [`Discovery (teams): ${value.status}.`];
|
|
47
|
+
for (const error of value.errors)
|
|
48
|
+
lines.push(`- ${error.message}`);
|
|
49
|
+
if (value.handoff)
|
|
50
|
+
lines.push(`Handoff: ${value.handoff}`);
|
|
51
|
+
for (const action of value.next_actions)
|
|
52
|
+
lines.push(`Next: ${action}`);
|
|
53
|
+
return { output: `${lines.join("\n")}\n`, exitCode, result: value };
|
|
54
|
+
}
|
|
55
|
+
function invalid(message, json, code = "E_DISCOVERY_VALIDATION", targetId, coverage = {}) {
|
|
56
|
+
return outcome(result("invalid", { study_id: targetId, errors: [{ code, message }], coverage }), json);
|
|
57
|
+
}
|
|
58
|
+
// Typed provider failures map to the discovery command's closed status set.
|
|
59
|
+
// Messages are productfoo's own words: no provider body, id, or credential.
|
|
60
|
+
function fromRuntimeError(error, json, targetId, coverage) {
|
|
61
|
+
const base = { study_id: targetId, coverage: { ...coverage, provider_error: error.code, mode: MODE } };
|
|
62
|
+
switch (error.code) {
|
|
63
|
+
case "not_connected":
|
|
64
|
+
return outcome(result("needs_provider_connector", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_CONNECTOR", message: error.message }], handoff: "A Microsoft 365 administrator connects the tenant-owned Teams connection through Nango (NANGO_SECRET_KEY and FOO_TEAMS_NANGO_CONNECTION, plus NANGO_HOST / FOO_TEAMS_NANGO_INTEGRATION when they differ from the defaults)." }), json);
|
|
65
|
+
case "auth_expired":
|
|
66
|
+
case "access_denied":
|
|
67
|
+
return outcome(result("needs_provider_access", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: error.message }], handoff: "Ask the Microsoft 365 administrator to refresh the connection or grant the read permission, then rerun." }), json);
|
|
68
|
+
case "no_transcript":
|
|
69
|
+
// Observed absence from the provider's own metadata (R-005): the
|
|
70
|
+
// meeting has no transcript to open — a degraded-capability stop, not
|
|
71
|
+
// an error.
|
|
72
|
+
return outcome(result("needs_provider_access", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: error.message }], handoff: "Request transcript access from the Microsoft 365 administrator." }), json);
|
|
73
|
+
case "budget_exhausted":
|
|
74
|
+
// A throttling stop is final for the run (R-004/R-010): the run ends,
|
|
75
|
+
// nothing widens, nothing retries. The next run needs a narrower window.
|
|
76
|
+
return outcome(result("budget_exhausted", { ...base, errors: [{ code: "E_DISCOVERY_VALIDATION", message: error.message }], handoff: "Graph throttling stopped this run; narrow the window or selectors and rerun — productfoo never widens the window or auto-retries." }), json);
|
|
77
|
+
default:
|
|
78
|
+
return outcome(result("invalid", { ...base, errors: [{ code: "E_DISCOVERY_VALIDATION", message: error.message }], handoff: error.code === "transcript_too_large" || error.code === "provider_timeout" ? "Nothing was written and no retry was attempted; a new attempt needs a fresh match." : null }), json);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function isoDaysAgo(now, days) {
|
|
82
|
+
return new Date(Date.parse(now) - days * 86_400_000).toISOString();
|
|
83
|
+
}
|
|
84
|
+
function loadTarget(root, targetId, env, now, phase = "list") {
|
|
85
|
+
if (isInboxTarget(targetId)) {
|
|
86
|
+
const scope = inboxScope(root, targetId);
|
|
87
|
+
if (scope.provider.toLowerCase() !== "microsoft-teams")
|
|
88
|
+
throw new Error("this inbox scope is not a Microsoft Teams scope");
|
|
89
|
+
if (scope.state !== "approved")
|
|
90
|
+
throw new Error("this inbox scope is not approved");
|
|
91
|
+
// R-004: validation is pre-flight — an invalid scope never reaches the
|
|
92
|
+
// provider. The connection alias is narrowing only and never counts
|
|
93
|
+
// toward the selector minimum.
|
|
94
|
+
const scopeError = teamsScopeValidationError({
|
|
95
|
+
window: scope.time_window,
|
|
96
|
+
named_external_participants: scope.participant_allowlist,
|
|
97
|
+
external_companies_or_domains: scope.domain_allowlist,
|
|
98
|
+
organizers: [],
|
|
99
|
+
});
|
|
100
|
+
if (scopeError)
|
|
101
|
+
throw new Error(scopeError);
|
|
102
|
+
return { target_id: targetId, lane: "unassigned_call_evidence", dir: discoveryTargetDir(root, targetId), connection_alias: scope.connection_alias, window: { from: new Date(scope.time_window.start).toISOString(), to: new Date(scope.time_window.end).toISOString() }, selectors: { named_external_participants: scope.participant_allowlist, external_companies_or_domains: scope.domain_allowlist, organizers: scope.call_owners ?? [] } };
|
|
103
|
+
}
|
|
104
|
+
const dir = discoveryTargetDir(root, targetId);
|
|
105
|
+
const run = loadDiscoveryRun(dir);
|
|
106
|
+
if (run.source.provider.toLowerCase() !== "microsoft-teams")
|
|
107
|
+
throw new Error("this study is not a Microsoft Teams study");
|
|
108
|
+
// Listing needs a matchable study; processing needs the state `match` left
|
|
109
|
+
// behind (ready_to_synthesize) — the proof check decides the rest.
|
|
110
|
+
const allowed = phase === "process" ? run.state === "ready_to_synthesize" || MATCHABLE_STUDY_STATES.has(run.state) : MATCHABLE_STUDY_STATES.has(run.state);
|
|
111
|
+
if (!allowed)
|
|
112
|
+
throw new Error(`study ${targetId} is not ready for provider reads (state ${run.state})`);
|
|
113
|
+
let from;
|
|
114
|
+
let to;
|
|
115
|
+
if (run.match.expected_local_date) {
|
|
116
|
+
const day = Date.parse(`${run.match.expected_local_date}T00:00:00Z`);
|
|
117
|
+
if (!Number.isFinite(day))
|
|
118
|
+
throw new Error("the study's expected date is invalid");
|
|
119
|
+
from = new Date(day - 86_400_000).toISOString();
|
|
120
|
+
to = new Date(day + 2 * 86_400_000).toISOString();
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const lookback = Number(env.FOO_TEAMS_LOOKBACK_DAYS ?? DEFAULT_LOOKBACK_DAYS);
|
|
124
|
+
if (!Number.isFinite(lookback) || lookback <= 0 || lookback > 90)
|
|
125
|
+
throw new Error("FOO_TEAMS_LOOKBACK_DAYS must be between 1 and 90");
|
|
126
|
+
from = isoDaysAgo(now, lookback);
|
|
127
|
+
to = now;
|
|
128
|
+
}
|
|
129
|
+
// R-004 applies to study scopes too: a Teams study names its customer — the
|
|
130
|
+
// study target is the named external participant or company, and the match
|
|
131
|
+
// constraints narrow further. A study with neither is invalid.
|
|
132
|
+
const selectors = {
|
|
133
|
+
named_external_participants: [...(run.match?.required_participants ?? []), ...(run.target?.name?.trim() ? [run.target.name.trim()] : [])],
|
|
134
|
+
external_companies_or_domains: run.target?.organization?.trim() ? [run.target.organization.trim()] : [],
|
|
135
|
+
organizers: [],
|
|
136
|
+
};
|
|
137
|
+
const selectorError = teamsScopeValidationError({ window: { start: from, end: to }, ...selectors });
|
|
138
|
+
if (selectorError)
|
|
139
|
+
throw new Error(selectorError);
|
|
140
|
+
return { target_id: targetId, lane: "study_linked", dir, connection_alias: run.source.connection_alias, window: { from, to }, selectors };
|
|
141
|
+
}
|
|
142
|
+
function toRecord(meeting) {
|
|
143
|
+
if (!meeting.start)
|
|
144
|
+
return null;
|
|
145
|
+
return {
|
|
146
|
+
meeting_id: meeting.id,
|
|
147
|
+
subject: meeting.subject?.trim() || "Untitled meeting",
|
|
148
|
+
start_time: meeting.start,
|
|
149
|
+
...(meeting.end ? { end_time: meeting.end } : {}),
|
|
150
|
+
...(meeting.state ? { state: meeting.state } : {}),
|
|
151
|
+
...(meeting.organizer ? { organizer_display_name: meeting.organizer } : {}),
|
|
152
|
+
// No url: Teams has no approved stable-link form yet, so joinWebUrl is
|
|
153
|
+
// dropped at the worker boundary rather than handed to the adapter, which
|
|
154
|
+
// refuses every undeclared link (R-006). Canonical links stay absent until
|
|
155
|
+
// feasibility verifies one (T-001).
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// R-004: listing is bounded by the approved window and internal-only meetings
|
|
159
|
+
// are excluded by default. Classification is observed, not assumed: the
|
|
160
|
+
// attendance report — the Teams analog of the gong parties read (R-005) — is
|
|
161
|
+
// metadata for every listed meeting, and a meeting is internal-only only when
|
|
162
|
+
// its attendees are observed and every attendee's email domain is the tenant's
|
|
163
|
+
// own. A meeting whose attendee data is absent stays in (absent data is not
|
|
164
|
+
// evidence of internal-only) — the match step still narrows, and scope
|
|
165
|
+
// selectors keep validating the read before any request is made.
|
|
166
|
+
function internalOnly(attendees, tenantDomain) {
|
|
167
|
+
if (!tenantDomain || attendees.length === 0)
|
|
168
|
+
return false;
|
|
169
|
+
return attendees.every((attendee) => (attendee.email ?? "").trim().toLowerCase().endsWith(`@${tenantDomain}`));
|
|
170
|
+
}
|
|
171
|
+
async function connect(deps) {
|
|
172
|
+
const connection = await resolveTeamsConnection({ env: deps.env, fetchImpl: deps.fetchImpl });
|
|
173
|
+
const client = new TeamsGraphClient(connection.credential, { fetchImpl: deps.fetchImpl, sleep: deps.sleep, fetchTimeoutMs: deps.fetchTimeoutMs });
|
|
174
|
+
return { connection, client };
|
|
175
|
+
}
|
|
176
|
+
async function listSessions(target, connection, client) {
|
|
177
|
+
const listing = await client.listMeetings({ from: target.window.from, to: target.window.to });
|
|
178
|
+
// A ceiling stop is final for the run (R-004/R-010): no further provider
|
|
179
|
+
// reads happen — the next run needs a narrower window, never a wider one.
|
|
180
|
+
if (listing.truncated)
|
|
181
|
+
return { sessions: [], listed: listing.meetings.length, pages: listing.pages, truncated: true, internal_only_excluded: 0, incomplete: 0 };
|
|
182
|
+
const sessions = [];
|
|
183
|
+
let internalExcluded = 0;
|
|
184
|
+
let incomplete = 0;
|
|
185
|
+
for (const meeting of listing.meetings) {
|
|
186
|
+
const record = toRecord(meeting);
|
|
187
|
+
if (!record) {
|
|
188
|
+
incomplete += 1;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
// The attendance report is the Teams analog of the gong parties read
|
|
192
|
+
// (R-005): metadata for every listed meeting — display names and email
|
|
193
|
+
// domains, no content. A missing report is observed absence; the match
|
|
194
|
+
// step then fails closed.
|
|
195
|
+
try {
|
|
196
|
+
record.attendees = await client.meetingAttendance(meeting.id);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (error instanceof TeamsRuntimeError && error.code === "provider_failure")
|
|
200
|
+
record.attendees = [];
|
|
201
|
+
else
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
if (internalOnly(record.attendees, connection.tenant_domain)) {
|
|
205
|
+
internalExcluded += 1;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
// Transcript capability is observed on the connection (the brokered
|
|
209
|
+
// scope), not per meeting — the listing carries no per-meeting grant
|
|
210
|
+
// (R-005). `unknown` fails closed, so the adapter never reports a
|
|
211
|
+
// capability the connection did not prove.
|
|
212
|
+
record.transcript_available = connection.transcript_scope === "granted";
|
|
213
|
+
const session = teamsDiscoveryAdapter.normalizeSession(record, target.connection_alias);
|
|
214
|
+
if (session.completion_state !== "completed") {
|
|
215
|
+
incomplete += 1;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
sessions.push({ ...session, access: connection.transcript_scope === "missing" ? "denied" : "available" });
|
|
219
|
+
}
|
|
220
|
+
return { sessions, listed: listing.meetings.length, pages: listing.pages, truncated: listing.truncated, internal_only_excluded: internalExcluded, incomplete };
|
|
221
|
+
}
|
|
222
|
+
async function readyCommand(root, json, deps) {
|
|
223
|
+
const env = deps.env;
|
|
224
|
+
const coverage = {
|
|
225
|
+
provider: "microsoft-teams",
|
|
226
|
+
mode: MODE,
|
|
227
|
+
support_state: SUPPORT_STATE,
|
|
228
|
+
connection_owner: "workspace",
|
|
229
|
+
configured: teamsConnectionConfigured(env),
|
|
230
|
+
credential_broker: teamsCredentialBroker(env),
|
|
231
|
+
synthesis: { available: synthesisCredentialPresent(env), model: defaultSynthesisModel(env) },
|
|
232
|
+
policy: { ...TEAMS_POLICY },
|
|
233
|
+
};
|
|
234
|
+
try {
|
|
235
|
+
const { connection, client } = await connect(deps);
|
|
236
|
+
// The readiness probe is one metadata call inside a one-hour window: it
|
|
237
|
+
// proves the credential and the listing permission without reading
|
|
238
|
+
// meeting content.
|
|
239
|
+
const now = deps.now();
|
|
240
|
+
await client.listMeetings({ from: isoDaysAgo(now, 1 / 24), to: now });
|
|
241
|
+
coverage.connection = connection.connection_ref;
|
|
242
|
+
coverage.credential_broker = connection.broker;
|
|
243
|
+
coverage.credential_type = connection.credential_type;
|
|
244
|
+
coverage.capabilities = { list_sessions: true, open_transcript: connection.transcript_scope !== "missing", stable_link: false, timestamps: false, transcript_scope: connection.transcript_scope, stable_link_note: "no Teams link form is declared yet; canonical links stay absent until feasibility verifies one (T-001)" };
|
|
245
|
+
coverage.provider_calls = client.requests;
|
|
246
|
+
return outcome(result("ok", { coverage, next_actions: [
|
|
247
|
+
"Approve a bounded Microsoft Teams scope, then run foo discovery teams list <study-id|inbox:scope-id>.",
|
|
248
|
+
...(coverage.synthesis && !coverage.synthesis.available ? ["Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) before processing a transcript."] : []),
|
|
249
|
+
] }), json);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
if (error instanceof TeamsRuntimeError)
|
|
253
|
+
return fromRuntimeError(error, json, undefined, coverage);
|
|
254
|
+
return invalid("Microsoft Teams readiness failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", undefined, coverage);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function listCommand(root, targetId, json, deps) {
|
|
258
|
+
if (!targetId)
|
|
259
|
+
return invalid("Usage: foo discovery teams list <study-id|inbox:scope-id>", json);
|
|
260
|
+
const now = deps.now();
|
|
261
|
+
let target;
|
|
262
|
+
try {
|
|
263
|
+
target = loadTarget(root, targetId, deps.env, now);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
return invalid(error instanceof Error ? error.message : "invalid discovery target", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
267
|
+
}
|
|
268
|
+
const coverage = { mode: MODE, window: target.window, lane: target.lane, transcript_opened: false };
|
|
269
|
+
try {
|
|
270
|
+
const { connection, client } = await connect(deps);
|
|
271
|
+
coverage.connection = connection.connection_ref;
|
|
272
|
+
const listed = await listSessions(target, connection, client);
|
|
273
|
+
Object.assign(coverage, { meetings_listed: listed.listed, pages: listed.pages, truncated: listed.truncated, internal_only_excluded: listed.internal_only_excluded, incomplete_excluded: listed.incomplete, candidates_evaluated: listed.sessions.length, provider_calls: client.requests });
|
|
274
|
+
if (listed.truncated) {
|
|
275
|
+
return outcome(result("budget_exhausted", {
|
|
276
|
+
study_id: targetId,
|
|
277
|
+
coverage: { ...coverage, provider_error: "budget_exhausted" },
|
|
278
|
+
errors: [{ code: "E_DISCOVERY_VALIDATION", message: `The window holds more meetings than the ${TEAMS_POLICY.max_list_pages}-page ceiling; narrow the window or selectors instead of widening the read.` }],
|
|
279
|
+
handoff: "Narrow the approved window or selectors and list again; the run never widens the window and never retries.",
|
|
280
|
+
}), json);
|
|
281
|
+
}
|
|
282
|
+
// R-008: a missing transcript scope degrades the run before any match
|
|
283
|
+
// decision. The listing itself succeeded, so the run reports that plainly;
|
|
284
|
+
// the transcript gate surfaces at process as needs_provider_access.
|
|
285
|
+
if (connection.transcript_scope === "missing") {
|
|
286
|
+
return outcome(result("ok", {
|
|
287
|
+
study_id: targetId,
|
|
288
|
+
coverage: { ...coverage, transcript_scope: connection.transcript_scope, degraded: "transcript_scope_missing" },
|
|
289
|
+
next_actions: ["The tenant credential has no transcript permission; the Microsoft 365 administrator grants it, then list runs again to match, and process opens the one matched transcript."],
|
|
290
|
+
}), json);
|
|
291
|
+
}
|
|
292
|
+
if (listed.sessions.length === 0)
|
|
293
|
+
return outcome(result("awaiting_match", { study_id: targetId, coverage, next_actions: ["No completed meeting selected by the approved scope was listed inside the window."] }), json);
|
|
294
|
+
if (target.lane === "study_linked") {
|
|
295
|
+
const matched = await runDiscoveryCommand(root, ["match", targetId, "--sessions-stdin", "--json"], JSON.stringify(listed.sessions), now);
|
|
296
|
+
return outcome({ ...matched.result, coverage: { ...matched.result.coverage, ...coverage }, next_actions: [...matched.result.next_actions, ...(matched.result.status === "ok" ? [`Run foo discovery teams process ${targetId} to open the one matched transcript and apply the sanitized evidence.`] : [])] }, json);
|
|
297
|
+
}
|
|
298
|
+
// Inbox: one session at a time so each source gets its own one-time proof.
|
|
299
|
+
let lastMiss = null;
|
|
300
|
+
let evaluated = 0;
|
|
301
|
+
for (const session of listed.sessions) {
|
|
302
|
+
evaluated += 1;
|
|
303
|
+
const matched = await runDiscoveryCommand(root, ["match", targetId, "--sessions-stdin", "--json"], JSON.stringify([session]), now);
|
|
304
|
+
if (matched.result.status === "awaiting_match") {
|
|
305
|
+
lastMiss = matched.result;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const remaining = listed.sessions.length - evaluated;
|
|
309
|
+
return outcome({
|
|
310
|
+
...matched.result,
|
|
311
|
+
coverage: { ...matched.result.coverage, ...coverage, candidates_evaluated: evaluated, candidates_remaining: remaining },
|
|
312
|
+
next_actions: [
|
|
313
|
+
...matched.result.next_actions,
|
|
314
|
+
...(matched.result.status === "ok" ? [`Run foo discovery teams process ${targetId} to open this one transcript and apply the sanitized evidence.`] : []),
|
|
315
|
+
...(remaining > 0 ? [`${remaining} more listed meeting(s) were not evaluated; run list again after this one is processed.`] : []),
|
|
316
|
+
],
|
|
317
|
+
}, json);
|
|
318
|
+
}
|
|
319
|
+
return outcome(result("awaiting_match", { study_id: targetId, coverage: { ...coverage, candidates_evaluated: evaluated }, next_actions: lastMiss?.next_actions ?? ["No listed meeting matched the approved scope; nothing was opened."] }), json);
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
if (error instanceof TeamsRuntimeError)
|
|
323
|
+
return fromRuntimeError(error, json, targetId, coverage);
|
|
324
|
+
return invalid("Microsoft Teams listing failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", targetId, coverage);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function readMatch(root, targetId) {
|
|
328
|
+
const file = path.join(localRoot(root), localTargetName(targetId), "match.json");
|
|
329
|
+
if (!existsSync(file))
|
|
330
|
+
return null;
|
|
331
|
+
try {
|
|
332
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function studyHypotheses(dir) {
|
|
339
|
+
const file = path.join(dir, "study.md");
|
|
340
|
+
if (!existsSync(file))
|
|
341
|
+
return [];
|
|
342
|
+
const out = [];
|
|
343
|
+
for (const line of readFileSync(file, "utf8").split("\n")) {
|
|
344
|
+
const match = /^\s*(?:[-*]|\d+\.)?\s*\**(H-\d{3})\**\s*[:—–-]?\s*(.+?)\s*$/.exec(line);
|
|
345
|
+
if (match && !out.some((entry) => entry.id === match[1]))
|
|
346
|
+
out.push({ id: match[1], text: match[2] });
|
|
347
|
+
}
|
|
348
|
+
return out;
|
|
349
|
+
}
|
|
350
|
+
function containsIdentity(envelopeText, identities) {
|
|
351
|
+
const haystack = envelopeText.toLowerCase();
|
|
352
|
+
return identities.some((identity) => identity.length >= 4 && haystack.includes(identity.toLowerCase()));
|
|
353
|
+
}
|
|
354
|
+
async function processCommand(root, targetId, json, deps) {
|
|
355
|
+
if (!targetId)
|
|
356
|
+
return invalid("Usage: foo discovery teams process <study-id|inbox:scope-id>", json);
|
|
357
|
+
const now = deps.now();
|
|
358
|
+
let target;
|
|
359
|
+
try {
|
|
360
|
+
target = loadTarget(root, targetId, deps.env, now, "process");
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
return invalid(error instanceof Error ? error.message : "invalid discovery target", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
364
|
+
}
|
|
365
|
+
const model = defaultSynthesisModel(deps.env);
|
|
366
|
+
const coverage = { mode: MODE, lane: target.lane, policy_version: TEAMS_POLICY.policy_version, model };
|
|
367
|
+
let client;
|
|
368
|
+
try {
|
|
369
|
+
const connected = await connect(deps);
|
|
370
|
+
client = connected.client;
|
|
371
|
+
coverage.connection = connected.connection.connection_ref;
|
|
372
|
+
if (connected.connection.transcript_scope === "missing") {
|
|
373
|
+
// R-008: a missing transcript scope is degraded with needs_provider_access
|
|
374
|
+
// before any transcript metadata, content, or proof machinery runs.
|
|
375
|
+
return outcome(result("needs_provider_access", { study_id: targetId, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: "The tenant credential has no transcript permission; the meeting was not opened." }], handoff: "Request transcript access from the Microsoft 365 administrator.", coverage }), json);
|
|
376
|
+
}
|
|
377
|
+
const match = readMatch(root, targetId);
|
|
378
|
+
if (!match || !Array.isArray(match.candidates) || match.candidates.length === 0)
|
|
379
|
+
return invalid("No matched Teams meeting is recorded for this target; run foo discovery teams list first.", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
380
|
+
if (match.candidates.length > 1)
|
|
381
|
+
return outcome(result("ambiguous_match", { study_id: targetId, errors: [{ code: "E_DISCOVERY_AMBIGUOUS_MATCH", message: "Several meetings matched; a person must select one before any transcript is opened." }], handoff: `foo discovery review ${targetId} select-source --fingerprint <sha256> --by <person>` }), json);
|
|
382
|
+
const candidate = match.candidates[0];
|
|
383
|
+
const session = candidate.session;
|
|
384
|
+
const meetingId = session.runtime_handle;
|
|
385
|
+
if (!synthesisCredentialPresent(deps.env))
|
|
386
|
+
return outcome(result("needs_research_proof", { study_id: targetId, errors: [{ code: "E_DISCOVERY_RESEARCH_PROOF", message: "No model credential is available for server-side synthesis, so the transcript was not opened." }], handoff: "Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) for this runtime, then rerun process." }), json);
|
|
387
|
+
const hypotheses = target.lane === "study_linked" ? studyHypotheses(target.dir) : [];
|
|
388
|
+
if (target.lane === "study_linked" && hypotheses.length === 0)
|
|
389
|
+
return invalid("The study records no H-### hypotheses in study.md; study-linked evidence needs one to test.", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
390
|
+
// Observe transcript availability from the provider's own metadata —
|
|
391
|
+
// never assumed (R-005) — but only after the proof gate: no transcript
|
|
392
|
+
// request of any kind (metadata included) happens before the proof
|
|
393
|
+
// (R-007), so the metadata read lives inside the authorized wrapper.
|
|
394
|
+
let openError;
|
|
395
|
+
let transcriptText;
|
|
396
|
+
try {
|
|
397
|
+
transcriptText = await openAuthorizedTranscript({ study_dir: target.dir, local_root: localRoot(root), study_id: targetId, source_fingerprint: candidate.source_fingerprint, now }, async () => {
|
|
398
|
+
try {
|
|
399
|
+
const transcripts = await client.meetingTranscripts(meetingId);
|
|
400
|
+
if (transcripts.length === 0)
|
|
401
|
+
throw new TeamsRuntimeError("no_transcript", "No transcript is recorded for the selected meeting; the meeting was not opened.");
|
|
402
|
+
// Exactly one content read for one meeting id and one transcript
|
|
403
|
+
// id (R-007) — the only content request in this run.
|
|
404
|
+
return await client.transcriptContent(meetingId, transcripts[0].id);
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
// A transport failure whose state is unknown (a fetch that threw
|
|
408
|
+
// before a response existed) is not a provider answer: it degrades
|
|
409
|
+
// to the fresh-proof path below instead of a typed result.
|
|
410
|
+
if (error instanceof TeamsRuntimeError && error.code !== "network")
|
|
411
|
+
openError = error;
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
if (openError)
|
|
418
|
+
return fromRuntimeError(openError, json, targetId, { ...coverage, transcript_body_requests: client.requests.transcript_content });
|
|
419
|
+
return outcome(result("needs_research_proof", { study_id: targetId, errors: [{ code: "E_DISCOVERY_RESEARCH_PROOF", message: "The transcript was not opened: the discovery proof is missing, expired, already used, or no longer matches the approval." }], handoff: `Run foo discovery teams list ${targetId} again to re-match and mint a fresh proof.`, coverage }), json);
|
|
420
|
+
}
|
|
421
|
+
coverage.transcript_body_requests = client.requests.transcript_content;
|
|
422
|
+
const estimatedTokens = Math.ceil(transcriptText.length / 4);
|
|
423
|
+
if (estimatedTokens > TEAMS_POLICY.max_model_input_tokens) {
|
|
424
|
+
transcriptText = null;
|
|
425
|
+
return fromRuntimeError(new TeamsRuntimeError("transcript_too_large", "the transcript exceeds the model-input ceiling; nothing was synthesized"), json, targetId, coverage);
|
|
426
|
+
}
|
|
427
|
+
let synthesized;
|
|
428
|
+
try {
|
|
429
|
+
synthesized = await synthesizeDiscoveryEnvelope({
|
|
430
|
+
target_id: targetId,
|
|
431
|
+
lane: target.lane,
|
|
432
|
+
source: { provider: "microsoft-teams", ...(session.stable_url ? { stable_url: session.stable_url } : {}), source_fingerprint: candidate.source_fingerprint, completed_at: session.completed_at, locator_capability: session.capabilities.timestamps ? "timestamp" : "none" },
|
|
433
|
+
title: session.title,
|
|
434
|
+
hypotheses,
|
|
435
|
+
transcript_text: transcriptText,
|
|
436
|
+
transcript_complete: true,
|
|
437
|
+
caveats: session.stable_url ? [] : ["The provider returned no canonical meeting link for this meeting."],
|
|
438
|
+
}, { model, transport: deps.transport });
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
return invalid("Synthesis did not produce a valid closed envelope; nothing was written and the transcript was discarded.", json, "E_DISCOVERY_INVALID_ENVELOPE", targetId, coverage);
|
|
442
|
+
}
|
|
443
|
+
finally {
|
|
444
|
+
transcriptText = null;
|
|
445
|
+
}
|
|
446
|
+
coverage.synthesis_usage = synthesized.usage ?? null;
|
|
447
|
+
// Privacy screen beyond the envelope validator: participant identities
|
|
448
|
+
// observed from the provider's own metadata must not appear in derived text.
|
|
449
|
+
const identities = [...target.selectors.named_external_participants, ...target.selectors.organizers, ...session.participants].filter((entry) => Boolean(entry?.trim()));
|
|
450
|
+
const derivedText = [
|
|
451
|
+
...synthesized.envelope.findings.flatMap((finding) => [finding.headline, finding.paraphrase, finding.dissent]),
|
|
452
|
+
...synthesized.envelope.coverage.caveats,
|
|
453
|
+
].join("\n");
|
|
454
|
+
if (containsIdentity(derivedText, identities))
|
|
455
|
+
return invalid("Synthesis leaked a participant identity into derived text; nothing was written.", json, "E_DISCOVERY_INVALID_ENVELOPE", targetId, coverage);
|
|
456
|
+
const applied = await runDiscoveryCommand(root, ["apply", targetId, "--evidence-stdin", "--json"], JSON.stringify(synthesized.envelope), now);
|
|
457
|
+
return outcome({ ...applied.result, coverage: { ...applied.result.coverage, ...coverage } }, json);
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
if (error instanceof TeamsRuntimeError)
|
|
461
|
+
return fromRuntimeError(error, json, targetId, { ...coverage, transcript_body_requests: client?.requests.transcript_content ?? 0 });
|
|
462
|
+
return invalid("Microsoft Teams processing failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", targetId, coverage);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async function syncCommand(root, json, deps) {
|
|
466
|
+
const now = deps.now();
|
|
467
|
+
const plan = planDiscoverySync(root, now);
|
|
468
|
+
const lanes = plan.lanes.filter((lane) => lane.provider.toLowerCase() === "microsoft-teams");
|
|
469
|
+
if (lanes.length === 0)
|
|
470
|
+
return outcome(result("clean_no_op", { errors: plan.errors.map((message) => ({ code: "E_DISCOVERY_VALIDATION", message })), next_actions: ["No approved Microsoft Teams lane is active; approve a study source or an inbox scope first."], coverage: { mode: MODE, lanes: 0, connector_reads: 0 } }), json);
|
|
471
|
+
const results = [];
|
|
472
|
+
// Metadata and match only: a transcript is opened only by an explicit
|
|
473
|
+
// `process` on one target. The loop stays unarmed (R-010).
|
|
474
|
+
for (const lane of lanes) {
|
|
475
|
+
const targetId = lane.kind === "inbox" ? `inbox:${lane.scope_id}` : lane.study_id;
|
|
476
|
+
const listed = await listCommand(root, targetId, true, deps);
|
|
477
|
+
results.push({ lane_id: lane.lane_id, status: listed.result.status, handoff: listed.result.handoff });
|
|
478
|
+
}
|
|
479
|
+
const ready = results.filter((entry) => entry.status === "ok").map((entry) => entry.lane_id);
|
|
480
|
+
return outcome(result("ok", {
|
|
481
|
+
errors: plan.errors.map((message) => ({ code: "E_DISCOVERY_VALIDATION", message })),
|
|
482
|
+
next_actions: [
|
|
483
|
+
...ready.map((laneId) => `${laneId}: one meeting matched — run foo discovery teams process ${laneId.replace(/^study:/, "")} to open it.`),
|
|
484
|
+
...results.filter((entry) => entry.status !== "ok").map((entry) => `${entry.lane_id}: ${entry.status}${entry.handoff ? ` — ${entry.handoff}` : ""}`),
|
|
485
|
+
],
|
|
486
|
+
coverage: { mode: MODE, lanes: results.length, transcripts_opened: 0, results },
|
|
487
|
+
}), json);
|
|
488
|
+
}
|
|
489
|
+
export async function runTeamsDiscovery(root, args, deps = {}) {
|
|
490
|
+
const json = hasFlag(args, "--json");
|
|
491
|
+
const env = deps.env ?? process.env;
|
|
492
|
+
const resolved = {
|
|
493
|
+
fetchImpl: deps.fetchImpl ?? globalThis.fetch,
|
|
494
|
+
env,
|
|
495
|
+
now: deps.now ?? (() => new Date().toISOString()),
|
|
496
|
+
sleep: deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
|
|
497
|
+
transport: deps.transport ?? anthropicSynthesisTransport({ fetchImpl: deps.fetchImpl, env }),
|
|
498
|
+
fetchTimeoutMs: deps.fetchTimeoutMs ?? TEAMS_POLICY.fetch_timeout_ms,
|
|
499
|
+
};
|
|
500
|
+
const command = args[0];
|
|
501
|
+
const target = args[1] && !args[1].startsWith("--") ? args[1] : undefined;
|
|
502
|
+
try {
|
|
503
|
+
if (command === "ready")
|
|
504
|
+
return await readyCommand(root, json, resolved);
|
|
505
|
+
if (command === "list")
|
|
506
|
+
return await listCommand(root, target, json, resolved);
|
|
507
|
+
if (command === "process")
|
|
508
|
+
return await processCommand(root, target, json, resolved);
|
|
509
|
+
if (command === "sync")
|
|
510
|
+
return await syncCommand(root, json, resolved);
|
|
511
|
+
return invalid("Try: ready | list <study-id|inbox:scope-id> | process <study-id|inbox:scope-id> | sync", json);
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
return invalid("Microsoft Teams discovery failed closed without exposing private source content.", json);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type DiscoveryCommandOutcome } from "./discoveryCommand.js";
|
|
2
|
+
import { type SynthesisTransport } from "./discoverySynthesis.js";
|
|
3
|
+
import { type FetchLike } from "./zoomNango.js";
|
|
4
|
+
export type ZoomDiscoveryDeps = {
|
|
5
|
+
fetchImpl?: FetchLike;
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
7
|
+
now?: () => string;
|
|
8
|
+
sleep?: (ms: number) => Promise<void>;
|
|
9
|
+
transport?: SynthesisTransport;
|
|
10
|
+
fetchTimeoutMs?: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function runZoomDiscovery(root: string, args: string[], deps?: ZoomDiscoveryDeps): Promise<DiscoveryCommandOutcome>;
|