mirror-mind 0.0.1 → 0.32.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/.claude/hooks/log-session-end.sh +85 -0
- package/.claude/hooks/log-user-prompt.sh +85 -0
- package/.claude/hooks/mirror-inject.sh +85 -0
- package/.claude/hooks/session-start.sh +85 -0
- package/.gemini/hooks/log-assistant.sh +85 -0
- package/.gemini/hooks/log-user.sh +85 -0
- package/.gemini/hooks/session-end.sh +85 -0
- package/.gemini/hooks/session-start.sh +85 -0
- package/.pi/extensions/mirror-logger.ts +561 -0
- package/.pi/skills/mm-backup/SKILL.md +21 -0
- package/.pi/skills/mm-build/SKILL.md +1206 -0
- package/.pi/skills/mm-consolidate/SKILL.md +112 -0
- package/.pi/skills/mm-consult/SKILL.md +70 -0
- package/.pi/skills/mm-conversations/SKILL.md +16 -0
- package/.pi/skills/mm-discard/SKILL.md +39 -0
- package/.pi/skills/mm-explore/SKILL.md +255 -0
- package/.pi/skills/mm-help/SKILL.md +74 -0
- package/.pi/skills/mm-identity/SKILL.md +96 -0
- package/.pi/skills/mm-journal/SKILL.md +16 -0
- package/.pi/skills/mm-journey/SKILL.md +23 -0
- package/.pi/skills/mm-journeys/SKILL.md +15 -0
- package/.pi/skills/mm-memories/SKILL.md +17 -0
- package/.pi/skills/mm-mirror/SKILL.md +98 -0
- package/.pi/skills/mm-mute/SKILL.md +18 -0
- package/.pi/skills/mm-new/SKILL.md +18 -0
- package/.pi/skills/mm-recall/SKILL.md +17 -0
- package/.pi/skills/mm-release-notes/SKILL.md +52 -0
- package/.pi/skills/mm-seed/SKILL.md +19 -0
- package/.pi/skills/mm-shadow/SKILL.md +117 -0
- package/.pi/skills/mm-soul/SKILL.md +502 -0
- package/.pi/skills/mm-tasks/SKILL.md +24 -0
- package/.pi/skills/mm-update/SKILL.md +32 -0
- package/.pi/skills/mm-week/SKILL.md +28 -0
- package/.pi/skills/mm-welcome/SKILL.md +24 -0
- package/AGENTS.md +188 -0
- package/README.md +126 -9
- package/bin/loader.js +67 -0
- package/bin/mirror-hook.js +7 -0
- package/bin/mirror.js +7 -0
- package/docs/releases/index.md +99 -0
- package/docs/releases/pending-cutoffs.md +510 -0
- package/docs/releases/v0.10.0.md +123 -0
- package/docs/releases/v0.10.1.md +66 -0
- package/docs/releases/v0.10.10.md +51 -0
- package/docs/releases/v0.10.11.md +39 -0
- package/docs/releases/v0.10.12.md +57 -0
- package/docs/releases/v0.10.13.md +35 -0
- package/docs/releases/v0.10.14.md +42 -0
- package/docs/releases/v0.10.15.md +53 -0
- package/docs/releases/v0.10.16.md +35 -0
- package/docs/releases/v0.10.17.md +43 -0
- package/docs/releases/v0.10.2.md +64 -0
- package/docs/releases/v0.10.3.md +78 -0
- package/docs/releases/v0.10.4.md +42 -0
- package/docs/releases/v0.10.5.md +57 -0
- package/docs/releases/v0.10.6.md +47 -0
- package/docs/releases/v0.10.7.md +53 -0
- package/docs/releases/v0.10.8.md +49 -0
- package/docs/releases/v0.10.9.md +37 -0
- package/docs/releases/v0.11.0.md +86 -0
- package/docs/releases/v0.12.0.md +67 -0
- package/docs/releases/v0.13.0.md +64 -0
- package/docs/releases/v0.14.0.md +68 -0
- package/docs/releases/v0.15.0.md +48 -0
- package/docs/releases/v0.16.0.md +50 -0
- package/docs/releases/v0.16.1.md +55 -0
- package/docs/releases/v0.16.2.md +36 -0
- package/docs/releases/v0.17.0.md +50 -0
- package/docs/releases/v0.17.1.md +58 -0
- package/docs/releases/v0.17.2.md +42 -0
- package/docs/releases/v0.17.3.md +58 -0
- package/docs/releases/v0.18.0.md +84 -0
- package/docs/releases/v0.18.1.md +51 -0
- package/docs/releases/v0.18.2.md +69 -0
- package/docs/releases/v0.19.0.md +85 -0
- package/docs/releases/v0.19.1.md +46 -0
- package/docs/releases/v0.20.0.md +85 -0
- package/docs/releases/v0.21.0.md +61 -0
- package/docs/releases/v0.21.1.md +47 -0
- package/docs/releases/v0.22.0.md +77 -0
- package/docs/releases/v0.22.1.md +42 -0
- package/docs/releases/v0.23.0.md +105 -0
- package/docs/releases/v0.24.0.md +125 -0
- package/docs/releases/v0.25.0.md +107 -0
- package/docs/releases/v0.26.0.md +139 -0
- package/docs/releases/v0.26.1.md +65 -0
- package/docs/releases/v0.26.2.md +43 -0
- package/docs/releases/v0.27.0.md +63 -0
- package/docs/releases/v0.28.0.md +59 -0
- package/docs/releases/v0.29.0.md +61 -0
- package/docs/releases/v0.29.1.md +59 -0
- package/docs/releases/v0.29.2.md +59 -0
- package/docs/releases/v0.29.3.md +93 -0
- package/docs/releases/v0.30.0.md +75 -0
- package/docs/releases/v0.30.1.md +93 -0
- package/docs/releases/v0.30.2.md +66 -0
- package/docs/releases/v0.31.0.md +122 -0
- package/docs/releases/v0.31.1.md +142 -0
- package/docs/releases/v0.31.10.md +118 -0
- package/docs/releases/v0.31.11.md +88 -0
- package/docs/releases/v0.31.12.md +105 -0
- package/docs/releases/v0.31.13.md +99 -0
- package/docs/releases/v0.31.14.md +97 -0
- package/docs/releases/v0.31.2.md +115 -0
- package/docs/releases/v0.31.3.md +142 -0
- package/docs/releases/v0.31.4.md +117 -0
- package/docs/releases/v0.31.5.md +155 -0
- package/docs/releases/v0.31.6.md +132 -0
- package/docs/releases/v0.31.7.md +132 -0
- package/docs/releases/v0.31.8.md +120 -0
- package/docs/releases/v0.31.9.md +120 -0
- package/docs/releases/v0.32.0.md +261 -0
- package/docs/releases/v0.32.1.md +59 -0
- package/docs/releases/v0.8.0.md +118 -0
- package/docs/releases/v0.9.0.md +98 -0
- package/docs/releases/v0.9.1.md +83 -0
- package/package.json +102 -6
- package/plugins/mirror-mind/.claude-plugin/plugin.json +13 -0
- package/plugins/mirror-mind/hooks/hooks.json +50 -0
- package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
- package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
- package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
- package/plugins/mirror-mind/hooks/session-start.sh +98 -0
- package/plugins/mirror-mind/mcp/launch.sh +52 -0
- package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
- package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
- package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
- package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
- package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
- package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
- package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
- package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
- package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
- package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
- package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
- package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
- package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
- package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +65 -0
- package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
- package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
- package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
- package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
- package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
- package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
- package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
- package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
- package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
- package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
- package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
- package/scripts/codex-hooks/session-end.sh +85 -0
- package/scripts/codex-hooks/session-start.sh +85 -0
- package/scripts/codex-mirror.sh +48 -0
- package/templates/identity/README.md +119 -0
- package/templates/identity/ego/behavior.yaml +52 -0
- package/templates/identity/ego/constraints.yaml +15 -0
- package/templates/identity/ego/identity.yaml +43 -0
- package/templates/identity/journeys/personal-growth.yaml +39 -0
- package/templates/identity/organization/identity.yaml +24 -0
- package/templates/identity/organization/principles.yaml +17 -0
- package/templates/identity/personas/coach.yaml +94 -0
- package/templates/identity/personas/designer.yaml +93 -0
- package/templates/identity/personas/doctor.yaml +96 -0
- package/templates/identity/personas/engineer.yaml +77 -0
- package/templates/identity/personas/financial.yaml +97 -0
- package/templates/identity/personas/prompt-engineer.yaml +122 -0
- package/templates/identity/personas/researcher.yaml +88 -0
- package/templates/identity/personas/strategist.yaml +91 -0
- package/templates/identity/personas/teacher.yaml +95 -0
- package/templates/identity/personas/therapist.yaml +91 -0
- package/templates/identity/personas/thinker.yaml +74 -0
- package/templates/identity/personas/writer.yaml +76 -0
- package/templates/identity/self/config.yaml +16 -0
- package/templates/identity/self/soul.yaml +105 -0
- package/templates/identity/user/identity.yaml +30 -0
- package/ts/src/backup/staging.ts +62 -0
- package/ts/src/backup/zipBackup.ts +267 -0
- package/ts/src/backup/zipReader.ts +109 -0
- package/ts/src/backup/zipWriter.ts +148 -0
- package/ts/src/builder/activeCheckpoint.ts +134 -0
- package/ts/src/builder/aggregateStatus.ts +34 -0
- package/ts/src/builder/approve.ts +149 -0
- package/ts/src/builder/argv.ts +292 -0
- package/ts/src/builder/ariadMethod.ts +536 -0
- package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
- package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
- package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
- package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
- package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
- package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
- package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
- package/ts/src/builder/artifacts/storyFiles.ts +61 -0
- package/ts/src/builder/artifacts/storyIndex.ts +166 -0
- package/ts/src/builder/cadence.ts +59 -0
- package/ts/src/builder/card.ts +192 -0
- package/ts/src/builder/checkpointRefused.ts +66 -0
- package/ts/src/builder/closure.ts +964 -0
- package/ts/src/builder/commands.ts +2053 -0
- package/ts/src/builder/cursorPosition.ts +176 -0
- package/ts/src/builder/cursorTransitions.ts +77 -0
- package/ts/src/builder/deliveryCursor.ts +577 -0
- package/ts/src/builder/deliveryStoryClosure.ts +521 -0
- package/ts/src/builder/deliveryStoryPlan.ts +536 -0
- package/ts/src/builder/deliveryStoryReady.ts +160 -0
- package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
- package/ts/src/builder/expand.ts +336 -0
- package/ts/src/builder/flowUnit.ts +339 -0
- package/ts/src/builder/homeSurface.ts +122 -0
- package/ts/src/builder/implementationGuard.ts +125 -0
- package/ts/src/builder/index.ts +11 -0
- package/ts/src/builder/lifecycleRefusal.ts +92 -0
- package/ts/src/builder/lifecycleRibbon.ts +159 -0
- package/ts/src/builder/load.ts +313 -0
- package/ts/src/builder/methodAdoption.ts +113 -0
- package/ts/src/builder/methodDefinition.ts +429 -0
- package/ts/src/builder/methodInspection.ts +371 -0
- package/ts/src/builder/plan.ts +408 -0
- package/ts/src/builder/planPreauthorization.ts +265 -0
- package/ts/src/builder/prepare.ts +225 -0
- package/ts/src/builder/projectPaths.ts +64 -0
- package/ts/src/builder/pull.ts +356 -0
- package/ts/src/builder/pullCandidates.ts +463 -0
- package/ts/src/builder/pullCandidatesRender.ts +238 -0
- package/ts/src/builder/refinementField.ts +93 -0
- package/ts/src/builder/releaseIntent.ts +173 -0
- package/ts/src/builder/resumeState.ts +84 -0
- package/ts/src/builder/resumeSurface.ts +149 -0
- package/ts/src/builder/roadmapGrammar.ts +300 -0
- package/ts/src/builder/roadmapScan.ts +97 -0
- package/ts/src/builder/roadmapScope.ts +210 -0
- package/ts/src/builder/scopePhrases.ts +146 -0
- package/ts/src/builder/storyPaths.ts +196 -0
- package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
- package/ts/src/builder/surfaceProtocol.ts +42 -0
- package/ts/src/builder/templateGeneration.ts +148 -0
- package/ts/src/builder/transition.ts +178 -0
- package/ts/src/consult/args.ts +99 -0
- package/ts/src/consult/core.ts +122 -0
- package/ts/src/consult/modelCatalog.ts +55 -0
- package/ts/src/consult/render.ts +47 -0
- package/ts/src/conversation/append.ts +414 -0
- package/ts/src/conversation/backfill.ts +387 -0
- package/ts/src/conversation/closeTail.ts +467 -0
- package/ts/src/conversation/extraction.ts +333 -0
- package/ts/src/conversation/extractionDriver.ts +130 -0
- package/ts/src/conversation/extractionRun.ts +70 -0
- package/ts/src/conversation/journeyInference.ts +165 -0
- package/ts/src/conversation/journeyRepair.ts +114 -0
- package/ts/src/conversation/lifecycleFaces.ts +197 -0
- package/ts/src/conversation/lifecycleWrites.ts +313 -0
- package/ts/src/conversation/listing.ts +77 -0
- package/ts/src/conversation/logger.ts +576 -0
- package/ts/src/conversation/loggerCli.ts +282 -0
- package/ts/src/conversation/loggerRuntime.ts +259 -0
- package/ts/src/conversation/metadataLifecycle.ts +443 -0
- package/ts/src/conversation/recall.ts +66 -0
- package/ts/src/conversation/sessionComposites.ts +258 -0
- package/ts/src/conversation/sessionImport.ts +90 -0
- package/ts/src/conversation/transcriptBackfill.ts +141 -0
- package/ts/src/cultivation/applyActions.ts +241 -0
- package/ts/src/cultivation/cluster.ts +86 -0
- package/ts/src/cultivation/consolidationStore.ts +281 -0
- package/ts/src/cultivation/promptContext.ts +62 -0
- package/ts/src/cultivation/propose.ts +305 -0
- package/ts/src/cultivation/scan.ts +155 -0
- package/ts/src/db/backupGate.ts +47 -0
- package/ts/src/db/bootstrap.ts +108 -0
- package/ts/src/db/bootstrapLock.ts +223 -0
- package/ts/src/db/copyGuard.ts +60 -0
- package/ts/src/db/database.ts +397 -0
- package/ts/src/db/decode.ts +66 -0
- package/ts/src/db/ftsIntegrity.ts +41 -0
- package/ts/src/db/migrateOnOpen.ts +214 -0
- package/ts/src/db/migrations.ts +576 -0
- package/ts/src/db/readyOnOpen.ts +35 -0
- package/ts/src/db/rowDecode.ts +42 -0
- package/ts/src/db/schema.ts +432 -0
- package/ts/src/db/schemaInventory.ts +225 -0
- package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
- package/ts/src/db/schemaState.ts +91 -0
- package/ts/src/db/schemaTsDivergence.ts +114 -0
- package/ts/src/descriptor/descriptorRead.ts +49 -0
- package/ts/src/docs/docsLint.ts +265 -0
- package/ts/src/explorer/handoff.ts +522 -0
- package/ts/src/explorer/render.ts +335 -0
- package/ts/src/explorer/story.ts +769 -0
- package/ts/src/explorer/transition.ts +72 -0
- package/ts/src/extensions/bindings.ts +170 -0
- package/ts/src/extensions/catalog.ts +249 -0
- package/ts/src/extensions/catalogCommands.ts +513 -0
- package/ts/src/extensions/catalogWrites.ts +652 -0
- package/ts/src/extensions/contextRuntime.ts +325 -0
- package/ts/src/extensions/dispatch.ts +374 -0
- package/ts/src/extensions/errors.ts +24 -0
- package/ts/src/extensions/manifest.ts +356 -0
- package/ts/src/extensions/migrations.ts +350 -0
- package/ts/src/extraction/conversation.ts +304 -0
- package/ts/src/extraction/conversationMetadata.ts +206 -0
- package/ts/src/extraction/fencing.ts +106 -0
- package/ts/src/extraction/json.ts +17 -0
- package/ts/src/extraction/prompts.ts +470 -0
- package/ts/src/frontDoor/args.ts +30 -0
- package/ts/src/frontDoor/argvShape.ts +106 -0
- package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
- package/ts/src/frontDoor/buildRoute.ts +398 -0
- package/ts/src/frontDoor/cli.ts +1865 -0
- package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
- package/ts/src/frontDoor/consultRoute.ts +50 -0
- package/ts/src/frontDoor/contentTailRoute.ts +239 -0
- package/ts/src/frontDoor/conversationLoggerRoute.ts +73 -0
- package/ts/src/frontDoor/cultivationRoute.ts +210 -0
- package/ts/src/frontDoor/dbPath.ts +131 -0
- package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
- package/ts/src/frontDoor/exploreRoute.ts +572 -0
- package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
- package/ts/src/frontDoor/frontDoorLog.ts +57 -0
- package/ts/src/frontDoor/identityWrite.ts +36 -0
- package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
- package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
- package/ts/src/frontDoor/liveBackup.ts +100 -0
- package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
- package/ts/src/frontDoor/namedRefusal.ts +35 -0
- package/ts/src/frontDoor/nodeSupport.ts +24 -0
- package/ts/src/frontDoor/render/consolidate.ts +199 -0
- package/ts/src/frontDoor/render/conversations.ts +33 -0
- package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
- package/ts/src/frontDoor/render/descriptor.ts +20 -0
- package/ts/src/frontDoor/render/detectPersona.ts +50 -0
- package/ts/src/frontDoor/render/icons.ts +13 -0
- package/ts/src/frontDoor/render/identity.ts +59 -0
- package/ts/src/frontDoor/render/identityRows.ts +17 -0
- package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
- package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
- package/ts/src/frontDoor/render/journeys.ts +79 -0
- package/ts/src/frontDoor/render/list.ts +41 -0
- package/ts/src/frontDoor/render/memories.ts +67 -0
- package/ts/src/frontDoor/render/recall.ts +49 -0
- package/ts/src/frontDoor/render/shadow.ts +155 -0
- package/ts/src/frontDoor/render/tasks.ts +99 -0
- package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
- package/ts/src/frontDoor/render/week.ts +113 -0
- package/ts/src/frontDoor/routing.ts +999 -0
- package/ts/src/frontDoor/runtimeRoute.ts +623 -0
- package/ts/src/frontDoor/searchRoute.ts +166 -0
- package/ts/src/frontDoor/seedPaths.ts +98 -0
- package/ts/src/frontDoor/soulRoute.ts +433 -0
- package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
- package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
- package/ts/src/frontDoor/usage.ts +176 -0
- package/ts/src/guards/claudePlugin.ts +252 -0
- package/ts/src/guards/packContents.ts +306 -0
- package/ts/src/guards/piSourcedSkills.ts +70 -0
- package/ts/src/guards/retiredRouteShadows.ts +57 -0
- package/ts/src/guards/retiredSurfaces.ts +564 -0
- package/ts/src/hooks/claude.ts +176 -0
- package/ts/src/hooks/codex.ts +45 -0
- package/ts/src/hooks/gemini.ts +126 -0
- package/ts/src/hooks/main.ts +109 -0
- package/ts/src/hooks/mirrorState.ts +71 -0
- package/ts/src/hooks/payload.ts +68 -0
- package/ts/src/hooks/runtime.ts +159 -0
- package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
- package/ts/src/identity/descriptorGenerate.ts +162 -0
- package/ts/src/identity/identityEdit.ts +143 -0
- package/ts/src/identity/identityKey.ts +77 -0
- package/ts/src/identity/identityRead.ts +69 -0
- package/ts/src/identity/identityStore.ts +80 -0
- package/ts/src/identity/journeyListing.ts +53 -0
- package/ts/src/identity/personaInspect.ts +44 -0
- package/ts/src/identity/personaListing.ts +37 -0
- package/ts/src/identity/setIdentity.ts +64 -0
- package/ts/src/index.ts +231 -0
- package/ts/src/init/init.ts +111 -0
- package/ts/src/journey/journeyOptions.ts +131 -0
- package/ts/src/journey/journeyRemoval.ts +158 -0
- package/ts/src/journey/journeyStatus.ts +149 -0
- package/ts/src/journey/journeySyncFile.ts +87 -0
- package/ts/src/journey/journeyWrite.ts +203 -0
- package/ts/src/journey/parentJourney.ts +41 -0
- package/ts/src/journey/validateParentJourney.ts +55 -0
- package/ts/src/mcp/boundary.ts +151 -0
- package/ts/src/mcp/guards.ts +244 -0
- package/ts/src/mcp/main.ts +121 -0
- package/ts/src/mcp/payload.ts +141 -0
- package/ts/src/mcp/protocol.ts +141 -0
- package/ts/src/mcp/readModels.ts +241 -0
- package/ts/src/mcp/registry.ts +201 -0
- package/ts/src/mcp/serve.ts +120 -0
- package/ts/src/mcp/tools/deterministic.ts +137 -0
- package/ts/src/mcp/tools/providerCrossing.ts +187 -0
- package/ts/src/mcp/wire.ts +8 -0
- package/ts/src/memory/journal.ts +180 -0
- package/ts/src/memory/listing.ts +107 -0
- package/ts/src/memory/memoryWrite.ts +88 -0
- package/ts/src/memory/reinforcement.ts +35 -0
- package/ts/src/mirror/context.ts +161 -0
- package/ts/src/mirror/defaultResolution.ts +199 -0
- package/ts/src/mirror/orchestration.ts +364 -0
- package/ts/src/mirror/reception.ts +194 -0
- package/ts/src/mirror/render.ts +75 -0
- package/ts/src/mirror/runtimeSession.ts +205 -0
- package/ts/src/mode/operatingMode.ts +148 -0
- package/ts/src/observability/callOutcome.ts +115 -0
- package/ts/src/observability/inspectLedger.ts +329 -0
- package/ts/src/observability/ledgerHooks.ts +126 -0
- package/ts/src/observability/llmCalls.ts +154 -0
- package/ts/src/persona/detectPersona.ts +81 -0
- package/ts/src/planning/promptAssembly.ts +111 -0
- package/ts/src/planning/weekPending.ts +81 -0
- package/ts/src/planning/weekPlan.ts +183 -0
- package/ts/src/planning/weekSave.ts +117 -0
- package/ts/src/providers/config.ts +189 -0
- package/ts/src/providers/cost.ts +64 -0
- package/ts/src/providers/credits.ts +204 -0
- package/ts/src/providers/embedding.ts +321 -0
- package/ts/src/providers/familyProviders.ts +147 -0
- package/ts/src/providers/llm.ts +334 -0
- package/ts/src/providers/openrouter.ts +244 -0
- package/ts/src/providers/redaction.ts +110 -0
- package/ts/src/providers/replay.ts +15 -0
- package/ts/src/providers/transport.ts +410 -0
- package/ts/src/repair/encodingRepair.ts +253 -0
- package/ts/src/runtime/backup.ts +151 -0
- package/ts/src/runtime/channel.ts +146 -0
- package/ts/src/runtime/config.ts +168 -0
- package/ts/src/runtime/diagnose.ts +686 -0
- package/ts/src/runtime/git.ts +510 -0
- package/ts/src/runtime/installKind.ts +220 -0
- package/ts/src/runtime/migrate.ts +154 -0
- package/ts/src/runtime/packageIdentity.ts +102 -0
- package/ts/src/runtime/releaseNotes.ts +244 -0
- package/ts/src/runtime/status.ts +557 -0
- package/ts/src/runtime/strategies/clone.ts +200 -0
- package/ts/src/runtime/strategies/package.ts +328 -0
- package/ts/src/runtime/treeRoot.ts +30 -0
- package/ts/src/runtime/update.ts +445 -0
- package/ts/src/runtime/updateGate.ts +40 -0
- package/ts/src/runtime/updatePipeline.ts +159 -0
- package/ts/src/runtime/version.ts +36 -0
- package/ts/src/runtime/wiring.ts +130 -0
- package/ts/src/search/memorySearch.ts +338 -0
- package/ts/src/search/ranker.ts +154 -0
- package/ts/src/seed/seed.ts +256 -0
- package/ts/src/seed/seedYaml.ts +133 -0
- package/ts/src/soul/apply.ts +218 -0
- package/ts/src/soul/harvest.ts +267 -0
- package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
- package/ts/src/soul/prompts/soul_self_voice.md +219 -0
- package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
- package/ts/src/soul/prompts.ts +85 -0
- package/ts/src/soul/render.ts +358 -0
- package/ts/src/soul/state.ts +212 -0
- package/ts/src/soul/transition.ts +37 -0
- package/ts/src/tasks/journeyPathParse.ts +113 -0
- package/ts/src/tasks/taskImportSync.ts +142 -0
- package/ts/src/tasks/taskStore.ts +312 -0
- package/ts/src/tasks/weekView.ts +120 -0
- package/ts/src/util/clipCodePoints.ts +26 -0
- package/ts/src/util/paths.ts +63 -0
- package/ts/src/util/program.ts +9 -0
- package/ts/src/util/pyGenerators.ts +144 -0
- package/ts/src/util/pythonText.ts +317 -0
- package/ts/src/util/semver.ts +45 -0
- package/ts/src/util/shellWord.ts +25 -0
- package/ts/src/util/slug.ts +64 -0
- package/ts/src/welcome/card.ts +339 -0
- package/ts/src/welcome/statusLine.ts +101 -0
- package/ts/src/welcome/updateCache.ts +129 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
// CV22.DS7.US8 plateau 1 — roadmap snapshot and pull candidates.
|
|
2
|
+
//
|
|
3
|
+
// Port of `src/memory/builder/pull_candidates.py` (the inspection half; the
|
|
4
|
+
// three rendered surfaces live in `pullCandidatesRender.ts`).
|
|
5
|
+
//
|
|
6
|
+
// Three snapshot grammars with precedence, and they are not interchangeable:
|
|
7
|
+
//
|
|
8
|
+
// * `cvTableItems` reads `| Code | Capability Value | Status |` and STOPS at
|
|
9
|
+
// the first non-empty, non-table line;
|
|
10
|
+
// * `dsTableItems` reads `| Code | Delivery Story | Status |` and merely
|
|
11
|
+
// leaves table mode at such a line, so it ACCUMULATES across
|
|
12
|
+
// `## Chapter N —` sections;
|
|
13
|
+
// * `cvHeadingItems` reads `## CV<n>: <title>` followed by `**Status:**`.
|
|
14
|
+
//
|
|
15
|
+
// The first grammar that yields anything wins, so a file carrying a CV table
|
|
16
|
+
// and a DS table returns only the CV rows. A port that merges them, or that
|
|
17
|
+
// gives the DS table the CV table's termination rule, reads a different roadmap.
|
|
18
|
+
|
|
19
|
+
import { PYTHON_WHITESPACE_CLASS, pySplitLines, pyStrip } from "#util/pythonText.ts";
|
|
20
|
+
import {
|
|
21
|
+
linkFreeTitle,
|
|
22
|
+
matchHeading,
|
|
23
|
+
matchStatus,
|
|
24
|
+
matchType,
|
|
25
|
+
statusClause,
|
|
26
|
+
stripMarkdownLink,
|
|
27
|
+
tableRowCells,
|
|
28
|
+
} from "./roadmapGrammar.ts";
|
|
29
|
+
import { readRoadmapFile, scanRoadmapIndexFiles } from "./roadmapScan.ts";
|
|
30
|
+
|
|
31
|
+
// Every line walk below is Python `content.splitlines()` via `pySplitLines`, not
|
|
32
|
+
// `split("\n")`: Python knows ELEVEN line boundaries (including \v, \f, U+001C
|
|
33
|
+
// \u2028, and \u2029), and a roadmap file carrying a form feed would otherwise
|
|
34
|
+
// parse as one long line here and as two lines there. Likewise every `.strip()`
|
|
35
|
+
// is `pyStrip`, because Python's whitespace set includes U+001C-U+001F and
|
|
36
|
+
// U+0085 and excludes U+FEFF — the exact opposite of `String.prototype.trim()`
|
|
37
|
+
// on both counts.
|
|
38
|
+
|
|
39
|
+
/** Python `_CANDIDATE_STATUSES`: substring markers, not an enum. */
|
|
40
|
+
const CANDIDATE_STATUSES = ["Planned", "Active", "Blocked", "Candidate"] as const;
|
|
41
|
+
|
|
42
|
+
const CV_TABLE_HEADER = "| Code | Capability Value | Status |";
|
|
43
|
+
const DS_TABLE_HEADER = "| Code | Delivery Story | Status |";
|
|
44
|
+
|
|
45
|
+
/** Python `_TOP_LEVEL_DS_RE`: a bare `DS-35`, anchored. */
|
|
46
|
+
const TOP_LEVEL_DS_RE = /^DS-\d+$/u;
|
|
47
|
+
|
|
48
|
+
// These three are Python `re.match` against an ALREADY-STRIPPED single line, so
|
|
49
|
+
// they anchor at the start and end of that line. `\s` is spelled out as Python's
|
|
50
|
+
// set for the same reason as in `roadmapGrammar.ts`, and `.` as `[^\n]`.
|
|
51
|
+
const WS = `[${PYTHON_WHITESPACE_CLASS}]`;
|
|
52
|
+
|
|
53
|
+
/** Python's `^##\s+(CV\d+):\s+(.+?)\s*$`. */
|
|
54
|
+
const CV_HEADING_RE = new RegExp(`^##${WS}+(CV\\d+):${WS}+([^\\n]+?)${WS}*$`, "u");
|
|
55
|
+
|
|
56
|
+
/** Python's `^\*\*Status:\*\*\s*(.+?)\s*$`. */
|
|
57
|
+
const LINE_STATUS_RE = new RegExp(`^\\*\\*Status:\\*\\*${WS}*([^\\n]+?)${WS}*$`, "u");
|
|
58
|
+
|
|
59
|
+
/** Python's `^-\s+(DS\d+)\s+(.+?)\.?$`. */
|
|
60
|
+
const DS_BULLET_RE = new RegExp(`^-${WS}+(DS\\d+)${WS}+([^\\n]+?)\\.?$`, "u");
|
|
61
|
+
|
|
62
|
+
export interface RoadmapSnapshotItem {
|
|
63
|
+
code: string;
|
|
64
|
+
title: string;
|
|
65
|
+
/** The status cell's clause, never the cell (CR103). */
|
|
66
|
+
status: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface RoadmapSnapshotReport {
|
|
70
|
+
journey: string;
|
|
71
|
+
method: string;
|
|
72
|
+
items: RoadmapSnapshotItem[];
|
|
73
|
+
source: string | null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type CandidateLevel = "cv" | "delivery_story" | "user_story" | "technical_story";
|
|
77
|
+
|
|
78
|
+
export interface PullCandidate {
|
|
79
|
+
code: string;
|
|
80
|
+
title: string;
|
|
81
|
+
level: string;
|
|
82
|
+
/** The status line's clause, never the line (CR103). */
|
|
83
|
+
status: string;
|
|
84
|
+
/** POSIX path relative to the project root, as Python's `relative_to(root)`. */
|
|
85
|
+
path: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The raw project scan. It carries no recommendation: since CR002 a
|
|
90
|
+
* recommendation exists only for a journey's scope (`scopePullCandidates` in
|
|
91
|
+
* `roadmapScope.ts`), never for the project as a whole.
|
|
92
|
+
*/
|
|
93
|
+
export interface PullCandidatesReport {
|
|
94
|
+
journey: string;
|
|
95
|
+
method: string;
|
|
96
|
+
candidates: PullCandidate[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ProjectPaths {
|
|
100
|
+
/** Absolute project root. */
|
|
101
|
+
root: string;
|
|
102
|
+
/** Absolute `<root>/docs/project/roadmap`. */
|
|
103
|
+
roadmapRoot: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** `<root>/docs/project/roadmap`, the only roadmap location Python looks in. */
|
|
107
|
+
export function roadmapPaths(root: string): ProjectPaths {
|
|
108
|
+
return { root, roadmapRoot: `${root}/docs/project/roadmap` };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function hasCandidateStatus(status: string): boolean {
|
|
112
|
+
return CANDIDATE_STATUSES.some((marker) => status.includes(marker));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Python `inspect_roadmap_snapshot`: the compact snapshot from
|
|
117
|
+
* `docs/project/roadmap/index.md` alone — not the whole tree.
|
|
118
|
+
*
|
|
119
|
+
* `source` is the project-relative path of that one file, or `null` when the
|
|
120
|
+
* project path is absent, the file is missing, or it cannot be read.
|
|
121
|
+
*/
|
|
122
|
+
export function inspectRoadmapSnapshot(
|
|
123
|
+
root: string | null,
|
|
124
|
+
options: { journey: string; method: string },
|
|
125
|
+
): RoadmapSnapshotReport {
|
|
126
|
+
const empty: RoadmapSnapshotReport = {
|
|
127
|
+
journey: options.journey,
|
|
128
|
+
method: options.method,
|
|
129
|
+
items: [],
|
|
130
|
+
source: null,
|
|
131
|
+
};
|
|
132
|
+
if (root === null) return empty;
|
|
133
|
+
const indexPath = `${root}/docs/project/roadmap/index.md`;
|
|
134
|
+
const content = readRoadmapFile(indexPath);
|
|
135
|
+
if (content === null) return empty;
|
|
136
|
+
return {
|
|
137
|
+
journey: options.journey,
|
|
138
|
+
method: options.method,
|
|
139
|
+
items: snapshotItemsFromContent(content),
|
|
140
|
+
source: "docs/project/roadmap/index.md",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function snapshotItemsFromContent(content: string): RoadmapSnapshotItem[] {
|
|
145
|
+
const cv = cvTableItems(content);
|
|
146
|
+
if (cv.length > 0) return cv;
|
|
147
|
+
const ds = dsTableItems(content);
|
|
148
|
+
if (ds.length > 0) return ds;
|
|
149
|
+
return cvHeadingItems(content);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Python `_cv_table_items`. Note the termination: any non-empty line that does
|
|
154
|
+
* not start with `|` BREAKS the scan, so only the first CV table is read.
|
|
155
|
+
*/
|
|
156
|
+
function cvTableItems(content: string): RoadmapSnapshotItem[] {
|
|
157
|
+
const items: RoadmapSnapshotItem[] = [];
|
|
158
|
+
let inTable = false;
|
|
159
|
+
for (const rawLine of pySplitLines(content)) {
|
|
160
|
+
const line = pyStrip(rawLine);
|
|
161
|
+
if (line === CV_TABLE_HEADER) {
|
|
162
|
+
inTable = true;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (inTable && line.startsWith("|---")) continue;
|
|
166
|
+
if (inTable && line.startsWith("|")) {
|
|
167
|
+
const parts = tableRowCells(line);
|
|
168
|
+
if (parts.length >= 3) {
|
|
169
|
+
items.push({
|
|
170
|
+
code: stripMarkdownLink(parts[0] ?? ""),
|
|
171
|
+
title: linkFreeTitle(parts[1] ?? ""),
|
|
172
|
+
status: statusClause(parts[2] ?? ""),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (inTable && line) break;
|
|
178
|
+
}
|
|
179
|
+
return items;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Python `_ds_table_items`. Same shape as the CV table with one crucial
|
|
184
|
+
* difference: a non-table line only leaves table mode (`in_table = False`), so
|
|
185
|
+
* a later `| Code | Delivery Story | Status |` header resumes and the rows
|
|
186
|
+
* ACCUMULATE across chapters.
|
|
187
|
+
*/
|
|
188
|
+
function dsTableItems(content: string): RoadmapSnapshotItem[] {
|
|
189
|
+
const items: RoadmapSnapshotItem[] = [];
|
|
190
|
+
let inTable = false;
|
|
191
|
+
for (const rawLine of pySplitLines(content)) {
|
|
192
|
+
const line = pyStrip(rawLine);
|
|
193
|
+
if (line === DS_TABLE_HEADER) {
|
|
194
|
+
inTable = true;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (inTable && line.startsWith("|---")) continue;
|
|
198
|
+
if (inTable && line.startsWith("|")) {
|
|
199
|
+
const parts = tableRowCells(line);
|
|
200
|
+
if (parts.length >= 3) {
|
|
201
|
+
items.push({
|
|
202
|
+
code: stripMarkdownLink(parts[0] ?? ""),
|
|
203
|
+
title: linkFreeTitle(parts[1] ?? ""),
|
|
204
|
+
status: statusClause(parts[2] ?? ""),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (inTable) inTable = false;
|
|
210
|
+
}
|
|
211
|
+
return items;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Python `_cv_heading_items`: `## CV<n>: <title>` then the next `**Status:**`.
|
|
216
|
+
* The pairing is consumed — `current_cv` is cleared after a status matches — so
|
|
217
|
+
* a second status line without an intervening heading is ignored.
|
|
218
|
+
*/
|
|
219
|
+
function cvHeadingItems(content: string): RoadmapSnapshotItem[] {
|
|
220
|
+
const items: RoadmapSnapshotItem[] = [];
|
|
221
|
+
let currentCv: { code: string; title: string } | null = null;
|
|
222
|
+
for (const rawLine of pySplitLines(content)) {
|
|
223
|
+
const line = pyStrip(rawLine);
|
|
224
|
+
const cvMatch = CV_HEADING_RE.exec(line);
|
|
225
|
+
if (cvMatch) {
|
|
226
|
+
currentCv = { code: cvMatch[1] ?? "", title: linkFreeTitle(pyStrip(cvMatch[2] ?? "")) };
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const statusMatch = LINE_STATUS_RE.exec(line);
|
|
230
|
+
if (statusMatch && currentCv) {
|
|
231
|
+
items.push({
|
|
232
|
+
code: currentCv.code,
|
|
233
|
+
title: currentCv.title,
|
|
234
|
+
status: statusClause(statusMatch[1] ?? ""),
|
|
235
|
+
});
|
|
236
|
+
currentCv = null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return items;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Python `inspect_pull_candidates`: scan every non-legacy `index.md`, collect
|
|
244
|
+
* the package's own candidate plus any `Candidate Delivery Stories:` bullets,
|
|
245
|
+
* then drop everything a `done.md` covers.
|
|
246
|
+
*/
|
|
247
|
+
export function inspectPullCandidates(
|
|
248
|
+
root: string | null,
|
|
249
|
+
options: { journey: string; method: string },
|
|
250
|
+
): PullCandidatesReport {
|
|
251
|
+
if (root === null) {
|
|
252
|
+
return { journey: options.journey, method: options.method, candidates: [] };
|
|
253
|
+
}
|
|
254
|
+
const { roadmapRoot } = roadmapPaths(root);
|
|
255
|
+
const files = scanRoadmapIndexFiles(roadmapRoot);
|
|
256
|
+
|
|
257
|
+
// Python's path is relative to the PROJECT root, not the roadmap root.
|
|
258
|
+
const projectPrefix = "docs/project/roadmap/";
|
|
259
|
+
const raw: PullCandidate[] = [];
|
|
260
|
+
const contents = new Map<string, string>();
|
|
261
|
+
for (const file of files) {
|
|
262
|
+
const content = readRoadmapFile(file.absolutePath);
|
|
263
|
+
if (content === null) continue;
|
|
264
|
+
contents.set(file.relativePath, content);
|
|
265
|
+
const projectRelative = `${projectPrefix}${file.relativePath}`;
|
|
266
|
+
const own = candidateFromIndexContent(projectRelative, content);
|
|
267
|
+
if (own !== null) raw.push(own);
|
|
268
|
+
raw.push(...candidateDeliveryStoriesFromContent(projectRelative, content));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const candidates = raw.filter(
|
|
272
|
+
(candidate) => !candidateHasDoneArtifact(root, candidate, files, contents),
|
|
273
|
+
);
|
|
274
|
+
return { journey: options.journey, method: options.method, candidates };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function candidateFromIndexContent(
|
|
278
|
+
projectRelativePath: string,
|
|
279
|
+
content: string,
|
|
280
|
+
): PullCandidate | null {
|
|
281
|
+
const heading = matchHeading(content);
|
|
282
|
+
const status = matchStatus(content);
|
|
283
|
+
if (!heading || status === null) return null;
|
|
284
|
+
const statusText = statusClause(status);
|
|
285
|
+
if (!hasCandidateStatus(statusText)) return null;
|
|
286
|
+
const code = pyStrip(heading.code);
|
|
287
|
+
return {
|
|
288
|
+
code,
|
|
289
|
+
title: linkFreeTitle(pyStrip(heading.title)),
|
|
290
|
+
level: levelFor(code, content),
|
|
291
|
+
status: statusText,
|
|
292
|
+
path: projectRelativePath,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Python `_candidate_delivery_stories_from_content`: the `Candidate Delivery
|
|
298
|
+
* Stories:` bullet list under a `## CV<n>:` heading.
|
|
299
|
+
*
|
|
300
|
+
* Each Delivery Story carries its own title. Python prefixed the CV's as
|
|
301
|
+
* `"<CV title> / <DS title>"`, and five readers cut every title at a `/` to undo
|
|
302
|
+
* it, so any title with a slash of its own reached the Navigator as a fragment.
|
|
303
|
+
* CR018 removed the prefix and the cuts together; a surface that needs the CV's
|
|
304
|
+
* title reads the CV's own row.
|
|
305
|
+
*/
|
|
306
|
+
function candidateDeliveryStoriesFromContent(
|
|
307
|
+
projectRelativePath: string,
|
|
308
|
+
content: string,
|
|
309
|
+
): PullCandidate[] {
|
|
310
|
+
const candidates: PullCandidate[] = [];
|
|
311
|
+
let currentCv: string | null = null;
|
|
312
|
+
let currentStatus: string | null = null;
|
|
313
|
+
let inCandidateDeliveryStories = false;
|
|
314
|
+
|
|
315
|
+
for (const rawLine of pySplitLines(content)) {
|
|
316
|
+
const line = pyStrip(rawLine);
|
|
317
|
+
const cvMatch = CV_HEADING_RE.exec(line);
|
|
318
|
+
if (cvMatch) {
|
|
319
|
+
currentCv = cvMatch[1] ?? null;
|
|
320
|
+
currentStatus = null;
|
|
321
|
+
inCandidateDeliveryStories = false;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const statusMatch = LINE_STATUS_RE.exec(line);
|
|
325
|
+
if (statusMatch && currentCv) {
|
|
326
|
+
currentStatus = statusClause(statusMatch[1] ?? "");
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (line === "Candidate Delivery Stories:") {
|
|
330
|
+
inCandidateDeliveryStories = true;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (inCandidateDeliveryStories && line.startsWith("- ") && currentCv) {
|
|
334
|
+
const dsMatch = DS_BULLET_RE.exec(line);
|
|
335
|
+
if (!dsMatch) continue;
|
|
336
|
+
const status = currentStatus || "Candidate";
|
|
337
|
+
if (!hasCandidateStatus(status)) continue;
|
|
338
|
+
candidates.push({
|
|
339
|
+
code: `${currentCv}.${dsMatch[1]}`,
|
|
340
|
+
title: linkFreeTitle(pyStrip(dsMatch[2] ?? "")),
|
|
341
|
+
level: "delivery_story",
|
|
342
|
+
status,
|
|
343
|
+
path: projectRelativePath,
|
|
344
|
+
});
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (inCandidateDeliveryStories && line && !line.startsWith("- ")) {
|
|
348
|
+
inCandidateDeliveryStories = false;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return candidates;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Python `_candidate_has_done_artifact`. Two-step, and the second step is the
|
|
356
|
+
* one a port drops: when the candidate's own file does not claim its code (a
|
|
357
|
+
* bullet-derived candidate, whose path is the CV index), Python searches the
|
|
358
|
+
* WHOLE tree for a package whose heading claims that code, then checks for a
|
|
359
|
+
* `done.md` at that package or any ancestor up to the roadmap root.
|
|
360
|
+
*
|
|
361
|
+
* So a child package with no `done.md` of its own is still filtered out when an
|
|
362
|
+
* ancestor has one.
|
|
363
|
+
*/
|
|
364
|
+
function candidateHasDoneArtifact(
|
|
365
|
+
root: string,
|
|
366
|
+
candidate: PullCandidate,
|
|
367
|
+
files: readonly { components: string[]; relativePath: string; absolutePath: string }[],
|
|
368
|
+
contents: ReadonlyMap<string, string>,
|
|
369
|
+
): boolean {
|
|
370
|
+
const projectPrefix = "docs/project/roadmap/";
|
|
371
|
+
const candidateRelative = candidate.path.startsWith(projectPrefix)
|
|
372
|
+
? candidate.path.slice(projectPrefix.length)
|
|
373
|
+
: null;
|
|
374
|
+
|
|
375
|
+
let searchRelatives: string[] = [];
|
|
376
|
+
if (candidateRelative !== null) {
|
|
377
|
+
const ownContent = contents.get(candidateRelative);
|
|
378
|
+
const ownHeading = ownContent ? matchHeading(ownContent) : null;
|
|
379
|
+
const ownCode = ownHeading ? pyStrip(ownHeading.code) : undefined;
|
|
380
|
+
if (ownCode === candidate.code) searchRelatives = [candidateRelative];
|
|
381
|
+
}
|
|
382
|
+
if (searchRelatives.length === 0) {
|
|
383
|
+
searchRelatives = files
|
|
384
|
+
.filter((file) => {
|
|
385
|
+
const heading = matchHeading(contents.get(file.relativePath) ?? "");
|
|
386
|
+
return heading !== null && pyStrip(heading.code) === candidate.code;
|
|
387
|
+
})
|
|
388
|
+
.map((file) => file.relativePath);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for (const relativePath of searchRelatives) {
|
|
392
|
+
const packageComponents = relativePath.split("/").slice(0, -1);
|
|
393
|
+
if (hasDoneArtifact(root, packageComponents)) return true;
|
|
394
|
+
}
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Python `_has_done_artifact`: walk up to (and including) the roadmap root. */
|
|
399
|
+
function hasDoneArtifact(root: string, packageComponents: readonly string[]): boolean {
|
|
400
|
+
const { roadmapRoot } = roadmapPaths(root);
|
|
401
|
+
for (let depth = packageComponents.length; depth >= 0; depth -= 1) {
|
|
402
|
+
const current = [roadmapRoot, ...packageComponents.slice(0, depth)].join("/");
|
|
403
|
+
if (readRoadmapFile(`${current}/done.md`) !== null) return true;
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Python `_level_for`: `**Type:**` decides by SUBSTRING, so "Technical Story"
|
|
410
|
+
* and "a technical spike" both mean `technical_story`. Falls back to the code
|
|
411
|
+
* shape.
|
|
412
|
+
*/
|
|
413
|
+
export function levelFor(code: string, content: string): string {
|
|
414
|
+
const type = matchType(content);
|
|
415
|
+
if (type !== null) {
|
|
416
|
+
const storyType = pyStrip(type).toLowerCase();
|
|
417
|
+
if (storyType.includes("technical")) return "technical_story";
|
|
418
|
+
if (storyType.includes("user")) return "user_story";
|
|
419
|
+
}
|
|
420
|
+
if (code.includes(".DS") || TOP_LEVEL_DS_RE.test(code)) return "delivery_story";
|
|
421
|
+
return "cv";
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Python `_recommend`: status is the OUTER loop and level the inner one, so a
|
|
426
|
+
* Planned delivery story beats a Candidate user story. Falls back to the first
|
|
427
|
+
* candidate in scan order — which is why the scan order matters.
|
|
428
|
+
*/
|
|
429
|
+
export function recommend(candidates: readonly PullCandidate[]): PullCandidate | null {
|
|
430
|
+
for (const preferredStatus of ["Planned", "Candidate", "Active", "Blocked"]) {
|
|
431
|
+
for (const preferredLevel of ["user_story", "technical_story", "delivery_story"]) {
|
|
432
|
+
for (const candidate of candidates) {
|
|
433
|
+
if (candidate.level === preferredLevel && candidate.status.includes(preferredStatus)) {
|
|
434
|
+
return candidate;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return candidates[0] ?? null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Python `_status_marker`. Order matters: Active is tested before Candidate. Given a
|
|
444
|
+
* clause (CR103), so its fallback prints at most `○ ` and `STATUS_CLAUSE_WIDTH`, and a
|
|
445
|
+
* Blocked CV in focus keeps its title on the snapshot's focus row.
|
|
446
|
+
*/
|
|
447
|
+
export function statusMarker(status: string): string {
|
|
448
|
+
if (status.includes("Active") || status.includes("In Progress")) return "◉ active";
|
|
449
|
+
if (status.includes("Candidate")) return "◉ candidate";
|
|
450
|
+
if (status.includes("Planned")) return "○ planned";
|
|
451
|
+
if (status.includes("Done")) return "✓ done";
|
|
452
|
+
return `○ ${status.toLowerCase()}`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** Python `_format_candidate`, given a candidate whose status is its clause (CR103). */
|
|
456
|
+
export function formatCandidate(candidate: PullCandidate): string {
|
|
457
|
+
return `${candidate.code} — ${candidate.title} [${candidate.level}] ${candidate.status} (${candidate.path})`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Python `_candidate_lines`. */
|
|
461
|
+
export function candidateLines(candidates: readonly PullCandidate[]): string[] {
|
|
462
|
+
return candidates.map(formatCandidate);
|
|
463
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// CV22.DS7.US8 plateau 1 — the three roadmap-inspection surfaces.
|
|
2
|
+
//
|
|
3
|
+
// Port of the renderers in `src/memory/builder/pull_candidates.py`:
|
|
4
|
+
// `PROJECT POSITION`, `ROADMAP SNAPSHOT`, and `PULL CANDIDATES`. All three are
|
|
5
|
+
// `transport=verbatim`, so what matters is bytes, not layout intent.
|
|
6
|
+
//
|
|
7
|
+
// The hardcoded frame lines in Python are RAGGED, measured in code points of
|
|
8
|
+
// their inner width:
|
|
9
|
+
//
|
|
10
|
+
// ╭…╮ / ╰…╯ and the blank rows 56 (what `cardText` also produces)
|
|
11
|
+
// `🧭 PROJECT POSITION` 54 (two short)
|
|
12
|
+
// `🟪■ PULL CANDIDATES` 55 (one short)
|
|
13
|
+
//
|
|
14
|
+
// That is not a defect to fix here. The author padded them by eye against a
|
|
15
|
+
// terminal, where an astral-plane glyph occupies two columns but one code point,
|
|
16
|
+
// so the literals are visually aligned and numerically inconsistent. A port that
|
|
17
|
+
// normalizes them to 56 produces a prettier card and fails parity. They are
|
|
18
|
+
// copied verbatim and the golden grades them. Two more, the empty `roadmap field`
|
|
19
|
+
// and `Backlog` rows, left with CR002: an unscoped snapshot now states the
|
|
20
|
+
// scope in `cardLine`'s own width instead.
|
|
21
|
+
//
|
|
22
|
+
// CR002: "where are we" and "what next" come from the journey's scoped view
|
|
23
|
+
// (`roadmapScope.ts`), never from the whole project. Unscoped, a surface names no
|
|
24
|
+
// focus and no recommendation, labels its list project-wide, and gives the literal
|
|
25
|
+
// Pull command; the words are `scopePhrases.ts`'s.
|
|
26
|
+
//
|
|
27
|
+
// CR103: every status these surfaces print or mark is a clause, read once by the
|
|
28
|
+
// roadmap readers. The focus row and the progress line mark it; the candidate rows
|
|
29
|
+
// print it. A Blocked CV in focus once printed its whole status on the right of the
|
|
30
|
+
// focus row and pushed the border off the card.
|
|
31
|
+
|
|
32
|
+
import { cardClipped, cardLine, cardPrefixed, cardText, cardWrapped } from "./card.ts";
|
|
33
|
+
import {
|
|
34
|
+
candidateLines,
|
|
35
|
+
formatCandidate,
|
|
36
|
+
type PullCandidate,
|
|
37
|
+
type RoadmapSnapshotItem,
|
|
38
|
+
type RoadmapSnapshotReport,
|
|
39
|
+
statusMarker,
|
|
40
|
+
} from "./pullCandidates.ts";
|
|
41
|
+
import type { ScopedPullCandidates } from "./roadmapScope.ts";
|
|
42
|
+
import {
|
|
43
|
+
candidateListHeader,
|
|
44
|
+
NO_ITEM_PULLED_YET,
|
|
45
|
+
noRecommendationLines,
|
|
46
|
+
outsideCountLines,
|
|
47
|
+
scopeFocus,
|
|
48
|
+
} from "./scopePhrases.ts";
|
|
49
|
+
import { wrapAriadSurface } from "./surfaceProtocol.ts";
|
|
50
|
+
|
|
51
|
+
const FRAME_TOP = "╭────────────────────────────────────────────────────────╮";
|
|
52
|
+
const FRAME_BOTTOM = "╰────────────────────────────────────────────────────────╯";
|
|
53
|
+
const FRAME_BLANK = "│ │";
|
|
54
|
+
|
|
55
|
+
/** Python `_format_project_recommendation`, for a recommendation that exists. */
|
|
56
|
+
function formatProjectRecommendation(candidate: PullCandidate): string {
|
|
57
|
+
return `🟦[${candidate.code}] ${candidate.title} — recommended next pull`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Python `_project_focus_lines`, minus its `available candidates: none` line:
|
|
62
|
+
* "What looks next?" now says what is left in the scope, so the focus does not
|
|
63
|
+
* repeat it. The progress line strips the marker glyphs from `statusMarker`'s
|
|
64
|
+
* output with three sequential replaces, so a status whose text itself contains
|
|
65
|
+
* `◉ ` would lose it too. A focus with no status (a stated placeholder) has no
|
|
66
|
+
* progress to show.
|
|
67
|
+
*/
|
|
68
|
+
function projectFocusLines(focus: RoadmapSnapshotItem | null): string[] {
|
|
69
|
+
if (focus === null) return [cardText(NO_ITEM_PULLED_YET)];
|
|
70
|
+
const lines = cardWrapped(`🟪[${focus.code}] ${focus.title}`);
|
|
71
|
+
if (focus.status) {
|
|
72
|
+
const marker = statusMarker(focus.status);
|
|
73
|
+
const progress = marker.replace("◉ ", "").replace("○ ", "").replace("✓ ", "");
|
|
74
|
+
lines.push(...cardWrapped(`progress: ${progress}`));
|
|
75
|
+
}
|
|
76
|
+
return lines;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The scoped candidate list: its header, the candidates, and what it leaves out. */
|
|
80
|
+
function candidateListLines(view: ScopedPullCandidates): string[] {
|
|
81
|
+
return [
|
|
82
|
+
cardText(candidateListHeader(view.scope)),
|
|
83
|
+
...cardPrefixed(candidateLines(view.shown), "-"),
|
|
84
|
+
...outsideCountLines(view).flatMap(cardWrapped),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The recommendation, formatted by the surface; or what stands in its place. */
|
|
89
|
+
function nextMoveLines(
|
|
90
|
+
view: ScopedPullCandidates,
|
|
91
|
+
format: (candidate: PullCandidate) => string,
|
|
92
|
+
): string[] {
|
|
93
|
+
if (view.recommended !== null) return cardWrapped(format(view.recommended));
|
|
94
|
+
return noRecommendationLines(view).flatMap(cardWrapped);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Python `render_project_position_report`, over the journey's scoped view. */
|
|
98
|
+
export function renderProjectPositionReport(
|
|
99
|
+
report: RoadmapSnapshotReport,
|
|
100
|
+
options: { view: ScopedPullCandidates; justMoved?: string | null },
|
|
101
|
+
): string {
|
|
102
|
+
const { view } = options;
|
|
103
|
+
const justMoved = options.justMoved ?? null;
|
|
104
|
+
const focus = scopeFocus(report.items, view.scope);
|
|
105
|
+
const lines: string[] = [
|
|
106
|
+
"Roadmap",
|
|
107
|
+
"",
|
|
108
|
+
FRAME_TOP,
|
|
109
|
+
"│ 🧭 PROJECT POSITION │",
|
|
110
|
+
FRAME_BLANK,
|
|
111
|
+
cardText("Where are we now?"),
|
|
112
|
+
...projectFocusLines(focus),
|
|
113
|
+
FRAME_BLANK,
|
|
114
|
+
];
|
|
115
|
+
if (justMoved) {
|
|
116
|
+
lines.push(cardText("What just moved?"), ...cardWrapped(justMoved), FRAME_BLANK);
|
|
117
|
+
}
|
|
118
|
+
lines.push(
|
|
119
|
+
cardText("What looks next?"),
|
|
120
|
+
...nextMoveLines(view, formatProjectRecommendation),
|
|
121
|
+
FRAME_BLANK,
|
|
122
|
+
...candidateListLines(view),
|
|
123
|
+
FRAME_BLANK,
|
|
124
|
+
...cardWrapped("Choose a pull when ready."),
|
|
125
|
+
FRAME_BOTTOM,
|
|
126
|
+
);
|
|
127
|
+
return wrapAriadSurface("project_position", `${lines.join("\n")}\n`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Python `render_roadmap_snapshot_report`.
|
|
132
|
+
*
|
|
133
|
+
* Since CR002 the focus is the journey's CV and the `current` row is the scoped
|
|
134
|
+
* recommendation: one question, where Python asked two. Python chose `current` by
|
|
135
|
+
* running `_recommend` over only the focus CV's OWN delivery candidates
|
|
136
|
+
* (`code.startswith(f"{focus.code}.")`), a different question from the
|
|
137
|
+
* report-level recommendation — so the two rows can
|
|
138
|
+
* name different stories, and do whenever the recommended pull belongs to
|
|
139
|
+
* another CV.
|
|
140
|
+
*/
|
|
141
|
+
export function renderRoadmapSnapshotReport(
|
|
142
|
+
report: RoadmapSnapshotReport,
|
|
143
|
+
options: { view: ScopedPullCandidates },
|
|
144
|
+
): string {
|
|
145
|
+
const { view } = options;
|
|
146
|
+
const focus = scopeFocus(report.items, view.scope);
|
|
147
|
+
const result = view.shown.length > 0 ? "ready to pull" : "no pull candidates";
|
|
148
|
+
const lines: string[] = [
|
|
149
|
+
"ROADMAP SNAPSHOT",
|
|
150
|
+
"Delivery field overview",
|
|
151
|
+
"",
|
|
152
|
+
"view overview",
|
|
153
|
+
`result of roadmap-snapshot ${result}`,
|
|
154
|
+
"",
|
|
155
|
+
FRAME_TOP,
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
if (focus === null) {
|
|
159
|
+
lines.push(cardLine("roadmap field", NO_ITEM_PULLED_YET));
|
|
160
|
+
} else {
|
|
161
|
+
lines.push(
|
|
162
|
+
// The value row says the CV's title whole, so the focus row may cut it (CR018).
|
|
163
|
+
cardLine(
|
|
164
|
+
`🟪[${focus.code}] ${focus.title}`,
|
|
165
|
+
focus.status ? statusMarker(focus.status) : "",
|
|
166
|
+
{
|
|
167
|
+
clip: true,
|
|
168
|
+
},
|
|
169
|
+
),
|
|
170
|
+
...cardWrapped(`value: ${focus.title}`),
|
|
171
|
+
FRAME_BLANK,
|
|
172
|
+
);
|
|
173
|
+
const current = view.recommended;
|
|
174
|
+
if (current !== null) {
|
|
175
|
+
const codeTail = current.code.split(".").pop() ?? current.code;
|
|
176
|
+
lines.push(
|
|
177
|
+
cardLine(` └─ 🟦[${codeTail}] ${current.title}`, "◉ current", { clip: true }),
|
|
178
|
+
cardText(" progress: not started"),
|
|
179
|
+
FRAME_BLANK,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
lines.push(cardText(" Backlog"));
|
|
183
|
+
if (view.shown.length > 0) {
|
|
184
|
+
for (const candidate of view.shown) {
|
|
185
|
+
// Pull Candidates, rendered with this surface, lists each title whole (CR018).
|
|
186
|
+
lines.push(cardClipped(` ○ 🟦[${candidate.code}] ${candidate.title}`));
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
lines.push(cardText(" none"));
|
|
190
|
+
}
|
|
191
|
+
lines.push(
|
|
192
|
+
FRAME_BLANK,
|
|
193
|
+
cardText(" Recently promoted from Exploration"),
|
|
194
|
+
cardText(" none"),
|
|
195
|
+
FRAME_BLANK,
|
|
196
|
+
cardText(" Active constraints"),
|
|
197
|
+
cardText(" ✕ do not start candidates automatically"),
|
|
198
|
+
cardText(" ✓ Navigator explicitly chooses Pull"),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
lines.push(
|
|
203
|
+
FRAME_BOTTOM,
|
|
204
|
+
"",
|
|
205
|
+
"source",
|
|
206
|
+
report.source ?? "none",
|
|
207
|
+
"",
|
|
208
|
+
"boundary",
|
|
209
|
+
"Roadmap was inspected only. No item was pulled. No lifecycle work was executed.",
|
|
210
|
+
);
|
|
211
|
+
return wrapAriadSurface("roadmap_snapshot", `${lines.join("\n")}\n`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Python `render_pull_candidates_report`, over the journey's scoped view. */
|
|
215
|
+
export function renderPullCandidatesReport(view: ScopedPullCandidates): string {
|
|
216
|
+
const lines: string[] = [
|
|
217
|
+
"Delivery",
|
|
218
|
+
"",
|
|
219
|
+
FRAME_TOP,
|
|
220
|
+
"│ 🟪■ PULL CANDIDATES │",
|
|
221
|
+
FRAME_BLANK,
|
|
222
|
+
cardText("journey"),
|
|
223
|
+
cardText(view.journey),
|
|
224
|
+
FRAME_BLANK,
|
|
225
|
+
cardText("method"),
|
|
226
|
+
cardText(view.method),
|
|
227
|
+
FRAME_BLANK,
|
|
228
|
+
...candidateListLines(view),
|
|
229
|
+
FRAME_BLANK,
|
|
230
|
+
cardText("recommended pull"),
|
|
231
|
+
...nextMoveLines(view, formatCandidate),
|
|
232
|
+
FRAME_BLANK,
|
|
233
|
+
cardText("boundary"),
|
|
234
|
+
...cardWrapped("No item was pulled. No lifecycle work was executed."),
|
|
235
|
+
FRAME_BOTTOM,
|
|
236
|
+
];
|
|
237
|
+
return wrapAriadSurface("pull_candidates", `${lines.join("\n")}\n`);
|
|
238
|
+
}
|