mirror-mind 0.0.1 → 0.33.0
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-assistant-reply.sh +85 -0
- 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 +646 -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 +100 -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.33.0.md +164 -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 +17 -0
- package/plugins/mirror-mind/hooks/hooks.json +62 -0
- package/plugins/mirror-mind/hooks/log-assistant-reply.sh +98 -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/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 +67 -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/hookPayload.ts +51 -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 +851 -0
- package/ts/src/conversation/loggerCli.ts +321 -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 +1872 -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 +83 -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 +1001 -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/sessionStartWelcomeRoute.ts +166 -0
- package/ts/src/frontDoor/soulRoute.ts +433 -0
- package/ts/src/frontDoor/stdinSource.ts +37 -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 +298 -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 +214 -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 +73 -0
- package/ts/src/hooks/runtime.ts +179 -0
- package/ts/src/hooks/runtimeDetection.ts +34 -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/mirrorForm.ts +43 -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,267 @@
|
|
|
1
|
+
// Dated zip backup of the memory database — port of `src/memory/cli/backup.py`
|
|
2
|
+
// (CV22.DS7.TS1). This is the archive `repair-encoding --apply` and
|
|
3
|
+
// `repair-journeys --apply` gate on; the front door's own fixed-name pre-write
|
|
4
|
+
// snapshot (`liveBackup.ts`) is a different, weaker property and stays as is.
|
|
5
|
+
//
|
|
6
|
+
// What the archive is (CR061, which replaced the raw copy DS7.TS1 reproduced):
|
|
7
|
+
// - one member, `memory.db`: a consistent snapshot of the database as
|
|
8
|
+
// committed at one instant, taken with `VACUUM INTO` from a read-only
|
|
9
|
+
// connection, switched to WAL so a restore runs as the original did, and
|
|
10
|
+
// checked with `PRAGMA quick_check` before it is published. The old raw
|
|
11
|
+
// copy of `memory.db` plus its `-wal`/`-shm` could restore a torn pair,
|
|
12
|
+
// and its `memory.db` alone lacked every row still in the WAL;
|
|
13
|
+
// - the snapshot is taken in a private directory under the OS temp dir, never
|
|
14
|
+
// in the backups directory, which may be a synced folder;
|
|
15
|
+
// - the zip is staged beside its final name as
|
|
16
|
+
// `memory_<stamp>.zip.<pid>-<12 hex>.partial` and renamed into place, so a
|
|
17
|
+
// process killed mid-write never leaves a plausible archive, and two
|
|
18
|
+
// backups in the same second never write into one file;
|
|
19
|
+
// - owner-only (CR062): the archive is written 0600 from its first byte, a
|
|
20
|
+
// directory the backup creates is 0700, and every `memory_*.zip` in the
|
|
21
|
+
// directory is tightened to 0600. The directory itself keeps its mode:
|
|
22
|
+
// REFERENCE.md's posture never mutates a pre-existing directory;
|
|
23
|
+
// - the member is always named `memory.db` whatever the on-disk file is
|
|
24
|
+
// called (the restore contract reads that name), and its DOS time is the
|
|
25
|
+
// moment of capture;
|
|
26
|
+
// - after writing, stranded staging whose process is gone is swept (the
|
|
27
|
+
// shared rule in `staging.ts`), a pre-CR061 `.zip.partial` is removed as it
|
|
28
|
+
// always was, and `memory_*.zip` archives older than 30 days by the stamp in
|
|
29
|
+
// their NAME are deleted (unparseable names are left alone);
|
|
30
|
+
// - stdout lines, the deprecated `BACKUP_DIR` warning, and `--silent`
|
|
31
|
+
// printing nothing on success are Python's. `--silent` no longer hides
|
|
32
|
+
// failure: the CLI exits 1 with one line when no archive was written
|
|
33
|
+
// (CR060, `dbSafetyToolsRoute.ts`).
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
chmodSync,
|
|
37
|
+
existsSync,
|
|
38
|
+
mkdirSync,
|
|
39
|
+
mkdtempSync,
|
|
40
|
+
readdirSync,
|
|
41
|
+
readFileSync,
|
|
42
|
+
renameSync,
|
|
43
|
+
rmSync,
|
|
44
|
+
statSync,
|
|
45
|
+
writeFileSync,
|
|
46
|
+
} from "node:fs";
|
|
47
|
+
import { tmpdir } from "node:os";
|
|
48
|
+
import { dirname, join } from "node:path";
|
|
49
|
+
import { prepareArchiveSnapshot, snapshotDatabaseTo } from "#db/database.ts";
|
|
50
|
+
import { stagingToken, sweepAbandoned } from "./staging.ts";
|
|
51
|
+
import { writeZipArchive, type ZipEntryInput } from "./zipWriter.ts";
|
|
52
|
+
|
|
53
|
+
export const RETENTION_DAYS = 30;
|
|
54
|
+
const ARCHIVE_MEMBER = "memory.db";
|
|
55
|
+
/** What Mirror calls an archive; the retention sweep and the tightening both read it. */
|
|
56
|
+
const ARCHIVE_NAME = /^memory_.*\.zip$/;
|
|
57
|
+
/** A zip staged beside its final name: `<archive>.<pid>-<12 hex>.partial`. */
|
|
58
|
+
const ZIP_STAGING = /^memory_\d{8}_\d{6}\.zip\.(\d+)-[0-9a-f]{12}\.partial$/;
|
|
59
|
+
/** A zip staged before CR061 carried no process id, and is swept as it always was. */
|
|
60
|
+
const LEGACY_ZIP_STAGING = /^memory_.*\.zip\.partial$/;
|
|
61
|
+
/** A snapshot's private directory under the temp dir: `mirror-backup-<pid>-XXXXXX`. */
|
|
62
|
+
const SNAPSHOT_DIR_PREFIX = "mirror-backup-";
|
|
63
|
+
const SNAPSHOT_DIR = /^mirror-backup-(\d+)-[A-Za-z0-9]{6}$/;
|
|
64
|
+
const BACKUP_DIR_NAME = "backups";
|
|
65
|
+
|
|
66
|
+
export const DEPRECATED_BACKUP_DIR_WARNING =
|
|
67
|
+
"warning: BACKUP_DIR is deprecated and no longer redirects backups. " +
|
|
68
|
+
"Backups are written under <mirror_home>/backups. " +
|
|
69
|
+
"Use --backup-dir <path> to choose a destination.";
|
|
70
|
+
|
|
71
|
+
export interface ZipBackupOptions {
|
|
72
|
+
/** The database file to archive. Resolved by the caller (mirror home, env, `--db-path`). */
|
|
73
|
+
dbPath: string;
|
|
74
|
+
/** Printed as `Mirror home:` when given; `null` reproduces Python's `mirror_home=None` output. */
|
|
75
|
+
mirrorHome: string | null;
|
|
76
|
+
/** Explicit destination; `null` means `<dirname(dbPath)>/backups`. */
|
|
77
|
+
backupDir: string | null;
|
|
78
|
+
silent?: boolean;
|
|
79
|
+
/** Process environment view, for the deprecated `BACKUP_DIR` warning. */
|
|
80
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
81
|
+
now?: () => Date;
|
|
82
|
+
/** Where the snapshot's private directory is made; the OS temp dir by default. */
|
|
83
|
+
tempDir?: string;
|
|
84
|
+
stdout?: (line: string) => void;
|
|
85
|
+
stderr?: (line: string) => void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Python `f"{size / 1024:.0f}"`: round half to even, no decimals. */
|
|
89
|
+
export function formatKilobytes(sizeBytes: number): string {
|
|
90
|
+
const kb = sizeBytes / 1024;
|
|
91
|
+
const floor = Math.floor(kb);
|
|
92
|
+
const fraction = kb - floor;
|
|
93
|
+
if (fraction > 0.5) return String(floor + 1);
|
|
94
|
+
if (fraction < 0.5) return String(floor);
|
|
95
|
+
return String(floor % 2 === 0 ? floor : floor + 1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function pad(value: number, width = 2): string {
|
|
99
|
+
return String(value).padStart(width, "0");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** `datetime.now().strftime("%Y%m%d_%H%M%S")` in local time. */
|
|
103
|
+
export function archiveStamp(now: Date): string {
|
|
104
|
+
return (
|
|
105
|
+
`${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_` +
|
|
106
|
+
`${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Parse `memory_YYYYMMDD_HHMMSS.zip` back into a local-time Date, or null. */
|
|
111
|
+
export function parseArchiveStamp(fileName: string): Date | null {
|
|
112
|
+
const match = /^memory_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})\.zip$/.exec(fileName);
|
|
113
|
+
if (!match) return null;
|
|
114
|
+
const [year, month, day, hour, minute, second] = match.slice(1).map(Number) as number[];
|
|
115
|
+
const date = new Date(year as number, (month as number) - 1, day, hour, minute, second);
|
|
116
|
+
// strptime rejects impossible dates; so do we, by round-tripping.
|
|
117
|
+
return archiveStamp(date) === fileName.slice("memory_".length, -".zip".length) ? date : null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The snapshot could not be taken, or failed its check: nothing is published. */
|
|
121
|
+
export class BackupSnapshotError extends Error {
|
|
122
|
+
constructor(detail: string) {
|
|
123
|
+
super(`could not snapshot the database: ${detail}`);
|
|
124
|
+
this.name = "BackupSnapshotError";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The archive's one member: a snapshot of the database as committed, taken in
|
|
130
|
+
* a private directory under `tempDir` and removed whatever happens. The file is
|
|
131
|
+
* created empty with mode 0600 before `VACUUM INTO` writes into it, so the copy
|
|
132
|
+
* is owner-only from its first byte, and the member carries that mode, which is
|
|
133
|
+
* the mode `unzip` restores it with.
|
|
134
|
+
*/
|
|
135
|
+
export function snapshotEntry(dbPath: string, tempDir: string, capturedAt: Date): ZipEntryInput {
|
|
136
|
+
const dir = mkdtempSync(join(tempDir, `${SNAPSHOT_DIR_PREFIX}${process.pid}-`));
|
|
137
|
+
try {
|
|
138
|
+
const path = join(dir, ARCHIVE_MEMBER);
|
|
139
|
+
writeFileSync(path, "", { mode: 0o600, flag: "wx" });
|
|
140
|
+
try {
|
|
141
|
+
snapshotDatabaseTo(dbPath, path);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new BackupSnapshotError(error instanceof Error ? error.message : String(error));
|
|
144
|
+
}
|
|
145
|
+
const check = prepareArchiveSnapshot(path);
|
|
146
|
+
if (!check.ok) throw new BackupSnapshotError(`quick_check: ${check.verdict}`);
|
|
147
|
+
return {
|
|
148
|
+
name: ARCHIVE_MEMBER,
|
|
149
|
+
data: readFileSync(path),
|
|
150
|
+
mtime: capturedAt,
|
|
151
|
+
mode: statSync(path).mode,
|
|
152
|
+
};
|
|
153
|
+
} finally {
|
|
154
|
+
rmSync(dir, { recursive: true, force: true });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Write an archive through a staging file unique to this process and owner-only
|
|
160
|
+
* from its first byte (CR062), then rename it into place. A process killed
|
|
161
|
+
* mid-write never leaves a plausible archive, two writers never share a file,
|
|
162
|
+
* and a failure removes the staging file before it propagates.
|
|
163
|
+
*/
|
|
164
|
+
export function publishArchive(archivePath: string, bytes: Uint8Array): void {
|
|
165
|
+
const stagingPath = `${archivePath}.${stagingToken()}.partial`;
|
|
166
|
+
try {
|
|
167
|
+
writeFileSync(stagingPath, bytes, { mode: 0o600 });
|
|
168
|
+
renameSync(stagingPath, archivePath);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
rmSync(stagingPath, { force: true });
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* CR062, decision D2a: every archive Mirror named in this directory becomes
|
|
177
|
+
* owner-only, including those written before this CR. The directory keeps its
|
|
178
|
+
* mode: REFERENCE.md's posture never mutates a pre-existing directory, and on
|
|
179
|
+
* the Navigator's install this one is a Dropbox folder. Best-effort: an
|
|
180
|
+
* archive that cannot be tightened is not this backup's failure.
|
|
181
|
+
*/
|
|
182
|
+
function tightenArchives(backupDir: string): void {
|
|
183
|
+
for (const name of readdirSync(backupDir)) {
|
|
184
|
+
if (!ARCHIVE_NAME.test(name)) continue;
|
|
185
|
+
try {
|
|
186
|
+
chmodSync(join(backupDir, name), 0o600);
|
|
187
|
+
} catch {
|
|
188
|
+
// Left as it is; `runtime diagnose` reports loose permissions.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Staging left behind: pre-CR061 partials as always, the rest only when their writer is gone. */
|
|
194
|
+
function sweepStaging(backupDir: string, tempDir: string): void {
|
|
195
|
+
for (const name of readdirSync(backupDir)) {
|
|
196
|
+
if (!LEGACY_ZIP_STAGING.test(name)) continue;
|
|
197
|
+
try {
|
|
198
|
+
rmSync(join(backupDir, name));
|
|
199
|
+
} catch {
|
|
200
|
+
// A stranded file we cannot remove is not this run's failure.
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
sweepAbandoned(backupDir, ZIP_STAGING);
|
|
204
|
+
sweepAbandoned(tempDir, SNAPSHOT_DIR);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function sweepRetention(backupDir: string, keep: string, now: Date): number {
|
|
208
|
+
const cutoff = new Date(now.getTime() - RETENTION_DAYS * 24 * 60 * 60 * 1000);
|
|
209
|
+
let removed = 0;
|
|
210
|
+
for (const name of readdirSync(backupDir)) {
|
|
211
|
+
if (name === keep || !ARCHIVE_NAME.test(name)) continue;
|
|
212
|
+
const stamp = parseArchiveStamp(name);
|
|
213
|
+
if (stamp === null || stamp.getTime() >= cutoff.getTime()) continue;
|
|
214
|
+
try {
|
|
215
|
+
rmSync(join(backupDir, name));
|
|
216
|
+
removed += 1;
|
|
217
|
+
} catch {
|
|
218
|
+
// Same tolerance as the oracle's `except (ValueError, OSError): continue`.
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return removed;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Create the dated archive and sweep old ones. Returns the archive path, or
|
|
226
|
+
* `null` when the database does not exist (already reported on stdout unless
|
|
227
|
+
* silent). Exit-code semantics belong to the CLI, as in Python.
|
|
228
|
+
*/
|
|
229
|
+
export function createZipBackup(options: ZipBackupOptions): string | null {
|
|
230
|
+
const silent = options.silent ?? false;
|
|
231
|
+
const now = options.now ?? (() => new Date());
|
|
232
|
+
const env = options.env ?? process.env;
|
|
233
|
+
const out = options.stdout ?? ((line: string) => process.stdout.write(`${line}\n`));
|
|
234
|
+
const err = options.stderr ?? ((line: string) => process.stderr.write(`${line}\n`));
|
|
235
|
+
|
|
236
|
+
if (options.backupDir === null && env.BACKUP_DIR) err(DEPRECATED_BACKUP_DIR_WARNING);
|
|
237
|
+
const backupDir = options.backupDir ?? join(dirname(options.dbPath), BACKUP_DIR_NAME);
|
|
238
|
+
|
|
239
|
+
if (!silent) {
|
|
240
|
+
if (options.mirrorHome !== null) out(`Mirror home: ${options.mirrorHome}`);
|
|
241
|
+
out(`Database: ${options.dbPath}`);
|
|
242
|
+
out(`Backup dir: ${backupDir}`);
|
|
243
|
+
}
|
|
244
|
+
if (!existsSync(options.dbPath)) {
|
|
245
|
+
if (!silent) out(`Database not found: ${options.dbPath}`);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
mkdirSync(backupDir, { recursive: true, mode: 0o700 });
|
|
250
|
+
const capturedAt = now();
|
|
251
|
+
const archiveName = `memory_${archiveStamp(capturedAt)}.zip`;
|
|
252
|
+
const archivePath = join(backupDir, archiveName);
|
|
253
|
+
const tempDir = options.tempDir ?? tmpdir();
|
|
254
|
+
const member = snapshotEntry(options.dbPath, tempDir, capturedAt);
|
|
255
|
+
publishArchive(archivePath, writeZipArchive([member]));
|
|
256
|
+
|
|
257
|
+
if (!silent)
|
|
258
|
+
out(`Backup created: ${archiveName} (${formatKilobytes(statSync(archivePath).size)} KB)`);
|
|
259
|
+
|
|
260
|
+
sweepStaging(backupDir, tempDir);
|
|
261
|
+
const removed = sweepRetention(backupDir, archiveName, now());
|
|
262
|
+
tightenArchives(backupDir);
|
|
263
|
+
if (!silent && removed > 0)
|
|
264
|
+
out(`Removed ${removed} backup(s) older than ${RETENTION_DAYS} days.`);
|
|
265
|
+
|
|
266
|
+
return archivePath;
|
|
267
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// The read half of `ts/src/backup/zipWriter.ts` (CV22.DS10.US2).
|
|
2
|
+
//
|
|
3
|
+
// Node ships a zip WRITER's primitives (`deflateRawSync`, `crc32`) but no
|
|
4
|
+
// reader, and `runtime backup --verify` has to open archives written by three
|
|
5
|
+
// different producers: this repository's writer (deflate-raw), Python's
|
|
6
|
+
// `zipfile` in the shipped backup path (deflate), and Python's `zipfile`
|
|
7
|
+
// defaults in any hand-made archive (STORED). Both methods are supported for
|
|
8
|
+
// that reason, not for generality.
|
|
9
|
+
//
|
|
10
|
+
// Scope is deliberately small: list the central directory, and extract one
|
|
11
|
+
// named entry. No streaming, no zip64, no encryption, no directory creation --
|
|
12
|
+
// this module never writes to the filesystem, which is also why zip-slip is not
|
|
13
|
+
// ITS problem: the caller refuses unsafe names (see `runtime/backup.ts`), and
|
|
14
|
+
// nothing here ever turns an entry name into a path.
|
|
15
|
+
|
|
16
|
+
import { inflateRawSync } from "node:zlib";
|
|
17
|
+
|
|
18
|
+
const EOCD_SIGNATURE = 0x06054b50;
|
|
19
|
+
const CENTRAL_SIGNATURE = 0x02014b50;
|
|
20
|
+
const LOCAL_SIGNATURE = 0x04034b50;
|
|
21
|
+
const EOCD_MIN_SIZE = 22;
|
|
22
|
+
/** The comment field is 16 bits, so the EOCD starts at most this far from the end. */
|
|
23
|
+
const EOCD_MAX_SCAN = EOCD_MIN_SIZE + 0xffff;
|
|
24
|
+
|
|
25
|
+
const METHOD_STORED = 0;
|
|
26
|
+
const METHOD_DEFLATED = 8;
|
|
27
|
+
|
|
28
|
+
export class NotAReadableZipError extends Error {}
|
|
29
|
+
|
|
30
|
+
export interface ZipDirectoryEntry {
|
|
31
|
+
name: string;
|
|
32
|
+
method: number;
|
|
33
|
+
compressedSize: number;
|
|
34
|
+
uncompressedSize: number;
|
|
35
|
+
localHeaderOffset: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function findEndOfCentralDirectory(buffer: Buffer): number {
|
|
39
|
+
const earliest = Math.max(0, buffer.length - EOCD_MAX_SCAN);
|
|
40
|
+
for (let offset = buffer.length - EOCD_MIN_SIZE; offset >= earliest; offset -= 1) {
|
|
41
|
+
if (buffer.readUInt32LE(offset) === EOCD_SIGNATURE) return offset;
|
|
42
|
+
}
|
|
43
|
+
throw new NotAReadableZipError("end of central directory not found");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The archive's central directory, in the order the archive lists it. */
|
|
47
|
+
export function readZipDirectory(buffer: Buffer): ZipDirectoryEntry[] {
|
|
48
|
+
if (buffer.length < EOCD_MIN_SIZE) throw new NotAReadableZipError("file is too small for a zip");
|
|
49
|
+
const eocd = findEndOfCentralDirectory(buffer);
|
|
50
|
+
const count = buffer.readUInt16LE(eocd + 10);
|
|
51
|
+
let offset = buffer.readUInt32LE(eocd + 16);
|
|
52
|
+
|
|
53
|
+
const entries: ZipDirectoryEntry[] = [];
|
|
54
|
+
for (let index = 0; index < count; index += 1) {
|
|
55
|
+
if (offset + 46 > buffer.length) throw new NotAReadableZipError("central directory truncated");
|
|
56
|
+
if (buffer.readUInt32LE(offset) !== CENTRAL_SIGNATURE) {
|
|
57
|
+
throw new NotAReadableZipError("central directory header not found");
|
|
58
|
+
}
|
|
59
|
+
const nameLength = buffer.readUInt16LE(offset + 28);
|
|
60
|
+
const extraLength = buffer.readUInt16LE(offset + 30);
|
|
61
|
+
const commentLength = buffer.readUInt16LE(offset + 32);
|
|
62
|
+
entries.push({
|
|
63
|
+
// Zip stores names as bytes; `zipfile` writes UTF-8 when the flag is set
|
|
64
|
+
// and cp437 otherwise. Mirror's own entries are ASCII either way.
|
|
65
|
+
name: buffer.toString("utf8", offset + 46, offset + 46 + nameLength),
|
|
66
|
+
method: buffer.readUInt16LE(offset + 10),
|
|
67
|
+
compressedSize: buffer.readUInt32LE(offset + 20),
|
|
68
|
+
uncompressedSize: buffer.readUInt32LE(offset + 24),
|
|
69
|
+
localHeaderOffset: buffer.readUInt32LE(offset + 42),
|
|
70
|
+
});
|
|
71
|
+
offset += 46 + nameLength + extraLength + commentLength;
|
|
72
|
+
}
|
|
73
|
+
return entries;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Every entry name, sorted — the oracle's `sorted(zf.namelist())`. */
|
|
77
|
+
export function readZipEntryNames(buffer: Buffer): string[] {
|
|
78
|
+
return readZipDirectory(buffer)
|
|
79
|
+
.map((entry) => entry.name)
|
|
80
|
+
.sort();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The bytes of one named entry, or null when the archive does not hold it. */
|
|
84
|
+
export function readZipEntry(buffer: Buffer, name: string): Buffer | null {
|
|
85
|
+
const entry = readZipDirectory(buffer).find((candidate) => candidate.name === name);
|
|
86
|
+
if (entry === undefined) return null;
|
|
87
|
+
|
|
88
|
+
const header = entry.localHeaderOffset;
|
|
89
|
+
if (header + 30 > buffer.length) throw new NotAReadableZipError("local header truncated");
|
|
90
|
+
if (buffer.readUInt32LE(header) !== LOCAL_SIGNATURE) {
|
|
91
|
+
throw new NotAReadableZipError("local header not found");
|
|
92
|
+
}
|
|
93
|
+
// The local header's own name/extra lengths are authoritative for the data
|
|
94
|
+
// offset: `zipfile` pads extra fields differently from the central copy.
|
|
95
|
+
const nameLength = buffer.readUInt16LE(header + 26);
|
|
96
|
+
const extraLength = buffer.readUInt16LE(header + 28);
|
|
97
|
+
const start = header + 30 + nameLength + extraLength;
|
|
98
|
+
const data = buffer.subarray(start, start + entry.compressedSize);
|
|
99
|
+
|
|
100
|
+
if (entry.method === METHOD_STORED) return Buffer.from(data);
|
|
101
|
+
if (entry.method === METHOD_DEFLATED) {
|
|
102
|
+
try {
|
|
103
|
+
return inflateRawSync(data);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new NotAReadableZipError(`entry '${name}' could not be inflated: ${String(error)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
throw new NotAReadableZipError(`entry '${name}' uses unsupported compression ${entry.method}`);
|
|
109
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Minimal ZIP writer for the dated database backup (CV22.DS7.TS1).
|
|
2
|
+
//
|
|
3
|
+
// Mirrors what Python's `zipfile.ZipFile(..., ZIP_DEFLATED).write(path, name)`
|
|
4
|
+
// produces for a small set of whole files: one deflate-raw local header per
|
|
5
|
+
// member (no data descriptor — sizes are known before the header is written),
|
|
6
|
+
// a central directory, and the end-of-central-directory record. DOS mtimes
|
|
7
|
+
// come from each file's stat in LOCAL time, as zipfile does.
|
|
8
|
+
//
|
|
9
|
+
// Deliberately not implemented: ZIP64 (refused above 4 GiB with a clear error;
|
|
10
|
+
// Python would switch formats silently), encryption, comments, streaming.
|
|
11
|
+
// Members are compressed in memory — fine for a database of tens of MB, which
|
|
12
|
+
// is what this backs up; a streaming writer is the change to make if that
|
|
13
|
+
// stops being true.
|
|
14
|
+
|
|
15
|
+
import { statSync } from "node:fs";
|
|
16
|
+
import { crc32, deflateRawSync } from "node:zlib";
|
|
17
|
+
|
|
18
|
+
export interface ZipEntryInput {
|
|
19
|
+
/** Member name inside the archive (ASCII; `memory.db`, `memory.db-wal`, ...). */
|
|
20
|
+
name: string;
|
|
21
|
+
data: Uint8Array;
|
|
22
|
+
/** Modification time stored as DOS date/time in local time. */
|
|
23
|
+
mtime: Date;
|
|
24
|
+
/** POSIX mode bits, stored in the external attributes like zipfile does. */
|
|
25
|
+
mode: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const LOCAL_SIG = 0x04034b50;
|
|
29
|
+
const CENTRAL_SIG = 0x02014b50;
|
|
30
|
+
const EOCD_SIG = 0x06054b50;
|
|
31
|
+
const VERSION_NEEDED = 20;
|
|
32
|
+
const METHOD_DEFLATE = 8;
|
|
33
|
+
const UINT32_MAX = 0xffffffff;
|
|
34
|
+
const UINT16_MAX = 0xffff;
|
|
35
|
+
|
|
36
|
+
// `version made by`: high byte is the host system (3 = Unix, 0 = MS-DOS/
|
|
37
|
+
// Windows), low byte the zip spec version — the same pair zipfile records.
|
|
38
|
+
const CREATE_SYSTEM = process.platform === "win32" ? 0 : 3;
|
|
39
|
+
|
|
40
|
+
export class Zip64RequiredError extends Error {
|
|
41
|
+
constructor(detail: string) {
|
|
42
|
+
super(`archive needs ZIP64, which this writer does not implement: ${detail}`);
|
|
43
|
+
this.name = "Zip64RequiredError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** DOS date/time pair for a local-time instant; years before 1980 clamp to 1980. */
|
|
48
|
+
export function dosDateTime(date: Date): { time: number; date: number } {
|
|
49
|
+
const year = Math.max(date.getFullYear(), 1980);
|
|
50
|
+
return {
|
|
51
|
+
time: (date.getHours() << 11) | (date.getMinutes() << 5) | (date.getSeconds() >> 1),
|
|
52
|
+
date: ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Read a whole file as a member input, stat-derived mtime and mode included. */
|
|
57
|
+
export function entryFromFile(path: string, name: string, data: Uint8Array): ZipEntryInput {
|
|
58
|
+
const stat = statSync(path);
|
|
59
|
+
return { name, data, mtime: stat.mtime, mode: stat.mode };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface WrittenEntry {
|
|
63
|
+
input: ZipEntryInput;
|
|
64
|
+
crc: number;
|
|
65
|
+
compressed: Buffer;
|
|
66
|
+
localOffset: number;
|
|
67
|
+
dos: { time: number; date: number };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function assertFits(value: number, what: string): void {
|
|
71
|
+
if (value > UINT32_MAX) throw new Zip64RequiredError(`${what} exceeds 4 GiB`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Build the complete archive bytes for `entries`, in the given order. */
|
|
75
|
+
export function writeZipArchive(entries: readonly ZipEntryInput[]): Buffer {
|
|
76
|
+
if (entries.length > UINT16_MAX) throw new Zip64RequiredError("more than 65535 members");
|
|
77
|
+
|
|
78
|
+
const parts: Buffer[] = [];
|
|
79
|
+
const written: WrittenEntry[] = [];
|
|
80
|
+
let offset = 0;
|
|
81
|
+
|
|
82
|
+
for (const input of entries) {
|
|
83
|
+
assertFits(input.data.byteLength, `${input.name} uncompressed size`);
|
|
84
|
+
const compressed = deflateRawSync(input.data);
|
|
85
|
+
assertFits(compressed.byteLength, `${input.name} compressed size`);
|
|
86
|
+
assertFits(offset, "archive offset");
|
|
87
|
+
const name = Buffer.from(input.name, "utf8");
|
|
88
|
+
const dos = dosDateTime(input.mtime);
|
|
89
|
+
const crc = crc32(input.data);
|
|
90
|
+
|
|
91
|
+
const header = Buffer.alloc(30);
|
|
92
|
+
header.writeUInt32LE(LOCAL_SIG, 0);
|
|
93
|
+
header.writeUInt16LE(VERSION_NEEDED, 4);
|
|
94
|
+
header.writeUInt16LE(0, 6); // flags: no data descriptor, no UTF-8 bit (ASCII names)
|
|
95
|
+
header.writeUInt16LE(METHOD_DEFLATE, 8);
|
|
96
|
+
header.writeUInt16LE(dos.time, 10);
|
|
97
|
+
header.writeUInt16LE(dos.date, 12);
|
|
98
|
+
header.writeUInt32LE(crc, 14);
|
|
99
|
+
header.writeUInt32LE(compressed.byteLength, 18);
|
|
100
|
+
header.writeUInt32LE(input.data.byteLength, 22);
|
|
101
|
+
header.writeUInt16LE(name.byteLength, 26);
|
|
102
|
+
header.writeUInt16LE(0, 28);
|
|
103
|
+
|
|
104
|
+
written.push({ input, crc, compressed, localOffset: offset, dos });
|
|
105
|
+
parts.push(header, name, compressed);
|
|
106
|
+
offset += header.byteLength + name.byteLength + compressed.byteLength;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const centralStart = offset;
|
|
110
|
+
for (const entry of written) {
|
|
111
|
+
const name = Buffer.from(entry.input.name, "utf8");
|
|
112
|
+
const header = Buffer.alloc(46);
|
|
113
|
+
header.writeUInt32LE(CENTRAL_SIG, 0);
|
|
114
|
+
header.writeUInt16LE((CREATE_SYSTEM << 8) | VERSION_NEEDED, 4);
|
|
115
|
+
header.writeUInt16LE(VERSION_NEEDED, 6);
|
|
116
|
+
header.writeUInt16LE(0, 8);
|
|
117
|
+
header.writeUInt16LE(METHOD_DEFLATE, 10);
|
|
118
|
+
header.writeUInt16LE(entry.dos.time, 12);
|
|
119
|
+
header.writeUInt16LE(entry.dos.date, 14);
|
|
120
|
+
header.writeUInt32LE(entry.crc, 16);
|
|
121
|
+
header.writeUInt32LE(entry.compressed.byteLength, 20);
|
|
122
|
+
header.writeUInt32LE(entry.input.data.byteLength, 24);
|
|
123
|
+
header.writeUInt16LE(name.byteLength, 28);
|
|
124
|
+
header.writeUInt16LE(0, 30); // extra
|
|
125
|
+
header.writeUInt16LE(0, 32); // comment
|
|
126
|
+
header.writeUInt16LE(0, 34); // disk number start
|
|
127
|
+
header.writeUInt16LE(0, 36); // internal attributes
|
|
128
|
+
header.writeUInt32LE(((entry.input.mode & 0xffff) << 16) >>> 0, 38);
|
|
129
|
+
header.writeUInt32LE(entry.localOffset, 42);
|
|
130
|
+
parts.push(header, name);
|
|
131
|
+
offset += header.byteLength + name.byteLength;
|
|
132
|
+
}
|
|
133
|
+
const centralSize = offset - centralStart;
|
|
134
|
+
assertFits(centralStart, "central directory offset");
|
|
135
|
+
|
|
136
|
+
const eocd = Buffer.alloc(22);
|
|
137
|
+
eocd.writeUInt32LE(EOCD_SIG, 0);
|
|
138
|
+
eocd.writeUInt16LE(0, 4);
|
|
139
|
+
eocd.writeUInt16LE(0, 6);
|
|
140
|
+
eocd.writeUInt16LE(written.length, 8);
|
|
141
|
+
eocd.writeUInt16LE(written.length, 10);
|
|
142
|
+
eocd.writeUInt32LE(centralSize, 12);
|
|
143
|
+
eocd.writeUInt32LE(centralStart, 16);
|
|
144
|
+
eocd.writeUInt16LE(0, 20);
|
|
145
|
+
parts.push(eocd);
|
|
146
|
+
|
|
147
|
+
return Buffer.concat(parts);
|
|
148
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// CR067 with CR020 — the journey's active checkpoint, shown again without changing it.
|
|
2
|
+
//
|
|
3
|
+
// A checkpoint surface is printed once, by the command that advances the cursor, and the
|
|
4
|
+
// cursor keeps position only, never a checkpoint's evidence. So a lost surface cannot be
|
|
5
|
+
// replayed from it; the Navigator chose not to store emissions (option A). This surface
|
|
6
|
+
// shows where the cursor stands and where the full record lives: the story package's
|
|
7
|
+
// Plan-stage artifacts, each with its state, and its closure records, each marked
|
|
8
|
+
// present or missing. The package path is printed once, because record paths are
|
|
9
|
+
// longer than a card line.
|
|
10
|
+
//
|
|
11
|
+
// CR112: `index.md`, `plan.md`, and `test-guide.md` are written as scaffolds and then
|
|
12
|
+
// authored, and present-or-missing said nothing about which. Each now carries its
|
|
13
|
+
// state -- missing, scaffold, partly authored, authored -- and, while not authored,
|
|
14
|
+
// the sections still to write, by their exact heading text. The glyph carries only
|
|
15
|
+
// done or not; the word carries the state. Closure records keep present or missing:
|
|
16
|
+
// their truth is the seal. The story files' lines come from `artifacts/storyFiles.ts`,
|
|
17
|
+
// which the Plan checkpoint prints too (CR111).
|
|
18
|
+
//
|
|
19
|
+
// CR114: it prints the steps the cursor accepts next, the list the Builder resume
|
|
20
|
+
// prints, from the same selector, so the two cannot disagree. With no cursor it says
|
|
21
|
+
// so, and offers the sync the resume offers, where it gave a Pull command Pull refuses.
|
|
22
|
+
// CR115: before the steps, the cadence they will run at, as the resume prints it.
|
|
23
|
+
|
|
24
|
+
import type { ArtifactVerdict } from "./artifacts/scaffoldState.ts";
|
|
25
|
+
import { storyFileLines } from "./artifacts/storyFiles.ts";
|
|
26
|
+
import { cadenceCardLines } from "./cadence.ts";
|
|
27
|
+
import { cardPrefixed, cardText, cardWrapped } from "./card.ts";
|
|
28
|
+
import { allowedNextActions, lifecycleStageOf } from "./cursorPosition.ts";
|
|
29
|
+
import type { BuilderDeliveryCursor } from "./deliveryCursor.ts";
|
|
30
|
+
import { renderLifecycleRibbon } from "./lifecycleRibbon.ts";
|
|
31
|
+
import { NO_ITEM_PULLED_YET, pullExplicitly } from "./scopePhrases.ts";
|
|
32
|
+
import { wrapAriadSurface } from "./surfaceProtocol.ts";
|
|
33
|
+
|
|
34
|
+
const FRAME_TOP = "╭────────────────────────────────────────────────────────╮";
|
|
35
|
+
const FRAME_BOTTOM = "╰────────────────────────────────────────────────────────╯";
|
|
36
|
+
const FRAME_BLANK = "│ │";
|
|
37
|
+
|
|
38
|
+
/** A story package's closure records, in lifecycle order. */
|
|
39
|
+
export const CLOSURE_RECORDS = ["validation.md", "review.md", "coherence.md", "done.md"] as const;
|
|
40
|
+
|
|
41
|
+
export interface StoryRecords {
|
|
42
|
+
/** The package directory, as the Navigator sees it (project-relative when inside). */
|
|
43
|
+
readonly folder: string;
|
|
44
|
+
/** The Plan-stage artifacts, judged, in `PLAN_STAGE_ARTIFACTS` order (`judgeStoryFiles`). */
|
|
45
|
+
readonly artifacts: readonly ArtifactVerdict[];
|
|
46
|
+
/** The names in `CLOSURE_RECORDS` that exist on disk. */
|
|
47
|
+
readonly present: ReadonlySet<string>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ActiveCheckpointView {
|
|
51
|
+
readonly journey: string;
|
|
52
|
+
readonly cursor: BuilderDeliveryCursor | null;
|
|
53
|
+
/** `null` when no story package can be resolved for the cursor. */
|
|
54
|
+
readonly records: StoryRecords | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function recordLines(records: StoryRecords | null): string[] {
|
|
58
|
+
if (records === null) return [cardText("no story package found")];
|
|
59
|
+
return [
|
|
60
|
+
...cardWrapped(records.folder),
|
|
61
|
+
...records.artifacts.flatMap(storyFileLines),
|
|
62
|
+
...CLOSURE_RECORDS.map((name) => cardText(`${records.present.has(name) ? "✓" : "○"} ${name}`)),
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** What a journey whose delivery cursor was never synced holds. */
|
|
67
|
+
const NO_DELIVERY_CURSOR_YET = "no delivery cursor yet";
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The cadence the steps run at (CR115), then the steps the cursor accepts next: what
|
|
71
|
+
* the Builder resume prints, in its order (CR114).
|
|
72
|
+
*/
|
|
73
|
+
function nextActionLines(cursor: BuilderDeliveryCursor | null): string[] {
|
|
74
|
+
return [
|
|
75
|
+
FRAME_BLANK,
|
|
76
|
+
...cadenceCardLines(cursor),
|
|
77
|
+
FRAME_BLANK,
|
|
78
|
+
cardText("allowed next actions"),
|
|
79
|
+
...cardPrefixed(allowedNextActions(cursor), "-"),
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function positionLines(view: ActiveCheckpointView): string[] {
|
|
84
|
+
const cursor = view.cursor;
|
|
85
|
+
if (cursor === null) {
|
|
86
|
+
return [cardText("active item"), cardText(NO_DELIVERY_CURSOR_YET), ...nextActionLines(null)];
|
|
87
|
+
}
|
|
88
|
+
if (!cursor.activeItem) {
|
|
89
|
+
return [
|
|
90
|
+
cardText("active item"),
|
|
91
|
+
cardText(NO_ITEM_PULLED_YET),
|
|
92
|
+
...cardWrapped(pullExplicitly(view.journey)),
|
|
93
|
+
...nextActionLines(cursor),
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
const item = cursor.activeItemTitle
|
|
97
|
+
? `${cursor.activeItem} — ${cursor.activeItemTitle}`
|
|
98
|
+
: cursor.activeItem;
|
|
99
|
+
return [
|
|
100
|
+
cardText("active item"),
|
|
101
|
+
...cardWrapped(item),
|
|
102
|
+
FRAME_BLANK,
|
|
103
|
+
cardText("last event"),
|
|
104
|
+
cardText(cursor.lastDeliveryEvent ?? "none"),
|
|
105
|
+
FRAME_BLANK,
|
|
106
|
+
cardText("pending confirmation"),
|
|
107
|
+
cardText(cursor.pendingConfirmation ?? "none"),
|
|
108
|
+
FRAME_BLANK,
|
|
109
|
+
cardText("active checkpoint"),
|
|
110
|
+
cardText(cursor.activeCheckpoint ?? "none"),
|
|
111
|
+
...nextActionLines(cursor),
|
|
112
|
+
FRAME_BLANK,
|
|
113
|
+
cardText("records"),
|
|
114
|
+
...recordLines(view.records),
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function renderActiveCheckpoint(view: ActiveCheckpointView): string {
|
|
119
|
+
const stage = view.cursor === null ? null : lifecycleStageOf(view.cursor);
|
|
120
|
+
const body = [
|
|
121
|
+
"Delivery",
|
|
122
|
+
...(stage === null ? [] : [renderLifecycleRibbon(stage, view.cursor?.activeItemLevel ?? null)]),
|
|
123
|
+
"",
|
|
124
|
+
FRAME_TOP,
|
|
125
|
+
cardText(" ■ ACTIVE CHECKPOINT"),
|
|
126
|
+
FRAME_BLANK,
|
|
127
|
+
...positionLines(view),
|
|
128
|
+
FRAME_BLANK,
|
|
129
|
+
cardText("boundary"),
|
|
130
|
+
...cardWrapped("Read-only: the cursor and the project files were not changed."),
|
|
131
|
+
FRAME_BOTTOM,
|
|
132
|
+
].join("\n");
|
|
133
|
+
return wrapAriadSurface("active_checkpoint", `${body}\n`);
|
|
134
|
+
}
|