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,300 @@
|
|
|
1
|
+
// CV22.DS7.US8 plateau 1 — shared parsing primitives for the roadmap grammars.
|
|
2
|
+
//
|
|
3
|
+
// Port of `src/memory/builder/roadmap_grammar.py`. Both the legacy
|
|
4
|
+
// `CV → Epic → Story` grammar and the Delivery Story grammar are parsed with
|
|
5
|
+
// these, and keeping them in one module is what stopped the heading/status
|
|
6
|
+
// regex drift that once made DS-grammar roadmaps unreadable while the CV
|
|
7
|
+
// grammar still worked.
|
|
8
|
+
//
|
|
9
|
+
// Three dialect traps, each of which a literal transcription of the Python
|
|
10
|
+
// patterns would walk into:
|
|
11
|
+
//
|
|
12
|
+
// 1. **`\s` is not `\s`.** In a Python `str` pattern `\s` is `str.isspace()`:
|
|
13
|
+
// it INCLUDES U+001C–U+001F and U+0085 and EXCLUDES U+FEFF. JavaScript's
|
|
14
|
+
// `\s` is the opposite on both counts. So these patterns are built from
|
|
15
|
+
// `PYTHON_WHITESPACE_CLASS`, the set US6 established, rather than `\s`.
|
|
16
|
+
// 2. **`.` diverges on line separators.** Python's `.` (no DOTALL) excludes
|
|
17
|
+
// only `\n`; JavaScript's excludes `\n`, `\r`, U+2028, and U+2029. A title
|
|
18
|
+
// containing U+2028 therefore continues in Python and stops in JavaScript.
|
|
19
|
+
// `[^\n]` restores Python's meaning.
|
|
20
|
+
// 3. **`str.strip()` is not `.trim()`**, same separator-set reason, so cell
|
|
21
|
+
// trimming goes through `pyStrip`.
|
|
22
|
+
//
|
|
23
|
+
// And one that cost a test failure before it was found: **`re.MULTILINE` is not
|
|
24
|
+
// the `m` flag.** Python's MULTILINE `^`/`$` anchor at `\n` only, while
|
|
25
|
+
// JavaScript's `m` also anchors at `\r`, U+2028, and U+2029. With `m`, a lazy
|
|
26
|
+
// title followed by `$` stops at an embedded U+2028 and silently truncates —
|
|
27
|
+
// which is what the `line_separator` fixture caught. So the anchors are written
|
|
28
|
+
// out as `(?<=^|\n)` and `(?=\n|$)` and the `m` flag is deliberately absent.
|
|
29
|
+
//
|
|
30
|
+
// `re.fullmatch` maps to anchoring the pattern, not to `.test()` on a substring.
|
|
31
|
+
|
|
32
|
+
import { clipCodePoints } from "#util/clipCodePoints.ts";
|
|
33
|
+
import { PYTHON_WHITESPACE_CLASS, pyStrip } from "#util/pythonText.ts";
|
|
34
|
+
|
|
35
|
+
const WS = `[${PYTHON_WHITESPACE_CLASS}]`;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `# <code> — <title>`, multiline. Codes may carry dots (`CV2.DS1`), hyphens
|
|
39
|
+
* (`DS-35`), dotted hyphenated children (`DS-35.US-1`), and a lowercase
|
|
40
|
+
* sub-story suffix (`CV21.E2.S1b`). The separator is an em dash OR a hyphen.
|
|
41
|
+
*/
|
|
42
|
+
export const HEADING_RE = new RegExp(
|
|
43
|
+
`(?<=^|\\n)#${WS}+(?<code>[A-Za-z0-9.\\-]+)${WS}+[—-]${WS}+(?<title>[^\\n]+?)${WS}*(?=\\n|$)`,
|
|
44
|
+
"u",
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
/** `**Status:** <status>`. Unanchored at the start, exactly as Python's is. */
|
|
48
|
+
export const STATUS_RE = new RegExp(
|
|
49
|
+
`\\*\\*Status:\\*\\*${WS}*(?<status>[^\\n]+?)${WS}*(?=\\n|$)`,
|
|
50
|
+
"u",
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
/** `**Type:** <type>`. Lives beside the patterns it is parsed with. */
|
|
54
|
+
export const TYPE_RE = new RegExp(`\\*\\*Type:\\*\\*${WS}*(?<type>[^\\n]+?)${WS}*(?=\\n|$)`, "u");
|
|
55
|
+
|
|
56
|
+
/** A whole-cell `[label](target)` link. Anchored, to mean `re.fullmatch`. */
|
|
57
|
+
const MARKDOWN_LINK_RE = /^\[(?<label>[^\]]+)\]\((?<target>[^)]*)\)$/u;
|
|
58
|
+
|
|
59
|
+
export interface HeadingMatch {
|
|
60
|
+
code: string;
|
|
61
|
+
title: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** First `# <code> — <title>` heading in a document, or `null`. */
|
|
65
|
+
export function matchHeading(content: string): HeadingMatch | null {
|
|
66
|
+
const match = HEADING_RE.exec(content);
|
|
67
|
+
if (!match?.groups) return null;
|
|
68
|
+
return { code: match.groups.code, title: match.groups.title };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** First `**Status:**` value in a document, or `null`. */
|
|
72
|
+
export function matchStatus(content: string): string | null {
|
|
73
|
+
return STATUS_RE.exec(content)?.groups?.status ?? null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** First `**Type:**` value in a document, or `null`. */
|
|
77
|
+
export function matchType(content: string): string | null {
|
|
78
|
+
return TYPE_RE.exec(content)?.groups?.type ?? null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Python `is_legacy_path`: true when the path sits under a `legacy/` component
|
|
83
|
+
* of the roadmap root. Component-wise, not a substring test — a directory named
|
|
84
|
+
* `non-legacy-notes` is not legacy.
|
|
85
|
+
*
|
|
86
|
+
* Python resolves both sides and returns `False` when the path lies outside the
|
|
87
|
+
* root (`relative_to` raises). Callers here pass components already relative to
|
|
88
|
+
* the root, so that case is an empty list.
|
|
89
|
+
*/
|
|
90
|
+
export function isLegacyPath(relativeComponents: readonly string[]): boolean {
|
|
91
|
+
return relativeComponents.includes("legacy");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface MarkdownLink {
|
|
95
|
+
label: string;
|
|
96
|
+
target: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Python `parse_markdown_link`: a whole-cell link, or `null`. */
|
|
100
|
+
export function parseMarkdownLink(value: string): MarkdownLink | null {
|
|
101
|
+
const match = MARKDOWN_LINK_RE.exec(pyStrip(value));
|
|
102
|
+
if (!match?.groups) return null;
|
|
103
|
+
return { label: match.groups.label, target: match.groups.target };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Python `strip_markdown_link`: the link's label, or the stripped raw value. */
|
|
107
|
+
export function stripMarkdownLink(value: string): string {
|
|
108
|
+
return parseMarkdownLink(value)?.label ?? pyStrip(value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** A code span: a run of backticks up to the next run of the same length. */
|
|
112
|
+
const CODE_SPAN_RE = /(?<!`)(`+)(?!`)[\s\S]*?(?<!`)\1(?!`)/gu;
|
|
113
|
+
|
|
114
|
+
/** An inline link or image with a label, not escaped. The target may carry a quoted title. */
|
|
115
|
+
const INLINE_LINK_RE = /(?<!\\)!?\[([^\]\n]+)\]\([^)\n]*\)/gu;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A title with every inline Markdown link or image reduced to its label (CR018).
|
|
119
|
+
*
|
|
120
|
+
* A relative link is true only in the file it was written in, and Ariad carries a title
|
|
121
|
+
* into terminal cards, into files at other depths, and into folder names. So a title
|
|
122
|
+
* enters Ariad this way, from the roadmap and from `--item-title` alike. What
|
|
123
|
+
* `stripMarkdownLink` does for a cell that is one whole link is a case of this rule;
|
|
124
|
+
* that function stays for codes.
|
|
125
|
+
*
|
|
126
|
+
* A link inside a code span is code and stays as written, as does a link with an empty
|
|
127
|
+
* label, which has nothing to keep. Reference links (`[label][ref]`) and labels with
|
|
128
|
+
* nested brackets are left alone.
|
|
129
|
+
*/
|
|
130
|
+
export function linkFreeTitle(title: string): string {
|
|
131
|
+
let result = "";
|
|
132
|
+
let last = 0;
|
|
133
|
+
for (const span of title.matchAll(CODE_SPAN_RE)) {
|
|
134
|
+
result += title.slice(last, span.index).replace(INLINE_LINK_RE, "$1");
|
|
135
|
+
result += span[0];
|
|
136
|
+
last = span.index + span[0].length;
|
|
137
|
+
}
|
|
138
|
+
return result + title.slice(last).replace(INLINE_LINK_RE, "$1");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The most a status clause takes on a card row; the widest status on this roadmap. */
|
|
142
|
+
export const STATUS_CLAUSE_WIDTH = 24;
|
|
143
|
+
|
|
144
|
+
/** The separators after which a status line stops being its status (CR103). */
|
|
145
|
+
const STATUS_SEPARATOR_RE = /\s(?:\u2014|\u2013|-|\u00b7)\s|\s\(|[;,.:]\s/u;
|
|
146
|
+
|
|
147
|
+
/** `**`, `__`, and the backticks of a code span: Markdown a card cannot render. */
|
|
148
|
+
const STATUS_MARKS_RE = /\*\*|__|`/gu;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* A status line as the status its author declared: its first clause, Markdown
|
|
152
|
+
* reduced, bounded to `STATUS_CLAUSE_WIDTH` (CR103).
|
|
153
|
+
*
|
|
154
|
+
* Several roadmaps write a package's `**Status:**` line as a running changelog, and
|
|
155
|
+
* every Builder reader read it whole: the rows printed a hundred card lines of it, and
|
|
156
|
+
* the classifiers found a status word in its prose. The clause is what a row prints
|
|
157
|
+
* and what a classifier reads, so the two cannot disagree. It is applied where a status
|
|
158
|
+
* leaves a reader, so every `PullCandidate`, `RoadmapSnapshotItem`, `AuthoredPackage`,
|
|
159
|
+
* and `CandidateChild` carries a clause by construction; `matchStatus` still returns the
|
|
160
|
+
* line, for the reader that wants it.
|
|
161
|
+
*
|
|
162
|
+
* In order: `pyStrip`; links and images to their labels, then `**`, `__`, and backticks
|
|
163
|
+
* out, so a separator inside a link's target cannot cut, a label's words cut like the
|
|
164
|
+
* author's own, and a `**` that closes after the separator still goes; the cut before the first separator, never at the
|
|
165
|
+
* start; trailing punctuation off, and an empty clause gives way to the reduced line;
|
|
166
|
+
* then the clip, by the card's rule.
|
|
167
|
+
*/
|
|
168
|
+
export function statusClause(status: string): string {
|
|
169
|
+
const reduced = pyStrip(linkFreeTitle(pyStrip(status)).replace(STATUS_MARKS_RE, ""));
|
|
170
|
+
const cut = reduced.search(STATUS_SEPARATOR_RE);
|
|
171
|
+
const clause = pyStrip((cut > 0 ? reduced.slice(0, cut) : reduced).replace(/[.:;,]+$/u, ""));
|
|
172
|
+
return clipCodePoints(clause === "" ? reduced : clause, STATUS_CLAUSE_WIDTH);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The cells of a Markdown table row, each `pyStrip`ped, with GFM's escape honored
|
|
177
|
+
* (CR107): `\|` is a pipe inside the cell, a code span included, and `\\` is a backslash,
|
|
178
|
+
* so `\\|` is a backslash and a border. Every other character is itself, as every other
|
|
179
|
+
* reader leaves it. The row's leading pipes go, and its trailing unescaped ones, as
|
|
180
|
+
* Python's `strip("|")` took them; an escaped trailing pipe is content.
|
|
181
|
+
*
|
|
182
|
+
* Three readers split on every pipe before this: the roadmap index's CV and DS tables,
|
|
183
|
+
* a Delivery Story's candidate table, and the Done preflight's status tables. A cell
|
|
184
|
+
* with an escape split in two and shifted every column after it, so Expand recommended
|
|
185
|
+
* a fragment, the index read a status from the wrong cell, and the preflight refused a
|
|
186
|
+
* Done row. One reader now, and `tableCell` is its inverse.
|
|
187
|
+
*/
|
|
188
|
+
export function tableRowCells(line: string): string[] {
|
|
189
|
+
let start = 0;
|
|
190
|
+
while (start < line.length && line[start] === "|") start += 1;
|
|
191
|
+
let end = line.length;
|
|
192
|
+
while (end > start && line[end - 1] === "|" && !escapedAt(line, end - 1)) end -= 1;
|
|
193
|
+
const cells: string[] = [];
|
|
194
|
+
let cell = "";
|
|
195
|
+
for (let at = start; at < end; at += 1) {
|
|
196
|
+
const char = line[at];
|
|
197
|
+
const next = line[at + 1];
|
|
198
|
+
if (char === "\\" && at + 1 < end && (next === "|" || next === "\\")) {
|
|
199
|
+
cell += next;
|
|
200
|
+
at += 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (char === "|") {
|
|
204
|
+
cells.push(pyStrip(cell));
|
|
205
|
+
cell = "";
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
cell += char;
|
|
209
|
+
}
|
|
210
|
+
cells.push(pyStrip(cell));
|
|
211
|
+
return cells;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Whether the pipe at `index` is escaped: preceded by an odd run of backslashes. */
|
|
215
|
+
function escapedAt(line: string, index: number): boolean {
|
|
216
|
+
let backslashes = 0;
|
|
217
|
+
for (let at = index - 1; at >= 0 && line[at] === "\\"; at -= 1) backslashes += 1;
|
|
218
|
+
return backslashes % 2 === 1;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* `text` as a table cell: every backslash doubled, then every pipe escaped, so
|
|
223
|
+
* `tableRowCells` gives the text back exactly (CR107). The Delivery Story scaffold writes
|
|
224
|
+
* a title into two cells through it; nothing else Ariad writes is a table cell.
|
|
225
|
+
*/
|
|
226
|
+
export function tableCell(text: string): string {
|
|
227
|
+
return text.replace(/\\/gu, "\\\\").replace(/\|/gu, "\\|");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Python `_CandidateChild`. Its `status` is the cell's clause (CR103). */
|
|
231
|
+
export interface CandidateChild {
|
|
232
|
+
readonly code: string;
|
|
233
|
+
readonly title: string;
|
|
234
|
+
readonly level: string;
|
|
235
|
+
readonly status: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const REQUIRED_COLUMNS = ["code", "story", "type", "status"] as const;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Python `_parse_candidate_stories`: a Delivery Story's `## Candidate Stories` table,
|
|
242
|
+
* header-driven. Expand reads its children with it, and Pull asks it whether a
|
|
243
|
+
* Delivery Story names a story (CR113), so the two read the table by one rule.
|
|
244
|
+
*
|
|
245
|
+
* Line-by-line rather than block-by-block, and the details matter:
|
|
246
|
+
*
|
|
247
|
+
* * a non-table line BREAKS the scan once a header was found, but is SKIPPED
|
|
248
|
+
* before — so the first canonical table wins and nothing after it is read;
|
|
249
|
+
* * `line.strip("|")` removes every leading and trailing pipe, not one;
|
|
250
|
+
* * duplicate header names keep the LAST index, because Python builds the map
|
|
251
|
+
* with a dict comprehension over `enumerate`;
|
|
252
|
+
* * a row shorter than the widest required column is skipped, not an error,
|
|
253
|
+
* which is how a ragged authored table degrades instead of blocking.
|
|
254
|
+
*/
|
|
255
|
+
export function parseCandidateStories(content: string): CandidateChild[] {
|
|
256
|
+
const children: CandidateChild[] = [];
|
|
257
|
+
let columns: Map<string, number> | null = null;
|
|
258
|
+
|
|
259
|
+
for (const rawLine of content.split("\n")) {
|
|
260
|
+
const line = pyStrip(rawLine);
|
|
261
|
+
if (!line.startsWith("|")) {
|
|
262
|
+
if (columns !== null) break;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const cells = tableRowCells(line);
|
|
266
|
+
|
|
267
|
+
if (columns === null) {
|
|
268
|
+
// `cell.lower()` — ASCII header names in practice; Python's `str.lower()`
|
|
269
|
+
// and JavaScript's `toLowerCase()` differ only on characters no canonical
|
|
270
|
+
// header uses (`İ`, `ẞ`), and a divergence there fails closed by not
|
|
271
|
+
// matching the required set.
|
|
272
|
+
const lowered = cells.map((cell) => cell.toLowerCase());
|
|
273
|
+
if (REQUIRED_COLUMNS.every((name) => lowered.includes(name))) {
|
|
274
|
+
columns = new Map();
|
|
275
|
+
lowered.forEach((name, index) => {
|
|
276
|
+
columns?.set(name, index);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (line.startsWith("|---")) continue;
|
|
283
|
+
if (cells.every((cell) => cell === "")) continue;
|
|
284
|
+
if (cells.every((cell) => [...cell].every((character) => character === "-"))) continue;
|
|
285
|
+
|
|
286
|
+
const widest = Math.max(...[...columns.values()]);
|
|
287
|
+
if (cells.length <= widest) continue;
|
|
288
|
+
|
|
289
|
+
const code = stripMarkdownLink(cells[columns.get("code") ?? 0] ?? "");
|
|
290
|
+
if (!code) continue;
|
|
291
|
+
const typeText = (cells[columns.get("type") ?? 0] ?? "").toLowerCase();
|
|
292
|
+
children.push({
|
|
293
|
+
code,
|
|
294
|
+
title: linkFreeTitle(cells[columns.get("story") ?? 0] ?? ""),
|
|
295
|
+
level: typeText.includes("technical") ? "technical_story" : "user_story",
|
|
296
|
+
status: statusClause(cells[columns.get("status") ?? 0] ?? ""),
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return children;
|
|
300
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// CV22.DS7.US8 plateau 1 — the one filesystem scan every roadmap reader shares.
|
|
2
|
+
//
|
|
3
|
+
// Python has three call sites that all do `sorted(roadmap_root.rglob("index.md"))`
|
|
4
|
+
// and then skip `legacy/`: `pull_candidates.inspect_pull_candidates`,
|
|
5
|
+
// `roadmap_position.resolve_roadmap_position`, and
|
|
6
|
+
// `story_paths._group_roadmap_headings`. They are one function here for the same
|
|
7
|
+
// reason `roadmap_grammar.py` exists in Python — three copies of a scan is three
|
|
8
|
+
// chances for the ordering to drift. CR002 deleted the position reader: a
|
|
9
|
+
// journey's position now comes from its cursor (`roadmapScope.ts`), not from the
|
|
10
|
+
// first file whose status reads "Active".
|
|
11
|
+
//
|
|
12
|
+
// The ordering is the whole point. `sorted()` over `Path` objects compares
|
|
13
|
+
// COMPONENT LISTS, so `a/b` precedes `a-x/c`; a JavaScript `sort()` over joined
|
|
14
|
+
// paths puts `a-x/c` first because `-` (0x2D) sorts before `/` (0x2F). That
|
|
15
|
+
// order decides which candidate `_recommend` returns and the order of the
|
|
16
|
+
// rendered Backlog, so a naive sort changes what the Navigator is told to pull
|
|
17
|
+
// next. Measured: Python 3.10 and 3.12 agree with each other, and on this
|
|
18
|
+
// project's real 352-file tree the two orders happen to coincide today — which
|
|
19
|
+
// is why the fixture stages the collision deliberately rather than trusting an
|
|
20
|
+
// accident of directory naming.
|
|
21
|
+
|
|
22
|
+
import { type Dirent, readdirSync, readFileSync } from "node:fs";
|
|
23
|
+
import { relative, sep } from "node:path";
|
|
24
|
+
import { comparePathComponents } from "#util/pythonText.ts";
|
|
25
|
+
import { isLegacyPath } from "./roadmapGrammar.ts";
|
|
26
|
+
|
|
27
|
+
export interface RoadmapFile {
|
|
28
|
+
/** Path components relative to the roadmap root, ending in `index.md`. */
|
|
29
|
+
components: string[];
|
|
30
|
+
/** POSIX path relative to the roadmap root. */
|
|
31
|
+
relativePath: string;
|
|
32
|
+
/** Absolute path on disk. */
|
|
33
|
+
absolutePath: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RoadmapScanOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Keep `legacy/` in the result. Default `false`, which is what the three
|
|
39
|
+
* grammar call sites need.
|
|
40
|
+
*
|
|
41
|
+
* It is an OPTION rather than a second function because Python has a fourth
|
|
42
|
+
* call site that does the same walk and does NOT skip the archive:
|
|
43
|
+
* `delivery_story_roadmap_closure.inspect_authored_closure`, whose status scan
|
|
44
|
+
* reads every `index.md` under the roadmap. So an archived table row for a known
|
|
45
|
+
* code blocks a Delivery Story's Done. Reusing this scan with its default would
|
|
46
|
+
* let TypeScript close a Delivery Story that Python refuses — the exact defect
|
|
47
|
+
* shape the plateau-5 panel stopped, and `authored_closure_reads_legacy_rows`
|
|
48
|
+
* fails a port that does it.
|
|
49
|
+
*/
|
|
50
|
+
readonly includeLegacy?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Python `sorted(roadmap_root.rglob("index.md"))`, minus `legacy/` unless asked.
|
|
55
|
+
*
|
|
56
|
+
* A missing or unreadable root yields an empty list, matching Python's
|
|
57
|
+
* `is_dir()` guard at every call site.
|
|
58
|
+
*/
|
|
59
|
+
export function scanRoadmapIndexFiles(
|
|
60
|
+
roadmapRoot: string,
|
|
61
|
+
options: RoadmapScanOptions = {},
|
|
62
|
+
): RoadmapFile[] {
|
|
63
|
+
let entries: Dirent<string>[];
|
|
64
|
+
try {
|
|
65
|
+
entries = readdirSync(roadmapRoot, { recursive: true, withFileTypes: true });
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const found: string[][] = [];
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
if (entry.name !== "index.md") continue;
|
|
72
|
+
if (!entry.isFile()) continue;
|
|
73
|
+
const parents = relative(roadmapRoot, entry.parentPath).split(sep).filter(Boolean);
|
|
74
|
+
found.push([...parents, entry.name]);
|
|
75
|
+
}
|
|
76
|
+
found.sort(comparePathComponents);
|
|
77
|
+
return found
|
|
78
|
+
.filter((components) => options.includeLegacy === true || !isLegacyPath(components))
|
|
79
|
+
.map((components) => ({
|
|
80
|
+
components,
|
|
81
|
+
relativePath: components.join("/"),
|
|
82
|
+
absolutePath: [roadmapRoot, ...components].join(sep),
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Python's `try: path.read_text(encoding="utf-8") except OSError: continue`.
|
|
88
|
+
* An unreadable file is skipped, not fatal — a permission error in one package
|
|
89
|
+
* must not empty the whole roadmap.
|
|
90
|
+
*/
|
|
91
|
+
export function readRoadmapFile(absolutePath: string): string | null {
|
|
92
|
+
try {
|
|
93
|
+
return readFileSync(absolutePath, "utf8");
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// CR002 — the journey's roadmap scope, derived from its delivery cursor.
|
|
2
|
+
//
|
|
3
|
+
// Every Builder surface that answers "where are we" or "what next" reads this
|
|
4
|
+
// scope instead of the whole project. Before CR002 two readers did: one returned
|
|
5
|
+
// the first `index.md` whose status contained "Active", the other recommended
|
|
6
|
+
// across every candidate in the tree. Neither knew which journey was asking, so a
|
|
7
|
+
// project holding several journeys' work told each of them the same answer, and
|
|
8
|
+
// usually a wrong one.
|
|
9
|
+
//
|
|
10
|
+
// The only roadmap fact a journey owns is the active item in its delivery cursor.
|
|
11
|
+
// Pull is the one cursor writer that sets it and `sync-cursor` the one that clears
|
|
12
|
+
// it — every other writer carries it forward — so "unscoped" always means no item
|
|
13
|
+
// has been pulled since the cursor was last synchronized.
|
|
14
|
+
//
|
|
15
|
+
// Identity is the authored heading code, never a folder. A package can live under
|
|
16
|
+
// any directory a human chose (the rule `storyPaths.ts` states), so the CV is the
|
|
17
|
+
// active item's code up to the first `.`, its package is found by its heading, and
|
|
18
|
+
// membership is strict descent by code. Strict, because the CV is the position,
|
|
19
|
+
// not a candidate: `recommend()` falls back to its first candidate at ANY level,
|
|
20
|
+
// so a CV still marked Active with nothing left under it would otherwise be
|
|
21
|
+
// recommended as its own next pull.
|
|
22
|
+
|
|
23
|
+
import { pyStrip } from "#util/pythonText.ts";
|
|
24
|
+
import {
|
|
25
|
+
type PullCandidate,
|
|
26
|
+
type PullCandidatesReport,
|
|
27
|
+
recommend,
|
|
28
|
+
roadmapPaths,
|
|
29
|
+
} from "./pullCandidates.ts";
|
|
30
|
+
import { linkFreeTitle, matchHeading, matchStatus, statusClause } from "./roadmapGrammar.ts";
|
|
31
|
+
import { readRoadmapFile } from "./roadmapScan.ts";
|
|
32
|
+
import { roadmapHeadingDirectories } from "./storyPaths.ts";
|
|
33
|
+
|
|
34
|
+
export type UnscopedReason = "no_cursor" | "no_active_item";
|
|
35
|
+
|
|
36
|
+
/** An authored roadmap package, as its own `index.md` describes it. */
|
|
37
|
+
export interface AuthoredPackage {
|
|
38
|
+
readonly code: string;
|
|
39
|
+
readonly title: string;
|
|
40
|
+
/** The clause of the package's `**Status:**` line (CR103), or `""` when it has none. */
|
|
41
|
+
readonly status: string;
|
|
42
|
+
/** POSIX, relative to the project root, ending in `index.md`. */
|
|
43
|
+
readonly path: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Where the journey stands, as far as the authored roadmap can say. */
|
|
47
|
+
export type ScopePosition =
|
|
48
|
+
/** The CV's own package. */
|
|
49
|
+
| { readonly kind: "cv_package"; readonly package: AuthoredPackage }
|
|
50
|
+
/** The CV has no package; the active item's own stands in for it. */
|
|
51
|
+
| { readonly kind: "item_package"; readonly package: AuthoredPackage }
|
|
52
|
+
/** Neither the CV nor the active item has an authored package. */
|
|
53
|
+
| { readonly kind: "no_package" }
|
|
54
|
+
/** Two or more packages claim `code` (the CV's, or the item's when the CV has none). */
|
|
55
|
+
| { readonly kind: "ambiguous"; readonly code: string; readonly paths: readonly string[] }
|
|
56
|
+
/** The journey has no project path, so there is no roadmap to read. */
|
|
57
|
+
| { readonly kind: "no_project" };
|
|
58
|
+
|
|
59
|
+
export type RoadmapScope =
|
|
60
|
+
| { readonly kind: "unscoped"; readonly reason: UnscopedReason }
|
|
61
|
+
| {
|
|
62
|
+
readonly kind: "active_item";
|
|
63
|
+
readonly activeItem: string;
|
|
64
|
+
readonly cvCode: string;
|
|
65
|
+
readonly position: ScopePosition;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** The one cursor field the scope reads; every cursor view in the builder has it. */
|
|
69
|
+
export interface ScopeCursor {
|
|
70
|
+
readonly activeItem: string | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The candidate list a journey sees, and the one recommendation it may get. */
|
|
74
|
+
export interface ScopedPullCandidates {
|
|
75
|
+
readonly journey: string;
|
|
76
|
+
readonly method: string;
|
|
77
|
+
readonly scope: RoadmapScope;
|
|
78
|
+
/** Unscoped: every candidate. Scoped: the CV's strict descendants, minus the active item. */
|
|
79
|
+
readonly shown: readonly PullCandidate[];
|
|
80
|
+
/** Scoped: candidates outside the CV, the CV itself not counted. Unscoped: 0. */
|
|
81
|
+
readonly outsideCount: number;
|
|
82
|
+
/** Scoped: `recommend(shown)`. Unscoped: always `null`. */
|
|
83
|
+
readonly recommended: PullCandidate | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The CV a roadmap code belongs to: everything before the first `.`. */
|
|
87
|
+
export function cvCodeOf(code: string): string {
|
|
88
|
+
return code.split(".", 1)[0] ?? code;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Strict descent: `CV2.DS1` is inside `CV2`; `CV2` and `CV20.DS1` are not. */
|
|
92
|
+
export function isInsideCv(code: string, cvCode: string): boolean {
|
|
93
|
+
return code.startsWith(`${cvCode}.`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The code one level up: everything before the last `.`. A CV has no parent. */
|
|
97
|
+
export function parentCodeOf(code: string): string | null {
|
|
98
|
+
const cut = code.lastIndexOf(".");
|
|
99
|
+
return cut === -1 ? null : code.slice(0, cut);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The active item's siblings: the other items with the same parent, in the order
|
|
104
|
+
* given. Its ancestors, its cousins, and its parent's own siblings never qualify,
|
|
105
|
+
* and exact comparison keeps `CV1.DS1` from claiming `CV1.DS10`'s children (CR019).
|
|
106
|
+
*/
|
|
107
|
+
export function siblingsOf<T extends { readonly code: string }>(
|
|
108
|
+
items: readonly T[],
|
|
109
|
+
activeItem: string,
|
|
110
|
+
): T[] {
|
|
111
|
+
const parent = parentCodeOf(activeItem);
|
|
112
|
+
if (parent === null) return [];
|
|
113
|
+
return items.filter((item) => item.code !== activeItem && parentCodeOf(item.code) === parent);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The scope a journey's cursor implies. Never throws for a roadmap defect. */
|
|
117
|
+
export function resolveRoadmapScope(
|
|
118
|
+
projectRoot: string | null,
|
|
119
|
+
cursor: ScopeCursor | null,
|
|
120
|
+
): RoadmapScope {
|
|
121
|
+
if (cursor === null) return { kind: "unscoped", reason: "no_cursor" };
|
|
122
|
+
const activeItem = cursor.activeItem;
|
|
123
|
+
if (!activeItem) return { kind: "unscoped", reason: "no_active_item" };
|
|
124
|
+
const cvCode = cvCodeOf(activeItem);
|
|
125
|
+
return {
|
|
126
|
+
kind: "active_item",
|
|
127
|
+
activeItem,
|
|
128
|
+
cvCode,
|
|
129
|
+
position: resolvePosition(projectRoot, activeItem, cvCode),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function resolvePosition(
|
|
134
|
+
projectRoot: string | null,
|
|
135
|
+
activeItem: string,
|
|
136
|
+
cvCode: string,
|
|
137
|
+
): ScopePosition {
|
|
138
|
+
if (projectRoot === null) return { kind: "no_project" };
|
|
139
|
+
const claims = roadmapHeadingDirectories(projectRoot);
|
|
140
|
+
const cv = claimFor(projectRoot, claims, cvCode);
|
|
141
|
+
if (cv.kind === "one") return { kind: "cv_package", package: cv.package };
|
|
142
|
+
if (cv.kind === "many") return { kind: "ambiguous", code: cvCode, paths: cv.paths };
|
|
143
|
+
if (activeItem === cvCode) return { kind: "no_package" };
|
|
144
|
+
const item = claimFor(projectRoot, claims, activeItem);
|
|
145
|
+
if (item.kind === "one") return { kind: "item_package", package: item.package };
|
|
146
|
+
if (item.kind === "many") return { kind: "ambiguous", code: activeItem, paths: item.paths };
|
|
147
|
+
return { kind: "no_package" };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type Claim =
|
|
151
|
+
| { readonly kind: "none" }
|
|
152
|
+
| { readonly kind: "one"; readonly package: AuthoredPackage }
|
|
153
|
+
| { readonly kind: "many"; readonly paths: readonly string[] };
|
|
154
|
+
|
|
155
|
+
function claimFor(
|
|
156
|
+
projectRoot: string,
|
|
157
|
+
claims: ReadonlyMap<string, readonly string[]>,
|
|
158
|
+
code: string,
|
|
159
|
+
): Claim {
|
|
160
|
+
const directories = claims.get(code) ?? [];
|
|
161
|
+
if (directories.length > 1) return { kind: "many", paths: directories.map(indexPath) };
|
|
162
|
+
const only = directories[0];
|
|
163
|
+
if (only === undefined) return { kind: "none" };
|
|
164
|
+
const authored = readPackage(projectRoot, only);
|
|
165
|
+
return authored === null ? { kind: "none" } : { kind: "one", package: authored };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** A roadmap-relative package directory as the project-relative path of its `index.md`. */
|
|
169
|
+
function indexPath(directory: string): string {
|
|
170
|
+
return directory ? `docs/project/roadmap/${directory}/index.md` : "docs/project/roadmap/index.md";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function readPackage(projectRoot: string, directory: string): AuthoredPackage | null {
|
|
174
|
+
const { roadmapRoot } = roadmapPaths(projectRoot);
|
|
175
|
+
const file = directory ? `${roadmapRoot}/${directory}/index.md` : `${roadmapRoot}/index.md`;
|
|
176
|
+
const content = readRoadmapFile(file);
|
|
177
|
+
if (content === null) return null;
|
|
178
|
+
const heading = matchHeading(content);
|
|
179
|
+
if (heading === null) return null;
|
|
180
|
+
const status = matchStatus(content);
|
|
181
|
+
return {
|
|
182
|
+
code: pyStrip(heading.code),
|
|
183
|
+
title: linkFreeTitle(pyStrip(heading.title)),
|
|
184
|
+
status: status === null ? "" : statusClause(status),
|
|
185
|
+
path: indexPath(directory),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The candidate list scoped to the journey. Unscoped, every candidate is shown and
|
|
191
|
+
* nothing is recommended: there is no journey position to recommend from, and a
|
|
192
|
+
* project-wide ranking is exactly the answer CR002 removed.
|
|
193
|
+
*/
|
|
194
|
+
export function scopePullCandidates(
|
|
195
|
+
report: PullCandidatesReport,
|
|
196
|
+
scope: RoadmapScope,
|
|
197
|
+
): ScopedPullCandidates {
|
|
198
|
+
const { journey, method, candidates } = report;
|
|
199
|
+
if (scope.kind === "unscoped") {
|
|
200
|
+
return { journey, method, scope, shown: candidates, outsideCount: 0, recommended: null };
|
|
201
|
+
}
|
|
202
|
+
const { cvCode, activeItem } = scope;
|
|
203
|
+
const shown = candidates.filter(
|
|
204
|
+
(candidate) => isInsideCv(candidate.code, cvCode) && candidate.code !== activeItem,
|
|
205
|
+
);
|
|
206
|
+
const outsideCount = candidates.filter(
|
|
207
|
+
(candidate) => candidate.code !== cvCode && !isInsideCv(candidate.code, cvCode),
|
|
208
|
+
).length;
|
|
209
|
+
return { journey, method, scope, shown, outsideCount, recommended: recommend(shown) };
|
|
210
|
+
}
|