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,448 @@
|
|
|
1
|
+
// Cheap, safe write path for Adaptive Spec Packets (asp-cli-lifecycle-v0).
|
|
2
|
+
//
|
|
3
|
+
// `foo asp` could already read packets well; this closes the write side so a
|
|
4
|
+
// human or coding agent can scaffold a packet, append an event, and move a task
|
|
5
|
+
// without hand-editing events.jsonl or hand-choosing ids. Three guarantees:
|
|
6
|
+
// - append-only: events are appended, prior ledger lines are never rewritten;
|
|
7
|
+
// - collision-safe ids: every event id is a fresh ULID, re-rolled if a
|
|
8
|
+
// (vanishingly unlikely) collision with an existing id is seen;
|
|
9
|
+
// - legible actor identity: agent writers carry full identity, humans record
|
|
10
|
+
// type "human" — and B1 refuses any write that smells like raw secrets.
|
|
11
|
+
import { randomBytes } from "node:crypto";
|
|
12
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { parse as parseYaml } from "./vendorYaml.js";
|
|
15
|
+
import { decisionRequestAuthoringComment } from "./specSections.js";
|
|
16
|
+
import { UsageError } from "./errorTaxonomy.js";
|
|
17
|
+
const TASK_STATUSES = ["todo", "doing", "blocked", "done", "dropped"];
|
|
18
|
+
// --- ULID (Crockford base32, 10 time chars + 16 random) ---------------------
|
|
19
|
+
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
20
|
+
function ulid(now = Date.now()) {
|
|
21
|
+
let time = now;
|
|
22
|
+
const timeChars = [];
|
|
23
|
+
for (let i = 0; i < 10; i += 1) {
|
|
24
|
+
timeChars.unshift(CROCKFORD[time % 32]);
|
|
25
|
+
time = Math.floor(time / 32);
|
|
26
|
+
}
|
|
27
|
+
const bytes = randomBytes(16);
|
|
28
|
+
let rand = "";
|
|
29
|
+
for (let i = 0; i < 16; i += 1)
|
|
30
|
+
rand += CROCKFORD[bytes[i] % 32];
|
|
31
|
+
return timeChars.join("") + rand;
|
|
32
|
+
}
|
|
33
|
+
function existingEventIds(eventsPath) {
|
|
34
|
+
if (!existsSync(eventsPath))
|
|
35
|
+
return new Set();
|
|
36
|
+
const ids = new Set();
|
|
37
|
+
for (const line of readFileSync(eventsPath, "utf8").split(/\r?\n/)) {
|
|
38
|
+
if (!line.trim())
|
|
39
|
+
continue;
|
|
40
|
+
try {
|
|
41
|
+
const id = JSON.parse(line).id;
|
|
42
|
+
if (typeof id === "string")
|
|
43
|
+
ids.add(id);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// A malformed prior line is a read concern, not a uniqueness concern.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return ids;
|
|
50
|
+
}
|
|
51
|
+
function freshEventId(eventsPath) {
|
|
52
|
+
const taken = existingEventIds(eventsPath);
|
|
53
|
+
let id = ulid();
|
|
54
|
+
while (taken.has(id))
|
|
55
|
+
id = ulid();
|
|
56
|
+
return id;
|
|
57
|
+
}
|
|
58
|
+
// --- B1 safety: refuse writes that carry raw private material ---------------
|
|
59
|
+
const SECRET_PATTERNS = [
|
|
60
|
+
{ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/, label: "a private key block" },
|
|
61
|
+
{ re: /\bAKIA[0-9A-Z]{16}\b/, label: "an AWS access key id" },
|
|
62
|
+
{ re: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/, label: "a GitHub token" },
|
|
63
|
+
{ re: /\bsk-[A-Za-z0-9]{20,}\b/, label: "an API secret key" },
|
|
64
|
+
{ re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, label: "a Slack token" },
|
|
65
|
+
{ re: /(?:password|passwd|secret|api[_-]?key|token|bearer)\s*[:=]\s*\S{8,}/i, label: "a secret assignment" },
|
|
66
|
+
];
|
|
67
|
+
export function findSecretLeak(text) {
|
|
68
|
+
for (const { re, label } of SECRET_PATTERNS) {
|
|
69
|
+
if (re.test(text))
|
|
70
|
+
return label;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
function guardSafety(payload) {
|
|
75
|
+
const leak = findSecretLeak(JSON.stringify(payload));
|
|
76
|
+
if (leak) {
|
|
77
|
+
throw new Error(`refusing write: payload looks like raw private material (${leak}). B1 safety blocks committing secrets into the ledger.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// AGENT_EVENT_TYPES require full agent identity in the validator; status_changed
|
|
81
|
+
// is one of them, so an agent writer must present complete identity here.
|
|
82
|
+
function env(name) {
|
|
83
|
+
const value = process.env[name];
|
|
84
|
+
return value && value.trim() !== "" ? value.trim() : undefined;
|
|
85
|
+
}
|
|
86
|
+
export function resolveActor(overrides = {}) {
|
|
87
|
+
const type = overrides.type ?? env("FOO_ACTOR_TYPE") ?? "human";
|
|
88
|
+
if (type === "agent") {
|
|
89
|
+
return {
|
|
90
|
+
type: "agent",
|
|
91
|
+
id: overrides.id ?? env("FOO_ACTOR_ID") ?? "agent:unknown",
|
|
92
|
+
provider: env("FOO_ACTOR_PROVIDER") ?? "unknown",
|
|
93
|
+
model: env("FOO_ACTOR_MODEL") ?? "unknown",
|
|
94
|
+
persona: env("FOO_ACTOR_PERSONA") ?? "unknown",
|
|
95
|
+
skills: env("FOO_ACTOR_SKILLS") ? env("FOO_ACTOR_SKILLS").split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
96
|
+
adapter: env("FOO_ACTOR_ADAPTER") ?? "unknown",
|
|
97
|
+
mode: env("FOO_ACTOR_MODE") ?? "human-in-loop",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return { type: "human", id: overrides.id ?? env("FOO_ACTOR_ID") ?? "human" };
|
|
101
|
+
}
|
|
102
|
+
function specIdOf(packetDir) {
|
|
103
|
+
const doc = parseYaml(readFileSync(path.join(packetDir, "tasks.yaml"), "utf8"));
|
|
104
|
+
const id = doc?.spec_id;
|
|
105
|
+
if (typeof id !== "string")
|
|
106
|
+
throw new Error("tasks.yaml is missing a string spec_id");
|
|
107
|
+
return id;
|
|
108
|
+
}
|
|
109
|
+
export function appendEvent(options) {
|
|
110
|
+
if (!existsSync(path.join(options.packetDir, "tasks.yaml"))) {
|
|
111
|
+
throw new Error(`not a packet directory (no tasks.yaml): ${options.packetDir}`);
|
|
112
|
+
}
|
|
113
|
+
const eventsPath = path.join(options.packetDir, "events.jsonl");
|
|
114
|
+
const now = options.now ?? new Date();
|
|
115
|
+
const data = options.data ?? {};
|
|
116
|
+
const actor = resolveActor(options.actor);
|
|
117
|
+
const event = {
|
|
118
|
+
specversion: "1.0",
|
|
119
|
+
id: freshEventId(eventsPath),
|
|
120
|
+
source: "foo-cli",
|
|
121
|
+
type: options.type,
|
|
122
|
+
subject: options.subject ?? `packet:${specIdOf(options.packetDir)}`,
|
|
123
|
+
time: now.toISOString(),
|
|
124
|
+
actor,
|
|
125
|
+
data,
|
|
126
|
+
};
|
|
127
|
+
guardSafety(event);
|
|
128
|
+
// Append-only: a trailing newline is ensured without rewriting prior lines.
|
|
129
|
+
const prefix = existsSync(eventsPath) && readFileSync(eventsPath, "utf8").length > 0 && !readFileSync(eventsPath, "utf8").endsWith("\n") ? "\n" : "";
|
|
130
|
+
appendFileSync(eventsPath, `${prefix}${JSON.stringify(event)}\n`);
|
|
131
|
+
return event;
|
|
132
|
+
}
|
|
133
|
+
function findTaskBlock(text, taskId, tasksPath) {
|
|
134
|
+
const lines = text.split("\n");
|
|
135
|
+
let start = -1;
|
|
136
|
+
let indent = "";
|
|
137
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
138
|
+
// The locator must accept every id shape the YAML reader accepts: quoted
|
|
139
|
+
// (both styles) and a trailing comment (#776).
|
|
140
|
+
const m = lines[i].match(/^(\s*)-\s+id:\s*(?:"([^"]*)"|'([^']*)'|([^\s#]+))(?:\s+#.*)?\s*$/);
|
|
141
|
+
const id = m?.[2] ?? m?.[3] ?? m?.[4];
|
|
142
|
+
if (m && id === taskId) {
|
|
143
|
+
start = i;
|
|
144
|
+
indent = m[1];
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (start === -1) {
|
|
149
|
+
// The reader accepted this id but the writer's locator could not place the
|
|
150
|
+
// block. A usage error names the packet and id and never files a capsule.
|
|
151
|
+
throw new UsageError(`task ${taskId} not found in tasks.yaml (${tasksPath})`, "E_BAD_FLAG");
|
|
152
|
+
}
|
|
153
|
+
// The block runs until the next list item at the same indent, or EOF.
|
|
154
|
+
let end = lines.length;
|
|
155
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
156
|
+
if (new RegExp(`^${indent}-\\s`).test(lines[i])) {
|
|
157
|
+
end = i;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { start, end, indent };
|
|
162
|
+
}
|
|
163
|
+
export function updateTask(options) {
|
|
164
|
+
// `--status bogus` is a typo, not a defect. As a bare Error it classified as
|
|
165
|
+
// command_error, so `foo asp task <p> T-001 --status dnoe` PAGED and filed a
|
|
166
|
+
// Bug Capsule — against productfoo's own promise that "a typo or a blocked gate never
|
|
167
|
+
// produces a capsule at all". A queue full of the reader's spelling mistakes is
|
|
168
|
+
// exactly the noise that teaches people to stop reading the queue.
|
|
169
|
+
if (!TASK_STATUSES.includes(options.status)) {
|
|
170
|
+
throw new UsageError(`invalid status: ${options.status} (expected ${TASK_STATUSES.join(", ")})`, "E_BAD_FLAG");
|
|
171
|
+
}
|
|
172
|
+
const tasksPath = path.join(options.packetDir, "tasks.yaml");
|
|
173
|
+
if (!existsSync(tasksPath))
|
|
174
|
+
throw new Error(`not a packet directory (no tasks.yaml): ${options.packetDir}`);
|
|
175
|
+
const text = readFileSync(tasksPath, "utf8");
|
|
176
|
+
const doc = parseYaml(text);
|
|
177
|
+
const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
|
|
178
|
+
const task = tasks.find((t) => t && t.id === options.taskId);
|
|
179
|
+
// A typo'd or pasted-quoted id is a usage mistake, not a foo defect —
|
|
180
|
+
// UsageError classifies as command error and never files a Bug Capsule (#776).
|
|
181
|
+
if (!task)
|
|
182
|
+
throw new UsageError(`task ${options.taskId} not found in tasks.yaml (${tasksPath})`, "E_BAD_FLAG");
|
|
183
|
+
const from = typeof task.status === "string" ? task.status : "todo";
|
|
184
|
+
const to = options.status;
|
|
185
|
+
const now = options.now ?? new Date();
|
|
186
|
+
guardSafety({ proof: options.proof, summary: options.summary, reason: options.reason });
|
|
187
|
+
// The block locator is a parallel scan over the raw text, so it can fail where
|
|
188
|
+
// the tolerant YAML reader succeeded (a quoted id, a trailing comment). Resolve
|
|
189
|
+
// it BEFORE the ledger append: on failure nothing has been written yet, so the
|
|
190
|
+
// ledger can never claim a transition tasks.yaml doesn't carry, and a corrected
|
|
191
|
+
// retry appends exactly one event (#776).
|
|
192
|
+
const block = findTaskBlock(text, options.taskId, tasksPath);
|
|
193
|
+
const lines = text.split("\n");
|
|
194
|
+
// The event is appended first so its id can be referenced from the task block;
|
|
195
|
+
// everything fallible between here and the tasks.yaml write is already resolved.
|
|
196
|
+
const event = appendEvent({
|
|
197
|
+
packetDir: options.packetDir,
|
|
198
|
+
type: "com.adaptivespec.task.status_changed",
|
|
199
|
+
subject: `task:${options.taskId}`,
|
|
200
|
+
data: {
|
|
201
|
+
from,
|
|
202
|
+
to,
|
|
203
|
+
...(options.proof ? { proof: options.proof } : {}),
|
|
204
|
+
...(options.summary ? { summary: options.summary } : {}),
|
|
205
|
+
},
|
|
206
|
+
actor: options.actor,
|
|
207
|
+
now,
|
|
208
|
+
});
|
|
209
|
+
// Build the replacement task block from the structured object, then splice it
|
|
210
|
+
// back in place so surrounding tasks and ledger lines are untouched.
|
|
211
|
+
const updated = { ...task, status: to };
|
|
212
|
+
if (to === "done") {
|
|
213
|
+
updated.completed = {
|
|
214
|
+
at: now.toISOString(),
|
|
215
|
+
by: event.actor && typeof event.actor === "object" ? event.actor.id : "human",
|
|
216
|
+
summary: options.summary ?? `Marked ${options.taskId} done.`,
|
|
217
|
+
event: event.id,
|
|
218
|
+
};
|
|
219
|
+
updated.blocked = null;
|
|
220
|
+
if (Array.isArray(updated.done_when)) {
|
|
221
|
+
updated.done_when = updated.done_when.map((check) => ({
|
|
222
|
+
...check,
|
|
223
|
+
done: true,
|
|
224
|
+
...(options.proof ? { proof: options.proof } : {}),
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (to === "blocked") {
|
|
229
|
+
updated.blocked = {
|
|
230
|
+
reason: options.reason ?? options.summary ?? `${options.taskId} is blocked.`,
|
|
231
|
+
needs: options.needs ?? ["resolve the blocker before continuing"],
|
|
232
|
+
event: event.id,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const rendered = renderTaskBlock(updated, block.indent);
|
|
236
|
+
const next = [...lines.slice(0, block.start), ...rendered, ...lines.slice(block.end)].join("\n");
|
|
237
|
+
// Temp-plus-rename so a torn write can never leave half a tasks.yaml behind a
|
|
238
|
+
// ledger event that already claims the transition (#776).
|
|
239
|
+
const temp = `${tasksPath}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
240
|
+
try {
|
|
241
|
+
writeFileSync(temp, next);
|
|
242
|
+
renameSync(temp, tasksPath);
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
if (existsSync(temp))
|
|
246
|
+
rmSync(temp, { force: true });
|
|
247
|
+
}
|
|
248
|
+
return { event, from, to };
|
|
249
|
+
}
|
|
250
|
+
// Minimal, stable YAML renderer for a single task block. Keys are emitted in the
|
|
251
|
+
// canonical packet order so a written task reads like a hand-authored one.
|
|
252
|
+
const TASK_KEY_ORDER = [
|
|
253
|
+
"id", "title", "status", "agreement", "stage", "refs", "depends_on", "owner",
|
|
254
|
+
"autonomy", "created_by", "touches", "do", "done_when", "completed", "blocked",
|
|
255
|
+
"review", "extensions",
|
|
256
|
+
];
|
|
257
|
+
function renderTaskBlock(task, indent) {
|
|
258
|
+
const lines = [];
|
|
259
|
+
const itemIndent = indent;
|
|
260
|
+
const fieldIndent = `${indent} `;
|
|
261
|
+
const keys = [...TASK_KEY_ORDER.filter((k) => k in task), ...Object.keys(task).filter((k) => !TASK_KEY_ORDER.includes(k))];
|
|
262
|
+
keys.forEach((key, i) => {
|
|
263
|
+
const prefix = i === 0 ? `${itemIndent}- ` : `${fieldIndent}`;
|
|
264
|
+
lines.push(...renderField(key, task[key], prefix, `${fieldIndent} `));
|
|
265
|
+
});
|
|
266
|
+
return lines;
|
|
267
|
+
}
|
|
268
|
+
function renderField(key, value, prefix, childIndent) {
|
|
269
|
+
if (value === null || value === undefined)
|
|
270
|
+
return [`${prefix}${key}: null`];
|
|
271
|
+
if (Array.isArray(value)) {
|
|
272
|
+
if (value.length === 0)
|
|
273
|
+
return [`${prefix}${key}: []`];
|
|
274
|
+
const lines = [`${prefix}${key}:`];
|
|
275
|
+
for (const item of value) {
|
|
276
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
277
|
+
const entries = Object.entries(item);
|
|
278
|
+
entries.forEach(([k, v], idx) => {
|
|
279
|
+
const p = idx === 0 ? `${childIndent}- ` : `${childIndent} `;
|
|
280
|
+
lines.push(...renderField(k, v, p, `${childIndent} `));
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
lines.push(`${childIndent}- ${scalar(item)}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return lines;
|
|
288
|
+
}
|
|
289
|
+
if (typeof value === "object") {
|
|
290
|
+
const entries = Object.entries(value);
|
|
291
|
+
if (entries.length === 0)
|
|
292
|
+
return [`${prefix}${key}: {}`];
|
|
293
|
+
const lines = [`${prefix}${key}:`];
|
|
294
|
+
for (const [k, v] of entries)
|
|
295
|
+
lines.push(...renderField(k, v, `${childIndent}`, `${childIndent} `));
|
|
296
|
+
return lines;
|
|
297
|
+
}
|
|
298
|
+
return [`${prefix}${key}: ${scalar(value)}`];
|
|
299
|
+
}
|
|
300
|
+
function scalar(value) {
|
|
301
|
+
if (value === null || value === undefined)
|
|
302
|
+
return "null";
|
|
303
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
304
|
+
return String(value);
|
|
305
|
+
const s = String(value);
|
|
306
|
+
if (s === "" || /[:#\[\]{}&*!|>'"%@`]/.test(s) || /^\s|\s$/.test(s) || /^(true|false|null|~)$/i.test(s) || /^[-?]/.test(s) || /^\d/.test(s)) {
|
|
307
|
+
return JSON.stringify(s);
|
|
308
|
+
}
|
|
309
|
+
return s;
|
|
310
|
+
}
|
|
311
|
+
// --- Scaffold (foo asp init) ------------------------------------------------
|
|
312
|
+
function titleCase(slug) {
|
|
313
|
+
return slug
|
|
314
|
+
.split(/[-_]/)
|
|
315
|
+
.filter(Boolean)
|
|
316
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
317
|
+
.join(" ");
|
|
318
|
+
}
|
|
319
|
+
export function initPacket(options) {
|
|
320
|
+
const now = options.now ?? new Date();
|
|
321
|
+
const name = options.name;
|
|
322
|
+
if (!name || /[^A-Za-z0-9_./-]/.test(name))
|
|
323
|
+
throw new Error("packet name must be a slug or path (letters, digits, -, _, /)");
|
|
324
|
+
// A name with a separator is treated as a path; otherwise it lands under specs/.
|
|
325
|
+
const relDir = name.includes("/") ? name : path.posix.join("specs", name);
|
|
326
|
+
const packetDir = path.resolve(options.root, relDir);
|
|
327
|
+
if (existsSync(packetDir))
|
|
328
|
+
throw new Error(`refusing to overwrite existing directory: ${relDir}`);
|
|
329
|
+
const base = path.basename(packetDir);
|
|
330
|
+
const specId = base.replace(/-/g, "_");
|
|
331
|
+
const title = titleCase(base);
|
|
332
|
+
const relSpecRef = path.relative(options.root, path.join(packetDir, "spec.md")).split(path.sep).join("/");
|
|
333
|
+
const isoDate = now.toISOString().slice(0, 10);
|
|
334
|
+
mkdirSync(packetDir, { recursive: true });
|
|
335
|
+
const specMd = `---
|
|
336
|
+
spec_id: ${specId}
|
|
337
|
+
title: ${title}
|
|
338
|
+
mode: capability
|
|
339
|
+
lifecycle: draft
|
|
340
|
+
stage: exploring
|
|
341
|
+
owner: product
|
|
342
|
+
autonomy: human-in-loop
|
|
343
|
+
updated: ${isoDate}
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
# ${title}
|
|
347
|
+
|
|
348
|
+
<!-- Identity (\`spec_id\`) is read from the YAML frontmatter above. Progress is rendered by \`foo asp status\`, never stored here. -->
|
|
349
|
+
|
|
350
|
+
## Intent
|
|
351
|
+
|
|
352
|
+
State the product bet in one or two sentences: what this packet is for and why now.
|
|
353
|
+
|
|
354
|
+
## Current Understanding
|
|
355
|
+
|
|
356
|
+
- What is already true in the repo and approved sources.
|
|
357
|
+
|
|
358
|
+
## Requirements
|
|
359
|
+
|
|
360
|
+
### R-001 First requirement
|
|
361
|
+
|
|
362
|
+
- Priority: P1
|
|
363
|
+
- Risk: medium
|
|
364
|
+
- Confidence: low
|
|
365
|
+
|
|
366
|
+
Describe the first requirement this packet must satisfy.
|
|
367
|
+
|
|
368
|
+
## Decisions
|
|
369
|
+
|
|
370
|
+
- D-001 — (open) Record decisions as they are made.
|
|
371
|
+
|
|
372
|
+
## Risks
|
|
373
|
+
|
|
374
|
+
- RISK-001 — The dominant risk and how it will be surfaced.
|
|
375
|
+
|
|
376
|
+
## Validation
|
|
377
|
+
|
|
378
|
+
- V-001 — How a reviewer confirms the work matches intent.
|
|
379
|
+
|
|
380
|
+
## Evidence
|
|
381
|
+
|
|
382
|
+
- Links to repo, approved sources, and proof as it accrues.
|
|
383
|
+
|
|
384
|
+
## Open Questions
|
|
385
|
+
|
|
386
|
+
${decisionRequestAuthoringComment()}
|
|
387
|
+
`;
|
|
388
|
+
const tasksYaml = `schema_version: "0.1"
|
|
389
|
+
spec_id: ${specId}
|
|
390
|
+
spec_ref: ${relSpecRef}
|
|
391
|
+
updated: "${now.toISOString()}"
|
|
392
|
+
|
|
393
|
+
defaults:
|
|
394
|
+
autonomy: human-in-loop
|
|
395
|
+
stale_after_days:
|
|
396
|
+
suggested: 7
|
|
397
|
+
discovered: 7
|
|
398
|
+
|
|
399
|
+
ledger:
|
|
400
|
+
segment_order:
|
|
401
|
+
- events.jsonl
|
|
402
|
+
|
|
403
|
+
tasks:
|
|
404
|
+
- id: T-001
|
|
405
|
+
title: Shape the packet intent and first requirement
|
|
406
|
+
status: todo
|
|
407
|
+
agreement: agreed
|
|
408
|
+
stage: shaping
|
|
409
|
+
refs:
|
|
410
|
+
- R-001
|
|
411
|
+
depends_on: []
|
|
412
|
+
owner: agent:any
|
|
413
|
+
autonomy: human-in-loop
|
|
414
|
+
created_by:
|
|
415
|
+
type: human
|
|
416
|
+
id: product
|
|
417
|
+
touches:
|
|
418
|
+
- ${relSpecRef}
|
|
419
|
+
do:
|
|
420
|
+
- Capture the intent and current understanding from approved sources.
|
|
421
|
+
- Write the first requirement and its acceptance check.
|
|
422
|
+
done_when:
|
|
423
|
+
- id: C-001
|
|
424
|
+
text: Intent and R-001 are written into spec.md.
|
|
425
|
+
done: false
|
|
426
|
+
proof: null
|
|
427
|
+
completed: null
|
|
428
|
+
blocked: null
|
|
429
|
+
review:
|
|
430
|
+
owner: product
|
|
431
|
+
due: null
|
|
432
|
+
stale_after_days: null
|
|
433
|
+
extensions: {}
|
|
434
|
+
`;
|
|
435
|
+
writeFileSync(path.join(packetDir, "spec.md"), specMd);
|
|
436
|
+
writeFileSync(path.join(packetDir, "tasks.yaml"), tasksYaml);
|
|
437
|
+
writeFileSync(path.join(packetDir, "events.jsonl"), "");
|
|
438
|
+
// Genesis event: subject is the packet (not a task), so no task-event matching
|
|
439
|
+
// is triggered and the scaffold validates with no manual edits.
|
|
440
|
+
appendEvent({
|
|
441
|
+
packetDir,
|
|
442
|
+
type: "com.adaptivespec.packet.created",
|
|
443
|
+
subject: `packet:${specId}`,
|
|
444
|
+
data: { summary: "Packet scaffolded by foo asp init.", spec_ref: relSpecRef },
|
|
445
|
+
now,
|
|
446
|
+
});
|
|
447
|
+
return { packetDir, specId, relSpecRef };
|
|
448
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type DeepEstimate } from "./research.js";
|
|
2
|
+
export type PitchStage = "auto" | "0to1" | "1ton" | "nx";
|
|
3
|
+
export type SourceStatus = "available" | "absent";
|
|
4
|
+
export interface PitchSources {
|
|
5
|
+
repo: SourceStatus;
|
|
6
|
+
web_free: SourceStatus;
|
|
7
|
+
web_firecrawl: SourceStatus;
|
|
8
|
+
/** Human-readable reason for the firecrawl status (present-and-use vs degrade). */
|
|
9
|
+
web_firecrawl_note: string;
|
|
10
|
+
}
|
|
11
|
+
export interface PitchPlan {
|
|
12
|
+
command: "pitch";
|
|
13
|
+
/** Pre-flight cost of the planned work — bounded by sections x panel, honest about what it excludes. */
|
|
14
|
+
estimate: DeepEstimate;
|
|
15
|
+
opportunity: string;
|
|
16
|
+
stage: PitchStage;
|
|
17
|
+
stage_note: string | null;
|
|
18
|
+
output_path: string;
|
|
19
|
+
from_research: string[];
|
|
20
|
+
to_shape: boolean;
|
|
21
|
+
posture: "read-and-synthesize-only";
|
|
22
|
+
page_budget: string;
|
|
23
|
+
sources: PitchSources;
|
|
24
|
+
evidence_directive: string;
|
|
25
|
+
skill: string;
|
|
26
|
+
references: string[];
|
|
27
|
+
sections: string[];
|
|
28
|
+
panel: string[];
|
|
29
|
+
verdict_options: ["GO", "NOT-NOW", "NO-GO"];
|
|
30
|
+
next: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function detectPitchSources(env?: NodeJS.ProcessEnv): PitchSources;
|
|
33
|
+
export declare function slugifyOpportunity(text: string): string;
|
|
34
|
+
export declare function buildPitchPlan(args: string[], env?: NodeJS.ProcessEnv, root?: string): PitchPlan;
|
|
35
|
+
export declare function runPitchCommand(root: string, args: string[], env?: NodeJS.ProcessEnv): {
|
|
36
|
+
output: string;
|
|
37
|
+
exitCode: number;
|
|
38
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// pitch-command-v0 — `foo pitch <opportunity>`: the whether & why gate.
|
|
2
|
+
//
|
|
3
|
+
// pitch is an authoring command: its intelligence lives in the skill
|
|
4
|
+
// (skills/pitch/SKILL.md) and the reviewed command doc (commands/pitch.md), not
|
|
5
|
+
// in a heavy CLI engine. This module emits a bounded, deterministic PLAN + the
|
|
6
|
+
// output path the agent then fills — the same "CLI declares, agent authors"
|
|
7
|
+
// shape research uses, minus the source fan-out. It performs no network, no
|
|
8
|
+
// mutation, no outbound action (read-and-synthesize-only, R-005).
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { estimatePitch } from "./research.js";
|
|
11
|
+
import { loadPricing } from "./loopBudget.js";
|
|
12
|
+
import { loadModelRoster, resolveActiveModel } from "./activeModel.js";
|
|
13
|
+
import { flagValue, hasFlag } from "./cliArgs.js";
|
|
14
|
+
import { resolveFirecrawlKey } from "./tokenStore.js";
|
|
15
|
+
const STAGES = {
|
|
16
|
+
"0to1": "New product (0→1): net-new solution / new market; highest innovation + market-introduction risk.",
|
|
17
|
+
"1ton": "Major enhancement (1→n): expand an existing product — new features, markets, or value proposition.",
|
|
18
|
+
nx: "Incremental improvement (n^x): refine an established product; smaller changes that compound.",
|
|
19
|
+
};
|
|
20
|
+
const SECTION_COUNT = 7; // the PR/FAQ sections below
|
|
21
|
+
const PANEL_COUNT = 2; // the pitch panel lenses below
|
|
22
|
+
// Detect which external evidence classes this run may use. Mirrors research's
|
|
23
|
+
// source-class contract so pitch can never silently skip an available class:
|
|
24
|
+
// firecrawl is capability-gated on a present key/entitlement and degrades to
|
|
25
|
+
// free web when absent — it is never a block, and private/connected content is
|
|
26
|
+
// never sent to it.
|
|
27
|
+
export function detectPitchSources(env = process.env) {
|
|
28
|
+
// Env-first, then the key saved by `foo login --firecrawl` (tokenStore).
|
|
29
|
+
const firecrawl = Boolean(resolveFirecrawlKey(env).key);
|
|
30
|
+
return {
|
|
31
|
+
repo: "available",
|
|
32
|
+
web_free: "available",
|
|
33
|
+
web_firecrawl: firecrawl ? "available" : "absent",
|
|
34
|
+
web_firecrawl_note: firecrawl
|
|
35
|
+
? "use it to verify quantified / load-bearing claims; never send private or connected content to Firecrawl"
|
|
36
|
+
: "degrades to web_free; save a key with `foo login --firecrawl <key>` to enrich (never blocks)",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function slugifyOpportunity(text) {
|
|
40
|
+
const slug = text
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
43
|
+
.replace(/^-+|-+$/g, "")
|
|
44
|
+
.slice(0, 60)
|
|
45
|
+
.replace(/-+$/g, "");
|
|
46
|
+
return slug || "opportunity";
|
|
47
|
+
}
|
|
48
|
+
function todayIso() {
|
|
49
|
+
return new Date().toISOString().slice(0, 10);
|
|
50
|
+
}
|
|
51
|
+
function parseStage(args) {
|
|
52
|
+
if (!hasFlag(args, "--stage"))
|
|
53
|
+
return "auto";
|
|
54
|
+
const value = flagValue(args, "--stage");
|
|
55
|
+
if (value === "0to1" || value === "1ton" || value === "nx")
|
|
56
|
+
return value;
|
|
57
|
+
throw new Error(`unknown --stage: ${value ?? "(missing)"}. Use one of: 0to1, 1ton, nx.`);
|
|
58
|
+
}
|
|
59
|
+
function collectFlagValues(args, flag) {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
62
|
+
if (args[i] === flag && args[i + 1] && !args[i + 1].startsWith("--"))
|
|
63
|
+
out.push(args[i + 1]);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
export function buildPitchPlan(args, env = process.env, root = process.cwd()) {
|
|
68
|
+
const stage = parseStage(args);
|
|
69
|
+
const fromResearch = collectFlagValues(args, "--from-research");
|
|
70
|
+
const flagValues = new Set([...fromResearch, stage]);
|
|
71
|
+
const opportunity = args
|
|
72
|
+
.filter((a) => !a.startsWith("--") && !flagValues.has(a))
|
|
73
|
+
.join(" ")
|
|
74
|
+
.trim();
|
|
75
|
+
const slug = slugifyOpportunity(opportunity || "opportunity");
|
|
76
|
+
const sources = detectPitchSources(env);
|
|
77
|
+
// Same honest primitive as research: the work is BOUNDED (a ~2-page paper over N
|
|
78
|
+
// sections + a panel), so a range is defensible — but it excludes what the agent
|
|
79
|
+
// actually burns executing it, and says so rather than implying a forecast.
|
|
80
|
+
const active = resolveActiveModel(env, loadModelRoster(root));
|
|
81
|
+
const estimate = estimatePitch(SECTION_COUNT, PANEL_COUNT, loadPricing(root), active.worker);
|
|
82
|
+
return {
|
|
83
|
+
command: "pitch",
|
|
84
|
+
estimate,
|
|
85
|
+
opportunity,
|
|
86
|
+
stage,
|
|
87
|
+
stage_note: stage === "auto" ? null : STAGES[stage],
|
|
88
|
+
output_path: path.posix.join(".foo", "docs", "pitch", `${todayIso()}-${slug}.md`),
|
|
89
|
+
from_research: fromResearch,
|
|
90
|
+
to_shape: args.includes("--to-shape"),
|
|
91
|
+
posture: "read-and-synthesize-only",
|
|
92
|
+
page_budget: "~2 pages, highlights only — early exec/founder read; deeper detail belongs in the full spec.",
|
|
93
|
+
sources,
|
|
94
|
+
evidence_directive: "Gather evidence across every available source class; when web_firecrawl is available, use it to verify quantified / load-bearing claims. Report per-class status in the paper; degrade honestly, never silently skip an available class.",
|
|
95
|
+
// Skill pointers are `foo skill` ARGUMENTS, never filesystem paths (R-003,
|
|
96
|
+
// alpha_tester_fixes_v0). `skills/pitch/SKILL.md` does not exist in a thin
|
|
97
|
+
// install and is not a valid `foo skill` argument either — it was a pointer
|
|
98
|
+
// the agent could not open by any route. A name resolves on disk or over the wire.
|
|
99
|
+
skill: "pitch",
|
|
100
|
+
references: ["pitch/references/prfaq-template.md", "pitch/references/opportunity-assessment.md"],
|
|
101
|
+
sections: [
|
|
102
|
+
"Press Release (worked backwards: heading, precise-customer subheading, summary + real launch date, customer-POV problem, differentiated solution, quotes)",
|
|
103
|
+
"External FAQ",
|
|
104
|
+
"Internal FAQ = staged opportunity assessment (0→1 / 1→n / n^x)",
|
|
105
|
+
"What must be true · Top 3 reasons it fails · Current solution & will they switch",
|
|
106
|
+
"Evidence (every claim typed [source · confidence] or hypothesis)",
|
|
107
|
+
"Recommendation (GO / NOT-NOW / NO-GO + numbered next steps + B3 risk read)",
|
|
108
|
+
"Panel dissent",
|
|
109
|
+
],
|
|
110
|
+
panel: ["research-tuned lenses", "PR/FAQ reviewer (customer? problem? solution? behavior change? prize?)"],
|
|
111
|
+
verdict_options: ["GO", "NOT-NOW", "NO-GO"],
|
|
112
|
+
next: "Author the Pitch paper per `foo skill pitch`, then hand off with --to-shape or point deeper work at the full spec (foo spec then spec start).",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function renderPlanText(plan) {
|
|
116
|
+
const lines = [];
|
|
117
|
+
lines.push("foo pitch — Working Backwards PR/FAQ × staged opportunity assessment");
|
|
118
|
+
lines.push("");
|
|
119
|
+
if (!plan.opportunity) {
|
|
120
|
+
lines.push("No opportunity given. Provide one, e.g.:");
|
|
121
|
+
lines.push(' foo pitch "governed PR/FAQ command for coding agents" --stage 0to1 --to-shape');
|
|
122
|
+
lines.push("");
|
|
123
|
+
lines.push("Then author the paper per `foo skill pitch` (frame the customer first, pick the stage).");
|
|
124
|
+
return `${lines.join("\n")}\n`;
|
|
125
|
+
}
|
|
126
|
+
lines.push(`Opportunity : ${plan.opportunity}`);
|
|
127
|
+
lines.push(`Stage : ${plan.stage}${plan.stage_note ? ` — ${plan.stage_note}` : " (confirm 0→1 / 1→n / n^x)"}`);
|
|
128
|
+
lines.push(`Output : ${plan.output_path} (gitignored scratch; promote to commit)`);
|
|
129
|
+
lines.push(`Budget : ${plan.page_budget}`);
|
|
130
|
+
lines.push(plan.estimate.expected_usd === null
|
|
131
|
+
? `Cost : ${plan.estimate.expected_tokens.toLocaleString("en-US")}–${plan.estimate.ceiling_tokens.toLocaleString("en-US")} tokens (dollars unavailable — ${plan.estimate.priced_with})`
|
|
132
|
+
: `Cost : ~$${plan.estimate.expected_usd.toFixed(2)}–$${plan.estimate.ceiling_usd.toFixed(2)} for the planned paper (${plan.estimate.expected_tokens.toLocaleString("en-US")}–${plan.estimate.ceiling_tokens.toLocaleString("en-US")} tokens, priced with ${plan.estimate.priced_with})`);
|
|
133
|
+
lines.push(` floor, not a forecast — excludes ${plan.estimate.excludes[0]}`);
|
|
134
|
+
lines.push(`Posture : ${plan.posture}`);
|
|
135
|
+
lines.push(`Sources : repo ${plan.sources.repo} · web_free ${plan.sources.web_free} · web_firecrawl ${plan.sources.web_firecrawl} — ${plan.sources.web_firecrawl_note}`);
|
|
136
|
+
if (plan.from_research.length)
|
|
137
|
+
lines.push(`Seeded from : ${plan.from_research.join(", ")}`);
|
|
138
|
+
lines.push(`Hand off : ${plan.to_shape ? "foo spec (carry labeled claims + dissent + verdict)" : "add --to-shape to route into foo spec"}`);
|
|
139
|
+
lines.push("");
|
|
140
|
+
lines.push("Author these sections (run `foo skill pitch` and execute as written):");
|
|
141
|
+
for (const s of plan.sections)
|
|
142
|
+
lines.push(` - ${s}`);
|
|
143
|
+
lines.push("");
|
|
144
|
+
lines.push(`Panel (never blocks): ${plan.panel.join("; ")}`);
|
|
145
|
+
lines.push(`Verdict: one of ${plan.verdict_options.join(" / ")}. ${plan.next}`);
|
|
146
|
+
return `${lines.join("\n")}\n`;
|
|
147
|
+
}
|
|
148
|
+
export function runPitchCommand(root, args, env = process.env) {
|
|
149
|
+
const plan = buildPitchPlan(args, env, root);
|
|
150
|
+
if (args.includes("--json")) {
|
|
151
|
+
return { output: `${JSON.stringify(plan, null, 2)}\n`, exitCode: 0 };
|
|
152
|
+
}
|
|
153
|
+
return { output: renderPlanText(plan), exitCode: 0 };
|
|
154
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type MemoEntry<V> = {
|
|
2
|
+
key: string;
|
|
3
|
+
value: V;
|
|
4
|
+
};
|
|
5
|
+
/** Cheap staleness key for a file's current content; "absent" when missing. */
|
|
6
|
+
export declare function fileStatKey(file: string): string;
|
|
7
|
+
export declare function readMemoized<V>(store: Map<string, MemoEntry<V>>, file: string, statKey: string): V | undefined;
|
|
8
|
+
export declare function writeMemoized<V>(store: Map<string, MemoEntry<V>>, file: string, statKey: string, value: V): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Per-process memoization for small config files (cli_performance-v0 R-002):
|
|
2
|
+
// within one invocation a config file is read and parsed at most once, keyed
|
|
3
|
+
// on (mtime, size) so an external write between reads in the same process —
|
|
4
|
+
// a test fixture, another tool — invalidates naturally. Absent files memoize
|
|
5
|
+
// under the "absent" key.
|
|
6
|
+
import { statSync } from "node:fs";
|
|
7
|
+
/** Cheap staleness key for a file's current content; "absent" when missing. */
|
|
8
|
+
export function fileStatKey(file) {
|
|
9
|
+
try {
|
|
10
|
+
const st = statSync(file);
|
|
11
|
+
return `${st.mtimeMs}:${st.size}`;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return "absent";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function readMemoized(store, file, statKey) {
|
|
18
|
+
const entry = store.get(file);
|
|
19
|
+
return entry && entry.key === statKey ? entry.value : undefined;
|
|
20
|
+
}
|
|
21
|
+
export function writeMemoized(store, file, statKey, value) {
|
|
22
|
+
store.set(file, { key: statKey, value });
|
|
23
|
+
}
|