@proletariat/cli 0.3.94 → 0.3.96
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/dist/commands/agent/cleanup.d.ts +3 -4
- package/dist/commands/agent/cleanup.js +5 -4
- package/dist/commands/agent/cleanup.js.map +1 -1
- package/dist/commands/agent/gc.d.ts +3 -4
- package/dist/commands/agent/gc.js +5 -4
- package/dist/commands/agent/gc.js.map +1 -1
- package/dist/commands/agent/index.d.ts +3 -4
- package/dist/commands/agent/index.js +5 -4
- package/dist/commands/agent/index.js.map +1 -1
- package/dist/commands/agent/list.d.ts +3 -4
- package/dist/commands/agent/list.js +5 -4
- package/dist/commands/agent/list.js.map +1 -1
- package/dist/commands/agent/remove.d.ts +3 -4
- package/dist/commands/agent/remove.js +5 -4
- package/dist/commands/agent/remove.js.map +1 -1
- package/dist/commands/agent/staff/index.d.ts +3 -4
- package/dist/commands/agent/staff/index.js +5 -4
- package/dist/commands/agent/staff/index.js.map +1 -1
- package/dist/commands/agent/staff/remove.d.ts +3 -4
- package/dist/commands/agent/staff/remove.js +5 -4
- package/dist/commands/agent/staff/remove.js.map +1 -1
- package/dist/commands/agent/status.d.ts +3 -4
- package/dist/commands/agent/status.js +5 -4
- package/dist/commands/agent/status.js.map +1 -1
- package/dist/commands/agent/visit.d.ts +3 -4
- package/dist/commands/agent/visit.js +5 -4
- package/dist/commands/agent/visit.js.map +1 -1
- package/dist/commands/branch/create.js +1 -12
- package/dist/commands/branch/create.js.map +1 -1
- package/dist/commands/branch/list.d.ts +3 -4
- package/dist/commands/branch/list.js +5 -4
- package/dist/commands/branch/list.js.map +1 -1
- package/dist/commands/branch/validate.d.ts +3 -4
- package/dist/commands/branch/validate.js +6 -5
- package/dist/commands/branch/validate.js.map +1 -1
- package/dist/commands/branch/where.d.ts +3 -4
- package/dist/commands/branch/where.js +5 -4
- package/dist/commands/branch/where.js.map +1 -1
- package/dist/commands/commit.js +1 -1
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/config/index.js +1 -1
- package/dist/commands/config/index.js.map +1 -1
- package/dist/commands/db/backup.d.ts +16 -0
- package/dist/commands/db/backup.js +125 -0
- package/dist/commands/db/backup.js.map +1 -0
- package/dist/commands/db/repair.js +167 -45
- package/dist/commands/db/repair.js.map +1 -1
- package/dist/commands/execution/config.d.ts +3 -4
- package/dist/commands/execution/config.js +5 -4
- package/dist/commands/execution/config.js.map +1 -1
- package/dist/commands/execution/index.d.ts +3 -4
- package/dist/commands/execution/index.js +5 -4
- package/dist/commands/execution/index.js.map +1 -1
- package/dist/commands/execution/list.d.ts +3 -4
- package/dist/commands/execution/list.js +5 -4
- package/dist/commands/execution/list.js.map +1 -1
- package/dist/commands/execution/logs.d.ts +3 -4
- package/dist/commands/execution/logs.js +5 -4
- package/dist/commands/execution/logs.js.map +1 -1
- package/dist/commands/execution/stop.d.ts +3 -4
- package/dist/commands/execution/stop.js +5 -4
- package/dist/commands/execution/stop.js.map +1 -1
- package/dist/commands/execution/view.d.ts +3 -4
- package/dist/commands/execution/view.js +5 -4
- package/dist/commands/execution/view.js.map +1 -1
- package/dist/commands/gc.d.ts +13 -0
- package/dist/commands/gc.js +178 -0
- package/dist/commands/gc.js.map +1 -0
- package/dist/commands/hook/export.d.ts +3 -4
- package/dist/commands/hook/export.js +5 -4
- package/dist/commands/hook/export.js.map +1 -1
- package/dist/commands/hook/fire.d.ts +3 -4
- package/dist/commands/hook/fire.js +5 -4
- package/dist/commands/hook/fire.js.map +1 -1
- package/dist/commands/hook/list.d.ts +3 -4
- package/dist/commands/hook/list.js +5 -4
- package/dist/commands/hook/list.js.map +1 -1
- package/dist/commands/hook/preset.d.ts +3 -4
- package/dist/commands/hook/preset.js +5 -4
- package/dist/commands/hook/preset.js.map +1 -1
- package/dist/commands/orchestrate/index.d.ts +3 -4
- package/dist/commands/orchestrate/index.js +5 -4
- package/dist/commands/orchestrate/index.js.map +1 -1
- package/dist/commands/orchestrator/index.d.ts +3 -4
- package/dist/commands/orchestrator/index.js +5 -4
- package/dist/commands/orchestrator/index.js.map +1 -1
- package/dist/commands/pr/checks.d.ts +3 -4
- package/dist/commands/pr/checks.js +5 -4
- package/dist/commands/pr/checks.js.map +1 -1
- package/dist/commands/pr/merge.js +1 -1
- package/dist/commands/pr/merge.js.map +1 -1
- package/dist/commands/repo/add.d.ts +3 -4
- package/dist/commands/repo/add.js +5 -4
- package/dist/commands/repo/add.js.map +1 -1
- package/dist/commands/repo/create.d.ts +3 -4
- package/dist/commands/repo/create.js +5 -4
- package/dist/commands/repo/create.js.map +1 -1
- package/dist/commands/repo/fix-remotes.d.ts +3 -4
- package/dist/commands/repo/fix-remotes.js +5 -4
- package/dist/commands/repo/fix-remotes.js.map +1 -1
- package/dist/commands/repo/index.d.ts +3 -4
- package/dist/commands/repo/index.js +5 -4
- package/dist/commands/repo/index.js.map +1 -1
- package/dist/commands/repo/list.d.ts +3 -4
- package/dist/commands/repo/list.js +5 -4
- package/dist/commands/repo/list.js.map +1 -1
- package/dist/commands/session/attach.d.ts +3 -3
- package/dist/commands/session/attach.js +37 -79
- package/dist/commands/session/attach.js.map +1 -1
- package/dist/commands/session/cleanup.d.ts +3 -4
- package/dist/commands/session/cleanup.js +5 -4
- package/dist/commands/session/cleanup.js.map +1 -1
- package/dist/commands/session/create.d.ts +3 -4
- package/dist/commands/session/create.js +5 -4
- package/dist/commands/session/create.js.map +1 -1
- package/dist/commands/session/exec.d.ts +3 -4
- package/dist/commands/session/exec.js +6 -5
- package/dist/commands/session/exec.js.map +1 -1
- package/dist/commands/session/health.js +19 -1
- package/dist/commands/session/health.js.map +1 -1
- package/dist/commands/session/index.d.ts +3 -4
- package/dist/commands/session/index.js +9 -4
- package/dist/commands/session/index.js.map +1 -1
- package/dist/commands/session/inspect.d.ts +3 -4
- package/dist/commands/session/inspect.js +6 -5
- package/dist/commands/session/inspect.js.map +1 -1
- package/dist/commands/session/list.d.ts +4 -4
- package/dist/commands/session/list.js +129 -82
- package/dist/commands/session/list.js.map +1 -1
- package/dist/commands/session/peek.js +1 -1
- package/dist/commands/session/peek.js.map +1 -1
- package/dist/commands/session/poke.js +1 -1
- package/dist/commands/session/poke.js.map +1 -1
- package/dist/commands/session/prune.d.ts +3 -4
- package/dist/commands/session/prune.js +55 -15
- package/dist/commands/session/prune.js.map +1 -1
- package/dist/commands/session/report.d.ts +25 -5
- package/dist/commands/session/report.js +201 -5
- package/dist/commands/session/report.js.map +1 -1
- package/dist/commands/session/restart.d.ts +3 -4
- package/dist/commands/session/restart.js +6 -5
- package/dist/commands/session/restart.js.map +1 -1
- package/dist/commands/session/watch.d.ts +17 -0
- package/dist/commands/session/watch.js +139 -0
- package/dist/commands/session/watch.js.map +1 -0
- package/dist/commands/{action → sync}/index.d.ts +2 -6
- package/dist/commands/sync/index.js +68 -0
- package/dist/commands/sync/index.js.map +1 -0
- package/dist/commands/sync/pause.d.ts +10 -0
- package/dist/commands/sync/pause.js +35 -0
- package/dist/commands/sync/pause.js.map +1 -0
- package/dist/commands/sync/queue.d.ts +10 -0
- package/dist/commands/sync/queue.js +106 -0
- package/dist/commands/sync/queue.js.map +1 -0
- package/dist/commands/sync/resume.d.ts +10 -0
- package/dist/commands/sync/resume.js +34 -0
- package/dist/commands/sync/resume.js.map +1 -0
- package/dist/commands/sync/start.d.ts +11 -0
- package/dist/commands/sync/start.js +73 -0
- package/dist/commands/sync/start.js.map +1 -0
- package/dist/commands/sync/status.d.ts +10 -0
- package/dist/commands/sync/status.js +40 -0
- package/dist/commands/sync/status.js.map +1 -0
- package/dist/commands/sync/stop.d.ts +10 -0
- package/dist/commands/sync/stop.js +39 -0
- package/dist/commands/sync/stop.js.map +1 -0
- package/dist/commands/ticket/create.js +1 -1
- package/dist/commands/ticket/create.js.map +1 -1
- package/dist/commands/ticket/edit.js +1 -1
- package/dist/commands/ticket/edit.js.map +1 -1
- package/dist/commands/ticket/index.d.ts +3 -4
- package/dist/commands/ticket/index.js +5 -4
- package/dist/commands/ticket/index.js.map +1 -1
- package/dist/commands/ticket/list.js +1 -1
- package/dist/commands/ticket/list.js.map +1 -1
- package/dist/commands/ticket/update.js +1 -1
- package/dist/commands/ticket/update.js.map +1 -1
- package/dist/commands/web.d.ts +20 -0
- package/dist/commands/web.js +82 -0
- package/dist/commands/web.js.map +1 -0
- package/dist/commands/work/complete.js +1 -1
- package/dist/commands/work/complete.js.map +1 -1
- package/dist/commands/work/hooks/add.d.ts +3 -4
- package/dist/commands/work/hooks/add.js +5 -4
- package/dist/commands/work/hooks/add.js.map +1 -1
- package/dist/commands/work/hooks/list.d.ts +3 -4
- package/dist/commands/work/hooks/list.js +5 -4
- package/dist/commands/work/hooks/list.js.map +1 -1
- package/dist/commands/work/hooks/toggle.d.ts +3 -4
- package/dist/commands/work/hooks/toggle.js +5 -4
- package/dist/commands/work/hooks/toggle.js.map +1 -1
- package/dist/commands/work/peek.d.ts +3 -4
- package/dist/commands/work/peek.js +5 -4
- package/dist/commands/work/peek.js.map +1 -1
- package/dist/commands/work/propose.d.ts +23 -0
- package/dist/commands/work/propose.js +57 -0
- package/dist/commands/work/propose.js.map +1 -0
- package/dist/commands/work/ready.js +3 -3
- package/dist/commands/work/ready.js.map +1 -1
- package/dist/commands/work/rebase.d.ts +1 -1
- package/dist/commands/work/rebase.js +52 -49
- package/dist/commands/work/rebase.js.map +1 -1
- package/dist/commands/work/resolve.js +1 -1
- package/dist/commands/work/resolve.js.map +1 -1
- package/dist/commands/work/ship.d.ts +6 -0
- package/dist/commands/work/ship.js +218 -49
- package/dist/commands/work/ship.js.map +1 -1
- package/dist/commands/work/start.js +16 -90
- package/dist/commands/work/start.js.map +1 -1
- package/dist/commands/work/stop.d.ts +3 -4
- package/dist/commands/work/stop.js +5 -4
- package/dist/commands/work/stop.js.map +1 -1
- package/dist/hooks/init.js +11 -1
- package/dist/hooks/init.js.map +1 -1
- package/dist/lib/branch/index.d.ts +6 -5
- package/dist/lib/branch/index.js +8 -13
- package/dist/lib/branch/index.js.map +1 -1
- package/dist/lib/dashboard/data.js +1 -1
- package/dist/lib/dashboard/data.js.map +1 -1
- package/dist/lib/dashboard/html.d.ts +2 -1
- package/dist/lib/dashboard/html.js +150 -522
- package/dist/lib/dashboard/html.js.map +1 -1
- package/dist/lib/database/db-safety.d.ts +70 -9
- package/dist/lib/database/db-safety.js +395 -43
- package/dist/lib/database/db-safety.js.map +1 -1
- package/dist/lib/database/index.d.ts +1 -1
- package/dist/lib/database/index.js +1 -1
- package/dist/lib/database/index.js.map +1 -1
- package/dist/lib/database/migrations/0017_drop_agent_work_fk.d.ts +13 -0
- package/dist/lib/database/migrations/0017_drop_agent_work_fk.js +85 -0
- package/dist/lib/database/migrations/0017_drop_agent_work_fk.js.map +1 -0
- package/dist/lib/database/migrations/0018_create_ticket_refs.d.ts +11 -0
- package/dist/lib/database/migrations/0018_create_ticket_refs.js +40 -0
- package/dist/lib/database/migrations/0018_create_ticket_refs.js.map +1 -0
- package/dist/lib/database/migrations/index.js +4 -0
- package/dist/lib/database/migrations/index.js.map +1 -1
- package/dist/lib/database/workspace.js +3 -1
- package/dist/lib/database/workspace.js.map +1 -1
- package/dist/lib/execution/devcontainer.js +1 -1
- package/dist/lib/execution/devcontainer.js.map +1 -1
- package/dist/lib/execution/runners/docker-management.d.ts +9 -0
- package/dist/lib/execution/runners/docker-management.js +14 -3
- package/dist/lib/execution/runners/docker-management.js.map +1 -1
- package/dist/lib/execution/runners/docker.js +8 -0
- package/dist/lib/execution/runners/docker.js.map +1 -1
- package/dist/lib/execution/runners/prompt-builder.js +24 -9
- package/dist/lib/execution/runners/prompt-builder.js.map +1 -1
- package/dist/lib/execution/runners/shared.d.ts +1 -1
- package/dist/lib/execution/runners/shared.js +1 -1
- package/dist/lib/execution/runners/shared.js.map +1 -1
- package/dist/lib/execution/session-utils.d.ts +16 -0
- package/dist/lib/execution/session-utils.js +42 -0
- package/dist/lib/execution/session-utils.js.map +1 -1
- package/dist/lib/execution/spawner.js +4 -5
- package/dist/lib/execution/spawner.js.map +1 -1
- package/dist/lib/execution/storage.d.ts +29 -1
- package/dist/lib/execution/storage.js +79 -1
- package/dist/lib/execution/storage.js.map +1 -1
- package/dist/lib/execution/types.d.ts +17 -6
- package/dist/lib/execution/types.js +10 -7
- package/dist/lib/execution/types.js.map +1 -1
- package/dist/lib/external-issues/utils.d.ts +25 -0
- package/dist/lib/external-issues/utils.js +32 -0
- package/dist/lib/external-issues/utils.js.map +1 -0
- package/dist/lib/gc/index.d.ts +126 -0
- package/dist/lib/gc/index.js +410 -0
- package/dist/lib/gc/index.js.map +1 -0
- package/dist/lib/machine-config.d.ts +8 -0
- package/dist/lib/machine-config.js +37 -0
- package/dist/lib/machine-config.js.map +1 -1
- package/dist/lib/mcp/tools/action.js +1 -1
- package/dist/lib/mcp/tools/action.js.map +1 -1
- package/dist/lib/mcp/tools/ticket.js +1 -1
- package/dist/lib/mcp/tools/ticket.js.map +1 -1
- package/dist/lib/orchestrate/actions.js +30 -0
- package/dist/lib/orchestrate/actions.js.map +1 -1
- package/dist/lib/orchestrate/poller.d.ts +1 -1
- package/dist/lib/orchestrate/poller.js +4 -4
- package/dist/lib/orchestrate/poller.js.map +1 -1
- package/dist/lib/orchestrate/presets.js +2 -1
- package/dist/lib/orchestrate/presets.js.map +1 -1
- package/dist/lib/orchestrate/types.d.ts +1 -1
- package/dist/lib/orchestrate/types.js +1 -0
- package/dist/lib/orchestrate/types.js.map +1 -1
- package/dist/lib/pmo/index.js +1 -1
- package/dist/lib/pmo/index.js.map +1 -1
- package/dist/lib/pmo/markdown.js +1 -1
- package/dist/lib/pmo/markdown.js.map +1 -1
- package/dist/lib/pmo/schema.d.ts +3 -1
- package/dist/lib/pmo/schema.js +26 -2
- package/dist/lib/pmo/schema.js.map +1 -1
- package/dist/lib/pmo/storage/actions.js +1 -1
- package/dist/lib/pmo/storage/actions.js.map +1 -1
- package/dist/lib/pmo/storage/base.js +42 -18
- package/dist/lib/pmo/storage/base.js.map +1 -1
- package/dist/lib/pmo/storage/projects.js +2 -1
- package/dist/lib/pmo/storage/projects.js.map +1 -1
- package/dist/lib/pmo/storage/statuses.js +1 -1
- package/dist/lib/pmo/storage/statuses.js.map +1 -1
- package/dist/lib/pmo/storage/subtasks.js +1 -1
- package/dist/lib/pmo/storage/subtasks.js.map +1 -1
- package/dist/lib/pmo/storage/templates.js +1 -1
- package/dist/lib/pmo/storage/templates.js.map +1 -1
- package/dist/lib/pmo/storage/tickets.js +2 -1
- package/dist/lib/pmo/storage/tickets.js.map +1 -1
- package/dist/lib/pmo/storage/workflow-rules.js +1 -1
- package/dist/lib/pmo/storage/workflow-rules.js.map +1 -1
- package/dist/lib/pmo/utils.d.ts +6 -189
- package/dist/lib/pmo/utils.js +6 -306
- package/dist/lib/pmo/utils.js.map +1 -1
- package/dist/lib/providers/index.d.ts +1 -0
- package/dist/lib/providers/index.js +1 -0
- package/dist/lib/providers/index.js.map +1 -1
- package/dist/lib/providers/resolver.js +16 -0
- package/dist/lib/providers/resolver.js.map +1 -1
- package/dist/lib/providers/trello-provider.d.ts +28 -0
- package/dist/lib/providers/trello-provider.js +381 -0
- package/dist/lib/providers/trello-provider.js.map +1 -0
- package/dist/lib/session/heartbeat.d.ts +45 -0
- package/dist/lib/session/heartbeat.js +150 -0
- package/dist/lib/session/heartbeat.js.map +1 -0
- package/dist/lib/session/index.d.ts +7 -0
- package/dist/lib/session/index.js +8 -0
- package/dist/lib/session/index.js.map +1 -0
- package/dist/lib/session/watcher.d.ts +79 -0
- package/dist/lib/session/watcher.js +142 -0
- package/dist/lib/session/watcher.js.map +1 -0
- package/dist/lib/shipping/auto-merge.d.ts +57 -0
- package/dist/lib/shipping/auto-merge.js +311 -0
- package/dist/lib/shipping/auto-merge.js.map +1 -0
- package/dist/lib/shipping/github.d.ts +68 -0
- package/dist/lib/shipping/github.js +217 -0
- package/dist/lib/shipping/github.js.map +1 -0
- package/dist/lib/shipping/index.d.ts +13 -0
- package/dist/lib/shipping/index.js +11 -0
- package/dist/lib/shipping/index.js.map +1 -0
- package/dist/lib/shipping/rebase.d.ts +35 -0
- package/dist/lib/shipping/rebase.js +107 -0
- package/dist/lib/shipping/rebase.js.map +1 -0
- package/dist/lib/shipping/types.d.ts +181 -0
- package/dist/lib/shipping/types.js +9 -0
- package/dist/lib/shipping/types.js.map +1 -0
- package/dist/lib/sync/daemon-process.d.ts +9 -0
- package/dist/lib/sync/daemon-process.js +184 -0
- package/dist/lib/sync/daemon-process.js.map +1 -0
- package/dist/lib/sync/daemon.d.ts +39 -0
- package/dist/lib/sync/daemon.js +91 -0
- package/dist/lib/sync/daemon.js.map +1 -0
- package/dist/lib/sync/engine.d.ts +38 -0
- package/dist/lib/sync/engine.js +145 -0
- package/dist/lib/sync/engine.js.map +1 -0
- package/dist/lib/sync/merge-queue.d.ts +116 -0
- package/dist/lib/sync/merge-queue.js +321 -0
- package/dist/lib/sync/merge-queue.js.map +1 -0
- package/dist/lib/sync/reconciler.d.ts +44 -0
- package/dist/lib/sync/reconciler.js +88 -0
- package/dist/lib/sync/reconciler.js.map +1 -0
- package/dist/lib/trello/client.d.ts +1 -0
- package/dist/lib/trello/client.js +6 -0
- package/dist/lib/trello/client.js.map +1 -1
- package/dist/lib/utils/text.d.ts +44 -0
- package/dist/lib/utils/text.js +72 -0
- package/dist/lib/utils/text.js.map +1 -0
- package/dist/lib/work-lifecycle/post-execution.js +1 -1
- package/dist/lib/work-lifecycle/post-execution.js.map +1 -1
- package/dist/lib/work-lifecycle/settings.d.ts +138 -0
- package/dist/lib/work-lifecycle/settings.js +213 -0
- package/dist/lib/work-lifecycle/settings.js.map +1 -0
- package/oclif.manifest.json +2549 -2901
- package/package.json +8 -8
- package/dist/commands/action/create.d.ts +0 -26
- package/dist/commands/action/create.js +0 -245
- package/dist/commands/action/create.js.map +0 -1
- package/dist/commands/action/delete.d.ts +0 -18
- package/dist/commands/action/delete.js +0 -78
- package/dist/commands/action/delete.js.map +0 -1
- package/dist/commands/action/index.js +0 -103
- package/dist/commands/action/index.js.map +0 -1
- package/dist/commands/action/list.d.ts +0 -15
- package/dist/commands/action/list.js +0 -90
- package/dist/commands/action/list.js.map +0 -1
- package/dist/commands/action/run.d.ts +0 -20
- package/dist/commands/action/run.js +0 -188
- package/dist/commands/action/run.js.map +0 -1
- package/dist/commands/action/show.d.ts +0 -17
- package/dist/commands/action/show.js +0 -78
- package/dist/commands/action/show.js.map +0 -1
- package/dist/commands/action/update.d.ts +0 -27
- package/dist/commands/action/update.js +0 -288
- package/dist/commands/action/update.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../../src/lib/dashboard/html.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../../src/lib/dashboard/html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA2SD,CAAA;AACR,CAAC"}
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides:
|
|
5
5
|
* - WAL journal mode configuration
|
|
6
|
-
* -
|
|
6
|
+
* - Timestamped backup in .proletariat/backups/ (keeps last 5)
|
|
7
7
|
* - Integrity check on open with auto-recovery
|
|
8
8
|
* - Manual repair via dump/reimport
|
|
9
|
+
* - Migration of legacy scattered backup files
|
|
9
10
|
*
|
|
10
|
-
* See: PRLT-1081
|
|
11
|
+
* See: PRLT-1081, PRLT-1154
|
|
11
12
|
*/
|
|
12
13
|
import Database from 'better-sqlite3';
|
|
13
14
|
/**
|
|
@@ -16,18 +17,56 @@ import Database from 'better-sqlite3';
|
|
|
16
17
|
* more resistant to corruption than the default journal_mode=delete.
|
|
17
18
|
*/
|
|
18
19
|
export declare function enableWALMode(db: Database.Database): void;
|
|
20
|
+
/**
|
|
21
|
+
* Get the backups directory for a given database path.
|
|
22
|
+
* The backups directory is always a sibling `backups/` directory
|
|
23
|
+
* relative to the database file (e.g., `.proletariat/backups/`).
|
|
24
|
+
*/
|
|
25
|
+
export declare function getBackupsDir(dbPath: string): string;
|
|
19
26
|
/**
|
|
20
27
|
* Get the backup path for a given database path and backup number.
|
|
28
|
+
* Backups are stored in the backups/ directory with timestamps.
|
|
29
|
+
*
|
|
30
|
+
* For numbered access (used by backup restore), returns a path
|
|
31
|
+
* based on sorted backup files in the directory.
|
|
32
|
+
* Returns null if the backup doesn't exist.
|
|
21
33
|
*/
|
|
22
|
-
export declare function getBackupPath(dbPath: string, n: number): string;
|
|
34
|
+
export declare function getBackupPath(dbPath: string, n: number): string | null;
|
|
23
35
|
/**
|
|
24
|
-
* Create a
|
|
25
|
-
* Keeps the last MAX_BACKUPS copies,
|
|
26
|
-
*
|
|
36
|
+
* Create a timestamped backup of the database file in the backups/ directory.
|
|
37
|
+
* Keeps the last MAX_BACKUPS copies, deletes older ones.
|
|
38
|
+
*
|
|
39
|
+
* Returns the backup path if created, null if source didn't exist.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createRotatingBackup(dbPath: string): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Create a manual backup with an optional label.
|
|
44
|
+
* Unlike auto-backups, manual backups include a "manual" prefix.
|
|
45
|
+
* Returns the backup path.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createManualBackup(dbPath: string, label?: string): string | null;
|
|
48
|
+
/**
|
|
49
|
+
* Migrate legacy backup files from the .proletariat/ top level into backups/.
|
|
50
|
+
*
|
|
51
|
+
* Handles:
|
|
52
|
+
* - workspace.db.backup (plain backup)
|
|
53
|
+
* - workspace.db.backup-YYYYMMDD-HHMMSS (timestamped backups)
|
|
54
|
+
* - workspace.db.backup.N (numbered backups with -wal/-shm companions)
|
|
55
|
+
* - workspace.db.corrupt (corrupt file preservations)
|
|
27
56
|
*
|
|
28
|
-
*
|
|
57
|
+
* Called on first run after upgrade. Safe to call multiple times.
|
|
29
58
|
*/
|
|
30
|
-
export declare function
|
|
59
|
+
export declare function migrateExistingBackups(dbPath: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* List all backup entries with metadata, sorted newest-first.
|
|
62
|
+
* Used by the repair and backup commands to display backup info.
|
|
63
|
+
*/
|
|
64
|
+
export declare function listBackups(dbPath: string): Array<{
|
|
65
|
+
path: string;
|
|
66
|
+
filename: string;
|
|
67
|
+
size: number;
|
|
68
|
+
mtime: Date;
|
|
69
|
+
}>;
|
|
31
70
|
export interface IntegrityCheckResult {
|
|
32
71
|
ok: boolean;
|
|
33
72
|
errors: string[];
|
|
@@ -42,6 +81,28 @@ export declare function checkIntegrity(db: Database.Database): IntegrityCheckRes
|
|
|
42
81
|
* Skips checking that the contents of table rows match the indexes.
|
|
43
82
|
*/
|
|
44
83
|
export declare function quickCheckIntegrity(db: Database.Database): IntegrityCheckResult;
|
|
84
|
+
export interface SchemaCheckResult {
|
|
85
|
+
ok: boolean;
|
|
86
|
+
missingTables: string[];
|
|
87
|
+
missingColumns: Array<{
|
|
88
|
+
table: string;
|
|
89
|
+
column: string;
|
|
90
|
+
}>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check schema completeness — verify all expected columns exist in all tables.
|
|
94
|
+
*
|
|
95
|
+
* Builds the expected schema in a temporary in-memory database from the
|
|
96
|
+
* canonical CREATE TABLE definitions, then compares actual columns via
|
|
97
|
+
* PRAGMA table_info(). Only checks tables that already exist in the target
|
|
98
|
+
* database (PMO tables are skipped if PMO is not initialized).
|
|
99
|
+
*
|
|
100
|
+
* This catches the case where PRAGMA integrity_check returns "ok" but
|
|
101
|
+
* the schema is missing columns added in later migrations.
|
|
102
|
+
*
|
|
103
|
+
* See: PRLT-1131
|
|
104
|
+
*/
|
|
105
|
+
export declare function checkSchemaCompleteness(db: Database.Database): SchemaCheckResult;
|
|
45
106
|
export interface RepairResult {
|
|
46
107
|
success: boolean;
|
|
47
108
|
method: 'dump-reimport' | 'backup-restore' | 'none';
|
|
@@ -54,6 +115,6 @@ export interface RepairResult {
|
|
|
54
115
|
* 1. Try dump/reimport — opens the corrupt DB, dumps all SQL, creates a new DB
|
|
55
116
|
* 2. If dump fails, fall back to the most recent backup
|
|
56
117
|
*
|
|
57
|
-
* The original corrupt file is preserved
|
|
118
|
+
* The original corrupt file is preserved in backups/ for forensics.
|
|
58
119
|
*/
|
|
59
120
|
export declare function repairDatabase(dbPath: string): RepairResult;
|
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides:
|
|
5
5
|
* - WAL journal mode configuration
|
|
6
|
-
* -
|
|
6
|
+
* - Timestamped backup in .proletariat/backups/ (keeps last 5)
|
|
7
7
|
* - Integrity check on open with auto-recovery
|
|
8
8
|
* - Manual repair via dump/reimport
|
|
9
|
+
* - Migration of legacy scattered backup files
|
|
9
10
|
*
|
|
10
|
-
* See: PRLT-1081
|
|
11
|
+
* See: PRLT-1081, PRLT-1154
|
|
11
12
|
*/
|
|
12
13
|
import Database from 'better-sqlite3';
|
|
13
14
|
import * as fs from 'node:fs';
|
|
14
15
|
import * as path from 'node:path';
|
|
16
|
+
import { CREATE_TABLES_SQL } from './workspace-schema.js';
|
|
17
|
+
import { PMO_TABLE_SCHEMAS } from '../pmo/schema.js';
|
|
15
18
|
const MAX_BACKUPS = 5;
|
|
16
19
|
/**
|
|
17
20
|
* Enable WAL journal mode on a database connection.
|
|
@@ -21,53 +24,314 @@ const MAX_BACKUPS = 5;
|
|
|
21
24
|
export function enableWALMode(db) {
|
|
22
25
|
db.pragma('journal_mode = WAL');
|
|
23
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Get the backups directory for a given database path.
|
|
29
|
+
* The backups directory is always a sibling `backups/` directory
|
|
30
|
+
* relative to the database file (e.g., `.proletariat/backups/`).
|
|
31
|
+
*/
|
|
32
|
+
export function getBackupsDir(dbPath) {
|
|
33
|
+
return path.join(path.dirname(dbPath), 'backups');
|
|
34
|
+
}
|
|
24
35
|
/**
|
|
25
36
|
* Get the backup path for a given database path and backup number.
|
|
37
|
+
* Backups are stored in the backups/ directory with timestamps.
|
|
38
|
+
*
|
|
39
|
+
* For numbered access (used by backup restore), returns a path
|
|
40
|
+
* based on sorted backup files in the directory.
|
|
41
|
+
* Returns null if the backup doesn't exist.
|
|
26
42
|
*/
|
|
27
43
|
export function getBackupPath(dbPath, n) {
|
|
28
|
-
|
|
44
|
+
const backupsDir = getBackupsDir(dbPath);
|
|
45
|
+
if (!fs.existsSync(backupsDir)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const backups = listBackupFiles(backupsDir);
|
|
49
|
+
if (n < 1 || n > backups.length) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
// backups are sorted newest-first, so n=1 is the most recent
|
|
53
|
+
return backups[n - 1];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* List backup files in the backups directory, sorted newest-first.
|
|
57
|
+
* Only returns .db files (excludes -wal, -shm, and .corrupt files).
|
|
58
|
+
*/
|
|
59
|
+
function listBackupFiles(backupsDir) {
|
|
60
|
+
if (!fs.existsSync(backupsDir)) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const files = fs.readdirSync(backupsDir)
|
|
64
|
+
.filter(f => f.endsWith('.db') && !f.endsWith('.corrupt.db'))
|
|
65
|
+
.map(f => path.join(backupsDir, f))
|
|
66
|
+
.sort((a, b) => {
|
|
67
|
+
// Sort by filename descending (newest first).
|
|
68
|
+
// Filenames encode timestamps (workspace-YYYYMMDD-HHMMSS-mmm.db)
|
|
69
|
+
// so lexicographic order matches chronological order.
|
|
70
|
+
return path.basename(b).localeCompare(path.basename(a));
|
|
71
|
+
});
|
|
72
|
+
return files;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Generate a unique timestamped backup filename.
|
|
76
|
+
* Checks the backups directory to avoid filename collisions.
|
|
77
|
+
*/
|
|
78
|
+
function generateUniqueBackupPath(backupsDir, prefix = 'workspace') {
|
|
79
|
+
const ts = formatTimestamp(new Date());
|
|
80
|
+
const baseName = `${prefix}-${ts}`;
|
|
81
|
+
const candidate = path.join(backupsDir, `${baseName}.db`);
|
|
82
|
+
if (!fs.existsSync(candidate)) {
|
|
83
|
+
return candidate;
|
|
84
|
+
}
|
|
85
|
+
// If collision, append a counter
|
|
86
|
+
for (let i = 1; i < 100; i++) {
|
|
87
|
+
const alt = path.join(backupsDir, `${baseName}-${i}.db`);
|
|
88
|
+
if (!fs.existsSync(alt)) {
|
|
89
|
+
return alt;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Extremely unlikely fallback
|
|
93
|
+
return path.join(backupsDir, `${baseName}-${Date.now()}.db`);
|
|
29
94
|
}
|
|
30
95
|
/**
|
|
31
|
-
* Create a
|
|
32
|
-
* Keeps the last MAX_BACKUPS copies,
|
|
33
|
-
* Rotates existing backups before copying the current database.
|
|
96
|
+
* Create a timestamped backup of the database file in the backups/ directory.
|
|
97
|
+
* Keeps the last MAX_BACKUPS copies, deletes older ones.
|
|
34
98
|
*
|
|
35
|
-
* Returns
|
|
99
|
+
* Returns the backup path if created, null if source didn't exist.
|
|
36
100
|
*/
|
|
37
101
|
export function createRotatingBackup(dbPath) {
|
|
38
102
|
if (!fs.existsSync(dbPath)) {
|
|
39
|
-
return
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const backupsDir = getBackupsDir(dbPath);
|
|
106
|
+
fs.mkdirSync(backupsDir, { recursive: true });
|
|
107
|
+
const backupPath = generateUniqueBackupPath(backupsDir);
|
|
108
|
+
// Copy current database as timestamped backup
|
|
109
|
+
try {
|
|
110
|
+
fs.copyFileSync(dbPath, backupPath);
|
|
111
|
+
// Also copy WAL and SHM files if they exist (for WAL-mode databases)
|
|
112
|
+
const walPath = `${dbPath}-wal`;
|
|
113
|
+
const shmPath = `${dbPath}-shm`;
|
|
114
|
+
if (fs.existsSync(walPath)) {
|
|
115
|
+
fs.copyFileSync(walPath, `${backupPath}-wal`);
|
|
116
|
+
}
|
|
117
|
+
if (fs.existsSync(shmPath)) {
|
|
118
|
+
fs.copyFileSync(shmPath, `${backupPath}-shm`);
|
|
119
|
+
}
|
|
40
120
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
fs.unlinkSync(oldest);
|
|
121
|
+
catch {
|
|
122
|
+
// Backup failure is not fatal — log and continue
|
|
123
|
+
return null;
|
|
45
124
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
125
|
+
// Rotate: delete older backups beyond MAX_BACKUPS
|
|
126
|
+
rotateBackups(backupsDir);
|
|
127
|
+
return backupPath;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Delete older backups beyond MAX_BACKUPS.
|
|
131
|
+
* Keeps the newest MAX_BACKUPS .db files, deletes the rest
|
|
132
|
+
* (along with their -wal and -shm companions).
|
|
133
|
+
*/
|
|
134
|
+
function rotateBackups(backupsDir) {
|
|
135
|
+
const backups = listBackupFiles(backupsDir);
|
|
136
|
+
// Keep the first MAX_BACKUPS, delete the rest
|
|
137
|
+
for (let i = MAX_BACKUPS; i < backups.length; i++) {
|
|
138
|
+
const bp = backups[i];
|
|
139
|
+
try {
|
|
140
|
+
fs.unlinkSync(bp);
|
|
141
|
+
// Also clean up WAL and SHM companions
|
|
142
|
+
for (const suffix of ['-wal', '-shm']) {
|
|
143
|
+
const companion = `${bp}${suffix}`;
|
|
144
|
+
if (fs.existsSync(companion)) {
|
|
145
|
+
fs.unlinkSync(companion);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Best-effort cleanup
|
|
51
151
|
}
|
|
52
152
|
}
|
|
53
|
-
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Create a manual backup with an optional label.
|
|
156
|
+
* Unlike auto-backups, manual backups include a "manual" prefix.
|
|
157
|
+
* Returns the backup path.
|
|
158
|
+
*/
|
|
159
|
+
export function createManualBackup(dbPath, label) {
|
|
160
|
+
if (!fs.existsSync(dbPath)) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const backupsDir = getBackupsDir(dbPath);
|
|
164
|
+
fs.mkdirSync(backupsDir, { recursive: true });
|
|
165
|
+
const suffix = label ? `-${label}` : '';
|
|
166
|
+
const backupPath = generateUniqueBackupPath(backupsDir, `workspace-manual${suffix}`);
|
|
54
167
|
try {
|
|
55
|
-
fs.copyFileSync(dbPath,
|
|
56
|
-
// Also copy WAL and SHM files if they exist (for WAL-mode databases)
|
|
168
|
+
fs.copyFileSync(dbPath, backupPath);
|
|
57
169
|
const walPath = `${dbPath}-wal`;
|
|
58
170
|
const shmPath = `${dbPath}-shm`;
|
|
59
171
|
if (fs.existsSync(walPath)) {
|
|
60
|
-
fs.copyFileSync(walPath, `${
|
|
172
|
+
fs.copyFileSync(walPath, `${backupPath}-wal`);
|
|
61
173
|
}
|
|
62
174
|
if (fs.existsSync(shmPath)) {
|
|
63
|
-
fs.copyFileSync(shmPath, `${
|
|
175
|
+
fs.copyFileSync(shmPath, `${backupPath}-shm`);
|
|
64
176
|
}
|
|
65
|
-
return
|
|
177
|
+
return backupPath;
|
|
66
178
|
}
|
|
67
179
|
catch {
|
|
68
|
-
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Migrate legacy backup files from the .proletariat/ top level into backups/.
|
|
185
|
+
*
|
|
186
|
+
* Handles:
|
|
187
|
+
* - workspace.db.backup (plain backup)
|
|
188
|
+
* - workspace.db.backup-YYYYMMDD-HHMMSS (timestamped backups)
|
|
189
|
+
* - workspace.db.backup.N (numbered backups with -wal/-shm companions)
|
|
190
|
+
* - workspace.db.corrupt (corrupt file preservations)
|
|
191
|
+
*
|
|
192
|
+
* Called on first run after upgrade. Safe to call multiple times.
|
|
193
|
+
*/
|
|
194
|
+
export function migrateExistingBackups(dbPath) {
|
|
195
|
+
const proletariatDir = path.dirname(dbPath);
|
|
196
|
+
const backupsDir = getBackupsDir(dbPath);
|
|
197
|
+
const dbBasename = path.basename(dbPath); // workspace.db
|
|
198
|
+
let files;
|
|
199
|
+
try {
|
|
200
|
+
files = fs.readdirSync(proletariatDir);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Find legacy backup/corrupt files at the top level
|
|
206
|
+
const legacyFiles = files.filter(f => {
|
|
207
|
+
// Match workspace.db.backup*, workspace.db.corrupt*
|
|
208
|
+
if (f.startsWith(`${dbBasename}.backup`) || f.startsWith(`${dbBasename}.corrupt`)) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
// Match workspace.db.repair-temp (leftover from failed repairs)
|
|
212
|
+
if (f === `${dbBasename}.repair-temp`) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
69
215
|
return false;
|
|
216
|
+
});
|
|
217
|
+
if (legacyFiles.length === 0) {
|
|
218
|
+
return;
|
|
70
219
|
}
|
|
220
|
+
// Ensure backups directory exists
|
|
221
|
+
fs.mkdirSync(backupsDir, { recursive: true });
|
|
222
|
+
for (const file of legacyFiles) {
|
|
223
|
+
const srcPath = path.join(proletariatDir, file);
|
|
224
|
+
let destName;
|
|
225
|
+
if (file.endsWith('.corrupt') || file.includes('.corrupt')) {
|
|
226
|
+
// Corrupt files → workspace-corrupt-TIMESTAMP.db or just move as-is
|
|
227
|
+
const stat = fs.statSync(srcPath);
|
|
228
|
+
const ts = formatTimestamp(stat.mtime);
|
|
229
|
+
destName = `workspace-corrupt-${ts}.db`;
|
|
230
|
+
}
|
|
231
|
+
else if (file === `${dbBasename}.backup`) {
|
|
232
|
+
// Plain backup → workspace-migrated-TIMESTAMP.db
|
|
233
|
+
const stat = fs.statSync(srcPath);
|
|
234
|
+
const ts = formatTimestamp(stat.mtime);
|
|
235
|
+
destName = `workspace-migrated-${ts}.db`;
|
|
236
|
+
}
|
|
237
|
+
else if (file.match(/\.backup-\d{8}-\d{6}$/)) {
|
|
238
|
+
// Timestamped backup (workspace.db.backup-20260106-112422)
|
|
239
|
+
const match = file.match(/\.backup-(\d{8}-\d{6})$/);
|
|
240
|
+
destName = `workspace-migrated-${match[1]}.db`;
|
|
241
|
+
}
|
|
242
|
+
else if (file.match(/\.backup\.\d+$/)) {
|
|
243
|
+
// Numbered backup (workspace.db.backup.1)
|
|
244
|
+
const stat = fs.statSync(srcPath);
|
|
245
|
+
const ts = formatTimestamp(stat.mtime);
|
|
246
|
+
destName = `workspace-migrated-${ts}.db`;
|
|
247
|
+
}
|
|
248
|
+
else if (file.match(/\.backup\.\d+-(wal|shm)$/)) {
|
|
249
|
+
// WAL/SHM companion for numbered backup — these follow their .db file
|
|
250
|
+
// Skip them here; they'll be handled when we move the main backup
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
else if (file === `${dbBasename}.repair-temp`) {
|
|
254
|
+
// Leftover repair temp file — just move it
|
|
255
|
+
destName = 'workspace-repair-temp.db';
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// Fallback: use file modification time
|
|
259
|
+
const stat = fs.statSync(srcPath);
|
|
260
|
+
const ts = formatTimestamp(stat.mtime);
|
|
261
|
+
destName = `workspace-legacy-${ts}.db`;
|
|
262
|
+
}
|
|
263
|
+
const destPath = path.join(backupsDir, destName);
|
|
264
|
+
// Don't overwrite existing files in backups/
|
|
265
|
+
if (fs.existsSync(destPath)) {
|
|
266
|
+
// Add a suffix to avoid collision
|
|
267
|
+
const ext = path.extname(destName);
|
|
268
|
+
const base = destName.slice(0, -ext.length);
|
|
269
|
+
const uniqueDest = path.join(backupsDir, `${base}-${Date.now()}${ext}`);
|
|
270
|
+
try {
|
|
271
|
+
fs.renameSync(srcPath, uniqueDest);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// Best-effort migration
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
try {
|
|
279
|
+
fs.renameSync(srcPath, destPath);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
// Best-effort migration
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Also move WAL/SHM companions if they exist
|
|
286
|
+
for (const suffix of ['-wal', '-shm']) {
|
|
287
|
+
const companionSrc = `${srcPath}${suffix}`;
|
|
288
|
+
if (fs.existsSync(companionSrc)) {
|
|
289
|
+
const companionDest = `${destPath}${suffix}`;
|
|
290
|
+
try {
|
|
291
|
+
fs.renameSync(companionSrc, companionDest);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
// Best-effort
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// After migration, rotate to keep only MAX_BACKUPS
|
|
300
|
+
rotateBackups(backupsDir);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Format a Date as YYYYMMDD-HHMMSS-mmm for backup filenames.
|
|
304
|
+
* Includes milliseconds to avoid collisions when called multiple times per second.
|
|
305
|
+
*/
|
|
306
|
+
function formatTimestamp(date) {
|
|
307
|
+
return [
|
|
308
|
+
date.getFullYear(),
|
|
309
|
+
String(date.getMonth() + 1).padStart(2, '0'),
|
|
310
|
+
String(date.getDate()).padStart(2, '0'),
|
|
311
|
+
'-',
|
|
312
|
+
String(date.getHours()).padStart(2, '0'),
|
|
313
|
+
String(date.getMinutes()).padStart(2, '0'),
|
|
314
|
+
String(date.getSeconds()).padStart(2, '0'),
|
|
315
|
+
'-',
|
|
316
|
+
String(date.getMilliseconds()).padStart(3, '0'),
|
|
317
|
+
].join('');
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* List all backup entries with metadata, sorted newest-first.
|
|
321
|
+
* Used by the repair and backup commands to display backup info.
|
|
322
|
+
*/
|
|
323
|
+
export function listBackups(dbPath) {
|
|
324
|
+
const backupsDir = getBackupsDir(dbPath);
|
|
325
|
+
const backups = listBackupFiles(backupsDir);
|
|
326
|
+
return backups.map(bp => {
|
|
327
|
+
const stat = fs.statSync(bp);
|
|
328
|
+
return {
|
|
329
|
+
path: bp,
|
|
330
|
+
filename: path.basename(bp),
|
|
331
|
+
size: stat.size,
|
|
332
|
+
mtime: stat.mtime,
|
|
333
|
+
};
|
|
334
|
+
});
|
|
71
335
|
}
|
|
72
336
|
/**
|
|
73
337
|
* Run PRAGMA integrity_check on a database.
|
|
@@ -107,6 +371,91 @@ export function quickCheckIntegrity(db) {
|
|
|
107
371
|
};
|
|
108
372
|
}
|
|
109
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Check schema completeness — verify all expected columns exist in all tables.
|
|
376
|
+
*
|
|
377
|
+
* Builds the expected schema in a temporary in-memory database from the
|
|
378
|
+
* canonical CREATE TABLE definitions, then compares actual columns via
|
|
379
|
+
* PRAGMA table_info(). Only checks tables that already exist in the target
|
|
380
|
+
* database (PMO tables are skipped if PMO is not initialized).
|
|
381
|
+
*
|
|
382
|
+
* This catches the case where PRAGMA integrity_check returns "ok" but
|
|
383
|
+
* the schema is missing columns added in later migrations.
|
|
384
|
+
*
|
|
385
|
+
* See: PRLT-1131
|
|
386
|
+
*/
|
|
387
|
+
export function checkSchemaCompleteness(db) {
|
|
388
|
+
let expected = null;
|
|
389
|
+
try {
|
|
390
|
+
// Build expected schema in a temporary in-memory database
|
|
391
|
+
expected = new Database(':memory:');
|
|
392
|
+
expected.exec(CREATE_TABLES_SQL);
|
|
393
|
+
// Only check PMO tables if PMO is initialized in the actual database
|
|
394
|
+
const hasPMO = !!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='pmo_projects'").get();
|
|
395
|
+
if (hasPMO) {
|
|
396
|
+
for (const sql of Object.values(PMO_TABLE_SCHEMAS)) {
|
|
397
|
+
try {
|
|
398
|
+
expected.exec(sql);
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
// Some PMO tables may have FK references that fail in the
|
|
402
|
+
// isolated in-memory DB — skip those; the column check on
|
|
403
|
+
// tables that DO exist is still valuable.
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Get all expected tables from the reference database
|
|
408
|
+
const expectedTables = expected.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all();
|
|
409
|
+
const missingTables = [];
|
|
410
|
+
const missingColumns = [];
|
|
411
|
+
for (const { name: table } of expectedTables) {
|
|
412
|
+
// Check if table exists in actual database
|
|
413
|
+
const actualTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table);
|
|
414
|
+
if (!actualTable) {
|
|
415
|
+
// Only report core workspace tables as missing.
|
|
416
|
+
// PMO tables may legitimately not exist if PMO was partially initialized.
|
|
417
|
+
const coreWorkspaceTables = new Set([
|
|
418
|
+
'workspace', 'repositories', 'agents', 'agent_themes',
|
|
419
|
+
'agent_theme_names', 'agent_worktrees', 'workspace_settings', 'media_items',
|
|
420
|
+
]);
|
|
421
|
+
if (coreWorkspaceTables.has(table)) {
|
|
422
|
+
missingTables.push(table);
|
|
423
|
+
}
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
// Compare columns
|
|
427
|
+
const expectedCols = expected.prepare(`PRAGMA table_info('${table}')`).all();
|
|
428
|
+
const actualCols = db.prepare(`PRAGMA table_info('${table}')`).all();
|
|
429
|
+
const actualColSet = new Set(actualCols.map(c => c.name));
|
|
430
|
+
for (const { name: col } of expectedCols) {
|
|
431
|
+
if (!actualColSet.has(col)) {
|
|
432
|
+
missingColumns.push({ table, column: col });
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
expected.close();
|
|
437
|
+
expected = null;
|
|
438
|
+
return {
|
|
439
|
+
ok: missingTables.length === 0 && missingColumns.length === 0,
|
|
440
|
+
missingTables,
|
|
441
|
+
missingColumns,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
if (expected) {
|
|
446
|
+
try {
|
|
447
|
+
expected.close();
|
|
448
|
+
}
|
|
449
|
+
catch { /* cleanup — safe to ignore */ }
|
|
450
|
+
}
|
|
451
|
+
// If schema check itself fails, report as a single error
|
|
452
|
+
return {
|
|
453
|
+
ok: false,
|
|
454
|
+
missingTables: [],
|
|
455
|
+
missingColumns: [{ table: '(unknown)', column: error instanceof Error ? error.message : String(error) }],
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
110
459
|
/**
|
|
111
460
|
* Attempt to repair a corrupted database.
|
|
112
461
|
*
|
|
@@ -114,7 +463,7 @@ export function quickCheckIntegrity(db) {
|
|
|
114
463
|
* 1. Try dump/reimport — opens the corrupt DB, dumps all SQL, creates a new DB
|
|
115
464
|
* 2. If dump fails, fall back to the most recent backup
|
|
116
465
|
*
|
|
117
|
-
* The original corrupt file is preserved
|
|
466
|
+
* The original corrupt file is preserved in backups/ for forensics.
|
|
118
467
|
*/
|
|
119
468
|
export function repairDatabase(dbPath) {
|
|
120
469
|
// Try dump/reimport first
|
|
@@ -133,6 +482,17 @@ export function repairDatabase(dbPath) {
|
|
|
133
482
|
message: `Could not repair database. Dump failed: ${dumpResult.message}. No usable backups found.`,
|
|
134
483
|
};
|
|
135
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* Move a corrupt database file to the backups/ directory with a corrupt prefix.
|
|
487
|
+
* Returns the destination path.
|
|
488
|
+
*/
|
|
489
|
+
function moveCorruptToBackups(dbPath) {
|
|
490
|
+
const backupsDir = getBackupsDir(dbPath);
|
|
491
|
+
fs.mkdirSync(backupsDir, { recursive: true });
|
|
492
|
+
const corruptPath = generateUniqueBackupPath(backupsDir, 'workspace-corrupt');
|
|
493
|
+
fs.renameSync(dbPath, corruptPath);
|
|
494
|
+
return corruptPath;
|
|
495
|
+
}
|
|
136
496
|
/**
|
|
137
497
|
* Attempt recovery via .dump and reimport.
|
|
138
498
|
* Opens the corrupt database, extracts as much SQL as possible,
|
|
@@ -189,12 +549,9 @@ function attemptDumpReimport(dbPath) {
|
|
|
189
549
|
corruptDb = null;
|
|
190
550
|
newDb.close();
|
|
191
551
|
newDb = null;
|
|
192
|
-
//
|
|
193
|
-
const corruptBackupPath =
|
|
194
|
-
|
|
195
|
-
fs.unlinkSync(corruptBackupPath);
|
|
196
|
-
}
|
|
197
|
-
fs.renameSync(dbPath, corruptBackupPath);
|
|
552
|
+
// Move corrupt file to backups/ directory
|
|
553
|
+
const corruptBackupPath = moveCorruptToBackups(dbPath);
|
|
554
|
+
// Move repaired database into place
|
|
198
555
|
fs.renameSync(tempPath, dbPath);
|
|
199
556
|
// Clean up old WAL/SHM files from the corrupt database
|
|
200
557
|
for (const suffix of ['-wal', '-shm']) {
|
|
@@ -238,14 +595,13 @@ function attemptDumpReimport(dbPath) {
|
|
|
238
595
|
}
|
|
239
596
|
/**
|
|
240
597
|
* Attempt recovery by restoring from the most recent valid backup.
|
|
241
|
-
* Tries backups
|
|
598
|
+
* Tries backups from newest to oldest, validates each with integrity_check.
|
|
242
599
|
*/
|
|
243
600
|
function attemptBackupRestore(dbPath) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
601
|
+
const backupsDir = getBackupsDir(dbPath);
|
|
602
|
+
const backups = listBackupFiles(backupsDir);
|
|
603
|
+
for (let i = 0; i < backups.length; i++) {
|
|
604
|
+
const backupPath = backups[i];
|
|
249
605
|
// Validate the backup
|
|
250
606
|
let backupDb = null;
|
|
251
607
|
try {
|
|
@@ -257,11 +613,7 @@ function attemptBackupRestore(dbPath) {
|
|
|
257
613
|
continue;
|
|
258
614
|
}
|
|
259
615
|
// Backup is valid — swap it in
|
|
260
|
-
const corruptBackupPath =
|
|
261
|
-
if (fs.existsSync(corruptBackupPath)) {
|
|
262
|
-
fs.unlinkSync(corruptBackupPath);
|
|
263
|
-
}
|
|
264
|
-
fs.renameSync(dbPath, corruptBackupPath);
|
|
616
|
+
const corruptBackupPath = moveCorruptToBackups(dbPath);
|
|
265
617
|
fs.copyFileSync(backupPath, dbPath);
|
|
266
618
|
// Clean up old WAL/SHM files
|
|
267
619
|
for (const suffix of ['-wal', '-shm']) {
|
|
@@ -273,7 +625,7 @@ function attemptBackupRestore(dbPath) {
|
|
|
273
625
|
return {
|
|
274
626
|
success: true,
|
|
275
627
|
method: 'backup-restore',
|
|
276
|
-
message: `Restored from
|
|
628
|
+
message: `Restored from ${path.basename(backupPath)}. Corrupt file saved as ${path.basename(corruptBackupPath)}.`,
|
|
277
629
|
};
|
|
278
630
|
}
|
|
279
631
|
catch {
|