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,492 @@
|
|
|
1
|
+
// `foo discovery outlook …` — the local worker for a verified user's Outlook
|
|
2
|
+
// connection (outlook-discovery-connector-v0, per-user mailbox, D-002).
|
|
3
|
+
//
|
|
4
|
+
// This is Gmail's email-class worker composition (the class Gmail's PR #810
|
|
5
|
+
// landed) realized over Microsoft Graph: listing happens only inside an
|
|
6
|
+
// approved inbox scope naming explicit Outlook folders, a bounded window, and
|
|
7
|
+
// a customer selector (R-004); the readiness probe never lists (a folder-less
|
|
8
|
+
// messages.list would be a mailbox-wide read, so `ready` proves the
|
|
9
|
+
// credential with one profile call); listing fetches message metadata only
|
|
10
|
+
// (no body select); and process opens exactly one proof-authorized
|
|
11
|
+
// conversation's bodies into process memory, reduces them in one bounded
|
|
12
|
+
// model request to a PII-minimized closed envelope, screens derived text
|
|
13
|
+
// against the conversation's own participants, applies locally, and discards
|
|
14
|
+
// the text in a `finally` (R-007). Attachments are never opened (D-003) — the
|
|
15
|
+
// client has no attachment method at all. Message text never reaches argv,
|
|
16
|
+
// stdin, a file, Git, a log, an error, or the host agent's context; every
|
|
17
|
+
// surface this module writes is content-free.
|
|
18
|
+
//
|
|
19
|
+
// What it is not: live support. Outlook reports `contract_only` until the
|
|
20
|
+
// feasibility report (T-001) proves the Graph surface and a real per-user
|
|
21
|
+
// dogfood run (T-008) succeeds — and the connection belongs to the verified
|
|
22
|
+
// user whose mailbox it is, never to the workspace (R-010).
|
|
23
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { hasFlag } from "./cliArgs.js";
|
|
26
|
+
import { discoveryTargetDir, inboxScope, isInboxTarget, localRoot, localTargetName, outlookScopeValidationError, runDiscoveryCommand, } from "./discoveryCommand.js";
|
|
27
|
+
import { openAuthorizedTranscript } from "./discoveryProof.js";
|
|
28
|
+
import { outlookConversationAdmissibility, outlookDiscoveryAdapter, outlookInternalOnly } from "./discoveryProviders.js";
|
|
29
|
+
import { planDiscoverySync } from "./discoverySync.js";
|
|
30
|
+
import { anthropicSynthesisTransport, defaultSynthesisModel, synthesisCredentialPresent, synthesizeDiscoveryEnvelope } from "./discoverySynthesis.js";
|
|
31
|
+
import { OUTLOOK_POLICY, OutlookClient, OutlookRuntimeError, outlookConnectionConfigured, outlookCredentialBroker, resolveOutlookConnection } from "./outlookNango.js";
|
|
32
|
+
const SUPPORT_STATE = "contract_only";
|
|
33
|
+
const MODE = "local_worker";
|
|
34
|
+
function result(status, options = {}) {
|
|
35
|
+
return { schema_version: "0.1", status, writes: [], errors: [], handoff: null, next_actions: [], coverage: {}, ...options };
|
|
36
|
+
}
|
|
37
|
+
function exitCodeFor(status) {
|
|
38
|
+
return ["ambiguous_match", "needs_source_approval", "needs_provider_access", "needs_provider_connector", "needs_research_proof", "invalid", "budget_exhausted"].includes(status) ? 1 : 0;
|
|
39
|
+
}
|
|
40
|
+
function outcome(value, json) {
|
|
41
|
+
const exitCode = exitCodeFor(value.status);
|
|
42
|
+
if (json)
|
|
43
|
+
return { output: `${JSON.stringify(value, null, 2)}\n`, exitCode, result: value };
|
|
44
|
+
const lines = [`Discovery (outlook): ${value.status}.`];
|
|
45
|
+
for (const error of value.errors)
|
|
46
|
+
lines.push(`- ${error.message}`);
|
|
47
|
+
if (value.handoff)
|
|
48
|
+
lines.push(`Handoff: ${value.handoff}`);
|
|
49
|
+
for (const action of value.next_actions)
|
|
50
|
+
lines.push(`Next: ${action}`);
|
|
51
|
+
return { output: `${lines.join("\n")}\n`, exitCode, result: value };
|
|
52
|
+
}
|
|
53
|
+
function invalid(message, json, code = "E_DISCOVERY_VALIDATION", targetId, coverage = {}) {
|
|
54
|
+
return outcome(result("invalid", { study_id: targetId, errors: [{ code, message }], coverage }), json);
|
|
55
|
+
}
|
|
56
|
+
// Typed provider failures map to the discovery command's closed status set.
|
|
57
|
+
// Messages are productfoo's own words: no provider body, address, or credential.
|
|
58
|
+
function fromRuntimeError(error, json, targetId, coverage) {
|
|
59
|
+
const base = { study_id: targetId, coverage: { ...coverage, provider_error: error.code, mode: MODE } };
|
|
60
|
+
switch (error.code) {
|
|
61
|
+
case "not_connected":
|
|
62
|
+
return outcome(result("needs_provider_connector", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_CONNECTOR", message: error.message }], handoff: "Connect the Outlook mailbox through Nango and set NANGO_SECRET_KEY and FOO_OUTLOOK_NANGO_CONNECTION (plus NANGO_HOST / FOO_OUTLOOK_NANGO_INTEGRATION when they differ from the defaults); the connection belongs to the verified user whose mailbox it is." }), json);
|
|
63
|
+
case "auth_expired":
|
|
64
|
+
case "access_denied":
|
|
65
|
+
return outcome(result("needs_provider_access", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: error.message }], handoff: "Reconnect the mailbox in Nango or grant the missing least-privilege read scope, then rerun." }), json);
|
|
66
|
+
case "budget_exhausted":
|
|
67
|
+
// A rate-limit stop is final for the run (R-004): the run ends, nothing
|
|
68
|
+
// widens, nothing retries. The next run needs a narrower window.
|
|
69
|
+
return outcome(result("budget_exhausted", { ...base, errors: [{ code: "E_DISCOVERY_VALIDATION", message: error.message }], handoff: "The agreed page or quota ceiling stopped this run; narrow the window or selectors and rerun — productfoo never widens the window or auto-retries." }), json);
|
|
70
|
+
default:
|
|
71
|
+
return outcome(result("invalid", { ...base, errors: [{ code: "E_DISCOVERY_VALIDATION", message: error.message }], handoff: error.code === "conversation_too_large" || error.code === "provider_timeout" ? "Nothing was written and no retry was attempted; a new attempt needs a fresh match." : null }), json);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// D-001's pinned boundary (T-005): a conversation is settled when its last
|
|
75
|
+
// message predates the scope window's end by the quiet period, unless it
|
|
76
|
+
// carries an explicit done category.
|
|
77
|
+
function quietPeriodBoundary(windowEnd, quietPeriodHours) {
|
|
78
|
+
return new Date(Date.parse(windowEnd) - quietPeriodHours * 3_600_000).toISOString();
|
|
79
|
+
}
|
|
80
|
+
// R-004: the outlook scope is the strictest composition — validation is
|
|
81
|
+
// pre-flight, so an invalid scope never reaches the provider.
|
|
82
|
+
function outlookTarget(root, targetId) {
|
|
83
|
+
if (!isInboxTarget(targetId)) {
|
|
84
|
+
// V0 keeps every Outlook read inside an approved, folder-bearing scope;
|
|
85
|
+
// the study lane is deferred with the hosted service.
|
|
86
|
+
throw new Error("outlook evidence is inbox-scoped in V0: approve an inbox scope naming explicit folders, a bounded window, and a customer selector (R-004)");
|
|
87
|
+
}
|
|
88
|
+
const scope = inboxScope(root, targetId);
|
|
89
|
+
if (scope.provider.toLowerCase() !== "outlook")
|
|
90
|
+
throw new Error("this inbox scope is not an Outlook scope");
|
|
91
|
+
if (scope.state !== "approved")
|
|
92
|
+
throw new Error("this inbox scope is not approved");
|
|
93
|
+
// The workspace env selector is narrowing only and never counts toward the
|
|
94
|
+
// selector minimum.
|
|
95
|
+
const selectors = {
|
|
96
|
+
window: scope.time_window,
|
|
97
|
+
folders: scope.folders,
|
|
98
|
+
external_domains: scope.domain_allowlist,
|
|
99
|
+
external_names: scope.participant_allowlist,
|
|
100
|
+
include_internal_only: false,
|
|
101
|
+
};
|
|
102
|
+
const scopeError = outlookScopeValidationError(selectors);
|
|
103
|
+
if (scopeError)
|
|
104
|
+
throw new Error(scopeError);
|
|
105
|
+
return {
|
|
106
|
+
target_id: targetId,
|
|
107
|
+
lane: "unassigned_call_evidence",
|
|
108
|
+
dir: discoveryTargetDir(root, targetId),
|
|
109
|
+
connection_alias: scope.connection_alias,
|
|
110
|
+
window: { from: new Date(scope.time_window.start).toISOString(), to: new Date(scope.time_window.end).toISOString() },
|
|
111
|
+
folders: scope.folders ?? [],
|
|
112
|
+
external_domains: scope.domain_allowlist ?? [],
|
|
113
|
+
external_names: scope.participant_allowlist ?? [],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Folds one conversation's Graph messages into the email-class record. The
|
|
117
|
+
// last message by received time carries the conversation's recency; the
|
|
118
|
+
// category union is the class's done-override vocabulary; folders stay the
|
|
119
|
+
// approved scope's vocabulary (where the messages were observed), never
|
|
120
|
+
// stored downstream.
|
|
121
|
+
function toRecord(conversationId, messages, folders, ownerEmail, bodyAccess, quietPeriodEndsAt) {
|
|
122
|
+
if (messages.length === 0)
|
|
123
|
+
return null;
|
|
124
|
+
const ordered = [...messages].sort((left, right) => Date.parse(left.received_at ?? "0") - Date.parse(right.received_at ?? "0"));
|
|
125
|
+
const last = ordered[ordered.length - 1];
|
|
126
|
+
const lastAt = last.received_at ? new Date(Date.parse(last.received_at)).toISOString() : undefined;
|
|
127
|
+
if (!lastAt || !Number.isFinite(Date.parse(lastAt)))
|
|
128
|
+
return null;
|
|
129
|
+
const participants = [];
|
|
130
|
+
for (const message of ordered) {
|
|
131
|
+
if (message.from?.display_name || message.from?.smtp)
|
|
132
|
+
participants.push({ ...(message.from.display_name ? { display_name: message.from.display_name } : {}), ...(message.from.smtp ? { smtp: message.from.smtp } : {}) });
|
|
133
|
+
for (const recipient of message.to ?? [])
|
|
134
|
+
participants.push({ ...(recipient.display_name ? { display_name: recipient.display_name } : {}), ...(recipient.smtp ? { smtp: recipient.smtp } : {}) });
|
|
135
|
+
}
|
|
136
|
+
const categories = [...new Set(ordered.flatMap((message) => message.categories ?? []))];
|
|
137
|
+
const subject = [...ordered].reverse().map((message) => message.subject?.trim()).find(Boolean);
|
|
138
|
+
return {
|
|
139
|
+
conversation_id: conversationId,
|
|
140
|
+
subject: subject || last.subject?.trim() || "",
|
|
141
|
+
last_message_at: lastAt,
|
|
142
|
+
participants,
|
|
143
|
+
folders,
|
|
144
|
+
quiet_period_ends_at: quietPeriodEndsAt,
|
|
145
|
+
...(ownerEmail ? { mailbox_owner_email: ownerEmail } : {}),
|
|
146
|
+
...(bodyAccess === "granted" ? { body_access: "granted" } : { body_access: "missing" }),
|
|
147
|
+
categories,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
async function connect(deps) {
|
|
151
|
+
const connection = await resolveOutlookConnection({ env: deps.env, fetchImpl: deps.fetchImpl });
|
|
152
|
+
const client = new OutlookClient(connection.credential, { fetchImpl: deps.fetchImpl, baseUrl: deps.env.FOO_OUTLOOK_API_BASE, sleep: deps.sleep, fetchTimeoutMs: deps.fetchTimeoutMs });
|
|
153
|
+
return { connection, client };
|
|
154
|
+
}
|
|
155
|
+
// The one bounded model request (R-007): render the conversation's text
|
|
156
|
+
// bodies into role-labeled lines. The owner is "Mailbox owner"; every other
|
|
157
|
+
// address is "Customer". The rendered text exists only inside this
|
|
158
|
+
// function's caller.
|
|
159
|
+
function renderConversation(conversation, ownerEmail) {
|
|
160
|
+
const lines = [];
|
|
161
|
+
for (const message of conversation.messages) {
|
|
162
|
+
const at = message.received_at ? new Date(Date.parse(message.received_at)).toISOString().slice(0, 16).replace("T", " ") : "unknown date";
|
|
163
|
+
const role = ownerEmail && message.from?.smtp?.toLowerCase() === ownerEmail.toLowerCase() ? "Mailbox owner" : "Customer";
|
|
164
|
+
const body = message.body_text?.trim() || "(no text body in this message)";
|
|
165
|
+
lines.push(`[${at}] ${role}: ${body.replaceAll(/\s+/g, " ").trim()}`);
|
|
166
|
+
}
|
|
167
|
+
return lines.join("\n");
|
|
168
|
+
}
|
|
169
|
+
function containsIdentity(envelopeText, identities) {
|
|
170
|
+
const haystack = envelopeText.toLowerCase();
|
|
171
|
+
return identities.some((identity) => identity.length >= 4 && haystack.includes(identity.toLowerCase()));
|
|
172
|
+
}
|
|
173
|
+
async function readyCommand(_root, json, deps) {
|
|
174
|
+
const env = deps.env;
|
|
175
|
+
const coverage = {
|
|
176
|
+
provider: "outlook",
|
|
177
|
+
mode: MODE,
|
|
178
|
+
support_state: SUPPORT_STATE,
|
|
179
|
+
// R-010: the mailbox is personal even at work — ownership is reported
|
|
180
|
+
// honestly, never emulated as workspace access.
|
|
181
|
+
connection_owner: "user",
|
|
182
|
+
configured: outlookConnectionConfigured(env),
|
|
183
|
+
credential_broker: outlookCredentialBroker(env),
|
|
184
|
+
synthesis: { available: synthesisCredentialPresent(env), model: defaultSynthesisModel(env) },
|
|
185
|
+
policy: { ...OUTLOOK_POLICY },
|
|
186
|
+
};
|
|
187
|
+
try {
|
|
188
|
+
const { connection, client } = await connect(deps);
|
|
189
|
+
// The readiness probe is one profile call: it proves the credential and
|
|
190
|
+
// reports the mailbox identity this connection owns, without listing —
|
|
191
|
+
// a folder-less messages list would be a mailbox-wide read (R-004).
|
|
192
|
+
const profile = await client.profile();
|
|
193
|
+
coverage.connection = connection.connection_ref;
|
|
194
|
+
coverage.credential_broker = connection.broker;
|
|
195
|
+
coverage.credential_type = connection.credential_type;
|
|
196
|
+
coverage.body_scope = connection.body_scope;
|
|
197
|
+
coverage.capabilities = { list_conversations: "pending_approved_scope", open_bodies: connection.body_scope === "granted", stable_link: false, timestamps: false, link_form: "none declared in V0 (R-006)" };
|
|
198
|
+
coverage.provider_calls = client.requests;
|
|
199
|
+
return outcome(result("ok", { coverage, next_actions: [
|
|
200
|
+
"Approve a bounded Outlook scope (explicit folders, a time window, and a customer selector), then run foo discovery outlook list <inbox:scope-id>.",
|
|
201
|
+
...(connection.body_scope !== "granted" ? ["The connection lacks the Mail.Read scope: listing works, but process reports needs_provider_access until the least-privilege body scope is granted."] : []),
|
|
202
|
+
...(coverage.synthesis && !coverage.synthesis.available ? ["Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) before processing a conversation."] : []),
|
|
203
|
+
] }), json);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
if (error instanceof OutlookRuntimeError)
|
|
207
|
+
return fromRuntimeError(error, json, undefined, coverage);
|
|
208
|
+
return invalid("Outlook readiness failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", undefined, coverage);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function listCommand(root, targetId, json, deps) {
|
|
212
|
+
if (!targetId)
|
|
213
|
+
return invalid("Usage: foo discovery outlook list <inbox:scope-id>", json);
|
|
214
|
+
const now = deps.now();
|
|
215
|
+
let target;
|
|
216
|
+
try {
|
|
217
|
+
target = outlookTarget(root, targetId);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
return invalid(error instanceof Error ? error.message : "invalid discovery target", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
221
|
+
}
|
|
222
|
+
const coverage = { mode: MODE, window: target.window, lane: target.lane, folders: target.folders.length, bodies_opened: false };
|
|
223
|
+
try {
|
|
224
|
+
const { connection, client } = await connect(deps);
|
|
225
|
+
coverage.connection = connection.connection_ref;
|
|
226
|
+
coverage.body_scope = connection.body_scope;
|
|
227
|
+
const profile = await client.profile();
|
|
228
|
+
const quietPeriodEndsAt = quietPeriodBoundary(target.window.to, OUTLOOK_POLICY.quiet_period_hours);
|
|
229
|
+
// Metadata listing only, one approved folder at a time (R-004): every
|
|
230
|
+
// page re-issues the pinned filter and no body is ever selected.
|
|
231
|
+
const byConversation = new Map();
|
|
232
|
+
let pages = 0;
|
|
233
|
+
let truncated = false;
|
|
234
|
+
for (const folder of target.folders) {
|
|
235
|
+
const listing = await client.listFolderMessages({ folder, from: target.window.from, to: target.window.to });
|
|
236
|
+
pages += listing.pages;
|
|
237
|
+
truncated = truncated || listing.truncated;
|
|
238
|
+
for (const message of listing.messages) {
|
|
239
|
+
if (!message.conversation_id)
|
|
240
|
+
continue;
|
|
241
|
+
const entry = byConversation.get(message.conversation_id) ?? { messages: [], folders: new Set() };
|
|
242
|
+
entry.messages.push(message);
|
|
243
|
+
entry.folders.add(folder);
|
|
244
|
+
byConversation.set(message.conversation_id, entry);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// A ceiling stop is final for the run (R-004): no further provider reads
|
|
248
|
+
// happen — the run waits for a window that fits the agreed pages, never
|
|
249
|
+
// for a wider one.
|
|
250
|
+
if (truncated) {
|
|
251
|
+
return outcome(result("budget_exhausted", {
|
|
252
|
+
study_id: targetId,
|
|
253
|
+
coverage: { ...coverage, provider_error: "budget_exhausted", conversations_listed: byConversation.size, pages },
|
|
254
|
+
errors: [{ code: "E_DISCOVERY_VALIDATION", message: `The window holds more messages than the ${OUTLOOK_POLICY.max_list_pages}-page-per-folder ceiling; narrow the window or selectors instead of widening the read.` }],
|
|
255
|
+
handoff: "Narrow the approved window or selectors and list again; the run never widens the window and never retries.",
|
|
256
|
+
}), json);
|
|
257
|
+
}
|
|
258
|
+
const sessions = [];
|
|
259
|
+
let internalExcluded = 0;
|
|
260
|
+
let incomplete = 0;
|
|
261
|
+
for (const [conversationId, entry] of byConversation) {
|
|
262
|
+
const record = toRecord(conversationId, entry.messages, [...entry.folders], profile.email, connection.body_scope, quietPeriodEndsAt);
|
|
263
|
+
if (!record) {
|
|
264
|
+
incomplete += 1;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (outlookInternalOnly(record)) {
|
|
268
|
+
internalExcluded += 1;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
// D-001: only settled conversations are evidence; the quiet-period
|
|
272
|
+
// boundary and the done-category override are applied by the adapter.
|
|
273
|
+
if (outlookConversationAdmissibility(record) !== "completed") {
|
|
274
|
+
incomplete += 1;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
sessions.push({ ...outlookDiscoveryAdapter.normalizeSession(record, target.connection_alias), access: "available" });
|
|
278
|
+
}
|
|
279
|
+
Object.assign(coverage, { conversations_listed: byConversation.size, pages, internal_only_excluded: internalExcluded, incomplete_excluded: incomplete, candidates_evaluated: sessions.length, provider_calls: client.requests });
|
|
280
|
+
if (sessions.length === 0)
|
|
281
|
+
return outcome(result("awaiting_match", { study_id: targetId, coverage, next_actions: ["No settled conversation with an external participant was listed inside the approved scope."] }), json);
|
|
282
|
+
// One session at a time so each source gets its own one-time proof.
|
|
283
|
+
let lastMiss = null;
|
|
284
|
+
let evaluated = 0;
|
|
285
|
+
for (const session of sessions) {
|
|
286
|
+
evaluated += 1;
|
|
287
|
+
const matched = await runDiscoveryCommand(root, ["match", targetId, "--sessions-stdin", "--json"], JSON.stringify([session]), now);
|
|
288
|
+
if (matched.result.status === "awaiting_match") {
|
|
289
|
+
lastMiss = matched.result;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
// R-008: the shared gate refuses a session that cannot open its
|
|
293
|
+
// transcript generically (needs_provider_connector); for Outlook the
|
|
294
|
+
// actual cause is observable — a missing body scope is an access
|
|
295
|
+
// problem, not a connector one. The match state the shared command
|
|
296
|
+
// already wrote stays intact: degrade to needs_provider_access with
|
|
297
|
+
// the honest handoff, preserving the metadata and the match.
|
|
298
|
+
if (matched.result.status === "needs_provider_connector" && connection.body_scope !== "granted") {
|
|
299
|
+
return outcome(result("needs_provider_access", {
|
|
300
|
+
study_id: targetId,
|
|
301
|
+
errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: "The Outlook connection lacks the Mail.Read scope; the conversation's metadata matched, but its bodies cannot be opened." }],
|
|
302
|
+
handoff: "Grant the least-privilege read scope (Mail.Read) on the connection in Nango, then rerun foo discovery outlook process.",
|
|
303
|
+
coverage: { ...matched.result.coverage, ...coverage, candidates_evaluated: evaluated, candidates_remaining: sessions.length - evaluated },
|
|
304
|
+
}), json);
|
|
305
|
+
}
|
|
306
|
+
const remaining = sessions.length - evaluated;
|
|
307
|
+
return outcome({
|
|
308
|
+
...matched.result,
|
|
309
|
+
coverage: { ...matched.result.coverage, ...coverage, candidates_evaluated: evaluated, candidates_remaining: remaining },
|
|
310
|
+
next_actions: [
|
|
311
|
+
...matched.result.next_actions,
|
|
312
|
+
...(matched.result.status === "ok" ? [`Run foo discovery outlook process ${targetId} to open this one conversation and apply the sanitized evidence.`] : []),
|
|
313
|
+
...(remaining > 0 ? [`${remaining} more listed conversation(s) were not evaluated; run list again after this one is processed.`] : []),
|
|
314
|
+
],
|
|
315
|
+
}, json);
|
|
316
|
+
}
|
|
317
|
+
return outcome(result("awaiting_match", { study_id: targetId, coverage: { ...coverage, candidates_evaluated: evaluated }, next_actions: lastMiss?.next_actions ?? ["No listed conversation matched the approved scope; nothing was opened."] }), json);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
if (error instanceof OutlookRuntimeError)
|
|
321
|
+
return fromRuntimeError(error, json, targetId, coverage);
|
|
322
|
+
return invalid("Outlook listing failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", targetId, coverage);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function readMatch(root, targetId) {
|
|
326
|
+
const file = path.join(localRoot(root), localTargetName(targetId), "match.json");
|
|
327
|
+
if (!existsSync(file))
|
|
328
|
+
return null;
|
|
329
|
+
try {
|
|
330
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async function processCommand(root, targetId, json, deps) {
|
|
337
|
+
if (!targetId)
|
|
338
|
+
return invalid("Usage: foo discovery outlook process <inbox:scope-id>", json);
|
|
339
|
+
const now = deps.now();
|
|
340
|
+
let target;
|
|
341
|
+
try {
|
|
342
|
+
target = outlookTarget(root, targetId);
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
return invalid(error instanceof Error ? error.message : "invalid discovery target", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
346
|
+
}
|
|
347
|
+
const match = readMatch(root, targetId);
|
|
348
|
+
if (!match || !Array.isArray(match.candidates) || match.candidates.length === 0)
|
|
349
|
+
return invalid("No matched Outlook conversation is recorded for this target; run foo discovery outlook list first.", json, "E_DISCOVERY_VALIDATION", targetId);
|
|
350
|
+
if (match.candidates.length > 1)
|
|
351
|
+
return outcome(result("ambiguous_match", { study_id: targetId, errors: [{ code: "E_DISCOVERY_AMBIGUOUS_MATCH", message: "Several conversations matched; a person must select one before any conversation is opened." }], handoff: `foo discovery review ${targetId} select-source --fingerprint <sha256> --by <person>` }), json);
|
|
352
|
+
const candidate = match.candidates[0];
|
|
353
|
+
const session = candidate.session;
|
|
354
|
+
const conversationId = session.runtime_handle;
|
|
355
|
+
if (!synthesisCredentialPresent(deps.env))
|
|
356
|
+
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 conversation was not opened." }], handoff: "Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) for this runtime, then rerun process." }), json);
|
|
357
|
+
const model = defaultSynthesisModel(deps.env);
|
|
358
|
+
const coverage = { mode: MODE, lane: target.lane, policy_version: OUTLOOK_POLICY.policy_version, model, bodies_opened: false };
|
|
359
|
+
let client;
|
|
360
|
+
try {
|
|
361
|
+
const connected = await connect(deps);
|
|
362
|
+
client = connected.client;
|
|
363
|
+
coverage.connection = connected.connection.connection_ref;
|
|
364
|
+
if (connected.connection.body_scope !== "granted") {
|
|
365
|
+
// R-008: a missing body scope degrades reads to needs_provider_access
|
|
366
|
+
// while metadata and the matching state stay intact.
|
|
367
|
+
return outcome(result("needs_provider_access", { study_id: targetId, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: "The Outlook connection lacks the Mail.Read scope; the conversation was not opened." }], handoff: "Grant the least-privilege read scope (Mail.Read) on the connection in Nango, then rerun process.", coverage }), json);
|
|
368
|
+
}
|
|
369
|
+
const profile = await client.profile();
|
|
370
|
+
const identities = [];
|
|
371
|
+
for (const participant of [profile.email, ...(session.participants ?? [])]) {
|
|
372
|
+
if (participant?.trim())
|
|
373
|
+
identities.push(participant.trim());
|
|
374
|
+
}
|
|
375
|
+
let openError;
|
|
376
|
+
let conversationText;
|
|
377
|
+
try {
|
|
378
|
+
conversationText = await openAuthorizedTranscript({ study_dir: target.dir, local_root: localRoot(root), study_id: targetId, source_fingerprint: candidate.source_fingerprint, now }, async () => {
|
|
379
|
+
try {
|
|
380
|
+
// R-007: exactly one conversation identifier, read under the
|
|
381
|
+
// minted proof.
|
|
382
|
+
return await client.conversation(conversationId).then((messages) => renderConversation({ messages }, profile.email));
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
if (error instanceof OutlookRuntimeError)
|
|
386
|
+
openError = error;
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
if (openError)
|
|
393
|
+
return fromRuntimeError(openError, json, targetId, { ...coverage, conversation_body_requests: client.requests.conversation });
|
|
394
|
+
return outcome(result("needs_research_proof", { study_id: targetId, errors: [{ code: "E_DISCOVERY_RESEARCH_PROOF", message: "The conversation was not opened: the discovery proof is missing, expired, already used, or no longer matches the approval." }], handoff: `Run foo discovery outlook list ${targetId} again to re-match and mint a fresh proof.` }), json);
|
|
395
|
+
}
|
|
396
|
+
coverage.conversation_body_requests = client.requests.conversation;
|
|
397
|
+
const estimatedTokens = Math.ceil(conversationText.length / 4);
|
|
398
|
+
if (estimatedTokens > OUTLOOK_POLICY.max_model_input_tokens) {
|
|
399
|
+
return fromRuntimeError(new OutlookRuntimeError("conversation_too_large", "the conversation exceeds the model-input ceiling; nothing was synthesized"), json, targetId, coverage);
|
|
400
|
+
}
|
|
401
|
+
let synthesized;
|
|
402
|
+
try {
|
|
403
|
+
synthesized = await synthesizeDiscoveryEnvelope({
|
|
404
|
+
target_id: targetId,
|
|
405
|
+
lane: target.lane,
|
|
406
|
+
source: { provider: "outlook", source_fingerprint: candidate.source_fingerprint, completed_at: session.completed_at, locator_capability: "none" },
|
|
407
|
+
title: session.title,
|
|
408
|
+
hypotheses: [],
|
|
409
|
+
// The conversation's text bodies are the "transcript" here:
|
|
410
|
+
// role-labeled, screen-then-discard, never quoted verbatim by the
|
|
411
|
+
// model.
|
|
412
|
+
transcript_text: conversationText,
|
|
413
|
+
transcript_complete: true,
|
|
414
|
+
caveats: ["Outlook declares no stable link form in V0 (R-006): the conversation is identified by its matched fingerprint only.", "Attachments were never opened (D-003)."],
|
|
415
|
+
}, { model, transport: deps.transport });
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// R-007: the conversation text is discarded with this frame — it never
|
|
419
|
+
// reaches disk, a log, or an error surface, even when synthesis throws.
|
|
420
|
+
return invalid("Synthesis did not produce a valid closed envelope; nothing was written and the conversation was discarded.", json, "E_DISCOVERY_INVALID_ENVELOPE", targetId, coverage);
|
|
421
|
+
}
|
|
422
|
+
coverage.synthesis_usage = synthesized.usage ?? null;
|
|
423
|
+
// Privacy screen beyond the envelope validator: the mailbox owner's
|
|
424
|
+
// address and the conversation's own participant labels must not appear
|
|
425
|
+
// in derived text.
|
|
426
|
+
const derivedText = [
|
|
427
|
+
...synthesized.envelope.findings.flatMap((finding) => [finding.headline, finding.paraphrase, finding.dissent]),
|
|
428
|
+
...synthesized.envelope.coverage.caveats,
|
|
429
|
+
].join("\n");
|
|
430
|
+
if (containsIdentity(derivedText, identities))
|
|
431
|
+
return invalid("Synthesis leaked a participant identity into derived text; nothing was written.", json, "E_DISCOVERY_INVALID_ENVELOPE", targetId, coverage);
|
|
432
|
+
const applied = await runDiscoveryCommand(root, ["apply", targetId, "--evidence-stdin", "--json"], JSON.stringify(synthesized.envelope), now);
|
|
433
|
+
return outcome({ ...applied.result, coverage: { ...applied.result.coverage, ...coverage } }, json);
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
if (error instanceof OutlookRuntimeError)
|
|
437
|
+
return fromRuntimeError(error, json, targetId, { ...coverage, conversation_body_requests: client?.requests.conversation ?? 0 });
|
|
438
|
+
return invalid("Outlook processing failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", targetId, coverage);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function syncCommand(root, json, deps) {
|
|
442
|
+
const now = deps.now();
|
|
443
|
+
const plan = planDiscoverySync(root, now);
|
|
444
|
+
const lanes = plan.lanes.filter((lane) => lane.provider.toLowerCase() === "outlook");
|
|
445
|
+
if (lanes.length === 0)
|
|
446
|
+
return outcome(result("clean_no_op", { errors: plan.errors.map((message) => ({ code: "E_DISCOVERY_VALIDATION", message })), next_actions: ["No approved Outlook lane is active; approve an inbox scope naming explicit folders first."], coverage: { mode: MODE, lanes: 0, connector_reads: 0 } }), json);
|
|
447
|
+
const results = [];
|
|
448
|
+
// Metadata and match only: a conversation body is opened only by an
|
|
449
|
+
// explicit `process` on one target. The loop stays unarmed (R-007/V-010).
|
|
450
|
+
for (const lane of lanes) {
|
|
451
|
+
const targetId = `inbox:${lane.scope_id}`;
|
|
452
|
+
const listed = await listCommand(root, targetId, true, deps);
|
|
453
|
+
results.push({ lane_id: lane.lane_id, status: listed.result.status, handoff: listed.result.handoff });
|
|
454
|
+
}
|
|
455
|
+
const ready = results.filter((entry) => entry.status === "ok").map((entry) => entry.lane_id);
|
|
456
|
+
return outcome(result("ok", {
|
|
457
|
+
errors: plan.errors.map((message) => ({ code: "E_DISCOVERY_VALIDATION", message })),
|
|
458
|
+
next_actions: [
|
|
459
|
+
...ready.map((laneId) => `${laneId}: one conversation matched — run foo discovery outlook process ${laneId} to open it.`),
|
|
460
|
+
...results.filter((entry) => entry.status !== "ok").map((entry) => `${entry.lane_id}: ${entry.status}${entry.handoff ? ` — ${entry.handoff}` : ""}`),
|
|
461
|
+
],
|
|
462
|
+
coverage: { mode: MODE, lanes: results.length, conversations_opened: 0, results },
|
|
463
|
+
}), json);
|
|
464
|
+
}
|
|
465
|
+
export async function runOutlookDiscovery(root, args, deps = {}) {
|
|
466
|
+
const json = hasFlag(args, "--json");
|
|
467
|
+
const env = deps.env ?? process.env;
|
|
468
|
+
const resolved = {
|
|
469
|
+
fetchImpl: deps.fetchImpl ?? globalThis.fetch,
|
|
470
|
+
env,
|
|
471
|
+
now: deps.now ?? (() => new Date().toISOString()),
|
|
472
|
+
sleep: deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
|
|
473
|
+
transport: deps.transport ?? anthropicSynthesisTransport({ fetchImpl: deps.fetchImpl, env }),
|
|
474
|
+
fetchTimeoutMs: deps.fetchTimeoutMs ?? OUTLOOK_POLICY.fetch_timeout_ms,
|
|
475
|
+
};
|
|
476
|
+
const command = args[0];
|
|
477
|
+
const target = args[1] && !args[1].startsWith("--") ? args[1] : undefined;
|
|
478
|
+
try {
|
|
479
|
+
if (command === "ready")
|
|
480
|
+
return await readyCommand(root, json, resolved);
|
|
481
|
+
if (command === "list")
|
|
482
|
+
return await listCommand(root, target, json, resolved);
|
|
483
|
+
if (command === "process")
|
|
484
|
+
return await processCommand(root, target, json, resolved);
|
|
485
|
+
if (command === "sync")
|
|
486
|
+
return await syncCommand(root, json, resolved);
|
|
487
|
+
return invalid("Try: ready | list <inbox:scope-id> | process <inbox:scope-id> | sync", json);
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
return invalid("Outlook discovery failed closed without exposing private source content.", json);
|
|
491
|
+
}
|
|
492
|
+
}
|