@bosun-sh/logbook 1.1.0 → 2.0.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/CHANGELOG.md +139 -0
- package/README.md +248 -318
- package/bin/logbook.cjs +18 -0
- package/dist/context/attachments.d.ts +55 -0
- package/dist/context/attachments.d.ts.map +1 -0
- package/dist/context/attachments.js +329 -0
- package/dist/context/attachments.js.map +1 -0
- package/dist/context/create.d.ts +31 -0
- package/dist/context/create.d.ts.map +1 -0
- package/dist/context/create.js +101 -0
- package/dist/context/create.js.map +1 -0
- package/dist/context/delete.d.ts +20 -0
- package/dist/context/delete.d.ts.map +1 -0
- package/dist/context/delete.js +55 -0
- package/dist/context/delete.js.map +1 -0
- package/dist/context/get.d.ts +20 -0
- package/dist/context/get.d.ts.map +1 -0
- package/dist/context/get.js +55 -0
- package/dist/context/get.js.map +1 -0
- package/dist/context/list.d.ts +30 -0
- package/dist/context/list.d.ts.map +1 -0
- package/dist/context/list.js +172 -0
- package/dist/context/list.js.map +1 -0
- package/dist/context/schema.d.ts +156 -0
- package/dist/context/schema.d.ts.map +1 -0
- package/dist/context/schema.js +34 -0
- package/dist/context/schema.js.map +1 -0
- package/dist/context/search.d.ts +27 -0
- package/dist/context/search.d.ts.map +1 -0
- package/dist/context/search.js +266 -0
- package/dist/context/search.js.map +1 -0
- package/dist/context/topics.d.ts +4 -0
- package/dist/context/topics.d.ts.map +1 -0
- package/dist/context/topics.js +54 -0
- package/dist/context/topics.js.map +1 -0
- package/dist/context/update.d.ts +29 -0
- package/dist/context/update.d.ts.map +1 -0
- package/dist/context/update.js +134 -0
- package/dist/context/update.js.map +1 -0
- package/dist/epic/cascade-delete.d.ts +30 -0
- package/dist/epic/cascade-delete.d.ts.map +1 -0
- package/dist/epic/cascade-delete.js +173 -0
- package/dist/epic/cascade-delete.js.map +1 -0
- package/dist/epic/create.d.ts +24 -0
- package/dist/epic/create.d.ts.map +1 -0
- package/dist/epic/create.js +54 -0
- package/dist/epic/create.js.map +1 -0
- package/dist/epic/delete.d.ts +21 -0
- package/dist/epic/delete.d.ts.map +1 -0
- package/dist/epic/delete.js +42 -0
- package/dist/epic/delete.js.map +1 -0
- package/dist/epic/get.d.ts +19 -0
- package/dist/epic/get.d.ts.map +1 -0
- package/dist/epic/get.js +31 -0
- package/dist/epic/get.js.map +1 -0
- package/dist/epic/list.d.ts +25 -0
- package/dist/epic/list.d.ts.map +1 -0
- package/dist/epic/list.js +114 -0
- package/dist/epic/list.js.map +1 -0
- package/dist/epic/rules.d.ts +34 -0
- package/dist/epic/rules.d.ts.map +1 -0
- package/dist/epic/rules.js +127 -0
- package/dist/epic/rules.js.map +1 -0
- package/dist/epic/schema.d.ts +85 -0
- package/dist/epic/schema.d.ts.map +1 -0
- package/dist/epic/schema.js +14 -0
- package/dist/epic/schema.js.map +1 -0
- package/dist/epic/update.d.ts +25 -0
- package/dist/epic/update.d.ts.map +1 -0
- package/dist/epic/update.js +69 -0
- package/dist/epic/update.js.map +1 -0
- package/dist/hook/list.d.ts +71 -0
- package/dist/hook/list.d.ts.map +1 -0
- package/dist/hook/list.js +364 -0
- package/dist/hook/list.js.map +1 -0
- package/dist/hook/ports.d.ts +16 -0
- package/dist/hook/ports.d.ts.map +1 -0
- package/dist/hook/ports.js +3 -0
- package/dist/hook/ports.js.map +1 -0
- package/dist/hook/run.d.ts +24 -0
- package/dist/hook/run.d.ts.map +1 -0
- package/dist/hook/run.js +185 -0
- package/dist/hook/run.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/hook-tools.d.ts +8 -0
- package/dist/plugin/hook-tools.d.ts.map +1 -0
- package/dist/plugin/hook-tools.js +31 -0
- package/dist/plugin/hook-tools.js.map +1 -0
- package/dist/plugin/linear-pull-tool.d.ts +20 -0
- package/dist/plugin/linear-pull-tool.d.ts.map +1 -0
- package/dist/plugin/linear-pull-tool.js +19 -0
- package/dist/plugin/linear-pull-tool.js.map +1 -0
- package/dist/plugin/linear-push-tool.d.ts +20 -0
- package/dist/plugin/linear-push-tool.d.ts.map +1 -0
- package/dist/plugin/linear-push-tool.js +27 -0
- package/dist/plugin/linear-push-tool.js.map +1 -0
- package/dist/plugin/linear-setup-tool.d.ts +2 -0
- package/dist/plugin/linear-setup-tool.d.ts.map +1 -0
- package/dist/plugin/linear-setup-tool.js +22 -0
- package/dist/plugin/linear-setup-tool.js.map +1 -0
- package/dist/plugin/linear-status-tool.d.ts +10 -0
- package/dist/plugin/linear-status-tool.d.ts.map +1 -0
- package/dist/plugin/linear-status-tool.js +10 -0
- package/dist/plugin/linear-status-tool.js.map +1 -0
- package/dist/plugin/list.d.ts +15 -0
- package/dist/plugin/list.d.ts.map +1 -0
- package/dist/plugin/list.js +87 -0
- package/dist/plugin/list.js.map +1 -0
- package/dist/plugin/public-schemas.d.ts +42 -0
- package/dist/plugin/public-schemas.d.ts.map +1 -0
- package/dist/plugin/public-schemas.js +577 -0
- package/dist/plugin/public-schemas.js.map +1 -0
- package/dist/plugin/registry.d.ts +3 -0
- package/dist/plugin/registry.d.ts.map +1 -0
- package/dist/plugin/registry.js +3 -0
- package/dist/plugin/registry.js.map +1 -0
- package/dist/plugin/results.d.ts +8 -0
- package/dist/plugin/results.d.ts.map +1 -0
- package/dist/plugin/results.js +114 -0
- package/dist/plugin/results.js.map +1 -0
- package/dist/plugin/sync-conflict-tools.d.ts +2 -0
- package/dist/plugin/sync-conflict-tools.d.ts.map +1 -0
- package/dist/plugin/sync-conflict-tools.js +5 -0
- package/dist/plugin/sync-conflict-tools.js.map +1 -0
- package/dist/plugin/tool-registry.d.ts +23 -0
- package/dist/plugin/tool-registry.d.ts.map +1 -0
- package/dist/plugin/tool-registry.js +251 -0
- package/dist/plugin/tool-registry.js.map +1 -0
- package/dist/plugin/workspace-init-tool.d.ts +2 -0
- package/dist/plugin/workspace-init-tool.d.ts.map +1 -0
- package/dist/plugin/workspace-init-tool.js +16 -0
- package/dist/plugin/workspace-init-tool.js.map +1 -0
- package/dist/plugin/workspace-status-tool.d.ts +2 -0
- package/dist/plugin/workspace-status-tool.d.ts.map +1 -0
- package/dist/plugin/workspace-status-tool.js +15 -0
- package/dist/plugin/workspace-status-tool.js.map +1 -0
- package/dist/shared/ids.d.ts +3 -0
- package/dist/shared/ids.d.ts.map +1 -0
- package/dist/shared/ids.js +15 -0
- package/dist/shared/ids.js.map +1 -0
- package/dist/shared/pagination.d.ts +19 -0
- package/dist/shared/pagination.d.ts.map +1 -0
- package/dist/shared/pagination.js +110 -0
- package/dist/shared/pagination.js.map +1 -0
- package/dist/shared/result.d.ts +20 -0
- package/dist/shared/result.d.ts.map +1 -0
- package/dist/shared/result.js +6 -0
- package/dist/shared/result.js.map +1 -0
- package/dist/shared/schema/value-objects.d.ts +363 -0
- package/dist/shared/schema/value-objects.d.ts.map +1 -0
- package/dist/shared/schema/value-objects.js +112 -0
- package/dist/shared/schema/value-objects.js.map +1 -0
- package/dist/shared/storage/atomic-write.d.ts +25 -0
- package/dist/shared/storage/atomic-write.d.ts.map +1 -0
- package/dist/shared/storage/atomic-write.js +71 -0
- package/dist/shared/storage/atomic-write.js.map +1 -0
- package/dist/shared/storage/jsonl-repository.d.ts +85 -0
- package/dist/shared/storage/jsonl-repository.d.ts.map +1 -0
- package/dist/shared/storage/jsonl-repository.js +278 -0
- package/dist/shared/storage/jsonl-repository.js.map +1 -0
- package/dist/shared/storage/transaction.d.ts +3 -0
- package/dist/shared/storage/transaction.d.ts.map +1 -0
- package/dist/shared/storage/transaction.js +22 -0
- package/dist/shared/storage/transaction.js.map +1 -0
- package/dist/shared/time.d.ts +3 -0
- package/dist/shared/time.d.ts.map +1 -0
- package/dist/shared/time.js +3 -0
- package/dist/shared/time.js.map +1 -0
- package/dist/story/cascade-delete.d.ts +22 -0
- package/dist/story/cascade-delete.d.ts.map +1 -0
- package/dist/story/cascade-delete.js +117 -0
- package/dist/story/cascade-delete.js.map +1 -0
- package/dist/story/create.d.ts +30 -0
- package/dist/story/create.d.ts.map +1 -0
- package/dist/story/create.js +69 -0
- package/dist/story/create.js.map +1 -0
- package/dist/story/delete.d.ts +21 -0
- package/dist/story/delete.d.ts.map +1 -0
- package/dist/story/delete.js +42 -0
- package/dist/story/delete.js.map +1 -0
- package/dist/story/get.d.ts +19 -0
- package/dist/story/get.d.ts.map +1 -0
- package/dist/story/get.js +31 -0
- package/dist/story/get.js.map +1 -0
- package/dist/story/hierarchy.d.ts +20 -0
- package/dist/story/hierarchy.d.ts.map +1 -0
- package/dist/story/hierarchy.js +30 -0
- package/dist/story/hierarchy.js.map +1 -0
- package/dist/story/list.d.ts +25 -0
- package/dist/story/list.d.ts.map +1 -0
- package/dist/story/list.js +118 -0
- package/dist/story/list.js.map +1 -0
- package/dist/story/rules.d.ts +34 -0
- package/dist/story/rules.d.ts.map +1 -0
- package/dist/story/rules.js +100 -0
- package/dist/story/rules.js.map +1 -0
- package/dist/story/schema.d.ts +65 -0
- package/dist/story/schema.d.ts.map +1 -0
- package/dist/story/schema.js +14 -0
- package/dist/story/schema.js.map +1 -0
- package/dist/story/update.d.ts +23 -0
- package/dist/story/update.d.ts.map +1 -0
- package/dist/story/update.js +67 -0
- package/dist/story/update.js.map +1 -0
- package/dist/sync/base-snapshot.d.ts +38 -0
- package/dist/sync/base-snapshot.d.ts.map +1 -0
- package/dist/sync/base-snapshot.js +86 -0
- package/dist/sync/base-snapshot.js.map +1 -0
- package/dist/sync/conflict-tools.d.ts +75 -0
- package/dist/sync/conflict-tools.d.ts.map +1 -0
- package/dist/sync/conflict-tools.js +53 -0
- package/dist/sync/conflict-tools.js.map +1 -0
- package/dist/sync/conflicts.d.ts +201 -0
- package/dist/sync/conflicts.d.ts.map +1 -0
- package/dist/sync/conflicts.js +526 -0
- package/dist/sync/conflicts.js.map +1 -0
- package/dist/sync/deferred-providers.d.ts +2 -0
- package/dist/sync/deferred-providers.d.ts.map +1 -0
- package/dist/sync/deferred-providers.js +6 -0
- package/dist/sync/deferred-providers.js.map +1 -0
- package/dist/sync/events.d.ts +401 -0
- package/dist/sync/events.d.ts.map +1 -0
- package/dist/sync/events.js +357 -0
- package/dist/sync/events.js.map +1 -0
- package/dist/sync/external-links.d.ts +154 -0
- package/dist/sync/external-links.d.ts.map +1 -0
- package/dist/sync/external-links.js +306 -0
- package/dist/sync/external-links.js.map +1 -0
- package/dist/sync/linear/config.d.ts +60 -0
- package/dist/sync/linear/config.d.ts.map +1 -0
- package/dist/sync/linear/config.js +302 -0
- package/dist/sync/linear/config.js.map +1 -0
- package/dist/sync/linear/mapping.d.ts +115 -0
- package/dist/sync/linear/mapping.d.ts.map +1 -0
- package/dist/sync/linear/mapping.js +159 -0
- package/dist/sync/linear/mapping.js.map +1 -0
- package/dist/sync/linear/pull.d.ts +33 -0
- package/dist/sync/linear/pull.d.ts.map +1 -0
- package/dist/sync/linear/pull.js +376 -0
- package/dist/sync/linear/pull.js.map +1 -0
- package/dist/sync/linear/push.d.ts +34 -0
- package/dist/sync/linear/push.d.ts.map +1 -0
- package/dist/sync/linear/push.js +681 -0
- package/dist/sync/linear/push.js.map +1 -0
- package/dist/sync/linear/setup.d.ts +33 -0
- package/dist/sync/linear/setup.d.ts.map +1 -0
- package/dist/sync/linear/setup.js +129 -0
- package/dist/sync/linear/setup.js.map +1 -0
- package/dist/sync/linear/status.d.ts +35 -0
- package/dist/sync/linear/status.d.ts.map +1 -0
- package/dist/sync/linear/status.js +138 -0
- package/dist/sync/linear/status.js.map +1 -0
- package/dist/sync/linear/transport.d.ts +47 -0
- package/dist/sync/linear/transport.d.ts.map +1 -0
- package/dist/sync/linear/transport.js +249 -0
- package/dist/sync/linear/transport.js.map +1 -0
- package/dist/sync/provider-port.d.ts +81 -0
- package/dist/sync/provider-port.d.ts.map +1 -0
- package/dist/sync/provider-port.js +16 -0
- package/dist/sync/provider-port.js.map +1 -0
- package/dist/sync/provider-registry.d.ts +38 -0
- package/dist/sync/provider-registry.d.ts.map +1 -0
- package/dist/sync/provider-registry.js +115 -0
- package/dist/sync/provider-registry.js.map +1 -0
- package/dist/sync/schema.d.ts +147 -0
- package/dist/sync/schema.d.ts.map +1 -0
- package/dist/sync/schema.js +28 -0
- package/dist/sync/schema.js.map +1 -0
- package/dist/task/comments.d.ts +9 -0
- package/dist/task/comments.d.ts.map +1 -0
- package/dist/task/comments.js +79 -0
- package/dist/task/comments.js.map +1 -0
- package/dist/task/create.d.ts +34 -0
- package/dist/task/create.d.ts.map +1 -0
- package/dist/task/create.js +126 -0
- package/dist/task/create.js.map +1 -0
- package/dist/task/current.d.ts +18 -0
- package/dist/task/current.d.ts.map +1 -0
- package/dist/task/current.js +105 -0
- package/dist/task/current.js.map +1 -0
- package/dist/task/edit.d.ts +22 -0
- package/dist/task/edit.d.ts.map +1 -0
- package/dist/task/edit.js +105 -0
- package/dist/task/edit.js.map +1 -0
- package/dist/task/estimate.d.ts +20 -0
- package/dist/task/estimate.d.ts.map +1 -0
- package/dist/task/estimate.js +141 -0
- package/dist/task/estimate.js.map +1 -0
- package/dist/task/get.d.ts +13 -0
- package/dist/task/get.d.ts.map +1 -0
- package/dist/task/get.js +29 -0
- package/dist/task/get.js.map +1 -0
- package/dist/task/hierarchy.d.ts +18 -0
- package/dist/task/hierarchy.d.ts.map +1 -0
- package/dist/task/hierarchy.js +56 -0
- package/dist/task/hierarchy.js.map +1 -0
- package/dist/task/lifecycle.d.ts +14 -0
- package/dist/task/lifecycle.d.ts.map +1 -0
- package/dist/task/lifecycle.js +80 -0
- package/dist/task/lifecycle.js.map +1 -0
- package/dist/task/list.d.ts +24 -0
- package/dist/task/list.d.ts.map +1 -0
- package/dist/task/list.js +116 -0
- package/dist/task/list.js.map +1 -0
- package/dist/task/model-assignment.d.ts +33 -0
- package/dist/task/model-assignment.d.ts.map +1 -0
- package/dist/task/model-assignment.js +145 -0
- package/dist/task/model-assignment.js.map +1 -0
- package/dist/task/ordering.d.ts +4 -0
- package/dist/task/ordering.d.ts.map +1 -0
- package/dist/task/ordering.js +14 -0
- package/dist/task/ordering.js.map +1 -0
- package/dist/task/ports.d.ts +37 -0
- package/dist/task/ports.d.ts.map +1 -0
- package/dist/task/ports.js +3 -0
- package/dist/task/ports.js.map +1 -0
- package/dist/task/schema.d.ts +447 -0
- package/dist/task/schema.d.ts.map +1 -0
- package/dist/task/schema.js +35 -0
- package/dist/task/schema.js.map +1 -0
- package/dist/task/session-assignment.d.ts +23 -0
- package/dist/task/session-assignment.d.ts.map +1 -0
- package/dist/task/session-assignment.js +197 -0
- package/dist/task/session-assignment.js.map +1 -0
- package/dist/task/session-registry.d.ts +8 -0
- package/dist/task/session-registry.d.ts.map +1 -0
- package/dist/task/session-registry.js +3 -0
- package/dist/task/session-registry.js.map +1 -0
- package/dist/task/update.d.ts +23 -0
- package/dist/task/update.d.ts.map +1 -0
- package/dist/task/update.js +92 -0
- package/dist/task/update.js.map +1 -0
- package/dist/task/v1-compat.d.ts +94 -0
- package/dist/task/v1-compat.d.ts.map +1 -0
- package/dist/task/v1-compat.js +181 -0
- package/dist/task/v1-compat.js.map +1 -0
- package/dist/workspace/bin-cli.d.ts +2 -0
- package/dist/workspace/bin-cli.d.ts.map +1 -0
- package/dist/workspace/bin-cli.js +36 -0
- package/dist/workspace/bin-cli.js.map +1 -0
- package/dist/workspace/cli-adapter.d.ts +16 -0
- package/dist/workspace/cli-adapter.d.ts.map +1 -0
- package/dist/workspace/cli-adapter.js +229 -0
- package/dist/workspace/cli-adapter.js.map +1 -0
- package/dist/workspace/cli-commands.d.ts +11 -0
- package/dist/workspace/cli-commands.d.ts.map +1 -0
- package/dist/workspace/cli-commands.js +12 -0
- package/dist/workspace/cli-commands.js.map +1 -0
- package/dist/workspace/duckdb-index.d.ts +56 -0
- package/dist/workspace/duckdb-index.d.ts.map +1 -0
- package/dist/workspace/duckdb-index.js +178 -0
- package/dist/workspace/duckdb-index.js.map +1 -0
- package/dist/workspace/hook-templates/need-info-notify/config.json +10 -0
- package/dist/workspace/hook-templates/need-info-notify/script.mjs +68 -0
- package/dist/workspace/hook-templates/review-spawn/config.json +10 -0
- package/dist/workspace/hook-templates/review-spawn/script.mjs +100 -0
- package/dist/workspace/init-onboarding.d.ts +9 -0
- package/dist/workspace/init-onboarding.d.ts.map +1 -0
- package/dist/workspace/init-onboarding.js +259 -0
- package/dist/workspace/init-onboarding.js.map +1 -0
- package/dist/workspace/init.d.ts +20 -0
- package/dist/workspace/init.d.ts.map +1 -0
- package/dist/workspace/init.js +288 -0
- package/dist/workspace/init.js.map +1 -0
- package/dist/workspace/layers.d.ts +127 -0
- package/dist/workspace/layers.d.ts.map +1 -0
- package/dist/workspace/layers.js +50 -0
- package/dist/workspace/layers.js.map +1 -0
- package/dist/workspace/mcp-server.d.ts +28 -0
- package/dist/workspace/mcp-server.d.ts.map +1 -0
- package/dist/workspace/mcp-server.js +190 -0
- package/dist/workspace/mcp-server.js.map +1 -0
- package/dist/workspace/mcp-stdio.d.ts +2 -0
- package/dist/workspace/mcp-stdio.d.ts.map +1 -0
- package/dist/workspace/mcp-stdio.js +66 -0
- package/dist/workspace/mcp-stdio.js.map +1 -0
- package/dist/workspace/mcp-tools.d.ts +24 -0
- package/dist/workspace/mcp-tools.d.ts.map +1 -0
- package/dist/workspace/mcp-tools.js +43 -0
- package/dist/workspace/mcp-tools.js.map +1 -0
- package/dist/workspace/migrate-v1.d.ts +12 -0
- package/dist/workspace/migrate-v1.d.ts.map +1 -0
- package/dist/workspace/migrate-v1.js +301 -0
- package/dist/workspace/migrate-v1.js.map +1 -0
- package/dist/workspace/ohtools-app.d.ts +3 -0
- package/dist/workspace/ohtools-app.d.ts.map +1 -0
- package/dist/workspace/ohtools-app.js +10 -0
- package/dist/workspace/ohtools-app.js.map +1 -0
- package/dist/workspace/repositories.d.ts +25 -0
- package/dist/workspace/repositories.d.ts.map +1 -0
- package/dist/workspace/repositories.js +76 -0
- package/dist/workspace/repositories.js.map +1 -0
- package/dist/workspace/runtime.d.ts +123 -0
- package/dist/workspace/runtime.d.ts.map +1 -0
- package/dist/workspace/runtime.js +4 -0
- package/dist/workspace/runtime.js.map +1 -0
- package/dist/workspace/session-liveness.d.ts +6 -0
- package/dist/workspace/session-liveness.d.ts.map +1 -0
- package/dist/workspace/session-liveness.js +3 -0
- package/dist/workspace/session-liveness.js.map +1 -0
- package/dist/workspace/status.d.ts +46 -0
- package/dist/workspace/status.d.ts.map +1 -0
- package/dist/workspace/status.js +345 -0
- package/dist/workspace/status.js.map +1 -0
- package/dist/workspace/storage-layout.d.ts +19 -0
- package/dist/workspace/storage-layout.d.ts.map +1 -0
- package/dist/workspace/storage-layout.js +55 -0
- package/dist/workspace/storage-layout.js.map +1 -0
- package/dist/workspace/storage-paths.d.ts +16 -0
- package/dist/workspace/storage-paths.d.ts.map +1 -0
- package/dist/workspace/storage-paths.js +15 -0
- package/dist/workspace/storage-paths.js.map +1 -0
- package/dist/workspace/v1-cli-aliases.d.ts +18 -0
- package/dist/workspace/v1-cli-aliases.d.ts.map +1 -0
- package/dist/workspace/v1-cli-aliases.js +223 -0
- package/dist/workspace/v1-cli-aliases.js.map +1 -0
- package/dist/workspace/v1-cli-task.d.ts +2 -0
- package/dist/workspace/v1-cli-task.d.ts.map +1 -0
- package/dist/workspace/v1-cli-task.js +53 -0
- package/dist/workspace/v1-cli-task.js.map +1 -0
- package/package.json +28 -12
- package/quickstart.md +163 -0
- package/hooks/need-info-notify/config.yml +0 -3
- package/hooks/need-info-notify/script.ts +0 -69
- package/hooks/review-spawn/config.yml +0 -3
- package/hooks/review-spawn/script.ts +0 -96
- package/src/cli/cli.ts +0 -489
- package/src/cli/init.ts +0 -230
- package/src/domain/fibonacci.ts +0 -39
- package/src/domain/kTokens.ts +0 -65
- package/src/domain/status-machine.ts +0 -49
- package/src/domain/types.ts +0 -66
- package/src/hook/hook-executor.ts +0 -70
- package/src/hook/ports.ts +0 -16
- package/src/infra/hook-config-loader.ts +0 -111
- package/src/infra/jsonl-task-repository.ts +0 -157
- package/src/infra/layer.ts +0 -34
- package/src/infra/logger.ts +0 -40
- package/src/infra/pid-session-registry.ts +0 -67
- package/src/mcp/error-codes.ts +0 -213
- package/src/mcp/server.ts +0 -396
- package/src/mcp/session.ts +0 -1
- package/src/mcp/tool-create-task.ts +0 -28
- package/src/mcp/tool-current-task.ts +0 -19
- package/src/mcp/tool-edit-task.ts +0 -40
- package/src/mcp/tool-list-tasks.ts +0 -34
- package/src/mcp/tool-update-task.ts +0 -55
- package/src/task/create-task.ts +0 -67
- package/src/task/current-task.ts +0 -111
- package/src/task/edit-task.ts +0 -59
- package/src/task/list-tasks.ts +0 -35
- package/src/task/ports.ts +0 -15
- package/src/task/session-registry.ts +0 -9
- package/src/task/update-task.ts +0 -160
package/src/cli/init.ts
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { access, lstat, mkdir, readFile, symlink, writeFile } from "node:fs/promises"
|
|
2
|
-
import { join } from "node:path"
|
|
3
|
-
|
|
4
|
-
const CLI_DOC_CONTENT = `# Logbook CLI
|
|
5
|
-
|
|
6
|
-
File-system kanban board for AI agents.
|
|
7
|
-
|
|
8
|
-
## Commands
|
|
9
|
-
|
|
10
|
-
| Command | Description |
|
|
11
|
-
|---------|-------------|
|
|
12
|
-
| \`logbook create-task\` | Create a new task in backlog |
|
|
13
|
-
| \`logbook list-tasks\` | List tasks, optionally filtered by status |
|
|
14
|
-
| \`logbook current-task\` | Get current in-progress task for this session |
|
|
15
|
-
| \`logbook update-task\` | Transition task status |
|
|
16
|
-
| \`logbook edit-task\` | Edit task fields without changing status |
|
|
17
|
-
| \`logbook init\` | Initialize project |
|
|
18
|
-
|
|
19
|
-
## Task Lifecycle
|
|
20
|
-
|
|
21
|
-
\`backlog → todo → in_progress → pending_review → done\`
|
|
22
|
-
|
|
23
|
-
Side-exits: \`in_progress → need_info\`, \`blocked\` (return to \`in_progress\`)
|
|
24
|
-
|
|
25
|
-
## Usage Examples
|
|
26
|
-
|
|
27
|
-
### Create a task
|
|
28
|
-
\`\`\`bash
|
|
29
|
-
logbook create-task --project myproject --milestone v1 --title "Fix bug" \\
|
|
30
|
-
--definition-of-done "Bug fixed and tested" --description "Details..." \\
|
|
31
|
-
--predicted-k-tokens 3
|
|
32
|
-
\`\`\`
|
|
33
|
-
|
|
34
|
-
### List tasks
|
|
35
|
-
\`\`\`bash
|
|
36
|
-
logbook list-tasks --status in_progress
|
|
37
|
-
logbook list-tasks --status "*"
|
|
38
|
-
logbook list-tasks --status todo --project myproject
|
|
39
|
-
\`\`\`
|
|
40
|
-
|
|
41
|
-
### Get current task
|
|
42
|
-
\`\`\`bash
|
|
43
|
-
logbook current-task
|
|
44
|
-
\`\`\`
|
|
45
|
-
|
|
46
|
-
### Update task status
|
|
47
|
-
\`\`\`bash
|
|
48
|
-
logbook update-task --id <uuid> --new-status in_progress
|
|
49
|
-
logbook update-task --id <uuid> --new-status need_info \\
|
|
50
|
-
--comment-title "Need info" --comment-content "What does X mean?"
|
|
51
|
-
\`\`\`
|
|
52
|
-
|
|
53
|
-
### Edit task
|
|
54
|
-
\`\`\`bash
|
|
55
|
-
logbook edit-task --id <uuid> --title "New title"
|
|
56
|
-
\`\`\`
|
|
57
|
-
|
|
58
|
-
## Environment Variables
|
|
59
|
-
|
|
60
|
-
| Variable | Default | Description |
|
|
61
|
-
|----------|---------|-------------|
|
|
62
|
-
| \`LOGBOOK_TASKS_FILE\` | \`./tasks.jsonl\` | Path to JSONL task store |
|
|
63
|
-
| \`LOGBOOK_HOOKS_DIR\` | \`./hooks\` | Directory for hook definitions |
|
|
64
|
-
| \`LOGBOOK_SESSION_ID\` | auto-generated | Session ID to use |
|
|
65
|
-
`
|
|
66
|
-
|
|
67
|
-
const CLAUDE_CODE_SNIPPET = `Claude Code — add to .claude/settings.json:
|
|
68
|
-
{
|
|
69
|
-
"mcpServers": {
|
|
70
|
-
"logbook": {
|
|
71
|
-
"command": "logbook-mcp"
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}`
|
|
75
|
-
|
|
76
|
-
const OPENCODE_SNIPPET = `OpenCode — add to opencode.json:
|
|
77
|
-
{
|
|
78
|
-
"mcp": {
|
|
79
|
-
"logbook": {
|
|
80
|
-
"type": "local",
|
|
81
|
-
"command": ["logbook-mcp"],
|
|
82
|
-
"enabled": true
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}`
|
|
86
|
-
|
|
87
|
-
const GITIGNORE_SNIPPET = `.gitignore — add these runtime files:
|
|
88
|
-
tasks.jsonl
|
|
89
|
-
sessions.json
|
|
90
|
-
.logbook-session`
|
|
91
|
-
|
|
92
|
-
const NEXT_STEPS = `Next steps:
|
|
93
|
-
1. Add the config snippet for your AI client (MCP) or use CLI directly
|
|
94
|
-
2. Run: LOGBOOK_TASKS_FILE=./tasks.jsonl logbook init
|
|
95
|
-
3. See AGENTS.md for CLI commands reference
|
|
96
|
-
4. See quickstart.md for the full walkthrough`
|
|
97
|
-
|
|
98
|
-
const fileExists = async (path: string): Promise<boolean> => {
|
|
99
|
-
try {
|
|
100
|
-
await access(path)
|
|
101
|
-
return true
|
|
102
|
-
} catch {
|
|
103
|
-
return false
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const isSymlink = async (path: string): Promise<boolean> => {
|
|
108
|
-
try {
|
|
109
|
-
const stats = await lstat(path)
|
|
110
|
-
return stats.isSymbolicLink()
|
|
111
|
-
} catch {
|
|
112
|
-
return false
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const scaffoldTasksFile = async (cwd: string): Promise<void> => {
|
|
117
|
-
const path = join(cwd, "tasks.jsonl")
|
|
118
|
-
if (await fileExists(path)) {
|
|
119
|
-
console.log("✓ tasks.jsonl already exists, skipping")
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
await writeFile(path, "", "utf8")
|
|
123
|
-
console.log("✓ tasks.jsonl created")
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const scaffoldHooksDir = async (cwd: string): Promise<void> => {
|
|
127
|
-
const path = join(cwd, "hooks")
|
|
128
|
-
if (await fileExists(path)) {
|
|
129
|
-
console.log("✓ hooks/ already exists, skipping")
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
await mkdir(path, { recursive: false })
|
|
133
|
-
console.log("✓ hooks/ created")
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const appendLogbookDocs = async (path: string, isAgents: boolean): Promise<void> => {
|
|
137
|
-
const existing = await readFile(path, "utf8")
|
|
138
|
-
if (isAgents) {
|
|
139
|
-
const separator = "\n\n---\n\n"
|
|
140
|
-
await writeFile(path, `${existing}${separator}${CLI_DOC_CONTENT}`, "utf8")
|
|
141
|
-
} else {
|
|
142
|
-
const cliSection = `
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## Logbook
|
|
147
|
-
|
|
148
|
-
${CLI_DOC_CONTENT}
|
|
149
|
-
`
|
|
150
|
-
await writeFile(path, `${existing}${cliSection}`, "utf8")
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const ensureBothDocs = async (cwd: string, force: boolean): Promise<void> => {
|
|
155
|
-
const agentsPath = join(cwd, "AGENTS.md")
|
|
156
|
-
const claudePath = join(cwd, "CLAUDE.md")
|
|
157
|
-
|
|
158
|
-
const agentsExists = await fileExists(agentsPath)
|
|
159
|
-
const claudeExists = await fileExists(claudePath)
|
|
160
|
-
const agentsIsSymlink = await isSymlink(agentsPath)
|
|
161
|
-
const claudeIsSymlink = await isSymlink(claudePath)
|
|
162
|
-
|
|
163
|
-
if (!force) {
|
|
164
|
-
if (agentsExists && !agentsIsSymlink) {
|
|
165
|
-
console.log("✓ AGENTS.md already exists, appending logbook documentation")
|
|
166
|
-
await appendLogbookDocs(agentsPath, true)
|
|
167
|
-
}
|
|
168
|
-
if (claudeExists && !claudeIsSymlink) {
|
|
169
|
-
console.log("✓ CLAUDE.md already exists, appending logbook documentation")
|
|
170
|
-
await appendLogbookDocs(claudePath, false)
|
|
171
|
-
}
|
|
172
|
-
if (!agentsExists && !claudeExists) {
|
|
173
|
-
await writeFile(agentsPath, CLI_DOC_CONTENT, "utf8")
|
|
174
|
-
console.log("✓ AGENTS.md created")
|
|
175
|
-
await symlink("AGENTS.md", claudePath)
|
|
176
|
-
console.log("✓ CLAUDE.md created (symlink to AGENTS.md)")
|
|
177
|
-
}
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (agentsExists && !agentsIsSymlink) {
|
|
182
|
-
await appendLogbookDocs(agentsPath, true)
|
|
183
|
-
} else if (!agentsExists) {
|
|
184
|
-
await writeFile(agentsPath, CLI_DOC_CONTENT, "utf8")
|
|
185
|
-
console.log("✓ AGENTS.md created")
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (claudeExists && !claudeIsSymlink) {
|
|
189
|
-
await appendLogbookDocs(claudePath, false)
|
|
190
|
-
} else if (!claudeExists) {
|
|
191
|
-
const targetExists = await fileExists(agentsPath)
|
|
192
|
-
if (targetExists) {
|
|
193
|
-
await symlink("AGENTS.md", claudePath)
|
|
194
|
-
console.log("✓ CLAUDE.md created (symlink to AGENTS.md)")
|
|
195
|
-
} else {
|
|
196
|
-
await writeFile(
|
|
197
|
-
claudePath,
|
|
198
|
-
`# Logbook
|
|
199
|
-
|
|
200
|
-
${CLI_DOC_CONTENT}
|
|
201
|
-
`,
|
|
202
|
-
"utf8"
|
|
203
|
-
)
|
|
204
|
-
console.log("✓ CLAUDE.md created")
|
|
205
|
-
}
|
|
206
|
-
} else if (claudeIsSymlink) {
|
|
207
|
-
console.log("✓ CLAUDE.md is already a symlink to AGENTS.md, skipping")
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const printSnippets = (): void => {
|
|
212
|
-
console.log("")
|
|
213
|
-
console.log(CLAUDE_CODE_SNIPPET)
|
|
214
|
-
console.log("")
|
|
215
|
-
console.log(OPENCODE_SNIPPET)
|
|
216
|
-
console.log("")
|
|
217
|
-
console.log(GITIGNORE_SNIPPET)
|
|
218
|
-
console.log("")
|
|
219
|
-
console.log(NEXT_STEPS)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export const runInit = async (
|
|
223
|
-
cwd: string = process.cwd(),
|
|
224
|
-
options: { force?: boolean } = {}
|
|
225
|
-
): Promise<void> => {
|
|
226
|
-
await scaffoldTasksFile(cwd)
|
|
227
|
-
await scaffoldHooksDir(cwd)
|
|
228
|
-
await ensureBothDocs(cwd, options.force ?? false)
|
|
229
|
-
printSnippets()
|
|
230
|
-
}
|
package/src/domain/fibonacci.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect"
|
|
2
|
-
import type { TaskError } from "./types.js"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Validates that n is a positive Fibonacci number.
|
|
6
|
-
* Returns Effect.succeed(void) when valid,
|
|
7
|
-
* Effect.fail({ _tag: 'validation_error', message: 'estimation must be a Fibonacci number' }) otherwise.
|
|
8
|
-
*
|
|
9
|
-
* Algorithm: n is Fibonacci iff one of 5n²+4 or 5n²-4 is a perfect square.
|
|
10
|
-
*/
|
|
11
|
-
export const validateFibonacci = (n: number): Effect.Effect<void, TaskError> => {
|
|
12
|
-
// Must be a positive integer
|
|
13
|
-
if (!Number.isInteger(n) || n <= 0) {
|
|
14
|
-
return Effect.fail({
|
|
15
|
-
_tag: "validation_error",
|
|
16
|
-
message: "estimation must be a Fibonacci number",
|
|
17
|
-
})
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Check if a number is a perfect square
|
|
21
|
-
const isPerfectSquare = (num: number): boolean => {
|
|
22
|
-
if (num < 0) return false
|
|
23
|
-
const sqrt = Math.sqrt(num)
|
|
24
|
-
return sqrt === Math.floor(sqrt)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// n is Fibonacci iff 5n²+4 or 5n²-4 is a perfect square
|
|
28
|
-
const fiveSqPlusFour = 5 * n * n + 4
|
|
29
|
-
const fiveSqMinusFour = 5 * n * n - 4
|
|
30
|
-
|
|
31
|
-
if (isPerfectSquare(fiveSqPlusFour) || isPerfectSquare(fiveSqMinusFour)) {
|
|
32
|
-
return Effect.succeed(void 0)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return Effect.fail({
|
|
36
|
-
_tag: "validation_error",
|
|
37
|
-
message: "estimation must be a Fibonacci number",
|
|
38
|
-
})
|
|
39
|
-
}
|
package/src/domain/kTokens.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect"
|
|
2
|
-
import type { TaskError } from "./types.js"
|
|
3
|
-
|
|
4
|
-
export interface KTokensConfig {
|
|
5
|
-
anchorPoint: number // Fibonacci number to anchor against
|
|
6
|
-
kTokensAtAnchor: number // how many kTokens map to anchorPoint
|
|
7
|
-
maxKTokens: number // cap (inclusive)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const defaultConfig: KTokensConfig = {
|
|
11
|
-
anchorPoint: 8,
|
|
12
|
-
kTokensAtAnchor: 20,
|
|
13
|
-
maxKTokens: 20,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Fibonacci sequence for lookup
|
|
17
|
-
const FIBONACCI = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
|
|
18
|
-
|
|
19
|
-
export const estimateFromKTokens = (
|
|
20
|
-
kTokens: number,
|
|
21
|
-
config: KTokensConfig = defaultConfig
|
|
22
|
-
): Effect.Effect<number, TaskError> => {
|
|
23
|
-
// Fail if kTokens <= 0
|
|
24
|
-
if (kTokens <= 0) {
|
|
25
|
-
return Effect.fail({
|
|
26
|
-
_tag: "validation_error",
|
|
27
|
-
message: "predicted kilotokens must be positive",
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Fail if kTokens > config.maxKTokens
|
|
32
|
-
if (kTokens > config.maxKTokens) {
|
|
33
|
-
return Effect.fail({
|
|
34
|
-
_tag: "validation_error",
|
|
35
|
-
message: "predicted kilotokens exceed maximum allowed",
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Calculate ratio: kTokensAtAnchor / anchorPoint
|
|
40
|
-
const ratio = config.kTokensAtAnchor / config.anchorPoint
|
|
41
|
-
|
|
42
|
-
// Scale kTokens to estimate space
|
|
43
|
-
const scaled = kTokens / ratio
|
|
44
|
-
|
|
45
|
-
// Find nearest Fibonacci number, rounding UP on tie
|
|
46
|
-
let nearestFib: number = FIBONACCI[0] ?? 1
|
|
47
|
-
let minDistance = Math.abs(nearestFib - scaled)
|
|
48
|
-
|
|
49
|
-
for (const fib of FIBONACCI) {
|
|
50
|
-
const distance = Math.abs(fib - scaled)
|
|
51
|
-
|
|
52
|
-
// On exact match, return immediately
|
|
53
|
-
if (distance === 0) {
|
|
54
|
-
return Effect.succeed(fib)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// On tie, pick the larger value (UP)
|
|
58
|
-
if (distance < minDistance || (distance === minDistance && fib > nearestFib)) {
|
|
59
|
-
nearestFib = fib
|
|
60
|
-
minDistance = distance
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return Effect.succeed(nearestFib)
|
|
65
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect"
|
|
2
|
-
import type { Status, TaskError } from "./types.js"
|
|
3
|
-
|
|
4
|
-
// Allowed state transitions: from → [to, to, ...]
|
|
5
|
-
export const allowedTransitions: Record<Status, Status[]> = {
|
|
6
|
-
backlog: ["todo"],
|
|
7
|
-
todo: ["backlog", "in_progress"],
|
|
8
|
-
in_progress: ["todo", "pending_review", "need_info", "blocked"],
|
|
9
|
-
blocked: ["in_progress"],
|
|
10
|
-
need_info: ["in_progress"],
|
|
11
|
-
pending_review: ["done", "in_progress"],
|
|
12
|
-
done: [],
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Guards a status transition, returning Effect.succeed(void) when allowed
|
|
17
|
-
* and Effect.fail({ _tag: 'transition_not_allowed', from, to }) otherwise.
|
|
18
|
-
* A same→same transition is always a no-op success.
|
|
19
|
-
* Review tasks (id starts with "review-") may go directly from in_progress to done.
|
|
20
|
-
*/
|
|
21
|
-
export const guardTransition = (
|
|
22
|
-
from: Status,
|
|
23
|
-
to: Status,
|
|
24
|
-
taskId?: string
|
|
25
|
-
): Effect.Effect<void, TaskError> => {
|
|
26
|
-
// Same→same is always a no-op success
|
|
27
|
-
if (from === to) {
|
|
28
|
-
return Effect.void
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Check if transition is in the allowed map
|
|
32
|
-
const allowed = allowedTransitions[from]
|
|
33
|
-
if (allowed.includes(to)) {
|
|
34
|
-
return Effect.void
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Review tasks may skip pending_review and go directly to done
|
|
38
|
-
if (from === "in_progress" && to === "done" && taskId?.startsWith("review-")) {
|
|
39
|
-
return Effect.void
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Transition not allowed
|
|
43
|
-
return Effect.fail({
|
|
44
|
-
_tag: "transition_not_allowed",
|
|
45
|
-
from,
|
|
46
|
-
to,
|
|
47
|
-
taskId,
|
|
48
|
-
} as TaskError)
|
|
49
|
-
}
|
package/src/domain/types.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
|
|
3
|
-
export const StatusSchema = z.enum([
|
|
4
|
-
"backlog",
|
|
5
|
-
"todo",
|
|
6
|
-
"need_info",
|
|
7
|
-
"blocked",
|
|
8
|
-
"in_progress",
|
|
9
|
-
"pending_review",
|
|
10
|
-
"done",
|
|
11
|
-
])
|
|
12
|
-
export type Status = z.infer<typeof StatusSchema>
|
|
13
|
-
|
|
14
|
-
export const CommentKindSchema = z.enum(["need_info", "regular"])
|
|
15
|
-
export type CommentKind = z.infer<typeof CommentKindSchema>
|
|
16
|
-
|
|
17
|
-
export const CommentSchema = z.object({
|
|
18
|
-
id: z.string().min(1),
|
|
19
|
-
timestamp: z.coerce.date(),
|
|
20
|
-
title: z.string().min(1),
|
|
21
|
-
content: z.string(),
|
|
22
|
-
reply: z.string(),
|
|
23
|
-
kind: CommentKindSchema,
|
|
24
|
-
})
|
|
25
|
-
export type Comment = z.infer<typeof CommentSchema>
|
|
26
|
-
|
|
27
|
-
export const AgentSchema = z.object({
|
|
28
|
-
id: z.string().min(1),
|
|
29
|
-
title: z.string().min(1),
|
|
30
|
-
description: z.string(),
|
|
31
|
-
})
|
|
32
|
-
export type Agent = z.infer<typeof AgentSchema>
|
|
33
|
-
|
|
34
|
-
export const TaskSchema = z.object({
|
|
35
|
-
project: z.string().min(1),
|
|
36
|
-
milestone: z.string().min(1),
|
|
37
|
-
id: z.string().min(1),
|
|
38
|
-
title: z.string().min(1),
|
|
39
|
-
definition_of_done: z.string().min(1),
|
|
40
|
-
description: z.string().min(1),
|
|
41
|
-
estimation: z.number().int().positive(),
|
|
42
|
-
comments: z.array(CommentSchema),
|
|
43
|
-
assignee: AgentSchema.optional(),
|
|
44
|
-
status: StatusSchema,
|
|
45
|
-
in_progress_since: z.coerce.date().optional(),
|
|
46
|
-
priority: z.number().int().min(0).default(0),
|
|
47
|
-
})
|
|
48
|
-
export type Task = z.infer<typeof TaskSchema>
|
|
49
|
-
|
|
50
|
-
// Error tags match Gherkin feature file error names
|
|
51
|
-
export type TaskError =
|
|
52
|
-
| { readonly _tag: "not_found"; readonly taskId: string }
|
|
53
|
-
| {
|
|
54
|
-
readonly _tag: "transition_not_allowed"
|
|
55
|
-
readonly from: Status
|
|
56
|
-
readonly to: Status
|
|
57
|
-
readonly taskId?: string
|
|
58
|
-
}
|
|
59
|
-
| {
|
|
60
|
-
readonly _tag: "validation_error"
|
|
61
|
-
readonly message: string
|
|
62
|
-
readonly context?: Record<string, unknown>
|
|
63
|
-
}
|
|
64
|
-
| { readonly _tag: "missing_comment"; readonly from?: Status; readonly to?: Status }
|
|
65
|
-
| { readonly _tag: "conflict"; readonly taskId: string }
|
|
66
|
-
| { readonly _tag: "no_current_task" }
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect"
|
|
2
|
-
import type { HookEvent } from "./ports.js"
|
|
3
|
-
|
|
4
|
-
export interface HookConfig {
|
|
5
|
-
event: string
|
|
6
|
-
condition?: string
|
|
7
|
-
timeout_ms?: number
|
|
8
|
-
script: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const HOOK_EVENT_NAME = "task.status_changed"
|
|
12
|
-
const DEFAULT_TIMEOUT_MS = 5000
|
|
13
|
-
|
|
14
|
-
const evaluateCondition = (condition: string, event: HookEvent): boolean => {
|
|
15
|
-
try {
|
|
16
|
-
const fn = new Function(
|
|
17
|
-
"new_status",
|
|
18
|
-
"old_status",
|
|
19
|
-
"task_id",
|
|
20
|
-
"session_id",
|
|
21
|
-
`return (${condition})`
|
|
22
|
-
)
|
|
23
|
-
return Boolean(fn(event.new_status, event.old_status, event.task_id, event.session_id))
|
|
24
|
-
} catch {
|
|
25
|
-
return false
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const runScript = (config: HookConfig, event: HookEvent): Promise<void> =>
|
|
30
|
-
new Promise((resolve) => {
|
|
31
|
-
const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS
|
|
32
|
-
|
|
33
|
-
const cmd = config.script.endsWith(".ts") ? ["bun", config.script] : ["sh", "-c", config.script]
|
|
34
|
-
|
|
35
|
-
const child = Bun.spawn(cmd, {
|
|
36
|
-
env: {
|
|
37
|
-
...process.env,
|
|
38
|
-
LOGBOOK_TASK_ID: event.task_id,
|
|
39
|
-
LOGBOOK_OLD_STATUS: event.old_status,
|
|
40
|
-
LOGBOOK_NEW_STATUS: event.new_status,
|
|
41
|
-
LOGBOOK_SESSION_ID: event.session_id,
|
|
42
|
-
},
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const timer = setTimeout(() => {
|
|
46
|
-
child.kill()
|
|
47
|
-
}, timeoutMs)
|
|
48
|
-
|
|
49
|
-
void (async () => {
|
|
50
|
-
try {
|
|
51
|
-
await child.exited
|
|
52
|
-
} finally {
|
|
53
|
-
clearTimeout(timer)
|
|
54
|
-
resolve()
|
|
55
|
-
}
|
|
56
|
-
})()
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
export const executeHooks = (
|
|
60
|
-
event: HookEvent,
|
|
61
|
-
configs: readonly HookConfig[]
|
|
62
|
-
): Effect.Effect<void, never> =>
|
|
63
|
-
Effect.promise(async () => {
|
|
64
|
-
await Promise.all(
|
|
65
|
-
configs
|
|
66
|
-
.filter((c) => c.event === HOOK_EVENT_NAME)
|
|
67
|
-
.filter((c) => !c.condition || evaluateCondition(c.condition, event))
|
|
68
|
-
.map((c) => runScript(c, event))
|
|
69
|
-
)
|
|
70
|
-
})
|
package/src/hook/ports.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Context, type Effect } from "effect"
|
|
2
|
-
import type { Comment, Status } from "../domain/types.js"
|
|
3
|
-
|
|
4
|
-
export interface HookEvent {
|
|
5
|
-
task_id: string
|
|
6
|
-
old_status: Status
|
|
7
|
-
new_status: Status
|
|
8
|
-
comment: Comment | null
|
|
9
|
-
session_id: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface HookRunner {
|
|
13
|
-
run(event: HookEvent): Effect.Effect<void, never>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const HookRunner = Context.GenericTag<HookRunner>("HookRunner")
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { readdir, readFile } from "node:fs/promises"
|
|
2
|
-
import { join } from "node:path"
|
|
3
|
-
import { z } from "zod"
|
|
4
|
-
import type { HookConfig } from "../hook/hook-executor.js"
|
|
5
|
-
import { logger } from "./logger.js"
|
|
6
|
-
|
|
7
|
-
const KNOWN_KEYS = ["event", "condition", "timeout_ms"] as const
|
|
8
|
-
|
|
9
|
-
const HookConfigFileSchema = z.object({
|
|
10
|
-
event: z.string(),
|
|
11
|
-
condition: z.string().optional(),
|
|
12
|
-
timeout_ms: z.number().optional(),
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Parses a strict subset of YAML: flat key-value pairs, no nesting.
|
|
17
|
-
* Supports quoted strings and bare integers.
|
|
18
|
-
*/
|
|
19
|
-
const parseSimpleYaml = (content: string): Record<string, unknown> => {
|
|
20
|
-
const result: Record<string, unknown> = {}
|
|
21
|
-
for (const line of content.split("\n")) {
|
|
22
|
-
const trimmed = line.trim()
|
|
23
|
-
if (!trimmed || trimmed.startsWith("#")) continue
|
|
24
|
-
const colonIdx = trimmed.indexOf(":")
|
|
25
|
-
if (colonIdx === -1) continue
|
|
26
|
-
const key = trimmed.slice(0, colonIdx).trim()
|
|
27
|
-
let value: unknown = trimmed.slice(colonIdx + 1).trim()
|
|
28
|
-
if (typeof value === "string" && value.startsWith('"') && value.endsWith('"')) {
|
|
29
|
-
value = value.slice(1, -1)
|
|
30
|
-
} else if (typeof value === "string" && value.startsWith("'") && value.endsWith("'")) {
|
|
31
|
-
value = value.slice(1, -1)
|
|
32
|
-
} else if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
33
|
-
value = parseInt(value, 10)
|
|
34
|
-
}
|
|
35
|
-
result[key] = value
|
|
36
|
-
}
|
|
37
|
-
return result
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const SCRIPT_CANDIDATES = ["script.ts", "script.sh"] as const
|
|
41
|
-
|
|
42
|
-
const findScript = async (hookDir: string): Promise<string | undefined> => {
|
|
43
|
-
for (const name of SCRIPT_CANDIDATES) {
|
|
44
|
-
const candidate = join(hookDir, name)
|
|
45
|
-
try {
|
|
46
|
-
await readFile(candidate)
|
|
47
|
-
return candidate
|
|
48
|
-
} catch {
|
|
49
|
-
// try next candidate
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return undefined
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export const loadHookConfigs = async (hooksDir: string): Promise<HookConfig[]> => {
|
|
56
|
-
let entries: string[]
|
|
57
|
-
try {
|
|
58
|
-
entries = await readdir(hooksDir)
|
|
59
|
-
} catch (e: unknown) {
|
|
60
|
-
if (isEnoent(e)) return []
|
|
61
|
-
throw e
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const configs: HookConfig[] = []
|
|
65
|
-
|
|
66
|
-
for (const entry of entries) {
|
|
67
|
-
const hookDir = join(hooksDir, entry)
|
|
68
|
-
try {
|
|
69
|
-
const configPath = join(hookDir, "config.yml")
|
|
70
|
-
const raw = await readFile(configPath, "utf8")
|
|
71
|
-
const parsed = parseSimpleYaml(raw)
|
|
72
|
-
const validated = HookConfigFileSchema.safeParse(parsed)
|
|
73
|
-
if (!validated.success) {
|
|
74
|
-
logger.warn("invalid hook config", { path: configPath, error: validated.error.message })
|
|
75
|
-
continue
|
|
76
|
-
}
|
|
77
|
-
// Warn on unrecognized keys
|
|
78
|
-
const parsedKeys = Object.keys(parsed)
|
|
79
|
-
for (const key of parsedKeys) {
|
|
80
|
-
if (!(KNOWN_KEYS as readonly string[]).includes(key)) {
|
|
81
|
-
const validKeysStr = KNOWN_KEYS.join(", ")
|
|
82
|
-
logger.warn("unrecognized key in hook config", {
|
|
83
|
-
hook: entry,
|
|
84
|
-
key,
|
|
85
|
-
validKeys: validKeysStr,
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
const script = await findScript(hookDir)
|
|
90
|
-
if (script === undefined) {
|
|
91
|
-
logger.warn("no script found in hook dir, skipping", { path: hookDir })
|
|
92
|
-
continue
|
|
93
|
-
}
|
|
94
|
-
const { event, condition, timeout_ms } = validated.data
|
|
95
|
-
const config: HookConfig = {
|
|
96
|
-
event,
|
|
97
|
-
script,
|
|
98
|
-
...(condition !== undefined ? { condition } : {}),
|
|
99
|
-
...(timeout_ms !== undefined ? { timeout_ms } : {}),
|
|
100
|
-
}
|
|
101
|
-
configs.push(config)
|
|
102
|
-
} catch (e: unknown) {
|
|
103
|
-
logger.warn("failed to load hook", { hook: entry, error: String(e) })
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return configs
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const isEnoent = (e: unknown): boolean =>
|
|
111
|
-
typeof e === "object" && e !== null && (e as { code?: unknown }).code === "ENOENT"
|