@kynetic-ai/spec 0.1.2 → 0.4.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 +107 -0
- package/dist/cli/batch-exec.d.ts.map +1 -0
- package/dist/cli/batch-exec.js +706 -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 +141 -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 +58 -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 +534 -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 +547 -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 +1109 -170
- 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 +810 -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 +1267 -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 +56 -0
- package/dist/cli/commands/skill-install.d.ts.map +1 -0
- package/dist/cli/commands/skill-install.js +509 -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 +584 -350
- 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 +225 -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 +483 -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 +237 -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 +402 -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 +457 -0
- package/dist/parser/config.d.ts.map +1 -0
- package/dist/parser/config.js +373 -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 +197 -0
- package/dist/parser/plan-document.d.ts.map +1 -0
- package/dist/parser/plan-document.js +341 -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 +301 -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 +94 -0
- package/dist/ralph/subagent.d.ts.map +1 -0
- package/dist/ralph/subagent.js +193 -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 +97 -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 +8 -2
- package/dist/schema/common.d.ts.map +1 -1
- package/dist/schema/common.js +42 -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 +241 -1
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +810 -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 +55 -0
- package/dist/strings/errors.d.ts.map +1 -1
- package/dist/strings/errors.js +138 -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/triage/actions.d.ts +27 -0
- package/dist/triage/actions.d.ts.map +1 -0
- package/dist/triage/actions.js +95 -0
- package/dist/triage/actions.js.map +1 -0
- package/dist/triage/constants.d.ts +6 -0
- package/dist/triage/constants.d.ts.map +1 -0
- package/dist/triage/constants.js +7 -0
- package/dist/triage/constants.js.map +1 -0
- package/dist/triage/index.d.ts +3 -0
- package/dist/triage/index.d.ts.map +1 -0
- package/dist/triage/index.js +3 -0
- package/dist/triage/index.js.map +1 -0
- 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/create-workflow/SKILL.md +235 -0
- package/plugin/plugins/kspec/skills/help/SKILL.md +42 -0
- package/plugin/plugins/kspec/skills/observations/SKILL.md +143 -0
- package/plugin/plugins/kspec/skills/plan/SKILL.md +343 -0
- package/plugin/plugins/kspec/skills/reflect/SKILL.md +161 -0
- package/plugin/plugins/kspec/skills/review/SKILL.md +193 -0
- package/plugin/plugins/kspec/skills/task-work/SKILL.md +303 -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/plugin/plugins/kspec/skills/triage-automation/SKILL.md +140 -0
- package/plugin/plugins/kspec/skills/triage-inbox/SKILL.md +232 -0
- package/plugin/plugins/kspec/skills/writing-specs/SKILL.md +340 -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/create-workflow/SKILL.md +228 -0
- package/templates/skills/help/SKILL.md +37 -0
- package/templates/skills/manifest.yaml +60 -0
- package/templates/skills/observations/SKILL.md +137 -0
- package/templates/skills/plan/SKILL.md +336 -0
- package/templates/skills/reflect/SKILL.md +155 -0
- package/templates/skills/review/SKILL.md +186 -0
- package/templates/skills/task-work/SKILL.md +296 -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
- package/templates/skills/triage-automation/SKILL.md +134 -0
- package/templates/skills/triage-inbox/SKILL.md +225 -0
- package/templates/skills/writing-specs/SKILL.md +333 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Context Middleware for Multi-Directory Daemon
|
|
3
|
+
*
|
|
4
|
+
* Extracts X-Kspec-Dir header and attaches ProjectContext to request state.
|
|
5
|
+
* Implements path validation and automatic project registration.
|
|
6
|
+
*
|
|
7
|
+
* AC: @multi-directory-daemon ac-1, ac-2, ac-3, ac-4, ac-5, ac-6, ac-7, ac-8, ac-8b, ac-8c, ac-20b
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Elysia } from 'elysia';
|
|
11
|
+
import { ProjectContextManager, type ProjectContext } from '../project-context';
|
|
12
|
+
import type { PubSubManager } from '../websocket/pubsub';
|
|
13
|
+
|
|
14
|
+
export interface ProjectContextMiddlewareOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Optional startup project path (daemon's cwd at boot if it has .kspec/)
|
|
17
|
+
*/
|
|
18
|
+
startupProject?: string;
|
|
19
|
+
/**
|
|
20
|
+
* PubSubManager for broadcasting file changes
|
|
21
|
+
*/
|
|
22
|
+
pubsub?: PubSubManager;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates project context middleware plugin for Elysia.
|
|
27
|
+
*
|
|
28
|
+
* Extracts X-Kspec-Dir header, validates path, registers/retrieves project,
|
|
29
|
+
* and attaches ProjectContext to request state.
|
|
30
|
+
*
|
|
31
|
+
* Returns both the manager (for external access) and the middleware function.
|
|
32
|
+
*/
|
|
33
|
+
export function projectContextMiddleware(options: ProjectContextMiddlewareOptions = {}) {
|
|
34
|
+
const manager = new ProjectContextManager(options.startupProject, options.pubsub);
|
|
35
|
+
|
|
36
|
+
// Register startup project if provided
|
|
37
|
+
// Note: Watcher will be started later after full initialization
|
|
38
|
+
if (options.startupProject) {
|
|
39
|
+
try {
|
|
40
|
+
manager.registerProject(options.startupProject, true);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.warn(`[daemon] Failed to register startup project: ${error}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const middleware = (app: Elysia) =>
|
|
47
|
+
app
|
|
48
|
+
// Store manager in app state for WebSocket access
|
|
49
|
+
.state('projectManager', manager)
|
|
50
|
+
.derive(async ({ request, set }) => {
|
|
51
|
+
try {
|
|
52
|
+
// AC: @multi-directory-daemon ac-1 - Extract X-Kspec-Dir header
|
|
53
|
+
const projectPath = request.headers.get('X-Kspec-Dir') || undefined;
|
|
54
|
+
|
|
55
|
+
let projectContext: ProjectContext;
|
|
56
|
+
|
|
57
|
+
if (projectPath) {
|
|
58
|
+
// AC: @multi-directory-daemon ac-1, ac-4, ac-5, ac-6, ac-7, ac-8, ac-8b, ac-8c
|
|
59
|
+
// Try to get existing or register new project
|
|
60
|
+
try {
|
|
61
|
+
projectContext = manager.getProject(projectPath);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// Not registered - try to register (ac-4: auto-register)
|
|
64
|
+
projectContext = manager.registerProject(projectPath);
|
|
65
|
+
// Start watcher asynchronously (don't block request)
|
|
66
|
+
void manager.startWatcher(projectPath).catch((watcherError) => {
|
|
67
|
+
console.error(`[daemon] Failed to start watcher for ${projectPath}:`, watcherError);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// AC: @multi-directory-daemon ac-2, ac-3, ac-20b
|
|
72
|
+
// No header - use default project
|
|
73
|
+
projectContext = manager.getProject();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { projectContext };
|
|
77
|
+
} catch (err: unknown) {
|
|
78
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
79
|
+
|
|
80
|
+
// AC: @multi-directory-daemon ac-3, ac-20b
|
|
81
|
+
if (
|
|
82
|
+
message.includes('No default project configured') ||
|
|
83
|
+
message.includes('Default project no longer valid')
|
|
84
|
+
) {
|
|
85
|
+
set.status = 400;
|
|
86
|
+
return { error: message };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// AC: @multi-directory-daemon ac-5
|
|
90
|
+
if (message.includes('Invalid kspec project')) {
|
|
91
|
+
set.status = 400;
|
|
92
|
+
return { error: message };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// AC: @multi-directory-daemon ac-6
|
|
96
|
+
if (message.includes('Path must be absolute')) {
|
|
97
|
+
set.status = 400;
|
|
98
|
+
return { error: 'Path must be absolute' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// AC: @multi-directory-daemon ac-7
|
|
102
|
+
if (message.includes('Path must not contain parent traversal')) {
|
|
103
|
+
set.status = 400;
|
|
104
|
+
return { error: 'Path must not contain parent traversal' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// AC: @multi-directory-daemon ac-8b - permission denied
|
|
108
|
+
if (message.includes('Permission denied')) {
|
|
109
|
+
set.status = 403;
|
|
110
|
+
return { error: message };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// AC: @multi-directory-daemon ac-19 - OS resource limits
|
|
114
|
+
if (message.includes('Unable to watch project - resource limit reached')) {
|
|
115
|
+
set.status = 503;
|
|
116
|
+
return { error: message };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Other errors
|
|
120
|
+
set.status = 500;
|
|
121
|
+
return { error: 'Internal server error' };
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return { manager, middleware };
|
|
126
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PID File Management
|
|
3
|
+
*
|
|
4
|
+
* Manages daemon process PID and port files for lifecycle control.
|
|
5
|
+
* Uses global config directory (~/.config/kspec/) instead of per-project .kspec/
|
|
6
|
+
* AC: @multi-directory-daemon ac-9, ac-9b, ac-9c, ac-10, ac-11, ac-13
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync, openSync, closeSync, constants } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
|
|
13
|
+
export class PidFileManager {
|
|
14
|
+
private configDir: string;
|
|
15
|
+
private pidFilePath: string;
|
|
16
|
+
private portFilePath: string;
|
|
17
|
+
|
|
18
|
+
constructor(configDir: string = join(homedir(), '.config', 'kspec')) {
|
|
19
|
+
this.configDir = configDir;
|
|
20
|
+
this.pidFilePath = join(configDir, 'daemon.pid');
|
|
21
|
+
this.portFilePath = join(configDir, 'daemon.port');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* AC: @multi-directory-daemon ac-9b
|
|
26
|
+
* Creates config directory with mode 0755 if it doesn't exist
|
|
27
|
+
*/
|
|
28
|
+
private ensureConfigDir(): void {
|
|
29
|
+
if (!existsSync(this.configDir)) {
|
|
30
|
+
mkdirSync(this.configDir, { recursive: true, mode: 0o755 });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* AC: @multi-directory-daemon ac-9, ac-10b
|
|
36
|
+
* Writes current process PID to ~/.config/kspec/daemon.pid
|
|
37
|
+
* Creates parent directory if it doesn't exist.
|
|
38
|
+
* Uses exclusive file creation flag to prevent concurrent daemon starts.
|
|
39
|
+
*/
|
|
40
|
+
writePid(): void {
|
|
41
|
+
this.ensureConfigDir();
|
|
42
|
+
|
|
43
|
+
// AC: @multi-directory-daemon ac-10b
|
|
44
|
+
// Use O_CREAT | O_EXCL flags for atomic file creation
|
|
45
|
+
// This prevents race conditions between concurrent daemon starts
|
|
46
|
+
try {
|
|
47
|
+
const fd = openSync(this.pidFilePath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o644);
|
|
48
|
+
try {
|
|
49
|
+
writeFileSync(fd, process.pid.toString(), 'utf-8');
|
|
50
|
+
} finally {
|
|
51
|
+
closeSync(fd);
|
|
52
|
+
}
|
|
53
|
+
} catch (err: unknown) {
|
|
54
|
+
if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'EEXIST') {
|
|
55
|
+
// File already exists - check if daemon is actually running
|
|
56
|
+
if (this.isDaemonRunning()) {
|
|
57
|
+
throw new Error('Daemon already running');
|
|
58
|
+
}
|
|
59
|
+
// Stale PID file - remove it and retry
|
|
60
|
+
this.remove();
|
|
61
|
+
const fd = openSync(this.pidFilePath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o644);
|
|
62
|
+
try {
|
|
63
|
+
writeFileSync(fd, process.pid.toString(), 'utf-8');
|
|
64
|
+
} finally {
|
|
65
|
+
closeSync(fd);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* AC: @multi-directory-daemon ac-9
|
|
75
|
+
* Writes daemon port to ~/.config/kspec/daemon.port
|
|
76
|
+
* Creates parent directory if it doesn't exist.
|
|
77
|
+
*/
|
|
78
|
+
writePort(port: number): void {
|
|
79
|
+
this.ensureConfigDir();
|
|
80
|
+
writeFileSync(this.portFilePath, port.toString(), 'utf-8');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Reads PID from ~/.config/kspec/daemon.pid
|
|
85
|
+
* Returns null if file doesn't exist or is invalid
|
|
86
|
+
*/
|
|
87
|
+
readPid(): number | null {
|
|
88
|
+
if (!existsSync(this.pidFilePath)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const content = readFileSync(this.pidFilePath, 'utf-8').trim();
|
|
94
|
+
const pid = parseInt(content, 10);
|
|
95
|
+
return isNaN(pid) ? null : pid;
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* AC: @multi-directory-daemon ac-9c, ac-13
|
|
103
|
+
* Reads port from ~/.config/kspec/daemon.port
|
|
104
|
+
* Throws error if file doesn't exist or contains invalid port
|
|
105
|
+
*/
|
|
106
|
+
readPort(): number {
|
|
107
|
+
if (!existsSync(this.portFilePath)) {
|
|
108
|
+
throw new Error('Invalid daemon port file');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const content = readFileSync(this.portFilePath, 'utf-8').trim();
|
|
113
|
+
const port = parseInt(content, 10);
|
|
114
|
+
|
|
115
|
+
// AC: @multi-directory-daemon ac-9c - validate port content
|
|
116
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
117
|
+
throw new Error('Invalid daemon port file');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return port;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
throw new Error('Invalid daemon port file');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* AC: @multi-directory-daemon ac-11
|
|
128
|
+
* Removes both PID and port files during graceful shutdown
|
|
129
|
+
*/
|
|
130
|
+
remove(): void {
|
|
131
|
+
if (existsSync(this.pidFilePath)) {
|
|
132
|
+
unlinkSync(this.pidFilePath);
|
|
133
|
+
}
|
|
134
|
+
if (existsSync(this.portFilePath)) {
|
|
135
|
+
unlinkSync(this.portFilePath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Checks if a process with given PID is running
|
|
141
|
+
*/
|
|
142
|
+
isProcessRunning(pid: number): boolean {
|
|
143
|
+
try {
|
|
144
|
+
// Sending signal 0 checks if process exists without actually sending a signal
|
|
145
|
+
process.kill(pid, 0);
|
|
146
|
+
return true;
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* AC: @multi-directory-daemon ac-10
|
|
154
|
+
* Checks if daemon is currently running based on PID file
|
|
155
|
+
*/
|
|
156
|
+
isDaemonRunning(): boolean {
|
|
157
|
+
const pid = this.readPid();
|
|
158
|
+
if (pid === null) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return this.isProcessRunning(pid);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Backwards compatibility: read() method maps to readPid()
|
|
166
|
+
* @deprecated Use readPid() instead
|
|
167
|
+
*/
|
|
168
|
+
read(): number | null {
|
|
169
|
+
return this.readPid();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Backwards compatibility: write() method maps to writePid()
|
|
174
|
+
* @deprecated Use writePid() instead
|
|
175
|
+
*/
|
|
176
|
+
write(): void {
|
|
177
|
+
this.writePid();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProjectContextManager - Multi-project daemon support
|
|
3
|
+
*
|
|
4
|
+
* Manages project registration, caching, path validation, and context management
|
|
5
|
+
* for multi-directory daemon architecture.
|
|
6
|
+
*
|
|
7
|
+
* AC: @multi-directory-daemon ac-1 through ac-20b
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import { isAbsolute, join, normalize, relative } from 'path';
|
|
12
|
+
import { KspecWatcher } from './watcher';
|
|
13
|
+
import type { PubSubManager } from './websocket/pubsub';
|
|
14
|
+
|
|
15
|
+
export interface ProjectContext {
|
|
16
|
+
path: string;
|
|
17
|
+
registeredAt: Date;
|
|
18
|
+
watcherActive: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Manages multiple kspec project contexts for the daemon server.
|
|
23
|
+
*
|
|
24
|
+
* Key responsibilities:
|
|
25
|
+
* - Project registration and caching
|
|
26
|
+
* - Path validation and normalization
|
|
27
|
+
* - Default project handling
|
|
28
|
+
* - Project lifecycle management
|
|
29
|
+
* - Per-project file watcher management
|
|
30
|
+
*/
|
|
31
|
+
export class ProjectContextManager {
|
|
32
|
+
private projects: Map<string, ProjectContext> = new Map();
|
|
33
|
+
private watchers: Map<string, KspecWatcher> = new Map();
|
|
34
|
+
private defaultProjectPath: string | null = null;
|
|
35
|
+
private pubsub: PubSubManager | null = null;
|
|
36
|
+
|
|
37
|
+
constructor(defaultProjectPath?: string, pubsub?: PubSubManager) {
|
|
38
|
+
if (defaultProjectPath) {
|
|
39
|
+
this.defaultProjectPath = defaultProjectPath;
|
|
40
|
+
}
|
|
41
|
+
if (pubsub) {
|
|
42
|
+
this.pubsub = pubsub;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Set the PubSubManager for broadcasting file changes.
|
|
48
|
+
* Must be called before starting watchers.
|
|
49
|
+
*
|
|
50
|
+
* @param pubsub - PubSubManager instance
|
|
51
|
+
*/
|
|
52
|
+
setPubSub(pubsub: PubSubManager): void {
|
|
53
|
+
this.pubsub = pubsub;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Start a file watcher for a project.
|
|
58
|
+
*
|
|
59
|
+
* AC: @multi-directory-daemon ac-17, ac-19
|
|
60
|
+
*
|
|
61
|
+
* @param projectPath - Absolute path to project root
|
|
62
|
+
* @throws Error if watcher creation fails (e.g., OS resource limits)
|
|
63
|
+
*/
|
|
64
|
+
async startWatcher(projectPath: string): Promise<void> {
|
|
65
|
+
const normalizedPath = this.normalizePath(projectPath);
|
|
66
|
+
|
|
67
|
+
// AC: @multi-directory-daemon ac-16 - Don't create duplicate watchers
|
|
68
|
+
if (this.watchers.has(normalizedPath)) {
|
|
69
|
+
return; // Watcher already running
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const kspecDir = join(normalizedPath, '.kspec');
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// AC: @multi-directory-daemon ac-17, ac-18 - Create watcher with project-scoped broadcasts
|
|
76
|
+
const watcher = new KspecWatcher({
|
|
77
|
+
kspecDir,
|
|
78
|
+
onFileChange: (file, content) => {
|
|
79
|
+
// AC: @multi-directory-daemon ac-17 - File changes trigger events scoped to project
|
|
80
|
+
if (this.pubsub) {
|
|
81
|
+
const relativePath = relative(kspecDir, file);
|
|
82
|
+
this.pubsub.broadcast('files:updates', 'file_changed', {
|
|
83
|
+
ref: relativePath,
|
|
84
|
+
action: 'modified'
|
|
85
|
+
}, normalizedPath);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
onError: (error, file) => {
|
|
89
|
+
// Broadcast error event scoped to project
|
|
90
|
+
if (this.pubsub) {
|
|
91
|
+
const relativePath = file ? relative(kspecDir, file) : undefined;
|
|
92
|
+
this.pubsub.broadcast('files:errors', 'file_error', {
|
|
93
|
+
ref: relativePath,
|
|
94
|
+
error: error.message
|
|
95
|
+
}, normalizedPath);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await watcher.start();
|
|
101
|
+
this.watchers.set(normalizedPath, watcher);
|
|
102
|
+
|
|
103
|
+
// Update context
|
|
104
|
+
const context = this.projects.get(normalizedPath);
|
|
105
|
+
if (context) {
|
|
106
|
+
context.watcherActive = true;
|
|
107
|
+
}
|
|
108
|
+
} catch (error: unknown) {
|
|
109
|
+
// AC: @multi-directory-daemon ac-19 - Handle OS limits (EMFILE/ENFILE)
|
|
110
|
+
const code = error instanceof Error && 'code' in error ? (error as NodeJS.ErrnoException).code : undefined;
|
|
111
|
+
if (code === 'EMFILE' || code === 'ENFILE') {
|
|
112
|
+
throw new Error('Unable to watch project - resource limit reached');
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Stop a file watcher for a project.
|
|
120
|
+
*
|
|
121
|
+
* AC: @multi-directory-daemon ac-20, ac-11b
|
|
122
|
+
*
|
|
123
|
+
* @param projectPath - Absolute path to project root
|
|
124
|
+
*/
|
|
125
|
+
async stopWatcher(projectPath: string): Promise<void> {
|
|
126
|
+
const normalizedPath = this.normalizePath(projectPath);
|
|
127
|
+
const watcher = this.watchers.get(normalizedPath);
|
|
128
|
+
|
|
129
|
+
if (watcher) {
|
|
130
|
+
await watcher.stop();
|
|
131
|
+
this.watchers.delete(normalizedPath);
|
|
132
|
+
|
|
133
|
+
// Update context
|
|
134
|
+
const context = this.projects.get(normalizedPath);
|
|
135
|
+
if (context) {
|
|
136
|
+
context.watcherActive = false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Stop all file watchers.
|
|
143
|
+
*
|
|
144
|
+
* AC: @multi-directory-daemon ac-11b - Shutdown stops all watchers
|
|
145
|
+
*/
|
|
146
|
+
async stopAllWatchers(): Promise<void> {
|
|
147
|
+
const stopPromises = Array.from(this.watchers.keys()).map(path =>
|
|
148
|
+
this.stopWatcher(path)
|
|
149
|
+
);
|
|
150
|
+
await Promise.all(stopPromises);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Register a project for multi-directory daemon support.
|
|
155
|
+
*
|
|
156
|
+
* AC: @multi-directory-daemon ac-4, ac-5, ac-6, ac-7, ac-8, ac-8c
|
|
157
|
+
*
|
|
158
|
+
* Note: This method is synchronous. Start watchers separately via startWatcher().
|
|
159
|
+
*
|
|
160
|
+
* @param projectPath - Absolute path to project root directory
|
|
161
|
+
* @param isDefault - Whether this project should be the default
|
|
162
|
+
* @returns Registered project context
|
|
163
|
+
* @throws Error if path validation fails or .kspec/ not found
|
|
164
|
+
*/
|
|
165
|
+
registerProject(projectPath: string, isDefault = false): ProjectContext {
|
|
166
|
+
// AC: @multi-directory-daemon ac-6 - reject relative paths
|
|
167
|
+
if (!this.isAbsolutePath(projectPath)) {
|
|
168
|
+
throw new Error('Path must be absolute');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// AC: @multi-directory-daemon ac-7 - reject parent traversal
|
|
172
|
+
if (projectPath.includes('..')) {
|
|
173
|
+
throw new Error('Path must not contain parent traversal');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// AC: @multi-directory-daemon ac-8 - normalize path (but don't resolve symlinks)
|
|
177
|
+
const normalizedPath = this.normalizePath(projectPath);
|
|
178
|
+
|
|
179
|
+
// AC: @multi-directory-daemon ac-5 - validate .kspec/ exists
|
|
180
|
+
const kspecDir = join(normalizedPath, '.kspec');
|
|
181
|
+
if (!existsSync(kspecDir)) {
|
|
182
|
+
throw new Error(`Invalid kspec project - .kspec/ not found at ${normalizedPath}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// AC: @multi-directory-daemon ac-16 - check if already registered (avoid duplicates)
|
|
186
|
+
if (this.projects.has(normalizedPath)) {
|
|
187
|
+
const existing = this.projects.get(normalizedPath)!;
|
|
188
|
+
if (isDefault) {
|
|
189
|
+
this.defaultProjectPath = normalizedPath;
|
|
190
|
+
}
|
|
191
|
+
return existing;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// AC: @multi-directory-daemon ac-4 - auto-register and cache
|
|
195
|
+
const context: ProjectContext = {
|
|
196
|
+
path: normalizedPath,
|
|
197
|
+
registeredAt: new Date(),
|
|
198
|
+
watcherActive: false, // Set to true when watcher is started
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
this.projects.set(normalizedPath, context);
|
|
202
|
+
|
|
203
|
+
if (isDefault) {
|
|
204
|
+
this.defaultProjectPath = normalizedPath;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return context;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get a project by path, or use default project if no path provided.
|
|
212
|
+
*
|
|
213
|
+
* AC: @multi-directory-daemon ac-1, ac-2, ac-3, ac-20b
|
|
214
|
+
*
|
|
215
|
+
* @param projectPath - Optional absolute path to project
|
|
216
|
+
* @returns Project context
|
|
217
|
+
* @throws Error if project not registered, no default, or default invalid
|
|
218
|
+
*/
|
|
219
|
+
getProject(projectPath?: string): ProjectContext {
|
|
220
|
+
// AC: @multi-directory-daemon ac-1 - use provided path
|
|
221
|
+
if (projectPath) {
|
|
222
|
+
const normalizedPath = this.normalizePath(projectPath);
|
|
223
|
+
const context = this.projects.get(normalizedPath);
|
|
224
|
+
if (!context) {
|
|
225
|
+
throw new Error(`Project not registered: ${normalizedPath}`);
|
|
226
|
+
}
|
|
227
|
+
return context;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// AC: @multi-directory-daemon ac-2, ac-3 - use default or error
|
|
231
|
+
if (!this.defaultProjectPath) {
|
|
232
|
+
throw new Error('No default project configured. Specify X-Kspec-Dir header.');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// AC: @multi-directory-daemon ac-20b - check if default project still valid
|
|
236
|
+
const kspecDir = join(this.defaultProjectPath, '.kspec');
|
|
237
|
+
if (!existsSync(kspecDir)) {
|
|
238
|
+
throw new Error('Default project no longer valid. Specify X-Kspec-Dir header.');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const context = this.projects.get(this.defaultProjectPath);
|
|
242
|
+
if (!context) {
|
|
243
|
+
throw new Error('Default project not registered');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return context;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Set the default project explicitly.
|
|
251
|
+
*
|
|
252
|
+
* AC: @multi-directory-daemon ac-2
|
|
253
|
+
*
|
|
254
|
+
* @param projectPath - Absolute path to project
|
|
255
|
+
* @throws Error if project not registered
|
|
256
|
+
*/
|
|
257
|
+
setDefaultProject(projectPath: string): void {
|
|
258
|
+
const normalizedPath = this.normalizePath(projectPath);
|
|
259
|
+
if (!this.projects.has(normalizedPath)) {
|
|
260
|
+
throw new Error('Project must be registered before setting as default');
|
|
261
|
+
}
|
|
262
|
+
this.defaultProjectPath = normalizedPath;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Check if a project is registered.
|
|
267
|
+
*
|
|
268
|
+
* @param projectPath - Absolute path to project
|
|
269
|
+
* @returns True if project is registered
|
|
270
|
+
*/
|
|
271
|
+
hasProject(projectPath: string): boolean {
|
|
272
|
+
const normalizedPath = this.normalizePath(projectPath);
|
|
273
|
+
return this.projects.has(normalizedPath);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Unregister a project and stop its watcher.
|
|
278
|
+
*
|
|
279
|
+
* AC: @multi-directory-daemon ac-20
|
|
280
|
+
*
|
|
281
|
+
* @param projectPath - Absolute path to project
|
|
282
|
+
*/
|
|
283
|
+
unregisterProject(projectPath: string): void {
|
|
284
|
+
const normalizedPath = this.normalizePath(projectPath);
|
|
285
|
+
|
|
286
|
+
// AC: @multi-directory-daemon ac-20 - Stop watcher when unregistering (async, fire-and-forget)
|
|
287
|
+
void this.stopWatcher(normalizedPath);
|
|
288
|
+
|
|
289
|
+
this.projects.delete(normalizedPath);
|
|
290
|
+
|
|
291
|
+
if (this.defaultProjectPath === normalizedPath) {
|
|
292
|
+
this.defaultProjectPath = null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* List all registered projects.
|
|
298
|
+
*
|
|
299
|
+
* AC: @multi-directory-daemon ac-14, ac-15
|
|
300
|
+
*
|
|
301
|
+
* @returns Array of registered project contexts
|
|
302
|
+
*/
|
|
303
|
+
listProjects(): ProjectContext[] {
|
|
304
|
+
return Array.from(this.projects.values());
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Normalize path without resolving symlinks.
|
|
309
|
+
*
|
|
310
|
+
* AC: @multi-directory-daemon ac-8, ac-8c
|
|
311
|
+
*
|
|
312
|
+
* Normalizes the path by:
|
|
313
|
+
* - Resolving "." segments
|
|
314
|
+
* - Removing trailing slashes
|
|
315
|
+
* - Normalizing multiple slashes
|
|
316
|
+
* - NOT resolving symlinks (symlinked paths treated as separate projects)
|
|
317
|
+
*
|
|
318
|
+
* @param projectPath - Path to normalize
|
|
319
|
+
* @returns Normalized path
|
|
320
|
+
*/
|
|
321
|
+
private normalizePath(projectPath: string): string {
|
|
322
|
+
// Remove trailing slashes and resolve "." segments
|
|
323
|
+
// But do NOT resolve symlinks (no realpath/fs.realpathSync)
|
|
324
|
+
let normalized = normalize(projectPath);
|
|
325
|
+
|
|
326
|
+
// Remove trailing slash (normalize doesn't always do this)
|
|
327
|
+
if (normalized !== '/' && normalized.endsWith('/')) {
|
|
328
|
+
normalized = normalized.slice(0, -1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return normalized;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Check if path is absolute.
|
|
336
|
+
*
|
|
337
|
+
* @param projectPath - Path to check
|
|
338
|
+
* @returns True if path is absolute
|
|
339
|
+
*/
|
|
340
|
+
private isAbsolutePath(projectPath: string): boolean {
|
|
341
|
+
return isAbsolute(projectPath);
|
|
342
|
+
}
|
|
343
|
+
}
|