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,718 @@
|
|
|
1
|
+
// One grammar for the spec.md structures every surface reads (packet-grammar-v1
|
|
2
|
+
// R-001). Three modules used to carry three private regexes that disagreed with
|
|
3
|
+
// each other AND with the packets on disk — packetCorrections required
|
|
4
|
+
// `### R-001:` (colon) while real packets also write `### R-001 — Title`, and it
|
|
5
|
+
// only saw open questions written as `- **OQ-1** ...` when most packets write
|
|
6
|
+
// plain `- ` bullets with no ID at all. A parser that disagrees with the corpus
|
|
7
|
+
// never fires, and a link that never fires looks like a working feature with
|
|
8
|
+
// nothing to say.
|
|
9
|
+
//
|
|
10
|
+
// The contract here is one token table, three profiles (R-001):
|
|
11
|
+
// tolerant read — parseAnchors/parseRequirementHeadings accept every form
|
|
12
|
+
// that lives in specs/ today; legacy forms are legal
|
|
13
|
+
// forever (R-003: read everything, rewrite nothing)
|
|
14
|
+
// strict lint — lintAnchors flags duplicate/malformed IDs and
|
|
15
|
+
// requirement-shaped lines the tolerant read cannot anchor
|
|
16
|
+
// (the sixth-grammar catcher, R-004); findings are
|
|
17
|
+
// advisory here — blocking is the commit gate's call
|
|
18
|
+
// canonical write — formatters + nextAnchorId emit the one blessed output
|
|
19
|
+
// form (`### R-001 Title`, `- V-001 — text`); writers are
|
|
20
|
+
// output-strict while the reader stays input-liberal
|
|
21
|
+
//
|
|
22
|
+
// Formats pinned by the legacy corpus (all live in specs/ today):
|
|
23
|
+
// ### R-001: Title ### R-001 — Title ### R-001 Title ### R1 — Title
|
|
24
|
+
// - **R-001** Title - **R1 — Title.** text
|
|
25
|
+
// - Plain question with no ID
|
|
26
|
+
// - **OQ-1** — text - **OQ-1 — Title.** text - ~~OQ-1~~ resolved
|
|
27
|
+
// ## Validation Bar (section headings may carry suffixes)
|
|
28
|
+
import { defineQuestion, projectQuestion } from "./questionModel.js";
|
|
29
|
+
import { redactText } from "./redactEngine.js";
|
|
30
|
+
export const ANCHOR_KINDS = ["R", "OQ", "D", "V", "RISK", "H"];
|
|
31
|
+
const ANCHOR_TABLE = {
|
|
32
|
+
R: { home: "Requirements", idRe: /R-?\d+/ },
|
|
33
|
+
OQ: { home: "Open Questions", idRe: /OQ-?\d+/ },
|
|
34
|
+
D: { home: "Decisions", idRe: /D-?\d+/ },
|
|
35
|
+
// V is dash-MANDATORY, unlike the others: "V0"/"V1" are release words in
|
|
36
|
+
// half the catalog's prose ("V0 adds no public command"), and the tolerant
|
|
37
|
+
// form turned two such bullets into duplicate anchors — a blocking false
|
|
38
|
+
// positive caught during the catalog migration. R keeps the tolerant form
|
|
39
|
+
// because undashed R1-style anchors exist in real packets; undashed
|
|
40
|
+
// V-anchors do not.
|
|
41
|
+
V: { home: "Validation", idRe: /V-\d+/ },
|
|
42
|
+
RISK: { home: "Risks", idRe: /RISK-?\d+/ },
|
|
43
|
+
// Hypotheses are optional. When present they share the same tolerant reader
|
|
44
|
+
// and lint authority as the older anchors, but their absence is never a
|
|
45
|
+
// grammar finding and migration never manufactures them.
|
|
46
|
+
H: { home: "Hypotheses", idRe: /H-?\d+/ },
|
|
47
|
+
};
|
|
48
|
+
// Separators accepted after an ID on input: colon, em/en dash, hyphen, or bare
|
|
49
|
+
// whitespace. Requirement writers use the visually quiet bare-heading form;
|
|
50
|
+
// the other anchor kinds keep their em-dash bullets (input-liberal,
|
|
51
|
+
// output-strict — R-002 as amended by the catalog-format normalization).
|
|
52
|
+
const SEPARATOR_CLASS = "[:—–-]";
|
|
53
|
+
// `R-?\d+` (dash optional) is the tolerant form packetVerify already accepted.
|
|
54
|
+
const REQUIREMENT_HEADING_RE = new RegExp(`^###\\s+(R-?\\d+)\\b\\s*${SEPARATOR_CLASS}?\\s*(.*)$`);
|
|
55
|
+
// Bullet-form anchors as the corpus writes them: `- R-001 — text`,
|
|
56
|
+
// `- **R-001** text`, `- **OQ-1 — Title.** text`, `- ~~OQ-1~~ resolved`,
|
|
57
|
+
// `- D-001 (owner) text`. The wrapper and separator are both optional.
|
|
58
|
+
function bulletAnchorRe(kind) {
|
|
59
|
+
return new RegExp(`^\\s*-\\s+(?:\\*\\*|~~)?\\s*(${ANCHOR_TABLE[kind].idRe.source})\\b`, "i");
|
|
60
|
+
}
|
|
61
|
+
function stripBulletDressing(text, idMatchLength) {
|
|
62
|
+
return text
|
|
63
|
+
.slice(idMatchLength)
|
|
64
|
+
.replace(new RegExp(`^(?:\\*\\*|~~)?\\s*${SEPARATOR_CLASS}?\\s*`), "")
|
|
65
|
+
.replace(/\*\*|~~/g, "")
|
|
66
|
+
.trim();
|
|
67
|
+
}
|
|
68
|
+
// Prefix section matching: `## Validation` claims "## Validation Bar" too —
|
|
69
|
+
// the T-001 audit found real packets titling sections with suffixes.
|
|
70
|
+
function sectionMatches(headingLine, home) {
|
|
71
|
+
return new RegExp(`^##\\s+${home}\\b`, "i").test(headingLine);
|
|
72
|
+
}
|
|
73
|
+
function fenceMarker(line) {
|
|
74
|
+
const match = line.match(/^\s*(`{3,}|~{3,})/);
|
|
75
|
+
return match ? { marker: match[1][0], length: match[1].length } : null;
|
|
76
|
+
}
|
|
77
|
+
function closesFence(line, fence) {
|
|
78
|
+
const match = line.match(/^\s*(`{3,}|~{3,})\s*$/);
|
|
79
|
+
return Boolean(match && match[1][0] === fence.marker && match[1].length >= fence.length);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Tolerant read over the whole spec: every anchor of every kind, with line
|
|
83
|
+
* numbers. Requirement headings count anywhere (historical behavior); bullet
|
|
84
|
+
* anchors count only inside their home section.
|
|
85
|
+
*/
|
|
86
|
+
export function parseAnchors(specText) {
|
|
87
|
+
const lines = specText.split(/\r?\n/);
|
|
88
|
+
const out = [];
|
|
89
|
+
let section = null;
|
|
90
|
+
let inComment = false;
|
|
91
|
+
let inFence = null;
|
|
92
|
+
lines.forEach((line, i) => {
|
|
93
|
+
if (inFence) {
|
|
94
|
+
if (closesFence(line, inFence))
|
|
95
|
+
inFence = null;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (inComment) {
|
|
99
|
+
if (line.includes("-->"))
|
|
100
|
+
inComment = false;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (line.includes("<!--")) {
|
|
104
|
+
if (!line.includes("-->"))
|
|
105
|
+
inComment = true;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const marker = fenceMarker(line);
|
|
109
|
+
if (marker) {
|
|
110
|
+
inFence = marker;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (/^##\s+/.test(line) && !/^###/.test(line)) {
|
|
114
|
+
section = line;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const heading = line.match(REQUIREMENT_HEADING_RE);
|
|
118
|
+
if (heading) {
|
|
119
|
+
out.push({ kind: "R", id: heading[1], line: i + 1, form: "heading", title: heading[2].trim() });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!section)
|
|
123
|
+
return;
|
|
124
|
+
for (const kind of ANCHOR_KINDS) {
|
|
125
|
+
if (!sectionMatches(section, ANCHOR_TABLE[kind].home))
|
|
126
|
+
continue;
|
|
127
|
+
const m = line.match(bulletAnchorRe(kind));
|
|
128
|
+
if (!m)
|
|
129
|
+
continue;
|
|
130
|
+
// Kinds cannot collide: each is matched only inside its own home
|
|
131
|
+
// section, so `R-001` under Risks or `RISK-001` under Requirements
|
|
132
|
+
// simply parses as nothing.
|
|
133
|
+
out.push({
|
|
134
|
+
kind,
|
|
135
|
+
id: m[1],
|
|
136
|
+
line: i + 1,
|
|
137
|
+
form: "bullet",
|
|
138
|
+
title: stripBulletDressing(line.replace(/^\s*-\s+/, ""), m[0].replace(/^\s*-\s+/, "").length),
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* All requirements — heading form anywhere plus bullet form inside
|
|
147
|
+
* `## Requirements`. Kept as the historical export name; every surface that
|
|
148
|
+
* links or counts requirements goes through here (R-001: no private regexes).
|
|
149
|
+
*/
|
|
150
|
+
export function parseRequirementHeadings(specText) {
|
|
151
|
+
return parseAnchors(specText)
|
|
152
|
+
.filter((a) => a.kind === "R")
|
|
153
|
+
.map((a) => ({ id: a.id, title: a.title }));
|
|
154
|
+
}
|
|
155
|
+
export function requirementIds(specText) {
|
|
156
|
+
const ids = new Set();
|
|
157
|
+
for (const h of parseRequirementHeadings(specText))
|
|
158
|
+
ids.add(h.id);
|
|
159
|
+
return [...ids];
|
|
160
|
+
}
|
|
161
|
+
// Bullets inside `## Open Questions` only — a plain `- ` bullet anywhere else
|
|
162
|
+
// in spec.md is not an open question. Continuation lines of multi-line bullets
|
|
163
|
+
// are ignored; the first line is the question.
|
|
164
|
+
export function parseOpenQuestions(specText) {
|
|
165
|
+
const lines = specText.split(/\r?\n/);
|
|
166
|
+
const out = [];
|
|
167
|
+
let inSection = false;
|
|
168
|
+
let inComment = false;
|
|
169
|
+
let inFence = null;
|
|
170
|
+
for (const line of lines) {
|
|
171
|
+
if (inFence) {
|
|
172
|
+
if (closesFence(line, inFence))
|
|
173
|
+
inFence = null;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (inComment) {
|
|
177
|
+
if (line.includes("-->"))
|
|
178
|
+
inComment = false;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (line.includes("<!--")) {
|
|
182
|
+
if (!line.includes("-->"))
|
|
183
|
+
inComment = true;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const marker = fenceMarker(line);
|
|
187
|
+
if (marker) {
|
|
188
|
+
inFence = marker;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (/^##\s+/.test(line) && !/^###/.test(line)) {
|
|
192
|
+
inSection = sectionMatches(line, "Open Questions");
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (!inSection)
|
|
196
|
+
continue;
|
|
197
|
+
// Only top-level bullets are questions. Nested fields belong to a
|
|
198
|
+
// canonical decision request and must never inflate the open-question
|
|
199
|
+
// count or surface as contextless questions of their own.
|
|
200
|
+
const m = line.match(/^-\s+(.*\S)\s*$/);
|
|
201
|
+
if (!m)
|
|
202
|
+
continue;
|
|
203
|
+
const text = m[1];
|
|
204
|
+
// Leading id in any of its dressings: **OQ-1**, **OQ-1 — Title.**, ~~OQ-1~~
|
|
205
|
+
const idMatch = text.match(/^(?:\*\*|~~)?\s*(OQ-?\d+)/i);
|
|
206
|
+
const id = idMatch ? idMatch[1].toUpperCase().replace(/^OQ(\d)/, "OQ-$1") : null;
|
|
207
|
+
let title = text;
|
|
208
|
+
if (idMatch)
|
|
209
|
+
title = stripBulletDressing(text, idMatch[0].length);
|
|
210
|
+
title = title.replace(/\*\*|~~/g, "").trim();
|
|
211
|
+
out.push({ id, text, title: title || text });
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
function nestedHypothesisField(line, field) {
|
|
216
|
+
const match = line.match(new RegExp(`^\\s{2,}-\\s+${field}:\\s*(.*?)\\s*$`, "i"));
|
|
217
|
+
if (!match)
|
|
218
|
+
return null;
|
|
219
|
+
const value = match[1]
|
|
220
|
+
.replace(/[\u0000-\u001f\u007f]/g, " ")
|
|
221
|
+
.replace(/[*_`~]/g, "")
|
|
222
|
+
.replace(/\s+/g, " ")
|
|
223
|
+
.trim();
|
|
224
|
+
return value || null;
|
|
225
|
+
}
|
|
226
|
+
/** Optional authored hypotheses, in document order, with no inferred fields. */
|
|
227
|
+
export function parseHypotheses(specText) {
|
|
228
|
+
const lines = specText.split(/\r?\n/);
|
|
229
|
+
const anchors = parseAnchors(specText).filter((anchor) => anchor.kind === "H");
|
|
230
|
+
return anchors.map((anchor) => {
|
|
231
|
+
const block = [];
|
|
232
|
+
for (let i = anchor.line; i < lines.length; i += 1) {
|
|
233
|
+
if (/^##\s+/.test(lines[i]) || /^-\s+\S/.test(lines[i]))
|
|
234
|
+
break;
|
|
235
|
+
block.push(lines[i]);
|
|
236
|
+
}
|
|
237
|
+
const state = block.map((line) => nestedHypothesisField(line, "State")).find((value) => value !== null) ?? null;
|
|
238
|
+
const validatedBy = block.map((line) => nestedHypothesisField(line, "Validated by")).find((value) => value !== null) ?? null;
|
|
239
|
+
const evidence = block.map((line) => nestedHypothesisField(line, "Evidence")).find((value) => value !== null) ?? null;
|
|
240
|
+
return {
|
|
241
|
+
id: anchor.id,
|
|
242
|
+
statement: anchor.title,
|
|
243
|
+
state,
|
|
244
|
+
validationRefs: validatedBy?.match(/\bV-\d+\b/gi)?.map((id) => id.toUpperCase()) ?? [],
|
|
245
|
+
evidence,
|
|
246
|
+
line: anchor.line,
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function plainDecisionText(value) {
|
|
251
|
+
const plain = value
|
|
252
|
+
.replace(/[\u0000-\u001f\u007f]/g, " ")
|
|
253
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
254
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
|
255
|
+
.replace(/[*_`~]/g, "")
|
|
256
|
+
.replace(/\s+/g, " ")
|
|
257
|
+
.trim();
|
|
258
|
+
return redactText(plain, { profiles: ["secrets"] }).redactedText;
|
|
259
|
+
}
|
|
260
|
+
function fieldValue(line, field) {
|
|
261
|
+
const match = line.match(new RegExp(`^\\s{2,}-\\s+${field}:\\s*(.*?)\\s*$`, "i"));
|
|
262
|
+
return match ? plainDecisionText(match[1]) : null;
|
|
263
|
+
}
|
|
264
|
+
function decisionFinding(code, line, text, detail) {
|
|
265
|
+
return { code, line, text, detail };
|
|
266
|
+
}
|
|
267
|
+
function parseDecisionBlock(bullet, startLine, lines, validTaskIds) {
|
|
268
|
+
const findings = [];
|
|
269
|
+
const state = lines.map((line) => fieldValue(line, "State")).find((value) => value !== null);
|
|
270
|
+
if (state?.toLowerCase() !== "needs-decision")
|
|
271
|
+
return { request: null, findings };
|
|
272
|
+
const add = (code, detail) => {
|
|
273
|
+
findings.push(decisionFinding(code, startLine, bullet.text, detail));
|
|
274
|
+
};
|
|
275
|
+
if (!bullet.id)
|
|
276
|
+
add("decision.missing_id", "a needs-decision block requires a stable OQ id, for example `OQ-001`");
|
|
277
|
+
const whyNow = lines.map((line) => fieldValue(line, "Why now")).find((value) => value !== null) ?? null;
|
|
278
|
+
const unblocksRaw = lines.map((line) => fieldValue(line, "Unblocks")).find((value) => value !== null) ?? null;
|
|
279
|
+
const evidence = lines.map((line) => fieldValue(line, "Evidence")).find((value) => value !== null) ?? null;
|
|
280
|
+
const confidenceRaw = lines.map((line) => fieldValue(line, "Confidence")).find((value) => value !== null) ?? null;
|
|
281
|
+
const dissent = lines.map((line) => fieldValue(line, "Dissent")).find((value) => value !== null) ?? undefined;
|
|
282
|
+
const answerEffect = lines.map((line) => fieldValue(line, "Answer effect")).find((value) => value !== null) ?? null;
|
|
283
|
+
const recommendationRaw = lines.map((line) => fieldValue(line, "Recommendation")).find((value) => value !== null) ?? null;
|
|
284
|
+
for (const [field, value] of [
|
|
285
|
+
["Why now", whyNow],
|
|
286
|
+
["Unblocks", unblocksRaw],
|
|
287
|
+
["Recommendation", recommendationRaw],
|
|
288
|
+
["Evidence", evidence],
|
|
289
|
+
["Confidence", confidenceRaw],
|
|
290
|
+
["Answer effect", answerEffect],
|
|
291
|
+
]) {
|
|
292
|
+
if (!value)
|
|
293
|
+
add("decision.missing_field", `needs-decision is missing \`${field}:\`; copy the canonical block from a fresh foo spec packet`);
|
|
294
|
+
}
|
|
295
|
+
let confidence = null;
|
|
296
|
+
const normalizedConfidence = confidenceRaw?.toLowerCase();
|
|
297
|
+
if (normalizedConfidence === "low" || normalizedConfidence === "medium" || normalizedConfidence === "high") {
|
|
298
|
+
confidence = normalizedConfidence;
|
|
299
|
+
}
|
|
300
|
+
else if (confidenceRaw) {
|
|
301
|
+
add("decision.invalid_confidence", "`Confidence:` must be low, medium, or high");
|
|
302
|
+
}
|
|
303
|
+
let unblocks = null;
|
|
304
|
+
if (unblocksRaw?.toLowerCase() === "packet") {
|
|
305
|
+
unblocks = { kind: "packet" };
|
|
306
|
+
}
|
|
307
|
+
else if (unblocksRaw && /^T-\d+$/i.test(unblocksRaw)) {
|
|
308
|
+
const id = unblocksRaw.toUpperCase();
|
|
309
|
+
if (validTaskIds && !validTaskIds.has(id)) {
|
|
310
|
+
add("decision.unknown_unblocks", `\`Unblocks: ${id}\` does not name a task in tasks.yaml`);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
unblocks = { kind: "task", id };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else if (unblocksRaw) {
|
|
317
|
+
add("decision.unknown_unblocks", "`Unblocks:` must be `packet` or a task id such as `T-002`");
|
|
318
|
+
}
|
|
319
|
+
const options = [];
|
|
320
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
321
|
+
const match = lines[i].match(/^\s{2,}-\s+Option\s+([A-Za-z0-9][A-Za-z0-9_-]*):\s*(.*?)\s*$/i);
|
|
322
|
+
if (!match)
|
|
323
|
+
continue;
|
|
324
|
+
const option = {
|
|
325
|
+
id: match[1],
|
|
326
|
+
label: plainDecisionText(match[2]),
|
|
327
|
+
outcome: null,
|
|
328
|
+
line: startLine + i + 1,
|
|
329
|
+
};
|
|
330
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
331
|
+
if (/^\s{2,}-\s+Option\s+/i.test(lines[j]))
|
|
332
|
+
break;
|
|
333
|
+
const outcome = lines[j].match(/^\s{4,}-\s+Outcome:\s*(.*?)\s*$/i);
|
|
334
|
+
if (outcome) {
|
|
335
|
+
option.outcome = plainDecisionText(outcome[1]);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
if (/^\s{2,}-\s+\S/.test(lines[j]))
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
options.push(option);
|
|
342
|
+
}
|
|
343
|
+
if (options.length < 2)
|
|
344
|
+
add("decision.invalid_option", "a decision needs at least two `Option <id>:` choices");
|
|
345
|
+
const optionIds = options.map((option) => option.id);
|
|
346
|
+
if (new Set(optionIds).size !== optionIds.length)
|
|
347
|
+
add("decision.invalid_option", "option ids must be unique");
|
|
348
|
+
for (const option of options) {
|
|
349
|
+
if (!option.label)
|
|
350
|
+
add("decision.invalid_option", `option \`${option.id}\` is missing a plain-English label`);
|
|
351
|
+
if (!option.outcome)
|
|
352
|
+
add("decision.invalid_option", `option \`${option.id}\` is missing its nested \`Outcome:\``);
|
|
353
|
+
}
|
|
354
|
+
if (!options.some((option) => /\b(?:pause|defer|not now|do not|don't|stop)\b/i.test(option.label))) {
|
|
355
|
+
add("decision.missing_opt_out", "include a real pause, defer, not-now, do-not-do, or stop option");
|
|
356
|
+
}
|
|
357
|
+
let recommendedOptionId = null;
|
|
358
|
+
let belief = null;
|
|
359
|
+
if (recommendationRaw) {
|
|
360
|
+
const match = recommendationRaw.match(/^([A-Za-z0-9][A-Za-z0-9_-]*)\s*(?:—|–|-)\s*(.+)$/);
|
|
361
|
+
if (!match) {
|
|
362
|
+
add("decision.missing_field", "`Recommendation:` must be `<option-id> — <plain-English rationale>`");
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
recommendedOptionId = match[1];
|
|
366
|
+
belief = plainDecisionText(match[2]);
|
|
367
|
+
if (!optionIds.includes(recommendedOptionId)) {
|
|
368
|
+
add("decision.invalid_option", `recommended option \`${recommendedOptionId}\` does not match an Option id`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (findings.length > 0 || !bullet.id || !whyNow || !unblocks || !answerEffect || !evidence || !belief || !recommendedOptionId || !confidence) {
|
|
373
|
+
return { request: null, findings };
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const question = projectQuestion(defineQuestion({
|
|
377
|
+
header: "Decision needed",
|
|
378
|
+
prompt: plainDecisionText(bullet.title),
|
|
379
|
+
options: options.map((option) => ({ id: option.id, label: option.label, description: option.outcome ?? undefined })),
|
|
380
|
+
recommendedOptionId,
|
|
381
|
+
judgment: { belief, evidence, confidence, ...(dissent ? { dissent } : {}) },
|
|
382
|
+
}));
|
|
383
|
+
return {
|
|
384
|
+
request: { id: bullet.id, why_now: whyNow, unblocks, answer_effect: answerEffect, question },
|
|
385
|
+
findings,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
add("decision.invalid_option", error instanceof Error ? error.message : String(error));
|
|
390
|
+
return { request: null, findings };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
export function parseDecisionRequests(specText, options = {}) {
|
|
394
|
+
const validTaskIds = options.taskIds ? new Set([...options.taskIds].map((id) => id.toUpperCase())) : null;
|
|
395
|
+
const allLines = specText.split(/\r?\n/);
|
|
396
|
+
const requests = [];
|
|
397
|
+
const findings = [];
|
|
398
|
+
let inSection = false;
|
|
399
|
+
let inComment = false;
|
|
400
|
+
// Same fence tracking as parseOpenQuestions (#775): a fenced canonical
|
|
401
|
+
// example is documentation and must never mint a live decision request.
|
|
402
|
+
let inFence = null;
|
|
403
|
+
for (let i = 0; i < allLines.length; i += 1) {
|
|
404
|
+
const line = allLines[i];
|
|
405
|
+
if (inFence) {
|
|
406
|
+
if (closesFence(line, inFence))
|
|
407
|
+
inFence = null;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (inComment) {
|
|
411
|
+
if (line.includes("-->"))
|
|
412
|
+
inComment = false;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (line.includes("<!--")) {
|
|
416
|
+
if (!line.includes("-->"))
|
|
417
|
+
inComment = true;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const marker = fenceMarker(line);
|
|
421
|
+
if (marker) {
|
|
422
|
+
inFence = marker;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (/^##\s+/.test(line) && !/^###/.test(line)) {
|
|
426
|
+
inSection = sectionMatches(line, "Open Questions");
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (!inSection)
|
|
430
|
+
continue;
|
|
431
|
+
const top = line.match(/^-\s+(.*\S)\s*$/);
|
|
432
|
+
if (!top)
|
|
433
|
+
continue;
|
|
434
|
+
const text = top[1];
|
|
435
|
+
const idMatch = text.match(/^(?:\*\*|~~)?\s*(OQ-?\d+)/i);
|
|
436
|
+
const id = idMatch ? idMatch[1].toUpperCase().replace(/^OQ(\d)/, "OQ-$1") : null;
|
|
437
|
+
let title = idMatch ? stripBulletDressing(text, idMatch[0].length) : text;
|
|
438
|
+
title = plainDecisionText(title);
|
|
439
|
+
const bullet = { id, text, title: title || plainDecisionText(text) };
|
|
440
|
+
const block = [];
|
|
441
|
+
let j = i + 1;
|
|
442
|
+
for (; j < allLines.length; j += 1) {
|
|
443
|
+
const blockLine = allLines[j];
|
|
444
|
+
// This scan advances the outer cursor, so it must consume fences too.
|
|
445
|
+
// Fenced bullets and fields are examples, never block boundaries or
|
|
446
|
+
// live decision metadata. Keep scanning for the next unfenced bullet.
|
|
447
|
+
if (inFence) {
|
|
448
|
+
if (closesFence(blockLine, inFence))
|
|
449
|
+
inFence = null;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
// Comments can contain unmatched example fences. As in the outer
|
|
453
|
+
// scan, comments are inert unless they are themselves inside a fence.
|
|
454
|
+
if (inComment) {
|
|
455
|
+
if (blockLine.includes("-->"))
|
|
456
|
+
inComment = false;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (blockLine.includes("<!--")) {
|
|
460
|
+
if (!blockLine.includes("-->"))
|
|
461
|
+
inComment = true;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const blockFence = fenceMarker(blockLine);
|
|
465
|
+
if (blockFence) {
|
|
466
|
+
inFence = blockFence;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (/^##\s+/.test(blockLine) || /^-\s+\S/.test(blockLine))
|
|
470
|
+
break;
|
|
471
|
+
block.push(blockLine);
|
|
472
|
+
}
|
|
473
|
+
const parsed = parseDecisionBlock(bullet, i + 1, block, validTaskIds);
|
|
474
|
+
if (parsed.request)
|
|
475
|
+
requests.push(parsed.request);
|
|
476
|
+
findings.push(...parsed.findings);
|
|
477
|
+
i = j - 1;
|
|
478
|
+
}
|
|
479
|
+
return { requests, findings };
|
|
480
|
+
}
|
|
481
|
+
// One copyable example for every packet scaffold. It is deliberately inside
|
|
482
|
+
// an HTML comment: a fresh packet teaches the contract without manufacturing
|
|
483
|
+
// a live question that status or a UI could mistake for user input.
|
|
484
|
+
export function decisionRequestAuthoringComment() {
|
|
485
|
+
return `<!--
|
|
486
|
+
Add the block below only when active work truly cannot continue without a
|
|
487
|
+
human choice. Ordinary uncertainties stay as plain deferred notes.
|
|
488
|
+
|
|
489
|
+
- OQ-001 — Which option should we take?
|
|
490
|
+
- State: needs-decision
|
|
491
|
+
- Why now: Work cannot continue until this choice is made.
|
|
492
|
+
- Unblocks: T-001
|
|
493
|
+
- Option continue: Continue with the smallest reversible path
|
|
494
|
+
- Outcome: The agent continues T-001 with that path.
|
|
495
|
+
- Option pause: Pause this work
|
|
496
|
+
- Outcome: The agent records the pause and does not build it.
|
|
497
|
+
- Recommendation: continue — This is the smallest reversible path.
|
|
498
|
+
- Evidence: Name the repo fact that supports the recommendation.
|
|
499
|
+
- Confidence: medium
|
|
500
|
+
- Dissent: Name the strongest reason to choose differently.
|
|
501
|
+
- Answer effect: The selected option becomes the instruction for the active task.
|
|
502
|
+
-->`;
|
|
503
|
+
}
|
|
504
|
+
// Duplicate detection compares numeric identity, not spelling: `R-001` and
|
|
505
|
+
// `R1` in one packet are the same anchor written twice, which is exactly the
|
|
506
|
+
// ambiguity the grammar exists to prevent.
|
|
507
|
+
function normalizedKey(kind, id) {
|
|
508
|
+
const num = id.replace(/^[A-Z]+-?/i, "");
|
|
509
|
+
return `${kind}:${Number(num)}`;
|
|
510
|
+
}
|
|
511
|
+
// A line that *tried* to be an anchor and failed: right shape, broken ID
|
|
512
|
+
// (`### R-01a — ...`). Kept deliberately narrow — a gate that fires on the
|
|
513
|
+
// mention of a risk rather than the risk is one people learn to override on
|
|
514
|
+
// sight (AGENTS.md, three arrivals of that lesson).
|
|
515
|
+
// Digits are mandatory before the broken tail: `R-01a` and a dangling `R-`
|
|
516
|
+
// are anchor attempts; `Risk mitigation` and `Rendering` are prose.
|
|
517
|
+
const MALFORMED_HEADING_RE = /^###\s+(R-?\d+[A-Za-z]\w*|R-)\s/;
|
|
518
|
+
const MALFORMED_BULLET_RE = /^\s*-\s+(?:\*\*)?((?:OQ|D|V|RISK|H)-?\d+[A-Za-z]\w*|(?:OQ|D|V|RISK|H)-)\s/;
|
|
519
|
+
export function requirementGrammarOf(anchors) {
|
|
520
|
+
const rs = anchors.filter((a) => a.kind === "R");
|
|
521
|
+
return {
|
|
522
|
+
headingForm: rs.some((a) => a.form === "heading"),
|
|
523
|
+
bulletForm: rs.some((a) => a.form === "bullet"),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function isRequirementCandidate(line, grammar) {
|
|
527
|
+
// Headings are candidates unless the packet writes requirements as bullets
|
|
528
|
+
// only (there a bare `###` is a subsection title). Bullets are candidates
|
|
529
|
+
// only where bullets are how requirements are written. An anchor-less
|
|
530
|
+
// section defaults to heading candidacy — the scaffold's own form.
|
|
531
|
+
const headingCandidate = !(!grammar.headingForm && grammar.bulletForm) && /^###\s+\S/.test(line);
|
|
532
|
+
const bulletCandidate = grammar.bulletForm && !grammar.headingForm && /^\s*-\s+\*\*\S/.test(line);
|
|
533
|
+
return headingCandidate || bulletCandidate;
|
|
534
|
+
}
|
|
535
|
+
export function lintAnchors(specText) {
|
|
536
|
+
const findings = [];
|
|
537
|
+
const anchors = parseAnchors(specText);
|
|
538
|
+
const anchoredLines = new Set(anchors.map((a) => a.line));
|
|
539
|
+
const grammar = requirementGrammarOf(anchors);
|
|
540
|
+
const seen = new Map();
|
|
541
|
+
for (const a of anchors) {
|
|
542
|
+
const key = normalizedKey(a.kind, a.id);
|
|
543
|
+
const first = seen.get(key);
|
|
544
|
+
if (first) {
|
|
545
|
+
findings.push({
|
|
546
|
+
code: "grammar.duplicate_id",
|
|
547
|
+
line: a.line,
|
|
548
|
+
text: `${a.id}`,
|
|
549
|
+
detail: `${a.id} duplicates ${first.id} (line ${first.line}) — same anchor written twice`,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
seen.set(key, a);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
const lines = specText.split(/\r?\n/);
|
|
557
|
+
let section = null;
|
|
558
|
+
let inComment = false;
|
|
559
|
+
let inFence = null;
|
|
560
|
+
lines.forEach((line, i) => {
|
|
561
|
+
if (inFence) {
|
|
562
|
+
if (closesFence(line, inFence))
|
|
563
|
+
inFence = null;
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (inComment) {
|
|
567
|
+
if (line.includes("-->"))
|
|
568
|
+
inComment = false;
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (line.includes("<!--")) {
|
|
572
|
+
if (!line.includes("-->"))
|
|
573
|
+
inComment = true;
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const marker = fenceMarker(line);
|
|
577
|
+
if (marker) {
|
|
578
|
+
inFence = marker;
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (/^##\s+/.test(line) && !/^###/.test(line)) {
|
|
582
|
+
section = line;
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (anchoredLines.has(i + 1))
|
|
586
|
+
return;
|
|
587
|
+
const malformed = line.match(MALFORMED_HEADING_RE) ?? line.match(MALFORMED_BULLET_RE);
|
|
588
|
+
if (malformed) {
|
|
589
|
+
findings.push({
|
|
590
|
+
code: "grammar.malformed_id",
|
|
591
|
+
line: i + 1,
|
|
592
|
+
text: line.trim(),
|
|
593
|
+
detail: `"${malformed[1]}" is anchor-shaped but not a valid ID`,
|
|
594
|
+
});
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (!section)
|
|
598
|
+
return;
|
|
599
|
+
if (sectionMatches(section, "Requirements") && isRequirementCandidate(line, grammar)) {
|
|
600
|
+
findings.push({
|
|
601
|
+
code: "grammar.unparsed_candidate",
|
|
602
|
+
line: i + 1,
|
|
603
|
+
text: line.trim(),
|
|
604
|
+
detail: "requirement-shaped line the grammar cannot anchor — a new format drifting in",
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
return findings;
|
|
609
|
+
}
|
|
610
|
+
// ---------------------------------------------------------------------------
|
|
611
|
+
// Canonical write profile (R-002, R-006). Output-strict: writers emit the
|
|
612
|
+
// blessed form only. Migration and the scaffold both mint through here.
|
|
613
|
+
// ---------------------------------------------------------------------------
|
|
614
|
+
/** Next free ID for a kind, zero-padded (`R-001` style), never reusing a
|
|
615
|
+
* number any existing anchor holds in any spelling. */
|
|
616
|
+
export function nextAnchorId(kind, existing) {
|
|
617
|
+
let max = 0;
|
|
618
|
+
for (const a of existing) {
|
|
619
|
+
if (a.kind !== kind)
|
|
620
|
+
continue;
|
|
621
|
+
const num = Number(a.id.replace(/^[A-Z]+-?/i, ""));
|
|
622
|
+
if (Number.isFinite(num) && num > max)
|
|
623
|
+
max = num;
|
|
624
|
+
}
|
|
625
|
+
return `${kind}-${String(max + 1).padStart(3, "0")}`;
|
|
626
|
+
}
|
|
627
|
+
export function formatRequirementHeading(id, title) {
|
|
628
|
+
return `### ${id} ${title}`;
|
|
629
|
+
}
|
|
630
|
+
export function formatAnchorBullet(id, text) {
|
|
631
|
+
return `- ${id} — ${text}`;
|
|
632
|
+
}
|
|
633
|
+
// Where the minted token lands: after `- ` for plain bullets, after the bold
|
|
634
|
+
// or strike opener when the bullet leads with one, after `### ` for headings.
|
|
635
|
+
// The bullet arm keeps any leading indentation (`\s*`) so an indented bullet —
|
|
636
|
+
// which candidacy and parseAnchors both accept — is stamped after its marker,
|
|
637
|
+
// not at column 0 where the minted ID would stop reading as an anchor.
|
|
638
|
+
function insertionColumn(line) {
|
|
639
|
+
const m = line.match(/^(###\s+|\s*-\s+(?:\*\*|~~)?)/);
|
|
640
|
+
return m ? m[0].length : 0;
|
|
641
|
+
}
|
|
642
|
+
/** Remove exactly the recorded tokens — the mechanical proof's inverse. */
|
|
643
|
+
export function stripInsertions(text, insertions) {
|
|
644
|
+
const lines = text.split("\n");
|
|
645
|
+
for (const ins of insertions) {
|
|
646
|
+
const line = lines[ins.line - 1];
|
|
647
|
+
lines[ins.line - 1] = line.slice(0, ins.column) + line.slice(ins.column + ins.token.length);
|
|
648
|
+
}
|
|
649
|
+
return lines.join("\n");
|
|
650
|
+
}
|
|
651
|
+
export function migrateSpecText(specText) {
|
|
652
|
+
const lines = specText.split("\n");
|
|
653
|
+
const existing = parseAnchors(specText);
|
|
654
|
+
const anchoredLines = new Set(existing.map((a) => a.line));
|
|
655
|
+
const grammar = requirementGrammarOf(existing);
|
|
656
|
+
const insertions = [];
|
|
657
|
+
// Counters continue past every number already used in any spelling.
|
|
658
|
+
const minted = [...existing];
|
|
659
|
+
let section = null;
|
|
660
|
+
let inComment = false;
|
|
661
|
+
let inFence = null;
|
|
662
|
+
lines.forEach((raw, i) => {
|
|
663
|
+
if (inFence) {
|
|
664
|
+
if (closesFence(raw, inFence))
|
|
665
|
+
inFence = null;
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (inComment) {
|
|
669
|
+
if (raw.includes("-->"))
|
|
670
|
+
inComment = false;
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (raw.includes("<!--")) {
|
|
674
|
+
if (!raw.includes("-->"))
|
|
675
|
+
inComment = true;
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const marker = fenceMarker(raw);
|
|
679
|
+
if (marker) {
|
|
680
|
+
inFence = marker;
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (/^##\s+/.test(raw) && !/^###/.test(raw)) {
|
|
684
|
+
section = raw;
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (!section || anchoredLines.has(i + 1))
|
|
688
|
+
return;
|
|
689
|
+
let kind = null;
|
|
690
|
+
for (const k of ANCHOR_KINDS) {
|
|
691
|
+
if (sectionMatches(section, ANCHOR_TABLE[k].home))
|
|
692
|
+
kind = k;
|
|
693
|
+
}
|
|
694
|
+
if (!kind)
|
|
695
|
+
return;
|
|
696
|
+
// Hypotheses are optional authored claims. Migration must never backfill
|
|
697
|
+
// or invent one from an unanchored prose bullet.
|
|
698
|
+
if (kind === "H")
|
|
699
|
+
return;
|
|
700
|
+
// Requirements are stamped only on requirement-shaped lines (headings and
|
|
701
|
+
// bold-led bullets) — a `- Priority: P1` metadata bullet is not a
|
|
702
|
+
// requirement and must never become one. Other home sections stamp every
|
|
703
|
+
// top-level bullet: in Decisions/Risks/Validation/Open Questions the
|
|
704
|
+
// bullets ARE the items.
|
|
705
|
+
const stampable = kind === "R" ? isRequirementCandidate(raw, grammar) : /^-\s+\S/.test(raw);
|
|
706
|
+
if (!stampable)
|
|
707
|
+
return;
|
|
708
|
+
const id = nextAnchorId(kind, minted);
|
|
709
|
+
const token = kind === "R" && raw.startsWith("###") ? `${id} ` : `${id} — `;
|
|
710
|
+
const column = insertionColumn(raw);
|
|
711
|
+
lines[i] = raw.slice(0, column) + token + raw.slice(column);
|
|
712
|
+
insertions.push({ line: i + 1, column, id, token });
|
|
713
|
+
minted.push({ kind, id, line: i + 1, form: raw.startsWith("###") ? "heading" : "bullet", title: "" });
|
|
714
|
+
});
|
|
715
|
+
const text = lines.join("\n");
|
|
716
|
+
const proofOk = stripInsertions(text, insertions) === specText;
|
|
717
|
+
return { text, insertions, proofOk };
|
|
718
|
+
}
|