@kynetic-ai/spec 0.1.2 → 0.3.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/README.md +250 -17
- package/dist/acp/client.d.ts +18 -4
- package/dist/acp/client.d.ts.map +1 -1
- package/dist/acp/client.js +44 -26
- package/dist/acp/client.js.map +1 -1
- package/dist/acp/framing.d.ts +2 -2
- package/dist/acp/framing.d.ts.map +1 -1
- package/dist/acp/framing.js +37 -29
- package/dist/acp/framing.js.map +1 -1
- package/dist/acp/index.d.ts +6 -7
- package/dist/acp/index.d.ts.map +1 -1
- package/dist/acp/index.js +3 -3
- package/dist/acp/index.js.map +1 -1
- package/dist/acp/types.d.ts +5 -5
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js +18 -18
- package/dist/acp/types.js.map +1 -1
- package/dist/agents/adapters.d.ts.map +1 -1
- package/dist/agents/adapters.js +24 -13
- package/dist/agents/adapters.js.map +1 -1
- package/dist/agents/index.d.ts +2 -2
- package/dist/agents/index.js +2 -2
- package/dist/agents/spawner.d.ts +4 -4
- package/dist/agents/spawner.d.ts.map +1 -1
- package/dist/agents/spawner.js +6 -6
- package/dist/agents/spawner.js.map +1 -1
- package/dist/cli/batch-context.d.ts +43 -0
- package/dist/cli/batch-context.d.ts.map +1 -0
- package/dist/cli/batch-context.js +93 -0
- package/dist/cli/batch-context.js.map +1 -0
- package/dist/cli/batch-exec.d.ts +116 -0
- package/dist/cli/batch-exec.d.ts.map +1 -0
- package/dist/cli/batch-exec.js +694 -0
- package/dist/cli/batch-exec.js.map +1 -0
- package/dist/cli/batch.d.ts +4 -2
- package/dist/cli/batch.d.ts.map +1 -1
- package/dist/cli/batch.js +15 -14
- package/dist/cli/batch.js.map +1 -1
- package/dist/cli/command-annotations.d.ts +23 -0
- package/dist/cli/command-annotations.d.ts.map +1 -0
- package/dist/cli/command-annotations.js +27 -0
- package/dist/cli/command-annotations.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +46 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +377 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/commands/batch.d.ts +20 -0
- package/dist/cli/commands/batch.d.ts.map +1 -0
- package/dist/cli/commands/batch.js +214 -0
- package/dist/cli/commands/batch.js.map +1 -0
- package/dist/cli/commands/clone-for-testing.d.ts +1 -1
- package/dist/cli/commands/clone-for-testing.d.ts.map +1 -1
- package/dist/cli/commands/clone-for-testing.js +37 -47
- package/dist/cli/commands/clone-for-testing.js.map +1 -1
- package/dist/cli/commands/derive.d.ts +1 -1
- package/dist/cli/commands/derive.d.ts.map +1 -1
- package/dist/cli/commands/derive.js +140 -88
- package/dist/cli/commands/derive.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +11 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +152 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/export.d.ts +12 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +134 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/help.d.ts +1 -1
- package/dist/cli/commands/help.d.ts.map +1 -1
- package/dist/cli/commands/help.js +163 -37
- package/dist/cli/commands/help.js.map +1 -1
- package/dist/cli/commands/inbox.d.ts +1 -1
- package/dist/cli/commands/inbox.d.ts.map +1 -1
- package/dist/cli/commands/inbox.js +178 -56
- package/dist/cli/commands/inbox.js.map +1 -1
- package/dist/cli/commands/index.d.ts +31 -19
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +31 -19
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +5 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +108 -57
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/item.d.ts +1 -1
- package/dist/cli/commands/item.d.ts.map +1 -1
- package/dist/cli/commands/item.js +557 -274
- package/dist/cli/commands/item.js.map +1 -1
- package/dist/cli/commands/link.d.ts +1 -1
- package/dist/cli/commands/link.d.ts.map +1 -1
- package/dist/cli/commands/link.js +55 -46
- package/dist/cli/commands/link.js.map +1 -1
- package/dist/cli/commands/log.d.ts +1 -1
- package/dist/cli/commands/log.d.ts.map +1 -1
- package/dist/cli/commands/log.js +57 -51
- package/dist/cli/commands/log.js.map +1 -1
- package/dist/cli/commands/merge-driver.d.ts +19 -0
- package/dist/cli/commands/merge-driver.d.ts.map +1 -0
- package/dist/cli/commands/merge-driver.js +398 -0
- package/dist/cli/commands/merge-driver.js.map +1 -0
- package/dist/cli/commands/meta.d.ts +1 -1
- package/dist/cli/commands/meta.d.ts.map +1 -1
- package/dist/cli/commands/meta.js +533 -399
- package/dist/cli/commands/meta.js.map +1 -1
- package/dist/cli/commands/module.d.ts +1 -1
- package/dist/cli/commands/module.d.ts.map +1 -1
- package/dist/cli/commands/module.js +30 -25
- package/dist/cli/commands/module.js.map +1 -1
- package/dist/cli/commands/plan-import.d.ts +11 -0
- package/dist/cli/commands/plan-import.d.ts.map +1 -0
- package/dist/cli/commands/plan-import.js +516 -0
- package/dist/cli/commands/plan-import.js.map +1 -0
- package/dist/cli/commands/plan.d.ts +10 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +421 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/ralph.d.ts +1 -1
- package/dist/cli/commands/ralph.d.ts.map +1 -1
- package/dist/cli/commands/ralph.js +1097 -169
- package/dist/cli/commands/ralph.js.map +1 -1
- package/dist/cli/commands/refs.d.ts +13 -0
- package/dist/cli/commands/refs.d.ts.map +1 -0
- package/dist/cli/commands/refs.js +283 -0
- package/dist/cli/commands/refs.js.map +1 -0
- package/dist/cli/commands/search.d.ts +1 -1
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/search.js +199 -37
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/serve.d.ts +10 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +491 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/session.d.ts +25 -6
- package/dist/cli/commands/session.d.ts.map +1 -1
- package/dist/cli/commands/session.js +811 -127
- package/dist/cli/commands/session.js.map +1 -1
- package/dist/cli/commands/setup-seeding.d.ts +81 -0
- package/dist/cli/commands/setup-seeding.d.ts.map +1 -0
- package/dist/cli/commands/setup-seeding.js +292 -0
- package/dist/cli/commands/setup-seeding.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +77 -3
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +1233 -274
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/shadow.d.ts +1 -1
- package/dist/cli/commands/shadow.d.ts.map +1 -1
- package/dist/cli/commands/shadow.js +70 -50
- package/dist/cli/commands/shadow.js.map +1 -1
- package/dist/cli/commands/skill-crud.d.ts +58 -0
- package/dist/cli/commands/skill-crud.d.ts.map +1 -0
- package/dist/cli/commands/skill-crud.js +753 -0
- package/dist/cli/commands/skill-crud.js.map +1 -0
- package/dist/cli/commands/skill-diff.d.ts +27 -0
- package/dist/cli/commands/skill-diff.d.ts.map +1 -0
- package/dist/cli/commands/skill-diff.js +840 -0
- package/dist/cli/commands/skill-diff.js.map +1 -0
- package/dist/cli/commands/skill-install.d.ts +53 -0
- package/dist/cli/commands/skill-install.d.ts.map +1 -0
- package/dist/cli/commands/skill-install.js +452 -0
- package/dist/cli/commands/skill-install.js.map +1 -0
- package/dist/cli/commands/skill.d.ts +20 -0
- package/dist/cli/commands/skill.d.ts.map +1 -0
- package/dist/cli/commands/skill.js +36 -0
- package/dist/cli/commands/skill.js.map +1 -0
- package/dist/cli/commands/task.d.ts +1 -1
- package/dist/cli/commands/task.d.ts.map +1 -1
- package/dist/cli/commands/task.js +569 -346
- package/dist/cli/commands/task.js.map +1 -1
- package/dist/cli/commands/tasks.d.ts +26 -1
- package/dist/cli/commands/tasks.d.ts.map +1 -1
- package/dist/cli/commands/tasks.js +227 -122
- package/dist/cli/commands/tasks.js.map +1 -1
- package/dist/cli/commands/trait.d.ts +1 -1
- package/dist/cli/commands/trait.d.ts.map +1 -1
- package/dist/cli/commands/trait.js +166 -101
- package/dist/cli/commands/trait.js.map +1 -1
- package/dist/cli/commands/triage.d.ts +7 -0
- package/dist/cli/commands/triage.d.ts.map +1 -0
- package/dist/cli/commands/triage.js +569 -0
- package/dist/cli/commands/triage.js.map +1 -0
- package/dist/cli/commands/util.d.ts +7 -0
- package/dist/cli/commands/util.d.ts.map +1 -0
- package/dist/cli/commands/util.js +30 -0
- package/dist/cli/commands/util.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +1 -1
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/cli/commands/validate.js +264 -83
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/commands/workflow.d.ts +16 -0
- package/dist/cli/commands/workflow.d.ts.map +1 -0
- package/dist/cli/commands/workflow.js +851 -0
- package/dist/cli/commands/workflow.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +7 -0
- package/dist/cli/exit-codes.d.ts.map +1 -1
- package/dist/cli/exit-codes.js +26 -18
- package/dist/cli/exit-codes.js.map +1 -1
- package/dist/cli/help/content.d.ts.map +1 -1
- package/dist/cli/help/content.js +86 -71
- package/dist/cli/help/content.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +131 -19
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/introspection.d.ts +6 -2
- package/dist/cli/introspection.d.ts.map +1 -1
- package/dist/cli/introspection.js +11 -8
- package/dist/cli/introspection.js.map +1 -1
- package/dist/cli/output.d.ts +64 -4
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +235 -85
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/parse-utils.d.ts +21 -0
- package/dist/cli/parse-utils.d.ts.map +1 -0
- package/dist/cli/parse-utils.js +32 -0
- package/dist/cli/parse-utils.js.map +1 -0
- package/dist/cli/pid-utils.d.ts +72 -0
- package/dist/cli/pid-utils.d.ts.map +1 -0
- package/dist/cli/pid-utils.js +174 -0
- package/dist/cli/pid-utils.js.map +1 -0
- package/dist/cli/suggest.d.ts.map +1 -1
- package/dist/cli/suggest.js +1 -2
- package/dist/cli/suggest.js.map +1 -1
- package/dist/cli/validators.d.ts +43 -0
- package/dist/cli/validators.d.ts.map +1 -0
- package/dist/cli/validators.js +84 -0
- package/dist/cli/validators.js.map +1 -0
- package/dist/daemon/index.ts +52 -0
- package/dist/daemon/middleware/project-context.ts +126 -0
- package/dist/daemon/pid.ts +179 -0
- package/dist/daemon/project-context.ts +343 -0
- package/dist/daemon/routes/inbox.ts +164 -0
- package/dist/daemon/routes/items.ts +322 -0
- package/dist/daemon/routes/meta.ts +118 -0
- package/dist/daemon/routes/projects.ts +162 -0
- package/dist/daemon/routes/tasks.ts +327 -0
- package/dist/daemon/routes/triage.ts +468 -0
- package/dist/daemon/routes/validation.ts +248 -0
- package/dist/daemon/server.ts +408 -0
- package/dist/daemon/watcher.ts +195 -0
- package/dist/daemon/websocket/handler.ts +138 -0
- package/dist/daemon/websocket/heartbeat.ts +71 -0
- package/dist/daemon/websocket/pubsub.ts +125 -0
- package/dist/daemon/websocket/types.ts +66 -0
- package/dist/export/html.d.ts +19 -0
- package/dist/export/html.d.ts.map +1 -0
- package/dist/export/html.js +239 -0
- package/dist/export/html.js.map +1 -0
- package/dist/export/index.d.ts +10 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +10 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/json.d.ts +24 -0
- package/dist/export/json.d.ts.map +1 -0
- package/dist/export/json.js +198 -0
- package/dist/export/json.js.map +1 -0
- package/dist/export/triage.d.ts +51 -0
- package/dist/export/triage.d.ts.map +1 -0
- package/dist/export/triage.js +83 -0
- package/dist/export/triage.js.map +1 -0
- package/dist/export/types.d.ts +122 -0
- package/dist/export/types.d.ts.map +1 -0
- package/dist/export/types.js +9 -0
- package/dist/export/types.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/lib/claude-plugin-registry.d.ts +66 -0
- package/dist/lib/claude-plugin-registry.d.ts.map +1 -0
- package/dist/lib/claude-plugin-registry.js +318 -0
- package/dist/lib/claude-plugin-registry.js.map +1 -0
- package/dist/merge/arrays.d.ts +87 -0
- package/dist/merge/arrays.d.ts.map +1 -0
- package/dist/merge/arrays.js +164 -0
- package/dist/merge/arrays.js.map +1 -0
- package/dist/merge/file-type.d.ts +32 -0
- package/dist/merge/file-type.d.ts.map +1 -0
- package/dist/merge/file-type.js +70 -0
- package/dist/merge/file-type.js.map +1 -0
- package/dist/merge/index.d.ts +14 -0
- package/dist/merge/index.d.ts.map +1 -0
- package/dist/merge/index.js +11 -0
- package/dist/merge/index.js.map +1 -0
- package/dist/merge/objects.d.ts +46 -0
- package/dist/merge/objects.d.ts.map +1 -0
- package/dist/merge/objects.js +193 -0
- package/dist/merge/objects.js.map +1 -0
- package/dist/merge/parse.d.ts +23 -0
- package/dist/merge/parse.d.ts.map +1 -0
- package/dist/merge/parse.js +78 -0
- package/dist/merge/parse.js.map +1 -0
- package/dist/merge/resolve.d.ts +66 -0
- package/dist/merge/resolve.d.ts.map +1 -0
- package/dist/merge/resolve.js +189 -0
- package/dist/merge/resolve.js.map +1 -0
- package/dist/merge/types.d.ts +82 -0
- package/dist/merge/types.d.ts.map +1 -0
- package/dist/merge/types.js +8 -0
- package/dist/merge/types.js.map +1 -0
- package/dist/parser/agent-data-sections.d.ts +53 -0
- package/dist/parser/agent-data-sections.d.ts.map +1 -0
- package/dist/parser/agent-data-sections.js +118 -0
- package/dist/parser/agent-data-sections.js.map +1 -0
- package/dist/parser/alignment.d.ts +4 -4
- package/dist/parser/alignment.d.ts.map +1 -1
- package/dist/parser/alignment.js +27 -22
- package/dist/parser/alignment.js.map +1 -1
- package/dist/parser/assess.d.ts +5 -5
- package/dist/parser/assess.d.ts.map +1 -1
- package/dist/parser/assess.js +36 -32
- package/dist/parser/assess.js.map +1 -1
- package/dist/parser/config.d.ts +351 -0
- package/dist/parser/config.d.ts.map +1 -0
- package/dist/parser/config.js +326 -0
- package/dist/parser/config.js.map +1 -0
- package/dist/parser/convention-validation.d.ts +1 -1
- package/dist/parser/convention-validation.d.ts.map +1 -1
- package/dist/parser/convention-validation.js +21 -16
- package/dist/parser/convention-validation.js.map +1 -1
- package/dist/parser/coverage-cache.d.ts +49 -0
- package/dist/parser/coverage-cache.d.ts.map +1 -0
- package/dist/parser/coverage-cache.js +123 -0
- package/dist/parser/coverage-cache.js.map +1 -0
- package/dist/parser/daemon-status.d.ts +37 -0
- package/dist/parser/daemon-status.d.ts.map +1 -0
- package/dist/parser/daemon-status.js +67 -0
- package/dist/parser/daemon-status.js.map +1 -0
- package/dist/parser/doctor.d.ts +107 -0
- package/dist/parser/doctor.d.ts.map +1 -0
- package/dist/parser/doctor.js +366 -0
- package/dist/parser/doctor.js.map +1 -0
- package/dist/parser/fix.d.ts +1 -1
- package/dist/parser/fix.d.ts.map +1 -1
- package/dist/parser/fix.js +31 -27
- package/dist/parser/fix.js.map +1 -1
- package/dist/parser/index.d.ts +16 -11
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +16 -11
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/items.d.ts +8 -2
- package/dist/parser/items.d.ts.map +1 -1
- package/dist/parser/items.js +71 -35
- package/dist/parser/items.js.map +1 -1
- package/dist/parser/meta.d.ts +167 -9
- package/dist/parser/meta.d.ts.map +1 -1
- package/dist/parser/meta.js +379 -46
- package/dist/parser/meta.js.map +1 -1
- package/dist/parser/plan-document.d.ts +189 -0
- package/dist/parser/plan-document.d.ts.map +1 -0
- package/dist/parser/plan-document.js +340 -0
- package/dist/parser/plan-document.js.map +1 -0
- package/dist/parser/plans.d.ts +59 -0
- package/dist/parser/plans.d.ts.map +1 -0
- package/dist/parser/plans.js +239 -0
- package/dist/parser/plans.js.map +1 -0
- package/dist/parser/refs.d.ts +22 -9
- package/dist/parser/refs.d.ts.map +1 -1
- package/dist/parser/refs.js +102 -50
- package/dist/parser/refs.js.map +1 -1
- package/dist/parser/setup-status.d.ts +71 -0
- package/dist/parser/setup-status.d.ts.map +1 -0
- package/dist/parser/setup-status.js +269 -0
- package/dist/parser/setup-status.js.map +1 -0
- package/dist/parser/shadow.d.ts +150 -19
- package/dist/parser/shadow.d.ts.map +1 -1
- package/dist/parser/shadow.js +548 -187
- package/dist/parser/shadow.js.map +1 -1
- package/dist/parser/skill-render.d.ts +317 -0
- package/dist/parser/skill-render.d.ts.map +1 -0
- package/dist/parser/skill-render.js +943 -0
- package/dist/parser/skill-render.js.map +1 -0
- package/dist/parser/traits.d.ts +3 -3
- package/dist/parser/traits.d.ts.map +1 -1
- package/dist/parser/traits.js +2 -2
- package/dist/parser/traits.js.map +1 -1
- package/dist/parser/validate-skills.d.ts +32 -0
- package/dist/parser/validate-skills.d.ts.map +1 -0
- package/dist/parser/validate-skills.js +202 -0
- package/dist/parser/validate-skills.js.map +1 -0
- package/dist/parser/validate.d.ts +45 -3
- package/dist/parser/validate.d.ts.map +1 -1
- package/dist/parser/validate.js +622 -105
- package/dist/parser/validate.js.map +1 -1
- package/dist/parser/yaml.d.ts +83 -19
- package/dist/parser/yaml.d.ts.map +1 -1
- package/dist/parser/yaml.js +478 -173
- package/dist/parser/yaml.js.map +1 -1
- package/dist/ralph/cli-renderer.d.ts +8 -1
- package/dist/ralph/cli-renderer.d.ts.map +1 -1
- package/dist/ralph/cli-renderer.js +105 -34
- package/dist/ralph/cli-renderer.js.map +1 -1
- package/dist/ralph/events.d.ts +10 -10
- package/dist/ralph/events.d.ts.map +1 -1
- package/dist/ralph/events.js +277 -98
- package/dist/ralph/events.js.map +1 -1
- package/dist/ralph/index.d.ts +5 -2
- package/dist/ralph/index.d.ts.map +1 -1
- package/dist/ralph/index.js +9 -3
- package/dist/ralph/index.js.map +1 -1
- package/dist/ralph/loop-errors.d.ts +83 -0
- package/dist/ralph/loop-errors.d.ts.map +1 -0
- package/dist/ralph/loop-errors.js +150 -0
- package/dist/ralph/loop-errors.js.map +1 -0
- package/dist/ralph/subagent.d.ts +83 -0
- package/dist/ralph/subagent.d.ts.map +1 -0
- package/dist/ralph/subagent.js +174 -0
- package/dist/ralph/subagent.js.map +1 -0
- package/dist/ralph/wrap-up.d.ts +125 -0
- package/dist/ralph/wrap-up.d.ts.map +1 -0
- package/dist/ralph/wrap-up.js +270 -0
- package/dist/ralph/wrap-up.js.map +1 -0
- package/dist/schema/batch.d.ts +95 -0
- package/dist/schema/batch.d.ts.map +1 -0
- package/dist/schema/batch.js +24 -0
- package/dist/schema/batch.js.map +1 -0
- package/dist/schema/common.d.ts +2 -2
- package/dist/schema/common.d.ts.map +1 -1
- package/dist/schema/common.js +34 -31
- package/dist/schema/common.js.map +1 -1
- package/dist/schema/inbox.d.ts +12 -12
- package/dist/schema/inbox.js +4 -4
- package/dist/schema/inbox.js.map +1 -1
- package/dist/schema/index.d.ts +8 -5
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +8 -5
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/meta.d.ts +1454 -27
- package/dist/schema/meta.d.ts.map +1 -1
- package/dist/schema/meta.js +198 -21
- package/dist/schema/meta.js.map +1 -1
- package/dist/schema/plan.d.ts +285 -0
- package/dist/schema/plan.d.ts.map +1 -0
- package/dist/schema/plan.js +81 -0
- package/dist/schema/plan.js.map +1 -0
- package/dist/schema/spec.d.ts +72 -33
- package/dist/schema/spec.d.ts.map +1 -1
- package/dist/schema/spec.js +22 -9
- package/dist/schema/spec.js.map +1 -1
- package/dist/schema/task.d.ts +172 -161
- package/dist/schema/task.d.ts.map +1 -1
- package/dist/schema/task.js +21 -12
- package/dist/schema/task.js.map +1 -1
- package/dist/schema/triage.d.ts +266 -0
- package/dist/schema/triage.d.ts.map +1 -0
- package/dist/schema/triage.js +134 -0
- package/dist/schema/triage.js.map +1 -0
- package/dist/sessions/index.d.ts +2 -2
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +3 -3
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/store.d.ts +233 -1
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +628 -31
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +10 -10
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js +10 -9
- package/dist/sessions/types.js.map +1 -1
- package/dist/strings/errors.d.ts +51 -0
- package/dist/strings/errors.d.ts.map +1 -1
- package/dist/strings/errors.js +136 -106
- package/dist/strings/errors.js.map +1 -1
- package/dist/strings/guidance.d.ts.map +1 -1
- package/dist/strings/guidance.js +16 -16
- package/dist/strings/guidance.js.map +1 -1
- package/dist/strings/index.d.ts +4 -4
- package/dist/strings/index.d.ts.map +1 -1
- package/dist/strings/index.js +4 -4
- package/dist/strings/index.js.map +1 -1
- package/dist/strings/labels.d.ts +4 -0
- package/dist/strings/labels.d.ts.map +1 -1
- package/dist/strings/labels.js +45 -41
- package/dist/strings/labels.js.map +1 -1
- package/dist/strings/validation.d.ts.map +1 -1
- package/dist/strings/validation.js +71 -71
- package/dist/strings/validation.js.map +1 -1
- package/dist/utils/commit.d.ts +1 -1
- package/dist/utils/commit.d.ts.map +1 -1
- package/dist/utils/commit.js +28 -26
- package/dist/utils/commit.js.map +1 -1
- package/dist/utils/git.d.ts +1 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +40 -38
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/grep.js +11 -11
- package/dist/utils/grep.js.map +1 -1
- package/dist/utils/index.d.ts +7 -7
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +10 -10
- package/dist/utils/time.js.map +1 -1
- package/package.json +28 -5
- package/plugin/.claude-plugin/marketplace.json +17 -0
- package/plugin/.claude-plugin/plugin.json +5 -0
- package/plugin/plugins/kspec/skills/help/SKILL.md +42 -0
- package/plugin/plugins/kspec/skills/triage/SKILL.md +206 -0
- package/plugin/plugins/kspec/skills/triage/docs/automation.md +120 -0
- package/plugin/plugins/kspec/skills/triage/docs/inbox.md +144 -0
- package/plugin/plugins/kspec/skills/triage/docs/observations.md +85 -0
- package/templates/agents-sections/01-quick-start.md +22 -0
- package/templates/agents-sections/02-shadow-branch.md +34 -0
- package/templates/agents-sections/03-task-lifecycle.md +48 -0
- package/templates/agents-sections/04-pr-workflow.md +17 -0
- package/templates/agents-sections/05-commit-convention.md +27 -0
- package/templates/agents-sections/06-ralph-loop.md +45 -0
- package/templates/hooks/pre-commit +34 -0
- package/templates/skills/help/SKILL.md +37 -0
- package/templates/skills/manifest.yaml +15 -0
- package/templates/skills/triage/SKILL.md +199 -0
- package/templates/skills/triage/docs/automation.md +120 -0
- package/templates/skills/triage/docs/inbox.md +144 -0
- package/templates/skills/triage/docs/observations.md +85 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation and Search API Routes
|
|
3
|
+
*
|
|
4
|
+
* REST endpoints for search and validation operations:
|
|
5
|
+
* - GET /api/search?q=query - Search across all items/tasks/inbox/meta
|
|
6
|
+
* - GET /api/validate - Run full validation
|
|
7
|
+
* - GET /api/alignment - Get alignment stats and warnings
|
|
8
|
+
*
|
|
9
|
+
* AC Coverage:
|
|
10
|
+
* - ac-19: GET /api/search?q=query searches across all entities
|
|
11
|
+
* - ac-20: GET /api/validate returns ValidationResult
|
|
12
|
+
* - ac-21: GET /api/alignment returns AlignmentIndex stats
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Elysia, t } from 'elysia';
|
|
16
|
+
import {
|
|
17
|
+
initContext,
|
|
18
|
+
buildIndexes,
|
|
19
|
+
loadInboxItems,
|
|
20
|
+
loadMetaContext,
|
|
21
|
+
validate,
|
|
22
|
+
AlignmentIndex,
|
|
23
|
+
type LoadedSpecItem,
|
|
24
|
+
type LoadedTask,
|
|
25
|
+
type LoadedInboxItem,
|
|
26
|
+
} from '../../parser/index.js';
|
|
27
|
+
import type {
|
|
28
|
+
LoadedAgent,
|
|
29
|
+
LoadedWorkflow,
|
|
30
|
+
LoadedObservation,
|
|
31
|
+
LoadedConvention,
|
|
32
|
+
} from '../../parser/meta.js';
|
|
33
|
+
import { grepItem } from '../../utils/grep.js';
|
|
34
|
+
|
|
35
|
+
interface ValidationRouteOptions {}
|
|
36
|
+
|
|
37
|
+
export function createValidationRoutes(options: ValidationRouteOptions = {}) {
|
|
38
|
+
// No closure-scoped kspecDir needed - comes from middleware
|
|
39
|
+
|
|
40
|
+
return new Elysia({ prefix: '/api' })
|
|
41
|
+
// AC: @api-contract ac-19 - Search across all entities
|
|
42
|
+
.get(
|
|
43
|
+
'/search',
|
|
44
|
+
async ({ query, projectContext }) => {
|
|
45
|
+
// AC: @multi-directory-daemon ac-1, ac-24 - Use project context from middleware
|
|
46
|
+
const ctx = await initContext(projectContext.path);
|
|
47
|
+
const { tasks, items } = await buildIndexes(ctx);
|
|
48
|
+
|
|
49
|
+
const pattern = query.q;
|
|
50
|
+
if (!pattern) {
|
|
51
|
+
return {
|
|
52
|
+
results: [],
|
|
53
|
+
total: 0,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const limit = query.limit ? parseInt(query.limit, 10) : 50;
|
|
58
|
+
|
|
59
|
+
interface SearchResult {
|
|
60
|
+
type:
|
|
61
|
+
| 'item'
|
|
62
|
+
| 'task'
|
|
63
|
+
| 'inbox'
|
|
64
|
+
| 'observation'
|
|
65
|
+
| 'agent'
|
|
66
|
+
| 'workflow'
|
|
67
|
+
| 'convention';
|
|
68
|
+
ulid: string;
|
|
69
|
+
title: string;
|
|
70
|
+
matchedFields: string[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const results: SearchResult[] = [];
|
|
74
|
+
|
|
75
|
+
// AC: @api-contract ac-19 - Search spec items
|
|
76
|
+
if (!query.tasksOnly) {
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
// Apply type filter if provided
|
|
79
|
+
if (query.type && item.type !== query.type) continue;
|
|
80
|
+
|
|
81
|
+
const match = grepItem(item as unknown as Record<string, unknown>, pattern);
|
|
82
|
+
if (match) {
|
|
83
|
+
results.push({
|
|
84
|
+
type: 'item',
|
|
85
|
+
ulid: item._ulid,
|
|
86
|
+
title: item.title,
|
|
87
|
+
matchedFields: match.matchedFields,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// AC: @api-contract ac-19 - Search tasks
|
|
94
|
+
if (!query.itemsOnly) {
|
|
95
|
+
for (const task of tasks) {
|
|
96
|
+
// Apply status filter if provided
|
|
97
|
+
if (query.status && task.status !== query.status) continue;
|
|
98
|
+
|
|
99
|
+
const match = grepItem(task as unknown as Record<string, unknown>, pattern);
|
|
100
|
+
if (match) {
|
|
101
|
+
results.push({
|
|
102
|
+
type: 'task',
|
|
103
|
+
ulid: task._ulid,
|
|
104
|
+
title: task.title,
|
|
105
|
+
matchedFields: match.matchedFields,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// AC: @api-contract ac-19 - Search inbox items
|
|
112
|
+
if (!query.itemsOnly && !query.tasksOnly) {
|
|
113
|
+
const inboxItems = await loadInboxItems(ctx);
|
|
114
|
+
for (const inboxItem of inboxItems) {
|
|
115
|
+
const match = grepItem(inboxItem as unknown as Record<string, unknown>, pattern);
|
|
116
|
+
if (match) {
|
|
117
|
+
results.push({
|
|
118
|
+
type: 'inbox',
|
|
119
|
+
ulid: inboxItem._ulid,
|
|
120
|
+
title: inboxItem.text,
|
|
121
|
+
matchedFields: match.matchedFields,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// AC: @api-contract ac-19 - Search meta entities
|
|
128
|
+
if (!query.itemsOnly && !query.tasksOnly) {
|
|
129
|
+
const metaCtx = await loadMetaContext(ctx);
|
|
130
|
+
|
|
131
|
+
// Search observations
|
|
132
|
+
for (const observation of metaCtx.observations) {
|
|
133
|
+
const match = grepItem(observation as unknown as Record<string, unknown>, pattern);
|
|
134
|
+
if (match) {
|
|
135
|
+
results.push({
|
|
136
|
+
type: 'observation',
|
|
137
|
+
ulid: observation._ulid,
|
|
138
|
+
title: observation.content,
|
|
139
|
+
matchedFields: match.matchedFields,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Search agents
|
|
145
|
+
for (const agent of metaCtx.agents) {
|
|
146
|
+
const match = grepItem(agent as unknown as Record<string, unknown>, pattern);
|
|
147
|
+
if (match) {
|
|
148
|
+
results.push({
|
|
149
|
+
type: 'agent',
|
|
150
|
+
ulid: agent._ulid,
|
|
151
|
+
title: `${agent.id} - ${agent.name}`,
|
|
152
|
+
matchedFields: match.matchedFields,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Search workflows
|
|
158
|
+
for (const workflow of metaCtx.workflows) {
|
|
159
|
+
const match = grepItem(workflow as unknown as Record<string, unknown>, pattern);
|
|
160
|
+
if (match) {
|
|
161
|
+
results.push({
|
|
162
|
+
type: 'workflow',
|
|
163
|
+
ulid: workflow._ulid,
|
|
164
|
+
title: workflow.id,
|
|
165
|
+
matchedFields: match.matchedFields,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Search conventions
|
|
171
|
+
for (const convention of metaCtx.conventions) {
|
|
172
|
+
const match = grepItem(convention as unknown as Record<string, unknown>, pattern);
|
|
173
|
+
if (match) {
|
|
174
|
+
results.push({
|
|
175
|
+
type: 'convention',
|
|
176
|
+
ulid: convention._ulid,
|
|
177
|
+
title: convention.domain,
|
|
178
|
+
matchedFields: match.matchedFields,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Apply limit
|
|
185
|
+
const limitedResults = results.slice(0, limit);
|
|
186
|
+
|
|
187
|
+
// AC: @api-contract ac-19 - Return search results with matched fields
|
|
188
|
+
return {
|
|
189
|
+
results: limitedResults,
|
|
190
|
+
total: results.length,
|
|
191
|
+
showing: limitedResults.length,
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
query: t.Object({
|
|
196
|
+
q: t.Optional(t.String()),
|
|
197
|
+
type: t.Optional(t.String()),
|
|
198
|
+
status: t.Optional(t.String()),
|
|
199
|
+
itemsOnly: t.Optional(t.String()),
|
|
200
|
+
tasksOnly: t.Optional(t.String()),
|
|
201
|
+
limit: t.Optional(t.String()),
|
|
202
|
+
}),
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
// AC: @api-contract ac-20 - Run full validation
|
|
207
|
+
.get('/validate', async ({ projectContext }) => {
|
|
208
|
+
// AC: @multi-directory-daemon ac-1, ac-24 - Use project context from middleware
|
|
209
|
+
const ctx = await initContext(projectContext.path);
|
|
210
|
+
|
|
211
|
+
// AC: @api-contract ac-20 - Run validation and return ValidationResult
|
|
212
|
+
const result = await validate(ctx);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
valid: result.valid,
|
|
216
|
+
schemaErrors: result.schemaErrors,
|
|
217
|
+
refErrors: result.refErrors,
|
|
218
|
+
refWarnings: result.refWarnings,
|
|
219
|
+
orphans: result.orphans,
|
|
220
|
+
completenessWarnings: result.completenessWarnings,
|
|
221
|
+
traitCycles: result.traitCycles,
|
|
222
|
+
};
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// AC: @api-contract ac-21 - Get alignment stats and warnings
|
|
226
|
+
.get('/alignment', async ({ projectContext }) => {
|
|
227
|
+
// AC: @multi-directory-daemon ac-1, ac-24 - Use project context from middleware
|
|
228
|
+
const ctx = await initContext(projectContext.path);
|
|
229
|
+
const { tasks, items, refIndex } = await buildIndexes(ctx);
|
|
230
|
+
|
|
231
|
+
// AC: @api-contract ac-21 - Create AlignmentIndex and get stats
|
|
232
|
+
const alignIndex = new AlignmentIndex(tasks, items);
|
|
233
|
+
alignIndex.buildLinks(refIndex);
|
|
234
|
+
|
|
235
|
+
const stats = alignIndex.getStats();
|
|
236
|
+
const warnings = alignIndex.findAlignmentWarnings();
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
stats: {
|
|
240
|
+
totalSpecs: stats.totalSpecs,
|
|
241
|
+
specsWithTasks: stats.specsWithTasks,
|
|
242
|
+
alignedSpecs: stats.alignedSpecs,
|
|
243
|
+
orphanedSpecs: stats.orphanedSpecs,
|
|
244
|
+
},
|
|
245
|
+
warnings,
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kspec Daemon Server
|
|
3
|
+
*
|
|
4
|
+
* Elysia.js HTTP server with WebSocket support for real-time kspec state updates.
|
|
5
|
+
* Implements localhost-only security, file watching, and graceful shutdown.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Elysia } from 'elysia';
|
|
9
|
+
import { cors } from '@elysiajs/cors';
|
|
10
|
+
import { staticPlugin } from '@elysiajs/static';
|
|
11
|
+
import { ulid } from 'ulidx';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import { PubSubManager } from './websocket/pubsub';
|
|
14
|
+
import { HeartbeatManager } from './websocket/heartbeat';
|
|
15
|
+
import { WebSocketHandler } from './websocket/handler';
|
|
16
|
+
import type { ConnectionData, ConnectedEvent } from './websocket/types';
|
|
17
|
+
import { PidFileManager } from './pid';
|
|
18
|
+
import { projectContextMiddleware } from './middleware/project-context';
|
|
19
|
+
import { createTasksRoutes } from './routes/tasks';
|
|
20
|
+
import { createItemsRoutes } from './routes/items';
|
|
21
|
+
import { createInboxRoutes } from './routes/inbox';
|
|
22
|
+
import { createMetaRoutes } from './routes/meta';
|
|
23
|
+
import { createValidationRoutes } from './routes/validation';
|
|
24
|
+
import { createProjectsRoutes } from './routes/projects';
|
|
25
|
+
import { createTriageRoutes } from './routes/triage';
|
|
26
|
+
import { join } from 'path';
|
|
27
|
+
|
|
28
|
+
export interface ServerOptions {
|
|
29
|
+
port: number;
|
|
30
|
+
isDaemon: boolean;
|
|
31
|
+
kspecDir?: string; // Path to .kspec directory (default: .kspec in cwd)
|
|
32
|
+
webUiDir?: string; // Path to web UI build directory (default: auto-detect)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolves the path to the web UI build directory.
|
|
37
|
+
* Tries multiple locations in order:
|
|
38
|
+
* 1. Explicit webUiDir option
|
|
39
|
+
* 2. WEB_UI_DIR environment variable
|
|
40
|
+
* 3. packages/web-ui/build in current working directory (monorepo dev)
|
|
41
|
+
* 4. web-ui/build in current working directory
|
|
42
|
+
*/
|
|
43
|
+
function resolveWebUiPath(webUiDir?: string): string | null {
|
|
44
|
+
// 1. Explicit option
|
|
45
|
+
if (webUiDir && existsSync(webUiDir)) {
|
|
46
|
+
return webUiDir;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 2. Environment variable
|
|
50
|
+
const envPath = process.env.WEB_UI_DIR;
|
|
51
|
+
if (envPath && existsSync(envPath)) {
|
|
52
|
+
return envPath;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 3. Monorepo development: packages/web-ui/build from cwd
|
|
56
|
+
// The daemon is spawned with cwd set to project root
|
|
57
|
+
const monorepoPath = join(process.cwd(), 'packages', 'web-ui', 'build');
|
|
58
|
+
if (existsSync(monorepoPath)) {
|
|
59
|
+
return monorepoPath;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 4. Alternate location: web-ui/build in cwd
|
|
63
|
+
const altPath = join(process.cwd(), 'web-ui', 'build');
|
|
64
|
+
if (existsSync(altPath)) {
|
|
65
|
+
return altPath;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// WebSocket pub/sub and heartbeat managers
|
|
72
|
+
let pubsubManager: PubSubManager;
|
|
73
|
+
let heartbeatManager: HeartbeatManager;
|
|
74
|
+
let wsHandler: WebSocketHandler;
|
|
75
|
+
let projectManager: import('./project-context').ProjectContextManager | undefined;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Middleware to enforce localhost-only connections.
|
|
79
|
+
* AC-3: Reject non-localhost connections with 403 Forbidden
|
|
80
|
+
*/
|
|
81
|
+
function localhostOnly() {
|
|
82
|
+
return (context: { request: Request }) => {
|
|
83
|
+
const host = context.request.headers.get('host');
|
|
84
|
+
if (!host) {
|
|
85
|
+
return new Response(JSON.stringify({
|
|
86
|
+
error: 'Forbidden',
|
|
87
|
+
message: 'This server only accepts connections from localhost'
|
|
88
|
+
}), {
|
|
89
|
+
status: 403,
|
|
90
|
+
headers: { 'Content-Type': 'application/json' }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Extract hostname, handling IPv6 brackets
|
|
95
|
+
let hostname: string;
|
|
96
|
+
if (host.startsWith('[')) {
|
|
97
|
+
// IPv6 with brackets: [::1]:3456 -> ::1
|
|
98
|
+
const closeBracket = host.indexOf(']');
|
|
99
|
+
hostname = closeBracket > 0 ? host.substring(1, closeBracket) : host;
|
|
100
|
+
} else {
|
|
101
|
+
// IPv4 or hostname: localhost:3456 -> localhost
|
|
102
|
+
hostname = host.split(':')[0];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Allow localhost, 127.0.0.1, and ::1
|
|
106
|
+
const isLocalhost =
|
|
107
|
+
hostname === 'localhost' ||
|
|
108
|
+
hostname === '127.0.0.1' ||
|
|
109
|
+
hostname === '::1';
|
|
110
|
+
|
|
111
|
+
if (!isLocalhost) {
|
|
112
|
+
return new Response(JSON.stringify({
|
|
113
|
+
error: 'Forbidden',
|
|
114
|
+
message: 'This server only accepts connections from localhost'
|
|
115
|
+
}), {
|
|
116
|
+
status: 403,
|
|
117
|
+
headers: { 'Content-Type': 'application/json' }
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Creates and configures the Elysia server instance.
|
|
125
|
+
*
|
|
126
|
+
* AC Coverage:
|
|
127
|
+
* - ac-1: Server starts on configurable port (default 3456)
|
|
128
|
+
* - ac-2: Binds to localhost only (127.0.0.1 and ::1)
|
|
129
|
+
* - ac-3: Rejects non-localhost connections with 403
|
|
130
|
+
* - ac-15: Uses plugin pattern for middleware
|
|
131
|
+
*/
|
|
132
|
+
export async function createServer(options: ServerOptions) {
|
|
133
|
+
const { port, isDaemon, kspecDir = join(process.cwd(), '.kspec'), webUiDir } = options;
|
|
134
|
+
|
|
135
|
+
// Determine startup project path (project root, not .kspec/)
|
|
136
|
+
// AC: @multi-directory-daemon ac-2 - daemon uses startup directory as default project
|
|
137
|
+
const startupProjectPath = kspecDir.endsWith('.kspec')
|
|
138
|
+
? kspecDir.slice(0, -('.kspec'.length + 1)) // Remove '/.kspec'
|
|
139
|
+
: kspecDir;
|
|
140
|
+
|
|
141
|
+
// Import ProjectContextManager (needed for WebSocket binding)
|
|
142
|
+
const { ProjectContextManager } = await import('./project-context');
|
|
143
|
+
|
|
144
|
+
// AC: @daemon-server ac-17 - Resolve web UI path for static file serving
|
|
145
|
+
const resolvedWebUiPath = resolveWebUiPath(webUiDir);
|
|
146
|
+
if (resolvedWebUiPath) {
|
|
147
|
+
console.log(`[daemon] Web UI assets found at: ${resolvedWebUiPath}`);
|
|
148
|
+
} else {
|
|
149
|
+
console.log('[daemon] Web UI assets not found - UI will not be served');
|
|
150
|
+
console.log('[daemon] Build the web UI with: cd packages/web-ui && npm run build');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Initialize PID file manager (uses global ~/.config/kspec/)
|
|
154
|
+
const pidManager = new PidFileManager();
|
|
155
|
+
|
|
156
|
+
// AC: @multi-directory-daemon ac-9 - Write PID and port files in daemon mode
|
|
157
|
+
if (isDaemon) {
|
|
158
|
+
pidManager.writePid();
|
|
159
|
+
pidManager.writePort(port);
|
|
160
|
+
console.log(`[daemon] PID file written: ${process.pid}`);
|
|
161
|
+
console.log(`[daemon] Port file written: ${port}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Initialize WebSocket managers
|
|
165
|
+
pubsubManager = new PubSubManager();
|
|
166
|
+
heartbeatManager = new HeartbeatManager();
|
|
167
|
+
wsHandler = new WebSocketHandler(pubsubManager);
|
|
168
|
+
|
|
169
|
+
// WeakMap to store project path during WebSocket upgrade
|
|
170
|
+
const wsProjectPaths = new Map<string, string>();
|
|
171
|
+
|
|
172
|
+
const app = new Elysia()
|
|
173
|
+
// AC-15: Plugin pattern for middleware
|
|
174
|
+
// AC: @api-contract ac-1 - Allow CORS from dev server on localhost:5173
|
|
175
|
+
.use(cors({
|
|
176
|
+
origin: ['http://localhost:5173', 'http://127.0.0.1:5173'], // Dev server origins
|
|
177
|
+
credentials: true,
|
|
178
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
|
|
179
|
+
}))
|
|
180
|
+
|
|
181
|
+
// AC-3: Enforce localhost-only connections
|
|
182
|
+
.onRequest(localhostOnly());
|
|
183
|
+
|
|
184
|
+
// AC: @multi-directory-daemon ac-1, ac-2, ac-3 - Project context middleware
|
|
185
|
+
const { manager: projectContextManager, middleware: projectMiddleware } = projectContextMiddleware({
|
|
186
|
+
startupProject: startupProjectPath,
|
|
187
|
+
pubsub: pubsubManager
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Store manager globally for shutdown
|
|
191
|
+
projectManager = projectContextManager;
|
|
192
|
+
|
|
193
|
+
app.use(projectMiddleware)
|
|
194
|
+
|
|
195
|
+
// AC-11: Health check endpoint
|
|
196
|
+
.get('/api/health', () => ({
|
|
197
|
+
status: 'ok',
|
|
198
|
+
uptime: process.uptime(),
|
|
199
|
+
connections: pubsubManager.getConnectionCount(),
|
|
200
|
+
version: '0.1.0'
|
|
201
|
+
}))
|
|
202
|
+
|
|
203
|
+
// AC: @api-contract ac-2 through ac-7 - Task API endpoints
|
|
204
|
+
// AC: @multi-directory-daemon ac-24 - Routes use projectContext from middleware
|
|
205
|
+
.use(createTasksRoutes({ pubsub: pubsubManager }))
|
|
206
|
+
|
|
207
|
+
// AC: @api-contract ac-8 through ac-11 - Spec Item API endpoints
|
|
208
|
+
.use(createItemsRoutes())
|
|
209
|
+
|
|
210
|
+
// AC: @api-contract ac-12 through ac-14 - Inbox API endpoints
|
|
211
|
+
.use(createInboxRoutes({ pubsub: pubsubManager }))
|
|
212
|
+
|
|
213
|
+
// AC: @api-contract ac-15 through ac-18 - Meta API endpoints
|
|
214
|
+
.use(createMetaRoutes())
|
|
215
|
+
|
|
216
|
+
// AC: @triage-daemon-api ac-1 through ac-9 - Triage API endpoints
|
|
217
|
+
.use(createTriageRoutes({ pubsub: pubsubManager }))
|
|
218
|
+
|
|
219
|
+
// AC: @api-contract ac-19 through ac-21 - Validation and search endpoints
|
|
220
|
+
.use(createValidationRoutes())
|
|
221
|
+
|
|
222
|
+
// AC: @multi-directory-daemon ac-28, ac-29, ac-30 - Projects management endpoints
|
|
223
|
+
.use(createProjectsRoutes({ projectManager: projectContextManager }))
|
|
224
|
+
|
|
225
|
+
// AC-4: WebSocket endpoint for real-time updates
|
|
226
|
+
.ws<ConnectionData>('/ws', {
|
|
227
|
+
beforeHandle({ request, store }) {
|
|
228
|
+
// AC: @multi-directory-daemon ac-21, ac-22, ac-23, ac-34 - Extract and validate project binding
|
|
229
|
+
// AC: @multi-directory-daemon ac-34 - Browser WebSocket API doesn't support custom headers,
|
|
230
|
+
// so we also accept project path as query parameter
|
|
231
|
+
const url = new URL(request.url, `http://${request.headers.get('host')}`);
|
|
232
|
+
const projectPath = request.headers.get('X-Kspec-Dir')
|
|
233
|
+
|| url.searchParams.get('project')
|
|
234
|
+
|| undefined;
|
|
235
|
+
const requestId = ulid(); // Temporary ID to correlate upgrade with open
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const manager = (store as Record<string, unknown>).projectManager as import('./project-context').ProjectContextManager | undefined;
|
|
239
|
+
if (!manager) {
|
|
240
|
+
// Fallback: project manager not initialized yet
|
|
241
|
+
wsProjectPaths.set(requestId, startupProjectPath);
|
|
242
|
+
return { wsRequestId: requestId };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let projectContext;
|
|
246
|
+
if (projectPath) {
|
|
247
|
+
// Explicit project specified
|
|
248
|
+
try {
|
|
249
|
+
projectContext = manager.getProject(projectPath);
|
|
250
|
+
} catch {
|
|
251
|
+
// AC: @multi-directory-daemon ac-4 - auto-register
|
|
252
|
+
projectContext = manager.registerProject(projectPath);
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
// AC: @multi-directory-daemon ac-22, ac-23 - Use default or reject
|
|
256
|
+
try {
|
|
257
|
+
projectContext = manager.getProject();
|
|
258
|
+
} catch (err: unknown) {
|
|
259
|
+
// AC: @multi-directory-daemon ac-23 - Reject when no default
|
|
260
|
+
if (err instanceof Error && err.message.includes('No default project configured')) {
|
|
261
|
+
throw new Error('No project specified');
|
|
262
|
+
}
|
|
263
|
+
throw err;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Store resolved path for open() handler
|
|
268
|
+
wsProjectPaths.set(requestId, projectContext.path);
|
|
269
|
+
return { wsRequestId: requestId };
|
|
270
|
+
} catch (err: unknown) {
|
|
271
|
+
console.error(`[daemon] WebSocket connection rejected: ${err instanceof Error ? err.message : String(err)}`);
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
open(ws) {
|
|
276
|
+
// AC: @api-contract ac-25, @trait-websocket-protocol ac-1
|
|
277
|
+
const sessionId = ulid();
|
|
278
|
+
|
|
279
|
+
// AC: @multi-directory-daemon ac-21 - Get bound project path
|
|
280
|
+
// Fallback to startup project if not found (shouldn't happen)
|
|
281
|
+
const requestId = (ws.data as ConnectionData & { wsRequestId?: string }).wsRequestId;
|
|
282
|
+
const projectPath = requestId ? wsProjectPaths.get(requestId) || startupProjectPath : startupProjectPath;
|
|
283
|
+
|
|
284
|
+
// Clean up temporary mapping
|
|
285
|
+
if (requestId) {
|
|
286
|
+
wsProjectPaths.delete(requestId);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
ws.data = {
|
|
290
|
+
sessionId,
|
|
291
|
+
topics: new Set<string>(),
|
|
292
|
+
seq: 0,
|
|
293
|
+
lastPing: undefined,
|
|
294
|
+
lastPong: Date.now(),
|
|
295
|
+
projectPath // AC: @multi-directory-daemon ac-21 - immutable binding
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
pubsubManager.addConnection(sessionId, ws);
|
|
299
|
+
console.log(`[daemon] WebSocket client connected: ${sessionId} bound to ${projectPath} (${pubsubManager.getConnectionCount()} total)`);
|
|
300
|
+
|
|
301
|
+
// Send connected event with session_id
|
|
302
|
+
const connectedEvent: ConnectedEvent = {
|
|
303
|
+
event: 'connected',
|
|
304
|
+
data: {
|
|
305
|
+
session_id: sessionId
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
ws.send(JSON.stringify(connectedEvent));
|
|
309
|
+
},
|
|
310
|
+
message(ws, message) {
|
|
311
|
+
// AC: @api-contract ac-26, ac-27
|
|
312
|
+
wsHandler.handleMessage(ws, message);
|
|
313
|
+
},
|
|
314
|
+
pong(ws) {
|
|
315
|
+
// AC: @trait-websocket-protocol ac-5
|
|
316
|
+
heartbeatManager.recordPong(ws);
|
|
317
|
+
},
|
|
318
|
+
close(ws, code, reason) {
|
|
319
|
+
pubsubManager.removeConnection(ws.data.sessionId);
|
|
320
|
+
console.log(`[daemon] WebSocket client disconnected: ${ws.data.sessionId} (code: ${code}, reason: ${reason})`);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// AC: @daemon-server ac-17 - Serve web UI static assets
|
|
325
|
+
// Added after API routes so API routes take precedence
|
|
326
|
+
if (resolvedWebUiPath) {
|
|
327
|
+
const indexHtmlPath = join(resolvedWebUiPath, 'index.html');
|
|
328
|
+
|
|
329
|
+
// Serve static files from web UI build directory
|
|
330
|
+
app.use(await staticPlugin({
|
|
331
|
+
assets: resolvedWebUiPath,
|
|
332
|
+
prefix: '/',
|
|
333
|
+
noCache: process.env.NODE_ENV === 'development', // Disable cache in dev
|
|
334
|
+
}));
|
|
335
|
+
|
|
336
|
+
// SPA fallback routes for client-side routing
|
|
337
|
+
// These catch paths like /tasks, /items, /inbox that don't have static files
|
|
338
|
+
const spaRoutes = ['/tasks', '/tasks/*', '/items', '/items/*', '/inbox', '/observations', '/triage'];
|
|
339
|
+
for (const route of spaRoutes) {
|
|
340
|
+
app.get(route, () => Bun.file(indexHtmlPath));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
console.log('[daemon] Web UI static file serving enabled');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// AC-1, AC-2: Start server on localhost only
|
|
347
|
+
// Using 'localhost' hostname allows Bun/OS to bind to both 127.0.0.1 and ::1
|
|
348
|
+
app.listen({
|
|
349
|
+
port,
|
|
350
|
+
hostname: 'localhost', // Resolves to both IPv4 and IPv6 loopback
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
console.log(`[daemon] Server listening on http://localhost:${port} (IPv4: 127.0.0.1, IPv6: ::1)`);
|
|
354
|
+
console.log(`[daemon] WebSocket available at ws://localhost:${port}/ws`);
|
|
355
|
+
|
|
356
|
+
// AC: @multi-directory-daemon ac-17 - Start file watcher for startup project
|
|
357
|
+
if (startupProjectPath) {
|
|
358
|
+
try {
|
|
359
|
+
await projectContextManager.startWatcher(startupProjectPath);
|
|
360
|
+
console.log(`[daemon] File watcher started for startup project: ${startupProjectPath}`);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('[daemon] Failed to start file watcher for startup project:', error);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// AC: @daemon-server ac-13, ac-14 - Start heartbeat monitoring
|
|
367
|
+
heartbeatManager.start(pubsubManager.getAllConnections());
|
|
368
|
+
|
|
369
|
+
// AC-12: Graceful shutdown on SIGTERM/SIGINT
|
|
370
|
+
const shutdown = async (signal: string) => {
|
|
371
|
+
console.log(`[daemon] Received ${signal}, shutting down gracefully...`);
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
// Stop heartbeat monitoring
|
|
375
|
+
heartbeatManager.stop();
|
|
376
|
+
|
|
377
|
+
// AC: @multi-directory-daemon ac-11b - Stop all file watchers
|
|
378
|
+
await projectContextManager.stopAllWatchers();
|
|
379
|
+
console.log('[daemon] All file watchers stopped');
|
|
380
|
+
|
|
381
|
+
// Close all WebSocket connections with code 1000 (clean close)
|
|
382
|
+
// AC: @trait-websocket-protocol ac-7
|
|
383
|
+
for (const [sessionId, ws] of pubsubManager.getAllConnections()) {
|
|
384
|
+
ws.close(1000, 'Server shutting down');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Stop the server
|
|
388
|
+
await app.server?.stop();
|
|
389
|
+
|
|
390
|
+
// AC: @daemon-server ac-10 - Remove PID file on shutdown
|
|
391
|
+
if (isDaemon) {
|
|
392
|
+
pidManager.remove();
|
|
393
|
+
console.log('[daemon] PID file removed');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
console.log('[daemon] Server stopped successfully');
|
|
397
|
+
process.exit(0);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.error('[daemon] Error during shutdown:', error);
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
405
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
406
|
+
|
|
407
|
+
return app;
|
|
408
|
+
}
|