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,509 @@
|
|
|
1
|
+
// Prompt Redaction Safety V0 — T-001 (R-001): the deterministic secrets
|
|
2
|
+
// detection engine. Pure text-in / findings-out, offline, no I/O — the CLI owns
|
|
3
|
+
// file reading and directory walking so this module stays fixture-testable
|
|
4
|
+
// (same purity discipline as sourceIngress.ts).
|
|
5
|
+
//
|
|
6
|
+
// R-007 hardening: a finding is METADATA-ONLY. A detected secret value is never
|
|
7
|
+
// stored in a finding, returned, or logged — only its type, location, and
|
|
8
|
+
// length. Detection is in-memory; the value is matched and discarded.
|
|
9
|
+
//
|
|
10
|
+
// Recall bias (R-001): provider patterns catch known credentials at high
|
|
11
|
+
// confidence; an entropy backstop catches unknown-format secrets at low
|
|
12
|
+
// confidence (advisory). Precision is recovered via a human-curated allowlist
|
|
13
|
+
// (R-007: never auto-populated), not by raising thresholds.
|
|
14
|
+
const CONFIDENCE_RANK = { high: 3, medium: 2, low: 1 };
|
|
15
|
+
// Order is fixed for deterministic output. Provider patterns first (high
|
|
16
|
+
// confidence), then credentialed connection strings, then keyed assignments.
|
|
17
|
+
const SECRET_DETECTORS = [
|
|
18
|
+
{
|
|
19
|
+
detector: "aws_access_key_id",
|
|
20
|
+
category: "cloud_credential",
|
|
21
|
+
confidence: "high",
|
|
22
|
+
regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA)[A-Z0-9]{16}\b/g,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
detector: "github_token",
|
|
26
|
+
category: "vcs_credential",
|
|
27
|
+
confidence: "high",
|
|
28
|
+
regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36}\b/g,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
detector: "github_pat",
|
|
32
|
+
category: "vcs_credential",
|
|
33
|
+
confidence: "high",
|
|
34
|
+
regex: /\bgithub_pat_[A-Za-z0-9_]{59,}\b/g,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
detector: "slack_token",
|
|
38
|
+
category: "saas_credential",
|
|
39
|
+
confidence: "high",
|
|
40
|
+
regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
detector: "stripe_secret_key",
|
|
44
|
+
category: "saas_credential",
|
|
45
|
+
confidence: "high",
|
|
46
|
+
regex: /\b(?:sk|rk)_live_[A-Za-z0-9]{16,}\b/g,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
detector: "stripe_test_key",
|
|
50
|
+
category: "saas_credential",
|
|
51
|
+
confidence: "medium",
|
|
52
|
+
regex: /\b(?:sk|rk)_test_[A-Za-z0-9]{16,}\b/g,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
detector: "gcp_api_key",
|
|
56
|
+
category: "cloud_credential",
|
|
57
|
+
confidence: "high",
|
|
58
|
+
regex: /\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
detector: "openai_key",
|
|
62
|
+
category: "saas_credential",
|
|
63
|
+
confidence: "medium",
|
|
64
|
+
regex: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
detector: "anthropic_key",
|
|
68
|
+
category: "saas_credential",
|
|
69
|
+
confidence: "high",
|
|
70
|
+
// productfoo's OWN provider — must not be a blind spot. sk-ant-<kind><n>-<long body>.
|
|
71
|
+
regex: /\bsk-ant-[a-z]+\d*-[A-Za-z0-9_-]{24,}\b/g,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
detector: "azure_storage_key",
|
|
75
|
+
category: "cloud_credential",
|
|
76
|
+
confidence: "high",
|
|
77
|
+
// The AccountKey of an Azure Storage connection string (base64, >= 80 chars).
|
|
78
|
+
regex: /AccountKey=[A-Za-z0-9+/=]{80,}/g,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
detector: "gcp_service_account",
|
|
82
|
+
category: "cloud_credential",
|
|
83
|
+
confidence: "medium",
|
|
84
|
+
// Marker for a GCP service-account key file; the embedded private key is caught
|
|
85
|
+
// at high confidence by private_key_block (which also covers OPENSSH SSH keys).
|
|
86
|
+
regex: /"type"\s*:\s*"service_account"/g,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
detector: "private_key_block",
|
|
90
|
+
category: "private_key",
|
|
91
|
+
confidence: "high",
|
|
92
|
+
regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
detector: "jwt",
|
|
96
|
+
category: "token",
|
|
97
|
+
confidence: "medium",
|
|
98
|
+
regex: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
detector: "db_connection_string",
|
|
102
|
+
category: "connection_string",
|
|
103
|
+
confidence: "high",
|
|
104
|
+
// Only flags when credentials are embedded (user:password@host).
|
|
105
|
+
regex: /\b(?:postgres|postgresql|mysql|mongodb(?:\+srv)?|redis|amqps?):\/\/[^\s:@/]+:[^\s:@/]+@[^\s/]+/g,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
detector: "secret_assignment",
|
|
109
|
+
category: "keyed_secret",
|
|
110
|
+
confidence: "high",
|
|
111
|
+
// KEY=VALUE / KEY: VALUE where the key name signals a secret. The whole
|
|
112
|
+
// match is treated as the span; refine() reads the value for confidence.
|
|
113
|
+
regex: /\b[\w.-]*(?:secret|token|password|passwd|pwd|api[_-]?key|access[_-]?key|client[_-]?secret|auth[_-]?token|credential)[\w.-]*\s*[:=]\s*["']?([^"'\s#]{4,})["']?/gi,
|
|
114
|
+
refine: (value) => (isPlaceholder(value) || !looksLikeSecretValue(value) ? null : "high"),
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
// Obvious non-secrets in assignment values: template/placeholder text.
|
|
118
|
+
function isPlaceholder(value) {
|
|
119
|
+
return (/^[*x.\-_]+$/i.test(value) ||
|
|
120
|
+
/^(changeme|change_me|placeholder|your[_-]?\w+|example|examplekey|todo|none|null|undefined|test|dummy|redacted)$/i.test(value) ||
|
|
121
|
+
/^<.+>$/.test(value) ||
|
|
122
|
+
/^\$\{?\w+\}?$/.test(value) // ${ENV_VAR} / $VAR interpolation
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
// Per-character Shannon entropy (bits/char) over the token.
|
|
126
|
+
export function shannonEntropy(value) {
|
|
127
|
+
if (value.length === 0)
|
|
128
|
+
return 0;
|
|
129
|
+
const freq = new Map();
|
|
130
|
+
for (const ch of value)
|
|
131
|
+
freq.set(ch, (freq.get(ch) ?? 0) + 1);
|
|
132
|
+
let entropy = 0;
|
|
133
|
+
for (const count of freq.values()) {
|
|
134
|
+
const p = count / value.length;
|
|
135
|
+
entropy -= p * Math.log2(p);
|
|
136
|
+
}
|
|
137
|
+
return entropy;
|
|
138
|
+
}
|
|
139
|
+
// Distinguish a literal secret value from a code reference. Real secrets are
|
|
140
|
+
// long, high-entropy, and contain a digit; identifiers, member access, and
|
|
141
|
+
// calls are rejected. The gate blocks high-confidence findings only, so the
|
|
142
|
+
// keyed-assignment detector must be precise — this kills source-code FPs like
|
|
143
|
+
// `authToken = req.headers` or `tokensPerCall = estimate`.
|
|
144
|
+
function looksLikeSecretValue(value) {
|
|
145
|
+
if (/[.()[\]{}<>]/.test(value))
|
|
146
|
+
return false; // code: a.b.c, getToken(, arr[1], <T>, {x}
|
|
147
|
+
if (!/[0-9]/.test(value) || !/[A-Za-z]/.test(value))
|
|
148
|
+
return false; // need mixed alnum
|
|
149
|
+
return value.length >= 12 && shannonEntropy(value) >= 3.4;
|
|
150
|
+
}
|
|
151
|
+
// Charset-aware entropy thresholds (detect-secrets defaults: 4.5 base64 / 3.0
|
|
152
|
+
// hex). Pure-numeric runs are treated as IDs, not secrets (false-positive cut).
|
|
153
|
+
function entropyFinding(token, minLen) {
|
|
154
|
+
if (token.length < minLen)
|
|
155
|
+
return { ok: false, entropy: 0 };
|
|
156
|
+
if (/^[0-9]+$/.test(token))
|
|
157
|
+
return { ok: false, entropy: 0 };
|
|
158
|
+
const entropy = shannonEntropy(token);
|
|
159
|
+
if (/^[A-Fa-f0-9]+$/.test(token))
|
|
160
|
+
return { ok: entropy >= 3.0, entropy };
|
|
161
|
+
if (/^[A-Za-z0-9+/=_-]+$/.test(token))
|
|
162
|
+
return { ok: entropy >= 4.5, entropy };
|
|
163
|
+
return { ok: false, entropy };
|
|
164
|
+
}
|
|
165
|
+
function lineStarts(text) {
|
|
166
|
+
const starts = [0];
|
|
167
|
+
for (let i = 0; i < text.length; i++) {
|
|
168
|
+
if (text[i] === "\n")
|
|
169
|
+
starts.push(i + 1);
|
|
170
|
+
}
|
|
171
|
+
return starts;
|
|
172
|
+
}
|
|
173
|
+
// Map a 0-based string offset to a 1-based line/column.
|
|
174
|
+
function locate(starts, offset) {
|
|
175
|
+
// Binary search for the last line start <= offset.
|
|
176
|
+
let lo = 0;
|
|
177
|
+
let hi = starts.length - 1;
|
|
178
|
+
while (lo < hi) {
|
|
179
|
+
const mid = (lo + hi + 1) >> 1;
|
|
180
|
+
if (starts[mid] <= offset)
|
|
181
|
+
lo = mid;
|
|
182
|
+
else
|
|
183
|
+
hi = mid - 1;
|
|
184
|
+
}
|
|
185
|
+
return { line: lo + 1, column: offset - starts[lo] + 1 };
|
|
186
|
+
}
|
|
187
|
+
function isAllowed(value, opts) {
|
|
188
|
+
if (opts.allowlist?.some((entry) => entry === value || value.includes(entry)))
|
|
189
|
+
return true;
|
|
190
|
+
if (opts.allowlistPatterns?.some((re) => re.test(value)))
|
|
191
|
+
return true;
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
// --- Detection profiles (T-005 / R-006) --------------------------------------
|
|
195
|
+
// A profile bundles pattern detectors and whether the entropy backstop runs.
|
|
196
|
+
// Adding coverage — internal infra, PII, sales-transcript — is a registry entry
|
|
197
|
+
// here, NOT an engine change. v1 runs only "secrets" by default.
|
|
198
|
+
// Opt-in seed profile: internal-infrastructure identifiers. Not active by
|
|
199
|
+
// default; medium confidence so it never gates. Exists to demonstrate that a
|
|
200
|
+
// new profile is purely a registry entry (and to seed the future infra pack).
|
|
201
|
+
const INTERNAL_DETECTORS = [
|
|
202
|
+
{
|
|
203
|
+
detector: "private_ip",
|
|
204
|
+
category: "internal_infra",
|
|
205
|
+
confidence: "medium",
|
|
206
|
+
regex: /\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3})\b/g,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
detector: "internal_hostname",
|
|
210
|
+
category: "internal_infra",
|
|
211
|
+
confidence: "medium",
|
|
212
|
+
regex: /\b[a-z0-9][a-z0-9-]*\.(?:internal|local|corp|intranet)\b/gi,
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
// --- The `pii` profile (feedback_triage_v0 T-001 / RISK-001) -----------------
|
|
216
|
+
// A feedback document is raw private material about a named person, and the
|
|
217
|
+
// engine could not touch it: `secrets` strips API keys, not a person's name,
|
|
218
|
+
// home path, employer, or machine. This profile is the prerequisite that lets
|
|
219
|
+
// feedback content be tracked at all.
|
|
220
|
+
//
|
|
221
|
+
// WHAT IT CAN AND CANNOT DO — read this before trusting it:
|
|
222
|
+
//
|
|
223
|
+
// Reliable (high confidence, blocks): email addresses, home directory paths,
|
|
224
|
+
// MAC addresses, and identity/employer fields that are LABELLED
|
|
225
|
+
// ("Reporter: Jane Doe", "Employer: Acme Corp"). These have a syntax, so a
|
|
226
|
+
// deterministic matcher can be precise about them.
|
|
227
|
+
//
|
|
228
|
+
// Best-effort (medium confidence, redacted at the default threshold but never
|
|
229
|
+
// blocking): device names, `.local` hostnames, install UUIDs, and employer
|
|
230
|
+
// mentions in prose ("works at Acme Corp").
|
|
231
|
+
//
|
|
232
|
+
// NOT DETECTED AT ALL: an arbitrary personal name in free prose. "I asked
|
|
233
|
+
// Jordan and she agreed" is indistinguishable, by regex, from any other
|
|
234
|
+
// capitalised word, and every generic name-matcher is either a name list
|
|
235
|
+
// (fails on the name you have not listed) or a capitalised-token heuristic
|
|
236
|
+
// that fires on every sentence start and product noun. Shipping either would
|
|
237
|
+
// be the failure this repo keeps re-learning: a gate that fires on the shape
|
|
238
|
+
// of a risk rather than the risk is one people override on sight. So this
|
|
239
|
+
// profile catches names where they carry a marker — a field label, an email
|
|
240
|
+
// local part, a home-path segment — and states plainly that free-prose names
|
|
241
|
+
// are the caller's job (pass them via `allowlist`'s inverse: redact them
|
|
242
|
+
// before handing text to the engine, or review the document).
|
|
243
|
+
//
|
|
244
|
+
// Consequence for callers: passing this profile is NOT proof a document is
|
|
245
|
+
// anonymous. It is proof the document carries no *machine-detectable* personal
|
|
246
|
+
// identifier. `foo self check` blocks on that, and says exactly that.
|
|
247
|
+
// Shared account names on shared machines — a home path under one of these
|
|
248
|
+
// identifies a CI runner, not a person. Precision, not a threshold change.
|
|
249
|
+
const IMPERSONAL_ACCOUNTS = new Set([
|
|
250
|
+
"runner", "root", "admin", "administrator", "ubuntu", "ec2-user", "user", "username", "you", "vagrant", "docker", "node", "shared", "public", "default",
|
|
251
|
+
]);
|
|
252
|
+
// The trailing path segment of a home directory: /Users/<account>, /home/<account>.
|
|
253
|
+
function homeAccountOf(value) {
|
|
254
|
+
const parts = value.split(/[\\/]/);
|
|
255
|
+
return (parts[parts.length - 1] ?? "").toLowerCase();
|
|
256
|
+
}
|
|
257
|
+
// A person's or company's name as written in a labelled field: one to four
|
|
258
|
+
// capitalised words. Deliberately strict — the label already carries the
|
|
259
|
+
// evidence, so the value only has to be name-SHAPED, not name-guessed.
|
|
260
|
+
const PROPER_NOUN = /^[A-Z][A-Za-z'’.-]*(?: (?:&|of|and|de|van|von|der|la|[A-Z][A-Za-z'’.,-]*)){0,3}$/;
|
|
261
|
+
function looksLikeProperNoun(value) {
|
|
262
|
+
const trimmed = value.trim().replace(/[.,;]$/, "");
|
|
263
|
+
if (isPlaceholder(trimmed))
|
|
264
|
+
return false;
|
|
265
|
+
return trimmed.length >= 2 && PROPER_NOUN.test(trimmed);
|
|
266
|
+
}
|
|
267
|
+
const PII_DETECTORS = [
|
|
268
|
+
{
|
|
269
|
+
detector: "pii_email",
|
|
270
|
+
category: "personal_contact",
|
|
271
|
+
confidence: "high",
|
|
272
|
+
regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*\.[A-Za-z]{2,}\b/g,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
detector: "pii_home_path",
|
|
276
|
+
category: "personal_path",
|
|
277
|
+
confidence: "high",
|
|
278
|
+
// The whole `/Users/<account>` prefix is the span; the rest of the path is
|
|
279
|
+
// left intact so the document still reads. Group 1 == whole match so that
|
|
280
|
+
// refine() can inspect the account without changing what gets replaced.
|
|
281
|
+
regex: /((?:\/Users\/|\/home\/|\\Users\\|C:\\Users\\)[A-Za-z0-9._-]+)/g,
|
|
282
|
+
refine: (value) => (IMPERSONAL_ACCOUNTS.has(homeAccountOf(value)) ? null : "high"),
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
detector: "pii_identity_field",
|
|
286
|
+
category: "personal_identity",
|
|
287
|
+
confidence: "high",
|
|
288
|
+
// A LABELLED name. The label is the evidence; the value must still be
|
|
289
|
+
// name-shaped so a stray `name: foo` in YAML does not fire.
|
|
290
|
+
regex: /\b(?:reporter|reported by|submitted by|filed by|author|full name|name|from|contact|signed)\s*[:=]\s*([^\n,;<(|]{2,60})/gi,
|
|
291
|
+
refine: (value) => (looksLikeProperNoun(value) ? "high" : null),
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
detector: "pii_employer",
|
|
295
|
+
category: "employer",
|
|
296
|
+
confidence: "high",
|
|
297
|
+
regex: /\b(?:employer|company|organi[sz]ation|workplace)\s*[:=]\s*([^\n,;<(|]{2,60})/gi,
|
|
298
|
+
refine: (value) => (looksLikeProperNoun(value) ? "high" : null),
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
detector: "pii_employer_mention",
|
|
302
|
+
category: "employer",
|
|
303
|
+
confidence: "medium",
|
|
304
|
+
// Best-effort: an employer named in prose. Medium, so it is redacted at the
|
|
305
|
+
// default threshold but never blocks — the phrasing space is unbounded.
|
|
306
|
+
regex: /\b(?:works? at|worked at|working at|employed by|employer is)\s+([A-Z][A-Za-z0-9&'’.-]*(?: [A-Z][A-Za-z0-9&'’.-]*){0,3})/g,
|
|
307
|
+
refine: (value) => (looksLikeProperNoun(value) ? "medium" : null),
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
detector: "pii_mac_address",
|
|
311
|
+
category: "machine_id",
|
|
312
|
+
confidence: "high",
|
|
313
|
+
regex: /\b(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}\b/g,
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
detector: "pii_device_name",
|
|
317
|
+
category: "machine_id",
|
|
318
|
+
confidence: "medium",
|
|
319
|
+
// "Jordans-MacBook-Pro", "jvance-laptop.local" — a device name usually
|
|
320
|
+
// carries its owner's name. Best-effort: naming conventions vary.
|
|
321
|
+
// `.local` first so a full `host.local` is one span, not a device name with
|
|
322
|
+
// a dangling suffix.
|
|
323
|
+
regex: /\b[a-z0-9][a-z0-9-]*\.local\b|\b[A-Za-z0-9]+(?:['’]?s)?-(?:MacBook(?:-Pro|-Air)?|iMac|Mac-mini|Mac-Studio|Laptop|PC)\b/gi,
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
detector: "pii_machine_uuid",
|
|
327
|
+
category: "machine_id",
|
|
328
|
+
confidence: "medium",
|
|
329
|
+
// Hardware/install UUID. Medium: a UUID is also a perfectly ordinary
|
|
330
|
+
// record id, so this informs redaction without gating a commit.
|
|
331
|
+
regex: /\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/g,
|
|
332
|
+
},
|
|
333
|
+
];
|
|
334
|
+
const PROFILE_REGISTRY = {
|
|
335
|
+
secrets: { name: "secrets", detectors: SECRET_DETECTORS, entropyBackstop: true },
|
|
336
|
+
internal: { name: "internal", detectors: INTERNAL_DETECTORS, entropyBackstop: false },
|
|
337
|
+
pii: { name: "pii", detectors: PII_DETECTORS, entropyBackstop: false },
|
|
338
|
+
};
|
|
339
|
+
// The profile that governs personal material in feedback content (RISK-001).
|
|
340
|
+
export const PII_PROFILE = "pii";
|
|
341
|
+
// Only "secrets" runs unless the caller opts into more (R-006: secrets-first v1).
|
|
342
|
+
export const DEFAULT_PROFILES = ["secrets"];
|
|
343
|
+
export function knownProfiles() {
|
|
344
|
+
return Object.keys(PROFILE_REGISTRY);
|
|
345
|
+
}
|
|
346
|
+
function resolveProfiles(names) {
|
|
347
|
+
return names.map((name) => {
|
|
348
|
+
const profile = PROFILE_REGISTRY[name];
|
|
349
|
+
if (!profile)
|
|
350
|
+
throw new Error(`unknown redaction profile: ${name} (known: ${knownProfiles().join(", ")})`);
|
|
351
|
+
return profile;
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
// Deterministic: same input + options => identical detections, ordered by
|
|
355
|
+
// offset then detector.
|
|
356
|
+
function detect(text, options = {}) {
|
|
357
|
+
const opts = { minEntropyLen: 20, ...options };
|
|
358
|
+
const profiles = resolveProfiles(opts.profiles ?? DEFAULT_PROFILES);
|
|
359
|
+
const detectors = profiles.flatMap((p) => p.detectors);
|
|
360
|
+
const runEntropy = profiles.some((p) => p.entropyBackstop);
|
|
361
|
+
const raw = [];
|
|
362
|
+
const covered = []; // [start, end) offsets already claimed
|
|
363
|
+
// Inline allow pragma (OQ2): a line containing `foo-redact: allow` suppresses
|
|
364
|
+
// findings on that line — for deliberate fixtures/examples. Pairs with the
|
|
365
|
+
// repo allowlist file. Human-authored only (R-007).
|
|
366
|
+
const starts = lineStarts(text);
|
|
367
|
+
const pragmaLines = new Set();
|
|
368
|
+
text.split(/\r?\n/).forEach((line, i) => {
|
|
369
|
+
if (/foo-redact:\s*allow/i.test(line))
|
|
370
|
+
pragmaLines.add(i + 1);
|
|
371
|
+
});
|
|
372
|
+
const pragmaAllowed = (offset) => pragmaLines.has(locate(starts, offset).line);
|
|
373
|
+
for (const det of detectors) {
|
|
374
|
+
for (const match of text.matchAll(det.regex)) {
|
|
375
|
+
const whole = match[0];
|
|
376
|
+
const start = match.index ?? 0;
|
|
377
|
+
// For keyed assignments, the secret of interest is the captured value.
|
|
378
|
+
const value = det.refine ? (match[1] ?? whole) : whole;
|
|
379
|
+
let confidence = det.confidence;
|
|
380
|
+
if (det.refine) {
|
|
381
|
+
const refined = det.refine(value);
|
|
382
|
+
if (refined === null)
|
|
383
|
+
continue; // placeholder => not a secret
|
|
384
|
+
confidence = refined;
|
|
385
|
+
}
|
|
386
|
+
const valueStart = det.refine ? start + whole.indexOf(value) : start;
|
|
387
|
+
if (isAllowed(value, opts) || pragmaAllowed(valueStart))
|
|
388
|
+
continue;
|
|
389
|
+
raw.push({ detector: det.detector, category: det.category, confidence, offset: valueStart, length: value.length });
|
|
390
|
+
covered.push([start, start + whole.length]);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// Entropy backstop over long tokens not already claimed by a pattern. Runs
|
|
394
|
+
// only for profiles that enable it (the secrets profile).
|
|
395
|
+
if (runEntropy) {
|
|
396
|
+
for (const match of text.matchAll(/[A-Za-z0-9+/=_-]{12,}/g)) {
|
|
397
|
+
const token = match[0];
|
|
398
|
+
const start = match.index ?? 0;
|
|
399
|
+
const end = start + token.length;
|
|
400
|
+
if (covered.some(([cs, ce]) => start < ce && end > cs))
|
|
401
|
+
continue;
|
|
402
|
+
if (isAllowed(token, opts) || pragmaAllowed(start))
|
|
403
|
+
continue;
|
|
404
|
+
const { ok, entropy } = entropyFinding(token, opts.minEntropyLen ?? 20);
|
|
405
|
+
if (!ok)
|
|
406
|
+
continue;
|
|
407
|
+
// Advisory only — the gate (T-003) blocks high-confidence findings.
|
|
408
|
+
raw.push({ detector: "high_entropy_string", category: "entropy", confidence: "low", offset: start, length: token.length, entropy: Math.round(entropy * 100) / 100 });
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
raw.sort((a, b) => a.offset - b.offset || a.detector.localeCompare(b.detector));
|
|
412
|
+
// Dedupe detections starting at the same offset (e.g. a keyed assignment and
|
|
413
|
+
// a provider pattern over the same value), keeping the highest confidence.
|
|
414
|
+
const deduped = [];
|
|
415
|
+
for (const f of raw) {
|
|
416
|
+
const prior = deduped[deduped.length - 1];
|
|
417
|
+
if (prior && prior.offset === f.offset) {
|
|
418
|
+
if (CONFIDENCE_RANK[f.confidence] > CONFIDENCE_RANK[prior.confidence])
|
|
419
|
+
deduped[deduped.length - 1] = f;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
deduped.push(f);
|
|
423
|
+
}
|
|
424
|
+
return deduped;
|
|
425
|
+
}
|
|
426
|
+
// Scan a single text blob and return metadata-only findings (R-007).
|
|
427
|
+
export function scanText(text, options = {}) {
|
|
428
|
+
const starts = lineStarts(text);
|
|
429
|
+
const findings = detect(text, options).map((r) => {
|
|
430
|
+
const { line, column } = locate(starts, r.offset);
|
|
431
|
+
const finding = { detector: r.detector, category: r.category, confidence: r.confidence, line, column, length: r.length };
|
|
432
|
+
if (r.entropy !== undefined)
|
|
433
|
+
finding.entropy = r.entropy;
|
|
434
|
+
return finding;
|
|
435
|
+
});
|
|
436
|
+
const counts = {
|
|
437
|
+
high: findings.filter((f) => f.confidence === "high").length,
|
|
438
|
+
medium: findings.filter((f) => f.confidence === "medium").length,
|
|
439
|
+
low: findings.filter((f) => f.confidence === "low").length,
|
|
440
|
+
total: findings.length,
|
|
441
|
+
};
|
|
442
|
+
return { schema_version: "0.1", findings, counts };
|
|
443
|
+
}
|
|
444
|
+
// Parse an allowlist file: one literal value per line, `#` comments and blank
|
|
445
|
+
// lines ignored. Human-curated trusted config (R-007).
|
|
446
|
+
export function parseAllowlist(text) {
|
|
447
|
+
return text
|
|
448
|
+
.split(/\r?\n/)
|
|
449
|
+
.map((line) => line.trim())
|
|
450
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
451
|
+
}
|
|
452
|
+
// Replace detected secrets with typed, consistent, irreversible placeholders.
|
|
453
|
+
// Same value => same token within the document (readability + diff stability).
|
|
454
|
+
// No value->token map is persisted (D-004): the mapping lives only for this
|
|
455
|
+
// call and is discarded on return, so redaction cannot be reversed.
|
|
456
|
+
export function redactText(text, options = {}) {
|
|
457
|
+
const threshold = options.threshold ?? "medium";
|
|
458
|
+
const minRank = CONFIDENCE_RANK[threshold];
|
|
459
|
+
const starts = lineStarts(text);
|
|
460
|
+
const raw = detect(text, options);
|
|
461
|
+
const tokens = new Map(); // value -> consistent placeholder (in-memory only)
|
|
462
|
+
const perType = new Map();
|
|
463
|
+
const tokenFor = (detector, value) => {
|
|
464
|
+
const existing = tokens.get(value);
|
|
465
|
+
if (existing)
|
|
466
|
+
return existing;
|
|
467
|
+
const n = (perType.get(detector) ?? 0) + 1;
|
|
468
|
+
perType.set(detector, n);
|
|
469
|
+
const token = `[${detector.toUpperCase()}_${n}]`;
|
|
470
|
+
tokens.set(value, token);
|
|
471
|
+
return token;
|
|
472
|
+
};
|
|
473
|
+
const by_type = {};
|
|
474
|
+
const by_confidence = { high: 0, medium: 0, low: 0 };
|
|
475
|
+
const placeholders = new Set();
|
|
476
|
+
const kept = [];
|
|
477
|
+
let out = "";
|
|
478
|
+
let cursor = 0;
|
|
479
|
+
let redactions = 0;
|
|
480
|
+
for (const f of raw) {
|
|
481
|
+
if (f.offset < cursor)
|
|
482
|
+
continue; // overlap guard
|
|
483
|
+
if (CONFIDENCE_RANK[f.confidence] < minRank) {
|
|
484
|
+
const { line, column } = locate(starts, f.offset);
|
|
485
|
+
kept.push({ detector: f.detector, confidence: f.confidence, line, column, reason: `below redaction threshold (${threshold})` });
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const value = text.slice(f.offset, f.offset + f.length);
|
|
489
|
+
const token = tokenFor(f.detector, value);
|
|
490
|
+
out += text.slice(cursor, f.offset) + token;
|
|
491
|
+
cursor = f.offset + f.length;
|
|
492
|
+
redactions += 1;
|
|
493
|
+
by_type[f.detector] = (by_type[f.detector] ?? 0) + 1;
|
|
494
|
+
by_confidence[f.confidence] += 1;
|
|
495
|
+
placeholders.add(token);
|
|
496
|
+
}
|
|
497
|
+
out += text.slice(cursor);
|
|
498
|
+
return {
|
|
499
|
+
redactedText: out,
|
|
500
|
+
receipt: {
|
|
501
|
+
schema_version: "0.1",
|
|
502
|
+
redactions,
|
|
503
|
+
by_type,
|
|
504
|
+
by_confidence,
|
|
505
|
+
placeholders: [...placeholders].sort(),
|
|
506
|
+
kept,
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type GitRunOptions, type GitRunResult } from "./gitRun.js";
|
|
2
|
+
export type StagedFile = {
|
|
3
|
+
file: string;
|
|
4
|
+
text: string;
|
|
5
|
+
};
|
|
6
|
+
export type UnscannableReason = "git-index-unavailable" | "unsupported-file-type" | "blob-unreadable" | "blob-too-large" | "binary-or-nul" | "decode-failed";
|
|
7
|
+
export type UnscannableStagedFile = {
|
|
8
|
+
file: string;
|
|
9
|
+
reason: UnscannableReason;
|
|
10
|
+
};
|
|
11
|
+
export type StagedRead = {
|
|
12
|
+
scanned: StagedFile[];
|
|
13
|
+
unscannable: UnscannableStagedFile[];
|
|
14
|
+
};
|
|
15
|
+
export type RedactGateGitSeams = {
|
|
16
|
+
gitRun: (args: string[], opts: GitRunOptions) => GitRunResult;
|
|
17
|
+
gitRunBuffer: (args: string[], opts: GitRunOptions) => {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
stdout: Buffer;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type GateFinding = {
|
|
23
|
+
file: string;
|
|
24
|
+
line: number;
|
|
25
|
+
column: number;
|
|
26
|
+
detector: string;
|
|
27
|
+
};
|
|
28
|
+
export type GateDecision = {
|
|
29
|
+
blocked: boolean;
|
|
30
|
+
overridden: boolean;
|
|
31
|
+
high: GateFinding[];
|
|
32
|
+
reason?: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function decideGate(staged: StagedFile[], opts?: {
|
|
35
|
+
allowlist?: string[];
|
|
36
|
+
overrideReason?: string;
|
|
37
|
+
}): GateDecision;
|
|
38
|
+
export declare function readStaged(root: string, seams?: Partial<RedactGateGitSeams>): StagedRead;
|
|
39
|
+
export type UnscannableRefusal = {
|
|
40
|
+
schema_version: "0.1";
|
|
41
|
+
status: "refused";
|
|
42
|
+
reason_code: "staged_file_unscannable";
|
|
43
|
+
message: string;
|
|
44
|
+
safe_next_action: string;
|
|
45
|
+
};
|
|
46
|
+
export declare const UNSCANNABLE_REFUSAL: UnscannableRefusal;
|
|
47
|
+
export type RedactGateResult = {
|
|
48
|
+
output: string;
|
|
49
|
+
exitCode: 0 | 1;
|
|
50
|
+
unscannable: false;
|
|
51
|
+
} | {
|
|
52
|
+
output: string;
|
|
53
|
+
exitCode: 1;
|
|
54
|
+
unscannable: true;
|
|
55
|
+
refusal: UnscannableRefusal;
|
|
56
|
+
files: UnscannableStagedFile[];
|
|
57
|
+
};
|
|
58
|
+
export declare function runRedactGate(root: string, opts?: {
|
|
59
|
+
overrideReason?: string;
|
|
60
|
+
at?: string;
|
|
61
|
+
machine?: boolean;
|
|
62
|
+
seams?: Partial<RedactGateGitSeams>;
|
|
63
|
+
}): RedactGateResult;
|