@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
|
@@ -1,11 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced setup command for kspec agent integration.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the full onboarding pipeline:
|
|
5
|
+
* - Detect agent environment (Claude Code, Cline, etc.)
|
|
6
|
+
* - Install hooks (UserPromptSubmit, Stop, PreToolUse guards)
|
|
7
|
+
* - Render skills from .kspec/skills/ to .claude/skills/
|
|
8
|
+
* - Generate kspec-agents.md
|
|
9
|
+
*
|
|
10
|
+
* AC: @enhanced-setup ac-1 - summary displayed listing each step performed
|
|
11
|
+
* AC: @enhanced-setup ac-2 - all hook entries (UserPromptSubmit, Stop, PreToolUse) present
|
|
12
|
+
* AC: @enhanced-setup ac-3 - rendered skill files exist for each skill targeting claude-code
|
|
13
|
+
* AC: @enhanced-setup ac-4 - kspec-agents.md exists after setup
|
|
14
|
+
* AC: @enhanced-setup ac-5 - --skip-skills flag skips skill rendering
|
|
15
|
+
* AC: @enhanced-setup ac-6 - --dry-run displays planned actions without changes
|
|
16
|
+
* AC: @enhanced-setup ac-7 - --status reports current state including agent detected
|
|
17
|
+
* AC: @enhanced-setup ac-8 - --status shows hooks status, skills rendered count, agents.md freshness
|
|
18
|
+
* AC: @enhanced-setup ac-9 - skills referenced by ralph (task-work, reflect) are present
|
|
19
|
+
*/
|
|
20
|
+
import * as fs from "node:fs/promises";
|
|
21
|
+
import * as os from "node:os";
|
|
22
|
+
import * as path from "node:path";
|
|
23
|
+
import * as readline from "node:readline/promises";
|
|
24
|
+
import chalk from "chalk";
|
|
25
|
+
import { getGitRoot, getShadowStatus, repairShadow, SHADOW_BRANCH_NAME, } from "../../parser/shadow.js";
|
|
26
|
+
import { errors } from "../../strings/index.js";
|
|
27
|
+
import { EXIT_CODES } from "../exit-codes.js";
|
|
28
|
+
import { error, output, success, warn } from "../output.js";
|
|
29
|
+
/**
|
|
30
|
+
* Log a message at debug level (only when KSPEC_DEBUG=1)
|
|
31
|
+
* AC: @setup-pipeline-unification ac-4
|
|
32
|
+
*/
|
|
33
|
+
function debugLog(message, detail) {
|
|
34
|
+
if (process.env.KSPEC_DEBUG === "1") {
|
|
35
|
+
if (detail) {
|
|
36
|
+
console.error(`[DEBUG] setup: ${message}`, detail);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.error(`[DEBUG] setup: ${message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
9
43
|
/**
|
|
10
44
|
* Detect which agent environment we're running in.
|
|
11
45
|
* Returns the detected agent type and confidence level.
|
|
@@ -17,23 +51,29 @@ export function detectAgent() {
|
|
|
17
51
|
// CLAUDECODE=1 is set in CLI sessions
|
|
18
52
|
// CLAUDE_CODE_ENTRYPOINT indicates entry point (cli, etc.)
|
|
19
53
|
// CLAUDE_PROJECT_DIR is set in some contexts
|
|
20
|
-
if (process.env.CLAUDECODE ===
|
|
54
|
+
if (process.env.CLAUDECODE === "1" ||
|
|
55
|
+
process.env.CLAUDE_CODE_ENTRYPOINT ||
|
|
56
|
+
process.env.CLAUDE_PROJECT_DIR) {
|
|
21
57
|
return {
|
|
22
|
-
type:
|
|
23
|
-
confidence:
|
|
24
|
-
configPath: path.join(os.homedir(),
|
|
58
|
+
type: "claude-code",
|
|
59
|
+
confidence: "high",
|
|
60
|
+
configPath: path.join(os.homedir(), ".claude", "settings.json"),
|
|
25
61
|
envVars: {
|
|
26
62
|
...(process.env.CLAUDECODE && { CLAUDECODE: process.env.CLAUDECODE }),
|
|
27
|
-
...(process.env.CLAUDE_CODE_ENTRYPOINT && {
|
|
28
|
-
|
|
63
|
+
...(process.env.CLAUDE_CODE_ENTRYPOINT && {
|
|
64
|
+
CLAUDE_CODE_ENTRYPOINT: process.env.CLAUDE_CODE_ENTRYPOINT,
|
|
65
|
+
}),
|
|
66
|
+
...(process.env.CLAUDE_PROJECT_DIR && {
|
|
67
|
+
CLAUDE_PROJECT_DIR: process.env.CLAUDE_PROJECT_DIR,
|
|
68
|
+
}),
|
|
29
69
|
},
|
|
30
70
|
};
|
|
31
71
|
}
|
|
32
72
|
// Cline: CLINE_ACTIVE is set when running in Cline terminal
|
|
33
73
|
if (process.env.CLINE_ACTIVE) {
|
|
34
74
|
return {
|
|
35
|
-
type:
|
|
36
|
-
confidence:
|
|
75
|
+
type: "cline",
|
|
76
|
+
confidence: "high",
|
|
37
77
|
// Cline uses VS Code settings, but env vars should be in shell profile
|
|
38
78
|
configPath: undefined,
|
|
39
79
|
envVars: { CLINE_ACTIVE: process.env.CLINE_ACTIVE },
|
|
@@ -42,63 +82,64 @@ export function detectAgent() {
|
|
|
42
82
|
// GitHub Copilot CLI: Check for copilot-specific markers
|
|
43
83
|
if (process.env.COPILOT_MODEL || process.env.GH_TOKEN) {
|
|
44
84
|
return {
|
|
45
|
-
type:
|
|
46
|
-
confidence:
|
|
47
|
-
configPath: path.join(os.homedir(),
|
|
85
|
+
type: "copilot-cli",
|
|
86
|
+
confidence: "medium",
|
|
87
|
+
configPath: path.join(os.homedir(), ".copilot", "config.json"),
|
|
48
88
|
};
|
|
49
89
|
}
|
|
50
90
|
// Aider: Check for AIDER_* env vars
|
|
51
91
|
if (process.env.AIDER_MODEL || process.env.AIDER_DARK_MODE !== undefined) {
|
|
52
92
|
return {
|
|
53
|
-
type:
|
|
54
|
-
confidence:
|
|
55
|
-
configPath: path.join(os.homedir(),
|
|
93
|
+
type: "aider",
|
|
94
|
+
confidence: "high",
|
|
95
|
+
configPath: path.join(os.homedir(), ".aider.conf.yml"),
|
|
56
96
|
};
|
|
57
97
|
}
|
|
58
98
|
// OpenCode: Check for OPENCODE_* env vars
|
|
59
99
|
if (process.env.OPENCODE_CONFIG_DIR || process.env.OPENCODE_CONFIG) {
|
|
60
100
|
return {
|
|
61
|
-
type:
|
|
62
|
-
confidence:
|
|
63
|
-
configPath: process.env.OPENCODE_CONFIG ||
|
|
101
|
+
type: "opencode",
|
|
102
|
+
confidence: "high",
|
|
103
|
+
configPath: process.env.OPENCODE_CONFIG ||
|
|
104
|
+
path.join(os.homedir(), ".config", "opencode", "opencode.json"),
|
|
64
105
|
};
|
|
65
106
|
}
|
|
66
107
|
// Gemini CLI: GEMINI_CLI=1 is set when running in Gemini CLI
|
|
67
|
-
if (process.env.GEMINI_CLI ===
|
|
108
|
+
if (process.env.GEMINI_CLI === "1") {
|
|
68
109
|
return {
|
|
69
|
-
type:
|
|
70
|
-
confidence:
|
|
71
|
-
configPath: path.join(os.homedir(),
|
|
72
|
-
envVars: { GEMINI_CLI:
|
|
110
|
+
type: "gemini-cli",
|
|
111
|
+
confidence: "high",
|
|
112
|
+
configPath: path.join(os.homedir(), ".gemini", "settings.json"),
|
|
113
|
+
envVars: { GEMINI_CLI: "1" },
|
|
73
114
|
};
|
|
74
115
|
}
|
|
75
116
|
// Codex CLI: CODEX_SANDBOX is set when running in sandbox
|
|
76
117
|
if (process.env.CODEX_SANDBOX) {
|
|
77
118
|
return {
|
|
78
|
-
type:
|
|
79
|
-
confidence:
|
|
80
|
-
configPath: path.join(os.homedir(),
|
|
119
|
+
type: "codex-cli",
|
|
120
|
+
confidence: "high",
|
|
121
|
+
configPath: path.join(os.homedir(), ".codex", "config.toml"),
|
|
81
122
|
envVars: { CODEX_SANDBOX: process.env.CODEX_SANDBOX },
|
|
82
123
|
};
|
|
83
124
|
}
|
|
84
125
|
// Amp (Sourcegraph): Check for AMP_API_KEY or AMP_TOOLBOX
|
|
85
126
|
if (process.env.AMP_API_KEY || process.env.AMP_TOOLBOX) {
|
|
86
127
|
return {
|
|
87
|
-
type:
|
|
88
|
-
confidence:
|
|
89
|
-
configPath: path.join(os.homedir(),
|
|
128
|
+
type: "amp",
|
|
129
|
+
confidence: "medium",
|
|
130
|
+
configPath: path.join(os.homedir(), ".config", "amp", "settings.json"),
|
|
90
131
|
};
|
|
91
132
|
}
|
|
92
133
|
return {
|
|
93
|
-
type:
|
|
94
|
-
confidence:
|
|
134
|
+
type: "unknown",
|
|
135
|
+
confidence: "low",
|
|
95
136
|
};
|
|
96
137
|
}
|
|
97
138
|
/**
|
|
98
139
|
* Install KSPEC_AUTHOR config for Claude Code (global settings)
|
|
99
140
|
*/
|
|
100
141
|
async function installClaudeCodeConfig(author) {
|
|
101
|
-
const configPath = path.join(os.homedir(),
|
|
142
|
+
const configPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
102
143
|
const configDir = path.dirname(configPath);
|
|
103
144
|
try {
|
|
104
145
|
// Ensure directory exists
|
|
@@ -106,56 +147,255 @@ async function installClaudeCodeConfig(author) {
|
|
|
106
147
|
// Read existing config or start fresh
|
|
107
148
|
let config = {};
|
|
108
149
|
try {
|
|
109
|
-
const existing = await fs.readFile(configPath,
|
|
150
|
+
const existing = await fs.readFile(configPath, "utf-8");
|
|
110
151
|
config = JSON.parse(existing);
|
|
111
152
|
}
|
|
112
|
-
catch {
|
|
113
|
-
|
|
153
|
+
catch (err) {
|
|
154
|
+
debugLog("No existing Claude Code config, starting fresh", err);
|
|
114
155
|
}
|
|
115
156
|
// Merge env settings
|
|
116
157
|
const env = config.env || {};
|
|
117
158
|
env.KSPEC_AUTHOR = author;
|
|
118
159
|
config.env = env;
|
|
119
160
|
// Write back
|
|
120
|
-
await fs.writeFile(configPath, JSON.stringify(config, null, 2)
|
|
161
|
+
await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
121
162
|
return true;
|
|
122
163
|
}
|
|
123
164
|
catch (err) {
|
|
165
|
+
debugLog("Failed to install Claude Code config", err);
|
|
124
166
|
return false;
|
|
125
167
|
}
|
|
126
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* PreToolUse guard hook scripts
|
|
171
|
+
* These are the shell scripts that will be installed to .claude/hooks/
|
|
172
|
+
*/
|
|
173
|
+
const GUARD_SCRIPTS = {
|
|
174
|
+
"kspec-worktree-guard.sh": `#!/bin/bash
|
|
175
|
+
# Guard against dangerous git operations in .kspec worktree
|
|
176
|
+
#
|
|
177
|
+
# This hook prevents accidentally creating branches or switching
|
|
178
|
+
# branches in the .kspec worktree, which should always stay on kspec-meta.
|
|
179
|
+
|
|
180
|
+
# Read the tool input from stdin
|
|
181
|
+
INPUT=$(cat)
|
|
182
|
+
|
|
183
|
+
# Extract the command from the JSON input
|
|
184
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
185
|
+
|
|
186
|
+
# If no command, allow (not a Bash tool call)
|
|
187
|
+
if [ -z "$COMMAND" ]; then
|
|
188
|
+
echo '{"decision": "allow"}'
|
|
189
|
+
exit 0
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
# Two views of the command for safe matching:
|
|
193
|
+
# 1. UNQUOTED: remove quote chars (keeps content) to catch split-quote
|
|
194
|
+
# bypasses like: git "reset" --hard → git reset --hard
|
|
195
|
+
# 2. STRIPPED: remove entire quoted strings to ignore patterns in args
|
|
196
|
+
# like: echo "git reset" → echo
|
|
197
|
+
UNQUOTED=$(echo "$COMMAND" | sed 's/["\\x27]//g')
|
|
198
|
+
STRIPPED=$(echo "$COMMAND" | sed -e "s/\\x27[^\\x27]*\\x27//g" -e 's/"[^"]*"//g')
|
|
199
|
+
# First command word (handles leading whitespace)
|
|
200
|
+
FIRST_CMD=$(echo "$COMMAND" | sed 's/^[[:space:]]*//' | cut -d' ' -f1)
|
|
201
|
+
|
|
202
|
+
# Block deleting kspec-meta from anywhere (check unquoted to catch bypasses)
|
|
203
|
+
if [[ "$UNQUOTED" == *"git branch -d kspec-meta"* || "$UNQUOTED" == *"git branch -D kspec-meta"* ]]; then
|
|
204
|
+
cat <<EOF
|
|
205
|
+
{
|
|
206
|
+
"decision": "block",
|
|
207
|
+
"reason": "[kspec-worktree-guard] BLOCKED: Cannot delete kspec-meta branch. This is the main branch for the .kspec worktree."
|
|
208
|
+
}
|
|
209
|
+
EOF
|
|
210
|
+
exit 0
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Get cwd from hook input (not pwd - hook runs in different context)
|
|
214
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
|
|
215
|
+
IN_KSPEC=false
|
|
216
|
+
|
|
217
|
+
if [[ "$CWD" == *"/.kspec"* || "$CWD" == *"/.kspec" ]]; then
|
|
218
|
+
IN_KSPEC=true
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# Also check if command contains cd to .kspec
|
|
222
|
+
if [[ "$COMMAND" == *"cd "*".kspec"* || "$COMMAND" == *"cd .kspec"* ]]; then
|
|
223
|
+
IN_KSPEC=true
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
if [ "$IN_KSPEC" = false ]; then
|
|
227
|
+
echo '{"decision": "allow"}'
|
|
228
|
+
exit 0
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
# Dangerous patterns in .kspec (branch creation/modification/history rewriting)
|
|
232
|
+
# Note: "git checkout kspec-meta" is safe and allowed
|
|
233
|
+
DANGEROUS_PATTERNS=(
|
|
234
|
+
# Branch creation
|
|
235
|
+
"git checkout -b"
|
|
236
|
+
"git checkout -B"
|
|
237
|
+
"git branch -c"
|
|
238
|
+
"git branch -C"
|
|
239
|
+
"git branch -m"
|
|
240
|
+
"git branch -M"
|
|
241
|
+
"git switch -c"
|
|
242
|
+
"git switch -C"
|
|
243
|
+
"git switch --create"
|
|
244
|
+
# History rewriting - these can cause conflicts with active sessions
|
|
245
|
+
"git reset"
|
|
246
|
+
"git rebase"
|
|
247
|
+
"git cherry-pick"
|
|
248
|
+
"git commit --amend"
|
|
249
|
+
# Force push
|
|
250
|
+
"git push --force"
|
|
251
|
+
"git push -f"
|
|
252
|
+
# Discarding changes
|
|
253
|
+
"git stash"
|
|
254
|
+
"git clean"
|
|
255
|
+
"git checkout -- "
|
|
256
|
+
"git restore"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
for pattern in "\${DANGEROUS_PATTERNS[@]}"; do
|
|
260
|
+
# Block if:
|
|
261
|
+
# - Pattern matches UNQUOTED AND first command is "git" (catches split-quote bypasses), OR
|
|
262
|
+
# - Pattern matches STRIPPED (actual command outside any quotes)
|
|
263
|
+
# This allows: echo "git reset", grep "git stash" (first cmd is echo/grep, pattern not in STRIPPED)
|
|
264
|
+
# This blocks: git reset, git "reset" --hard, git st'ash' (first cmd is git OR pattern in STRIPPED)
|
|
265
|
+
if [[ "$STRIPPED" == *"$pattern"* ]] || { [[ "$UNQUOTED" == *"$pattern"* ]] && [[ "$FIRST_CMD" == "git" ]]; }; then
|
|
266
|
+
cat <<EOF
|
|
267
|
+
{
|
|
268
|
+
"decision": "block",
|
|
269
|
+
"reason": "[kspec-worktree-guard] BLOCKED: Dangerous git operation in .kspec worktree. This worktree contains active session data and must stay on kspec-meta. Operations like reset, rebase, stash, and clean can corrupt session files."
|
|
270
|
+
}
|
|
271
|
+
EOF
|
|
272
|
+
exit 0
|
|
273
|
+
fi
|
|
274
|
+
done
|
|
275
|
+
|
|
276
|
+
# Allow all other commands
|
|
277
|
+
echo '{"decision": "allow"}'
|
|
278
|
+
`,
|
|
279
|
+
"ralph-task-limit-guard.sh": `#!/bin/bash
|
|
280
|
+
# Ralph task limit guard - blocks task start when limit reached
|
|
281
|
+
#
|
|
282
|
+
# This hook provides hard enforcement of the --max-tasks limit.
|
|
283
|
+
# Ralph writes a marker file when the limit is reached; this hook
|
|
284
|
+
# blocks 'kspec task start' commands when that marker exists.
|
|
285
|
+
|
|
286
|
+
# Marker file location (relative to project root)
|
|
287
|
+
MARKER_FILE=".claude/ralph-task-limit.json"
|
|
288
|
+
|
|
289
|
+
# Read the tool input from stdin
|
|
290
|
+
INPUT=$(cat)
|
|
291
|
+
|
|
292
|
+
# Extract the command from the JSON input
|
|
293
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
294
|
+
|
|
295
|
+
# If no command, allow (not a Bash tool call)
|
|
296
|
+
if [ -z "$COMMAND" ]; then
|
|
297
|
+
echo '{"decision": "allow"}'
|
|
298
|
+
exit 0
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
# Only check commands that match "kspec task start"
|
|
302
|
+
if [[ ! "$COMMAND" =~ kspec[[:space:]]+task[[:space:]]+start ]]; then
|
|
303
|
+
echo '{"decision": "allow"}'
|
|
304
|
+
exit 0
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
# Get cwd from hook input to find marker file
|
|
308
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
|
|
309
|
+
if [ -z "$CWD" ]; then
|
|
310
|
+
CWD="$PWD"
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
# Find project root by walking up looking for .claude directory
|
|
314
|
+
PROJECT_ROOT="$CWD"
|
|
315
|
+
while [ "$PROJECT_ROOT" != "/" ]; do
|
|
316
|
+
if [ -d "$PROJECT_ROOT/.claude" ]; then
|
|
317
|
+
break
|
|
318
|
+
fi
|
|
319
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
320
|
+
done
|
|
321
|
+
|
|
322
|
+
if [ "$PROJECT_ROOT" = "/" ]; then
|
|
323
|
+
# No .claude directory found, allow
|
|
324
|
+
echo '{"decision": "allow"}'
|
|
325
|
+
exit 0
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
MARKER_PATH="$PROJECT_ROOT/$MARKER_FILE"
|
|
329
|
+
|
|
330
|
+
# Check if marker file exists
|
|
331
|
+
if [ ! -f "$MARKER_PATH" ]; then
|
|
332
|
+
echo '{"decision": "allow"}'
|
|
333
|
+
exit 0
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
# Read marker file and check if active
|
|
337
|
+
ACTIVE=$(jq -r '.active // false' "$MARKER_PATH" 2>/dev/null)
|
|
338
|
+
if [ "$ACTIVE" != "true" ]; then
|
|
339
|
+
echo '{"decision": "allow"}'
|
|
340
|
+
exit 0
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
# Extract limit info for error message
|
|
344
|
+
MAX=$(jq -r '.max // "?"' "$MARKER_PATH" 2>/dev/null)
|
|
345
|
+
COMPLETED=$(jq -r '.completed // "?"' "$MARKER_PATH" 2>/dev/null)
|
|
346
|
+
|
|
347
|
+
# Block the command
|
|
348
|
+
cat <<EOF
|
|
349
|
+
{
|
|
350
|
+
"decision": "block",
|
|
351
|
+
"reason": "[ralph-task-limit-guard] BLOCKED: Task limit reached (\${COMPLETED}/\${MAX} tasks completed this iteration). This limit was set by --max-tasks. Please wrap up current work and let the iteration end naturally. Do not attempt to start new tasks."
|
|
352
|
+
}
|
|
353
|
+
EOF
|
|
354
|
+
exit 0
|
|
355
|
+
`,
|
|
356
|
+
};
|
|
127
357
|
/**
|
|
128
358
|
* Install hooks to project-level Claude Code settings (.claude/settings.json)
|
|
359
|
+
* AC: @enhanced-setup ac-2 - all hook entries present
|
|
129
360
|
*/
|
|
130
|
-
async function installClaudeCodeHooks(projectDir) {
|
|
131
|
-
const configPath = path.join(projectDir,
|
|
361
|
+
async function installClaudeCodeHooks(projectDir, dryRun = false) {
|
|
362
|
+
const configPath = path.join(projectDir, ".claude", "settings.json");
|
|
132
363
|
const configDir = path.dirname(configPath);
|
|
133
|
-
const
|
|
364
|
+
const hooksDir = path.join(projectDir, ".claude", "hooks");
|
|
365
|
+
const result = {
|
|
366
|
+
promptCheck: false,
|
|
367
|
+
stop: false,
|
|
368
|
+
preToolUse: false,
|
|
369
|
+
guardsCreated: [],
|
|
370
|
+
};
|
|
134
371
|
try {
|
|
135
|
-
// Ensure
|
|
136
|
-
|
|
372
|
+
// Ensure directories exist
|
|
373
|
+
if (!dryRun) {
|
|
374
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
375
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
376
|
+
}
|
|
137
377
|
// Read existing config or start fresh
|
|
138
378
|
let config = {};
|
|
139
379
|
try {
|
|
140
|
-
const existing = await fs.readFile(configPath,
|
|
380
|
+
const existing = await fs.readFile(configPath, "utf-8");
|
|
141
381
|
config = JSON.parse(existing);
|
|
142
382
|
}
|
|
143
|
-
catch {
|
|
144
|
-
|
|
383
|
+
catch (err) {
|
|
384
|
+
debugLog("No existing hooks config, starting fresh", err);
|
|
145
385
|
}
|
|
146
386
|
// Get or create hooks object
|
|
147
387
|
const hooks = config.hooks || {};
|
|
148
388
|
// Install UserPromptSubmit hook (spec-first reminder)
|
|
149
|
-
const promptCheckCommand =
|
|
389
|
+
const promptCheckCommand = "kspec session prompt-check";
|
|
150
390
|
const existingPromptHooks = hooks.UserPromptSubmit;
|
|
151
|
-
const promptAlreadyInstalled = existingPromptHooks?.some((entry) => entry.hooks?.some((hook) => hook.command?.includes(
|
|
391
|
+
const promptAlreadyInstalled = existingPromptHooks?.some((entry) => entry.hooks?.some((hook) => hook.command?.includes("session prompt-check")));
|
|
152
392
|
if (!promptAlreadyInstalled) {
|
|
153
393
|
hooks.UserPromptSubmit = [
|
|
154
394
|
...(existingPromptHooks || []),
|
|
155
395
|
{
|
|
156
396
|
hooks: [
|
|
157
397
|
{
|
|
158
|
-
type:
|
|
398
|
+
type: "command",
|
|
159
399
|
command: promptCheckCommand,
|
|
160
400
|
},
|
|
161
401
|
],
|
|
@@ -167,17 +407,17 @@ async function installClaudeCodeHooks(projectDir) {
|
|
|
167
407
|
result.promptCheck = true; // Already configured
|
|
168
408
|
}
|
|
169
409
|
// Install Stop hook (checkpoint)
|
|
170
|
-
const stopHookCommand =
|
|
410
|
+
const stopHookCommand = "kspec session checkpoint --json";
|
|
171
411
|
const existingStopHooks = hooks.Stop;
|
|
172
|
-
const stopAlreadyInstalled = existingStopHooks?.some((entry) => entry.hooks?.some((hook) => hook.command?.includes(
|
|
412
|
+
const stopAlreadyInstalled = existingStopHooks?.some((entry) => entry.hooks?.some((hook) => hook.command?.includes("session checkpoint")));
|
|
173
413
|
if (!stopAlreadyInstalled) {
|
|
174
414
|
hooks.Stop = [
|
|
175
415
|
...(existingStopHooks || []),
|
|
176
416
|
{
|
|
177
|
-
matcher:
|
|
417
|
+
matcher: "",
|
|
178
418
|
hooks: [
|
|
179
419
|
{
|
|
180
|
-
type:
|
|
420
|
+
type: "command",
|
|
181
421
|
command: stopHookCommand,
|
|
182
422
|
},
|
|
183
423
|
],
|
|
@@ -188,65 +428,46 @@ async function installClaudeCodeHooks(projectDir) {
|
|
|
188
428
|
else {
|
|
189
429
|
result.stop = true; // Already configured
|
|
190
430
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
431
|
+
// AC: @enhanced-setup ac-2 - Install PreToolUse hooks with guards
|
|
432
|
+
const existingPreToolUseHooks = hooks.PreToolUse;
|
|
433
|
+
// Check if our guards are already installed
|
|
434
|
+
const guardHookCommands = Object.keys(GUARD_SCRIPTS).map((name) => `.claude/hooks/${name}`);
|
|
435
|
+
const guardsAlreadyInstalled = existingPreToolUseHooks?.some((entry) => entry.hooks?.some((hook) => guardHookCommands.some((cmd) => hook.command?.includes(cmd))));
|
|
436
|
+
if (!guardsAlreadyInstalled) {
|
|
437
|
+
// Create guard script files
|
|
438
|
+
for (const [name, content] of Object.entries(GUARD_SCRIPTS)) {
|
|
439
|
+
const scriptPath = path.join(hooksDir, name);
|
|
440
|
+
if (!dryRun) {
|
|
441
|
+
await fs.writeFile(scriptPath, content, { mode: 0o755 });
|
|
442
|
+
}
|
|
443
|
+
result.guardsCreated.push(name);
|
|
444
|
+
}
|
|
445
|
+
// Add PreToolUse hook entry
|
|
446
|
+
hooks.PreToolUse = [
|
|
447
|
+
...(existingPreToolUseHooks || []),
|
|
448
|
+
{
|
|
449
|
+
matcher: "Bash",
|
|
450
|
+
hooks: Object.keys(GUARD_SCRIPTS).map((name) => ({
|
|
451
|
+
type: "command",
|
|
452
|
+
command: `.claude/hooks/${name}`,
|
|
453
|
+
})),
|
|
454
|
+
},
|
|
455
|
+
];
|
|
456
|
+
result.preToolUse = true;
|
|
215
457
|
}
|
|
216
|
-
|
|
217
|
-
|
|
458
|
+
else {
|
|
459
|
+
result.preToolUse = true; // Already configured
|
|
218
460
|
}
|
|
219
|
-
// Build the stop hook command
|
|
220
|
-
const stopHookCommand = 'npx kspec session checkpoint --json';
|
|
221
|
-
// Get or create hooks object
|
|
222
|
-
const hooks = config.hooks || {};
|
|
223
|
-
// Check if Stop hook already exists with our command
|
|
224
|
-
const existingStopHooks = hooks.Stop;
|
|
225
|
-
const alreadyInstalled = existingStopHooks?.some((entry) => entry.hooks?.some((hook) => hook.command?.includes('session checkpoint')));
|
|
226
|
-
if (alreadyInstalled) {
|
|
227
|
-
return true; // Already configured
|
|
228
|
-
}
|
|
229
|
-
// Add our stop hook using Claude Code hooks format
|
|
230
|
-
// Note: matcher field is required even if empty string
|
|
231
|
-
hooks.Stop = [
|
|
232
|
-
...(existingStopHooks || []),
|
|
233
|
-
{
|
|
234
|
-
matcher: '',
|
|
235
|
-
hooks: [
|
|
236
|
-
{
|
|
237
|
-
type: 'command',
|
|
238
|
-
command: stopHookCommand,
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
},
|
|
242
|
-
];
|
|
243
461
|
config.hooks = hooks;
|
|
244
462
|
// Write back
|
|
245
|
-
|
|
246
|
-
|
|
463
|
+
if (!dryRun) {
|
|
464
|
+
await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
465
|
+
}
|
|
466
|
+
return result;
|
|
247
467
|
}
|
|
248
|
-
catch {
|
|
249
|
-
|
|
468
|
+
catch (err) {
|
|
469
|
+
debugLog("installClaudeCodeHooks failed", err);
|
|
470
|
+
return result;
|
|
250
471
|
}
|
|
251
472
|
}
|
|
252
473
|
/**
|
|
@@ -254,23 +475,23 @@ async function installClaudeCodeStopHook(projectDir) {
|
|
|
254
475
|
* Aider uses `set-env:` for environment variables in list format
|
|
255
476
|
*/
|
|
256
477
|
async function installAiderConfig(author) {
|
|
257
|
-
const configPath = path.join(os.homedir(),
|
|
478
|
+
const configPath = path.join(os.homedir(), ".aider.conf.yml");
|
|
258
479
|
try {
|
|
259
|
-
let content =
|
|
480
|
+
let content = "";
|
|
260
481
|
try {
|
|
261
|
-
content = await fs.readFile(configPath,
|
|
482
|
+
content = await fs.readFile(configPath, "utf-8");
|
|
262
483
|
}
|
|
263
|
-
catch {
|
|
264
|
-
|
|
484
|
+
catch (err) {
|
|
485
|
+
debugLog("No existing Aider config, starting fresh", err);
|
|
265
486
|
}
|
|
266
487
|
// Check if KSPEC_AUTHOR is already set
|
|
267
|
-
if (content.includes(
|
|
488
|
+
if (content.includes("KSPEC_AUTHOR")) {
|
|
268
489
|
// Replace existing value (handles both old and new format)
|
|
269
490
|
content = content.replace(/^(\s*-?\s*KSPEC_AUTHOR\s*[=:]\s*).*$/m, ` - KSPEC_AUTHOR=${author}`);
|
|
270
491
|
}
|
|
271
492
|
else {
|
|
272
493
|
// Add to set-env section or create it
|
|
273
|
-
if (content.includes(
|
|
494
|
+
if (content.includes("set-env:")) {
|
|
274
495
|
// Append to existing set-env section
|
|
275
496
|
content = content.replace(/(set-env:\s*\n)/m, `$1 - KSPEC_AUTHOR=${author}\n`);
|
|
276
497
|
}
|
|
@@ -279,10 +500,11 @@ async function installAiderConfig(author) {
|
|
|
279
500
|
content += `\n# kspec author for note attribution\nset-env:\n - KSPEC_AUTHOR=${author}\n`;
|
|
280
501
|
}
|
|
281
502
|
}
|
|
282
|
-
await fs.writeFile(configPath, content,
|
|
503
|
+
await fs.writeFile(configPath, content, "utf-8");
|
|
283
504
|
return true;
|
|
284
505
|
}
|
|
285
|
-
catch {
|
|
506
|
+
catch (err) {
|
|
507
|
+
debugLog("installAiderConfig failed", err);
|
|
286
508
|
return false;
|
|
287
509
|
}
|
|
288
510
|
}
|
|
@@ -295,19 +517,20 @@ async function installGenericJsonConfig(configPath, author) {
|
|
|
295
517
|
await fs.mkdir(configDir, { recursive: true });
|
|
296
518
|
let config = {};
|
|
297
519
|
try {
|
|
298
|
-
const existing = await fs.readFile(configPath,
|
|
520
|
+
const existing = await fs.readFile(configPath, "utf-8");
|
|
299
521
|
config = JSON.parse(existing);
|
|
300
522
|
}
|
|
301
|
-
catch {
|
|
302
|
-
|
|
523
|
+
catch (err) {
|
|
524
|
+
debugLog(`No existing config at ${configPath}, starting fresh`, err);
|
|
303
525
|
}
|
|
304
526
|
const env = config.env || {};
|
|
305
527
|
env.KSPEC_AUTHOR = author;
|
|
306
528
|
config.env = env;
|
|
307
|
-
await fs.writeFile(configPath, JSON.stringify(config, null, 2)
|
|
529
|
+
await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
308
530
|
return true;
|
|
309
531
|
}
|
|
310
|
-
catch {
|
|
532
|
+
catch (err) {
|
|
533
|
+
debugLog(`installGenericJsonConfig failed for ${configPath}`, err);
|
|
311
534
|
return false;
|
|
312
535
|
}
|
|
313
536
|
}
|
|
@@ -316,26 +539,26 @@ async function installGenericJsonConfig(configPath, author) {
|
|
|
316
539
|
*/
|
|
317
540
|
function getDefaultAuthor(agentType) {
|
|
318
541
|
switch (agentType) {
|
|
319
|
-
case
|
|
320
|
-
return
|
|
321
|
-
case
|
|
322
|
-
return
|
|
323
|
-
case
|
|
324
|
-
return
|
|
325
|
-
case
|
|
326
|
-
return
|
|
327
|
-
case
|
|
328
|
-
return
|
|
329
|
-
case
|
|
330
|
-
return
|
|
331
|
-
case
|
|
332
|
-
return
|
|
333
|
-
case
|
|
334
|
-
return
|
|
335
|
-
case
|
|
336
|
-
return
|
|
542
|
+
case "claude-code":
|
|
543
|
+
return "@claude";
|
|
544
|
+
case "cline":
|
|
545
|
+
return "@cline";
|
|
546
|
+
case "roo-code":
|
|
547
|
+
return "@roo";
|
|
548
|
+
case "copilot-cli":
|
|
549
|
+
return "@copilot";
|
|
550
|
+
case "gemini-cli":
|
|
551
|
+
return "@gemini";
|
|
552
|
+
case "codex-cli":
|
|
553
|
+
return "@codex";
|
|
554
|
+
case "aider":
|
|
555
|
+
return "@aider";
|
|
556
|
+
case "opencode":
|
|
557
|
+
return "@opencode";
|
|
558
|
+
case "amp":
|
|
559
|
+
return "@amp";
|
|
337
560
|
default:
|
|
338
|
-
return
|
|
561
|
+
return "@agent";
|
|
339
562
|
}
|
|
340
563
|
}
|
|
341
564
|
/**
|
|
@@ -348,7 +571,7 @@ async function promptYesNo(question) {
|
|
|
348
571
|
});
|
|
349
572
|
try {
|
|
350
573
|
const answer = await rl.question(`${question} `);
|
|
351
|
-
return answer.toLowerCase() ===
|
|
574
|
+
return answer.toLowerCase() === "y";
|
|
352
575
|
}
|
|
353
576
|
finally {
|
|
354
577
|
rl.close();
|
|
@@ -379,7 +602,7 @@ async function ensureWorktree(autoWorktree) {
|
|
|
379
602
|
console.log(`Detected ${SHADOW_BRANCH_NAME} branch without .kspec worktree. Creating...`);
|
|
380
603
|
const result = await repairShadow(projectRoot);
|
|
381
604
|
if (result.success) {
|
|
382
|
-
success(
|
|
605
|
+
success("Created .kspec worktree");
|
|
383
606
|
return true;
|
|
384
607
|
}
|
|
385
608
|
else {
|
|
@@ -390,10 +613,10 @@ async function ensureWorktree(autoWorktree) {
|
|
|
390
613
|
// AC: detect-existing-repo - prompt user
|
|
391
614
|
const shouldCreate = await promptYesNo(`${SHADOW_BRANCH_NAME} branch exists but .kspec worktree is missing. Create it? (y/N)`);
|
|
392
615
|
if (shouldCreate) {
|
|
393
|
-
console.log(
|
|
616
|
+
console.log("Creating .kspec worktree...");
|
|
394
617
|
const result = await repairShadow(projectRoot);
|
|
395
618
|
if (result.success) {
|
|
396
|
-
success(
|
|
619
|
+
success("Created .kspec worktree");
|
|
397
620
|
return true;
|
|
398
621
|
}
|
|
399
622
|
else {
|
|
@@ -402,7 +625,7 @@ async function ensureWorktree(autoWorktree) {
|
|
|
402
625
|
}
|
|
403
626
|
}
|
|
404
627
|
else {
|
|
405
|
-
warn(
|
|
628
|
+
warn("Skipping worktree creation");
|
|
406
629
|
return false;
|
|
407
630
|
}
|
|
408
631
|
}
|
|
@@ -414,76 +637,837 @@ async function ensureWorktree(autoWorktree) {
|
|
|
414
637
|
*/
|
|
415
638
|
function printManualInstructions(agentType) {
|
|
416
639
|
const author = getDefaultAuthor(agentType);
|
|
417
|
-
console.log(
|
|
640
|
+
console.log("\nManual setup instructions:\n");
|
|
418
641
|
switch (agentType) {
|
|
419
|
-
case
|
|
420
|
-
console.log(
|
|
421
|
-
console.log(
|
|
642
|
+
case "claude-code":
|
|
643
|
+
console.log("Add to ~/.claude/settings.json:");
|
|
644
|
+
console.log("```json");
|
|
422
645
|
console.log(JSON.stringify({ env: { KSPEC_AUTHOR: author } }, null, 2));
|
|
423
|
-
console.log(
|
|
646
|
+
console.log("```");
|
|
424
647
|
break;
|
|
425
|
-
case
|
|
426
|
-
case
|
|
427
|
-
console.log(
|
|
428
|
-
console.log(
|
|
648
|
+
case "cline":
|
|
649
|
+
case "roo-code":
|
|
650
|
+
console.log("Add to your shell profile (~/.bashrc, ~/.zshrc):");
|
|
651
|
+
console.log("```bash");
|
|
429
652
|
console.log(`export KSPEC_AUTHOR="${author}"`);
|
|
430
|
-
console.log(
|
|
431
|
-
console.log(
|
|
653
|
+
console.log("```");
|
|
654
|
+
console.log("\nThis will be inherited by terminals spawned by the VS Code extension.");
|
|
432
655
|
break;
|
|
433
|
-
case
|
|
434
|
-
console.log(
|
|
435
|
-
console.log(
|
|
656
|
+
case "copilot-cli":
|
|
657
|
+
console.log("Add to ~/.copilot/config.json:");
|
|
658
|
+
console.log("```json");
|
|
436
659
|
console.log(JSON.stringify({ env: { KSPEC_AUTHOR: author } }, null, 2));
|
|
437
|
-
console.log(
|
|
660
|
+
console.log("```");
|
|
438
661
|
break;
|
|
439
|
-
case
|
|
440
|
-
console.log(
|
|
441
|
-
console.log(
|
|
442
|
-
console.log(
|
|
662
|
+
case "aider":
|
|
663
|
+
console.log("Add to ~/.aider.conf.yml:");
|
|
664
|
+
console.log("```yaml");
|
|
665
|
+
console.log("set-env:");
|
|
443
666
|
console.log(` - KSPEC_AUTHOR=${author}`);
|
|
444
|
-
console.log(
|
|
667
|
+
console.log("```");
|
|
445
668
|
break;
|
|
446
|
-
case
|
|
447
|
-
console.log(
|
|
448
|
-
console.log(
|
|
449
|
-
console.log(
|
|
669
|
+
case "codex-cli":
|
|
670
|
+
console.log("Add to ~/.codex/config.toml:");
|
|
671
|
+
console.log("```toml");
|
|
672
|
+
console.log("[shell_environment_policy]");
|
|
450
673
|
console.log(`set = { KSPEC_AUTHOR = "${author}" }`);
|
|
451
|
-
console.log(
|
|
674
|
+
console.log("```");
|
|
452
675
|
break;
|
|
453
|
-
case
|
|
454
|
-
console.log(
|
|
455
|
-
console.log(
|
|
676
|
+
case "opencode":
|
|
677
|
+
console.log("Add to ~/.config/opencode/opencode.json:");
|
|
678
|
+
console.log("```json");
|
|
456
679
|
console.log(JSON.stringify({ env: { KSPEC_AUTHOR: author } }, null, 2));
|
|
457
|
-
console.log(
|
|
680
|
+
console.log("```");
|
|
458
681
|
break;
|
|
459
|
-
case
|
|
460
|
-
console.log(
|
|
461
|
-
console.log(
|
|
682
|
+
case "amp":
|
|
683
|
+
console.log("Add to ~/.config/amp/settings.json:");
|
|
684
|
+
console.log("```json");
|
|
462
685
|
console.log(JSON.stringify({ env: { KSPEC_AUTHOR: author } }, null, 2));
|
|
463
|
-
console.log(
|
|
686
|
+
console.log("```");
|
|
464
687
|
break;
|
|
465
688
|
default:
|
|
466
|
-
console.log(
|
|
467
|
-
console.log(
|
|
689
|
+
console.log("Set the KSPEC_AUTHOR environment variable:");
|
|
690
|
+
console.log("```bash");
|
|
468
691
|
console.log(`export KSPEC_AUTHOR="${author}"`);
|
|
469
|
-
console.log(
|
|
470
|
-
console.log(
|
|
692
|
+
console.log("```");
|
|
693
|
+
console.log("\nOr add to your shell profile (~/.bashrc, ~/.zshrc, etc.)");
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Check the current setup status
|
|
698
|
+
* AC: @enhanced-setup ac-7, ac-8
|
|
699
|
+
*/
|
|
700
|
+
async function getSetupStatus(projectDir) {
|
|
701
|
+
const detected = detectAgent();
|
|
702
|
+
const configPath = path.join(projectDir, ".claude", "settings.json");
|
|
703
|
+
const hooksDir = path.join(projectDir, ".claude", "hooks");
|
|
704
|
+
const agentsMdPath = path.join(projectDir, "kspec-agents.md");
|
|
705
|
+
const hashPath = path.join(projectDir, ".kspec", ".kspec-agents-hash");
|
|
706
|
+
const skillsDir = path.join(projectDir, ".claude", "skills");
|
|
707
|
+
// Check hooks
|
|
708
|
+
const hooks = {
|
|
709
|
+
promptCheck: false,
|
|
710
|
+
stop: false,
|
|
711
|
+
preToolUse: false,
|
|
712
|
+
guardsPresent: [],
|
|
713
|
+
};
|
|
714
|
+
try {
|
|
715
|
+
const configContent = await fs.readFile(configPath, "utf-8");
|
|
716
|
+
const config = JSON.parse(configContent);
|
|
717
|
+
const hooksConfig = config.hooks || {};
|
|
718
|
+
// Check UserPromptSubmit
|
|
719
|
+
const promptHooks = hooksConfig.UserPromptSubmit;
|
|
720
|
+
hooks.promptCheck = promptHooks?.some((entry) => entry.hooks?.some((h) => h.command?.includes("prompt-check"))) ?? false;
|
|
721
|
+
// Check Stop
|
|
722
|
+
const stopHooks = hooksConfig.Stop;
|
|
723
|
+
hooks.stop = stopHooks?.some((entry) => entry.hooks?.some((h) => h.command?.includes("checkpoint"))) ?? false;
|
|
724
|
+
// Check PreToolUse
|
|
725
|
+
const preToolUseHooks = hooksConfig.PreToolUse;
|
|
726
|
+
hooks.preToolUse = preToolUseHooks?.some((entry) => entry.hooks?.some((h) => h.command?.includes(".claude/hooks/"))) ?? false;
|
|
727
|
+
}
|
|
728
|
+
catch (err) {
|
|
729
|
+
debugLog("Failed to read hooks config for status", err);
|
|
730
|
+
}
|
|
731
|
+
// Check guard scripts
|
|
732
|
+
try {
|
|
733
|
+
const guardFiles = await fs.readdir(hooksDir);
|
|
734
|
+
for (const name of Object.keys(GUARD_SCRIPTS)) {
|
|
735
|
+
if (guardFiles.includes(name)) {
|
|
736
|
+
hooks.guardsPresent.push(name);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
catch (err) {
|
|
741
|
+
debugLog("Hooks dir doesn't exist", err);
|
|
742
|
+
}
|
|
743
|
+
// Check skills
|
|
744
|
+
const skills = {
|
|
745
|
+
total: 0,
|
|
746
|
+
rendered: 0,
|
|
747
|
+
drifted: 0,
|
|
748
|
+
};
|
|
749
|
+
// Helper to scan a directory for skill subdirs with kspec-managed SKILL.md
|
|
750
|
+
async function scanForSkills(baseDir) {
|
|
751
|
+
try {
|
|
752
|
+
const dirs = await fs.readdir(baseDir, { withFileTypes: true });
|
|
753
|
+
for (const dir of dirs) {
|
|
754
|
+
if (dir.isDirectory()) {
|
|
755
|
+
const skillMdPath = path.join(baseDir, dir.name, "SKILL.md");
|
|
756
|
+
try {
|
|
757
|
+
const content = await fs.readFile(skillMdPath, "utf-8");
|
|
758
|
+
if (content.includes("<!-- kspec-managed -->")) {
|
|
759
|
+
skills.total++;
|
|
760
|
+
skills.rendered++;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
catch (_noSkillMd) {
|
|
764
|
+
// No SKILL.md
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
catch (_notReadable) {
|
|
770
|
+
// Directory doesn't exist
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
// Scan .claude/skills/ (project/local skills)
|
|
774
|
+
await scanForSkills(skillsDir);
|
|
775
|
+
// Check plugin marketplace health
|
|
776
|
+
// AC: @enhanced-setup ac-7, ac-8
|
|
777
|
+
const plugin = {
|
|
778
|
+
marketplaceRegistered: false,
|
|
779
|
+
marketplaceHealthy: false,
|
|
780
|
+
pluginEnabled: false,
|
|
781
|
+
};
|
|
782
|
+
try {
|
|
783
|
+
const { checkMarketplaceHealth } = await import("../../lib/claude-plugin-registry.js");
|
|
784
|
+
const health = await checkMarketplaceHealth();
|
|
785
|
+
plugin.marketplaceRegistered = health.status !== "missing";
|
|
786
|
+
plugin.marketplaceHealthy = health.status === "healthy";
|
|
787
|
+
plugin.registeredPath = health.registeredPath;
|
|
788
|
+
plugin.healthMessage = health.message;
|
|
789
|
+
}
|
|
790
|
+
catch (err) {
|
|
791
|
+
debugLog("Could not check marketplace health", err);
|
|
792
|
+
plugin.healthMessage = "Health check unavailable";
|
|
793
|
+
}
|
|
794
|
+
// Check if plugin is enabled in project settings
|
|
795
|
+
try {
|
|
796
|
+
const configContent = await fs.readFile(configPath, "utf-8");
|
|
797
|
+
const config = JSON.parse(configContent);
|
|
798
|
+
plugin.pluginEnabled = config.enabledPlugins?.["kspec@kspec-plugins"] === true;
|
|
799
|
+
}
|
|
800
|
+
catch (err) {
|
|
801
|
+
debugLog("Could not check plugin enablement", err);
|
|
802
|
+
}
|
|
803
|
+
// Check agents.md
|
|
804
|
+
const agentsMd = {
|
|
805
|
+
exists: false,
|
|
806
|
+
status: "missing",
|
|
807
|
+
};
|
|
808
|
+
try {
|
|
809
|
+
await fs.access(agentsMdPath);
|
|
810
|
+
agentsMd.exists = true;
|
|
811
|
+
try {
|
|
812
|
+
const hashContent = await fs.readFile(hashPath, "utf-8");
|
|
813
|
+
const hashData = JSON.parse(hashContent);
|
|
814
|
+
agentsMd.generatedAt = hashData.generatedAt;
|
|
815
|
+
// AC: @cross-platform-and-version-robustness ac-4
|
|
816
|
+
// Compare stored hash against current meta to detect staleness
|
|
817
|
+
try {
|
|
818
|
+
const { initContext, loadMetaContext } = await import("../../parser/index.js");
|
|
819
|
+
const { computeMetaHash, loadTemplateSections, getPackageRoot } = await import("./agents.js");
|
|
820
|
+
const ctx = await initContext();
|
|
821
|
+
if (ctx.manifestPath) {
|
|
822
|
+
const metaCtx = await loadMetaContext(ctx);
|
|
823
|
+
let templateSections = [];
|
|
824
|
+
try {
|
|
825
|
+
templateSections = await loadTemplateSections(getPackageRoot());
|
|
826
|
+
}
|
|
827
|
+
catch (err) {
|
|
828
|
+
debugLog("Templates not available for staleness check", err);
|
|
829
|
+
}
|
|
830
|
+
const currentHash = computeMetaHash(metaCtx.skills, metaCtx.conventions, metaCtx.workflows, templateSections);
|
|
831
|
+
agentsMd.status = hashData.metaHash === currentHash ? "current" : "stale";
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
// AC: @doctor-command ac-staleness-unknown — no manifest means we can't determine staleness
|
|
835
|
+
agentsMd.status = "unknown";
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
catch (err) {
|
|
839
|
+
// AC: @doctor-command ac-staleness-unknown — hash computation failed
|
|
840
|
+
debugLog("Could not compute meta hash for staleness check", err);
|
|
841
|
+
agentsMd.status = "unknown";
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
catch (err) {
|
|
845
|
+
debugLog("Hash file missing or invalid, marking stale", err);
|
|
846
|
+
agentsMd.status = "stale";
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
catch (err) {
|
|
850
|
+
debugLog("kspec-agents.md doesn't exist", err);
|
|
851
|
+
}
|
|
852
|
+
// Check seeding state
|
|
853
|
+
const seeding = {
|
|
854
|
+
permissionsSeeded: false,
|
|
855
|
+
memorySeeded: false,
|
|
856
|
+
};
|
|
857
|
+
try {
|
|
858
|
+
const configContent = await fs.readFile(path.join(projectDir, ".claude", "settings.json"), "utf-8");
|
|
859
|
+
const config = JSON.parse(configContent);
|
|
860
|
+
seeding.permissionsSeeded = !!config.permissions;
|
|
861
|
+
}
|
|
862
|
+
catch (err) {
|
|
863
|
+
debugLog("Could not check permissions seeding state", err);
|
|
864
|
+
}
|
|
865
|
+
if (detected.type === "claude-code") {
|
|
866
|
+
try {
|
|
867
|
+
const { claudeCodeMemoryWriter } = await import("./setup-seeding.js");
|
|
868
|
+
const memoryExists = await claudeCodeMemoryWriter.exists(projectDir);
|
|
869
|
+
seeding.memorySeeded = memoryExists;
|
|
870
|
+
if (memoryExists) {
|
|
871
|
+
seeding.memoryPath = claudeCodeMemoryWriter.getMemoryPath(projectDir);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
catch (err) {
|
|
875
|
+
debugLog("Could not check memory seeding state", err);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
agent: {
|
|
880
|
+
detected: detected.type,
|
|
881
|
+
confidence: detected.confidence,
|
|
882
|
+
},
|
|
883
|
+
hooks,
|
|
884
|
+
skills,
|
|
885
|
+
plugin,
|
|
886
|
+
agentsMd,
|
|
887
|
+
seeding,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Render skills using the platform renderer registry
|
|
892
|
+
* AC: @setup-pipeline-unification ac-2 - uses getRenderer/getAllRenderers, not legacy renderClaudeCodeSkill
|
|
893
|
+
* AC: @setup-pipeline-unification ac-4 - errors logged at debug level
|
|
894
|
+
*/
|
|
895
|
+
async function renderSkillsForSetup(projectDir, dryRun) {
|
|
896
|
+
// Dynamically import to avoid circular dependencies
|
|
897
|
+
const { initContext, loadMetaContext } = await import("../../parser/index.js");
|
|
898
|
+
const { getRenderer } = await import("../../parser/skill-render.js");
|
|
899
|
+
try {
|
|
900
|
+
const ctx = await initContext();
|
|
901
|
+
if (!ctx.manifestPath) {
|
|
902
|
+
return { rendered: 0, skipped: 0, pluginProvided: 0, skillIds: [] };
|
|
903
|
+
}
|
|
904
|
+
const metaCtx = await loadMetaContext(ctx);
|
|
905
|
+
// Collect all skills that have a registered renderer for their platform
|
|
906
|
+
const skillsToRender = [];
|
|
907
|
+
for (const skill of metaCtx.skills) {
|
|
908
|
+
for (const platform of skill.platforms) {
|
|
909
|
+
const renderer = getRenderer(platform);
|
|
910
|
+
if (renderer) {
|
|
911
|
+
skillsToRender.push({ skill, platform });
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if (skillsToRender.length === 0) {
|
|
916
|
+
return { rendered: 0, skipped: 0, pluginProvided: 0, skillIds: [] };
|
|
917
|
+
}
|
|
918
|
+
let rendered = 0;
|
|
919
|
+
let skipped = 0;
|
|
920
|
+
let pluginProvided = 0;
|
|
921
|
+
const skillIds = [];
|
|
922
|
+
for (const { skill, platform } of skillsToRender) {
|
|
923
|
+
const renderer = getRenderer(platform);
|
|
924
|
+
try {
|
|
925
|
+
const result = await renderer.render(ctx, projectDir, skill, {
|
|
926
|
+
dryRun,
|
|
927
|
+
});
|
|
928
|
+
if (result.action === "created" || result.action === "updated") {
|
|
929
|
+
rendered++;
|
|
930
|
+
if (!skillIds.includes(skill.id)) {
|
|
931
|
+
skillIds.push(skill.id);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
else if (result.action === "skipped" && result.skipCode === "plugin-provided") {
|
|
935
|
+
pluginProvided++;
|
|
936
|
+
}
|
|
937
|
+
else {
|
|
938
|
+
skipped++;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
catch (err) {
|
|
942
|
+
debugLog(`Failed to render skill ${skill.id} for ${platform}`, err);
|
|
943
|
+
skipped++;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return { rendered, skipped, pluginProvided, skillIds };
|
|
947
|
+
}
|
|
948
|
+
catch (err) {
|
|
949
|
+
debugLog("renderSkillsForSetup failed", err);
|
|
950
|
+
return { rendered: 0, skipped: 0, pluginProvided: 0, skillIds: [] };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Generate kspec-agents.md using the canonical implementation from agents.ts
|
|
955
|
+
* AC: @setup-pipeline-unification ac-1 - calls generateAgentsContent() from agents.ts
|
|
956
|
+
* AC: @setup-pipeline-unification ac-4 - errors logged at debug level
|
|
957
|
+
*/
|
|
958
|
+
async function generateAgentInstructions(projectDir, dryRun) {
|
|
959
|
+
const outputPath = path.join(projectDir, "kspec-agents.md");
|
|
960
|
+
const hashPath = path.join(projectDir, ".kspec", ".kspec-agents-hash");
|
|
961
|
+
// Dynamically import to avoid circular dependencies
|
|
962
|
+
const { initContext, loadMetaContext } = await import("../../parser/index.js");
|
|
963
|
+
const { generateAgentsContent, loadTemplateSections, getPackageRoot, computeMetaHash, } = await import("./agents.js");
|
|
964
|
+
try {
|
|
965
|
+
const ctx = await initContext();
|
|
966
|
+
if (!ctx.manifestPath) {
|
|
967
|
+
return { success: false, path: outputPath };
|
|
968
|
+
}
|
|
969
|
+
const metaCtx = await loadMetaContext(ctx);
|
|
970
|
+
const timestamp = new Date().toISOString();
|
|
971
|
+
// Load templates using the canonical implementation
|
|
972
|
+
let templateSections = [];
|
|
973
|
+
try {
|
|
974
|
+
templateSections = await loadTemplateSections(getPackageRoot());
|
|
975
|
+
}
|
|
976
|
+
catch (err) {
|
|
977
|
+
debugLog("Failed to load template sections", err);
|
|
978
|
+
}
|
|
979
|
+
// Generate content using the canonical implementation from agents.ts
|
|
980
|
+
const content = await generateAgentsContent(metaCtx.skills, metaCtx.conventions, metaCtx.workflows, timestamp, templateSections);
|
|
981
|
+
if (!dryRun) {
|
|
982
|
+
await fs.writeFile(outputPath, content, "utf-8");
|
|
983
|
+
// Write hash for freshness tracking using the canonical hash function
|
|
984
|
+
const metaHash = computeMetaHash(metaCtx.skills, metaCtx.conventions, metaCtx.workflows, templateSections);
|
|
985
|
+
await fs.mkdir(path.dirname(hashPath), { recursive: true });
|
|
986
|
+
// Dynamically import version to avoid top-level require
|
|
987
|
+
const { createRequire } = await import("node:module");
|
|
988
|
+
const req = createRequire(import.meta.url);
|
|
989
|
+
const { version } = req("../../../package.json");
|
|
990
|
+
await fs.writeFile(hashPath, JSON.stringify({
|
|
991
|
+
metaHash,
|
|
992
|
+
generatedAt: timestamp,
|
|
993
|
+
version,
|
|
994
|
+
}, null, 2), "utf-8");
|
|
995
|
+
}
|
|
996
|
+
return { success: true, path: outputPath };
|
|
997
|
+
}
|
|
998
|
+
catch (err) {
|
|
999
|
+
debugLog("generateAgentInstructions failed", err);
|
|
1000
|
+
return { success: false, path: outputPath };
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Install core skills for the setup pipeline
|
|
1005
|
+
* AC: @init-setup-integration ac-2 - core skills installed
|
|
1006
|
+
*/
|
|
1007
|
+
async function installCoreSkillsForSetup(projectDir, dryRun) {
|
|
1008
|
+
// Dynamically import to avoid circular dependencies
|
|
1009
|
+
const { initContext, loadMetaContext, saveMetaItem, getSkillContentPath, } = await import("../../parser/index.js");
|
|
1010
|
+
const { commitIfShadow } = await import("../../parser/shadow.js");
|
|
1011
|
+
const { SkillSchema } = await import("../../schema/index.js");
|
|
1012
|
+
const { loadCoreSkillsManifest, copyCoreSkillFiles, getKspecPackageVersion, } = await import("./skill.js");
|
|
1013
|
+
const { ulid } = await import("ulid");
|
|
1014
|
+
let installed = 0;
|
|
1015
|
+
let skipped = 0;
|
|
1016
|
+
try {
|
|
1017
|
+
const ctx = await initContext();
|
|
1018
|
+
if (!ctx.manifestPath) {
|
|
1019
|
+
return { installed: 0, skipped: 0 };
|
|
1020
|
+
}
|
|
1021
|
+
const metaCtx = await loadMetaContext(ctx);
|
|
1022
|
+
const coreSkills = await loadCoreSkillsManifest();
|
|
1023
|
+
// AC: @cross-platform-and-version-robustness ac-3
|
|
1024
|
+
const kspecVersion = await getKspecPackageVersion();
|
|
1025
|
+
if (!kspecVersion) {
|
|
1026
|
+
debugLog("Could not determine kspec version — skills installed without version tracking");
|
|
1027
|
+
}
|
|
1028
|
+
for (const coreSkill of coreSkills) {
|
|
1029
|
+
// Check if skill exists
|
|
1030
|
+
const existingSkill = metaCtx.skills.find((s) => s.id === coreSkill.id);
|
|
1031
|
+
if (existingSkill && existingSkill.origin !== "core") {
|
|
1032
|
+
// Custom/project skill exists, skip
|
|
1033
|
+
skipped++;
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
// Build skill data
|
|
1037
|
+
const skillData = {
|
|
1038
|
+
_ulid: existingSkill?._ulid || ulid(),
|
|
1039
|
+
id: coreSkill.id,
|
|
1040
|
+
name: coreSkill.name,
|
|
1041
|
+
description: coreSkill.description,
|
|
1042
|
+
origin: "core",
|
|
1043
|
+
...(kspecVersion && { version: kspecVersion }),
|
|
1044
|
+
platforms: coreSkill.platforms || ["claude-code"],
|
|
1045
|
+
depends_on: [],
|
|
1046
|
+
tags: ["core"],
|
|
1047
|
+
};
|
|
1048
|
+
const parsed = SkillSchema.safeParse(skillData);
|
|
1049
|
+
if (!parsed.success) {
|
|
1050
|
+
skipped++;
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
if (!dryRun) {
|
|
1054
|
+
await saveMetaItem(ctx, parsed.data, "skill");
|
|
1055
|
+
// Copy skill files (SKILL.md + supporting dirs)
|
|
1056
|
+
const targetDir = path.dirname(getSkillContentPath(ctx, parsed.data.id));
|
|
1057
|
+
await copyCoreSkillFiles(coreSkill.id, targetDir);
|
|
1058
|
+
}
|
|
1059
|
+
installed++;
|
|
1060
|
+
}
|
|
1061
|
+
// Commit changes
|
|
1062
|
+
if (!dryRun && installed > 0) {
|
|
1063
|
+
const ctx2 = await initContext();
|
|
1064
|
+
await commitIfShadow(ctx2.shadow, "skill-install-core", `${installed} core skills`);
|
|
1065
|
+
}
|
|
1066
|
+
// AC: @core-skill-install ac-6, ac-7 - Register marketplace and enable plugin
|
|
1067
|
+
let marketplaceResult;
|
|
1068
|
+
let enableResult;
|
|
1069
|
+
if (!dryRun) {
|
|
1070
|
+
const { registerCorePluginMarketplace, enablePluginInProject, } = await import("../../lib/claude-plugin-registry.js");
|
|
1071
|
+
marketplaceResult = await registerCorePluginMarketplace();
|
|
1072
|
+
enableResult = await enablePluginInProject(projectDir);
|
|
1073
|
+
}
|
|
1074
|
+
return {
|
|
1075
|
+
installed,
|
|
1076
|
+
skipped,
|
|
1077
|
+
marketplaceRegistered: marketplaceResult?.success ?? false,
|
|
1078
|
+
pluginEnabled: enableResult?.success ?? false,
|
|
1079
|
+
marketplaceMessage: marketplaceResult?.message,
|
|
1080
|
+
enableMessage: enableResult?.message,
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
catch (err) {
|
|
1084
|
+
debugLog("installCoreSkillsForSetup failed", err);
|
|
1085
|
+
return { installed: 0, skipped: 0 };
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Run the full setup pipeline programmatically.
|
|
1090
|
+
* Used by both 'kspec setup' command and 'kspec init --setup'.
|
|
1091
|
+
* AC: @init-setup-integration ac-2, ac-3
|
|
1092
|
+
*/
|
|
1093
|
+
export async function runSetupPipeline(projectDir, options) {
|
|
1094
|
+
const dryRun = options.dryRun ?? false;
|
|
1095
|
+
const skipSkills = options.skipSkills ?? false;
|
|
1096
|
+
const installHooksFlag = options.installHooks ?? true;
|
|
1097
|
+
const steps = [];
|
|
1098
|
+
let coreSkillsInstalled = 0;
|
|
1099
|
+
let skillsRendered = 0;
|
|
1100
|
+
let hooksInstalled = false;
|
|
1101
|
+
let agentsMdGenerated = false;
|
|
1102
|
+
let permissionsSeeded = false;
|
|
1103
|
+
let memorySeeded = false;
|
|
1104
|
+
try {
|
|
1105
|
+
const detected = detectAgent();
|
|
1106
|
+
// Step 1: Agent detection
|
|
1107
|
+
steps.push({
|
|
1108
|
+
name: "Agent detection",
|
|
1109
|
+
status: "done",
|
|
1110
|
+
message: `${detected.type} (${detected.confidence} confidence)`,
|
|
1111
|
+
});
|
|
1112
|
+
// Step 2: Install core skills
|
|
1113
|
+
// AC: @init-setup-integration ac-2 - core skills installed in .kspec/skills/
|
|
1114
|
+
const coreResult = await installCoreSkillsForSetup(projectDir, dryRun);
|
|
1115
|
+
coreSkillsInstalled = coreResult.installed;
|
|
1116
|
+
if (coreResult.installed > 0 || coreResult.skipped > 0) {
|
|
1117
|
+
steps.push({
|
|
1118
|
+
name: "Install core skills",
|
|
1119
|
+
status: "done",
|
|
1120
|
+
message: `${coreResult.installed} installed, ${coreResult.skipped} skipped`,
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
steps.push({
|
|
1125
|
+
name: "Install core skills",
|
|
1126
|
+
status: "skipped",
|
|
1127
|
+
message: "No core skills found in package",
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
// Step 2b: Register plugin marketplace (reports result from installCoreSkillsForSetup)
|
|
1131
|
+
// AC: @core-skill-install ac-6, ac-7
|
|
1132
|
+
if (!dryRun && (coreResult.installed > 0 || coreResult.skipped > 0)) {
|
|
1133
|
+
const bothOk = coreResult.marketplaceRegistered && coreResult.pluginEnabled;
|
|
1134
|
+
if (bothOk) {
|
|
1135
|
+
steps.push({
|
|
1136
|
+
name: "Register plugin marketplace",
|
|
1137
|
+
status: "done",
|
|
1138
|
+
message: "marketplace registered, plugin enabled",
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
const failures = [];
|
|
1143
|
+
if (!coreResult.marketplaceRegistered) {
|
|
1144
|
+
failures.push(coreResult.marketplaceMessage || "marketplace registration failed");
|
|
1145
|
+
}
|
|
1146
|
+
if (!coreResult.pluginEnabled) {
|
|
1147
|
+
failures.push(coreResult.enableMessage || "plugin enablement failed");
|
|
1148
|
+
}
|
|
1149
|
+
steps.push({
|
|
1150
|
+
name: "Register plugin marketplace",
|
|
1151
|
+
status: "failed",
|
|
1152
|
+
message: failures.join("; "),
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
// Step 3: Install hooks (Claude Code only)
|
|
1157
|
+
// AC: @init-setup-integration ac-3 - hooks present
|
|
1158
|
+
if (detected.type === "claude-code" && installHooksFlag) {
|
|
1159
|
+
const hooksResult = await installClaudeCodeHooks(projectDir, dryRun);
|
|
1160
|
+
const installedHooks = [];
|
|
1161
|
+
if (hooksResult.promptCheck)
|
|
1162
|
+
installedHooks.push("UserPromptSubmit");
|
|
1163
|
+
if (hooksResult.stop)
|
|
1164
|
+
installedHooks.push("Stop");
|
|
1165
|
+
if (hooksResult.preToolUse)
|
|
1166
|
+
installedHooks.push("PreToolUse");
|
|
1167
|
+
hooksInstalled =
|
|
1168
|
+
hooksResult.promptCheck || hooksResult.stop || hooksResult.preToolUse;
|
|
1169
|
+
steps.push({
|
|
1170
|
+
name: "Install hooks",
|
|
1171
|
+
status: "done",
|
|
1172
|
+
message: installedHooks.join(", "),
|
|
1173
|
+
details: {
|
|
1174
|
+
guards: hooksResult.guardsCreated,
|
|
1175
|
+
},
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
else if (!installHooksFlag) {
|
|
1179
|
+
steps.push({
|
|
1180
|
+
name: "Install hooks",
|
|
1181
|
+
status: "skipped",
|
|
1182
|
+
message: "--no-hooks flag",
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
else {
|
|
1186
|
+
steps.push({
|
|
1187
|
+
name: "Install hooks",
|
|
1188
|
+
status: "skipped",
|
|
1189
|
+
message: `not applicable for ${detected.type}`,
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
// Step 3a: Ensure artifacts directory exists
|
|
1193
|
+
// AC: @artifacts-directory ac-setup-ensures
|
|
1194
|
+
{
|
|
1195
|
+
const artifactsDir = path.join(projectDir, ".kspec", "artifacts");
|
|
1196
|
+
let artifactsCreated = false;
|
|
1197
|
+
try {
|
|
1198
|
+
await fs.access(artifactsDir);
|
|
1199
|
+
}
|
|
1200
|
+
catch (_e) {
|
|
1201
|
+
if (!dryRun) {
|
|
1202
|
+
await fs.mkdir(artifactsDir, { recursive: true });
|
|
1203
|
+
}
|
|
1204
|
+
artifactsCreated = true;
|
|
1205
|
+
}
|
|
1206
|
+
steps.push({
|
|
1207
|
+
name: "Ensure artifacts directory",
|
|
1208
|
+
status: artifactsCreated ? "done" : "skipped",
|
|
1209
|
+
message: artifactsCreated ? "created .kspec/artifacts/" : "already exists",
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
// Step 3b: Seed permissions (Claude Code only)
|
|
1213
|
+
// AC: @new-project-bootstrapping ac-1
|
|
1214
|
+
{
|
|
1215
|
+
const { seedPermissions } = await import("./setup-seeding.js");
|
|
1216
|
+
const permResult = await seedPermissions(projectDir, detected.type, {
|
|
1217
|
+
dryRun,
|
|
1218
|
+
force: options.force,
|
|
1219
|
+
});
|
|
1220
|
+
permissionsSeeded = permResult.seeded;
|
|
1221
|
+
steps.push({
|
|
1222
|
+
name: "Seed permissions",
|
|
1223
|
+
status: permResult.seeded ? "done" : "skipped",
|
|
1224
|
+
message: permResult.message,
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
// Step 3c: Seed memory (platform-extensible)
|
|
1228
|
+
// AC: @new-project-bootstrapping ac-2
|
|
1229
|
+
{
|
|
1230
|
+
const { seedMemory } = await import("./setup-seeding.js");
|
|
1231
|
+
const memResult = await seedMemory(projectDir, detected.type, {
|
|
1232
|
+
dryRun,
|
|
1233
|
+
force: options.force,
|
|
1234
|
+
});
|
|
1235
|
+
memorySeeded = memResult.seeded;
|
|
1236
|
+
steps.push({
|
|
1237
|
+
name: "Seed memory",
|
|
1238
|
+
status: memResult.seeded ? "done" : "skipped",
|
|
1239
|
+
message: memResult.seeded ? memResult.path : memResult.message,
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
// Step 4: Render skills
|
|
1243
|
+
// AC: @init-setup-integration ac-3 - rendered skill files present
|
|
1244
|
+
if (!skipSkills) {
|
|
1245
|
+
const skillsResult = await renderSkillsForSetup(projectDir, dryRun);
|
|
1246
|
+
skillsRendered = skillsResult.rendered;
|
|
1247
|
+
if (skillsResult.rendered > 0 || skillsResult.skipped > 0 || skillsResult.pluginProvided > 0) {
|
|
1248
|
+
const parts = [];
|
|
1249
|
+
if (skillsResult.rendered > 0)
|
|
1250
|
+
parts.push(`${skillsResult.rendered} rendered`);
|
|
1251
|
+
if (skillsResult.pluginProvided > 0)
|
|
1252
|
+
parts.push(`${skillsResult.pluginProvided} plugin-provided`);
|
|
1253
|
+
if (skillsResult.skipped > 0)
|
|
1254
|
+
parts.push(`${skillsResult.skipped} unchanged`);
|
|
1255
|
+
steps.push({
|
|
1256
|
+
name: "Render skills",
|
|
1257
|
+
status: "done",
|
|
1258
|
+
message: parts.join(", "),
|
|
1259
|
+
details: {
|
|
1260
|
+
skillIds: skillsResult.skillIds,
|
|
1261
|
+
},
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
steps.push({
|
|
1266
|
+
name: "Render skills",
|
|
1267
|
+
status: "skipped",
|
|
1268
|
+
message: "No claude-code skills in meta",
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
steps.push({
|
|
1274
|
+
name: "Render skills",
|
|
1275
|
+
status: "skipped",
|
|
1276
|
+
message: "--skip-skills flag",
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
// Step 5: Generate kspec-agents.md
|
|
1280
|
+
// AC: @init-setup-integration ac-3 - kspec-agents.md present
|
|
1281
|
+
const agentsResult = await generateAgentInstructions(projectDir, dryRun);
|
|
1282
|
+
agentsMdGenerated = agentsResult.success;
|
|
1283
|
+
if (agentsResult.success) {
|
|
1284
|
+
steps.push({
|
|
1285
|
+
name: "Generate kspec-agents.md",
|
|
1286
|
+
status: "done",
|
|
1287
|
+
message: agentsResult.path,
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
steps.push({
|
|
1292
|
+
name: "Generate kspec-agents.md",
|
|
1293
|
+
status: "failed",
|
|
1294
|
+
message: "No kspec project found",
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
// Step 6: Configure author (optional, used by setup command)
|
|
1298
|
+
if (options.configureAuthor) {
|
|
1299
|
+
const author = options.author || getDefaultAuthor(detected.type);
|
|
1300
|
+
if (!options.force && process.env.KSPEC_AUTHOR) {
|
|
1301
|
+
steps.push({
|
|
1302
|
+
name: "Configure author",
|
|
1303
|
+
status: "skipped",
|
|
1304
|
+
message: `KSPEC_AUTHOR already set to "${process.env.KSPEC_AUTHOR}"`,
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
else {
|
|
1308
|
+
let authorInstalled = false;
|
|
1309
|
+
switch (detected.type) {
|
|
1310
|
+
case "claude-code":
|
|
1311
|
+
if (!dryRun) {
|
|
1312
|
+
authorInstalled = await installClaudeCodeConfig(author);
|
|
1313
|
+
}
|
|
1314
|
+
else {
|
|
1315
|
+
authorInstalled = true;
|
|
1316
|
+
}
|
|
1317
|
+
break;
|
|
1318
|
+
case "aider":
|
|
1319
|
+
if (!dryRun) {
|
|
1320
|
+
authorInstalled = await installAiderConfig(author);
|
|
1321
|
+
}
|
|
1322
|
+
else {
|
|
1323
|
+
authorInstalled = true;
|
|
1324
|
+
}
|
|
1325
|
+
break;
|
|
1326
|
+
default:
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
if (authorInstalled) {
|
|
1330
|
+
steps.push({
|
|
1331
|
+
name: "Configure author",
|
|
1332
|
+
status: "done",
|
|
1333
|
+
message: `KSPEC_AUTHOR="${author}"`,
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
// Output summary
|
|
1339
|
+
if (!dryRun) {
|
|
1340
|
+
console.log(chalk.bold("kspec Setup Summary\n"));
|
|
1341
|
+
for (const step of steps) {
|
|
1342
|
+
const icon = step.status === "done"
|
|
1343
|
+
? chalk.green("✓")
|
|
1344
|
+
: step.status === "skipped"
|
|
1345
|
+
? chalk.gray("○")
|
|
1346
|
+
: chalk.red("✗");
|
|
1347
|
+
const statusText = step.status === "done"
|
|
1348
|
+
? ""
|
|
1349
|
+
: step.status === "skipped"
|
|
1350
|
+
? chalk.gray(" (skipped)")
|
|
1351
|
+
: chalk.red(" (failed)");
|
|
1352
|
+
console.log(`${icon} ${step.name}${statusText}`);
|
|
1353
|
+
if (step.message) {
|
|
1354
|
+
console.log(chalk.gray(` ${step.message}`));
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
const success = steps.every((s) => s.status !== "failed");
|
|
1359
|
+
return {
|
|
1360
|
+
success,
|
|
1361
|
+
steps,
|
|
1362
|
+
coreSkillsInstalled,
|
|
1363
|
+
skillsRendered,
|
|
1364
|
+
hooksInstalled,
|
|
1365
|
+
agentsMdGenerated,
|
|
1366
|
+
permissionsSeeded,
|
|
1367
|
+
memorySeeded,
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
catch (err) {
|
|
1371
|
+
debugLog("runSetupPipeline failed", err);
|
|
1372
|
+
return {
|
|
1373
|
+
success: false,
|
|
1374
|
+
steps,
|
|
1375
|
+
coreSkillsInstalled,
|
|
1376
|
+
skillsRendered,
|
|
1377
|
+
hooksInstalled,
|
|
1378
|
+
agentsMdGenerated,
|
|
1379
|
+
permissionsSeeded,
|
|
1380
|
+
memorySeeded,
|
|
1381
|
+
};
|
|
471
1382
|
}
|
|
472
1383
|
}
|
|
473
1384
|
/**
|
|
474
1385
|
* Register the 'setup' command
|
|
1386
|
+
* AC: @enhanced-setup ac-1 through ac-9
|
|
475
1387
|
*/
|
|
476
1388
|
export function registerSetupCommand(program) {
|
|
477
1389
|
program
|
|
478
|
-
.command(
|
|
479
|
-
.description(
|
|
480
|
-
.option(
|
|
481
|
-
.option(
|
|
482
|
-
.option(
|
|
483
|
-
.option(
|
|
484
|
-
.option(
|
|
1390
|
+
.command("setup")
|
|
1391
|
+
.description("Configure agent environment for kspec (orchestrated pipeline)")
|
|
1392
|
+
.option("--dry-run", "Show what would be done without making changes")
|
|
1393
|
+
.option("--author <author>", "Custom author string (default: auto-detected based on agent)")
|
|
1394
|
+
.option("--no-hooks", "Skip installing hooks")
|
|
1395
|
+
.option("--skip-skills", "Skip rendering skills")
|
|
1396
|
+
.option("--status", "Report current setup state without making changes")
|
|
1397
|
+
.option("--force", "Overwrite existing configuration")
|
|
1398
|
+
.option("--auto-worktree", "Automatically create .kspec worktree if kspec-meta branch exists")
|
|
485
1399
|
.action(async (options) => {
|
|
486
1400
|
try {
|
|
1401
|
+
const projectDir = process.cwd();
|
|
1402
|
+
// AC: @enhanced-setup ac-7, ac-8 - --status mode
|
|
1403
|
+
if (options.status) {
|
|
1404
|
+
const status = await getSetupStatus(projectDir);
|
|
1405
|
+
output(status, () => {
|
|
1406
|
+
console.log(chalk.bold("kspec Setup Status\n"));
|
|
1407
|
+
// Agent detection
|
|
1408
|
+
console.log(chalk.gray("Agent:"));
|
|
1409
|
+
console.log(` Detected: ${status.agent.detected} (${status.agent.confidence} confidence)`);
|
|
1410
|
+
console.log();
|
|
1411
|
+
// Hooks status
|
|
1412
|
+
console.log(chalk.gray("Hooks:"));
|
|
1413
|
+
console.log(` UserPromptSubmit: ${status.hooks.promptCheck ? chalk.green("✓") : chalk.red("✗")}`);
|
|
1414
|
+
console.log(` Stop: ${status.hooks.stop ? chalk.green("✓") : chalk.red("✗")}`);
|
|
1415
|
+
console.log(` PreToolUse: ${status.hooks.preToolUse ? chalk.green("✓") : chalk.red("✗")}`);
|
|
1416
|
+
if (status.hooks.guardsPresent.length > 0) {
|
|
1417
|
+
console.log(` Guards: ${status.hooks.guardsPresent.join(", ")}`);
|
|
1418
|
+
}
|
|
1419
|
+
console.log();
|
|
1420
|
+
// Skills status
|
|
1421
|
+
console.log(chalk.gray("Skills:"));
|
|
1422
|
+
console.log(` Rendered: ${status.skills.rendered}`);
|
|
1423
|
+
if (status.skills.drifted > 0) {
|
|
1424
|
+
console.log(` Drifted: ${chalk.yellow(status.skills.drifted.toString())}`);
|
|
1425
|
+
}
|
|
1426
|
+
console.log();
|
|
1427
|
+
// Plugin marketplace status
|
|
1428
|
+
// AC: @enhanced-setup ac-7, ac-8
|
|
1429
|
+
console.log(chalk.gray("Plugin:"));
|
|
1430
|
+
console.log(` Marketplace: ${status.plugin.marketplaceRegistered ? (status.plugin.marketplaceHealthy ? chalk.green("healthy") : chalk.yellow("registered")) : chalk.red("not registered")}`);
|
|
1431
|
+
console.log(` Enabled: ${status.plugin.pluginEnabled ? chalk.green("✓") : chalk.red("✗")}`);
|
|
1432
|
+
if (status.plugin.registeredPath) {
|
|
1433
|
+
console.log(chalk.gray(` Path: ${status.plugin.registeredPath}`));
|
|
1434
|
+
}
|
|
1435
|
+
if (status.plugin.healthMessage && !status.plugin.marketplaceHealthy) {
|
|
1436
|
+
console.log(chalk.yellow(` ${status.plugin.healthMessage}`));
|
|
1437
|
+
}
|
|
1438
|
+
console.log();
|
|
1439
|
+
// Agents.md status
|
|
1440
|
+
console.log(chalk.gray("kspec-agents.md:"));
|
|
1441
|
+
if (status.agentsMd.exists) {
|
|
1442
|
+
// AC: @doctor-command ac-staleness-unknown — show appropriate color for unknown status
|
|
1443
|
+
const statusColor = status.agentsMd.status === "current"
|
|
1444
|
+
? chalk.green
|
|
1445
|
+
: status.agentsMd.status === "unknown"
|
|
1446
|
+
? chalk.yellow
|
|
1447
|
+
: chalk.yellow;
|
|
1448
|
+
console.log(` Status: ${statusColor(status.agentsMd.status)}`);
|
|
1449
|
+
if (status.agentsMd.status === "unknown") {
|
|
1450
|
+
console.log(chalk.gray(" Could not determine staleness (no manifest or hash unavailable)"));
|
|
1451
|
+
}
|
|
1452
|
+
if (status.agentsMd.generatedAt) {
|
|
1453
|
+
console.log(` Generated: ${status.agentsMd.generatedAt}`);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
console.log(` Status: ${chalk.red("missing")}`);
|
|
1458
|
+
console.log(chalk.gray(" Run 'kspec setup' to generate"));
|
|
1459
|
+
}
|
|
1460
|
+
console.log();
|
|
1461
|
+
// Seeding status
|
|
1462
|
+
console.log(chalk.gray("Seeding:"));
|
|
1463
|
+
console.log(` Permissions: ${status.seeding.permissionsSeeded ? chalk.green("✓") : chalk.gray("○")}`);
|
|
1464
|
+
console.log(` Memory: ${status.seeding.memorySeeded ? chalk.green("✓") : chalk.gray("○")}`);
|
|
1465
|
+
if (status.seeding.memoryPath) {
|
|
1466
|
+
console.log(chalk.gray(` Path: ${status.seeding.memoryPath}`));
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
487
1471
|
// AC: detect-existing-repo, auto-worktree-flag, worktree-already-exists
|
|
488
1472
|
const worktreeReady = await ensureWorktree(options.autoWorktree || false);
|
|
489
1473
|
if (!worktreeReady) {
|
|
@@ -491,91 +1475,66 @@ export function registerSetupCommand(program) {
|
|
|
491
1475
|
process.exit(EXIT_CODES.ERROR);
|
|
492
1476
|
}
|
|
493
1477
|
const detected = detectAgent();
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
printManualInstructions('unknown');
|
|
1478
|
+
const dryRun = options.dryRun || false;
|
|
1479
|
+
if (detected.type === "unknown") {
|
|
1480
|
+
warn("Could not auto-detect agent environment");
|
|
1481
|
+
printManualInstructions("unknown");
|
|
499
1482
|
return;
|
|
500
1483
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
1484
|
+
// AC: @setup-pipeline-unification ac-3 - delegate to runSetupPipeline()
|
|
1485
|
+
// One code path for both 'kspec setup' and 'kspec init --setup'
|
|
1486
|
+
const result = await runSetupPipeline(projectDir, {
|
|
1487
|
+
dryRun,
|
|
1488
|
+
skipSkills: options.skipSkills || false,
|
|
1489
|
+
installHooks: options.hooks !== false,
|
|
1490
|
+
force: options.force || false,
|
|
1491
|
+
author: options.author,
|
|
1492
|
+
configureAuthor: true,
|
|
1493
|
+
});
|
|
1494
|
+
// AC: @enhanced-setup ac-1 - Display summary
|
|
1495
|
+
// AC: @enhanced-setup ac-6 - dry-run displays planned actions
|
|
1496
|
+
output({
|
|
1497
|
+
dry_run: dryRun,
|
|
1498
|
+
steps: result.steps.map((s) => ({
|
|
1499
|
+
name: s.name,
|
|
1500
|
+
status: s.status,
|
|
1501
|
+
message: s.message,
|
|
1502
|
+
details: s.details,
|
|
1503
|
+
})),
|
|
1504
|
+
}, () => {
|
|
1505
|
+
if (dryRun) {
|
|
1506
|
+
console.log(chalk.yellow("DRY RUN - No changes made\n"));
|
|
513
1507
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
break;
|
|
534
|
-
case 'aider':
|
|
535
|
-
installed = await installAiderConfig(author);
|
|
536
|
-
break;
|
|
537
|
-
case 'cline':
|
|
538
|
-
case 'roo-code':
|
|
539
|
-
// These VS Code extensions use shell env vars, not config files
|
|
540
|
-
// Can't auto-install, must print instructions
|
|
541
|
-
printManualInstructions(detected.type);
|
|
542
|
-
return;
|
|
543
|
-
case 'copilot-cli':
|
|
544
|
-
case 'gemini-cli':
|
|
545
|
-
case 'opencode':
|
|
546
|
-
case 'amp':
|
|
547
|
-
if (detected.configPath) {
|
|
548
|
-
installed = await installGenericJsonConfig(detected.configPath, author);
|
|
1508
|
+
// Pipeline already prints the summary when not dry-run
|
|
1509
|
+
// For dry-run, print it here since the pipeline skips output
|
|
1510
|
+
if (dryRun) {
|
|
1511
|
+
console.log(chalk.bold("kspec Setup Summary\n"));
|
|
1512
|
+
for (const step of result.steps) {
|
|
1513
|
+
const icon = step.status === "done"
|
|
1514
|
+
? chalk.green("✓")
|
|
1515
|
+
: step.status === "skipped"
|
|
1516
|
+
? chalk.gray("○")
|
|
1517
|
+
: chalk.red("✗");
|
|
1518
|
+
const statusText = step.status === "done"
|
|
1519
|
+
? ""
|
|
1520
|
+
: step.status === "skipped"
|
|
1521
|
+
? chalk.gray(" (skipped)")
|
|
1522
|
+
: chalk.red(" (failed)");
|
|
1523
|
+
console.log(`${icon} ${step.name}${statusText}`);
|
|
1524
|
+
if (step.message) {
|
|
1525
|
+
console.log(chalk.gray(` ${step.message}`));
|
|
1526
|
+
}
|
|
549
1527
|
}
|
|
550
|
-
break;
|
|
551
|
-
case 'codex-cli':
|
|
552
|
-
// Codex uses TOML config, would need special handling
|
|
553
|
-
// For now, print manual instructions
|
|
554
|
-
printManualInstructions(detected.type);
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
if (installed) {
|
|
558
|
-
success(`Configured ${detected.type} with KSPEC_AUTHOR="${author}"`, {
|
|
559
|
-
agent: detected.type,
|
|
560
|
-
author,
|
|
561
|
-
configPath: detected.configPath,
|
|
562
|
-
});
|
|
563
|
-
if (hooksInstalled && hooksResult) {
|
|
564
|
-
const installedHooks = [];
|
|
565
|
-
if (hooksResult.promptCheck)
|
|
566
|
-
installedHooks.push('UserPromptSubmit (prompt-check)');
|
|
567
|
-
if (hooksResult.stop)
|
|
568
|
-
installedHooks.push('Stop (checkpoint)');
|
|
569
|
-
success(`Installed hooks to .claude/settings.json`, {
|
|
570
|
-
hooks: installedHooks,
|
|
571
|
-
});
|
|
572
1528
|
}
|
|
573
|
-
console.log(
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
1529
|
+
console.log();
|
|
1530
|
+
if (dryRun) {
|
|
1531
|
+
console.log(chalk.yellow("Run without --dry-run to apply changes."));
|
|
1532
|
+
}
|
|
1533
|
+
else {
|
|
1534
|
+
console.log(chalk.green("Setup complete."));
|
|
1535
|
+
console.log(chalk.gray("Restart your agent session for changes to take effect."));
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
579
1538
|
}
|
|
580
1539
|
catch (err) {
|
|
581
1540
|
error(errors.failures.setupFailed, err);
|