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,308 @@
|
|
|
1
|
+
// Gong through Nango (gong-discovery-connector-v0, local worker mode).
|
|
2
|
+
//
|
|
3
|
+
// Nango is the credential broker only (Granola packet D-007, carried by this
|
|
4
|
+
// packet's D-003/D-010): productfoo asks Nango for the company's Gong credential and
|
|
5
|
+
// then talks to Gong's REST API directly, so transcript bytes never transit
|
|
6
|
+
// Nango. Everything here is content-free on every surface productfoo owns: credentials
|
|
7
|
+
// are held in process memory for one command, the Nango connection id is never
|
|
8
|
+
// printed (an opaque `connection_ref` stands in for it), and errors carry a
|
|
9
|
+
// typed code plus a bounded message that never embeds a provider response.
|
|
10
|
+
//
|
|
11
|
+
// Gong API shapes below are beliefs owned by the packet's feasibility task
|
|
12
|
+
// (T-001) and pinned by fixtures in test/discoveryGongRuntime.test.ts; the
|
|
13
|
+
// live dogfood run (T-009) is what promotes them from belief to verified.
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
import { refuseSourceWrite } from "./sourceIngress.js";
|
|
16
|
+
export class GongRuntimeError extends Error {
|
|
17
|
+
code;
|
|
18
|
+
constructor(code, message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.name = "GongRuntimeError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Versioned policy fields (Granola packet R-005): hard ceilings the worker
|
|
25
|
+
// enforces and reports, so a later change is explicit rather than silent.
|
|
26
|
+
export const GONG_POLICY = {
|
|
27
|
+
policy_version: "gong-local-worker-0.1",
|
|
28
|
+
max_response_bytes: 8 * 1024 * 1024,
|
|
29
|
+
fetch_timeout_ms: 90_000,
|
|
30
|
+
max_model_input_tokens: 100_000,
|
|
31
|
+
max_list_pages: 5,
|
|
32
|
+
min_request_interval_ms: 350,
|
|
33
|
+
};
|
|
34
|
+
const GONG_TRANSCRIPT_SCOPE = "api:calls:read:transcript";
|
|
35
|
+
const DEFAULT_NANGO_HOST = "https://api.nango.dev";
|
|
36
|
+
const DEFAULT_GONG_BASE = "https://api.gong.io";
|
|
37
|
+
function nangoConfigured(env) {
|
|
38
|
+
return Boolean(env.NANGO_SECRET_KEY?.trim() && env.FOO_GONG_NANGO_CONNECTION?.trim());
|
|
39
|
+
}
|
|
40
|
+
function directConfigured(env) {
|
|
41
|
+
return Boolean(env.FOO_GONG_ACCESS_KEY?.trim() && env.FOO_GONG_ACCESS_KEY_SECRET?.trim());
|
|
42
|
+
}
|
|
43
|
+
export function gongConnectionConfigured(env) {
|
|
44
|
+
return nangoConfigured(env) || directConfigured(env);
|
|
45
|
+
}
|
|
46
|
+
export function gongCredentialBroker(env) {
|
|
47
|
+
if (nangoConfigured(env))
|
|
48
|
+
return "nango";
|
|
49
|
+
if (directConfigured(env))
|
|
50
|
+
return "direct";
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function opaqueConnectionRef(integration, connectionId) {
|
|
54
|
+
return `gong:${createHash("sha256").update(`${integration}\n${connectionId}`).digest("hex").slice(0, 16)}`;
|
|
55
|
+
}
|
|
56
|
+
async function fetchWithTimeout(fetchImpl, url, init, timeoutMs) {
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
59
|
+
try {
|
|
60
|
+
return await fetchImpl(url, { ...init, signal: controller.signal });
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (controller.signal.aborted)
|
|
64
|
+
throw new GongRuntimeError("provider_timeout", "the provider did not answer within the fetch ceiling");
|
|
65
|
+
throw new GongRuntimeError("network", `network failure reaching ${new URL(url).hostname}`);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Reads a response body while counting toward the byte ceiling, so an
|
|
72
|
+
// oversized transcript is dropped before it is ever materialized in full.
|
|
73
|
+
async function readBounded(response, maxBytes) {
|
|
74
|
+
const declared = Number(response.headers.get("content-length") ?? "0");
|
|
75
|
+
if (Number.isFinite(declared) && declared > maxBytes)
|
|
76
|
+
throw new GongRuntimeError("transcript_too_large", "the provider response exceeds the byte ceiling");
|
|
77
|
+
if (!response.body) {
|
|
78
|
+
const buffer = await response.arrayBuffer();
|
|
79
|
+
if (buffer.byteLength > maxBytes)
|
|
80
|
+
throw new GongRuntimeError("transcript_too_large", "the provider response exceeds the byte ceiling");
|
|
81
|
+
return new TextDecoder().decode(buffer);
|
|
82
|
+
}
|
|
83
|
+
const reader = response.body.getReader();
|
|
84
|
+
const chunks = [];
|
|
85
|
+
let total = 0;
|
|
86
|
+
for (;;) {
|
|
87
|
+
const { done, value } = await reader.read();
|
|
88
|
+
if (done)
|
|
89
|
+
break;
|
|
90
|
+
total += value.byteLength;
|
|
91
|
+
if (total > maxBytes) {
|
|
92
|
+
await reader.cancel().catch(() => undefined);
|
|
93
|
+
chunks.length = 0;
|
|
94
|
+
throw new GongRuntimeError("transcript_too_large", "the provider response exceeds the byte ceiling");
|
|
95
|
+
}
|
|
96
|
+
chunks.push(value);
|
|
97
|
+
}
|
|
98
|
+
return new TextDecoder().decode(Buffer.concat(chunks));
|
|
99
|
+
}
|
|
100
|
+
function parseJson(text, label) {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(text);
|
|
103
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
104
|
+
throw new Error("not an object");
|
|
105
|
+
return parsed;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
throw new GongRuntimeError("provider_failure", `${label} returned a response productfoo could not read`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the company's Gong credential. Nango is the primary broker: it holds
|
|
113
|
+
* the credential, refreshes it, and is where an administrator revokes it. When
|
|
114
|
+
* Nango is not configured, an administrator-issued access key and secret set
|
|
115
|
+
* directly in this runtime's environment (FOO_GONG_ACCESS_KEY /
|
|
116
|
+
* FOO_GONG_ACCESS_KEY_SECRET) is accepted as the direct fallback — the same
|
|
117
|
+
* company credential, minus the central refresh and revoke story. Either way
|
|
118
|
+
* productfoo receives it for one command and never persists or prints it.
|
|
119
|
+
*/
|
|
120
|
+
export async function resolveGongConnection(input) {
|
|
121
|
+
const broker = gongCredentialBroker(input.env);
|
|
122
|
+
if (broker === null) {
|
|
123
|
+
throw new GongRuntimeError("not_connected", "Gong is not connected: set NANGO_SECRET_KEY and FOO_GONG_NANGO_CONNECTION for the company connection through Nango, or FOO_GONG_ACCESS_KEY and FOO_GONG_ACCESS_KEY_SECRET for a direct administrator-issued access key");
|
|
124
|
+
}
|
|
125
|
+
if (broker === "direct") {
|
|
126
|
+
const username = input.env.FOO_GONG_ACCESS_KEY.trim();
|
|
127
|
+
const password = input.env.FOO_GONG_ACCESS_KEY_SECRET.trim();
|
|
128
|
+
return {
|
|
129
|
+
connection_ref: `gong:direct:${createHash("sha256").update(username).digest("hex").slice(0, 16)}`,
|
|
130
|
+
broker: "direct",
|
|
131
|
+
credential: { type: "basic", username, password },
|
|
132
|
+
credential_type: "basic",
|
|
133
|
+
transcript_scope: "granted",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const secret = input.env.NANGO_SECRET_KEY.trim();
|
|
137
|
+
const connectionId = input.env.FOO_GONG_NANGO_CONNECTION.trim();
|
|
138
|
+
const integration = input.env.FOO_GONG_NANGO_INTEGRATION?.trim() || "gong";
|
|
139
|
+
const host = (input.env.NANGO_HOST?.trim() || DEFAULT_NANGO_HOST).replace(/\/+$/, "");
|
|
140
|
+
const url = `${host}/connection/${encodeURIComponent(connectionId)}?provider_config_key=${encodeURIComponent(integration)}`;
|
|
141
|
+
const response = await fetchWithTimeout(input.fetchImpl, url, { method: "GET", headers: { authorization: `Bearer ${secret}`, accept: "application/json" } }, GONG_POLICY.fetch_timeout_ms);
|
|
142
|
+
if (response.status === 401 || response.status === 403)
|
|
143
|
+
throw new GongRuntimeError("not_connected", "Nango refused the secret key; reconnect Gong");
|
|
144
|
+
if (response.status === 404)
|
|
145
|
+
throw new GongRuntimeError("not_connected", "Nango has no Gong connection with that reference; connect Gong first");
|
|
146
|
+
if (!response.ok)
|
|
147
|
+
throw new GongRuntimeError("provider_failure", `Nango answered ${response.status} while resolving the connection`);
|
|
148
|
+
const body = parseJson(await readBounded(response, 256 * 1024), "Nango");
|
|
149
|
+
const credentials = (body.credentials ?? {});
|
|
150
|
+
const type = String(credentials.type ?? "").toUpperCase();
|
|
151
|
+
const ref = opaqueConnectionRef(integration, connectionId);
|
|
152
|
+
if (type === "BASIC") {
|
|
153
|
+
const username = String(credentials.username ?? "");
|
|
154
|
+
const password = String(credentials.password ?? "");
|
|
155
|
+
if (!username || !password)
|
|
156
|
+
throw new GongRuntimeError("not_connected", "the Nango connection carries no usable Gong access key");
|
|
157
|
+
return { connection_ref: ref, broker: "nango", credential: { type: "basic", username, password }, credential_type: "basic", transcript_scope: "granted" };
|
|
158
|
+
}
|
|
159
|
+
if (type === "OAUTH2") {
|
|
160
|
+
const token = String(credentials.access_token ?? "");
|
|
161
|
+
if (!token)
|
|
162
|
+
throw new GongRuntimeError("auth_expired", "the Nango connection carries no usable Gong access token; refresh or reconnect");
|
|
163
|
+
const raw = (credentials.raw ?? {});
|
|
164
|
+
const scopeValue = typeof raw.scope === "string" ? raw.scope : typeof credentials.scope === "string" ? credentials.scope : "";
|
|
165
|
+
const scopes = scopeValue.split(/[\s,]+/).filter(Boolean);
|
|
166
|
+
const transcriptScope = scopes.length === 0 ? "unknown" : scopes.includes(GONG_TRANSCRIPT_SCOPE) ? "granted" : "missing";
|
|
167
|
+
return { connection_ref: ref, broker: "nango", credential: { type: "oauth2", access_token: token }, credential_type: "oauth2", transcript_scope: transcriptScope };
|
|
168
|
+
}
|
|
169
|
+
throw new GongRuntimeError("not_connected", "the Nango connection uses a credential type productfoo does not support for Gong");
|
|
170
|
+
}
|
|
171
|
+
const READ_ONLY_POSTS = new Set(["/v2/calls/extensive", "/v2/calls/transcript"]);
|
|
172
|
+
/**
|
|
173
|
+
* Minimal read-only Gong REST client. Only documented read operations are
|
|
174
|
+
* reachable; any other method or path routes through the single write-refusal
|
|
175
|
+
* choke point (R-009) so the refusal is testable rather than scattered.
|
|
176
|
+
*/
|
|
177
|
+
export class GongClient {
|
|
178
|
+
credential;
|
|
179
|
+
options;
|
|
180
|
+
requests = { list: 0, extensive: 0, transcript: 0 };
|
|
181
|
+
lastRequestAt = 0;
|
|
182
|
+
baseUrl;
|
|
183
|
+
sleep;
|
|
184
|
+
now;
|
|
185
|
+
fetchTimeoutMs;
|
|
186
|
+
constructor(credential, options) {
|
|
187
|
+
this.credential = credential;
|
|
188
|
+
this.options = options;
|
|
189
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_GONG_BASE).replace(/\/+$/, "");
|
|
190
|
+
this.sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
191
|
+
this.now = options.now ?? (() => Date.now());
|
|
192
|
+
this.fetchTimeoutMs = options.fetchTimeoutMs ?? GONG_POLICY.fetch_timeout_ms;
|
|
193
|
+
}
|
|
194
|
+
authorization() {
|
|
195
|
+
if (this.credential.type === "basic")
|
|
196
|
+
return `Basic ${Buffer.from(`${this.credential.username}:${this.credential.password}`).toString("base64")}`;
|
|
197
|
+
return `Bearer ${this.credential.access_token}`;
|
|
198
|
+
}
|
|
199
|
+
async request(method, pathname, body, maxBytes = GONG_POLICY.max_response_bytes) {
|
|
200
|
+
const readOnly = method === "GET" || (method === "POST" && READ_ONLY_POSTS.has(pathname.split("?")[0]));
|
|
201
|
+
if (!readOnly)
|
|
202
|
+
refuseSourceWrite(`gong ${method} ${pathname}`);
|
|
203
|
+
const wait = this.lastRequestAt + GONG_POLICY.min_request_interval_ms - this.now();
|
|
204
|
+
if (wait > 0)
|
|
205
|
+
await this.sleep(wait);
|
|
206
|
+
this.lastRequestAt = this.now();
|
|
207
|
+
const response = await fetchWithTimeout(this.options.fetchImpl, `${this.baseUrl}${pathname}`, {
|
|
208
|
+
method,
|
|
209
|
+
headers: { authorization: this.authorization(), accept: "application/json", ...(body !== undefined ? { "content-type": "application/json" } : {}) },
|
|
210
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
211
|
+
}, this.fetchTimeoutMs);
|
|
212
|
+
if (response.status === 401)
|
|
213
|
+
throw new GongRuntimeError("auth_expired", "Gong rejected the credential; refresh or reconnect");
|
|
214
|
+
if (response.status === 403)
|
|
215
|
+
throw new GongRuntimeError("access_denied", "Gong denied this read; the credential lacks the required permission");
|
|
216
|
+
if (response.status === 429)
|
|
217
|
+
throw new GongRuntimeError("budget_exhausted", "Gong rate limit reached; no retry was attempted");
|
|
218
|
+
if (response.status === 404)
|
|
219
|
+
throw new GongRuntimeError("provider_failure", "Gong reported the requested record does not exist");
|
|
220
|
+
if (!response.ok)
|
|
221
|
+
throw new GongRuntimeError("provider_failure", `Gong answered ${response.status}`);
|
|
222
|
+
return parseJson(await readBounded(response, maxBytes), "Gong");
|
|
223
|
+
}
|
|
224
|
+
/** Bounded metadata listing: one time window, a page ceiling, no content. */
|
|
225
|
+
async listCalls(window) {
|
|
226
|
+
const calls = [];
|
|
227
|
+
let cursor;
|
|
228
|
+
let pages = 0;
|
|
229
|
+
for (;;) {
|
|
230
|
+
if (pages >= GONG_POLICY.max_list_pages)
|
|
231
|
+
return { calls, pages, truncated: true };
|
|
232
|
+
const params = new URLSearchParams({ fromDateTime: window.from, toDateTime: window.to });
|
|
233
|
+
if (window.workspaceId)
|
|
234
|
+
params.set("workspaceId", window.workspaceId);
|
|
235
|
+
if (cursor)
|
|
236
|
+
params.set("cursor", cursor);
|
|
237
|
+
this.requests.list += 1;
|
|
238
|
+
const body = await this.request("GET", `/v2/calls?${params.toString()}`, undefined, 4 * 1024 * 1024);
|
|
239
|
+
pages += 1;
|
|
240
|
+
for (const entry of Array.isArray(body.calls) ? body.calls : []) {
|
|
241
|
+
if (!entry || typeof entry.id !== "string")
|
|
242
|
+
continue;
|
|
243
|
+
calls.push({
|
|
244
|
+
id: entry.id,
|
|
245
|
+
title: typeof entry.title === "string" ? entry.title : undefined,
|
|
246
|
+
url: typeof entry.url === "string" ? entry.url : undefined,
|
|
247
|
+
scheduled: typeof entry.scheduled === "string" ? entry.scheduled : undefined,
|
|
248
|
+
started: typeof entry.started === "string" ? entry.started : undefined,
|
|
249
|
+
duration: typeof entry.duration === "number" ? entry.duration : undefined,
|
|
250
|
+
workspaceId: typeof entry.workspaceId === "string" ? entry.workspaceId : undefined,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const records = (body.records ?? {});
|
|
254
|
+
cursor = typeof records.cursor === "string" && records.cursor ? records.cursor : undefined;
|
|
255
|
+
if (!cursor)
|
|
256
|
+
return { calls, pages, truncated: false };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/** Party metadata for the given calls (names, titles, affiliation, speaker ids) — no content fields are requested. */
|
|
260
|
+
async parties(callIds) {
|
|
261
|
+
const out = new Map();
|
|
262
|
+
for (let index = 0; index < callIds.length; index += 100) {
|
|
263
|
+
const batch = callIds.slice(index, index + 100);
|
|
264
|
+
this.requests.extensive += 1;
|
|
265
|
+
const body = await this.request("POST", "/v2/calls/extensive", {
|
|
266
|
+
filter: { callIds: batch },
|
|
267
|
+
contentSelector: { exposedFields: { parties: true } },
|
|
268
|
+
}, 4 * 1024 * 1024);
|
|
269
|
+
for (const call of Array.isArray(body.calls) ? body.calls : []) {
|
|
270
|
+
const meta = (call.metaData ?? {});
|
|
271
|
+
const id = typeof meta.id === "string" ? meta.id : undefined;
|
|
272
|
+
if (!id)
|
|
273
|
+
continue;
|
|
274
|
+
const parties = Array.isArray(call.parties) ? call.parties : [];
|
|
275
|
+
out.set(id, parties.map((party) => ({
|
|
276
|
+
name: typeof party.name === "string" ? party.name : undefined,
|
|
277
|
+
emailAddress: typeof party.emailAddress === "string" ? party.emailAddress : undefined,
|
|
278
|
+
title: typeof party.title === "string" ? party.title : undefined,
|
|
279
|
+
affiliation: typeof party.affiliation === "string" ? party.affiliation : undefined,
|
|
280
|
+
speakerId: typeof party.speakerId === "string" ? party.speakerId : undefined,
|
|
281
|
+
})));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Exactly one call's transcript (R-007). Gong's transcript operation is
|
|
288
|
+
* batch-capable; this method refuses to ask for more than one identifier and
|
|
289
|
+
* a fixture observes that no request ever carries more.
|
|
290
|
+
*/
|
|
291
|
+
async transcript(callId) {
|
|
292
|
+
if (!callId || /\s/.test(callId))
|
|
293
|
+
throw new GongRuntimeError("provider_failure", "a single well-formed call identifier is required");
|
|
294
|
+
this.requests.transcript += 1;
|
|
295
|
+
const body = await this.request("POST", "/v2/calls/transcript", { filter: { callIds: [callId] } });
|
|
296
|
+
const transcripts = Array.isArray(body.callTranscripts) ? body.callTranscripts : [];
|
|
297
|
+
const own = transcripts.find((entry) => entry.callId === callId);
|
|
298
|
+
if (!own || !Array.isArray(own.transcript))
|
|
299
|
+
throw new GongRuntimeError("access_denied", "Gong returned no transcript for the selected call");
|
|
300
|
+
return own.transcript.map((monologue) => ({
|
|
301
|
+
speakerId: typeof monologue.speakerId === "string" ? monologue.speakerId : undefined,
|
|
302
|
+
topic: typeof monologue.topic === "string" ? monologue.topic : undefined,
|
|
303
|
+
sentences: (Array.isArray(monologue.sentences) ? monologue.sentences : [])
|
|
304
|
+
.filter((sentence) => typeof sentence.text === "string")
|
|
305
|
+
.map((sentence) => ({ start: typeof sentence.start === "number" ? sentence.start : undefined, end: typeof sentence.end === "number" ? sentence.end : undefined, text: sentence.text })),
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type GrammarFinding } from "./specSections.js";
|
|
2
|
+
export type StagedSpec = {
|
|
3
|
+
file: string;
|
|
4
|
+
text: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function decideGrammarGate(staged: StagedSpec[]): Array<{
|
|
7
|
+
file: string;
|
|
8
|
+
finding: GrammarFinding;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function runGrammarGate(root: string): {
|
|
11
|
+
output: string;
|
|
12
|
+
exitCode: number;
|
|
13
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// packet-grammar-v1 R-005: the commit-time grammar gate, a B2 spec-identity
|
|
2
|
+
// extension. Blocks a STAGED spec.md whose anchors are duplicated or
|
|
3
|
+
// malformed; everything else the lint can say (missing IDs, unparsed
|
|
4
|
+
// candidates) stays advisory in `asp validate` and never gates.
|
|
5
|
+
//
|
|
6
|
+
// Fail direction copies the redact gate's split (guard-redaction.sh): any
|
|
7
|
+
// plumbing failure — git missing, blob unreadable, a bug in the linter —
|
|
8
|
+
// exits 0 with a note, because a broken gate must not brick every commit
|
|
9
|
+
// (V-006); the detection decision itself is fail-closed. It lints staged
|
|
10
|
+
// blobs (`git show :<path>`), never the working tree, so half-staged edits
|
|
11
|
+
// are judged only on what is actually being committed.
|
|
12
|
+
//
|
|
13
|
+
// One channel, said plainly: a pre-commit terminal has no separate agent/user
|
|
14
|
+
// surface, so the denial carries both halves — one calm line of what
|
|
15
|
+
// happened, then the exact fix and the override.
|
|
16
|
+
import { gitRun, gitRunBuffer } from "./gitRun.js";
|
|
17
|
+
import { lintAnchors } from "./specSections.js";
|
|
18
|
+
const BLOCKING = new Set(["grammar.duplicate_id", "grammar.malformed_id"]);
|
|
19
|
+
// Pure decision, side-effect-free: which staged specs carry blocking findings.
|
|
20
|
+
export function decideGrammarGate(staged) {
|
|
21
|
+
const blocked = [];
|
|
22
|
+
for (const s of staged) {
|
|
23
|
+
for (const finding of lintAnchors(s.text)) {
|
|
24
|
+
if (BLOCKING.has(finding.code))
|
|
25
|
+
blocked.push({ file: s.file, finding });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return blocked;
|
|
29
|
+
}
|
|
30
|
+
const SPEC_PATH_RE = /^specs\/[^/]+\/spec\.md$/;
|
|
31
|
+
export function runGrammarGate(root) {
|
|
32
|
+
try {
|
|
33
|
+
if (process.env.FOO_ALLOW_GRAMMAR === "1") {
|
|
34
|
+
return { output: "foo grammar gate — OVERRIDE: FOO_ALLOW_GRAMMAR=1, staged spec.md anchors not checked.\n", exitCode: 0 };
|
|
35
|
+
}
|
|
36
|
+
const listed = gitRun(["diff", "--cached", "--name-only", "--diff-filter=ACM", "-z"], { cwd: root });
|
|
37
|
+
if (!listed.ok) {
|
|
38
|
+
return { output: "foo grammar gate — skipped: git unavailable, cannot read staged content (fail-open).\n", exitCode: 0 };
|
|
39
|
+
}
|
|
40
|
+
const names = listed.stdout.split("\0").filter((n) => SPEC_PATH_RE.test(n));
|
|
41
|
+
if (names.length === 0)
|
|
42
|
+
return { output: "", exitCode: 0 };
|
|
43
|
+
const staged = [];
|
|
44
|
+
for (const file of names) {
|
|
45
|
+
const blob = gitRunBuffer(["show", `:${file}`], { cwd: root, maxBuffer: 16 * 1024 * 1024 });
|
|
46
|
+
if (!blob.ok)
|
|
47
|
+
continue; // unreadable staged blob — fail-open, skip
|
|
48
|
+
staged.push({ file, text: blob.stdout.toString("utf8") });
|
|
49
|
+
}
|
|
50
|
+
const blocked = decideGrammarGate(staged);
|
|
51
|
+
if (blocked.length === 0) {
|
|
52
|
+
return { output: `foo grammar gate — ok: ${staged.length} staged spec.md file(s) well-anchored.\n`, exitCode: 0 };
|
|
53
|
+
}
|
|
54
|
+
const byPacket = new Set(blocked.map((b) => b.file.split("/").slice(0, 2).join("/")));
|
|
55
|
+
const lines = [
|
|
56
|
+
"foo grammar gate — BLOCKED: a staged spec has ambiguous anchors (B2 spec identity).",
|
|
57
|
+
"",
|
|
58
|
+
...blocked.map((b) => ` ${b.file}:${b.finding.line} ${b.finding.code} — ${b.finding.detail}`),
|
|
59
|
+
"",
|
|
60
|
+
"Canonical form: `### R-001 Title` headings, `- V-001 — text` bullets.",
|
|
61
|
+
...[...byPacket].map((p) => `Fix (one paste): foo asp migrate ${p} — or edit the line named above.`),
|
|
62
|
+
'Override (deliberate): FOO_ALLOW_GRAMMAR=1 git commit ...',
|
|
63
|
+
"",
|
|
64
|
+
];
|
|
65
|
+
return { output: lines.join("\n"), exitCode: 1 };
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// A bug in the linter must not brick every spec commit (R-005, V-006).
|
|
69
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
70
|
+
return { output: `foo grammar gate — skipped: internal error (fail-open): ${msg}\n`, exitCode: 0 };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export type HarnessGateFormat = "claude" | "cursor" | "codex" | "opencode" | "grok";
|
|
2
|
+
/** What the gate can actually intercept in a given harness. Reported honestly, never rounded up. */
|
|
3
|
+
export interface GateCoverage {
|
|
4
|
+
/** Can we refuse a subagent fan-out? */
|
|
5
|
+
readonly fanOut: boolean;
|
|
6
|
+
/** Can we refuse a direct web read? (The behavioural trip needs this.) */
|
|
7
|
+
readonly webRead: boolean;
|
|
8
|
+
/** Does a foo crash DENY the user's tool call instead of allowing it? */
|
|
9
|
+
readonly failsClosed: boolean;
|
|
10
|
+
/** Does the harness refuse to run a hook it has not been told to trust? */
|
|
11
|
+
readonly needsTrust: boolean;
|
|
12
|
+
readonly note?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface HarnessGate {
|
|
15
|
+
readonly format: HarnessGateFormat;
|
|
16
|
+
readonly label: string;
|
|
17
|
+
/** Where the gate config belongs, relative to the repo. */
|
|
18
|
+
readonly configPath: string;
|
|
19
|
+
readonly coverage: GateCoverage;
|
|
20
|
+
/** The config productfoo would write. */
|
|
21
|
+
render(shim: string): string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A denial has TWO audiences, and they are not the same reader.
|
|
25
|
+
*
|
|
26
|
+
* The `agent` text is an instruction: the command to run instead, the escape hatch, the
|
|
27
|
+
* reason the gate fired. It is productfoo's machinery, and the agent is the only one who can act
|
|
28
|
+
* on any of it. The `user` text is one calm line telling the human what just took over —
|
|
29
|
+
* because a denial is not a failure, it is foo doing its job, and it should not read like
|
|
30
|
+
* a crash in their transcript.
|
|
31
|
+
*
|
|
32
|
+
* foo used to send one string to both. Every deny therefore printed skill paths, `bin/foo`
|
|
33
|
+
* invocations and env-var overrides into the user's terminal in red, on a product whose
|
|
34
|
+
* first rule is that the user never sees the machinery. Cursor's own API had been saying so
|
|
35
|
+
* the whole time — it asks for `user_message` and `agent_message` separately, and foo handed
|
|
36
|
+
* it the same string twice.
|
|
37
|
+
*/
|
|
38
|
+
export interface DenialText {
|
|
39
|
+
/** One line, for the human. No paths, no commands, no env vars, no foo vocabulary. */
|
|
40
|
+
readonly user: string;
|
|
41
|
+
/** The instruction, for the agent. Machinery belongs here and only here. */
|
|
42
|
+
readonly agent: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Render a denial in the shape a given harness understands.
|
|
46
|
+
*
|
|
47
|
+
* Returns the stdout to print and the exit code to use. Getting either wrong is a silent
|
|
48
|
+
* allow — the harness shrugs at output it cannot parse and runs the tool anyway.
|
|
49
|
+
*
|
|
50
|
+
* Some harnesses have a second channel for the agent. Where there is
|
|
51
|
+
* no split, the agent's text is what goes out, because a deny the agent cannot act on just
|
|
52
|
+
* blocks the work with no route forward — a worse failure than a leaky message. Those gaps
|
|
53
|
+
* are named below rather than quietly rounded up: productfoo does not report a thing as clean in a
|
|
54
|
+
* harness where it isn't.
|
|
55
|
+
*/
|
|
56
|
+
export declare function renderDenial(format: HarnessGateFormat, text: DenialText): {
|
|
57
|
+
stdout: string;
|
|
58
|
+
exit: number;
|
|
59
|
+
};
|
|
60
|
+
/** An allow. Silence is the safe answer everywhere: no harness treats empty stdout as a deny. */
|
|
61
|
+
export declare const ALLOW: {
|
|
62
|
+
readonly stdout: "";
|
|
63
|
+
readonly exit: 0;
|
|
64
|
+
};
|
|
65
|
+
export declare const HARNESS_GATES: readonly HarnessGate[];
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Supported harness gate adapters share one policy with explicit wire formats.
|
|
2
|
+
// Claude Code and Codex use the same denial schema; Cursor has separate user
|
|
3
|
+
// and agent fields. OpenCode denies by throwing and must fail open on internal
|
|
4
|
+
// errors. Grok Bot coverage remains provisional. Preserve each host's actual
|
|
5
|
+
// coverage rather than claiming every hook can intercept every tool.
|
|
6
|
+
/**
|
|
7
|
+
* Render a denial in the shape a given harness understands.
|
|
8
|
+
*
|
|
9
|
+
* Returns the stdout to print and the exit code to use. Getting either wrong is a silent
|
|
10
|
+
* allow — the harness shrugs at output it cannot parse and runs the tool anyway.
|
|
11
|
+
*
|
|
12
|
+
* Some harnesses have a second channel for the agent. Where there is
|
|
13
|
+
* no split, the agent's text is what goes out, because a deny the agent cannot act on just
|
|
14
|
+
* blocks the work with no route forward — a worse failure than a leaky message. Those gaps
|
|
15
|
+
* are named below rather than quietly rounded up: productfoo does not report a thing as clean in a
|
|
16
|
+
* harness where it isn't.
|
|
17
|
+
*/
|
|
18
|
+
export function renderDenial(format, text) {
|
|
19
|
+
switch (format) {
|
|
20
|
+
case "claude":
|
|
21
|
+
case "codex":
|
|
22
|
+
// Codex deliberately mirrors Claude's schema. One shape, two harnesses.
|
|
23
|
+
// permissionDecisionReason is shown to the USER; additionalContext reaches the MODEL
|
|
24
|
+
// only, and never renders in the transcript. That is the whole split.
|
|
25
|
+
return {
|
|
26
|
+
stdout: JSON.stringify({
|
|
27
|
+
hookSpecificOutput: {
|
|
28
|
+
hookEventName: "PreToolUse",
|
|
29
|
+
permissionDecision: "deny",
|
|
30
|
+
permissionDecisionReason: text.user,
|
|
31
|
+
additionalContext: text.agent,
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
exit: 0,
|
|
35
|
+
};
|
|
36
|
+
case "cursor":
|
|
37
|
+
// Two audiences, two fields — as Cursor intended.
|
|
38
|
+
return {
|
|
39
|
+
stdout: JSON.stringify({ permission: "deny", user_message: text.user, agent_message: text.agent }),
|
|
40
|
+
exit: 0,
|
|
41
|
+
};
|
|
42
|
+
case "opencode":
|
|
43
|
+
// OpenCode has no JSON deny channel — the plugin throws. The shim prints the reason
|
|
44
|
+
// and exits 1; the generated plugin turns that into a throw.
|
|
45
|
+
return { stdout: text.agent, exit: 1 };
|
|
46
|
+
case "grok":
|
|
47
|
+
// Grok Bot wire format provisional — conservatively fail open with no message until verified.
|
|
48
|
+
// No second channel for now.
|
|
49
|
+
return { stdout: JSON.stringify({ permission: "deny", reason: text.agent }), exit: 0 };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** An allow. Silence is the safe answer everywhere: no harness treats empty stdout as a deny. */
|
|
53
|
+
export const ALLOW = { stdout: "", exit: 0 };
|
|
54
|
+
export const HARNESS_GATES = [
|
|
55
|
+
{
|
|
56
|
+
format: "claude",
|
|
57
|
+
label: "Claude Code",
|
|
58
|
+
configPath: ".claude/settings.json",
|
|
59
|
+
coverage: { fanOut: true, webRead: true, failsClosed: false, needsTrust: false },
|
|
60
|
+
render: () => "", // already wired by productfoo's existing settings.json
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
format: "cursor",
|
|
64
|
+
label: "Cursor",
|
|
65
|
+
configPath: ".cursor/hooks.json",
|
|
66
|
+
coverage: {
|
|
67
|
+
fanOut: true,
|
|
68
|
+
webRead: false,
|
|
69
|
+
failsClosed: false,
|
|
70
|
+
needsTrust: false,
|
|
71
|
+
note: "Cursor has no web-search tool to match, so the Nth-read trip cannot fire here — only the fan-out gate does. Reported, not rounded up.",
|
|
72
|
+
},
|
|
73
|
+
render: (shim) => `${JSON.stringify({
|
|
74
|
+
version: 1,
|
|
75
|
+
hooks: {
|
|
76
|
+
// Scoped to Task, NOT the unmatched default: Cursor's preToolUse fires for
|
|
77
|
+
// EVERY tool, and there is no documented default timeout. An unscoped foo hook
|
|
78
|
+
// would spawn a subprocess on every keystroke-adjacent action the agent takes.
|
|
79
|
+
subagentStart: [{ command: shim, timeout: 10, failClosed: false }],
|
|
80
|
+
preToolUse: [{ command: shim, matcher: "Task", timeout: 10, failClosed: false }],
|
|
81
|
+
},
|
|
82
|
+
}, null, 2)}\n`,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
format: "codex",
|
|
86
|
+
label: "Codex",
|
|
87
|
+
configPath: ".codex/hooks.json",
|
|
88
|
+
coverage: {
|
|
89
|
+
fanOut: true,
|
|
90
|
+
webRead: false,
|
|
91
|
+
failsClosed: false,
|
|
92
|
+
needsTrust: true,
|
|
93
|
+
note: "Codex will NOT run a hook it has not been told to trust, and productfoo cannot self-trust. This is the correct design and productfoo should copy it, not route around it.",
|
|
94
|
+
},
|
|
95
|
+
render: (shim) => `${JSON.stringify({
|
|
96
|
+
description: "foo research gate (B5) — routes web research through `foo research`",
|
|
97
|
+
hooks: {
|
|
98
|
+
PreToolUse: [
|
|
99
|
+
{ matcher: "^(spawn_agent|Agent)$", hooks: [{ type: "command", command: shim, timeout: 10 }] },
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
}, null, 2)}\n`,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
format: "opencode",
|
|
106
|
+
label: "OpenCode",
|
|
107
|
+
configPath: ".opencode/plugins/foo-research-gate.ts",
|
|
108
|
+
coverage: {
|
|
109
|
+
fanOut: true,
|
|
110
|
+
webRead: true,
|
|
111
|
+
failsClosed: true,
|
|
112
|
+
needsTrust: false,
|
|
113
|
+
note: "FAIL-CLOSED with NO timeout: an unhandled throw in productfoo's plugin kills every tool call, forever, with no escape. The plugin below swallows every error except a deliberate deny.",
|
|
114
|
+
},
|
|
115
|
+
render: (shim) => `// foo research gate (B5). Generated by \`foo agent setup --gate\`. Safe to delete.
|
|
116
|
+
//
|
|
117
|
+
// OpenCode's only deny channel is a throw, it is fail-closed, and it has NO timeout wrapper.
|
|
118
|
+
// So a BUG in foo here is indistinguishable from a foo POLICY to the user: every tool call
|
|
119
|
+
// dies with a stack trace and nothing saves them. Hence the shape below — the try/catch
|
|
120
|
+
// swallows everything except the one throw we mean.
|
|
121
|
+
import { execFileSync } from "node:child_process";
|
|
122
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
123
|
+
|
|
124
|
+
export const FooResearchGate: Plugin = async () => ({
|
|
125
|
+
"tool.execute.before": async (input: { tool: string }, output: { args: unknown }) => {
|
|
126
|
+
let denial: string | null = null;
|
|
127
|
+
try {
|
|
128
|
+
// execFileSync takes a PATH and an ARGV — it does not split a command string. Passing
|
|
129
|
+
// "script.sh opencode" as one argument looks for a file with a space in its name, throws
|
|
130
|
+
// ENOENT, and the catch below fails open: the gate would silently never enforce, while
|
|
131
|
+
// reporting success. A silent allow is the worst failure this module has, so: argv.
|
|
132
|
+
execFileSync(${JSON.stringify(shim)}, ["opencode"], {
|
|
133
|
+
input: JSON.stringify({ tool_name: input.tool, tool_input: output.args }),
|
|
134
|
+
encoding: "utf8",
|
|
135
|
+
timeout: 10_000,
|
|
136
|
+
});
|
|
137
|
+
} catch (err) {
|
|
138
|
+
// Exit 1 with a reason IS the deny. Anything else — foo missing, node missing, a bug
|
|
139
|
+
// in the gate — must fail OPEN, because a gate that blocks every tool call the moment
|
|
140
|
+
// it breaks is a gate the user rips out, and here they cannot even see why.
|
|
141
|
+
const e = err as { status?: number; stdout?: string };
|
|
142
|
+
if (e.status === 1 && e.stdout) denial = String(e.stdout);
|
|
143
|
+
}
|
|
144
|
+
if (denial) throw new Error(denial);
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
`,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
format: "grok",
|
|
151
|
+
label: "Grok Bot",
|
|
152
|
+
configPath: ".grok/hooks.json",
|
|
153
|
+
coverage: {
|
|
154
|
+
fanOut: false,
|
|
155
|
+
webRead: false,
|
|
156
|
+
failsClosed: false,
|
|
157
|
+
needsTrust: false,
|
|
158
|
+
note: "Grok Bot gate coverage unverified — wire format and hook surface are provisional pending x.ai platform docs. Conservatively reported as no coverage until confirmed.",
|
|
159
|
+
},
|
|
160
|
+
render: (shim) => `${JSON.stringify({
|
|
161
|
+
version: 1,
|
|
162
|
+
description: "foo research gate (B5) — routes web research through `foo research`",
|
|
163
|
+
hooks: {
|
|
164
|
+
preToolUse: [{ command: shim, timeout: 10 }],
|
|
165
|
+
},
|
|
166
|
+
}, null, 2)}\n`,
|
|
167
|
+
},
|
|
168
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type HumanSurfaceRule = "HS-001" | "HS-002" | "HS-003" | "HS-004";
|
|
2
|
+
export type HumanSurfaceFinding = {
|
|
3
|
+
rule: HumanSurfaceRule;
|
|
4
|
+
line: number;
|
|
5
|
+
phrase: string;
|
|
6
|
+
rewrite: string;
|
|
7
|
+
};
|
|
8
|
+
export type HumanSurfaceEvalResult = {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
findings: HumanSurfaceFinding[];
|
|
11
|
+
error?: "empty-input";
|
|
12
|
+
};
|
|
13
|
+
export declare function evaluateHumanSurface(text: string): HumanSurfaceEvalResult;
|
|
14
|
+
export declare function renderHumanSurfaceEval(result: HumanSurfaceEvalResult): string;
|
|
15
|
+
export declare class HumanSurfaceLeakError extends Error {
|
|
16
|
+
readonly result: HumanSurfaceEvalResult;
|
|
17
|
+
constructor(result: HumanSurfaceEvalResult);
|
|
18
|
+
}
|
|
19
|
+
export declare function guardHumanSurface(text: string): string;
|