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,91 @@
|
|
|
1
|
+
// Schema-state guard for the database seam (CR018, RS003 database audit).
|
|
2
|
+
//
|
|
3
|
+
// CV22 freezes the SQLite schema as a compatibility contract. This guard is
|
|
4
|
+
// its enforcement point: before serving a command, the front door asserts that
|
|
5
|
+
// the database's `_migrations` bookkeeping matches the migration set this core
|
|
6
|
+
// was built against, converting silent schema drift into an explicit,
|
|
7
|
+
// actionable error.
|
|
8
|
+
//
|
|
9
|
+
// `KNOWN_MIGRATION_IDS` is the authoritative migration set. It began as the
|
|
10
|
+
// Python list plus whatever TypeScript authored on its own (CV22.DS6.US2 made
|
|
11
|
+
// TS ⊇ Python), and `tests/unit/test_ts_schema_contract.py` held the other
|
|
12
|
+
// side of that contract so a new Python migration could not land without
|
|
13
|
+
// extending this list.
|
|
14
|
+
//
|
|
15
|
+
// CV22.DS10.TS5 collapses the two-custodian model: TypeScript carries all
|
|
16
|
+
// seventeen, Python carries sixteen and is being deleted, so there is no second
|
|
17
|
+
// list to stay a prefix of. The remedies below name `runtime migrate` rather
|
|
18
|
+
// than an interpreter, for the same reason.
|
|
19
|
+
|
|
20
|
+
import { PROGRAM } from "#util/program.ts";
|
|
21
|
+
import type { Database } from "./database.ts";
|
|
22
|
+
|
|
23
|
+
/** Raised when the database's migration state does not match this TS build. */
|
|
24
|
+
export class SchemaStateError extends Error {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Every migration this core knows, in order. Until CV22.DS10.TS5 the list was
|
|
28
|
+
* the Python engine's plus the TypeScript-authored tail; since then the
|
|
29
|
+
* TypeScript engine is the only custodian, and the list is simply its own.
|
|
30
|
+
*/
|
|
31
|
+
export const KNOWN_MIGRATION_IDS: readonly string[] = [
|
|
32
|
+
"001_project_to_travessia",
|
|
33
|
+
"002_create_attachments",
|
|
34
|
+
"003_create_tasks",
|
|
35
|
+
"004_tasks_temporal_fields",
|
|
36
|
+
"005_travessia_to_journey",
|
|
37
|
+
"006_create_llm_calls",
|
|
38
|
+
"007_create_identity_descriptors",
|
|
39
|
+
"008_create_memories_fts",
|
|
40
|
+
"009_memories_reinforcement_columns",
|
|
41
|
+
"010_create_consolidations",
|
|
42
|
+
"011_create_operation_runs",
|
|
43
|
+
"012_create_operation_run_events",
|
|
44
|
+
"013_create_exploratory_stories",
|
|
45
|
+
"014_create_identity_integrations",
|
|
46
|
+
"015_create_builder_workbench",
|
|
47
|
+
"016_builder_workbench_display_codes",
|
|
48
|
+
// TS-authored, no Python counterpart (CV22.DS6.US2 — first TS ⊋ Python migration).
|
|
49
|
+
"017_journey_parent_column",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Assert that the database's migration ledger matches this build. Three
|
|
54
|
+
* failure modes, each named in the error: `_migrations` absent (not a
|
|
55
|
+
* bootstrapped Mirror database), a known id missing (database older than this
|
|
56
|
+
* core — `runtime migrate` brings it forward), an unknown id present (database
|
|
57
|
+
* migrated by a newer core than the running one — `runtime update` brings the
|
|
58
|
+
* core forward, for a clone and a package alike).
|
|
59
|
+
*/
|
|
60
|
+
export function assertSchemaState(db: Database): void {
|
|
61
|
+
let rows: { id: string }[];
|
|
62
|
+
try {
|
|
63
|
+
rows = db.prepare("SELECT id FROM _migrations").all() as { id: string }[];
|
|
64
|
+
} catch {
|
|
65
|
+
throw new SchemaStateError(
|
|
66
|
+
"database has no _migrations table — not a bootstrapped Mirror database. " +
|
|
67
|
+
"Point Mirror at the right file, or create one: any write command bootstraps a missing database.",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const applied = new Set(rows.map((row) => row.id));
|
|
71
|
+
// Every known migration is required now. Until CV22.DS10.TS5 this filtered
|
|
72
|
+
// out the TS-authored tail, because Python could not apply those and the read
|
|
73
|
+
// path did not need them -- a split that only made sense while two engines
|
|
74
|
+
// shared custody. TypeScript carries all seventeen and is the only engine
|
|
75
|
+
// left to apply them, so "pending" means pending.
|
|
76
|
+
const missingRequired = KNOWN_MIGRATION_IDS.filter((id) => !applied.has(id));
|
|
77
|
+
if (missingRequired.length > 0) {
|
|
78
|
+
throw new SchemaStateError(
|
|
79
|
+
`database schema is older than this TS core (pending migrations: ${missingRequired.join(", ")}). ` +
|
|
80
|
+
"Run `runtime migrate` to bring it forward, then retry.",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const known = new Set(KNOWN_MIGRATION_IDS);
|
|
84
|
+
const unknown = rows.map((row) => row.id).filter((id) => !known.has(id));
|
|
85
|
+
if (unknown.length > 0) {
|
|
86
|
+
throw new SchemaStateError(
|
|
87
|
+
`database schema is newer than this TS core (unknown migrations: ${unknown.join(", ")}). ` +
|
|
88
|
+
`Update this Mirror installation so its core matches the database; run: ${PROGRAM} runtime update`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// TS ⊇ Python schema divergence — CV22.DS6.US2.
|
|
2
|
+
//
|
|
3
|
+
// Through DS6.TS1 the TS-created schema had to be structurally IDENTICAL to the
|
|
4
|
+
// committed Python snapshot. US2 is the first story where TS owns schema custody
|
|
5
|
+
// enough to author schema Python lacks: the `identity.parent_journey` column and
|
|
6
|
+
// its partial index. This module is the single, enumerated source of truth for
|
|
7
|
+
// exactly what TS adds beyond Python, so every guard (schema.test.ts,
|
|
8
|
+
// migrations.test.ts regression, the Navigator parity script) compares a
|
|
9
|
+
// TS-created inventory to the Python snapshot THROUGH it — an unexpected drift
|
|
10
|
+
// (anything not listed here) still fails loudly, in either direction.
|
|
11
|
+
|
|
12
|
+
import { isDeepStrictEqual } from "node:util";
|
|
13
|
+
import type { ColumnInventory, SchemaInventory } from "./schemaInventory.ts";
|
|
14
|
+
|
|
15
|
+
/** Columns TS adds to a Python-snapshot table, appended after its columns. */
|
|
16
|
+
const TS_ONLY_COLUMNS: Record<string, ColumnInventory[]> = {
|
|
17
|
+
identity: [{ name: "parent_journey", type: "TEXT", notnull: 0, dflt_value: null, pk: 0 }],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Indexes TS creates that the Python snapshot does not have. */
|
|
21
|
+
const TS_ONLY_INDEXES: Record<string, { table: string; unique: number; columns: string[] }> = {
|
|
22
|
+
idx_identity_parent_journey: { table: "identity", unique: 0, columns: ["parent_journey"] },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function sortedKeys(record: Record<string, unknown>): string[] {
|
|
26
|
+
return Object.keys(record).sort();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Return the list of structural problems in `actual` (a TS-created inventory)
|
|
31
|
+
* measured against the Python `snapshot` PLUS exactly the enumerated TS-only
|
|
32
|
+
* additions. Empty list == pass. Pure, so tests and the Navigator script share
|
|
33
|
+
* one definition of the intended divergence.
|
|
34
|
+
*/
|
|
35
|
+
export function diffTsInventoryAgainstSnapshot(
|
|
36
|
+
actual: SchemaInventory,
|
|
37
|
+
snapshot: SchemaInventory,
|
|
38
|
+
): string[] {
|
|
39
|
+
const problems: string[] = [];
|
|
40
|
+
|
|
41
|
+
// Tables: no new/removed tables; a listed table may gain listed columns.
|
|
42
|
+
if (!isDeepStrictEqual(sortedKeys(actual.tables), sortedKeys(snapshot.tables))) {
|
|
43
|
+
problems.push("tables: name set differs from the Python snapshot");
|
|
44
|
+
}
|
|
45
|
+
for (const name of Object.keys(snapshot.tables)) {
|
|
46
|
+
const actualTable = actual.tables[name];
|
|
47
|
+
if (!actualTable) continue;
|
|
48
|
+
const additions = TS_ONLY_COLUMNS[name] ?? [];
|
|
49
|
+
const expectedColumns = [...snapshot.tables[name].columns, ...additions];
|
|
50
|
+
if (!isDeepStrictEqual(actualTable.columns, expectedColumns)) {
|
|
51
|
+
problems.push(`tables[${name}].columns differ from snapshot + enumerated TS-only additions`);
|
|
52
|
+
}
|
|
53
|
+
if (!isDeepStrictEqual(actualTable.foreign_keys, snapshot.tables[name].foreign_keys)) {
|
|
54
|
+
problems.push(`tables[${name}].foreign_keys differ from snapshot`);
|
|
55
|
+
}
|
|
56
|
+
if (additions.length > 0) {
|
|
57
|
+
// The raw CREATE TABLE text diverges by exactly these columns; assert each
|
|
58
|
+
// added column name is present rather than reproduce SQLite's stored text.
|
|
59
|
+
for (const column of additions) {
|
|
60
|
+
if (!(actualTable.sql ?? "").includes(column.name)) {
|
|
61
|
+
problems.push(`tables[${name}].sql is missing TS-only column ${column.name}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} else if (!isDeepStrictEqual(actualTable.sql, snapshot.tables[name].sql)) {
|
|
65
|
+
problems.push(`tables[${name}].sql differs from snapshot`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Indexes: snapshot indexes exact; plus the enumerated TS-only ones whose
|
|
70
|
+
// owning table is present (a TS-only index on `identity` only exists when the
|
|
71
|
+
// schema/fixture actually has an `identity` table for migration 017 to alter).
|
|
72
|
+
const applicableTsIndexes = Object.entries(TS_ONLY_INDEXES).filter(
|
|
73
|
+
([, spec]) => spec.table in snapshot.tables,
|
|
74
|
+
);
|
|
75
|
+
const expectedIndexNames = [
|
|
76
|
+
...Object.keys(snapshot.indexes),
|
|
77
|
+
...applicableTsIndexes.map(([name]) => name),
|
|
78
|
+
].sort();
|
|
79
|
+
if (!isDeepStrictEqual(sortedKeys(actual.indexes), expectedIndexNames)) {
|
|
80
|
+
problems.push("indexes: name set differs from snapshot + enumerated TS-only indexes");
|
|
81
|
+
}
|
|
82
|
+
for (const name of Object.keys(snapshot.indexes)) {
|
|
83
|
+
if (actual.indexes[name] && !isDeepStrictEqual(actual.indexes[name], snapshot.indexes[name])) {
|
|
84
|
+
problems.push(`indexes[${name}] differs from snapshot`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const [name, spec] of applicableTsIndexes) {
|
|
88
|
+
const index = actual.indexes[name];
|
|
89
|
+
if (!index) {
|
|
90
|
+
problems.push(`missing enumerated TS-only index ${name}`);
|
|
91
|
+
} else if (
|
|
92
|
+
index.table !== spec.table ||
|
|
93
|
+
index.unique !== spec.unique ||
|
|
94
|
+
!isDeepStrictEqual(index.columns, spec.columns)
|
|
95
|
+
) {
|
|
96
|
+
problems.push(`TS-only index ${name} has an unexpected shape`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Triggers: exact, both directions.
|
|
101
|
+
if (!isDeepStrictEqual(sortedKeys(actual.triggers), sortedKeys(snapshot.triggers))) {
|
|
102
|
+
problems.push("triggers: name set differs from the Python snapshot");
|
|
103
|
+
}
|
|
104
|
+
for (const name of Object.keys(snapshot.triggers)) {
|
|
105
|
+
if (
|
|
106
|
+
actual.triggers[name] &&
|
|
107
|
+
!isDeepStrictEqual(actual.triggers[name], snapshot.triggers[name])
|
|
108
|
+
) {
|
|
109
|
+
problems.push(`triggers[${name}] differs from snapshot`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return problems;
|
|
114
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Descriptor read primitives — the port of Store.get_descriptors_by_layer and
|
|
2
|
+
// the `get_all_identity`-driven "all layers" iteration in
|
|
3
|
+
// memory.cli.descriptor._cmd_list (`generate`, which calls the LLM, stays on
|
|
4
|
+
// Python; see routing.ts).
|
|
5
|
+
|
|
6
|
+
import type { Database } from "#db/database.ts";
|
|
7
|
+
import { requireString } from "#db/rowDecode.ts";
|
|
8
|
+
|
|
9
|
+
export interface DescriptorRow {
|
|
10
|
+
layer: string;
|
|
11
|
+
key: string;
|
|
12
|
+
descriptor: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function toDescriptorRow(row: Record<string, unknown>): DescriptorRow {
|
|
16
|
+
return {
|
|
17
|
+
layer: requireString(row, "layer"),
|
|
18
|
+
key: requireString(row, "key"),
|
|
19
|
+
descriptor: requireString(row, "descriptor"),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Port of `get_descriptors_by_layer`: descriptors for one layer, ordered by key. */
|
|
24
|
+
export function descriptorsByLayer(db: Database, layer: string): DescriptorRow[] {
|
|
25
|
+
return db
|
|
26
|
+
.prepare("SELECT layer, key, descriptor FROM identity_descriptors WHERE layer = ? ORDER BY key")
|
|
27
|
+
.all(layer)
|
|
28
|
+
.map(toDescriptorRow);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Port of the "all layers" default path in `_cmd_list`: Python iterates
|
|
33
|
+
* `get_all_identity()` (ordered by layer, key) and keeps only the identity rows
|
|
34
|
+
* that have a matching descriptor row — an inner join driven by `identity`'s
|
|
35
|
+
* order, not a direct scan of `identity_descriptors`. An orphaned descriptor row
|
|
36
|
+
* with no identity row is silently excluded, exactly as the Python loop skips it.
|
|
37
|
+
* Pushed down to SQL per the database-seam philosophy (listing.ts precedent).
|
|
38
|
+
*/
|
|
39
|
+
export function allDescriptors(db: Database): DescriptorRow[] {
|
|
40
|
+
return db
|
|
41
|
+
.prepare(
|
|
42
|
+
`SELECT i.layer as layer, i.key as key, d.descriptor as descriptor
|
|
43
|
+
FROM identity i
|
|
44
|
+
JOIN identity_descriptors d ON d.layer = i.layer AND d.key = i.key
|
|
45
|
+
ORDER BY i.layer, i.key`,
|
|
46
|
+
)
|
|
47
|
+
.all()
|
|
48
|
+
.map(toDescriptorRow);
|
|
49
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// Docs link/anchor checker — the TypeScript port of `src/memory/docs_lint.py`
|
|
2
|
+
// (CV22.DS10.TS5, slice B).
|
|
3
|
+
//
|
|
4
|
+
// The Zero Python gate assigns `scripts/check_doc_links.py` to this story. The
|
|
5
|
+
// script is a 57-line wrapper; the subject is this module's Python original
|
|
6
|
+
// (221 lines) plus the duplicate-heading guard, which TypeScript already owns
|
|
7
|
+
// through `findDuplicateRoadmapHeadings` (DS7.US8). So this port is the
|
|
8
|
+
// link/anchor half, and the roadmap half is a thin adapter over existing code.
|
|
9
|
+
//
|
|
10
|
+
// Every behavior here is pinned by `ts/test/scripts/docsLint.test.ts`, which
|
|
11
|
+
// carries all 26 cases of `tests/unit/memory/test_docs_lint.py`. That test is
|
|
12
|
+
// the reason this file exists in the shape it does: each case records a
|
|
13
|
+
// failure mode the checker actually caught, or once missed, during the
|
|
14
|
+
// repo-wide docs audit. The Python self-test dies with its suite; the
|
|
15
|
+
// assertions do not.
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
type Dirent,
|
|
19
|
+
existsSync,
|
|
20
|
+
readdirSync,
|
|
21
|
+
readFileSync,
|
|
22
|
+
realpathSync,
|
|
23
|
+
statSync,
|
|
24
|
+
} from "node:fs";
|
|
25
|
+
import { dirname, join, normalize, relative, resolve, sep } from "node:path";
|
|
26
|
+
|
|
27
|
+
import { roadmapPaths } from "#builder/pullCandidates.ts";
|
|
28
|
+
import { findDuplicateRoadmapHeadings } from "#builder/storyPaths.ts";
|
|
29
|
+
|
|
30
|
+
const LINK_RE = /\[[^\]]*\]\(([^)]+)\)/g;
|
|
31
|
+
const HEADING_RE = /^#{1,6}[ \t]+(.+?)[ \t]*$/gm;
|
|
32
|
+
const FENCE_RE = /```[\s\S]*?```/g;
|
|
33
|
+
const SLUG_STRIP_RE = /[^a-z0-9 _-]/g;
|
|
34
|
+
|
|
35
|
+
const EXCLUDED_DIR_NAMES = new Set(["node_modules", ".venv", "__pycache__", ".git"]);
|
|
36
|
+
|
|
37
|
+
// Parity fixture trees (`ts/test/fixtures/**`) are test INPUT, not
|
|
38
|
+
// documentation. They carry authored-Markdown edge cases an oracle is graded
|
|
39
|
+
// on -- dangling links, duplicate heading codes, malformed tables -- so
|
|
40
|
+
// linting them as docs would force the fixtures to stop being fixtures.
|
|
41
|
+
// Narrow on purpose: a directory named `fixtures` is skipped only beneath a
|
|
42
|
+
// `test`/`tests` directory, so a real `docs/**/fixtures/` tree is still
|
|
43
|
+
// checked.
|
|
44
|
+
const TEST_DIR_NAMES = new Set(["test", "tests"]);
|
|
45
|
+
const FIXTURE_DIR_NAME = "fixtures";
|
|
46
|
+
|
|
47
|
+
// `roadmap/templates/*.md` link to a bare `index.md` breadcrumb meant to
|
|
48
|
+
// resolve once the template is copied into a real story folder; `templates/`
|
|
49
|
+
// itself has no `index.md`, by design. Not a broken link.
|
|
50
|
+
const TEMPLATE_DIR_MARKER = "/roadmap/templates/";
|
|
51
|
+
|
|
52
|
+
export interface BrokenLink {
|
|
53
|
+
readonly sourceFile: string;
|
|
54
|
+
readonly line: number;
|
|
55
|
+
readonly target: string;
|
|
56
|
+
readonly reason: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface DuplicateRoadmapHeading {
|
|
60
|
+
readonly code: string;
|
|
61
|
+
readonly paths: readonly string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Reproduce GitHub's heading-to-anchor algorithm.
|
|
66
|
+
*
|
|
67
|
+
* Lowercase, strip everything except letters/digits/spaces/underscores/
|
|
68
|
+
* hyphens (existing hyphens and underscores are PRESERVED -- `MEMORY_ENV`
|
|
69
|
+
* slugs to `memory_env`, not `memory-env`), then spaces become hyphens.
|
|
70
|
+
* Repeated hyphens are never collapsed, matching GitHub: an em dash is
|
|
71
|
+
* stripped outright and leaves its surrounding spaces behind, which is why
|
|
72
|
+
* `D1 — Local-first` slugs to `d1--local-first`.
|
|
73
|
+
*/
|
|
74
|
+
export function slugify(heading: string): string {
|
|
75
|
+
return heading.toLowerCase().replace(SLUG_STRIP_RE, "").replace(/ /g, "-");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Blank out fenced code blocks so link-like text inside ``` ``` is never
|
|
80
|
+
* scanned, preserving the line count exactly -- a problem reported after a
|
|
81
|
+
* fence must still point at its real source line.
|
|
82
|
+
*/
|
|
83
|
+
export function stripFencedCode(text: string): string {
|
|
84
|
+
return text.replace(FENCE_RE, (match) => "\n".repeat((match.match(/\n/g) ?? []).length));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Every Markdown heading's text, in document order. */
|
|
88
|
+
export function extractHeadings(text: string): string[] {
|
|
89
|
+
const headings: string[] = [];
|
|
90
|
+
for (const match of text.matchAll(HEADING_RE)) headings.push(match[1] as string);
|
|
91
|
+
return headings;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Every anchor id reachable in this document. Duplicate heading text is
|
|
96
|
+
* suffixed `-1`, `-2`, ... in encounter order, as GitHub disambiguates.
|
|
97
|
+
*/
|
|
98
|
+
export function computeAnchors(text: string): Set<string> {
|
|
99
|
+
const anchors = new Set<string>();
|
|
100
|
+
const seen = new Map<string, number>();
|
|
101
|
+
for (const heading of extractHeadings(text)) {
|
|
102
|
+
const base = slugify(heading);
|
|
103
|
+
const count = (seen.get(base) ?? -1) + 1;
|
|
104
|
+
seen.set(base, count);
|
|
105
|
+
anchors.add(count === 0 ? base : `${base}-${count}`);
|
|
106
|
+
}
|
|
107
|
+
return anchors;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function toPosix(path: string): string {
|
|
111
|
+
return sep === "/" ? path : path.split(sep).join("/");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isTemplatePlaceholder(sourceFile: string, targetPath: string): boolean {
|
|
115
|
+
return toPosix(sourceFile).includes(TEMPLATE_DIR_MARKER) && targetPath === "index.md";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** True for a `fixtures` directory nested under a `test`/`tests` one. */
|
|
119
|
+
function isTestFixturePath(path: string): boolean {
|
|
120
|
+
const parts = toPosix(path).split("/");
|
|
121
|
+
return parts.some(
|
|
122
|
+
(part, index) =>
|
|
123
|
+
part === FIXTURE_DIR_NAME && parts.slice(0, index).some((p) => TEST_DIR_NAMES.has(p)),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function lineOf(text: string, offset: number): number {
|
|
128
|
+
let line = 1;
|
|
129
|
+
for (let i = 0; i < offset; i += 1) if (text[i] === "\n") line += 1;
|
|
130
|
+
return line;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Every markdown file in the tree, in deterministic (sorted) order. */
|
|
134
|
+
function iterMarkdownFiles(repoRoot: string): string[] {
|
|
135
|
+
const found: string[] = [];
|
|
136
|
+
const walk = (dir: string): void => {
|
|
137
|
+
let entries: Dirent[];
|
|
138
|
+
try {
|
|
139
|
+
entries = readdirSync(dir, { withFileTypes: true, encoding: "utf8" });
|
|
140
|
+
} catch {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
const full = join(dir, entry.name);
|
|
145
|
+
if (entry.isDirectory()) {
|
|
146
|
+
if (EXCLUDED_DIR_NAMES.has(entry.name)) continue;
|
|
147
|
+
walk(full);
|
|
148
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
149
|
+
if (!isTestFixturePath(full)) found.push(full);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
walk(repoRoot);
|
|
154
|
+
return found.sort();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Check every relative/anchor link in one markdown file. */
|
|
158
|
+
export function checkFile(sourceFile: string, repoRoot: string): BrokenLink[] {
|
|
159
|
+
const raw = readFileSync(sourceFile, "utf8");
|
|
160
|
+
const scanned = stripFencedCode(raw);
|
|
161
|
+
const relSource = toPosix(relative(repoRoot, sourceFile));
|
|
162
|
+
const problems: BrokenLink[] = [];
|
|
163
|
+
let ownAnchors: Set<string> | null = null;
|
|
164
|
+
|
|
165
|
+
for (const match of scanned.matchAll(LINK_RE)) {
|
|
166
|
+
const link = (match[1] as string).trim();
|
|
167
|
+
if (/^(https?:\/\/|mailto:)/.test(link)) continue;
|
|
168
|
+
const line = lineOf(scanned, match.index ?? 0);
|
|
169
|
+
|
|
170
|
+
if (link.startsWith("#")) {
|
|
171
|
+
ownAnchors ??= computeAnchors(raw);
|
|
172
|
+
if (!ownAnchors.has(link.slice(1))) {
|
|
173
|
+
problems.push({
|
|
174
|
+
sourceFile: relSource,
|
|
175
|
+
line,
|
|
176
|
+
target: link,
|
|
177
|
+
reason: "anchor not found in this file",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const hashIndex = link.indexOf("#");
|
|
184
|
+
const pathPart = hashIndex === -1 ? link : link.slice(0, hashIndex);
|
|
185
|
+
const anchorPart = hashIndex === -1 ? "" : link.slice(hashIndex + 1);
|
|
186
|
+
if (isTemplatePlaceholder(sourceFile, pathPart)) continue;
|
|
187
|
+
|
|
188
|
+
const target = normalize(join(dirname(sourceFile), pathPart));
|
|
189
|
+
if (!existsSync(target)) {
|
|
190
|
+
problems.push({
|
|
191
|
+
sourceFile: relSource,
|
|
192
|
+
line,
|
|
193
|
+
target: link,
|
|
194
|
+
reason: "target file does not exist",
|
|
195
|
+
});
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (anchorPart && target.endsWith(".md")) {
|
|
200
|
+
if (!computeAnchors(readFileSync(target, "utf8")).has(anchorPart)) {
|
|
201
|
+
problems.push({
|
|
202
|
+
sourceFile: relSource,
|
|
203
|
+
line,
|
|
204
|
+
target: link,
|
|
205
|
+
reason: "anchor not found in target file",
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return problems;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Check every markdown file in the repository. Deterministic order. */
|
|
215
|
+
export function checkRepo(repoRoot: string): BrokenLink[] {
|
|
216
|
+
const problems: BrokenLink[] = [];
|
|
217
|
+
for (const file of iterMarkdownFiles(repoRoot)) problems.push(...checkFile(file, repoRoot));
|
|
218
|
+
return problems;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Two or more roadmap `index.md` files whose heading claims the same code.
|
|
223
|
+
*
|
|
224
|
+
* Sorted by code, then by path, for stable CI output.
|
|
225
|
+
*
|
|
226
|
+
* Two resolution hazards, both real, both pinned by the self-test:
|
|
227
|
+
*
|
|
228
|
+
* 1. **The helper's contract is not Python's.** `find_duplicate_roadmap_headings`
|
|
229
|
+
* returns ABSOLUTE directories; the TypeScript `findDuplicateRoadmapHeadings`
|
|
230
|
+
* returns paths RELATIVE TO THE ROADMAP ROOT (`cv2-ds1-a`), because that is
|
|
231
|
+
* what `resolveStoryDirectory` needed when DS7.US8 ported it. Same name,
|
|
232
|
+
* different contract -- so this adapter joins them onto the roadmap root
|
|
233
|
+
* rather than treating them as absolute. Assuming parity of a helper whose
|
|
234
|
+
* name matched would have produced `../../../..`-prefixed garbage in CI
|
|
235
|
+
* output.
|
|
236
|
+
* 2. **Both ends must be realpath'd or neither.** Python resolves the repo root
|
|
237
|
+
* and the roadmap root, so a root holding `..` or (on macOS) a
|
|
238
|
+
* `/var` -> `/private/var` symlink still yields a repo-relative path.
|
|
239
|
+
* Resolving only one end reintroduces exactly the defect the Python test
|
|
240
|
+
* says was caught by manual smoke-testing.
|
|
241
|
+
*/
|
|
242
|
+
export function checkRoadmapDuplicateHeadings(repoRoot: string): DuplicateRoadmapHeading[] {
|
|
243
|
+
const realpathOrSelf = (path: string): string => {
|
|
244
|
+
try {
|
|
245
|
+
return statSync(path).isDirectory() ? realpathSync(path) : path;
|
|
246
|
+
} catch {
|
|
247
|
+
return path;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const resolvedRoot = realpathOrSelf(resolve(repoRoot));
|
|
252
|
+
const resolvedRoadmapRoot = realpathOrSelf(resolve(roadmapPaths(repoRoot).roadmapRoot));
|
|
253
|
+
|
|
254
|
+
const duplicates = findDuplicateRoadmapHeadings(repoRoot);
|
|
255
|
+
return [...duplicates.entries()]
|
|
256
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
257
|
+
.map(([code, directories]) => ({
|
|
258
|
+
code,
|
|
259
|
+
paths: directories
|
|
260
|
+
.map((directory) =>
|
|
261
|
+
toPosix(relative(resolvedRoot, join(resolvedRoadmapRoot, directory, "index.md"))),
|
|
262
|
+
)
|
|
263
|
+
.sort(),
|
|
264
|
+
}));
|
|
265
|
+
}
|