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,76 @@
|
|
|
1
|
+
import { type AcceptedEnvelopeState, type EnvelopeFailureReason, type JsonValue, type SignedEnvelope, type SplitTrustStores } from "./trustEnvelope.js";
|
|
2
|
+
export declare const RESEARCH_COMPLETION_KIND = "research.completion";
|
|
3
|
+
/** The only states a *completion* receipt may carry. `planned` and `abandoned`
|
|
4
|
+
* are real run states, but they are not completions and are never signed as one:
|
|
5
|
+
* a signed record is a claim that a run finished, and nothing else may wear it. */
|
|
6
|
+
export declare const TERMINAL_STATES: readonly ["completed", "validated"];
|
|
7
|
+
export type TerminalState = (typeof TERMINAL_STATES)[number];
|
|
8
|
+
export interface ReceiptSourceFact {
|
|
9
|
+
source_class: string;
|
|
10
|
+
reached: boolean;
|
|
11
|
+
item_count: number;
|
|
12
|
+
}
|
|
13
|
+
export interface CompletionReceiptPayload {
|
|
14
|
+
kind: typeof RESEARCH_COMPLETION_KIND;
|
|
15
|
+
run_id: string;
|
|
16
|
+
topic: string;
|
|
17
|
+
state: TerminalState;
|
|
18
|
+
sources: ReceiptSourceFact[];
|
|
19
|
+
evidence_sha256: string;
|
|
20
|
+
provider: {
|
|
21
|
+
adapter: string;
|
|
22
|
+
model: string;
|
|
23
|
+
};
|
|
24
|
+
evaluator_version: string;
|
|
25
|
+
paper_sha256: string;
|
|
26
|
+
/** Versioned executor evidence; absent in legacy, declaration-only receipts. */
|
|
27
|
+
retrieval?: JsonValue;
|
|
28
|
+
}
|
|
29
|
+
export interface CompletionReceiptInput {
|
|
30
|
+
run_id: string;
|
|
31
|
+
topic: string;
|
|
32
|
+
state: TerminalState;
|
|
33
|
+
sources: ReceiptSourceFact[];
|
|
34
|
+
evidence_sha256: string;
|
|
35
|
+
provider: {
|
|
36
|
+
adapter: string;
|
|
37
|
+
model: string;
|
|
38
|
+
};
|
|
39
|
+
evaluator_version: string;
|
|
40
|
+
paper_sha256: string;
|
|
41
|
+
/** Versioned executor evidence; absent in legacy, declaration-only receipts. */
|
|
42
|
+
retrieval?: JsonValue;
|
|
43
|
+
issued_at: string;
|
|
44
|
+
sequence: number;
|
|
45
|
+
previous_hash: string | null;
|
|
46
|
+
}
|
|
47
|
+
export interface VerifyCompletionReceiptOptions {
|
|
48
|
+
paper_bytes: Buffer;
|
|
49
|
+
trust_stores: SplitTrustStores;
|
|
50
|
+
accepted_state: AcceptedEnvelopeState | null;
|
|
51
|
+
now: string;
|
|
52
|
+
expected_run_id?: string;
|
|
53
|
+
expected_provider?: {
|
|
54
|
+
adapter: string;
|
|
55
|
+
model: string;
|
|
56
|
+
};
|
|
57
|
+
expected_sources?: readonly string[];
|
|
58
|
+
}
|
|
59
|
+
export type CompletionReceiptFailure = EnvelopeFailureReason | "malformed_receipt" | "not_terminal" | "id_mismatch" | "paper_mismatch" | "provider_mismatch" | "source_mismatch";
|
|
60
|
+
export type VerifyCompletionReceiptResult = {
|
|
61
|
+
ok: true;
|
|
62
|
+
receipt: CompletionReceiptPayload;
|
|
63
|
+
next_state: AcceptedEnvelopeState;
|
|
64
|
+
} | {
|
|
65
|
+
ok: false;
|
|
66
|
+
reason: CompletionReceiptFailure;
|
|
67
|
+
};
|
|
68
|
+
/** Sign a terminal completion record. Refuses any non-terminal state up front:
|
|
69
|
+
* a planning or abandoned record must never exist as a signed completion. */
|
|
70
|
+
export declare function mintCompletionReceipt(input: CompletionReceiptInput, privateKeyPem: string, keyId: string): SignedEnvelope<JsonValue>;
|
|
71
|
+
/** Verify a completion receipt against the paper it claims to certify.
|
|
72
|
+
* The signature is necessary but not sufficient: the receipt must also bind the
|
|
73
|
+
* paper bytes in front of us, and (when the caller knows them) the expected run,
|
|
74
|
+
* provider, and source set. Any mismatch fails — a valid signature over the wrong
|
|
75
|
+
* paper is exactly the forgery this record exists to refuse. */
|
|
76
|
+
export declare function verifyCompletionReceipt(envelopeValue: unknown, options: VerifyCompletionReceiptOptions): VerifyCompletionReceiptResult;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// A research completion receipt is a productfoo-signed terminal record of a run.
|
|
2
|
+
//
|
|
3
|
+
// It replaces the pre-quarantine receipt, which was a plain `{run_id, topic, at}`
|
|
4
|
+
// file whose "verification" was "a file with this id exists on disk" — forgeable
|
|
5
|
+
// by anyone who can write a file (T-005 finding, B5 incident: a paper of invented
|
|
6
|
+
// citations passed every check foo had). This record instead binds, under an
|
|
7
|
+
// Ed25519 signature over the T-002 canonical envelope, the facts that make a run
|
|
8
|
+
// real: its sources, its evidence set, the provider that produced it, the
|
|
9
|
+
// evaluator version, and the exact bytes of the final paper. A receipt that does
|
|
10
|
+
// not match the paper it is presented with, or is signed by a key productfoo does not
|
|
11
|
+
// trust, or is replayed, does not verify — so it cannot become evidence.
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { signEnvelope, verifyEnvelope, } from "./trustEnvelope.js";
|
|
14
|
+
export const RESEARCH_COMPLETION_KIND = "research.completion";
|
|
15
|
+
/** The only states a *completion* receipt may carry. `planned` and `abandoned`
|
|
16
|
+
* are real run states, but they are not completions and are never signed as one:
|
|
17
|
+
* a signed record is a claim that a run finished, and nothing else may wear it. */
|
|
18
|
+
export const TERMINAL_STATES = ["completed", "validated"];
|
|
19
|
+
const TEN_YEARS_MS = 315_576_000_000;
|
|
20
|
+
function sha256Hex(bytes) {
|
|
21
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
22
|
+
}
|
|
23
|
+
function isTerminal(state) {
|
|
24
|
+
return TERMINAL_STATES.includes(state);
|
|
25
|
+
}
|
|
26
|
+
/** Sign a terminal completion record. Refuses any non-terminal state up front:
|
|
27
|
+
* a planning or abandoned record must never exist as a signed completion. */
|
|
28
|
+
export function mintCompletionReceipt(input, privateKeyPem, keyId) {
|
|
29
|
+
if (!isTerminal(input.state)) {
|
|
30
|
+
throw new TypeError(`a completion receipt cannot carry the non-terminal state "${input.state}"`);
|
|
31
|
+
}
|
|
32
|
+
const payload = {
|
|
33
|
+
kind: RESEARCH_COMPLETION_KIND,
|
|
34
|
+
run_id: input.run_id,
|
|
35
|
+
topic: input.topic,
|
|
36
|
+
state: input.state,
|
|
37
|
+
sources: input.sources.map((s) => ({
|
|
38
|
+
source_class: s.source_class,
|
|
39
|
+
reached: s.reached,
|
|
40
|
+
item_count: s.item_count,
|
|
41
|
+
})),
|
|
42
|
+
evidence_sha256: input.evidence_sha256,
|
|
43
|
+
provider: { adapter: input.provider.adapter, model: input.provider.model },
|
|
44
|
+
evaluator_version: input.evaluator_version,
|
|
45
|
+
paper_sha256: input.paper_sha256,
|
|
46
|
+
...(input.retrieval ? { retrieval: input.retrieval } : {}),
|
|
47
|
+
};
|
|
48
|
+
const expiresAt = new Date(Date.parse(input.issued_at) + TEN_YEARS_MS).toISOString();
|
|
49
|
+
return signEnvelope({
|
|
50
|
+
namespace: "foo",
|
|
51
|
+
artifact_kind: RESEARCH_COMPLETION_KIND,
|
|
52
|
+
artifact_id: input.run_id,
|
|
53
|
+
payload: payload,
|
|
54
|
+
key_id: keyId,
|
|
55
|
+
issued_at: input.issued_at,
|
|
56
|
+
not_before: input.issued_at,
|
|
57
|
+
expires_at: expiresAt,
|
|
58
|
+
sequence: input.sequence,
|
|
59
|
+
previous_hash: input.previous_hash,
|
|
60
|
+
}, privateKeyPem);
|
|
61
|
+
}
|
|
62
|
+
function readField(value, field) {
|
|
63
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
64
|
+
return undefined;
|
|
65
|
+
return value[field];
|
|
66
|
+
}
|
|
67
|
+
function parsePayload(payload) {
|
|
68
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
69
|
+
return null;
|
|
70
|
+
const p = payload;
|
|
71
|
+
if (p.kind !== RESEARCH_COMPLETION_KIND)
|
|
72
|
+
return null;
|
|
73
|
+
if (typeof p.run_id !== "string" || typeof p.topic !== "string")
|
|
74
|
+
return null;
|
|
75
|
+
if (typeof p.state !== "string" || !isTerminal(p.state))
|
|
76
|
+
return null;
|
|
77
|
+
if (!Array.isArray(p.sources))
|
|
78
|
+
return null;
|
|
79
|
+
const sources = [];
|
|
80
|
+
for (const s of p.sources) {
|
|
81
|
+
const sc = readField(s, "source_class");
|
|
82
|
+
const reached = readField(s, "reached");
|
|
83
|
+
const count = readField(s, "item_count");
|
|
84
|
+
if (typeof sc !== "string" || typeof reached !== "boolean" || typeof count !== "number" || !Number.isSafeInteger(count) || count < 0)
|
|
85
|
+
return null;
|
|
86
|
+
sources.push({ source_class: sc, reached, item_count: count });
|
|
87
|
+
}
|
|
88
|
+
const provider = p.provider;
|
|
89
|
+
if (!provider || typeof provider.adapter !== "string" || typeof provider.model !== "string")
|
|
90
|
+
return null;
|
|
91
|
+
if (typeof p.evidence_sha256 !== "string" || typeof p.evaluator_version !== "string" || typeof p.paper_sha256 !== "string") {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
kind: RESEARCH_COMPLETION_KIND,
|
|
96
|
+
run_id: p.run_id,
|
|
97
|
+
topic: p.topic,
|
|
98
|
+
state: p.state,
|
|
99
|
+
sources,
|
|
100
|
+
evidence_sha256: p.evidence_sha256,
|
|
101
|
+
provider: { adapter: provider.adapter, model: provider.model },
|
|
102
|
+
evaluator_version: p.evaluator_version,
|
|
103
|
+
paper_sha256: p.paper_sha256,
|
|
104
|
+
...(p.retrieval ? { retrieval: p.retrieval } : {}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Verify a completion receipt against the paper it claims to certify.
|
|
108
|
+
* The signature is necessary but not sufficient: the receipt must also bind the
|
|
109
|
+
* paper bytes in front of us, and (when the caller knows them) the expected run,
|
|
110
|
+
* provider, and source set. Any mismatch fails — a valid signature over the wrong
|
|
111
|
+
* paper is exactly the forgery this record exists to refuse. */
|
|
112
|
+
export function verifyCompletionReceipt(envelopeValue, options) {
|
|
113
|
+
const artifactId = readField(envelopeValue, "artifact_id");
|
|
114
|
+
const env = verifyEnvelope(envelopeValue, {
|
|
115
|
+
expected_namespace: "foo",
|
|
116
|
+
expected_artifact_kind: RESEARCH_COMPLETION_KIND,
|
|
117
|
+
expected_artifact_id: options.expected_run_id ?? (typeof artifactId === "string" ? artifactId : ""),
|
|
118
|
+
trust_stores: options.trust_stores,
|
|
119
|
+
accepted_state: options.accepted_state,
|
|
120
|
+
verification_mode: "accept",
|
|
121
|
+
now: options.now,
|
|
122
|
+
});
|
|
123
|
+
if (!env.ok)
|
|
124
|
+
return { ok: false, reason: env.reason };
|
|
125
|
+
const payload = parsePayload(env.verified_envelope.payload);
|
|
126
|
+
if (!payload)
|
|
127
|
+
return { ok: false, reason: "malformed_receipt" };
|
|
128
|
+
if (!isTerminal(payload.state))
|
|
129
|
+
return { ok: false, reason: "not_terminal" };
|
|
130
|
+
if (payload.run_id !== env.verified_envelope.artifact_id)
|
|
131
|
+
return { ok: false, reason: "id_mismatch" };
|
|
132
|
+
if (sha256Hex(options.paper_bytes) !== payload.paper_sha256) {
|
|
133
|
+
return { ok: false, reason: "paper_mismatch" };
|
|
134
|
+
}
|
|
135
|
+
if (options.expected_provider &&
|
|
136
|
+
(options.expected_provider.adapter !== payload.provider.adapter ||
|
|
137
|
+
options.expected_provider.model !== payload.provider.model)) {
|
|
138
|
+
return { ok: false, reason: "provider_mismatch" };
|
|
139
|
+
}
|
|
140
|
+
if (options.expected_sources) {
|
|
141
|
+
const have = [...payload.sources.map((s) => s.source_class)].sort();
|
|
142
|
+
const want = [...options.expected_sources].sort();
|
|
143
|
+
if (have.length !== want.length || have.some((c, i) => c !== want[i])) {
|
|
144
|
+
return { ok: false, reason: "source_mismatch" };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { ok: true, receipt: payload, next_state: env.next_state };
|
|
148
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Renders a research/*.md into a self-contained HTML page, using the same renderer
|
|
2
|
+
// proven in the initiative-brief preview: a markdown → HTML pass, the reusable
|
|
3
|
+
// analytical block registry (```heatmap → Harvey-ball matrix), a report-header card,
|
|
4
|
+
// an "On this page" jump-nav, and reading-measure prose. This is the shared render
|
|
5
|
+
// module (research-format-render-v0 R-003) so the preview and `foo research view`
|
|
6
|
+
// cannot drift. Self-contained: no CDN, CSP-safe.
|
|
7
|
+
function esc(s) {
|
|
8
|
+
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
9
|
+
}
|
|
10
|
+
// Escape the quote characters that would let a value break out of an href="…"
|
|
11
|
+
// attribute. Callers pass strings that have already had &<> escaped upstream.
|
|
12
|
+
function attr(s) {
|
|
13
|
+
return s.replace(/"/g, """).replace(/'/g, "'");
|
|
14
|
+
}
|
|
15
|
+
// Validate a markdown link target for use in an href. Returns an
|
|
16
|
+
// attribute-safe string, or null when the scheme is not allowed (so the caller
|
|
17
|
+
// renders the raw text instead of a clickable link). Only http(s), mailto, and
|
|
18
|
+
// scheme-less (relative/anchor) targets are permitted — `javascript:`, `data:`,
|
|
19
|
+
// `vbscript:`, etc. are rejected. Tab/newline/CR are stripped before the scheme
|
|
20
|
+
// check because browsers strip them from URLs before resolving the scheme.
|
|
21
|
+
function safeHref(h) {
|
|
22
|
+
const scheme = h.replace(/[\t\n\r]/g, "").trim().match(/^([a-z][a-z0-9+.-]*):/i);
|
|
23
|
+
if (scheme && !/^(https?|mailto)$/i.test(scheme[1]))
|
|
24
|
+
return null;
|
|
25
|
+
return attr(h.trim());
|
|
26
|
+
}
|
|
27
|
+
// ---- reusable analytical block components (```<type> routes here) ----
|
|
28
|
+
function renderHeatmap(src) {
|
|
29
|
+
let dims = [];
|
|
30
|
+
let legend = "";
|
|
31
|
+
const rows = [];
|
|
32
|
+
src.split("\n").filter((s) => s.trim()).forEach((l) => {
|
|
33
|
+
const t = l.trim();
|
|
34
|
+
if (/^legend:/i.test(t)) {
|
|
35
|
+
legend = t.replace(/^legend:/i, "").trim();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (/^dims:/i.test(t)) {
|
|
39
|
+
dims = t.replace(/^dims:/i, "").split("|").map((x) => x.trim());
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (/^tier:/i.test(t)) {
|
|
43
|
+
rows.push({ tier: t.replace(/^tier:/i, "").trim() });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const self = /^self:/i.test(t);
|
|
47
|
+
const body = self ? t.replace(/^self:/i, "").trim() : t;
|
|
48
|
+
const parts = body.split("|").map((x) => x.trim());
|
|
49
|
+
const name = parts.shift();
|
|
50
|
+
rows.push({ name, self, vals: parts.map((v) => parseInt(v, 10)) });
|
|
51
|
+
});
|
|
52
|
+
const ball = (lvl) => {
|
|
53
|
+
lvl = isNaN(lvl) ? 0 : Math.max(0, Math.min(4, lvl));
|
|
54
|
+
const w = { 1: "M9,9 L9,1 A8,8 0 0 1 17,9 Z", 2: "M9,9 L9,1 A8,8 0 0 1 9,17 Z", 3: "M9,9 L9,1 A8,8 0 1 1 1,9 Z" };
|
|
55
|
+
const inner = lvl >= 4 ? '<circle cx="9" cy="9" r="8" fill="currentColor"/>' : lvl > 0 ? `<path d="${w[lvl]}" fill="currentColor"/>` : "";
|
|
56
|
+
return `<svg class="hb" width="15" height="15" viewBox="0 0 18 18" role="img" aria-label="${lvl} of 4"><circle cx="9" cy="9" r="8" fill="none" stroke="currentColor" stroke-width="1.4" opacity="0.4"/>${inner}</svg>`;
|
|
57
|
+
};
|
|
58
|
+
let html = `<div class="hmap"><table class="hm"><thead><tr><th></th>${dims.map((d) => `<th>${esc(d)}</th>`).join("")}</tr></thead><tbody>`;
|
|
59
|
+
rows.forEach((r) => {
|
|
60
|
+
if (r.tier) {
|
|
61
|
+
html += `<tr class="hm-tier"><td colspan="${dims.length + 1}">${esc(r.tier)}</td></tr>`;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
html += `<tr${r.self ? ' class="hm-self"' : ""}><td class="hm-name">${esc(r.name || "")}</td>${(r.vals || []).map((v) => `<td class="hm-cell" title="${isNaN(v) ? 0 : v} of 4">${ball(v)}</td>`).join("")}</tr>`;
|
|
65
|
+
});
|
|
66
|
+
html += `</tbody></table>${legend ? `<div class="hm-legend">${esc(legend)}</div>` : ""}</div>`;
|
|
67
|
+
return html;
|
|
68
|
+
}
|
|
69
|
+
const BLOCKS = { heatmap: renderHeatmap };
|
|
70
|
+
function mdToHtml(md) {
|
|
71
|
+
const e = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
72
|
+
const inl = (s) => e(s)
|
|
73
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
74
|
+
.replace(/\*\*([\s\S]+?)\*\*/g, "<strong>$1</strong>")
|
|
75
|
+
.replace(/(^|[^*])\*([^*\s][^*]*?)\*/g, "$1<em>$2</em>")
|
|
76
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, t, h) => { const href = safeHref(h); return href === null ? `[${t}](${h})` : `<a href="${href}"${/^https?:/i.test(href) ? ' target="_blank" rel="noopener"' : ""}>${t}</a>`; })
|
|
77
|
+
.replace(/(^|[\s(])(https?:\/\/[^\s<]+)/g, (_m, pre, u) => { let tl = ""; const mt = u.match(/[.,;:)\]]+$/); if (mt) {
|
|
78
|
+
tl = mt[0];
|
|
79
|
+
u = u.slice(0, -tl.length);
|
|
80
|
+
} return `${pre}<a href="${attr(u)}" target="_blank" rel="noopener">${u}</a>${tl}`; });
|
|
81
|
+
const L = md.replace(/\r/g, "").split("\n");
|
|
82
|
+
const out = [];
|
|
83
|
+
let n = 0;
|
|
84
|
+
const cells = (r) => r.replace(/^\s*\|?|\|?\s*$/g, "").split("|").map((c) => c.trim());
|
|
85
|
+
const cont = (s, nx) => !!s && !!s.trim() && !/^\s*([-*+]|\d+\.)\s+/.test(s) && !/^\s*(#{1,6}\s|>|```|---)/.test(s) && !(/\|/.test(s) && /^\s*\|?\s*:?-{2,}/.test(nx || ""));
|
|
86
|
+
while (n < L.length) {
|
|
87
|
+
const line = L[n];
|
|
88
|
+
if (/^\s*```/.test(line)) {
|
|
89
|
+
const lang = line.trim().slice(3).trim().toLowerCase();
|
|
90
|
+
const buf = [];
|
|
91
|
+
n++;
|
|
92
|
+
while (n < L.length && !/^\s*```/.test(L[n])) {
|
|
93
|
+
buf.push(L[n]);
|
|
94
|
+
n++;
|
|
95
|
+
}
|
|
96
|
+
n++;
|
|
97
|
+
out.push(lang && BLOCKS[lang] ? BLOCKS[lang](buf.join("\n")) : `<pre class="cb"><code>${e(buf.join("\n"))}</code></pre>`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (/^\s*(---|\*\*\*|___)\s*$/.test(line)) {
|
|
101
|
+
out.push("<hr>");
|
|
102
|
+
n++;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const h = line.match(/^(#{1,6})\s+(.*)$/);
|
|
106
|
+
if (h) {
|
|
107
|
+
const d = h[1].length;
|
|
108
|
+
const id = "s-" + h[2].toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 48);
|
|
109
|
+
out.push(`<h${d}${d >= 2 && d <= 3 ? ` id="${id}"` : ""}>${inl(h[2])}</h${d}>`);
|
|
110
|
+
n++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (/^\s*>\s?/.test(line)) {
|
|
114
|
+
const buf = [];
|
|
115
|
+
while (n < L.length && /^\s*>\s?/.test(L[n])) {
|
|
116
|
+
buf.push(L[n].replace(/^\s*>\s?/, ""));
|
|
117
|
+
n++;
|
|
118
|
+
}
|
|
119
|
+
out.push(`<blockquote>${mdToHtml(buf.join("\n"))}</blockquote>`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (/\|/.test(line) && n + 1 < L.length && /^\s*\|?\s*:?-{2,}/.test(L[n + 1])) {
|
|
123
|
+
const head = line;
|
|
124
|
+
const trows = [];
|
|
125
|
+
n += 2;
|
|
126
|
+
while (n < L.length && /\|/.test(L[n]) && L[n].trim()) {
|
|
127
|
+
trows.push(L[n]);
|
|
128
|
+
n++;
|
|
129
|
+
}
|
|
130
|
+
let t = `<table><thead><tr>${cells(head).map((c) => `<th>${inl(c)}</th>`).join("")}</tr></thead><tbody>`;
|
|
131
|
+
trows.forEach((r) => { t += `<tr>${cells(r).map((c) => `<td>${inl(c)}</td>`).join("")}</tr>`; });
|
|
132
|
+
out.push(`<div class="tblw">${t}</tbody></table></div>`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (/^\s*[-*+]\s+/.test(line)) {
|
|
136
|
+
const items = [];
|
|
137
|
+
while (n < L.length && /^\s*[-*+]\s+/.test(L[n])) {
|
|
138
|
+
let txt = L[n].replace(/^\s*[-*+]\s+/, "");
|
|
139
|
+
n++;
|
|
140
|
+
while (n < L.length && cont(L[n], L[n + 1])) {
|
|
141
|
+
txt += " " + L[n].trim();
|
|
142
|
+
n++;
|
|
143
|
+
}
|
|
144
|
+
items.push(`<li>${inl(txt)}</li>`);
|
|
145
|
+
}
|
|
146
|
+
out.push(`<ul>${items.join("")}</ul>`);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (/^\s*\d+\.\s+/.test(line)) {
|
|
150
|
+
const items = [];
|
|
151
|
+
while (n < L.length && /^\s*\d+\.\s+/.test(L[n])) {
|
|
152
|
+
let txt = L[n].replace(/^\s*\d+\.\s+/, "");
|
|
153
|
+
n++;
|
|
154
|
+
while (n < L.length && cont(L[n], L[n + 1])) {
|
|
155
|
+
txt += " " + L[n].trim();
|
|
156
|
+
n++;
|
|
157
|
+
}
|
|
158
|
+
items.push(`<li>${inl(txt)}</li>`);
|
|
159
|
+
}
|
|
160
|
+
out.push(`<ol>${items.join("")}</ol>`);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (!line.trim()) {
|
|
164
|
+
n++;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const buf = [line];
|
|
168
|
+
n++;
|
|
169
|
+
while (n < L.length && L[n].trim() && !/^\s*(#{1,6}\s|>|[-*+]\s|\d+\.\s|```|---)/.test(L[n]) && !(/\|/.test(L[n]) && /^\s*\|?\s*:?-{2,}/.test(L[n + 1] || ""))) {
|
|
170
|
+
buf.push(L[n]);
|
|
171
|
+
n++;
|
|
172
|
+
}
|
|
173
|
+
out.push(`<p>${inl(buf.join(" "))}</p>`);
|
|
174
|
+
}
|
|
175
|
+
return out.join("\n");
|
|
176
|
+
}
|
|
177
|
+
function reportChrome(md) {
|
|
178
|
+
const inl = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([\s\S]+?)\*\*/g, "<strong>$1</strong>");
|
|
179
|
+
const lines = md.replace(/\r/g, "").split("\n");
|
|
180
|
+
let n = 0;
|
|
181
|
+
let title = null;
|
|
182
|
+
let kicker = null;
|
|
183
|
+
const rows = [];
|
|
184
|
+
if (/^#\s+/.test(lines[0] || "")) {
|
|
185
|
+
title = lines[0].replace(/^#\s+/, "");
|
|
186
|
+
n = 1;
|
|
187
|
+
}
|
|
188
|
+
while (n < lines.length) {
|
|
189
|
+
const ln = lines[n].trim();
|
|
190
|
+
if (!ln) {
|
|
191
|
+
n++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (/^---+$/.test(ln)) {
|
|
195
|
+
n++;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
const m = ln.match(/^\*\*(.+?):\*\*\s*(.*)$/);
|
|
199
|
+
if (m) {
|
|
200
|
+
rows.push([m[1], m[2]]);
|
|
201
|
+
n++;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const k = ln.match(/^\*\*(.+?)\*\*$/);
|
|
205
|
+
if (k && !kicker && !rows.length) {
|
|
206
|
+
kicker = k[1];
|
|
207
|
+
n++;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
if (!rows.length && !title)
|
|
213
|
+
return { card: "", rest: md };
|
|
214
|
+
let card = '<div class="rpt-head">';
|
|
215
|
+
if (kicker)
|
|
216
|
+
card += `<div class="rpt-kicker">${inl(kicker)}</div>`;
|
|
217
|
+
if (title)
|
|
218
|
+
card += `<h1 class="rpt-title">${inl(title)}</h1>`;
|
|
219
|
+
if (rows.length)
|
|
220
|
+
card += `<dl class="rpt-meta">${rows.map((r) => `<div><dt>${inl(r[0])}</dt><dd>${inl(r[1])}</dd></div>`).join("")}</dl>`;
|
|
221
|
+
card += "</div>";
|
|
222
|
+
return { card, rest: lines.slice(n).join("\n") };
|
|
223
|
+
}
|
|
224
|
+
function docToc(md) {
|
|
225
|
+
const heads = [];
|
|
226
|
+
md.replace(/\r/g, "").split("\n").forEach((l) => { const m = l.match(/^##\s+(.+)$/); if (m)
|
|
227
|
+
heads.push(m[1].trim()); });
|
|
228
|
+
if (heads.length < 4)
|
|
229
|
+
return "";
|
|
230
|
+
const slug = (t) => "s-" + t.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 48);
|
|
231
|
+
return `<nav class="toc"><div class="toc-k">On this page</div><ul>${heads.map((h) => `<li><a href="#${slug(h)}">${esc(h)}</a></li>`).join("")}</ul></nav>`;
|
|
232
|
+
}
|
|
233
|
+
const CSS = `
|
|
234
|
+
:root{--bg:#fff;--ink:#252525;--muted:#6b6b6b;--border:#e8e8e8;--border-2:#d8d8d8;--oxblood:#8a2b28;--oxblood-soft:#faf3f2;--oxblood-line:#e6c9c6;--proof:#177f86;--proof-soft:#f0f8f8;--code-bg:#201b1a;--code-ink:#f3efee;--sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;--mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace}
|
|
235
|
+
@media(prefers-color-scheme:dark){:root{--bg:#161311;--ink:#f0ece9;--muted:#a49c98;--border:#2c2725;--border-2:#3a3431;--oxblood:#d98a86;--oxblood-soft:#241a19;--oxblood-line:#4a302e;--proof:#6fc6cc;--proof-soft:#14201f}}
|
|
236
|
+
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans)}
|
|
237
|
+
.doc{max-width:min(1000px,92vw);margin:40px auto;padding:0 24px;font-size:14.5px;line-height:1.62}
|
|
238
|
+
.doc h1{font-size:25px;font-weight:720;letter-spacing:-.02em;line-height:1.2;margin:2px 0 6px}
|
|
239
|
+
.doc h2{font-size:19px;font-weight:680;letter-spacing:-.01em;line-height:1.25;margin:30px 0 10px;padding-top:16px;border-top:1px solid var(--border)}
|
|
240
|
+
.doc hr + h2{border-top:0;padding-top:0;margin-top:18px}
|
|
241
|
+
.doc h3{font-size:16px;font-weight:660;margin:22px 0 8px;max-width:74ch}
|
|
242
|
+
.doc h4{font-size:13.5px;font-weight:660;margin:16px 0 6px;max-width:74ch}
|
|
243
|
+
.doc h5,.doc h6{font-size:12px;font-family:var(--mono);text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin:14px 0 4px}
|
|
244
|
+
.doc p{margin:0 0 12px;max-width:74ch}.doc ul,.doc ol{margin:0 0 12px;padding-left:22px}.doc li{margin:0 0 6px;max-width:72ch}.doc li::marker{color:var(--muted)}
|
|
245
|
+
.doc a{color:var(--oxblood);text-underline-offset:2px}.doc strong{font-weight:680}
|
|
246
|
+
.doc code{font-family:var(--mono);font-size:.87em;background:#00000008;padding:1px 5px;border-radius:4px}
|
|
247
|
+
.doc pre.cb{background:var(--code-bg);color:var(--code-ink);border-radius:9px;padding:12px 14px;overflow-x:auto;margin:0 0 14px;font-size:12.5px;line-height:1.5}.doc pre.cb code{background:none;padding:0;color:inherit}
|
|
248
|
+
.doc blockquote{margin:0 0 14px;padding:11px 15px;background:var(--oxblood-soft);border:1px solid var(--oxblood-line);border-radius:9px}.doc blockquote p:last-child{margin-bottom:0}
|
|
249
|
+
.doc hr{border:0;border-top:1px solid var(--border);margin:24px 0}
|
|
250
|
+
.doc table{border-collapse:collapse;width:100%;margin:0;font-size:13px}.doc .tblw{overflow-x:auto;margin:0 0 16px}
|
|
251
|
+
.doc th,.doc td{border:1px solid var(--border);padding:7px 10px;text-align:left;vertical-align:top}.doc th{background:#00000006;font-weight:660;font-size:12px}.doc tbody tr:nth-child(even){background:#00000003}
|
|
252
|
+
.rpt-kicker{font-family:var(--mono);font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--oxblood);font-weight:700;margin-bottom:7px}
|
|
253
|
+
.rpt-title{font-size:26px;font-weight:720;letter-spacing:-.02em;line-height:1.18;margin:0 0 14px}
|
|
254
|
+
.rpt-meta{display:grid;gap:9px;border:1px solid var(--border);border-radius:10px;padding:13px 15px;background:#00000003;margin:0 0 6px}.rpt-meta>div{display:grid;grid-template-columns:120px 1fr;gap:12px;font-size:13px;line-height:1.5}.rpt-meta dt{font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:700}.rpt-meta dd{margin:0}
|
|
255
|
+
.toc{border:1px solid var(--border);border-radius:10px;padding:11px 15px;margin:16px 0 22px;background:#00000003}.toc-k{font-family:var(--mono);font-size:10px;letter-spacing:.07em;text-transform:uppercase;color:var(--muted);font-weight:700;margin-bottom:7px}.toc ul{margin:0;padding:0;list-style:none;columns:2;column-gap:30px}.toc li{margin:0 0 4px;font-size:12.5px}.toc a{color:var(--oxblood);text-decoration:none}
|
|
256
|
+
.plainbox{background:var(--proof-soft);border:1px solid var(--proof-soft);border-radius:10px;padding:12px 15px;margin:2px 0 18px;font-size:14.5px;line-height:1.58}.plainbox .pb-k{font-family:var(--mono);font-size:10px;letter-spacing:.07em;text-transform:uppercase;color:var(--proof);font-weight:700;margin-bottom:5px}
|
|
257
|
+
.hmap{margin:0 0 16px;overflow-x:auto}.hmap .hm{display:table;width:auto;border-collapse:collapse;font-size:12.5px}.hmap .hm thead th{font-weight:600;font-size:11px;color:var(--muted);text-align:center;padding:7px 8px;vertical-align:bottom;line-height:1.2;min-width:94px;max-width:120px;white-space:normal}.hmap .hm thead th:first-child{min-width:0;text-align:left}.hmap .hm .hm-name{text-align:left;font-weight:600;padding:8px 16px 8px 4px;white-space:nowrap}.hmap .hm .hm-cell{text-align:center;padding:6px 8px}.hmap .hm .hb{color:var(--ink);vertical-align:middle}.hmap .hm-self{background:var(--oxblood-soft)}.hmap .hm-self .hm-name,.hmap .hm-self .hb{color:var(--oxblood)}.hmap .hm-tier td{font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);padding:14px 4px 4px;font-weight:700}.hmap .hm-legend{font-size:11.5px;color:var(--muted);margin-top:10px}
|
|
258
|
+
`;
|
|
259
|
+
/** Render a research report markdown into a self-contained HTML page. */
|
|
260
|
+
export function renderReportHtml(md) {
|
|
261
|
+
const { card, rest } = reportChrome(md);
|
|
262
|
+
const toc = docToc(rest);
|
|
263
|
+
const body = mdToHtml(rest);
|
|
264
|
+
const titleMatch = md.match(/^#\s+(.+)$/m);
|
|
265
|
+
const title = titleMatch ? titleMatch[1].replace(/[`*]/g, "") : "Research report";
|
|
266
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc(title)}</title><style>${CSS}</style></head><body><article class="doc">${card}${toc}${body}</article></body></html>`;
|
|
267
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SplitTrustStores } from "./trustEnvelope.js";
|
|
2
|
+
export interface FooSigningKey {
|
|
3
|
+
keyId: string;
|
|
4
|
+
privateKeyPem: string;
|
|
5
|
+
publicKeyPem: string;
|
|
6
|
+
}
|
|
7
|
+
export interface KeyProvisionOptions {
|
|
8
|
+
/** Override the machine-local key directory (tests only). */
|
|
9
|
+
keyDir?: string;
|
|
10
|
+
/** Timestamp for a freshly-minted key's valid_from (tests inject; prod uses now). */
|
|
11
|
+
now?: string;
|
|
12
|
+
}
|
|
13
|
+
/** The split trust stores a verifier loads: productfoo's published research keys, and an
|
|
14
|
+
* empty customer store (research receipts are foo-namespace). */
|
|
15
|
+
export declare function loadFooResearchTrustStores(root: string): SplitTrustStores;
|
|
16
|
+
/** Load productfoo's research signing key, generating it on first use. Generating also
|
|
17
|
+
* publishes the public key into the tracked trust store, so a receipt minted
|
|
18
|
+
* here verifies for anyone who clones the repo. Idempotent. */
|
|
19
|
+
export declare function loadOrCreateFooSigningKey(root: string, options?: KeyProvisionOptions): FooSigningKey;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// productfoo's research signing key — T-005 wiring, Decision 1 (owner-agreed 2026-07-20).
|
|
2
|
+
//
|
|
3
|
+
// productfoo self-signs research completion receipts. The private key is machine-local
|
|
4
|
+
// (never committed — B1); the public key is published in a repo-tracked trust
|
|
5
|
+
// store so any reviewer on a fresh clone can verify a paper's receipt. Forgery
|
|
6
|
+
// needs the private key, which never leaves the machine; verification is public.
|
|
7
|
+
//
|
|
8
|
+
// Residual (recorded, deferred): one active key, no rotation/revocation ceremony
|
|
9
|
+
// yet. The envelope already carries key status (active/retired/revoked), so
|
|
10
|
+
// rotation is additive later. This is the minimal answer for the foo namespace
|
|
11
|
+
// only — it does not resolve the customer key ceremony verdict still waits on.
|
|
12
|
+
import { createHash, createPublicKey, generateKeyPairSync } from "node:crypto";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
const defaultKeyDir = () => path.join(homedir(), ".foo", "keys");
|
|
17
|
+
const privPath = (dir) => path.join(dir, "research-signing.pem");
|
|
18
|
+
const pubPath = (dir) => path.join(dir, "research-signing.pub.pem");
|
|
19
|
+
const trustStorePath = (root) => path.join(root, "research", ".trust", "foo-research.json");
|
|
20
|
+
function keyIdFor(publicKeyPem) {
|
|
21
|
+
const der = createPublicKey(publicKeyPem).export({ type: "spki", format: "der" });
|
|
22
|
+
return `foo-research-${createHash("sha256").update(der).digest("hex").slice(0, 16)}`;
|
|
23
|
+
}
|
|
24
|
+
function readTrustStoreFile(root) {
|
|
25
|
+
const file = trustStorePath(root);
|
|
26
|
+
if (!existsSync(file))
|
|
27
|
+
return { namespace: "foo", keys: [] };
|
|
28
|
+
try {
|
|
29
|
+
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
30
|
+
if (raw && typeof raw === "object" && raw.namespace === "foo" && Array.isArray(raw.keys)) {
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/* fall through to empty — an unreadable store verifies nothing, which is safe */
|
|
36
|
+
}
|
|
37
|
+
return { namespace: "foo", keys: [] };
|
|
38
|
+
}
|
|
39
|
+
function writeTrustStore(root, store) {
|
|
40
|
+
const file = trustStorePath(root);
|
|
41
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
42
|
+
writeFileSync(file, `${JSON.stringify(store, null, 2)}\n`);
|
|
43
|
+
}
|
|
44
|
+
/** Add a public key to the tracked trust store if not already present. */
|
|
45
|
+
function publishPublicKey(root, key) {
|
|
46
|
+
const store = readTrustStoreFile(root);
|
|
47
|
+
if (store.keys.some((k) => k.key_id === key.key_id))
|
|
48
|
+
return;
|
|
49
|
+
writeTrustStore(root, { namespace: "foo", keys: [...store.keys, key] });
|
|
50
|
+
}
|
|
51
|
+
/** The split trust stores a verifier loads: productfoo's published research keys, and an
|
|
52
|
+
* empty customer store (research receipts are foo-namespace). */
|
|
53
|
+
export function loadFooResearchTrustStores(root) {
|
|
54
|
+
return {
|
|
55
|
+
customer: { namespace: "customer", keys: [] },
|
|
56
|
+
foo: readTrustStoreFile(root),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Load productfoo's research signing key, generating it on first use. Generating also
|
|
60
|
+
* publishes the public key into the tracked trust store, so a receipt minted
|
|
61
|
+
* here verifies for anyone who clones the repo. Idempotent. */
|
|
62
|
+
export function loadOrCreateFooSigningKey(root, options = {}) {
|
|
63
|
+
const dir = options.keyDir ?? defaultKeyDir();
|
|
64
|
+
if (existsSync(privPath(dir))) {
|
|
65
|
+
const privateKeyPem = readFileSync(privPath(dir), "utf8");
|
|
66
|
+
const publicKeyPem = existsSync(pubPath(dir))
|
|
67
|
+
? readFileSync(pubPath(dir), "utf8")
|
|
68
|
+
: createPublicKey(privateKeyPem).export({ type: "spki", format: "pem" }).toString();
|
|
69
|
+
const keyId = keyIdFor(publicKeyPem);
|
|
70
|
+
// ensure the tracked store carries this key (e.g. a clone missing the store)
|
|
71
|
+
publishPublicKey(root, {
|
|
72
|
+
key_id: keyId,
|
|
73
|
+
public_key_pem: publicKeyPem,
|
|
74
|
+
status: "active",
|
|
75
|
+
valid_from: options.now ?? new Date().toISOString(),
|
|
76
|
+
valid_until: null,
|
|
77
|
+
});
|
|
78
|
+
return { keyId, privateKeyPem, publicKeyPem };
|
|
79
|
+
}
|
|
80
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
81
|
+
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
|
82
|
+
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
|
|
83
|
+
const keyId = keyIdFor(publicKeyPem);
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
85
|
+
writeFileSync(privPath(dir), privateKeyPem, { mode: 0o600 });
|
|
86
|
+
writeFileSync(pubPath(dir), publicKeyPem, { mode: 0o644 });
|
|
87
|
+
publishPublicKey(root, {
|
|
88
|
+
key_id: keyId,
|
|
89
|
+
public_key_pem: publicKeyPem,
|
|
90
|
+
status: "active",
|
|
91
|
+
valid_from: options.now ?? new Date().toISOString(),
|
|
92
|
+
valid_until: null,
|
|
93
|
+
});
|
|
94
|
+
return { keyId, privateKeyPem, publicKeyPem };
|
|
95
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type RiskDimension = "irreversible" | "agentic" | "outbound";
|
|
2
|
+
/**
|
|
3
|
+
* How much the verdict is worth. A keyword hit is evidence, not proof — say so.
|
|
4
|
+
* high : the work names a behaviour only risky work has (a loop, an autonomous actor).
|
|
5
|
+
* medium : risky terms, no governing frame. Treated as risky; the usual case.
|
|
6
|
+
* low : risky terms, but the work reads as *governing* them rather than doing them.
|
|
7
|
+
*/
|
|
8
|
+
export type RiskConfidence = "high" | "medium" | "low";
|
|
9
|
+
export type RiskClassification = {
|
|
10
|
+
work: string;
|
|
11
|
+
risky: boolean;
|
|
12
|
+
dimensions: Record<RiskDimension, string[]>;
|
|
13
|
+
confidence: RiskConfidence;
|
|
14
|
+
/** True when risky terms matched but the work reads as governing, testing, or documenting them. */
|
|
15
|
+
governed_frame: boolean;
|
|
16
|
+
rationale: string;
|
|
17
|
+
override?: string;
|
|
18
|
+
spec_first_required: boolean;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Is the work *governing* the risky thing rather than *doing* it?
|
|
22
|
+
*
|
|
23
|
+
* "Add a hook that denies raw research agents" mentions agents; it does not act
|
|
24
|
+
* agentically — it is the thing that stops agents. "Add a test for the delete path"
|
|
25
|
+
* mentions delete; it deletes nothing. A bag of words cannot tell those from "delete
|
|
26
|
+
* the production table", and this classifier used to call all three risky.
|
|
27
|
+
*
|
|
28
|
+
* The tell is grammatical, and it is cheap to read: the statement OPENS with a build
|
|
29
|
+
* verb (so the risky term is an object, not the action) and names a governing
|
|
30
|
+
* construct (a hook, a gate, a test, an audit). Real risky work does not open with
|
|
31
|
+
* "add a guard that…".
|
|
32
|
+
*
|
|
33
|
+
* This is a heuristic and it will be wrong sometimes. That is why it lowers confidence
|
|
34
|
+
* and prints its reasoning instead of silently flipping the verdict.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isGovernedFrame(work: string): boolean;
|
|
37
|
+
export interface ClassifyOptions {
|
|
38
|
+
/** Block on any keyword hit — the pre-calibration behaviour. `--strict`. */
|
|
39
|
+
strict?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function classifyWork(work: string, override?: string, options?: ClassifyOptions): RiskClassification;
|
|
42
|
+
export declare function renderClassification(classification: RiskClassification, asJson: boolean, glyphsOn?: boolean): string;
|