opentasks 0.0.1 → 0.0.2
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/README.md +344 -1
- package/dist/__tests__/cli-tools.test.d.ts +8 -0
- package/dist/__tests__/cli-tools.test.d.ts.map +1 -0
- package/dist/__tests__/cli-tools.test.js +546 -0
- package/dist/__tests__/cli-tools.test.js.map +1 -0
- package/dist/__tests__/cli.test.d.ts +5 -0
- package/dist/__tests__/cli.test.d.ts.map +1 -0
- package/dist/__tests__/cli.test.js +77 -0
- package/dist/__tests__/cli.test.js.map +1 -0
- package/dist/__tests__/p1-p3-gaps.test.d.ts +2 -0
- package/dist/__tests__/p1-p3-gaps.test.d.ts.map +1 -0
- package/dist/__tests__/p1-p3-gaps.test.js +463 -0
- package/dist/__tests__/p1-p3-gaps.test.js.map +1 -0
- package/dist/cli.d.ts +19 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +702 -23
- package/dist/cli.js.map +1 -1
- package/dist/client/__tests__/client-crud.test.d.ts +7 -0
- package/dist/client/__tests__/client-crud.test.d.ts.map +1 -0
- package/dist/client/__tests__/client-crud.test.js +404 -0
- package/dist/client/__tests__/client-crud.test.js.map +1 -0
- package/dist/client/__tests__/client.test.d.ts +5 -0
- package/dist/client/__tests__/client.test.d.ts.map +1 -0
- package/dist/client/__tests__/client.test.js +518 -0
- package/dist/client/__tests__/client.test.js.map +1 -0
- package/dist/client/client.d.ts +228 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +393 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -0
- package/dist/config/__tests__/defaults.test.d.ts +2 -0
- package/dist/config/__tests__/defaults.test.d.ts.map +1 -0
- package/dist/config/__tests__/defaults.test.js +57 -0
- package/dist/config/__tests__/defaults.test.js.map +1 -0
- package/dist/config/__tests__/env.test.d.ts +2 -0
- package/dist/config/__tests__/env.test.d.ts.map +1 -0
- package/dist/config/__tests__/env.test.js +136 -0
- package/dist/config/__tests__/env.test.js.map +1 -0
- package/dist/config/__tests__/index.test.d.ts +2 -0
- package/dist/config/__tests__/index.test.d.ts.map +1 -0
- package/dist/config/__tests__/index.test.js +113 -0
- package/dist/config/__tests__/index.test.js.map +1 -0
- package/dist/config/__tests__/loader.test.d.ts +2 -0
- package/dist/config/__tests__/loader.test.d.ts.map +1 -0
- package/dist/config/__tests__/loader.test.js +128 -0
- package/dist/config/__tests__/loader.test.js.map +1 -0
- package/dist/config/__tests__/merge.test.d.ts +2 -0
- package/dist/config/__tests__/merge.test.d.ts.map +1 -0
- package/dist/config/__tests__/merge.test.js +79 -0
- package/dist/config/__tests__/merge.test.js.map +1 -0
- package/dist/config/__tests__/schema.test.d.ts +2 -0
- package/dist/config/__tests__/schema.test.d.ts.map +1 -0
- package/dist/config/__tests__/schema.test.js +300 -0
- package/dist/config/__tests__/schema.test.js.map +1 -0
- package/dist/config/defaults.d.ts +13 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +15 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/env.d.ts +14 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +130 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/errors.d.ts +21 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +30 -0
- package/dist/config/errors.js.map +1 -0
- package/dist/config/index.d.ts +21 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +41 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +8 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +87 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/merge.d.ts +12 -0
- package/dist/config/merge.d.ts.map +1 -0
- package/dist/config/merge.js +54 -0
- package/dist/config/merge.js.map +1 -0
- package/dist/config/schema.d.ts +644 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +491 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/core/__tests__/conditional-redirects.test.d.ts +2 -0
- package/dist/core/__tests__/conditional-redirects.test.d.ts.map +1 -0
- package/dist/core/__tests__/conditional-redirects.test.js +83 -0
- package/dist/core/__tests__/conditional-redirects.test.js.map +1 -0
- package/dist/core/__tests__/connections.test.d.ts +2 -0
- package/dist/core/__tests__/connections.test.d.ts.map +1 -0
- package/dist/core/__tests__/connections.test.js +158 -0
- package/dist/core/__tests__/connections.test.js.map +1 -0
- package/dist/core/__tests__/hash.test.d.ts +2 -0
- package/dist/core/__tests__/hash.test.d.ts.map +1 -0
- package/dist/core/__tests__/hash.test.js +139 -0
- package/dist/core/__tests__/hash.test.js.map +1 -0
- package/dist/core/__tests__/id.test.d.ts +2 -0
- package/dist/core/__tests__/id.test.d.ts.map +1 -0
- package/dist/core/__tests__/id.test.js +142 -0
- package/dist/core/__tests__/id.test.js.map +1 -0
- package/dist/core/__tests__/location.test.d.ts +2 -0
- package/dist/core/__tests__/location.test.d.ts.map +1 -0
- package/dist/core/__tests__/location.test.js +77 -0
- package/dist/core/__tests__/location.test.js.map +1 -0
- package/dist/core/__tests__/merge-driver.test.d.ts +2 -0
- package/dist/core/__tests__/merge-driver.test.d.ts.map +1 -0
- package/dist/core/__tests__/merge-driver.test.js +218 -0
- package/dist/core/__tests__/merge-driver.test.js.map +1 -0
- package/dist/core/__tests__/redirects.test.d.ts +2 -0
- package/dist/core/__tests__/redirects.test.d.ts.map +1 -0
- package/dist/core/__tests__/redirects.test.js +123 -0
- package/dist/core/__tests__/redirects.test.js.map +1 -0
- package/dist/core/__tests__/resolve-location-target.test.d.ts +8 -0
- package/dist/core/__tests__/resolve-location-target.test.d.ts.map +1 -0
- package/dist/core/__tests__/resolve-location-target.test.js +303 -0
- package/dist/core/__tests__/resolve-location-target.test.js.map +1 -0
- package/dist/core/__tests__/uri.test.d.ts +2 -0
- package/dist/core/__tests__/uri.test.d.ts.map +1 -0
- package/dist/core/__tests__/uri.test.js +159 -0
- package/dist/core/__tests__/uri.test.js.map +1 -0
- package/dist/core/__tests__/worktree.test.d.ts +2 -0
- package/dist/core/__tests__/worktree.test.d.ts.map +1 -0
- package/dist/core/__tests__/worktree.test.js +120 -0
- package/dist/core/__tests__/worktree.test.js.map +1 -0
- package/dist/core/conditional-redirects.d.ts +42 -0
- package/dist/core/conditional-redirects.d.ts.map +1 -0
- package/dist/core/conditional-redirects.js +59 -0
- package/dist/core/conditional-redirects.js.map +1 -0
- package/dist/core/connections.d.ts +87 -0
- package/dist/core/connections.d.ts.map +1 -0
- package/dist/core/connections.js +160 -0
- package/dist/core/connections.js.map +1 -0
- package/dist/core/discover.d.ts +39 -0
- package/dist/core/discover.d.ts.map +1 -0
- package/dist/core/discover.js +136 -0
- package/dist/core/discover.js.map +1 -0
- package/dist/core/hash.d.ts +25 -0
- package/dist/core/hash.d.ts.map +1 -0
- package/dist/core/hash.js +62 -0
- package/dist/core/hash.js.map +1 -0
- package/dist/core/id.d.ts +79 -0
- package/dist/core/id.d.ts.map +1 -0
- package/dist/core/id.js +141 -0
- package/dist/core/id.js.map +1 -0
- package/dist/core/index.d.ts +15 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +24 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/location.d.ts +70 -0
- package/dist/core/location.d.ts.map +1 -0
- package/dist/core/location.js +121 -0
- package/dist/core/location.js.map +1 -0
- package/dist/core/merge-driver.d.ts +50 -0
- package/dist/core/merge-driver.d.ts.map +1 -0
- package/dist/core/merge-driver.js +258 -0
- package/dist/core/merge-driver.js.map +1 -0
- package/dist/core/redirects.d.ts +91 -0
- package/dist/core/redirects.d.ts.map +1 -0
- package/dist/core/redirects.js +113 -0
- package/dist/core/redirects.js.map +1 -0
- package/dist/core/uri.d.ts +105 -0
- package/dist/core/uri.d.ts.map +1 -0
- package/dist/core/uri.js +190 -0
- package/dist/core/uri.js.map +1 -0
- package/dist/core/worktree.d.ts +106 -0
- package/dist/core/worktree.d.ts.map +1 -0
- package/dist/core/worktree.js +394 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/daemon/__tests__/flush.test.d.ts +5 -0
- package/dist/daemon/__tests__/flush.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/flush.test.js +213 -0
- package/dist/daemon/__tests__/flush.test.js.map +1 -0
- package/dist/daemon/__tests__/integration.test.d.ts +7 -0
- package/dist/daemon/__tests__/integration.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/integration.test.js +276 -0
- package/dist/daemon/__tests__/integration.test.js.map +1 -0
- package/dist/daemon/__tests__/ipc.test.d.ts +5 -0
- package/dist/daemon/__tests__/ipc.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/ipc.test.js +314 -0
- package/dist/daemon/__tests__/ipc.test.js.map +1 -0
- package/dist/daemon/__tests__/lifecycle.test.d.ts +5 -0
- package/dist/daemon/__tests__/lifecycle.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/lifecycle.test.js +301 -0
- package/dist/daemon/__tests__/lifecycle.test.js.map +1 -0
- package/dist/daemon/__tests__/lock.test.d.ts +5 -0
- package/dist/daemon/__tests__/lock.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/lock.test.js +192 -0
- package/dist/daemon/__tests__/lock.test.js.map +1 -0
- package/dist/daemon/__tests__/methods/graph.test.d.ts +5 -0
- package/dist/daemon/__tests__/methods/graph.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/methods/graph.test.js +309 -0
- package/dist/daemon/__tests__/methods/graph.test.js.map +1 -0
- package/dist/daemon/__tests__/methods/provider.test.d.ts +7 -0
- package/dist/daemon/__tests__/methods/provider.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/methods/provider.test.js +181 -0
- package/dist/daemon/__tests__/methods/provider.test.js.map +1 -0
- package/dist/daemon/__tests__/methods/tools.test.d.ts +5 -0
- package/dist/daemon/__tests__/methods/tools.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/methods/tools.test.js +587 -0
- package/dist/daemon/__tests__/methods/tools.test.js.map +1 -0
- package/dist/daemon/__tests__/multi-location.test.d.ts +8 -0
- package/dist/daemon/__tests__/multi-location.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/multi-location.test.js +669 -0
- package/dist/daemon/__tests__/multi-location.test.js.map +1 -0
- package/dist/daemon/__tests__/registry.test.d.ts +5 -0
- package/dist/daemon/__tests__/registry.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/registry.test.js +208 -0
- package/dist/daemon/__tests__/registry.test.js.map +1 -0
- package/dist/daemon/__tests__/watcher.test.d.ts +5 -0
- package/dist/daemon/__tests__/watcher.test.d.ts.map +1 -0
- package/dist/daemon/__tests__/watcher.test.js +234 -0
- package/dist/daemon/__tests__/watcher.test.js.map +1 -0
- package/dist/daemon/entire-linker.d.ts +68 -0
- package/dist/daemon/entire-linker.d.ts.map +1 -0
- package/dist/daemon/entire-linker.js +439 -0
- package/dist/daemon/entire-linker.js.map +1 -0
- package/dist/daemon/entire-watcher.d.ts +66 -0
- package/dist/daemon/entire-watcher.d.ts.map +1 -0
- package/dist/daemon/entire-watcher.js +258 -0
- package/dist/daemon/entire-watcher.js.map +1 -0
- package/dist/daemon/factory.d.ts +59 -0
- package/dist/daemon/factory.d.ts.map +1 -0
- package/dist/daemon/factory.js +72 -0
- package/dist/daemon/factory.js.map +1 -0
- package/dist/daemon/flush.d.ts +51 -0
- package/dist/daemon/flush.d.ts.map +1 -0
- package/dist/daemon/flush.js +89 -0
- package/dist/daemon/flush.js.map +1 -0
- package/dist/daemon/index.d.ts +39 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +24 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/ipc.d.ts +97 -0
- package/dist/daemon/ipc.d.ts.map +1 -0
- package/dist/daemon/ipc.js +304 -0
- package/dist/daemon/ipc.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +85 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +754 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/location-state.d.ts +85 -0
- package/dist/daemon/location-state.d.ts.map +1 -0
- package/dist/daemon/location-state.js +291 -0
- package/dist/daemon/location-state.js.map +1 -0
- package/dist/daemon/lock.d.ts +29 -0
- package/dist/daemon/lock.d.ts.map +1 -0
- package/dist/daemon/lock.js +131 -0
- package/dist/daemon/lock.js.map +1 -0
- package/dist/daemon/methods/__tests__/graph.test.d.ts +5 -0
- package/dist/daemon/methods/__tests__/graph.test.d.ts.map +1 -0
- package/dist/daemon/methods/__tests__/graph.test.js +274 -0
- package/dist/daemon/methods/__tests__/graph.test.js.map +1 -0
- package/dist/daemon/methods/__tests__/provider.test.d.ts +5 -0
- package/dist/daemon/methods/__tests__/provider.test.d.ts.map +1 -0
- package/dist/daemon/methods/__tests__/provider.test.js +184 -0
- package/dist/daemon/methods/__tests__/provider.test.js.map +1 -0
- package/dist/daemon/methods/__tests__/tools.test.d.ts +5 -0
- package/dist/daemon/methods/__tests__/tools.test.d.ts.map +1 -0
- package/dist/daemon/methods/__tests__/tools.test.js +295 -0
- package/dist/daemon/methods/__tests__/tools.test.js.map +1 -0
- package/dist/daemon/methods/archive.d.ts +22 -0
- package/dist/daemon/methods/archive.d.ts.map +1 -0
- package/dist/daemon/methods/archive.js +107 -0
- package/dist/daemon/methods/archive.js.map +1 -0
- package/dist/daemon/methods/graph.d.ts +26 -0
- package/dist/daemon/methods/graph.d.ts.map +1 -0
- package/dist/daemon/methods/graph.js +157 -0
- package/dist/daemon/methods/graph.js.map +1 -0
- package/dist/daemon/methods/lifecycle.d.ts +54 -0
- package/dist/daemon/methods/lifecycle.d.ts.map +1 -0
- package/dist/daemon/methods/lifecycle.js +46 -0
- package/dist/daemon/methods/lifecycle.js.map +1 -0
- package/dist/daemon/methods/location.d.ts +24 -0
- package/dist/daemon/methods/location.d.ts.map +1 -0
- package/dist/daemon/methods/location.js +72 -0
- package/dist/daemon/methods/location.js.map +1 -0
- package/dist/daemon/methods/provider.d.ts +22 -0
- package/dist/daemon/methods/provider.d.ts.map +1 -0
- package/dist/daemon/methods/provider.js +72 -0
- package/dist/daemon/methods/provider.js.map +1 -0
- package/dist/daemon/methods/tools.d.ts +23 -0
- package/dist/daemon/methods/tools.d.ts.map +1 -0
- package/dist/daemon/methods/tools.js +111 -0
- package/dist/daemon/methods/tools.js.map +1 -0
- package/dist/daemon/registry.d.ts +35 -0
- package/dist/daemon/registry.d.ts.map +1 -0
- package/dist/daemon/registry.js +189 -0
- package/dist/daemon/registry.js.map +1 -0
- package/dist/daemon/types.d.ts +101 -0
- package/dist/daemon/types.d.ts.map +1 -0
- package/dist/daemon/types.js +19 -0
- package/dist/daemon/types.js.map +1 -0
- package/dist/daemon/watcher.d.ts +62 -0
- package/dist/daemon/watcher.d.ts.map +1 -0
- package/dist/daemon/watcher.js +142 -0
- package/dist/daemon/watcher.js.map +1 -0
- package/dist/graph/EdgeTypeRegistry.d.ts +134 -0
- package/dist/graph/EdgeTypeRegistry.d.ts.map +1 -0
- package/dist/graph/EdgeTypeRegistry.js +255 -0
- package/dist/graph/EdgeTypeRegistry.js.map +1 -0
- package/dist/graph/FederatedGraph.d.ts +296 -0
- package/dist/graph/FederatedGraph.d.ts.map +1 -0
- package/dist/graph/FederatedGraph.js +406 -0
- package/dist/graph/FederatedGraph.js.map +1 -0
- package/dist/graph/GraphologyAdapter.d.ts +151 -0
- package/dist/graph/GraphologyAdapter.d.ts.map +1 -0
- package/dist/graph/GraphologyAdapter.js +209 -0
- package/dist/graph/GraphologyAdapter.js.map +1 -0
- package/dist/graph/HydratingFederatedGraph.d.ts +151 -0
- package/dist/graph/HydratingFederatedGraph.d.ts.map +1 -0
- package/dist/graph/HydratingFederatedGraph.js +327 -0
- package/dist/graph/HydratingFederatedGraph.js.map +1 -0
- package/dist/graph/__tests__/EdgeTypeRegistry.test.d.ts +2 -0
- package/dist/graph/__tests__/EdgeTypeRegistry.test.d.ts.map +1 -0
- package/dist/graph/__tests__/EdgeTypeRegistry.test.js +212 -0
- package/dist/graph/__tests__/EdgeTypeRegistry.test.js.map +1 -0
- package/dist/graph/__tests__/FederatedGraph.test.d.ts +2 -0
- package/dist/graph/__tests__/FederatedGraph.test.d.ts.map +1 -0
- package/dist/graph/__tests__/FederatedGraph.test.js +661 -0
- package/dist/graph/__tests__/FederatedGraph.test.js.map +1 -0
- package/dist/graph/__tests__/GraphologyAdapter.test.d.ts +2 -0
- package/dist/graph/__tests__/GraphologyAdapter.test.d.ts.map +1 -0
- package/dist/graph/__tests__/GraphologyAdapter.test.js +326 -0
- package/dist/graph/__tests__/GraphologyAdapter.test.js.map +1 -0
- package/dist/graph/__tests__/HydratingFederatedGraph.test.d.ts +2 -0
- package/dist/graph/__tests__/HydratingFederatedGraph.test.d.ts.map +1 -0
- package/dist/graph/__tests__/HydratingFederatedGraph.test.js +587 -0
- package/dist/graph/__tests__/HydratingFederatedGraph.test.js.map +1 -0
- package/dist/graph/__tests__/debounce.test.d.ts +5 -0
- package/dist/graph/__tests__/debounce.test.d.ts.map +1 -0
- package/dist/graph/__tests__/debounce.test.js +195 -0
- package/dist/graph/__tests__/debounce.test.js.map +1 -0
- package/dist/graph/__tests__/edge-cases.test.d.ts +8 -0
- package/dist/graph/__tests__/edge-cases.test.d.ts.map +1 -0
- package/dist/graph/__tests__/edge-cases.test.js +472 -0
- package/dist/graph/__tests__/edge-cases.test.js.map +1 -0
- package/dist/graph/__tests__/expansion.test.d.ts +2 -0
- package/dist/graph/__tests__/expansion.test.d.ts.map +1 -0
- package/dist/graph/__tests__/expansion.test.js +105 -0
- package/dist/graph/__tests__/expansion.test.js.map +1 -0
- package/dist/graph/__tests__/provider-store.test.d.ts +5 -0
- package/dist/graph/__tests__/provider-store.test.d.ts.map +1 -0
- package/dist/graph/__tests__/provider-store.test.js +791 -0
- package/dist/graph/__tests__/provider-store.test.js.map +1 -0
- package/dist/graph/__tests__/query.test.d.ts +5 -0
- package/dist/graph/__tests__/query.test.d.ts.map +1 -0
- package/dist/graph/__tests__/query.test.js +774 -0
- package/dist/graph/__tests__/query.test.js.map +1 -0
- package/dist/graph/__tests__/store.test.d.ts +5 -0
- package/dist/graph/__tests__/store.test.d.ts.map +1 -0
- package/dist/graph/__tests__/store.test.js +489 -0
- package/dist/graph/__tests__/store.test.js.map +1 -0
- package/dist/graph/__tests__/sync.test.d.ts +5 -0
- package/dist/graph/__tests__/sync.test.d.ts.map +1 -0
- package/dist/graph/__tests__/sync.test.js +129 -0
- package/dist/graph/__tests__/sync.test.js.map +1 -0
- package/dist/graph/__tests__/validation.test.d.ts +2 -0
- package/dist/graph/__tests__/validation.test.d.ts.map +1 -0
- package/dist/graph/__tests__/validation.test.js +521 -0
- package/dist/graph/__tests__/validation.test.js.map +1 -0
- package/dist/graph/coordination.d.ts +190 -0
- package/dist/graph/coordination.d.ts.map +1 -0
- package/dist/graph/coordination.js +180 -0
- package/dist/graph/coordination.js.map +1 -0
- package/dist/graph/debounce.d.ts +47 -0
- package/dist/graph/debounce.d.ts.map +1 -0
- package/dist/graph/debounce.js +95 -0
- package/dist/graph/debounce.js.map +1 -0
- package/dist/graph/expansion.d.ts +64 -0
- package/dist/graph/expansion.d.ts.map +1 -0
- package/dist/graph/expansion.js +205 -0
- package/dist/graph/expansion.js.map +1 -0
- package/dist/graph/history.d.ts +186 -0
- package/dist/graph/history.d.ts.map +1 -0
- package/dist/graph/history.js +155 -0
- package/dist/graph/history.js.map +1 -0
- package/dist/graph/index.d.ts +35 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +22 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/provider-store.d.ts +211 -0
- package/dist/graph/provider-store.d.ts.map +1 -0
- package/dist/graph/provider-store.js +568 -0
- package/dist/graph/provider-store.js.map +1 -0
- package/dist/graph/query.d.ts +90 -0
- package/dist/graph/query.d.ts.map +1 -0
- package/dist/graph/query.js +463 -0
- package/dist/graph/query.js.map +1 -0
- package/dist/graph/store.d.ts +71 -0
- package/dist/graph/store.d.ts.map +1 -0
- package/dist/graph/store.js +530 -0
- package/dist/graph/store.js.map +1 -0
- package/dist/graph/sync.d.ts +48 -0
- package/dist/graph/sync.d.ts.map +1 -0
- package/dist/graph/sync.js +60 -0
- package/dist/graph/sync.js.map +1 -0
- package/dist/graph/types.d.ts +289 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +19 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/graph/validation.d.ts +26 -0
- package/dist/graph/validation.d.ts.map +1 -0
- package/dist/graph/validation.js +338 -0
- package/dist/graph/validation.js.map +1 -0
- package/dist/index.d.ts +25 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -25
- package/dist/index.js.map +1 -1
- package/dist/materialization/archiver.d.ts +12 -0
- package/dist/materialization/archiver.d.ts.map +1 -0
- package/dist/materialization/archiver.js +273 -0
- package/dist/materialization/archiver.js.map +1 -0
- package/dist/materialization/git-archive-store.d.ts +17 -0
- package/dist/materialization/git-archive-store.d.ts.map +1 -0
- package/dist/materialization/git-archive-store.js +509 -0
- package/dist/materialization/git-archive-store.js.map +1 -0
- package/dist/materialization/git-remote-store.d.ts +22 -0
- package/dist/materialization/git-remote-store.d.ts.map +1 -0
- package/dist/materialization/git-remote-store.js +448 -0
- package/dist/materialization/git-remote-store.js.map +1 -0
- package/dist/materialization/graph-id.d.ts +53 -0
- package/dist/materialization/graph-id.d.ts.map +1 -0
- package/dist/materialization/graph-id.js +163 -0
- package/dist/materialization/graph-id.js.map +1 -0
- package/dist/materialization/http-remote-store.d.ts +12 -0
- package/dist/materialization/http-remote-store.d.ts.map +1 -0
- package/dist/materialization/http-remote-store.js +143 -0
- package/dist/materialization/http-remote-store.js.map +1 -0
- package/dist/materialization/index.d.ts +15 -0
- package/dist/materialization/index.d.ts.map +1 -0
- package/dist/materialization/index.js +17 -0
- package/dist/materialization/index.js.map +1 -0
- package/dist/materialization/remote-store-factory.d.ts +17 -0
- package/dist/materialization/remote-store-factory.d.ts.map +1 -0
- package/dist/materialization/remote-store-factory.js +46 -0
- package/dist/materialization/remote-store-factory.js.map +1 -0
- package/dist/materialization/snapshot.d.ts +34 -0
- package/dist/materialization/snapshot.d.ts.map +1 -0
- package/dist/materialization/snapshot.js +177 -0
- package/dist/materialization/snapshot.js.map +1 -0
- package/dist/materialization/types.d.ts +300 -0
- package/dist/materialization/types.d.ts.map +1 -0
- package/dist/materialization/types.js +17 -0
- package/dist/materialization/types.js.map +1 -0
- package/dist/providers/__tests__/beads.test.d.ts +5 -0
- package/dist/providers/__tests__/beads.test.d.ts.map +1 -0
- package/dist/providers/__tests__/beads.test.js +591 -0
- package/dist/providers/__tests__/beads.test.js.map +1 -0
- package/dist/providers/__tests__/claude-tasks.test.d.ts +5 -0
- package/dist/providers/__tests__/claude-tasks.test.d.ts.map +1 -0
- package/dist/providers/__tests__/claude-tasks.test.js +392 -0
- package/dist/providers/__tests__/claude-tasks.test.js.map +1 -0
- package/dist/providers/__tests__/from-config.test.d.ts +5 -0
- package/dist/providers/__tests__/from-config.test.d.ts.map +1 -0
- package/dist/providers/__tests__/from-config.test.js +152 -0
- package/dist/providers/__tests__/from-config.test.js.map +1 -0
- package/dist/providers/__tests__/materialization.test.d.ts +5 -0
- package/dist/providers/__tests__/materialization.test.d.ts.map +1 -0
- package/dist/providers/__tests__/materialization.test.js +407 -0
- package/dist/providers/__tests__/materialization.test.js.map +1 -0
- package/dist/providers/__tests__/native.test.d.ts +5 -0
- package/dist/providers/__tests__/native.test.d.ts.map +1 -0
- package/dist/providers/__tests__/native.test.js +566 -0
- package/dist/providers/__tests__/native.test.js.map +1 -0
- package/dist/providers/__tests__/registry.test.d.ts +5 -0
- package/dist/providers/__tests__/registry.test.d.ts.map +1 -0
- package/dist/providers/__tests__/registry.test.js +183 -0
- package/dist/providers/__tests__/registry.test.js.map +1 -0
- package/dist/providers/beads.d.ts +46 -0
- package/dist/providers/beads.d.ts.map +1 -0
- package/dist/providers/beads.js +865 -0
- package/dist/providers/beads.js.map +1 -0
- package/dist/providers/claude-tasks.d.ts +56 -0
- package/dist/providers/claude-tasks.d.ts.map +1 -0
- package/dist/providers/claude-tasks.js +282 -0
- package/dist/providers/claude-tasks.js.map +1 -0
- package/dist/providers/entire.d.ts +88 -0
- package/dist/providers/entire.d.ts.map +1 -0
- package/dist/providers/entire.js +409 -0
- package/dist/providers/entire.js.map +1 -0
- package/dist/providers/from-config.d.ts +47 -0
- package/dist/providers/from-config.d.ts.map +1 -0
- package/dist/providers/from-config.js +150 -0
- package/dist/providers/from-config.js.map +1 -0
- package/dist/providers/index.d.ts +26 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +29 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/location.d.ts +44 -0
- package/dist/providers/location.d.ts.map +1 -0
- package/dist/providers/location.js +157 -0
- package/dist/providers/location.js.map +1 -0
- package/dist/providers/materialization.d.ts +46 -0
- package/dist/providers/materialization.d.ts.map +1 -0
- package/dist/providers/materialization.js +237 -0
- package/dist/providers/materialization.js.map +1 -0
- package/dist/providers/native.d.ts +32 -0
- package/dist/providers/native.d.ts.map +1 -0
- package/dist/providers/native.js +552 -0
- package/dist/providers/native.js.map +1 -0
- package/dist/providers/registry.d.ts +11 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +97 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/sudocode.d.ts +49 -0
- package/dist/providers/sudocode.d.ts.map +1 -0
- package/dist/providers/sudocode.js +945 -0
- package/dist/providers/sudocode.js.map +1 -0
- package/dist/providers/sync.d.ts +299 -0
- package/dist/providers/sync.d.ts.map +1 -0
- package/dist/providers/sync.js +93 -0
- package/dist/providers/sync.js.map +1 -0
- package/dist/providers/traits/RelationshipQueryable.d.ts +129 -0
- package/dist/providers/traits/RelationshipQueryable.d.ts.map +1 -0
- package/dist/providers/traits/RelationshipQueryable.js +68 -0
- package/dist/providers/traits/RelationshipQueryable.js.map +1 -0
- package/dist/providers/traits/TaskManageable.d.ts +157 -0
- package/dist/providers/traits/TaskManageable.d.ts.map +1 -0
- package/dist/providers/traits/TaskManageable.js +37 -0
- package/dist/providers/traits/TaskManageable.js.map +1 -0
- package/dist/providers/traits/Watchable.d.ts +216 -0
- package/dist/providers/traits/Watchable.d.ts.map +1 -0
- package/dist/providers/traits/Watchable.js +37 -0
- package/dist/providers/traits/Watchable.js.map +1 -0
- package/dist/providers/traits/__tests__/RelationshipQueryable.test.d.ts +2 -0
- package/dist/providers/traits/__tests__/RelationshipQueryable.test.d.ts.map +1 -0
- package/dist/providers/traits/__tests__/RelationshipQueryable.test.js +169 -0
- package/dist/providers/traits/__tests__/RelationshipQueryable.test.js.map +1 -0
- package/dist/providers/traits/__tests__/TaskManageable.test.d.ts +2 -0
- package/dist/providers/traits/__tests__/TaskManageable.test.d.ts.map +1 -0
- package/dist/providers/traits/__tests__/TaskManageable.test.js +172 -0
- package/dist/providers/traits/__tests__/TaskManageable.test.js.map +1 -0
- package/dist/providers/traits/index.d.ts +13 -0
- package/dist/providers/traits/index.d.ts.map +1 -0
- package/dist/providers/traits/index.js +10 -0
- package/dist/providers/traits/index.js.map +1 -0
- package/dist/providers/types.d.ts +284 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +30 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/schema/__tests__/validation.test.d.ts +2 -0
- package/dist/schema/__tests__/validation.test.d.ts.map +1 -0
- package/dist/schema/__tests__/validation.test.js +241 -0
- package/dist/schema/__tests__/validation.test.js.map +1 -0
- package/dist/schema/base.d.ts +68 -0
- package/dist/schema/base.d.ts.map +1 -0
- package/dist/schema/base.js +5 -0
- package/dist/schema/base.js.map +1 -0
- package/dist/schema/edges.d.ts +49 -0
- package/dist/schema/edges.d.ts.map +1 -0
- package/dist/schema/edges.js +9 -0
- package/dist/schema/edges.js.map +1 -0
- package/dist/schema/index.d.ts +11 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +8 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/nodes.d.ts +97 -0
- package/dist/schema/nodes.d.ts.map +1 -0
- package/dist/schema/nodes.js +5 -0
- package/dist/schema/nodes.js.map +1 -0
- package/dist/schema/storage.d.ts +107 -0
- package/dist/schema/storage.d.ts.map +1 -0
- package/dist/schema/storage.js +10 -0
- package/dist/schema/storage.js.map +1 -0
- package/dist/schema/validation.d.ts +61 -0
- package/dist/schema/validation.d.ts.map +1 -0
- package/dist/schema/validation.js +170 -0
- package/dist/schema/validation.js.map +1 -0
- package/dist/storage/__tests__/atomic-write.test.d.ts +5 -0
- package/dist/storage/__tests__/atomic-write.test.d.ts.map +1 -0
- package/dist/storage/__tests__/atomic-write.test.js +170 -0
- package/dist/storage/__tests__/atomic-write.test.js.map +1 -0
- package/dist/storage/__tests__/file-lock.test.d.ts +2 -0
- package/dist/storage/__tests__/file-lock.test.d.ts.map +1 -0
- package/dist/storage/__tests__/file-lock.test.js +89 -0
- package/dist/storage/__tests__/file-lock.test.js.map +1 -0
- package/dist/storage/__tests__/jsonl.test.d.ts +2 -0
- package/dist/storage/__tests__/jsonl.test.d.ts.map +1 -0
- package/dist/storage/__tests__/jsonl.test.js +228 -0
- package/dist/storage/__tests__/jsonl.test.js.map +1 -0
- package/dist/storage/__tests__/locked-writer.test.d.ts +2 -0
- package/dist/storage/__tests__/locked-writer.test.d.ts.map +1 -0
- package/dist/storage/__tests__/locked-writer.test.js +109 -0
- package/dist/storage/__tests__/locked-writer.test.js.map +1 -0
- package/dist/storage/__tests__/sqlite.test.d.ts +2 -0
- package/dist/storage/__tests__/sqlite.test.d.ts.map +1 -0
- package/dist/storage/__tests__/sqlite.test.js +470 -0
- package/dist/storage/__tests__/sqlite.test.js.map +1 -0
- package/dist/storage/atomic-write.d.ts +38 -0
- package/dist/storage/atomic-write.d.ts.map +1 -0
- package/dist/storage/atomic-write.js +83 -0
- package/dist/storage/atomic-write.js.map +1 -0
- package/dist/storage/file-lock.d.ts +66 -0
- package/dist/storage/file-lock.d.ts.map +1 -0
- package/dist/storage/file-lock.js +176 -0
- package/dist/storage/file-lock.js.map +1 -0
- package/dist/storage/index.d.ts +11 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +13 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/interface.d.ts +219 -0
- package/dist/storage/interface.d.ts.map +1 -0
- package/dist/storage/interface.js +22 -0
- package/dist/storage/interface.js.map +1 -0
- package/dist/storage/jsonl.d.ts +106 -0
- package/dist/storage/jsonl.d.ts.map +1 -0
- package/dist/storage/jsonl.js +218 -0
- package/dist/storage/jsonl.js.map +1 -0
- package/dist/storage/locked-writer.d.ts +67 -0
- package/dist/storage/locked-writer.d.ts.map +1 -0
- package/dist/storage/locked-writer.js +105 -0
- package/dist/storage/locked-writer.js.map +1 -0
- package/dist/storage/sqlite-schema.d.ts +48 -0
- package/dist/storage/sqlite-schema.d.ts.map +1 -0
- package/dist/storage/sqlite-schema.js +169 -0
- package/dist/storage/sqlite-schema.js.map +1 -0
- package/dist/storage/sqlite.d.ts +73 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +698 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/tools/__tests__/annotate.test.d.ts +5 -0
- package/dist/tools/__tests__/annotate.test.d.ts.map +1 -0
- package/dist/tools/__tests__/annotate.test.js +314 -0
- package/dist/tools/__tests__/annotate.test.js.map +1 -0
- package/dist/tools/__tests__/link.test.d.ts +5 -0
- package/dist/tools/__tests__/link.test.d.ts.map +1 -0
- package/dist/tools/__tests__/link.test.js +245 -0
- package/dist/tools/__tests__/link.test.js.map +1 -0
- package/dist/tools/__tests__/query.test.d.ts +5 -0
- package/dist/tools/__tests__/query.test.d.ts.map +1 -0
- package/dist/tools/__tests__/query.test.js +288 -0
- package/dist/tools/__tests__/query.test.js.map +1 -0
- package/dist/tools/__tests__/task.test.d.ts +5 -0
- package/dist/tools/__tests__/task.test.d.ts.map +1 -0
- package/dist/tools/__tests__/task.test.js +178 -0
- package/dist/tools/__tests__/task.test.js.map +1 -0
- package/dist/tools/annotate.d.ts +17 -0
- package/dist/tools/annotate.d.ts.map +1 -0
- package/dist/tools/annotate.js +218 -0
- package/dist/tools/annotate.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/link.d.ts +17 -0
- package/dist/tools/link.d.ts.map +1 -0
- package/dist/tools/link.js +127 -0
- package/dist/tools/link.js.map +1 -0
- package/dist/tools/query.d.ts +17 -0
- package/dist/tools/query.d.ts.map +1 -0
- package/dist/tools/query.js +342 -0
- package/dist/tools/query.js.map +1 -0
- package/dist/tools/task.d.ts +20 -0
- package/dist/tools/task.d.ts.map +1 -0
- package/dist/tools/task.js +161 -0
- package/dist/tools/task.js.map +1 -0
- package/dist/tools/types.d.ts +334 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +19 -0
- package/dist/tools/types.js.map +1 -0
- package/package.json +40 -5
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sudocode Provider
|
|
3
|
+
*
|
|
4
|
+
* Provider that integrates with sudocode via CLI.
|
|
5
|
+
* Handles sudocode:// and sc:// URI schemes.
|
|
6
|
+
*
|
|
7
|
+
* Sudocode is a git-native agent orchestration system that stores
|
|
8
|
+
* specs, issues, and relationships in .sudocode/ (JSONL + SQLite).
|
|
9
|
+
* This provider bridges sudocode data into the OpenTasks graph.
|
|
10
|
+
*/
|
|
11
|
+
import { exec as execCallback } from 'child_process';
|
|
12
|
+
import { readFile } from 'fs/promises';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import { promisify } from 'util';
|
|
15
|
+
import chokidar from 'chokidar';
|
|
16
|
+
import { ProviderError as ProviderErrorClass } from './types.js';
|
|
17
|
+
import { filterEdgesByType, filterEdgesByDirection } from './traits/RelationshipQueryable.js';
|
|
18
|
+
const execAsync = promisify(execCallback);
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Constants
|
|
21
|
+
// ============================================================================
|
|
22
|
+
/**
|
|
23
|
+
* Pattern for sudocode:// or sc:// URIs
|
|
24
|
+
* Format: sudocode://[workspace/]id or sc://[workspace/]id
|
|
25
|
+
*/
|
|
26
|
+
const SUDOCODE_URI_PATTERN = /^(sudocode|sc):\/\/(?:([^/]+)\/)?(.+)$/i;
|
|
27
|
+
/**
|
|
28
|
+
* Pattern for Sudocode spec IDs (e.g., s-14sh, SPEC-001)
|
|
29
|
+
*/
|
|
30
|
+
const SPEC_ID_PATTERN = /^(?:s-[a-z0-9]+|SPEC-\d+)$/i;
|
|
31
|
+
/**
|
|
32
|
+
* Pattern for Sudocode issue IDs (e.g., i-x7k9, ISSUE-001)
|
|
33
|
+
*/
|
|
34
|
+
const ISSUE_ID_PATTERN = /^(?:i-[a-z0-9]+|ISSUE-\d+)$/i;
|
|
35
|
+
/**
|
|
36
|
+
* Pattern for any Sudocode entity ID
|
|
37
|
+
*/
|
|
38
|
+
const ENTITY_ID_PATTERN = /^(?:[si]-[a-z0-9]+|(?:SPEC|ISSUE)-\d+)$/i;
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Helper Functions
|
|
41
|
+
// ============================================================================
|
|
42
|
+
/**
|
|
43
|
+
* Determine entity type from ID prefix
|
|
44
|
+
*/
|
|
45
|
+
function entityTypeFromId(id) {
|
|
46
|
+
if (id.startsWith('s-') || id.toUpperCase().startsWith('SPEC-')) {
|
|
47
|
+
return 'spec';
|
|
48
|
+
}
|
|
49
|
+
return 'issue';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Valid task actions for each status state
|
|
53
|
+
*/
|
|
54
|
+
function validActionsForStatus(status) {
|
|
55
|
+
switch (status) {
|
|
56
|
+
case 'open':
|
|
57
|
+
return ['start', 'block', 'close'];
|
|
58
|
+
case 'in_progress':
|
|
59
|
+
return ['complete', 'block', 'close'];
|
|
60
|
+
case 'blocked':
|
|
61
|
+
return ['reopen', 'close'];
|
|
62
|
+
case 'closed':
|
|
63
|
+
return ['reopen'];
|
|
64
|
+
default:
|
|
65
|
+
return ['start', 'complete', 'block', 'reopen', 'close'];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Map Sudocode priority to normalized 0-4 scale
|
|
70
|
+
*/
|
|
71
|
+
function mapPriority(priority) {
|
|
72
|
+
if (priority === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
return Math.max(0, Math.min(4, priority));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Map Sudocode relationship types to OpenTasks edge types
|
|
78
|
+
*/
|
|
79
|
+
function mapRelationshipType(relType) {
|
|
80
|
+
switch (relType.toLowerCase()) {
|
|
81
|
+
case 'blocks':
|
|
82
|
+
return 'blocks';
|
|
83
|
+
case 'related':
|
|
84
|
+
return 'related';
|
|
85
|
+
case 'discovered-from':
|
|
86
|
+
return 'discovered-from';
|
|
87
|
+
case 'implements':
|
|
88
|
+
return 'implements';
|
|
89
|
+
case 'references':
|
|
90
|
+
return 'references';
|
|
91
|
+
case 'depends-on':
|
|
92
|
+
return 'depends-on';
|
|
93
|
+
default:
|
|
94
|
+
return relType;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Normalize relationships from either format into a flat array
|
|
99
|
+
* with consistent `from_id`, `to_id`, `relationship_type` fields.
|
|
100
|
+
*/
|
|
101
|
+
function normalizeRelationships(rels) {
|
|
102
|
+
if (!rels)
|
|
103
|
+
return [];
|
|
104
|
+
let flat;
|
|
105
|
+
if (Array.isArray(rels)) {
|
|
106
|
+
// JSONL format: flat array with from/to/type
|
|
107
|
+
flat = rels;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// show --json format: { outgoing, incoming } with from_id/to_id/relationship_type
|
|
111
|
+
flat = [...(rels.outgoing ?? []), ...(rels.incoming ?? [])];
|
|
112
|
+
}
|
|
113
|
+
// Normalize field names so consumers can always use from_id/to_id/relationship_type
|
|
114
|
+
return flat.map((rel) => ({
|
|
115
|
+
...rel,
|
|
116
|
+
from_id: rel.from_id ?? rel.from,
|
|
117
|
+
to_id: rel.to_id ?? rel.to,
|
|
118
|
+
relationship_type: rel.relationship_type ?? rel.type,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Check if an entity is archived (handles both boolean and number)
|
|
123
|
+
*/
|
|
124
|
+
function isArchived(entity) {
|
|
125
|
+
return entity.archived === true || entity.archived === 1;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Convert Sudocode spec to ProviderNode
|
|
129
|
+
*/
|
|
130
|
+
function specToProviderNode(spec, workspace = '.') {
|
|
131
|
+
return {
|
|
132
|
+
id: spec.id,
|
|
133
|
+
uri: `sudocode://${workspace}/${spec.id}`,
|
|
134
|
+
type: 'spec',
|
|
135
|
+
title: spec.title,
|
|
136
|
+
content: spec.content,
|
|
137
|
+
status: isArchived(spec) ? 'archived' : 'active',
|
|
138
|
+
priority: mapPriority(spec.priority),
|
|
139
|
+
rawData: spec,
|
|
140
|
+
fetchedAt: new Date().toISOString(),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Convert Sudocode issue to ProviderNode
|
|
145
|
+
*/
|
|
146
|
+
function issueToProviderNode(issue, workspace = '.') {
|
|
147
|
+
return {
|
|
148
|
+
id: issue.id,
|
|
149
|
+
uri: `sudocode://${workspace}/${issue.id}`,
|
|
150
|
+
type: 'issue',
|
|
151
|
+
title: issue.title,
|
|
152
|
+
content: issue.content,
|
|
153
|
+
status: issue.status,
|
|
154
|
+
priority: mapPriority(issue.priority),
|
|
155
|
+
rawData: issue,
|
|
156
|
+
fetchedAt: new Date().toISOString(),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Convert any Sudocode entity to ProviderNode
|
|
161
|
+
*/
|
|
162
|
+
function entityToProviderNode(entity, workspace = '.') {
|
|
163
|
+
const entityType = entityTypeFromId(entity.id);
|
|
164
|
+
if (entityType === 'spec') {
|
|
165
|
+
return specToProviderNode(entity, workspace);
|
|
166
|
+
}
|
|
167
|
+
return issueToProviderNode(entity, workspace);
|
|
168
|
+
}
|
|
169
|
+
// ============================================================================
|
|
170
|
+
// Sudocode Provider Implementation
|
|
171
|
+
// ============================================================================
|
|
172
|
+
/**
|
|
173
|
+
* Create a Sudocode provider with relationship querying and optional watching support
|
|
174
|
+
*/
|
|
175
|
+
export function createSudocodeProvider(config = {}) {
|
|
176
|
+
const executable = config.executable ?? 'sudocode';
|
|
177
|
+
const cwd = config.cwd;
|
|
178
|
+
const timeout = config.timeout ?? 30000;
|
|
179
|
+
const extraArgs = config.extraArgs ?? [];
|
|
180
|
+
const watchPath = config.watchPath;
|
|
181
|
+
const watchDebounceMs = config.watchDebounceMs ?? 200;
|
|
182
|
+
const capabilities = {
|
|
183
|
+
read: true,
|
|
184
|
+
write: true,
|
|
185
|
+
search: true,
|
|
186
|
+
watch: !!watchPath,
|
|
187
|
+
mount: true,
|
|
188
|
+
feedback: false,
|
|
189
|
+
};
|
|
190
|
+
// =========================================================================
|
|
191
|
+
// Watch State (only active when watchPath is configured)
|
|
192
|
+
// =========================================================================
|
|
193
|
+
/** Cached content hashes for change diffing: entity id → hash of serialized data */
|
|
194
|
+
const cachedHashes = new Map();
|
|
195
|
+
/** Cached edge signatures for edge change detection: "from\0to\0type" → true */
|
|
196
|
+
const cachedEdgeKeys = new Set();
|
|
197
|
+
/** chokidar watcher instance */
|
|
198
|
+
let fileWatcher = null;
|
|
199
|
+
/** Current watch callback */
|
|
200
|
+
let watchCallback = null;
|
|
201
|
+
/** Debounce timer for coalescing rapid file changes */
|
|
202
|
+
let debounceTimer = null;
|
|
203
|
+
/**
|
|
204
|
+
* Simple hash for diffing (not cryptographic — just for detecting changes).
|
|
205
|
+
*/
|
|
206
|
+
function quickHash(input) {
|
|
207
|
+
let hash = 0;
|
|
208
|
+
for (let i = 0; i < input.length; i++) {
|
|
209
|
+
const char = input.charCodeAt(i);
|
|
210
|
+
hash = (hash << 5) - hash + char;
|
|
211
|
+
hash = hash & hash;
|
|
212
|
+
}
|
|
213
|
+
return hash.toString(16);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Build a stable hash key for a Sudocode entity (substantive fields only)
|
|
217
|
+
*/
|
|
218
|
+
function entityHashKey(entity) {
|
|
219
|
+
const substantive = {
|
|
220
|
+
title: entity.title,
|
|
221
|
+
content: entity.content,
|
|
222
|
+
status: 'status' in entity ? entity.status : undefined,
|
|
223
|
+
priority: entity.priority,
|
|
224
|
+
tags: entity.tags ? [...entity.tags].sort() : undefined,
|
|
225
|
+
archived: entity.archived,
|
|
226
|
+
parent_id: entity.parent_id,
|
|
227
|
+
};
|
|
228
|
+
return quickHash(JSON.stringify(substantive));
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Compute the set of edge keys from an entity's relationship fields
|
|
232
|
+
*/
|
|
233
|
+
function entityEdgeKeys(entity) {
|
|
234
|
+
const keys = new Set();
|
|
235
|
+
for (const rel of normalizeRelationships(entity.relationships)) {
|
|
236
|
+
if (rel.from_id && rel.to_id && rel.relationship_type) {
|
|
237
|
+
const edgeType = mapRelationshipType(rel.relationship_type);
|
|
238
|
+
keys.add(`${rel.from_id}\0${rel.to_id}\0${edgeType}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (entity.parent_id) {
|
|
242
|
+
keys.add(`${entity.parent_id}\0${entity.id}\0parent-of`);
|
|
243
|
+
}
|
|
244
|
+
return keys;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Diff current Sudocode state against cached hashes and emit change events.
|
|
248
|
+
*/
|
|
249
|
+
async function diffAndEmit() {
|
|
250
|
+
if (!watchCallback)
|
|
251
|
+
return;
|
|
252
|
+
try {
|
|
253
|
+
// Read entities directly from JSONL files (includes relationships)
|
|
254
|
+
const issues = await readEntitiesFromJsonl('issue');
|
|
255
|
+
const specs = await readEntitiesFromJsonl('spec');
|
|
256
|
+
const allEntities = [...issues, ...specs];
|
|
257
|
+
const currentIds = new Set();
|
|
258
|
+
const currentEdgeKeys = new Set();
|
|
259
|
+
for (const entity of allEntities) {
|
|
260
|
+
if (isArchived(entity)) {
|
|
261
|
+
if (cachedHashes.has(entity.id)) {
|
|
262
|
+
watchCallback({
|
|
263
|
+
kind: 'node',
|
|
264
|
+
event: {
|
|
265
|
+
type: 'deleted',
|
|
266
|
+
nodeId: entity.id,
|
|
267
|
+
uri: `sudocode://./${entity.id}`,
|
|
268
|
+
timestamp: new Date().toISOString(),
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
cachedHashes.delete(entity.id);
|
|
272
|
+
}
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
currentIds.add(entity.id);
|
|
276
|
+
const hash = entityHashKey(entity);
|
|
277
|
+
const prevHash = cachedHashes.get(entity.id);
|
|
278
|
+
const providerNode = entityToProviderNode(entity);
|
|
279
|
+
if (!prevHash) {
|
|
280
|
+
watchCallback({
|
|
281
|
+
kind: 'node',
|
|
282
|
+
event: {
|
|
283
|
+
type: 'created',
|
|
284
|
+
nodeId: entity.id,
|
|
285
|
+
uri: providerNode.uri,
|
|
286
|
+
node: providerNode,
|
|
287
|
+
timestamp: new Date().toISOString(),
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
else if (prevHash !== hash) {
|
|
292
|
+
const event = {
|
|
293
|
+
type: 'updated',
|
|
294
|
+
nodeId: entity.id,
|
|
295
|
+
uri: providerNode.uri,
|
|
296
|
+
node: providerNode,
|
|
297
|
+
timestamp: new Date().toISOString(),
|
|
298
|
+
};
|
|
299
|
+
watchCallback({ kind: 'node', event });
|
|
300
|
+
}
|
|
301
|
+
cachedHashes.set(entity.id, hash);
|
|
302
|
+
for (const key of entityEdgeKeys(entity)) {
|
|
303
|
+
currentEdgeKeys.add(key);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Detect deleted entities
|
|
307
|
+
for (const [id] of cachedHashes) {
|
|
308
|
+
if (!currentIds.has(id)) {
|
|
309
|
+
watchCallback({
|
|
310
|
+
kind: 'node',
|
|
311
|
+
event: {
|
|
312
|
+
type: 'deleted',
|
|
313
|
+
nodeId: id,
|
|
314
|
+
uri: `sudocode://./${id}`,
|
|
315
|
+
timestamp: new Date().toISOString(),
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
cachedHashes.delete(id);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// Detect edge changes — new edges
|
|
322
|
+
for (const key of currentEdgeKeys) {
|
|
323
|
+
if (!cachedEdgeKeys.has(key)) {
|
|
324
|
+
const [from, to, type] = key.split('\0');
|
|
325
|
+
const event = {
|
|
326
|
+
type: 'created',
|
|
327
|
+
edge: { from, to, type },
|
|
328
|
+
sourceUri: `sudocode://./${from}`,
|
|
329
|
+
targetUri: `sudocode://./${to}`,
|
|
330
|
+
timestamp: new Date().toISOString(),
|
|
331
|
+
};
|
|
332
|
+
watchCallback({ kind: 'edge', event });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Deleted edges
|
|
336
|
+
for (const key of cachedEdgeKeys) {
|
|
337
|
+
if (!currentEdgeKeys.has(key)) {
|
|
338
|
+
const [from, to, type] = key.split('\0');
|
|
339
|
+
const event = {
|
|
340
|
+
type: 'deleted',
|
|
341
|
+
edge: { from, to, type },
|
|
342
|
+
sourceUri: `sudocode://./${from}`,
|
|
343
|
+
targetUri: `sudocode://./${to}`,
|
|
344
|
+
timestamp: new Date().toISOString(),
|
|
345
|
+
};
|
|
346
|
+
watchCallback({ kind: 'edge', event });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Update cached edge state
|
|
350
|
+
cachedEdgeKeys.clear();
|
|
351
|
+
for (const key of currentEdgeKeys) {
|
|
352
|
+
cachedEdgeKeys.add(key);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// Resilient — log internally but don't crash the watcher
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Handle a raw file change event with debouncing
|
|
361
|
+
*/
|
|
362
|
+
function onFileChange() {
|
|
363
|
+
if (debounceTimer) {
|
|
364
|
+
clearTimeout(debounceTimer);
|
|
365
|
+
}
|
|
366
|
+
debounceTimer = setTimeout(() => {
|
|
367
|
+
debounceTimer = null;
|
|
368
|
+
void diffAndEmit();
|
|
369
|
+
}, watchDebounceMs);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Seed the cached hashes from current Sudocode state
|
|
373
|
+
*/
|
|
374
|
+
async function seedCache() {
|
|
375
|
+
try {
|
|
376
|
+
const issues = await readEntitiesFromJsonl('issue');
|
|
377
|
+
const specs = await readEntitiesFromJsonl('spec');
|
|
378
|
+
const allEntities = [...issues, ...specs];
|
|
379
|
+
cachedHashes.clear();
|
|
380
|
+
cachedEdgeKeys.clear();
|
|
381
|
+
for (const entity of allEntities) {
|
|
382
|
+
if (isArchived(entity))
|
|
383
|
+
continue;
|
|
384
|
+
cachedHashes.set(entity.id, entityHashKey(entity));
|
|
385
|
+
for (const key of entityEdgeKeys(entity)) {
|
|
386
|
+
cachedEdgeKeys.add(key);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
// If we can't seed, first diff will treat everything as 'created'
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Shell-escape a single argument
|
|
396
|
+
*/
|
|
397
|
+
function shellEscape(arg) {
|
|
398
|
+
if (/['\s"\\$`!]/.test(arg)) {
|
|
399
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
400
|
+
}
|
|
401
|
+
return arg;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Execute a sudocode CLI command
|
|
405
|
+
*/
|
|
406
|
+
async function execSudocode(args, context) {
|
|
407
|
+
const command = [executable, ...extraArgs.map(shellEscape), ...args.map(shellEscape)].join(' ');
|
|
408
|
+
try {
|
|
409
|
+
const { stdout } = await execAsync(command, {
|
|
410
|
+
cwd: context?.cwd ?? cwd,
|
|
411
|
+
timeout: context?.timeout ?? timeout,
|
|
412
|
+
env: { ...process.env },
|
|
413
|
+
});
|
|
414
|
+
return stdout.trim();
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
const err = error;
|
|
418
|
+
if (err.code === 'ENOENT') {
|
|
419
|
+
throw new ProviderErrorClass('PROVIDER_ERROR', `Sudocode CLI not found: ${executable}`, 'sudocode');
|
|
420
|
+
}
|
|
421
|
+
if (err.killed) {
|
|
422
|
+
throw new ProviderErrorClass('TIMEOUT', `Command timed out: ${command}`, 'sudocode');
|
|
423
|
+
}
|
|
424
|
+
let errorMessage = err.message ?? 'Unknown error';
|
|
425
|
+
if (err.stdout) {
|
|
426
|
+
try {
|
|
427
|
+
const parsed = JSON.parse(err.stdout);
|
|
428
|
+
if (parsed.error) {
|
|
429
|
+
errorMessage = parsed.error;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
if (err.stdout.trim()) {
|
|
434
|
+
errorMessage = err.stdout.trim();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
throw new ProviderErrorClass('OPERATION_FAILED', `Sudocode CLI error: ${errorMessage}`, 'sudocode', error instanceof Error ? error : undefined);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Parse JSON output from sudocode CLI
|
|
443
|
+
*/
|
|
444
|
+
function parseJson(output) {
|
|
445
|
+
try {
|
|
446
|
+
return JSON.parse(output);
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
throw new ProviderErrorClass('PROVIDER_ERROR', 'Failed to parse Sudocode CLI output as JSON', 'sudocode');
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Fetch entities of a given type from sudocode CLI (list — no relationships)
|
|
454
|
+
*/
|
|
455
|
+
async function fetchEntities(type) {
|
|
456
|
+
const subcommand = type === 'spec' ? 'spec' : 'issue';
|
|
457
|
+
const output = await execSudocode(['--json', subcommand, 'list']);
|
|
458
|
+
return parseJson(output);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Resolve the path to the .sudocode data directory.
|
|
462
|
+
* Prefers watchPath, then derives from cwd.
|
|
463
|
+
*/
|
|
464
|
+
function sudocodeDataDir() {
|
|
465
|
+
return watchPath ?? (cwd ? join(cwd, '.sudocode') : null);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Read entities directly from a JSONL file in .sudocode/.
|
|
469
|
+
* The JSONL files contain fully denormalized data with relationships
|
|
470
|
+
* and tags already embedded — no CLI calls needed.
|
|
471
|
+
*
|
|
472
|
+
* Falls back to CLI `list` if the file can't be read.
|
|
473
|
+
*/
|
|
474
|
+
async function readEntitiesFromJsonl(type) {
|
|
475
|
+
const jsonlFile = type === 'spec' ? 'specs.jsonl' : 'issues.jsonl';
|
|
476
|
+
const dir = sudocodeDataDir();
|
|
477
|
+
if (!dir) {
|
|
478
|
+
// No path to .sudocode dir — fall back to CLI
|
|
479
|
+
return fetchEntities(type);
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const content = await readFile(join(dir, jsonlFile), 'utf-8');
|
|
483
|
+
const entities = [];
|
|
484
|
+
for (const line of content.split('\n')) {
|
|
485
|
+
const trimmed = line.trim();
|
|
486
|
+
if (!trimmed)
|
|
487
|
+
continue;
|
|
488
|
+
try {
|
|
489
|
+
entities.push(JSON.parse(trimmed));
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
// Skip malformed lines
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return entities;
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
// File doesn't exist or isn't readable — fall back to CLI
|
|
499
|
+
return fetchEntities(type);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Find a single entity by ID from the JSONL files.
|
|
504
|
+
* Falls back to CLI `show` if JSONL is unavailable or entity not found.
|
|
505
|
+
*/
|
|
506
|
+
async function findEntityById(id, workspace = '.') {
|
|
507
|
+
const entityType = entityTypeFromId(id);
|
|
508
|
+
const dir = sudocodeDataDir();
|
|
509
|
+
if (dir) {
|
|
510
|
+
try {
|
|
511
|
+
const entities = await readEntitiesFromJsonl(entityType);
|
|
512
|
+
const found = entities.find((e) => e.id === id);
|
|
513
|
+
if (found)
|
|
514
|
+
return found;
|
|
515
|
+
// Not in JSONL — might be a stale file; fall through to CLI
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
// JSONL read failed — fall through to CLI
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
// Fall back to CLI show
|
|
522
|
+
const subcommand = entityType === 'spec' ? 'spec' : 'issue';
|
|
523
|
+
try {
|
|
524
|
+
const output = await execSudocode(['--json', subcommand, 'show', id]);
|
|
525
|
+
return parseJson(output);
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
if (error instanceof ProviderErrorClass && error.code === 'OPERATION_FAILED') {
|
|
529
|
+
const message = error.message.toLowerCase();
|
|
530
|
+
if (message.includes('not found') ||
|
|
531
|
+
message.includes('does not exist') ||
|
|
532
|
+
message.includes('no issue found') ||
|
|
533
|
+
message.includes('no spec found')) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
throw error;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return {
|
|
541
|
+
name: 'sudocode',
|
|
542
|
+
schemes: ['sudocode', 'sc'],
|
|
543
|
+
capabilities,
|
|
544
|
+
// =========================================================================
|
|
545
|
+
// URI Operations
|
|
546
|
+
// =========================================================================
|
|
547
|
+
parseUri(uri) {
|
|
548
|
+
// Check for sudocode:// or sc:// URI
|
|
549
|
+
const match = uri.match(SUDOCODE_URI_PATTERN);
|
|
550
|
+
if (match) {
|
|
551
|
+
const scheme = match[1].toLowerCase();
|
|
552
|
+
const workspace = match[2] || '.';
|
|
553
|
+
const id = match[3];
|
|
554
|
+
return {
|
|
555
|
+
scheme,
|
|
556
|
+
workspace,
|
|
557
|
+
id,
|
|
558
|
+
isRelative: workspace === '.',
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
// Check for bare Sudocode entity ID
|
|
562
|
+
if (ENTITY_ID_PATTERN.test(uri)) {
|
|
563
|
+
return {
|
|
564
|
+
scheme: 'sudocode',
|
|
565
|
+
workspace: '.',
|
|
566
|
+
id: uri,
|
|
567
|
+
isRelative: true,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
return null;
|
|
571
|
+
},
|
|
572
|
+
buildUri(id, options) {
|
|
573
|
+
const workspace = options?.workspace ?? '.';
|
|
574
|
+
if (options?.relative) {
|
|
575
|
+
return id;
|
|
576
|
+
}
|
|
577
|
+
return `sudocode://${workspace}/${id}`;
|
|
578
|
+
},
|
|
579
|
+
isValidUri(uri) {
|
|
580
|
+
return this.parseUri(uri) !== null;
|
|
581
|
+
},
|
|
582
|
+
// =========================================================================
|
|
583
|
+
// CRUD Operations
|
|
584
|
+
// =========================================================================
|
|
585
|
+
async get(id, _context) {
|
|
586
|
+
const parsed = this.parseUri(id);
|
|
587
|
+
const entityId = parsed?.id ?? id;
|
|
588
|
+
const workspace = parsed?.workspace ?? '.';
|
|
589
|
+
const entity = await findEntityById(entityId, workspace);
|
|
590
|
+
if (!entity)
|
|
591
|
+
return null;
|
|
592
|
+
return entityToProviderNode(entity, workspace);
|
|
593
|
+
},
|
|
594
|
+
async list(filter, _context) {
|
|
595
|
+
const results = [];
|
|
596
|
+
const entityTypes = filter?.type === 'spec'
|
|
597
|
+
? ['spec']
|
|
598
|
+
: filter?.type === 'issue'
|
|
599
|
+
? ['issue']
|
|
600
|
+
: ['issue', 'spec'];
|
|
601
|
+
for (const entityType of entityTypes) {
|
|
602
|
+
try {
|
|
603
|
+
const entities = await readEntitiesFromJsonl(entityType);
|
|
604
|
+
for (const entity of entities) {
|
|
605
|
+
if (isArchived(entity))
|
|
606
|
+
continue;
|
|
607
|
+
// Client-side status filter (applies to issues; specs don't have status)
|
|
608
|
+
if (filter?.status && entityType === 'issue') {
|
|
609
|
+
const issue = entity;
|
|
610
|
+
if (issue.status !== filter.status)
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
results.push(entityToProviderNode(entity));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
catch {
|
|
617
|
+
// If one type fails (e.g., no specs), continue with the other
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Apply limit after combining
|
|
621
|
+
if (filter?.limit && results.length > filter.limit) {
|
|
622
|
+
return results.slice(0, filter.limit);
|
|
623
|
+
}
|
|
624
|
+
return results;
|
|
625
|
+
},
|
|
626
|
+
async create(input, context) {
|
|
627
|
+
const entityType = input.type === 'spec' ? 'spec' : 'issue';
|
|
628
|
+
const subcommand = entityType === 'spec' ? 'spec' : 'issue';
|
|
629
|
+
// --json is global (before subcommand), title is positional (after create)
|
|
630
|
+
const args = ['--json', subcommand, 'create', input.title];
|
|
631
|
+
if (input.content) {
|
|
632
|
+
args.push('-d', input.content);
|
|
633
|
+
}
|
|
634
|
+
if (input.priority !== undefined) {
|
|
635
|
+
args.push('-p', String(input.priority));
|
|
636
|
+
}
|
|
637
|
+
const output = await execSudocode(args, context);
|
|
638
|
+
const entity = parseJson(output);
|
|
639
|
+
return entityToProviderNode(entity);
|
|
640
|
+
},
|
|
641
|
+
async update(id, updates, context) {
|
|
642
|
+
const parsed = this.parseUri(id);
|
|
643
|
+
const entityId = parsed?.id ?? id;
|
|
644
|
+
const entityType = entityTypeFromId(entityId);
|
|
645
|
+
const subcommand = entityType === 'spec' ? 'spec' : 'issue';
|
|
646
|
+
// --json is global (before subcommand)
|
|
647
|
+
const args = ['--json', subcommand, 'update', entityId];
|
|
648
|
+
if (updates.title) {
|
|
649
|
+
args.push('--title', updates.title);
|
|
650
|
+
}
|
|
651
|
+
if (updates.content) {
|
|
652
|
+
args.push('--description', updates.content);
|
|
653
|
+
}
|
|
654
|
+
if (updates.status && entityType === 'issue') {
|
|
655
|
+
args.push('-s', updates.status);
|
|
656
|
+
}
|
|
657
|
+
if (updates.priority !== undefined) {
|
|
658
|
+
args.push('-p', String(updates.priority));
|
|
659
|
+
}
|
|
660
|
+
const output = await execSudocode(args, context);
|
|
661
|
+
const entity = parseJson(output);
|
|
662
|
+
return entityToProviderNode(entity);
|
|
663
|
+
},
|
|
664
|
+
async delete(id, context) {
|
|
665
|
+
const parsed = this.parseUri(id);
|
|
666
|
+
const entityId = parsed?.id ?? id;
|
|
667
|
+
const entityType = entityTypeFromId(entityId);
|
|
668
|
+
const subcommand = entityType === 'spec' ? 'spec' : 'issue';
|
|
669
|
+
await execSudocode([subcommand, 'delete', '--hard', entityId], context);
|
|
670
|
+
},
|
|
671
|
+
// =========================================================================
|
|
672
|
+
// Search
|
|
673
|
+
// =========================================================================
|
|
674
|
+
async search(query, options) {
|
|
675
|
+
const results = [];
|
|
676
|
+
const entityTypes = options?.type === 'spec'
|
|
677
|
+
? ['spec']
|
|
678
|
+
: options?.type === 'issue'
|
|
679
|
+
? ['issue']
|
|
680
|
+
: ['issue', 'spec'];
|
|
681
|
+
const queryLower = query.toLowerCase();
|
|
682
|
+
for (const entityType of entityTypes) {
|
|
683
|
+
try {
|
|
684
|
+
const entities = await readEntitiesFromJsonl(entityType);
|
|
685
|
+
for (const entity of entities) {
|
|
686
|
+
if (isArchived(entity))
|
|
687
|
+
continue;
|
|
688
|
+
// Client-side text search across title and content
|
|
689
|
+
const titleMatch = entity.title?.toLowerCase().includes(queryLower);
|
|
690
|
+
const contentMatch = entity.content?.toLowerCase().includes(queryLower);
|
|
691
|
+
if (!titleMatch && !contentMatch)
|
|
692
|
+
continue;
|
|
693
|
+
results.push(entityToProviderNode(entity));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
// If search fails for one type, continue with the other
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (options?.limit && results.length > options.limit) {
|
|
701
|
+
return results.slice(0, options.limit);
|
|
702
|
+
}
|
|
703
|
+
return results;
|
|
704
|
+
},
|
|
705
|
+
// =========================================================================
|
|
706
|
+
// RelationshipQueryable Implementation
|
|
707
|
+
// =========================================================================
|
|
708
|
+
async queryEdges(nodeId, options) {
|
|
709
|
+
const parsed = this.parseUri(nodeId);
|
|
710
|
+
const entityId = parsed?.id ?? nodeId;
|
|
711
|
+
const entity = await findEntityById(entityId);
|
|
712
|
+
if (!entity)
|
|
713
|
+
return [];
|
|
714
|
+
let edges = [];
|
|
715
|
+
// Extract relationships (handles both show and JSONL formats)
|
|
716
|
+
for (const rel of normalizeRelationships(entity.relationships)) {
|
|
717
|
+
if (rel.from_id && rel.to_id && rel.relationship_type) {
|
|
718
|
+
edges.push({
|
|
719
|
+
from: rel.from_id,
|
|
720
|
+
to: rel.to_id,
|
|
721
|
+
type: mapRelationshipType(rel.relationship_type),
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
// Parent-child relationship
|
|
726
|
+
if (entity.parent_id) {
|
|
727
|
+
edges.push({ from: entity.parent_id, to: entityId, type: 'parent-of' });
|
|
728
|
+
}
|
|
729
|
+
// Deduplicate edges
|
|
730
|
+
const seen = new Set();
|
|
731
|
+
edges = edges.filter((edge) => {
|
|
732
|
+
const key = `${edge.from}\0${edge.to}\0${edge.type}`;
|
|
733
|
+
if (seen.has(key))
|
|
734
|
+
return false;
|
|
735
|
+
seen.add(key);
|
|
736
|
+
return true;
|
|
737
|
+
});
|
|
738
|
+
// Apply filters
|
|
739
|
+
if (options?.edgeType) {
|
|
740
|
+
edges = filterEdgesByType(edges, options.edgeType);
|
|
741
|
+
}
|
|
742
|
+
if (options?.direction) {
|
|
743
|
+
edges = filterEdgesByDirection(edges, entityId, options.direction);
|
|
744
|
+
}
|
|
745
|
+
if (options?.limit && edges.length > options.limit) {
|
|
746
|
+
edges = edges.slice(0, options.limit);
|
|
747
|
+
}
|
|
748
|
+
return edges;
|
|
749
|
+
},
|
|
750
|
+
supportedEdgeTypes() {
|
|
751
|
+
return [
|
|
752
|
+
{ type: 'blocks', canQuery: true, canCreate: false, canDelete: false },
|
|
753
|
+
{ type: 'related', canQuery: true, canCreate: false, canDelete: false },
|
|
754
|
+
{ type: 'discovered-from', canQuery: true, canCreate: false, canDelete: false },
|
|
755
|
+
{ type: 'implements', canQuery: true, canCreate: false, canDelete: false },
|
|
756
|
+
{ type: 'references', canQuery: true, canCreate: false, canDelete: false },
|
|
757
|
+
{ type: 'depends-on', canQuery: true, canCreate: false, canDelete: false },
|
|
758
|
+
{ type: 'parent-of', canQuery: true, canCreate: false, canDelete: false },
|
|
759
|
+
];
|
|
760
|
+
},
|
|
761
|
+
// =========================================================================
|
|
762
|
+
// Watchable Implementation (only functional when watchPath is configured)
|
|
763
|
+
// =========================================================================
|
|
764
|
+
watchGranularity: {
|
|
765
|
+
reportsChangedFields: false,
|
|
766
|
+
reportsPreviousValues: false,
|
|
767
|
+
reportsEdgeChanges: true,
|
|
768
|
+
mechanism: 'file-watch',
|
|
769
|
+
},
|
|
770
|
+
startWatching(callback) {
|
|
771
|
+
if (!watchPath) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
watchCallback = callback;
|
|
775
|
+
if (fileWatcher) {
|
|
776
|
+
return; // Already watching, just updated callback
|
|
777
|
+
}
|
|
778
|
+
void seedCache().then(() => {
|
|
779
|
+
if (!watchCallback)
|
|
780
|
+
return;
|
|
781
|
+
fileWatcher = chokidar.watch(watchPath, {
|
|
782
|
+
ignoreInitial: true,
|
|
783
|
+
persistent: true,
|
|
784
|
+
awaitWriteFinish: {
|
|
785
|
+
stabilityThreshold: 100,
|
|
786
|
+
pollInterval: 20,
|
|
787
|
+
},
|
|
788
|
+
});
|
|
789
|
+
fileWatcher.on('add', onFileChange);
|
|
790
|
+
fileWatcher.on('change', onFileChange);
|
|
791
|
+
fileWatcher.on('unlink', onFileChange);
|
|
792
|
+
fileWatcher.on('error', () => {
|
|
793
|
+
// Resilient — continue watching despite transient errors
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
},
|
|
797
|
+
stopWatching() {
|
|
798
|
+
watchCallback = null;
|
|
799
|
+
if (debounceTimer) {
|
|
800
|
+
clearTimeout(debounceTimer);
|
|
801
|
+
debounceTimer = null;
|
|
802
|
+
}
|
|
803
|
+
if (fileWatcher) {
|
|
804
|
+
void fileWatcher.close();
|
|
805
|
+
fileWatcher = null;
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
get isWatching() {
|
|
809
|
+
return fileWatcher !== null && watchCallback !== null;
|
|
810
|
+
},
|
|
811
|
+
// =========================================================================
|
|
812
|
+
// TaskManageable Implementation
|
|
813
|
+
// =========================================================================
|
|
814
|
+
taskCapabilities: {
|
|
815
|
+
actions: ['start', 'complete', 'block', 'reopen', 'close'],
|
|
816
|
+
supportsAssignment: true,
|
|
817
|
+
supportsReadyQuery: true,
|
|
818
|
+
statusModel: ['open', 'in_progress', 'blocked', 'closed'],
|
|
819
|
+
},
|
|
820
|
+
async transitionTask(id, action, context) {
|
|
821
|
+
const parsed = this.parseUri(id);
|
|
822
|
+
const entityId = parsed?.id ?? id;
|
|
823
|
+
// Only issues support task transitions (not specs)
|
|
824
|
+
if (entityTypeFromId(entityId) !== 'issue') {
|
|
825
|
+
throw new ProviderErrorClass('NOT_SUPPORTED', `Cannot transition spec ${entityId} — only issues support task lifecycle`, 'sudocode');
|
|
826
|
+
}
|
|
827
|
+
const statusMap = {
|
|
828
|
+
start: 'in_progress',
|
|
829
|
+
complete: 'closed',
|
|
830
|
+
block: 'blocked',
|
|
831
|
+
reopen: 'open',
|
|
832
|
+
close: 'closed',
|
|
833
|
+
};
|
|
834
|
+
const targetStatus = statusMap[action];
|
|
835
|
+
if (!targetStatus) {
|
|
836
|
+
throw new ProviderErrorClass('NOT_SUPPORTED', `Unsupported task action: ${action}`, 'sudocode');
|
|
837
|
+
}
|
|
838
|
+
// Validate transition is allowed from current state
|
|
839
|
+
const current = await findEntityById(entityId);
|
|
840
|
+
if (current) {
|
|
841
|
+
const currentStatus = current.status ?? 'open';
|
|
842
|
+
const allowed = validActionsForStatus(currentStatus);
|
|
843
|
+
if (!allowed.includes(action)) {
|
|
844
|
+
throw new ProviderErrorClass('NOT_SUPPORTED', `Cannot ${action} an issue in '${currentStatus}' state. Valid actions: ${allowed.join(', ')}`, 'sudocode');
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const output = await execSudocode(['--json', 'issue', 'update', entityId, '-s', targetStatus], context);
|
|
848
|
+
const entity = parseJson(output);
|
|
849
|
+
return entityToProviderNode(entity);
|
|
850
|
+
},
|
|
851
|
+
async readyTasks(options, context) {
|
|
852
|
+
const issues = await readEntitiesFromJsonl('issue');
|
|
853
|
+
const readyIssues = [];
|
|
854
|
+
for (const entity of issues) {
|
|
855
|
+
const issue = entity;
|
|
856
|
+
if (isArchived(issue))
|
|
857
|
+
continue;
|
|
858
|
+
if (issue.status !== 'open')
|
|
859
|
+
continue;
|
|
860
|
+
// Apply tag filter
|
|
861
|
+
if (options?.tags) {
|
|
862
|
+
const issueTags = issue.tags ?? [];
|
|
863
|
+
if (!options.tags.every((t) => issueTags.includes(t)))
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
// Apply priority filter
|
|
867
|
+
if (options?.priority !== undefined) {
|
|
868
|
+
const p = mapPriority(issue.priority);
|
|
869
|
+
if (p === undefined || p > options.priority)
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
// Apply assignee filter
|
|
873
|
+
if (options?.assignee && issue.assignee !== options.assignee)
|
|
874
|
+
continue;
|
|
875
|
+
// Check for active blockers via relationships
|
|
876
|
+
let hasActiveBlocker = false;
|
|
877
|
+
const rels = normalizeRelationships(issue.relationships);
|
|
878
|
+
for (const rel of rels) {
|
|
879
|
+
if (!rel.from_id || !rel.to_id || !rel.relationship_type)
|
|
880
|
+
continue;
|
|
881
|
+
const relType = rel.relationship_type.toLowerCase();
|
|
882
|
+
const isBlocking = (relType === 'blocks' && rel.to_id === issue.id) ||
|
|
883
|
+
(relType === 'depends-on' && rel.from_id === issue.id);
|
|
884
|
+
if (!isBlocking)
|
|
885
|
+
continue;
|
|
886
|
+
// Resolve the blocker to check its status
|
|
887
|
+
const blockerId = relType === 'blocks' ? rel.from_id : rel.to_id;
|
|
888
|
+
try {
|
|
889
|
+
const blocker = await findEntityById(blockerId);
|
|
890
|
+
if (blocker && !isArchived(blocker)) {
|
|
891
|
+
const blockerStatus = blocker.status;
|
|
892
|
+
if (blockerStatus && blockerStatus !== 'closed') {
|
|
893
|
+
hasActiveBlocker = true;
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
catch {
|
|
899
|
+
// If we can't resolve the blocker, assume it's active
|
|
900
|
+
hasActiveBlocker = true;
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (!hasActiveBlocker) {
|
|
905
|
+
readyIssues.push(entityToProviderNode(issue));
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
// Sort by priority (lower number = higher priority)
|
|
909
|
+
readyIssues.sort((a, b) => {
|
|
910
|
+
const aPriority = a.priority ?? Infinity;
|
|
911
|
+
const bPriority = b.priority ?? Infinity;
|
|
912
|
+
return aPriority - bPriority;
|
|
913
|
+
});
|
|
914
|
+
// Apply limit
|
|
915
|
+
if (options?.limit && readyIssues.length > options.limit) {
|
|
916
|
+
return readyIssues.slice(0, options.limit);
|
|
917
|
+
}
|
|
918
|
+
return readyIssues;
|
|
919
|
+
},
|
|
920
|
+
async assignTask(id, assignee, context) {
|
|
921
|
+
const parsed = this.parseUri(id);
|
|
922
|
+
const entityId = parsed?.id ?? id;
|
|
923
|
+
if (entityTypeFromId(entityId) !== 'issue') {
|
|
924
|
+
throw new ProviderErrorClass('NOT_SUPPORTED', `Cannot assign spec ${entityId} — only issues support assignment`, 'sudocode');
|
|
925
|
+
}
|
|
926
|
+
const output = await execSudocode(['--json', 'issue', 'update', entityId, '--assignee', assignee], context);
|
|
927
|
+
const entity = parseJson(output);
|
|
928
|
+
return entityToProviderNode(entity);
|
|
929
|
+
},
|
|
930
|
+
async validActions(id, context) {
|
|
931
|
+
const parsed = this.parseUri(id);
|
|
932
|
+
const entityId = parsed?.id ?? id;
|
|
933
|
+
if (entityTypeFromId(entityId) !== 'issue') {
|
|
934
|
+
throw new ProviderErrorClass('NOT_SUPPORTED', `Cannot query actions for spec ${entityId} — only issues support task lifecycle`, 'sudocode');
|
|
935
|
+
}
|
|
936
|
+
const entity = await findEntityById(entityId);
|
|
937
|
+
if (!entity) {
|
|
938
|
+
throw new ProviderErrorClass('NOT_FOUND', `Issue not found: ${entityId}`, 'sudocode');
|
|
939
|
+
}
|
|
940
|
+
const issue = entity;
|
|
941
|
+
return validActionsForStatus(issue.status ?? 'open');
|
|
942
|
+
},
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
//# sourceMappingURL=sudocode.js.map
|