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,770 @@
|
|
|
1
|
+
// Derived packet status (R-019 / D-019 / D-020).
|
|
2
|
+
//
|
|
3
|
+
// Progress is never stored in spec.md — it is derived here from tasks.yaml on
|
|
4
|
+
// every call. The default path is fast: it reads only tasks.yaml and spec.md,
|
|
5
|
+
// never git or the network, and never emits percent-complete. Source-authority,
|
|
6
|
+
// work-evidence (R-011), and last-meaningful-learning require git or event
|
|
7
|
+
// replay and are deferred to an explicit --deep flag (T-015); the fast view
|
|
8
|
+
// marks them "not yet checked" rather than faking them.
|
|
9
|
+
//
|
|
10
|
+
// derivePacketStatus() is the shared reader: the text view, the agent --json
|
|
11
|
+
// view, and any future cross-packet index all render from this one object.
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { parse as parseYaml } from "./vendorYaml.js";
|
|
15
|
+
import { isRetired, isTerminal, latestVerdict } from "./packetLifecycle.js";
|
|
16
|
+
import { glyphFor, STATUS_GLYPHS, withGlyph } from "./statusGlyphs.js";
|
|
17
|
+
import { parseDecisionRequests, parseOpenQuestions, requirementIds, } from "./specSections.js";
|
|
18
|
+
export const PACKET_STATUS_SCHEMA_VERSION = "1.1";
|
|
19
|
+
const CLAIMED_HUMAN_LABEL = STATUS_GLYPHS.claimed.label;
|
|
20
|
+
function asRecord(value) {
|
|
21
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
22
|
+
}
|
|
23
|
+
function str(value) {
|
|
24
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
|
|
25
|
+
}
|
|
26
|
+
// Header identity is best-effort: packets mid-migration (T-013) still carry a
|
|
27
|
+
// flat `Spec ID:` / `Status:` header; restamped packets carry YAML frontmatter
|
|
28
|
+
// (D-017). Identity drives display only — counts never come from the header.
|
|
29
|
+
function parseHeader(specText) {
|
|
30
|
+
let title = null;
|
|
31
|
+
let lifecycle = null;
|
|
32
|
+
let stage = null;
|
|
33
|
+
let mode = null;
|
|
34
|
+
if (specText.startsWith("---")) {
|
|
35
|
+
const end = specText.indexOf("\n---", 3);
|
|
36
|
+
if (end !== -1) {
|
|
37
|
+
try {
|
|
38
|
+
const front = asRecord(parseYaml(specText.slice(3, end)));
|
|
39
|
+
title = str(front.title);
|
|
40
|
+
lifecycle = str(front.lifecycle) ?? str(front.status);
|
|
41
|
+
stage = str(front.stage);
|
|
42
|
+
mode = str(front.mode);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// fall through to flat parsing
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!title) {
|
|
50
|
+
const m = specText.match(/^#\s+(.+?)\s*$/m);
|
|
51
|
+
title = m ? m[1].trim() : null;
|
|
52
|
+
}
|
|
53
|
+
if (!lifecycle)
|
|
54
|
+
lifecycle = specText.match(/^Status:\s*(\S+)/m)?.[1] ?? null;
|
|
55
|
+
if (!stage)
|
|
56
|
+
stage = specText.match(/^Stage:\s*(\S+)/m)?.[1] ?? null;
|
|
57
|
+
if (!mode)
|
|
58
|
+
mode = specText.match(/^Mode:\s*(\S+)/m)?.[1] ?? null;
|
|
59
|
+
return { title, lifecycle, stage, mode };
|
|
60
|
+
}
|
|
61
|
+
function openQuestionTexts(specText) {
|
|
62
|
+
// Defensive de-dupe: a derived "needs attention" surface must never show the
|
|
63
|
+
// same question twice (duplicate content reads as a broken derivation).
|
|
64
|
+
return [...new Set(parseOpenQuestions(specText).map((q) => q.text))];
|
|
65
|
+
}
|
|
66
|
+
// Single-packet derivation from already-read inputs (cli-performance-v0 R-001):
|
|
67
|
+
// the index reads and parses each packet's tasks.yaml and spec.md exactly once
|
|
68
|
+
// and threads the results through here, instead of every derivation re-reading
|
|
69
|
+
// the files. Does no fs I/O of its own except the retired-packet verdict tail.
|
|
70
|
+
export function derivePacketStatusFromInputs(packetDir, inputs, options = {}) {
|
|
71
|
+
const now = options.now ?? new Date();
|
|
72
|
+
const doc = asRecord(inputs.tasksDoc);
|
|
73
|
+
const rawTasks = doc.tasks;
|
|
74
|
+
if (!Array.isArray(rawTasks))
|
|
75
|
+
throw new Error("tasks.yaml.tasks must be an array");
|
|
76
|
+
const specText = inputs.specText;
|
|
77
|
+
const specRequirements = new Set(requirementIds(specText));
|
|
78
|
+
const requirementPriorities = {};
|
|
79
|
+
for (const m of specText.matchAll(/^###\s+(R-?\d+)[^\n]*\r?\n(?:[ \t]*\r?\n)*-\s*Priority:\s*(P\d)/gm)) {
|
|
80
|
+
requirementPriorities[m[1]] = m[2];
|
|
81
|
+
}
|
|
82
|
+
const header = parseHeader(specText);
|
|
83
|
+
const counts = { total: 0, done: 0, doing: 0, blocked: 0, todo: 0, dropped: 0 };
|
|
84
|
+
const proof = { checks_total: 0, checks_done: 0 };
|
|
85
|
+
// Checks on live (non-dropped) tasks that are not yet done WITH a proof —
|
|
86
|
+
// the receipt gap that keeps lifecycle_effective at in_flight (R-001).
|
|
87
|
+
let unprovenChecks = 0;
|
|
88
|
+
const completed = [];
|
|
89
|
+
const blocked = [];
|
|
90
|
+
const stale = [];
|
|
91
|
+
const coveredReqs = new Set();
|
|
92
|
+
const statusById = new Map();
|
|
93
|
+
const tasks = [];
|
|
94
|
+
for (const raw of rawTasks) {
|
|
95
|
+
const t = asRecord(raw);
|
|
96
|
+
const id = str(t.id);
|
|
97
|
+
if (!id)
|
|
98
|
+
continue;
|
|
99
|
+
const status = (str(t.status) ?? "todo");
|
|
100
|
+
const title = str(t.title) ?? "";
|
|
101
|
+
const agreement = str(t.agreement) ?? "agreed";
|
|
102
|
+
const owner = str(t.owner);
|
|
103
|
+
const refs = Array.isArray(t.refs) ? t.refs.map(str).filter((x) => x !== null) : [];
|
|
104
|
+
const dependsOn = Array.isArray(t.depends_on) ? t.depends_on.map(str).filter((x) => x !== null) : [];
|
|
105
|
+
statusById.set(id, status);
|
|
106
|
+
// Per-task proof receipt: how many done_when checks are met. The packet
|
|
107
|
+
// total is their sum; the index uses the per-task counts to tell a proven
|
|
108
|
+
// task (every check met) from a merely done one.
|
|
109
|
+
const doneWhen = Array.isArray(t.done_when) ? t.done_when : [];
|
|
110
|
+
const checksTotal = doneWhen.length;
|
|
111
|
+
const checksDone = doneWhen.filter((dw) => asRecord(dw).done === true).length;
|
|
112
|
+
proof.checks_total += checksTotal;
|
|
113
|
+
proof.checks_done += checksDone;
|
|
114
|
+
if (status !== "dropped") {
|
|
115
|
+
// Done without a proof string is a claim, not a receipt — it does not
|
|
116
|
+
// count toward effective-done. Dropped work is excluded: its unmet
|
|
117
|
+
// checks were retired with the task, not left open.
|
|
118
|
+
unprovenChecks += doneWhen.filter((dw) => {
|
|
119
|
+
const check = asRecord(dw);
|
|
120
|
+
return check.done !== true || str(check.proof) === null;
|
|
121
|
+
}).length;
|
|
122
|
+
}
|
|
123
|
+
tasks.push({ id, title, status, agreement, owner, refs, dependsOn, checksDone, checksTotal });
|
|
124
|
+
counts.total += 1;
|
|
125
|
+
if (status === "done")
|
|
126
|
+
counts.done += 1;
|
|
127
|
+
else if (status === "doing")
|
|
128
|
+
counts.doing += 1;
|
|
129
|
+
else if (status === "blocked")
|
|
130
|
+
counts.blocked += 1;
|
|
131
|
+
else if (status === "dropped")
|
|
132
|
+
counts.dropped += 1;
|
|
133
|
+
else
|
|
134
|
+
counts.todo += 1;
|
|
135
|
+
// Requirement coverage counts requirements referenced by at least one
|
|
136
|
+
// non-rejected task (agreement !== "rejected"), intersected with the
|
|
137
|
+
// requirements that actually exist in spec.md.
|
|
138
|
+
if (agreement !== "rejected") {
|
|
139
|
+
for (const ref of refs)
|
|
140
|
+
if (specRequirements.has(ref))
|
|
141
|
+
coveredReqs.add(ref);
|
|
142
|
+
}
|
|
143
|
+
if (status === "done")
|
|
144
|
+
completed.push(id);
|
|
145
|
+
if (status === "blocked") {
|
|
146
|
+
blocked.push({ id, title, reason: str(t.blocked) });
|
|
147
|
+
}
|
|
148
|
+
if (agreement === "suggested" || agreement === "discovered") {
|
|
149
|
+
const due = str(asRecord(t.review).due);
|
|
150
|
+
const overdue = due !== null && !Number.isNaN(Date.parse(due)) && Date.parse(due) < now.getTime();
|
|
151
|
+
stale.push({ id, title, agreement, overdue });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Next decision: the first todo/doing task whose dependencies are all done.
|
|
155
|
+
// A retired packet (parked/killed) has no next decision by definition — its
|
|
156
|
+
// remaining tasks are not backlog, they are covered by the verdict.
|
|
157
|
+
const retired = isRetired(header.lifecycle);
|
|
158
|
+
let next = null;
|
|
159
|
+
for (const t of tasks) {
|
|
160
|
+
if (retired)
|
|
161
|
+
break;
|
|
162
|
+
if (t.status !== "todo" && t.status !== "doing")
|
|
163
|
+
continue;
|
|
164
|
+
const ready = t.dependsOn.every((dep) => statusById.get(dep) === "done");
|
|
165
|
+
if (ready) {
|
|
166
|
+
next = { id: t.id, title: t.title, kind: t.status };
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// The verdict reason is a bounded ledger-tail read, paid only by retired
|
|
171
|
+
// packets so the common path stays tasks.yaml + spec.md only.
|
|
172
|
+
const verdict = retired
|
|
173
|
+
? { state: header.lifecycle, ...verdictReason(packetDir, header.lifecycle) }
|
|
174
|
+
: null;
|
|
175
|
+
// Drift (foo-status-glyphs-v0 T-005, resolves OQ1): the header still claims an
|
|
176
|
+
// unfinished lifecycle while every task is done — a restamp nudge. Computed in
|
|
177
|
+
// the shared reader so asp status and asp index read the same signal. Mirrors
|
|
178
|
+
// the existing packetIndex restamp flag.
|
|
179
|
+
const restamp = header.lifecycle === "draft" && counts.total > 0 && counts.done === counts.total;
|
|
180
|
+
// Effective lifecycle (packet-lifecycle-truth-v0 R-001): derived purely from
|
|
181
|
+
// tasks.yaml, never from the header. Effectively done means every task is
|
|
182
|
+
// done (dropped work excluded, at least one done) and every done_when check
|
|
183
|
+
// on live tasks is done with a proof present; any open/doing/blocked task or
|
|
184
|
+
// missing receipt keeps the packet in flight. Read-side only — nothing here
|
|
185
|
+
// mutates the header; `foo asp verdict <packet> done` is the governed
|
|
186
|
+
// transition (R-003).
|
|
187
|
+
const effectivelyDone = counts.total > 0 &&
|
|
188
|
+
counts.done > 0 &&
|
|
189
|
+
counts.done + counts.dropped === counts.total &&
|
|
190
|
+
unprovenChecks === 0;
|
|
191
|
+
// Drift is a contradiction between claim and receipt (R-002): a non-terminal
|
|
192
|
+
// header over effectively-done work, or a completion stamp (done/built) over
|
|
193
|
+
// work that is not proofs-complete. Terminal retirement (parked/killed) is a
|
|
194
|
+
// recorded decision, never drift — even at 0% tasks.
|
|
195
|
+
const lifecycleDrift = effectivelyDone
|
|
196
|
+
? !isTerminal(header.lifecycle)
|
|
197
|
+
: header.lifecycle === "done" || header.lifecycle === "built";
|
|
198
|
+
const decisionParse = parseDecisionRequests(specText, { taskIds: statusById.keys() });
|
|
199
|
+
const blockedTask = tasks.find((task) => task.status === "blocked") ?? null;
|
|
200
|
+
const selectedDecision = (() => {
|
|
201
|
+
for (const task of tasks) {
|
|
202
|
+
if (task.status !== "blocked")
|
|
203
|
+
continue;
|
|
204
|
+
const request = decisionParse.requests.find((candidate) => candidate.unblocks.kind === "task" && candidate.unblocks.id === task.id);
|
|
205
|
+
if (request)
|
|
206
|
+
return request;
|
|
207
|
+
}
|
|
208
|
+
return decisionParse.requests.find((candidate) => candidate.unblocks.kind === "packet") ?? null;
|
|
209
|
+
})();
|
|
210
|
+
// One source of action truth. A retirement verdict wins over every stale
|
|
211
|
+
// question below it; lifecycle contradictions win over completion labels;
|
|
212
|
+
// only a complete canonical request can become a decision.
|
|
213
|
+
const nextAction = isRetired(header.lifecycle)
|
|
214
|
+
? { kind: "none", reason: header.lifecycle === "parked" ? "This work is paused." : "This work was stopped." }
|
|
215
|
+
: lifecycleDrift
|
|
216
|
+
? { kind: "reconcile", reason: lifecycleDriftText({ lifecycle_effective: effectivelyDone ? "done" : "in_flight", lifecycle_stored: header.lifecycle }) }
|
|
217
|
+
: header.lifecycle === "done" || header.lifecycle === "built"
|
|
218
|
+
? { kind: "none", reason: "This work is complete." }
|
|
219
|
+
: selectedDecision
|
|
220
|
+
? { kind: "decide", decision: selectedDecision }
|
|
221
|
+
: blockedTask
|
|
222
|
+
? { kind: "unblock", task: { id: blockedTask.id, title: blockedTask.title, reason: blocked.find((item) => item.id === blockedTask.id)?.reason ?? null } }
|
|
223
|
+
: next
|
|
224
|
+
? { kind: "task", task: { id: next.id, title: next.title, status: next.kind } }
|
|
225
|
+
: { kind: "none", reason: "No action is needed." };
|
|
226
|
+
return {
|
|
227
|
+
packet: path.basename(packetDir),
|
|
228
|
+
spec_id: str(doc.spec_id) ?? path.basename(packetDir),
|
|
229
|
+
title: header.title,
|
|
230
|
+
lifecycle: header.lifecycle,
|
|
231
|
+
lifecycle_stored: header.lifecycle,
|
|
232
|
+
lifecycle_effective: effectivelyDone ? "done" : "in_flight",
|
|
233
|
+
lifecycle_drift: lifecycleDrift,
|
|
234
|
+
stage: header.stage,
|
|
235
|
+
mode: header.mode,
|
|
236
|
+
tasks: counts,
|
|
237
|
+
proof,
|
|
238
|
+
requirements: { total: specRequirements.size, covered: coveredReqs.size },
|
|
239
|
+
requirement_priorities: requirementPriorities,
|
|
240
|
+
completed,
|
|
241
|
+
task_list: tasks.map((t) => ({ id: t.id, title: t.title, status: t.status, checksDone: t.checksDone, checksTotal: t.checksTotal, agreement: t.agreement, owner: t.owner, deps: t.dependsOn, refs: t.refs })),
|
|
242
|
+
blocked,
|
|
243
|
+
stale,
|
|
244
|
+
open_questions: openQuestionTexts(specText),
|
|
245
|
+
next_action: nextAction,
|
|
246
|
+
decision_findings: decisionParse.findings,
|
|
247
|
+
next,
|
|
248
|
+
drift: { restamp },
|
|
249
|
+
verdict,
|
|
250
|
+
deep_checked: false,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
// File-reading entry point: the shared reader for single-packet views (asp
|
|
254
|
+
// status). The index (packetIndex.ts) prefers derivePacketStatusFromInputs so
|
|
255
|
+
// each packet's files are read and parsed once per run (cli-performance-v0 R-001).
|
|
256
|
+
export function derivePacketStatus(packetDir, options = {}) {
|
|
257
|
+
const tasksPath = path.join(packetDir, "tasks.yaml");
|
|
258
|
+
const specPath = path.join(packetDir, "spec.md");
|
|
259
|
+
if (!existsSync(tasksPath))
|
|
260
|
+
throw new Error(`tasks.yaml missing in ${packetDir}`);
|
|
261
|
+
if (!existsSync(specPath))
|
|
262
|
+
throw new Error(`spec.md missing in ${packetDir}`);
|
|
263
|
+
return derivePacketStatusFromInputs(packetDir, {
|
|
264
|
+
tasksDoc: parseYaml(readFileSync(tasksPath, "utf8")),
|
|
265
|
+
specText: readFileSync(specPath, "utf8"),
|
|
266
|
+
}, options);
|
|
267
|
+
}
|
|
268
|
+
// The frontmatter stamp is canonical; the ledger event supplies the reason.
|
|
269
|
+
// A hand-stamped packet with no event still renders (reason null) rather than
|
|
270
|
+
// erroring — the missing trail is itself legible.
|
|
271
|
+
function verdictReason(packetDir, state) {
|
|
272
|
+
const record = latestVerdict(packetDir);
|
|
273
|
+
if (record && record.state === state)
|
|
274
|
+
return { reason: record.reason, at: record.at };
|
|
275
|
+
return { reason: null, at: null };
|
|
276
|
+
}
|
|
277
|
+
function pad(label) {
|
|
278
|
+
return (label + " ").slice(0, 11);
|
|
279
|
+
}
|
|
280
|
+
// Exported so the initiative brief (initiative-brief-v0 R-001) renders the same
|
|
281
|
+
// sentence as the CLI card instead of writing a second one. A projection that
|
|
282
|
+
// re-phrases the action is a second reader with extra steps.
|
|
283
|
+
export function nextActionText(action) {
|
|
284
|
+
switch (action.kind) {
|
|
285
|
+
case "none":
|
|
286
|
+
return `none — ${action.reason}`;
|
|
287
|
+
case "reconcile":
|
|
288
|
+
return action.reason;
|
|
289
|
+
case "decide":
|
|
290
|
+
return `decide — ${action.decision.question.prompt}`;
|
|
291
|
+
case "unblock":
|
|
292
|
+
return `unblock ${action.task.title}${action.task.reason ? ` (${action.task.reason})` : ""} · ${action.task.id}`;
|
|
293
|
+
case "task":
|
|
294
|
+
return `${action.task.title} · ${action.task.id}`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// One sentence naming both sides of a lifecycle contradiction (R-002). The
|
|
298
|
+
// word "drift" leads so the line is greppable with or without the glyph.
|
|
299
|
+
function lifecycleDriftText(s) {
|
|
300
|
+
return s.lifecycle_effective === "done"
|
|
301
|
+
? `drift — lifecycle says ${s.lifecycle_stored ?? "(unset)"}, but every task is done and proven; record it: foo asp verdict <packet> done --reason <text>`
|
|
302
|
+
: `drift — lifecycle says ${s.lifecycle_stored}, but the work is not proofs-complete; finish it or reopen with foo asp verdict <packet> active --reason <text>`;
|
|
303
|
+
}
|
|
304
|
+
const DEEP_SCAN_LIMIT = 2000;
|
|
305
|
+
const SECRET_HINT = /(-----BEGIN [A-Z ]*PRIVATE KEY-----|\bAKIA[0-9A-Z]{16}\b|\bgh[pousr]_[A-Za-z0-9]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b|(?:password|secret|api[_-]?key|token|bearer)\s*[:=]\s*\S{8,})/i;
|
|
306
|
+
function redact(text) {
|
|
307
|
+
return SECRET_HINT.test(text) ? "[redacted: looked like a secret]" : text;
|
|
308
|
+
}
|
|
309
|
+
function deriveDeep(packetDir) {
|
|
310
|
+
const eventsPath = path.join(packetDir, "events.jsonl");
|
|
311
|
+
if (!existsSync(eventsPath)) {
|
|
312
|
+
return { source_authority: "no event ledger", work_evidence: "no event ledger", last_learning: null };
|
|
313
|
+
}
|
|
314
|
+
const lines = readFileSync(eventsPath, "utf8").split(/\r?\n/).filter((l) => l.trim());
|
|
315
|
+
const scanned = lines.slice(-DEEP_SCAN_LIMIT);
|
|
316
|
+
const events = [];
|
|
317
|
+
for (const line of scanned) {
|
|
318
|
+
try {
|
|
319
|
+
const e = JSON.parse(line);
|
|
320
|
+
if (e && typeof e === "object")
|
|
321
|
+
events.push(e);
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
// A malformed ledger line is skipped, not fatal — this is a read.
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const providers = new Set();
|
|
328
|
+
let sourceSignals = 0;
|
|
329
|
+
let workEvidence = 0;
|
|
330
|
+
let latest = null;
|
|
331
|
+
for (const e of events) {
|
|
332
|
+
const type = String(e.type ?? "");
|
|
333
|
+
const subject = String(e.subject ?? "");
|
|
334
|
+
const data = asRecord(e.data);
|
|
335
|
+
if (/source|external|record|observ|synced/i.test(type) || "provider" in data || "synced_at" in data || "observed_at" in data) {
|
|
336
|
+
sourceSignals += 1;
|
|
337
|
+
const provider = str(data.provider) ?? str(data.source);
|
|
338
|
+
if (provider)
|
|
339
|
+
providers.add(provider);
|
|
340
|
+
}
|
|
341
|
+
if (/code\.changed|pr_merged|status_changed|work\./i.test(type))
|
|
342
|
+
workEvidence += 1;
|
|
343
|
+
const summary = str(data.summary);
|
|
344
|
+
if (summary && type !== "cli_usage") {
|
|
345
|
+
const t = Date.parse(String(e.time ?? "")) || 0;
|
|
346
|
+
if (!latest || t >= latest.time)
|
|
347
|
+
latest = { time: t, type, subject, summary };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const source_authority = sourceSignals === 0
|
|
351
|
+
? "no source-authority signals in the ledger"
|
|
352
|
+
: `${sourceSignals} source signal(s)${providers.size > 0 ? ` from: ${[...providers].sort().join(", ")}` : ""}`;
|
|
353
|
+
const work_evidence = workEvidence === 0 ? "no work-evidence events in the ledger" : `${workEvidence} work-evidence event(s) (status changes, code changes, merged work)`;
|
|
354
|
+
const last_learning = latest
|
|
355
|
+
? `${latest.type}${latest.subject ? ` (${latest.subject})` : ""}: ${redact(latest.summary).slice(0, 140)}`
|
|
356
|
+
: null;
|
|
357
|
+
return { source_authority, work_evidence, last_learning };
|
|
358
|
+
}
|
|
359
|
+
function renderText(s, deepRequested, deep, glyphs) {
|
|
360
|
+
const lines = [];
|
|
361
|
+
lines.push(`${s.packet} · ${s.spec_id}`);
|
|
362
|
+
const meta = [s.lifecycle && `lifecycle ${s.lifecycle}`, s.stage && `stage ${s.stage}`, s.mode && `mode ${s.mode}`]
|
|
363
|
+
.filter(Boolean)
|
|
364
|
+
.join(" · ");
|
|
365
|
+
if (s.title || meta)
|
|
366
|
+
lines.push(`${s.title ?? ""}${s.title && meta ? " — " : ""}${meta}`.trim());
|
|
367
|
+
lines.push("");
|
|
368
|
+
// A retirement verdict leads the card: the packet's headline truth is "we
|
|
369
|
+
// decided not to do this (yet)", not its task counts.
|
|
370
|
+
if (s.verdict) {
|
|
371
|
+
const state = s.verdict.state === "killed" ? "killed" : "parked";
|
|
372
|
+
const when = s.verdict.at ? ` (${s.verdict.at.slice(0, 10)})` : "";
|
|
373
|
+
lines.push(`${pad("Verdict")}${withGlyph(state, `${state}${when} — ${s.verdict.reason ?? "no reason recorded in the ledger"}`, glyphs)}`);
|
|
374
|
+
lines.push("");
|
|
375
|
+
}
|
|
376
|
+
// Task-level and spec-level progress stay on separate lines (R-019); no percentages.
|
|
377
|
+
lines.push(`${pad("Tasks")}${withGlyph("claimed", `${s.tasks.done} ${CLAIMED_HUMAN_LABEL}`, glyphs)} · ${withGlyph("doing", `${s.tasks.doing} doing`, glyphs)} · ${withGlyph("blocked", `${s.tasks.blocked} blocked`, glyphs)} · ${withGlyph("todo", `${s.tasks.todo} todo`, glyphs)} (${s.tasks.total} total)`);
|
|
378
|
+
lines.push(`${pad("Proof")}${withGlyph("proven", `${s.proof.checks_done} of ${s.proof.checks_total} checks proven`, glyphs)}`);
|
|
379
|
+
// 0-of-N renders loudly (packet-grammar-v1 R-009): a packet whose tasks
|
|
380
|
+
// reference none of its requirements is drift, not a quiet statistic. The
|
|
381
|
+
// silent "0 of 0" this line used to show for unparsed formats is extinct —
|
|
382
|
+
// the grammar's tolerant read gives every packet a real denominator.
|
|
383
|
+
lines.push(s.requirements.total > 0
|
|
384
|
+
? s.requirements.covered === 0
|
|
385
|
+
? `${pad("Spec")}${withGlyph("drift", `0 of ${s.requirements.total} requirements covered — no task references any requirement yet`, glyphs)}`
|
|
386
|
+
: `${pad("Spec")}${s.requirements.covered} of ${s.requirements.total} requirements covered by a task`
|
|
387
|
+
: `${pad("Spec")}no R-numbered requirements found`);
|
|
388
|
+
lines.push("");
|
|
389
|
+
lines.push(`${pad("Completed")}${s.completed.length > 0 ? s.completed.join(" ") : "none yet"}`);
|
|
390
|
+
if (s.blocked.length === 0) {
|
|
391
|
+
lines.push(`${pad("Blocked")}none`);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
s.blocked.forEach((b, i) => {
|
|
395
|
+
const text = `${b.id} ${b.title}${b.reason ? ` — ${b.reason}` : ""}`;
|
|
396
|
+
lines.push(`${pad(i === 0 ? "Blocked" : "")}${withGlyph("blocked", text, glyphs)}`);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
if (s.stale.length === 0) {
|
|
400
|
+
lines.push(`${pad("Review")}no stale suggested/discovered tasks`);
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
lines.push(`${pad("Review")}${s.stale.length} ${s.stale.length === 1 ? "task" : "tasks"} awaiting review:`);
|
|
404
|
+
for (const r of s.stale) {
|
|
405
|
+
const text = `${r.id} (${r.agreement})${r.overdue ? " — overdue" : ""} ${r.title}`;
|
|
406
|
+
lines.push(`${pad("")}${r.overdue ? withGlyph("drift", text, glyphs) : text}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// Lifecycle drift (packet-lifecycle-truth-v0 R-002): the header contradicts
|
|
410
|
+
// the derived truth. The restamp nudge (draft + all done) keeps its original
|
|
411
|
+
// wording; other contradictions name both states and the governed way out.
|
|
412
|
+
lines.push(s.drift.restamp
|
|
413
|
+
? `${pad("Drift")}${withGlyph("drift", "header says draft, but all tasks are done — restamp the lifecycle", glyphs)}`
|
|
414
|
+
: s.lifecycle_drift
|
|
415
|
+
? `${pad("Drift")}${withGlyph("drift", lifecycleDriftText(s), glyphs)}`
|
|
416
|
+
: `${pad("Drift")}none`);
|
|
417
|
+
if (s.open_questions.length > 0) {
|
|
418
|
+
const actionable = s.next_action.kind === "decide";
|
|
419
|
+
const label = actionable
|
|
420
|
+
? `${s.open_questions.length} saved · 1 decision needs your input`
|
|
421
|
+
: `${s.open_questions.length} saved for reference · not blocking`;
|
|
422
|
+
lines.push(`${pad("Questions")}${actionable ? withGlyph("needs_input", label, glyphs) : label}`);
|
|
423
|
+
for (const q of s.open_questions)
|
|
424
|
+
lines.push(`${pad("")}- ${q}`);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
lines.push(`${pad("Questions")}none saved`);
|
|
428
|
+
}
|
|
429
|
+
lines.push("");
|
|
430
|
+
lines.push(`${pad("Next")}${nextActionText(s.next_action)}`);
|
|
431
|
+
if (deepRequested && deep) {
|
|
432
|
+
lines.push(`${pad("Source")}${deep.source_authority}`);
|
|
433
|
+
lines.push(`${pad("Evidence")}${deep.work_evidence}`);
|
|
434
|
+
lines.push(`${pad("Learning")}${deep.last_learning ?? "no meaningful learning recorded yet"}`);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
lines.push(`${pad("Deep")}source-authority, work-evidence, last learning — not yet checked (run with --deep)`);
|
|
438
|
+
}
|
|
439
|
+
lines.push("");
|
|
440
|
+
return lines.join("\n");
|
|
441
|
+
}
|
|
442
|
+
// Glyphs are chosen for screen-reader and terminal-font portability (✓/✗ are
|
|
443
|
+
// widely spoken and have braille mappings; ●/○ are a filled/empty in-progress
|
|
444
|
+
// pair; × reads as "times"). The word is always load-bearing — color and glyph
|
|
445
|
+
// are additive, never the sole carrier of a status.
|
|
446
|
+
const STATUS_META = {
|
|
447
|
+
done: { glyph: "✓", label: CLAIMED_HUMAN_LABEL, tone: "ok" },
|
|
448
|
+
doing: { glyph: "●", label: "doing", tone: "active" },
|
|
449
|
+
blocked: { glyph: "✗", label: "blocked", tone: "bad" },
|
|
450
|
+
todo: { glyph: "○", label: "todo", tone: "muted" },
|
|
451
|
+
dropped: { glyph: "×", label: "dropped", tone: "muted" },
|
|
452
|
+
};
|
|
453
|
+
function makePaint(color) {
|
|
454
|
+
const wrap = (code) => (s) => (color ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
455
|
+
return {
|
|
456
|
+
ok: wrap("32"),
|
|
457
|
+
active: wrap("36"),
|
|
458
|
+
bad: wrap("31"),
|
|
459
|
+
warn: wrap("33"),
|
|
460
|
+
muted: wrap("2"),
|
|
461
|
+
bold: wrap("1"),
|
|
462
|
+
accent: wrap("1;36"),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function bar(done, total, p, tone, width = 16) {
|
|
466
|
+
if (total <= 0)
|
|
467
|
+
return p.muted("░".repeat(width));
|
|
468
|
+
let filled = Math.round((done / total) * width);
|
|
469
|
+
if (done > 0 && filled === 0)
|
|
470
|
+
filled = 1;
|
|
471
|
+
if (done < total && filled === width)
|
|
472
|
+
filled = width - 1;
|
|
473
|
+
filled = Math.max(0, Math.min(width, filled));
|
|
474
|
+
const empty = width - filled;
|
|
475
|
+
return `${p[tone]("█".repeat(filled))}${empty > 0 ? p.muted("░".repeat(empty)) : ""}`;
|
|
476
|
+
}
|
|
477
|
+
function renderRich(s, opts) {
|
|
478
|
+
const p = makePaint(opts.color);
|
|
479
|
+
const out = [];
|
|
480
|
+
// One fixed-width left label column so every value lands on the same edge.
|
|
481
|
+
const W = 10;
|
|
482
|
+
const field = (label, tone = "bold") => p[tone](label.padEnd(W));
|
|
483
|
+
// Bars are decorative (R-019 forbids a number) and read as repeated noise to a
|
|
484
|
+
// screen reader, so they render only on a real terminal; a pipe / --plain omits
|
|
485
|
+
// them. Truncation hides text behind an off-screen ellipsis, so it too applies
|
|
486
|
+
// only on a TTY where width is bounded — piped output stays whole.
|
|
487
|
+
const barCell = (done, total, tone) => (opts.bars ? `${bar(done, total, p, tone)} ` : "");
|
|
488
|
+
const clip = (text, n = 84) => (opts.bars && text.length > n ? `${text.slice(0, n - 1)}…` : text);
|
|
489
|
+
// Plain-first (jargon-supply-chain-v0 R-002): the human name leads the card;
|
|
490
|
+
// machine identifiers ride muted at the end of the line, never bold-first.
|
|
491
|
+
out.push(`${p.bold(s.title ?? s.packet)} ${p.muted("·")} ${p.muted(`${s.packet} · ${s.spec_id}`)}`);
|
|
492
|
+
const meta = [s.lifecycle && `lifecycle ${s.lifecycle}`, s.stage && `stage ${s.stage}`, s.mode && `mode ${s.mode}`]
|
|
493
|
+
.filter(Boolean)
|
|
494
|
+
.join(" · ");
|
|
495
|
+
if (meta)
|
|
496
|
+
out.push(p.muted(meta));
|
|
497
|
+
out.push("");
|
|
498
|
+
// Summary drops zero-count categories (so an at-risk packet never reads as
|
|
499
|
+
// complete) and surfaces blocked in red with the `!` token asp index uses.
|
|
500
|
+
const counts = [`${s.tasks.done} ${CLAIMED_HUMAN_LABEL}`];
|
|
501
|
+
if (s.tasks.doing)
|
|
502
|
+
counts.push(`${s.tasks.doing} doing`);
|
|
503
|
+
if (s.tasks.blocked)
|
|
504
|
+
counts.push(p.bad(`! ${s.tasks.blocked} blocked`));
|
|
505
|
+
if (s.tasks.todo)
|
|
506
|
+
counts.push(`${s.tasks.todo} todo`);
|
|
507
|
+
if (s.tasks.dropped)
|
|
508
|
+
counts.push(p.muted(`${s.tasks.dropped} dropped`));
|
|
509
|
+
out.push(` ${field("Tasks")}${barCell(s.tasks.done, s.tasks.total, "ok")}${counts.join(" · ")} ${p.muted(`(${s.tasks.total} total)`)}`);
|
|
510
|
+
out.push(` ${field("Proof")}${barCell(s.proof.checks_done, s.proof.checks_total, "active")}${s.proof.checks_done} of ${s.proof.checks_total} checks proven`);
|
|
511
|
+
out.push(s.requirements.total > 0
|
|
512
|
+
? ` ${field("Spec")}${barCell(s.requirements.covered, s.requirements.total, "active")}${s.requirements.covered} of ${s.requirements.total} requirements covered`
|
|
513
|
+
: ` ${field("Spec")}${p.muted("no R-numbered requirements found")}`);
|
|
514
|
+
out.push("");
|
|
515
|
+
// The decision this card exists to drive sits directly under the summary, in
|
|
516
|
+
// the only accent treatment, above the task detail (principal-designer P0).
|
|
517
|
+
// A retired packet's decision is already made — the verdict replaces Next.
|
|
518
|
+
if (s.verdict) {
|
|
519
|
+
const state = s.verdict.state === "killed" ? "killed" : "parked";
|
|
520
|
+
const glyph = glyphFor(state, true);
|
|
521
|
+
out.push(` ${field("Verdict", "warn")}${p.bold(`${glyph} ${state}`)} ${p.muted("—")} ${clip(s.verdict.reason ?? "no reason recorded in the ledger")}`);
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
const tone = s.next_action.kind === "none" ? "muted" : s.next_action.kind === "unblock" || s.next_action.kind === "reconcile" ? "warn" : "accent";
|
|
525
|
+
out.push(` ${field("Next", tone)}${p[tone](`→ ${clip(nextActionText(s.next_action))}`)}`);
|
|
526
|
+
}
|
|
527
|
+
out.push("");
|
|
528
|
+
if (s.task_list.length === 0) {
|
|
529
|
+
out.push(` ${p.muted("no tasks in tasks.yaml")}`);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
for (const t of s.task_list) {
|
|
533
|
+
const m = STATUS_META[t.status] ?? STATUS_META.todo;
|
|
534
|
+
const title = m.tone === "muted" ? p.muted(t.title) : t.title;
|
|
535
|
+
out.push(` ${p[m.tone](m.glyph)} ${p[m.tone](m.label.padEnd(7))} ${title} ${p.muted(`· ${t.id}`)}`.trimEnd());
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
// Exceptions the table can't carry — one line each, same aligned column, shown
|
|
539
|
+
// only when non-empty. Full text always lives in --plain / --json and spec.md.
|
|
540
|
+
const exceptions = [];
|
|
541
|
+
for (const b of s.blocked) {
|
|
542
|
+
exceptions.push(` ${field("Blocked", "bad")}${clip(`${b.title}${b.reason ? ` — ${b.reason}` : ""}`)} ${p.muted(`· ${b.id}`)}`);
|
|
543
|
+
}
|
|
544
|
+
if (s.stale.length > 0) {
|
|
545
|
+
const tail = s.stale.map((r) => `${r.id} (${r.agreement}${r.overdue ? ", overdue" : ""})`).join(", ");
|
|
546
|
+
exceptions.push(` ${field("Review", "warn")}${s.stale.length} awaiting review ${p.muted(`· ${tail}`)}`);
|
|
547
|
+
}
|
|
548
|
+
if (s.open_questions.length > 0) {
|
|
549
|
+
const word = s.open_questions.length === 1 ? "question" : "questions";
|
|
550
|
+
const actionable = s.next_action.kind === "decide";
|
|
551
|
+
exceptions.push(` ${field("Questions", actionable ? "warn" : "muted")}${s.open_questions.length} saved ${word} ${p.muted(actionable ? "· 1 needs your input" : "· reference only")}`);
|
|
552
|
+
}
|
|
553
|
+
// Lifecycle drift rides the exceptions column on the rich card too (R-002) —
|
|
554
|
+
// the quick view must not read healthier than the text/plain view.
|
|
555
|
+
if (s.lifecycle_drift) {
|
|
556
|
+
exceptions.push(` ${field("Drift", "warn")}${clip(s.lifecycle_effective === "done"
|
|
557
|
+
? `lifecycle says ${s.lifecycle_stored ?? "(unset)"}, work proven done — foo asp verdict <packet> done`
|
|
558
|
+
: `lifecycle says ${s.lifecycle_stored}, work not proofs-complete`)}`);
|
|
559
|
+
}
|
|
560
|
+
if (exceptions.length > 0) {
|
|
561
|
+
out.push("");
|
|
562
|
+
out.push(...exceptions);
|
|
563
|
+
}
|
|
564
|
+
out.push("");
|
|
565
|
+
// The rich card is the quick view; --deep renders the detailed text audit.
|
|
566
|
+
out.push(` ${field("Deep", "muted")}${p.muted("source-authority, work-evidence, last learning — run asp status --deep")}`);
|
|
567
|
+
out.push("");
|
|
568
|
+
return out.join("\n");
|
|
569
|
+
}
|
|
570
|
+
export function renderAspStatus(packetDir, options = {}) {
|
|
571
|
+
const status = derivePacketStatus(packetDir, { now: options.now });
|
|
572
|
+
const deep = options.deep === true ? deriveDeep(packetDir) : null;
|
|
573
|
+
if (deep)
|
|
574
|
+
status.deep_checked = true;
|
|
575
|
+
if (options.json) {
|
|
576
|
+
return `${JSON.stringify({ schema_version: PACKET_STATUS_SCHEMA_VERSION, ...status, ...(deep ? { deep } : {}) }, null, 2)}\n`;
|
|
577
|
+
}
|
|
578
|
+
if (options.html) {
|
|
579
|
+
return renderStatusHtml(status);
|
|
580
|
+
}
|
|
581
|
+
// --deep is the detailed audit; render it as text (with the deep findings)
|
|
582
|
+
// rather than the quick rich card.
|
|
583
|
+
if (options.rich && !deep) {
|
|
584
|
+
return renderRich(status, {
|
|
585
|
+
color: options.color === true,
|
|
586
|
+
deep: options.deep === true,
|
|
587
|
+
// Bars and truncation are TTY-only; default to the color decision (a real
|
|
588
|
+
// terminal) but let an explicit caller force them for capture.
|
|
589
|
+
bars: options.bars ?? options.color === true,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
return renderText(status, options.deep === true, deep, options.glyphs === true);
|
|
593
|
+
}
|
|
594
|
+
// Single-packet HTML progress card (the visual sibling of the rich TTY card and
|
|
595
|
+
// the cross-packet `asp index --html`). Server-rendered and self-contained: no
|
|
596
|
+
// client JS, so it degrades to readable semantic HTML, reuses the asp-index
|
|
597
|
+
// design tokens (Atkinson Hyperlegible, OKLCH), and carries the design-swarm +
|
|
598
|
+
// Impeccable findings — Next is the dominant call-out linked to its list row,
|
|
599
|
+
// every status shows a word (never color/glyph alone), the task list is a fixed
|
|
600
|
+
// grid, accent side-bars are dropped (an AI tell), and `--muted` is darkened for
|
|
601
|
+
// ≥4.5:1 contrast. No percent-complete (R-019): bars encode proportion only.
|
|
602
|
+
const HTML_STATUS_META = {
|
|
603
|
+
done: { glyph: "✓", word: CLAIMED_HUMAN_LABEL, cls: "d" },
|
|
604
|
+
doing: { glyph: "●", word: "doing", cls: "wip" },
|
|
605
|
+
blocked: { glyph: "✗", word: "blocked", cls: "b" },
|
|
606
|
+
todo: { glyph: "○", word: "todo", cls: "o" },
|
|
607
|
+
dropped: { glyph: "×", word: "dropped", cls: "x" },
|
|
608
|
+
};
|
|
609
|
+
function escHtml(value) {
|
|
610
|
+
return value.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
611
|
+
}
|
|
612
|
+
// One filled segment over the track background — no abutting zero-flex sliver to
|
|
613
|
+
// read as a glitch near 100%. aria-hidden: the adjacent value text is the number.
|
|
614
|
+
function pbarHtml(done, total, colorVar) {
|
|
615
|
+
// The card refuses a percent-complete number by design; this bar is a coarse
|
|
616
|
+
// proportion of discrete counts, not a precise metric. Round to one decimal so
|
|
617
|
+
// the markup never ships a long binary float (e.g. 82.35294117647058%).
|
|
618
|
+
const raw = total > 0 ? Math.max(0, Math.min(100, (done / total) * 100)) : 0;
|
|
619
|
+
const fill = Math.round(raw * 10) / 10;
|
|
620
|
+
return `<div class="pbar" aria-hidden="true"><i style="width:${fill}%;background:var(${colorVar})"></i></div>`;
|
|
621
|
+
}
|
|
622
|
+
export function renderStatusHtml(s) {
|
|
623
|
+
const title = s.title ?? s.packet;
|
|
624
|
+
// Summary line drops zero-count categories and leads with in-flight work.
|
|
625
|
+
const counts = [];
|
|
626
|
+
if (s.tasks.doing)
|
|
627
|
+
counts.push(`${s.tasks.doing} doing`);
|
|
628
|
+
counts.push(`${s.tasks.done} ${CLAIMED_HUMAN_LABEL}`);
|
|
629
|
+
if (s.tasks.blocked)
|
|
630
|
+
counts.push(`${s.tasks.blocked} blocked`);
|
|
631
|
+
if (s.tasks.todo)
|
|
632
|
+
counts.push(`${s.tasks.todo} todo`);
|
|
633
|
+
if (s.tasks.dropped)
|
|
634
|
+
counts.push(`${s.tasks.dropped} dropped`);
|
|
635
|
+
const countLine = counts.join(" · ");
|
|
636
|
+
const tags = [s.lifecycle && `lifecycle ${s.lifecycle}`, s.stage && `stage ${s.stage}`, s.mode && `mode ${s.mode}`]
|
|
637
|
+
.filter((x) => Boolean(x))
|
|
638
|
+
.map((t) => `<span class="tag">${escHtml(t)}</span>`)
|
|
639
|
+
.join("");
|
|
640
|
+
const nextId = s.next?.id;
|
|
641
|
+
const tasks = s.task_list
|
|
642
|
+
.map((t) => {
|
|
643
|
+
const m = HTML_STATUS_META[t.status] ?? HTML_STATUS_META.todo;
|
|
644
|
+
// Mark the row the Next call-out points at, so the two read as one object.
|
|
645
|
+
const mark = t.id === nextId ? " next" : "";
|
|
646
|
+
return `<li class="tk ${m.cls}${mark}"><span class="g ${m.cls}" aria-hidden="true">${m.glyph}</span><span class="tid">${escHtml(t.id)}</span><span class="tst">${m.word}</span><span class="tt">${escHtml(t.title)}</span></li>`;
|
|
647
|
+
})
|
|
648
|
+
.join("");
|
|
649
|
+
const nextValue = s.next_action.kind === "task"
|
|
650
|
+
? `<span class="next-t">${escHtml(s.next_action.task.title)}</span><span class="next-id">${escHtml(s.next_action.task.id)}</span>`
|
|
651
|
+
: `<span class="next-t">${escHtml(nextActionText(s.next_action))}</span>`;
|
|
652
|
+
const next = `<h2 class="next-k">Next</h2><p class="next-v">${nextValue}</p>`;
|
|
653
|
+
const exc = [];
|
|
654
|
+
if (s.blocked.length > 0) {
|
|
655
|
+
const items = s.blocked
|
|
656
|
+
.map((b) => `<li><b>${escHtml(b.id)}</b> ${escHtml(b.title)}${b.reason ? ` <span class="muted">(${escHtml(b.reason)})</span>` : ""}</li>`)
|
|
657
|
+
.join("");
|
|
658
|
+
exc.push(`<div class="exc b"><h3 class="exc-k">Blocked</h3><ul class="exc-l">${items}</ul></div>`);
|
|
659
|
+
}
|
|
660
|
+
if (s.stale.length > 0) {
|
|
661
|
+
const items = s.stale
|
|
662
|
+
.map((r) => `<li><b>${escHtml(r.id)}</b> ${escHtml(r.title)} <span class="muted">(${escHtml(r.agreement)}${r.overdue ? ", overdue" : ""})</span></li>`)
|
|
663
|
+
.join("");
|
|
664
|
+
exc.push(`<div class="exc w"><h3 class="exc-k">Review</h3><ul class="exc-l">${items}</ul></div>`);
|
|
665
|
+
}
|
|
666
|
+
if (s.open_questions.length > 0) {
|
|
667
|
+
const items = s.open_questions.map((q) => `<li>${escHtml(q)}</li>`).join("");
|
|
668
|
+
const actionable = s.next_action.kind === "decide";
|
|
669
|
+
exc.push(`<div class="exc ${actionable ? "w" : ""}"><h3 class="exc-k">Saved questions${actionable ? " · 1 needs your input" : " · reference only"}</h3><ul class="exc-l">${items}</ul></div>`);
|
|
670
|
+
}
|
|
671
|
+
const excHtml = exc.length > 0 ? `<section class="excs"><h2 class="sr">Needs attention</h2>${exc.join("")}</section>` : "";
|
|
672
|
+
const spec = s.requirements.total > 0
|
|
673
|
+
? `<div class="chip"><div class="k">requirements</div><div class="v">${s.requirements.covered}<small>/${s.requirements.total}</small></div>${pbarHtml(s.requirements.covered, s.requirements.total, "--proof")}</div>`
|
|
674
|
+
: `<div class="chip"><div class="k">requirements</div><div class="v">—<small> none</small></div></div>`;
|
|
675
|
+
return `<!doctype html>
|
|
676
|
+
<html lang="en">
|
|
677
|
+
<head>
|
|
678
|
+
<meta charset="utf-8" />
|
|
679
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
680
|
+
<title>foo · ${escHtml(s.packet)} progress</title>
|
|
681
|
+
<style>
|
|
682
|
+
@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:wght@400;700&display=swap');
|
|
683
|
+
:root{--ink:oklch(0.205 0.012 25);--muted:oklch(0.418 0.014 25);--line:oklch(0.875 0.006 25);--line2:oklch(0.705 0.012 25);--bg:oklch(1 0 0);--surf:oklch(0.985 0.001 25);--surf2:oklch(0.967 0.002 25);--att:oklch(0.36 0.12 0);--att-soft:oklch(0.967 0.015 8);--proof:oklch(0.47 0.095 190);--wip:oklch(0.5 0.12 255);--bad:oklch(0.52 0.18 25);--warn:oklch(0.55 0.1 70);--warn-soft:oklch(0.96 0.03 82);--bad-soft:oklch(0.96 0.025 25);--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--ui:"Atkinson Hyperlegible",ui-sans-serif,system-ui,sans-serif;}
|
|
684
|
+
*{box-sizing:border-box;}
|
|
685
|
+
body{margin:0;background:var(--surf);color:var(--ink);font-family:var(--ui);line-height:1.5;-webkit-font-smoothing:antialiased;}
|
|
686
|
+
.wrap{max-width:760px;margin:0 auto;padding:40px 24px 56px;}
|
|
687
|
+
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0;}
|
|
688
|
+
.muted{color:var(--muted);}
|
|
689
|
+
.mast{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap;}
|
|
690
|
+
.wm{font-size:13px;font-weight:700;color:var(--muted);letter-spacing:.02em;}
|
|
691
|
+
.wm b{color:var(--att);}
|
|
692
|
+
h1.ttl{font-size:25px;font-weight:700;letter-spacing:-0.01em;margin:5px 0 4px;line-height:1.2;}
|
|
693
|
+
.sub{margin:0;font-size:12.5px;color:var(--muted);font-family:var(--mono);}
|
|
694
|
+
.tags{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end;}
|
|
695
|
+
.tag{font-family:var(--mono);font-size:10.5px;font-weight:700;border:0.5px solid var(--line2);border-radius:5px;padding:3px 7px;color:var(--muted);white-space:nowrap;}
|
|
696
|
+
.chips{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;margin:22px 0 0;}
|
|
697
|
+
.chip{background:var(--bg);border:0.5px solid var(--line);border-radius:9px;padding:12px 14px;}
|
|
698
|
+
.chip .k{font-family:var(--mono);font-size:10.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.03em;}
|
|
699
|
+
.chip .v{font-size:22px;font-weight:700;margin:3px 0 9px;}
|
|
700
|
+
.chip .v small{font-size:12px;font-weight:400;color:var(--muted);}
|
|
701
|
+
.pbar{height:8px;border-radius:3px;overflow:hidden;background:var(--line);margin-top:1px;}
|
|
702
|
+
.pbar i{display:block;height:100%;}
|
|
703
|
+
.nextcard{display:block;margin:22px 0 0;background:var(--att-soft);border:0.5px solid var(--line2);border-radius:9px;padding:15px 17px;}
|
|
704
|
+
.next-k{font-family:var(--mono);font-size:10.5px;font-weight:700;color:var(--att);text-transform:uppercase;letter-spacing:.05em;margin:0 0 5px;}
|
|
705
|
+
.next-v{margin:0;font-size:18px;font-weight:700;display:flex;gap:10px;align-items:baseline;flex-wrap:wrap;}
|
|
706
|
+
.next-id{font-family:var(--mono);font-size:13px;color:var(--att);}
|
|
707
|
+
.next-t{font-weight:400;}
|
|
708
|
+
.seclabel{font-family:var(--mono);font-size:10.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:26px 0 8px;}
|
|
709
|
+
.tks{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;}
|
|
710
|
+
.tk{display:grid;grid-template-columns:1.4rem 6ch 8ch 1fr;align-items:center;gap:11px;padding:8px 6px;border-top:0.5px solid var(--line);font-size:14px;}
|
|
711
|
+
.tk:first-child{border-top:0;}
|
|
712
|
+
.tk.next{background:var(--att-soft);border-radius:7px;}
|
|
713
|
+
.g{width:19px;height:19px;border-radius:5px;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:oklch(0.99 0.004 190);}
|
|
714
|
+
.g.d{background:var(--proof);}
|
|
715
|
+
.g.wip{background:var(--wip);}
|
|
716
|
+
.g.b{background:var(--bad);}
|
|
717
|
+
.g.o{background:transparent;border:1.5px solid var(--line2);color:var(--muted);}
|
|
718
|
+
.g.x{background:transparent;border:1.5px solid var(--line2);color:var(--muted);}
|
|
719
|
+
.tid{font-family:var(--mono);font-size:11.5px;font-weight:700;color:var(--muted);}
|
|
720
|
+
.tst{font-family:var(--mono);font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;}
|
|
721
|
+
.tk.d .tst{color:var(--proof);}
|
|
722
|
+
.tk.wip .tst{color:var(--wip);}
|
|
723
|
+
.tk.b .tst{color:var(--bad);}
|
|
724
|
+
.tk.o .tst,.tk.x .tst{color:var(--muted);}
|
|
725
|
+
.tt{color:var(--ink);}
|
|
726
|
+
.tk.d .tt,.tk.x .tt{color:var(--muted);}
|
|
727
|
+
.tk.x .tt{text-decoration:line-through;}
|
|
728
|
+
.excs{margin-top:24px;display:flex;flex-direction:column;gap:10px;}
|
|
729
|
+
.exc{background:var(--bg);border:0.5px solid var(--line);border-radius:9px;padding:12px 15px;}
|
|
730
|
+
.exc.b{background:var(--bad-soft);}
|
|
731
|
+
.exc.w{background:var(--warn-soft);}
|
|
732
|
+
.exc-k{font-family:var(--mono);font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;margin:0 0 7px;color:var(--ink);}
|
|
733
|
+
.exc-l{margin:0;padding-left:18px;font-size:13.5px;display:flex;flex-direction:column;gap:4px;}
|
|
734
|
+
.deep{margin:26px 0 0;font-size:12.5px;color:var(--muted);font-style:italic;}
|
|
735
|
+
.foot{margin:18px 0 0;font-family:var(--mono);font-size:10.5px;color:var(--muted);border-top:0.5px solid var(--line);padding-top:12px;}
|
|
736
|
+
@media (max-width:520px){.wrap{padding:28px 16px 40px;}.tags{justify-content:flex-start;}}
|
|
737
|
+
</style>
|
|
738
|
+
</head>
|
|
739
|
+
<body>
|
|
740
|
+
<main class="wrap">
|
|
741
|
+
<header class="mast">
|
|
742
|
+
<div>
|
|
743
|
+
<div class="wm">foo<b>.</b> packet progress</div>
|
|
744
|
+
<h1 class="ttl">${escHtml(title)}</h1>
|
|
745
|
+
<p class="sub">${escHtml(s.spec_id)} · derived from tasks.yaml</p>
|
|
746
|
+
</div>
|
|
747
|
+
<div class="tags">${tags || '<span class="tag">no header</span>'}</div>
|
|
748
|
+
</header>
|
|
749
|
+
|
|
750
|
+
<section class="chips">
|
|
751
|
+
<h2 class="sr">Progress</h2>
|
|
752
|
+
<div class="chip"><div class="k">tasks</div><div class="v">${s.tasks.done}<small>/${s.tasks.total} ${CLAIMED_HUMAN_LABEL}</small></div>${pbarHtml(s.tasks.done, s.tasks.total, "--proof")}</div>
|
|
753
|
+
<div class="chip"><div class="k">proof</div><div class="v">${s.proof.checks_done}<small>/${s.proof.checks_total} checks</small></div>${pbarHtml(s.proof.checks_done, s.proof.checks_total, "--proof")}</div>
|
|
754
|
+
${spec}
|
|
755
|
+
</section>
|
|
756
|
+
|
|
757
|
+
<section class="nextcard">${next}</section>
|
|
758
|
+
|
|
759
|
+
<h2 class="seclabel">Tasks <span class="muted">· ${countLine}</span></h2>
|
|
760
|
+
<ul class="tks">${tasks || '<li class="tk o"><span class="tt muted">no tasks in tasks.yaml</span></li>'}</ul>
|
|
761
|
+
|
|
762
|
+
${excHtml}
|
|
763
|
+
|
|
764
|
+
<p class="deep">Deep checks not yet run: source-authority, work-evidence, last meaningful learning.</p>
|
|
765
|
+
<p class="foot">generated by foo asp status · read-only, derived from ${escHtml(s.packet)}/tasks.yaml · no percent-complete by design</p>
|
|
766
|
+
</main>
|
|
767
|
+
</body>
|
|
768
|
+
</html>
|
|
769
|
+
`;
|
|
770
|
+
}
|