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,577 @@
|
|
|
1
|
+
// The docs evidence-class runtime (notion-discovery-connector-v0 D-001, T-004).
|
|
2
|
+
//
|
|
3
|
+
// Documents are not sessions. Evidence runs through the substrate's
|
|
4
|
+
// selection-plus-approval model with ONE bounded summarize step under the same
|
|
5
|
+
// one-time proof boundary used for research calls — then only the summary
|
|
6
|
+
// lands. Nothing else about the model changes: same classification, same
|
|
7
|
+
// ledger, same revocation. The raw document content exists in process memory
|
|
8
|
+
// for exactly one model request and is never written, logged, or returned.
|
|
9
|
+
//
|
|
10
|
+
// Contract-only: this module makes no claim about live Notion access — the
|
|
11
|
+
// feasibility report (T-001) and the dogfood run (T-007) own that evidence.
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { appendIntegrationRecord, approvalStates, minimizeSummary, notionScopeAllowsSelection, sourceKey } from "./sourceIngress.js";
|
|
15
|
+
import { approvalLedgerDigest, consumeApprovedSourceProof, mintApprovedSourceProof, openApprovedSourceContent } from "./discoveryProof.js";
|
|
16
|
+
import { defaultSynthesisModel, synthesisCredentialPresent } from "./discoverySynthesis.js";
|
|
17
|
+
import { NOTION_POLICY, NotionClient, NotionRuntimeError, notionConnectionConfigured, resolveNotionConnection } from "./notionNango.js";
|
|
18
|
+
import { sanitizeDiscoveryStableUrl } from "./discoverySources.js";
|
|
19
|
+
export class DocsFlowError extends Error {
|
|
20
|
+
code;
|
|
21
|
+
constructor(code, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.name = "DocsFlowError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const NOTION_UUID = /^[0-9a-f]{32}$/i;
|
|
28
|
+
function canonicalNotionId(raw, label) {
|
|
29
|
+
if (typeof raw !== "string")
|
|
30
|
+
throw new DocsFlowError("invalid", `${label} is missing`);
|
|
31
|
+
const hex = raw.trim().replace(/-/g, "");
|
|
32
|
+
if (!NOTION_UUID.test(hex))
|
|
33
|
+
throw new DocsFlowError("invalid", `${label} carries an invalid Notion identifier`);
|
|
34
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
35
|
+
}
|
|
36
|
+
export function notionCanonicalPageUrl(pageId) {
|
|
37
|
+
return `https://www.notion.so/${canonicalNotionId(pageId, "page_id")}`;
|
|
38
|
+
}
|
|
39
|
+
export function parseNotionSourceRecord(raw, opts) {
|
|
40
|
+
if (!opts.connection_alias.trim())
|
|
41
|
+
throw new DocsFlowError("invalid", "a docs source record needs its connection alias");
|
|
42
|
+
const page_id = raw.page_id !== undefined ? canonicalNotionId(raw.page_id, "page_id") : undefined;
|
|
43
|
+
const database_id = raw.database_id !== undefined ? canonicalNotionId(raw.database_id, "database_id") : undefined;
|
|
44
|
+
if (!page_id && !database_id)
|
|
45
|
+
throw new DocsFlowError("invalid", "a docs source record names a page or a database");
|
|
46
|
+
if (page_id && database_id)
|
|
47
|
+
throw new DocsFlowError("invalid", "a docs source record names one selection, not both");
|
|
48
|
+
const lastEdited = raw.last_edited;
|
|
49
|
+
if (typeof lastEdited !== "string" || !Number.isFinite(Date.parse(lastEdited)))
|
|
50
|
+
throw new DocsFlowError("invalid", "a docs source record carries a last_edited timestamp");
|
|
51
|
+
let stableUrl;
|
|
52
|
+
if (raw.url !== undefined) {
|
|
53
|
+
const sanitized = sanitizeDiscoveryStableUrl("notion", String(raw.url));
|
|
54
|
+
if (sanitized)
|
|
55
|
+
stableUrl = sanitized;
|
|
56
|
+
}
|
|
57
|
+
if (!stableUrl && page_id)
|
|
58
|
+
stableUrl = notionCanonicalPageUrl(page_id);
|
|
59
|
+
return {
|
|
60
|
+
provider: "notion",
|
|
61
|
+
connection_alias: opts.connection_alias.trim(),
|
|
62
|
+
...(page_id ? { page_id } : {}),
|
|
63
|
+
...(database_id ? { database_id } : {}),
|
|
64
|
+
...(typeof raw.title === "string" && raw.title.trim() ? { title: raw.title.trim() } : {}),
|
|
65
|
+
last_edited: new Date(lastEdited).toISOString(),
|
|
66
|
+
...(stableUrl ? { stable_url: stableUrl } : {}),
|
|
67
|
+
...(typeof raw.parent_container === "string" && raw.parent_container.trim() ? { parent_container: raw.parent_container.trim() } : {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Binds the proof to this exact selection on this exact connection.
|
|
71
|
+
export function docsSelectionFingerprint(record) {
|
|
72
|
+
return `sha256:${createHash("sha256").update(["docs", record.connection_alias, record.page_id ?? "", record.database_id ?? ""].join("\n")).digest("hex")}`;
|
|
73
|
+
}
|
|
74
|
+
const EMAIL_PATTERN = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/;
|
|
75
|
+
const PHONE_PATTERN = /(?:\+?\d[\d\s().-]{8,}\d)/;
|
|
76
|
+
function escapeRegExp(text) {
|
|
77
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
78
|
+
}
|
|
79
|
+
// The identity screen (R-005): derived text is screened against the workspace's
|
|
80
|
+
// own people's identities before any write. A leak refuses the write — the
|
|
81
|
+
// summary is discarded, nothing lands in the ledger, and the caller gets a
|
|
82
|
+
// typed error with no content echo.
|
|
83
|
+
export function containsIdentity(text, identities) {
|
|
84
|
+
if (EMAIL_PATTERN.test(text) || PHONE_PATTERN.test(text))
|
|
85
|
+
return true;
|
|
86
|
+
return identities.some((identity) => identity.trim().length >= 4 && new RegExp(`\\b${escapeRegExp(identity.trim())}\\b`, "i").test(text));
|
|
87
|
+
}
|
|
88
|
+
function validateApprovedSourceSummary(value, identities) {
|
|
89
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
90
|
+
throw new DocsFlowError("invalid", "the summary payload is not an object");
|
|
91
|
+
const shape = value;
|
|
92
|
+
const ALLOWED = new Set(["schema_version", "kind", "source", "source_category", "evidence_class", "model_exposure", "summary", "coverage", "privacy_attestation"]);
|
|
93
|
+
for (const key of Object.keys(shape)) {
|
|
94
|
+
if (!ALLOWED.has(key))
|
|
95
|
+
throw new DocsFlowError("invalid", `the summary payload carries unknown field ${key}`);
|
|
96
|
+
}
|
|
97
|
+
if (shape.schema_version !== "0.1" || shape.kind !== "approved_source_artifact_summary")
|
|
98
|
+
throw new DocsFlowError("invalid", "the summary payload is not an approved_source_artifact_summary");
|
|
99
|
+
if (shape.source_category !== "docs" || shape.evidence_class !== "approved_source_artifact" || shape.model_exposure !== "summary_only")
|
|
100
|
+
throw new DocsFlowError("invalid", "the summary payload does not carry the docs class");
|
|
101
|
+
const summary = shape.summary;
|
|
102
|
+
if (typeof summary !== "string" || !summary.trim())
|
|
103
|
+
throw new DocsFlowError("invalid", "the summary payload carries no summary text");
|
|
104
|
+
const coverage = shape.coverage;
|
|
105
|
+
if (!coverage || typeof coverage !== "object" || Array.isArray(coverage) || !Array.isArray(coverage.caveats))
|
|
106
|
+
throw new DocsFlowError("invalid", "the summary payload carries no coverage caveats");
|
|
107
|
+
const attestation = shape.privacy_attestation;
|
|
108
|
+
if (!attestation || attestation.pii_redacted !== true || attestation.direct_quotes !== false || attestation.raw_content_present !== false) {
|
|
109
|
+
throw new DocsFlowError("invalid", "the summary payload carries no privacy attestation");
|
|
110
|
+
}
|
|
111
|
+
if (containsIdentity(summary, identities))
|
|
112
|
+
throw new DocsFlowError("identity_leak_refused", "the derived summary carried an identity; it was refused and nothing was written");
|
|
113
|
+
return shape;
|
|
114
|
+
}
|
|
115
|
+
// --- The one summarize step --------------------------------------------------
|
|
116
|
+
const DOCS_SYSTEM = [
|
|
117
|
+
"You summarize an approved internal document for product-research evidence.",
|
|
118
|
+
"Rules:",
|
|
119
|
+
"- Paraphrase only; never quote the document verbatim.",
|
|
120
|
+
"- Never include email addresses, phone numbers, or other personal contact details.",
|
|
121
|
+
"- Never include people's names; refer to roles (for example \"an engineer\", \"the account owner\").",
|
|
122
|
+
"- The summary must stand alone without the document text.",
|
|
123
|
+
"Output strict JSON with exactly these keys: {\"summary\": string, \"caveats\": string[]}.",
|
|
124
|
+
].join("\n");
|
|
125
|
+
function extractJsonObject(text) {
|
|
126
|
+
const start = text.indexOf("{");
|
|
127
|
+
const end = text.lastIndexOf("}");
|
|
128
|
+
if (start === -1 || end <= start)
|
|
129
|
+
throw new DocsFlowError("invalid", "the model reply carried no JSON object; nothing was written");
|
|
130
|
+
try {
|
|
131
|
+
const parsed = JSON.parse(text.slice(start, end + 1));
|
|
132
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
133
|
+
throw new Error("not an object");
|
|
134
|
+
return parsed;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
throw new DocsFlowError("invalid", "the model reply carried no readable JSON object; nothing was written");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Exactly one bounded model request over the approved content (R-005). The
|
|
142
|
+
* content exists only inside this one request; the reply is closed-validated,
|
|
143
|
+
* identity-screened, and turned into the summary record. On any refusal the
|
|
144
|
+
* caller receives a typed error and no write has happened.
|
|
145
|
+
*/
|
|
146
|
+
export async function summarizeApprovedSource(input, options) {
|
|
147
|
+
const estimatedTokens = Math.ceil(input.content_text.length / 4);
|
|
148
|
+
if (estimatedTokens > NOTION_POLICY.max_model_input_tokens) {
|
|
149
|
+
throw new DocsFlowError("content_too_large", "the selected document exceeds the model input ceiling; the content was dropped and nothing was written");
|
|
150
|
+
}
|
|
151
|
+
const user = [
|
|
152
|
+
"Approved document selection. Summarize it for product-research evidence.",
|
|
153
|
+
`Title: ${input.record.title ?? "(untitled)"}`,
|
|
154
|
+
`Last edited: ${input.record.last_edited}`,
|
|
155
|
+
"",
|
|
156
|
+
"Document content:",
|
|
157
|
+
input.content_text,
|
|
158
|
+
"",
|
|
159
|
+
'Respond with strict JSON: {"summary": string, "caveats": string[]}.',
|
|
160
|
+
].join("\n");
|
|
161
|
+
const reply = await options.transport({ model: options.model, system: DOCS_SYSTEM, user, max_tokens: 4000 });
|
|
162
|
+
if (reply.stop_reason === "refusal")
|
|
163
|
+
throw new DocsFlowError("model_refused", "the model declined to summarize this document; nothing was written");
|
|
164
|
+
if (reply.stop_reason === "max_tokens")
|
|
165
|
+
throw new DocsFlowError("model_truncated", "the summary output was truncated; nothing was written");
|
|
166
|
+
const payload = extractJsonObject(reply.text);
|
|
167
|
+
const summaryText = typeof payload.summary === "string" ? payload.summary : "";
|
|
168
|
+
const caveats = Array.isArray(payload.caveats) ? payload.caveats.filter((entry) => typeof entry === "string") : [];
|
|
169
|
+
const built = {
|
|
170
|
+
schema_version: "0.1",
|
|
171
|
+
kind: "approved_source_artifact_summary",
|
|
172
|
+
source: {
|
|
173
|
+
provider: input.record.provider,
|
|
174
|
+
connection_alias: input.record.connection_alias,
|
|
175
|
+
...(input.record.stable_url ? { stable_url: input.record.stable_url } : {}),
|
|
176
|
+
source_fingerprint: docsSelectionFingerprint(input.record),
|
|
177
|
+
last_edited: input.record.last_edited,
|
|
178
|
+
...(input.record.title ? { title: input.record.title } : {}),
|
|
179
|
+
},
|
|
180
|
+
source_category: "docs",
|
|
181
|
+
evidence_class: "approved_source_artifact",
|
|
182
|
+
model_exposure: "summary_only",
|
|
183
|
+
summary: summaryText.trim(),
|
|
184
|
+
coverage: { caveats },
|
|
185
|
+
privacy_attestation: { pii_redacted: true, direct_quotes: false, raw_content_present: false },
|
|
186
|
+
};
|
|
187
|
+
if (!built.summary)
|
|
188
|
+
throw new DocsFlowError("invalid", "the model reply carried no summary text; nothing was written");
|
|
189
|
+
return validateApprovedSourceSummary(built, input.identities);
|
|
190
|
+
}
|
|
191
|
+
function fail(status, code, message, input, nextAction) {
|
|
192
|
+
return {
|
|
193
|
+
schema_version: "0.1",
|
|
194
|
+
status,
|
|
195
|
+
selection: input.selection,
|
|
196
|
+
errors: [{ code, message }],
|
|
197
|
+
...(nextAction ? { next_action: nextAction } : {}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The one-read flow for an explicitly selected Notion page (R-004/R-005):
|
|
202
|
+
* approval-checked against the packet's ledger, proof-gated, one content read,
|
|
203
|
+
* one model request, identity screen, one summary-only ledger record, proof
|
|
204
|
+
* consumed. The canary content appears in no file, log, or output — only the
|
|
205
|
+
* PII-minimized summary survives.
|
|
206
|
+
*/
|
|
207
|
+
export async function processNotionSelection(input, deps = {}) {
|
|
208
|
+
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
209
|
+
if (input.selection.database_id) {
|
|
210
|
+
return fail("failed", "invalid", "database selections use the bounded listing flow (listNotionDatabaseSelection), not the one-read summarize flow", input);
|
|
211
|
+
}
|
|
212
|
+
let pageId;
|
|
213
|
+
try {
|
|
214
|
+
pageId = canonicalNotionId(input.selection.page_id, "page_id");
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
return fail("failed", "invalid", error instanceof Error ? error.message : "invalid selection", input);
|
|
218
|
+
}
|
|
219
|
+
if (!notionConnectionConfigured(input.env)) {
|
|
220
|
+
return fail("needs_provider_connector", "needs_provider_connector", "Notion is not connected; connect the company workspace through Nango", input);
|
|
221
|
+
}
|
|
222
|
+
// Fail-closed approval gate before ANY provider call: the connection must be
|
|
223
|
+
// approved in this packet's ledger and one accumulated scope must explicitly
|
|
224
|
+
// admit this page (never a wildcard).
|
|
225
|
+
const state = approvalStates(input.packet_dir).get(sourceKey({ provider: "notion", connection_id: input.connection_alias }));
|
|
226
|
+
const approved = state?.state === "approved" && state.scopes.some((scope) => notionScopeAllowsSelection(scope, { page_id: pageId }));
|
|
227
|
+
if (!approved) {
|
|
228
|
+
return fail("needs_source_approval", "needs_source_approval", "this page is not inside an approved Notion scope for this packet", input, `foo source approve ${path.relative(process.cwd(), input.packet_dir) || input.packet_dir} --provider notion --connection ${input.connection_alias} --scope '{"page_ids":["${pageId}"],"window":{"from":"<from>","to":"<to>"}}' --by <person>`);
|
|
229
|
+
}
|
|
230
|
+
if (!synthesisCredentialPresent(input.env)) {
|
|
231
|
+
return fail("failed", "invalid", "no model credential is configured for the summarize step", input, "set the synthesis credential before processing an approved selection");
|
|
232
|
+
}
|
|
233
|
+
const connection = await resolveNotionConnection({ env: input.env, fetchImpl }).catch((error) => {
|
|
234
|
+
if (error instanceof NotionRuntimeError && error.code === "not_connected")
|
|
235
|
+
return null;
|
|
236
|
+
throw error;
|
|
237
|
+
});
|
|
238
|
+
if (!connection) {
|
|
239
|
+
return fail("needs_provider_connector", "needs_provider_connector", "Notion is not connected; connect the company workspace through Nango", input);
|
|
240
|
+
}
|
|
241
|
+
const record = { provider: "notion", connection_alias: input.connection_alias, page_id: pageId, last_edited: new Date(input.now).toISOString() };
|
|
242
|
+
const fingerprint = docsSelectionFingerprint(record);
|
|
243
|
+
const client = new NotionClient(connection.credential, { fetchImpl, sleep: async () => undefined });
|
|
244
|
+
const digest = approvalLedgerDigest(input.packet_dir);
|
|
245
|
+
mintApprovedSourceProof(input.local_root, {
|
|
246
|
+
target_id: input.target_id,
|
|
247
|
+
provider: "notion",
|
|
248
|
+
connection_alias: input.connection_alias,
|
|
249
|
+
selection_fingerprint: fingerprint,
|
|
250
|
+
approval_digest: digest,
|
|
251
|
+
now: input.now,
|
|
252
|
+
});
|
|
253
|
+
try {
|
|
254
|
+
// Metadata first (listing boundary), then the ONE content read inside the
|
|
255
|
+
// proof-gated open. The open re-verifies approval at the moment of the
|
|
256
|
+
// read — a revocation between mint and open invalidates the proof.
|
|
257
|
+
const meta = await client.page(pageId);
|
|
258
|
+
const fullRecord = parseNotionSourceRecord({
|
|
259
|
+
page_id: meta.id,
|
|
260
|
+
title: meta.title,
|
|
261
|
+
last_edited: meta.last_edited_time ?? input.now,
|
|
262
|
+
url: meta.url,
|
|
263
|
+
}, { connection_alias: input.connection_alias });
|
|
264
|
+
const identityNames = (input.env.FOO_WORKSPACE_IDENTITIES ?? "")
|
|
265
|
+
.split(",")
|
|
266
|
+
.map((entry) => entry.trim())
|
|
267
|
+
.filter(Boolean);
|
|
268
|
+
const content = await openApprovedSourceContent({ packet_dir: input.packet_dir, local_root: input.local_root, target_id: input.target_id, selection_fingerprint: fingerprint, selection: { page_id: pageId }, now: input.now }, () => client.pageContent(pageId));
|
|
269
|
+
const summary = await summarizeApprovedSource({ record: fullRecord, content_text: content, identities: identityNames }, { model: defaultSynthesisModel(input.env), transport: input.transport });
|
|
270
|
+
// The summary-only ledger record. The generic recordSummary entry cannot
|
|
271
|
+
// carry the docs-class freshness/link fields, so the flow writes the full
|
|
272
|
+
// docs entry through the generic append — the approval gate above (and the
|
|
273
|
+
// proof's re-check at open time) is the fail-closed gate.
|
|
274
|
+
appendIntegrationRecord(input.packet_dir, {
|
|
275
|
+
kind: "approved_source_artifact_summary",
|
|
276
|
+
source_id: { ingress_path: "nango", provider: "notion", connection_id: input.connection_alias },
|
|
277
|
+
selection: `page:${pageId}`,
|
|
278
|
+
summary: minimizeSummary(summary.summary),
|
|
279
|
+
evidence_class: "approved_source_artifact",
|
|
280
|
+
source_category: "docs",
|
|
281
|
+
source_fingerprint: fingerprint,
|
|
282
|
+
last_edited: fullRecord.last_edited,
|
|
283
|
+
...(fullRecord.stable_url ? { stable_url: fullRecord.stable_url } : {}),
|
|
284
|
+
at: input.now,
|
|
285
|
+
tombstoned: false,
|
|
286
|
+
});
|
|
287
|
+
return { schema_version: "0.1", status: "recorded", selection: input.selection, summary, errors: [] };
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
if (error instanceof DocsFlowError) {
|
|
291
|
+
const status = error.code === "content_too_large" ? "content_too_large" : error.code === "model_refused" ? "model_refused" : error.code === "model_truncated" ? "model_truncated" : error.code === "identity_leak_refused" ? "identity_leak_refused" : "failed";
|
|
292
|
+
return fail(status, error.code, error.message, input);
|
|
293
|
+
}
|
|
294
|
+
if (error instanceof NotionRuntimeError) {
|
|
295
|
+
const code = error.code === "content_too_large" ? "content_too_large" : error.code === "access_denied" ? "access_denied" : "provider_failure";
|
|
296
|
+
const status = code === "content_too_large" ? "content_too_large" : "failed";
|
|
297
|
+
return fail(status, code, error.message, input);
|
|
298
|
+
}
|
|
299
|
+
return fail("failed", "provider_failure", error instanceof Error ? error.message : "the approved-source flow failed; nothing was written", input);
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
// Zero residue: consume the proof on every post-open path. A purged or
|
|
303
|
+
// missing receipt is fine — best-effort cleanup.
|
|
304
|
+
try {
|
|
305
|
+
consumeApprovedSourceProof(input.local_root, input.target_id, fingerprint, input.now);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// The receipt was already purged (failed open) or is missing.
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* The listing boundary for an approved database selection: a bounded metadata
|
|
314
|
+
* listing (titles + last-edited, capped by policy) and nothing else. No
|
|
315
|
+
* content read happens here, nothing is written, and the caller decides what
|
|
316
|
+
* the listing is evidence for.
|
|
317
|
+
*/
|
|
318
|
+
export async function listNotionDatabaseSelection(input, deps = {}) {
|
|
319
|
+
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
320
|
+
let databaseId;
|
|
321
|
+
try {
|
|
322
|
+
databaseId = canonicalNotionId(input.selection.database_id, "database_id");
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
return { schema_version: "0.1", status: "failed", records: [], errors: [{ code: "invalid", message: error instanceof Error ? error.message : "invalid selection" }] };
|
|
326
|
+
}
|
|
327
|
+
if (!notionConnectionConfigured(input.env)) {
|
|
328
|
+
return { schema_version: "0.1", status: "needs_provider_connector", records: [], errors: [{ code: "needs_provider_connector", message: "Notion is not connected; connect the company workspace through Nango" }] };
|
|
329
|
+
}
|
|
330
|
+
const state = approvalStates(input.packet_dir).get(sourceKey({ provider: "notion", connection_id: input.connection_alias }));
|
|
331
|
+
const approved = state?.state === "approved" && state.scopes.some((scope) => notionScopeAllowsSelection(scope, { database_id: databaseId }));
|
|
332
|
+
if (!approved) {
|
|
333
|
+
return { schema_version: "0.1", status: "needs_source_approval", records: [], errors: [{ code: "needs_source_approval", message: "this database is not inside an approved Notion scope for this packet" }] };
|
|
334
|
+
}
|
|
335
|
+
const connection = await resolveNotionConnection({ env: input.env, fetchImpl }).catch((error) => {
|
|
336
|
+
if (error instanceof NotionRuntimeError && error.code === "not_connected")
|
|
337
|
+
return null;
|
|
338
|
+
throw error;
|
|
339
|
+
});
|
|
340
|
+
if (!connection) {
|
|
341
|
+
return { schema_version: "0.1", status: "needs_provider_connector", records: [], errors: [{ code: "needs_provider_connector", message: "Notion is not connected; connect the company workspace through Nango" }] };
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const client = new NotionClient(connection.credential, { fetchImpl, sleep: async () => undefined });
|
|
345
|
+
const rows = await client.queryDatabase(databaseId);
|
|
346
|
+
const records = rows.map((row) => parseNotionSourceRecord({ page_id: row.id, title: row.title, last_edited: row.last_edited_time ?? input.now, url: row.url, parent_container: `database:${databaseId}` }, { connection_alias: input.connection_alias }));
|
|
347
|
+
return { schema_version: "0.1", status: "listed", records, errors: [] };
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
if (error instanceof NotionRuntimeError) {
|
|
351
|
+
return { schema_version: "0.1", status: "failed", records: [], errors: [{ code: error.code === "access_denied" ? "access_denied" : "provider_failure", message: error.message }] };
|
|
352
|
+
}
|
|
353
|
+
return { schema_version: "0.1", status: "failed", records: [], errors: [{ code: "provider_failure", message: error instanceof Error ? error.message : "the listing failed" }] };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// ─── CLI worker surfaces (T-005) ─────────────────────────────────────────────
|
|
357
|
+
// `foo discovery notion ready | list <packet> | process <packet> --page=<id>`
|
|
358
|
+
// — the same worker shape as the Gong runtime, reporting the substrate's
|
|
359
|
+
// support states truthfully: this connector is contract_only and read-only.
|
|
360
|
+
import { existsSync, statSync } from "node:fs";
|
|
361
|
+
import { hasFlag } from "./cliArgs.js";
|
|
362
|
+
import { localRoot } from "./discoveryCommand.js";
|
|
363
|
+
import { notionCredentialBroker } from "./notionNango.js";
|
|
364
|
+
import { parseNotionScope } from "./sourceIngress.js";
|
|
365
|
+
const MODE = "local_worker";
|
|
366
|
+
const SUPPORT_STATE = "contract_only";
|
|
367
|
+
function cliResult(status, options = {}) {
|
|
368
|
+
return { schema_version: "0.1", status, writes: [], errors: [], handoff: null, next_actions: [], coverage: {}, ...options };
|
|
369
|
+
}
|
|
370
|
+
function cliExitCode(status) {
|
|
371
|
+
return ["needs_source_approval", "needs_provider_connector", "needs_provider_access", "needs_review", "invalid", "budget_exhausted", "needs_research_proof"].includes(status) ? 1 : 0;
|
|
372
|
+
}
|
|
373
|
+
function cliOutcome(value, json) {
|
|
374
|
+
const exitCode = cliExitCode(value.status);
|
|
375
|
+
if (json)
|
|
376
|
+
return { output: `${JSON.stringify(value, null, 2)}\n`, exitCode, result: value };
|
|
377
|
+
const lines = [`Discovery (notion): ${value.status}.`];
|
|
378
|
+
for (const error of value.errors)
|
|
379
|
+
lines.push(`- ${error.message}`);
|
|
380
|
+
if (value.handoff)
|
|
381
|
+
lines.push(`Handoff: ${value.handoff}`);
|
|
382
|
+
for (const action of value.next_actions)
|
|
383
|
+
lines.push(`Next: ${action}`);
|
|
384
|
+
return { output: `${lines.join("\n")}\n`, exitCode, result: value };
|
|
385
|
+
}
|
|
386
|
+
function cliInvalid(message, json, code = "E_DISCOVERY_VALIDATION", targetId) {
|
|
387
|
+
return cliOutcome(cliResult("invalid", { study_id: targetId, errors: [{ code, message }] }), json);
|
|
388
|
+
}
|
|
389
|
+
// The docs scope for a packet: the accumulated approval scopes for every
|
|
390
|
+
// Notion connection, with each scope's page/database ids and window parsed
|
|
391
|
+
// fail-closed (an unparseable approved scope never widens what may be read).
|
|
392
|
+
export function approvedNotionScopes(packetDir) {
|
|
393
|
+
const out = [];
|
|
394
|
+
for (const [key, state] of approvalStates(packetDir)) {
|
|
395
|
+
if (state.state !== "approved")
|
|
396
|
+
continue;
|
|
397
|
+
const [provider, connectionId] = key.split("::");
|
|
398
|
+
if (provider !== "notion" || !connectionId)
|
|
399
|
+
continue;
|
|
400
|
+
for (const raw of state.scopes) {
|
|
401
|
+
try {
|
|
402
|
+
out.push({ connection_id: connectionId, scope: parseNotionScope(raw), raw });
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
// A scope that fails the closed parser is not a permission; skip it.
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return out;
|
|
410
|
+
}
|
|
411
|
+
function resolvePacketDir(root, target) {
|
|
412
|
+
if (!target)
|
|
413
|
+
return null;
|
|
414
|
+
const candidates = [path.join(root, target), path.join(root, "specs", target)];
|
|
415
|
+
for (const candidate of candidates) {
|
|
416
|
+
// The packet directory exists before its first approval; the approval
|
|
417
|
+
// ledger is created by `foo source approve`.
|
|
418
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory())
|
|
419
|
+
return candidate;
|
|
420
|
+
}
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
async function readyCommand(root, json, deps) {
|
|
424
|
+
const env = deps.env;
|
|
425
|
+
const coverage = {
|
|
426
|
+
provider: "notion",
|
|
427
|
+
mode: MODE,
|
|
428
|
+
support_state: SUPPORT_STATE,
|
|
429
|
+
connection_owner: "workspace",
|
|
430
|
+
configured: notionConnectionConfigured(env),
|
|
431
|
+
credential_broker: notionCredentialBroker(env),
|
|
432
|
+
synthesis: { available: synthesisCredentialPresent(env), model: defaultSynthesisModel(env) },
|
|
433
|
+
policy: { ...NOTION_POLICY },
|
|
434
|
+
// Honest capability map: writes and workspace-wide search do not exist in
|
|
435
|
+
// this client, and page content reads happen only under a one-time proof.
|
|
436
|
+
capabilities: { write_back: false, workspace_search: false, read_page_content: "one-time-proof gated", list_database_selections: "bounded metadata only" },
|
|
437
|
+
};
|
|
438
|
+
try {
|
|
439
|
+
const connection = await resolveNotionConnection({ env, fetchImpl: deps.fetchImpl });
|
|
440
|
+
coverage.connection = connection.connection_ref;
|
|
441
|
+
coverage.credential_broker = connection.broker;
|
|
442
|
+
coverage.credential_type = String(connection.credential.type);
|
|
443
|
+
coverage.provider_reads = "none: readiness proves the credential only; provider content reads happen under an approved selection";
|
|
444
|
+
return cliOutcome(cliResult("ok", { coverage, next_actions: [
|
|
445
|
+
...(connection.broker === "direct" ? ["Credential broker is direct: this runtime holds an administrator-issued integration token itself. Refresh and revoke happen in Notion, not centrally; move to Nango for the company connection."] : []),
|
|
446
|
+
"Approve a bounded Notion scope with foo source approve --provider notion …, then run foo discovery notion list <packet>.",
|
|
447
|
+
...(coverage.synthesis && !coverage.synthesis.available ? ["Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) before processing a page."] : []),
|
|
448
|
+
] }), json);
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
if (error instanceof NotionRuntimeError) {
|
|
452
|
+
const status = "needs_provider_connector";
|
|
453
|
+
return cliOutcome(cliResult(status, { coverage: { ...coverage, provider_error: error.code }, errors: [{ code: "E_DISCOVERY_PROVIDER_CONNECTOR", message: error.message }], handoff: "Connect the company Notion workspace through Nango and set NANGO_SECRET_KEY and FOO_NOTION_NANGO_CONNECTION; or, for this runtime only, set FOO_NOTION_API_TOKEN from an administrator-issued internal integration token." }), json);
|
|
454
|
+
}
|
|
455
|
+
return cliInvalid("Notion readiness failed closed without exposing private source content.", json);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// The docs flow's closed statuses map onto the discovery command's closed
|
|
459
|
+
// status set without inventing new states.
|
|
460
|
+
function flowToCommandStatus(flow) {
|
|
461
|
+
switch (flow) {
|
|
462
|
+
case "needs_source_approval": return { status: "needs_source_approval", code: "E_DISCOVERY_SOURCE_APPROVAL" };
|
|
463
|
+
case "needs_provider_connector": return { status: "needs_provider_connector", code: "E_DISCOVERY_PROVIDER_CONNECTOR" };
|
|
464
|
+
case "content_too_large": return { status: "budget_exhausted", code: "E_DISCOVERY_VALIDATION" };
|
|
465
|
+
case "model_refused":
|
|
466
|
+
case "model_truncated": return { status: "needs_review", code: "E_DISCOVERY_RESEARCH_PROOF" };
|
|
467
|
+
case "identity_leak_refused": return { status: "needs_review", code: "E_DISCOVERY_MANUAL_RESOLUTION" };
|
|
468
|
+
case "access_denied": return { status: "needs_provider_access", code: "E_DISCOVERY_PROVIDER_ACCESS" };
|
|
469
|
+
default: return { status: "invalid", code: "E_DISCOVERY_VALIDATION" };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function flowOutcome(flowStatus, errors, targetId, coverage, nextActions, json) {
|
|
473
|
+
if (flowStatus === "recorded" || flowStatus === "listed") {
|
|
474
|
+
return cliOutcome(cliResult("ok", { study_id: targetId, coverage, next_actions: nextActions }), json);
|
|
475
|
+
}
|
|
476
|
+
const first = errors[0];
|
|
477
|
+
const mapped = flowToCommandStatus(first?.code ?? "provider_failure");
|
|
478
|
+
return cliOutcome(cliResult(mapped.status, {
|
|
479
|
+
study_id: targetId,
|
|
480
|
+
coverage: { ...coverage, provider_error: first?.code },
|
|
481
|
+
errors: [{ code: mapped.code, message: first?.message ?? "the approved-docs flow stopped without writing anything" }],
|
|
482
|
+
...(mapped.status === "needs_review" ? { handoff: "A person should review the model output refusal; nothing was written." } : {}),
|
|
483
|
+
}), json);
|
|
484
|
+
}
|
|
485
|
+
async function listCommand(root, target, json, deps) {
|
|
486
|
+
const packetDir = resolvePacketDir(root, target);
|
|
487
|
+
if (!packetDir)
|
|
488
|
+
return cliInvalid("Usage: foo discovery notion list <packet-name-or-path> (a spec packet with an approved Notion scope)", json, "E_DISCOVERY_VALIDATION", target);
|
|
489
|
+
const scopes = approvedNotionScopes(packetDir);
|
|
490
|
+
if (scopes.length === 0) {
|
|
491
|
+
return cliOutcome(cliResult("needs_source_approval", {
|
|
492
|
+
study_id: target,
|
|
493
|
+
errors: [{ code: "E_DISCOVERY_SOURCE_APPROVAL", message: "No approved Notion scope is recorded for this packet; nothing was read." }],
|
|
494
|
+
handoff: "Approve a bounded scope first: foo source approve <packet> --provider notion --connection <alias> --scope '{\"page_ids\":[…],\"database_ids\":[…],\"window\":{…}}' --by <person>",
|
|
495
|
+
}), json);
|
|
496
|
+
}
|
|
497
|
+
const listed = [];
|
|
498
|
+
const perDatabase = [];
|
|
499
|
+
for (const approved of scopes) {
|
|
500
|
+
for (const databaseId of approved.scope.database_ids ?? []) {
|
|
501
|
+
const result = await listNotionDatabaseSelection({
|
|
502
|
+
packet_dir: packetDir,
|
|
503
|
+
local_root: localRoot(root),
|
|
504
|
+
target_id: `docs_notion_${approved.connection_id}`,
|
|
505
|
+
connection_alias: approved.connection_id,
|
|
506
|
+
selection: { database_id: databaseId },
|
|
507
|
+
now: deps.now(),
|
|
508
|
+
env: deps.env,
|
|
509
|
+
}, { fetchImpl: deps.fetchImpl });
|
|
510
|
+
if (result.status === "listed")
|
|
511
|
+
listed.push(...result.records.map((record) => ({ page_id: record.page_id, title: record.title, last_edited: record.last_edited, stable_url: record.stable_url, parent_container: record.parent_container })));
|
|
512
|
+
perDatabase.push({ database: result.status === "listed" ? "listed" : result.status, records: result.status === "listed" ? result.records.length : 0, errors: result.errors });
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const failed = perDatabase.filter((entry) => entry.database !== "listed");
|
|
516
|
+
if (failed.length > 0 && listed.length === 0) {
|
|
517
|
+
const first = failed[0].errors;
|
|
518
|
+
return flowOutcome("failed", first, target ?? "", { mode: MODE, support_state: SUPPORT_STATE, databases: perDatabase }, [], json);
|
|
519
|
+
}
|
|
520
|
+
return cliOutcome(cliResult("ok", {
|
|
521
|
+
study_id: target,
|
|
522
|
+
coverage: { mode: MODE, support_state: SUPPORT_STATE, content_reads: 0, records: listed.length, records_listed: listed, databases: perDatabase },
|
|
523
|
+
next_actions: [`Run foo discovery notion process ${target} --page <page-id> to open one approved page under a one-time proof.`],
|
|
524
|
+
}), json);
|
|
525
|
+
}
|
|
526
|
+
async function processCommand(root, target, args, json, deps) {
|
|
527
|
+
const packetDir = resolvePacketDir(root, target);
|
|
528
|
+
if (!packetDir)
|
|
529
|
+
return cliInvalid("Usage: foo discovery notion process <packet-name-or-path> --page <page-id>", json, "E_DISCOVERY_VALIDATION", target);
|
|
530
|
+
const pageId = hasFlag(args, "--page") ? args[args.indexOf("--page") + 1] : undefined;
|
|
531
|
+
if (!pageId || pageId.startsWith("--"))
|
|
532
|
+
return cliInvalid("One explicit --page <page-id> is required: the docs flow opens exactly the approved page you name, never a wider read.", json, "E_DISCOVERY_VALIDATION", target);
|
|
533
|
+
const scopes = approvedNotionScopes(packetDir);
|
|
534
|
+
const connectionAlias = scopes[0]?.connection_id || "workspace";
|
|
535
|
+
const flow = await processNotionSelection({
|
|
536
|
+
packet_dir: packetDir,
|
|
537
|
+
local_root: localRoot(root),
|
|
538
|
+
target_id: `docs_notion_${connectionAlias}`,
|
|
539
|
+
connection_alias: connectionAlias,
|
|
540
|
+
selection: { page_id: pageId },
|
|
541
|
+
now: deps.now(),
|
|
542
|
+
env: deps.env,
|
|
543
|
+
transport: deps.transport,
|
|
544
|
+
}, { fetchImpl: deps.fetchImpl });
|
|
545
|
+
return flowOutcome(flow.status, flow.errors, target ?? "", {
|
|
546
|
+
mode: MODE,
|
|
547
|
+
support_state: SUPPORT_STATE,
|
|
548
|
+
policy_version: NOTION_POLICY.policy_version,
|
|
549
|
+
content_reads: flow.status === "recorded" ? 1 : 0,
|
|
550
|
+
model_requests: flow.status === "recorded" ? 1 : 0,
|
|
551
|
+
evidence_class: flow.summary?.evidence_class,
|
|
552
|
+
}, flow.status === "recorded" ? ["The summary-only record is in the packet's local integration-events.jsonl; the raw page was never written."] : [], json);
|
|
553
|
+
}
|
|
554
|
+
export async function runNotionDocsDiscovery(root, args, deps = {}) {
|
|
555
|
+
const json = hasFlag(args, "--json");
|
|
556
|
+
const env = deps.env ?? process.env;
|
|
557
|
+
const resolved = {
|
|
558
|
+
fetchImpl: deps.fetchImpl ?? globalThis.fetch,
|
|
559
|
+
env,
|
|
560
|
+
now: deps.now ?? (() => new Date().toISOString()),
|
|
561
|
+
transport: deps.transport ?? ((request) => { throw new Error("no synthesis transport configured"); }),
|
|
562
|
+
};
|
|
563
|
+
const command = args[0];
|
|
564
|
+
const target = args[1] && !args[1].startsWith("--") ? args[1] : undefined;
|
|
565
|
+
try {
|
|
566
|
+
if (command === "ready")
|
|
567
|
+
return await readyCommand(root, json, resolved);
|
|
568
|
+
if (command === "list")
|
|
569
|
+
return await listCommand(root, target, json, resolved);
|
|
570
|
+
if (command === "process")
|
|
571
|
+
return await processCommand(root, target, args, json, resolved);
|
|
572
|
+
return cliInvalid("Try: ready | list <packet-name-or-path> | process <packet-name-or-path> --page <page-id>", json);
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
return cliInvalid("Notion discovery failed closed without exposing private source content.", json);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type DiscoveryLane = "study_linked" | "unassigned_call_evidence";
|
|
2
|
+
export type DiscoveryConfidence = "low" | "medium" | "high";
|
|
3
|
+
export type DiscoveryRelationship = "supports" | "contradicts" | "new";
|
|
4
|
+
export type DiscoveryEvidenceFinding = {
|
|
5
|
+
finding_id: string;
|
|
6
|
+
headline: string;
|
|
7
|
+
paraphrase: string;
|
|
8
|
+
evidence_type: "stated" | "observed" | "inferred";
|
|
9
|
+
locator?: string;
|
|
10
|
+
confidence: DiscoveryConfidence;
|
|
11
|
+
dissent: string;
|
|
12
|
+
hypothesis_id?: string;
|
|
13
|
+
explicit_refs?: string[];
|
|
14
|
+
relationship_hint?: DiscoveryRelationship;
|
|
15
|
+
};
|
|
16
|
+
export type DiscoveryContactCandidate = {
|
|
17
|
+
name?: string;
|
|
18
|
+
company?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
};
|
|
21
|
+
export type DiscoveryEvidenceEnvelope = {
|
|
22
|
+
schema_version: "0.1";
|
|
23
|
+
kind: "discovery_evidence";
|
|
24
|
+
study_id: string;
|
|
25
|
+
lane: DiscoveryLane;
|
|
26
|
+
source: {
|
|
27
|
+
provider: string;
|
|
28
|
+
stable_url?: string;
|
|
29
|
+
source_fingerprint: string;
|
|
30
|
+
completed_at: string;
|
|
31
|
+
locator_capability: "timestamp" | "section" | "none";
|
|
32
|
+
};
|
|
33
|
+
findings: DiscoveryEvidenceFinding[];
|
|
34
|
+
contact_candidates?: DiscoveryContactCandidate[];
|
|
35
|
+
coverage: {
|
|
36
|
+
transcript_complete: boolean;
|
|
37
|
+
caveats: string[];
|
|
38
|
+
};
|
|
39
|
+
privacy_attestation: {
|
|
40
|
+
pii_redacted: true;
|
|
41
|
+
direct_quotes: false;
|
|
42
|
+
};
|
|
43
|
+
raw_content_present: false;
|
|
44
|
+
};
|
|
45
|
+
export type EvidenceContext = {
|
|
46
|
+
expected_study_id: string;
|
|
47
|
+
expected_lane: DiscoveryLane;
|
|
48
|
+
};
|
|
49
|
+
export declare const LOCAL_SOURCE_FINGERPRINT: RegExp;
|
|
50
|
+
export declare const HOSTED_SOURCE_FINGERPRINT: RegExp;
|
|
51
|
+
export declare function isValidDiscoverySourceFingerprint(value: string): boolean;
|
|
52
|
+
export declare const BARE_DISCOVERY_REF: RegExp;
|
|
53
|
+
export declare const PACKET_QUALIFIED_REF: RegExp;
|
|
54
|
+
export declare function parseDiscoveryQualifiedRef(value: string): {
|
|
55
|
+
packet_slug: string;
|
|
56
|
+
record_id: string;
|
|
57
|
+
} | undefined;
|
|
58
|
+
export declare function validateDiscoveryEvidence(value: unknown, context: EvidenceContext): DiscoveryEvidenceEnvelope;
|
|
59
|
+
export declare function screenDiscoveryServiceEnvelope(value: unknown, context: EvidenceContext): DiscoveryEvidenceEnvelope;
|