@kynetic-ai/spec 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -17
- package/dist/acp/client.d.ts +18 -4
- package/dist/acp/client.d.ts.map +1 -1
- package/dist/acp/client.js +44 -26
- package/dist/acp/client.js.map +1 -1
- package/dist/acp/framing.d.ts +2 -2
- package/dist/acp/framing.d.ts.map +1 -1
- package/dist/acp/framing.js +37 -29
- package/dist/acp/framing.js.map +1 -1
- package/dist/acp/index.d.ts +6 -7
- package/dist/acp/index.d.ts.map +1 -1
- package/dist/acp/index.js +3 -3
- package/dist/acp/index.js.map +1 -1
- package/dist/acp/types.d.ts +5 -5
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js +18 -18
- package/dist/acp/types.js.map +1 -1
- package/dist/agents/adapters.d.ts.map +1 -1
- package/dist/agents/adapters.js +24 -13
- package/dist/agents/adapters.js.map +1 -1
- package/dist/agents/index.d.ts +2 -2
- package/dist/agents/index.js +2 -2
- package/dist/agents/spawner.d.ts +4 -4
- package/dist/agents/spawner.d.ts.map +1 -1
- package/dist/agents/spawner.js +6 -6
- package/dist/agents/spawner.js.map +1 -1
- package/dist/cli/batch-context.d.ts +43 -0
- package/dist/cli/batch-context.d.ts.map +1 -0
- package/dist/cli/batch-context.js +93 -0
- package/dist/cli/batch-context.js.map +1 -0
- package/dist/cli/batch-exec.d.ts +116 -0
- package/dist/cli/batch-exec.d.ts.map +1 -0
- package/dist/cli/batch-exec.js +694 -0
- package/dist/cli/batch-exec.js.map +1 -0
- package/dist/cli/batch.d.ts +4 -2
- package/dist/cli/batch.d.ts.map +1 -1
- package/dist/cli/batch.js +15 -14
- package/dist/cli/batch.js.map +1 -1
- package/dist/cli/command-annotations.d.ts +23 -0
- package/dist/cli/command-annotations.d.ts.map +1 -0
- package/dist/cli/command-annotations.js +27 -0
- package/dist/cli/command-annotations.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +46 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +377 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/commands/batch.d.ts +20 -0
- package/dist/cli/commands/batch.d.ts.map +1 -0
- package/dist/cli/commands/batch.js +214 -0
- package/dist/cli/commands/batch.js.map +1 -0
- package/dist/cli/commands/clone-for-testing.d.ts +1 -1
- package/dist/cli/commands/clone-for-testing.d.ts.map +1 -1
- package/dist/cli/commands/clone-for-testing.js +37 -47
- package/dist/cli/commands/clone-for-testing.js.map +1 -1
- package/dist/cli/commands/derive.d.ts +1 -1
- package/dist/cli/commands/derive.d.ts.map +1 -1
- package/dist/cli/commands/derive.js +140 -88
- package/dist/cli/commands/derive.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +11 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +152 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/export.d.ts +12 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +134 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/help.d.ts +1 -1
- package/dist/cli/commands/help.d.ts.map +1 -1
- package/dist/cli/commands/help.js +163 -37
- package/dist/cli/commands/help.js.map +1 -1
- package/dist/cli/commands/inbox.d.ts +1 -1
- package/dist/cli/commands/inbox.d.ts.map +1 -1
- package/dist/cli/commands/inbox.js +178 -56
- package/dist/cli/commands/inbox.js.map +1 -1
- package/dist/cli/commands/index.d.ts +31 -19
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +31 -19
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +5 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +108 -57
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/item.d.ts +1 -1
- package/dist/cli/commands/item.d.ts.map +1 -1
- package/dist/cli/commands/item.js +557 -274
- package/dist/cli/commands/item.js.map +1 -1
- package/dist/cli/commands/link.d.ts +1 -1
- package/dist/cli/commands/link.d.ts.map +1 -1
- package/dist/cli/commands/link.js +55 -46
- package/dist/cli/commands/link.js.map +1 -1
- package/dist/cli/commands/log.d.ts +1 -1
- package/dist/cli/commands/log.d.ts.map +1 -1
- package/dist/cli/commands/log.js +57 -51
- package/dist/cli/commands/log.js.map +1 -1
- package/dist/cli/commands/merge-driver.d.ts +19 -0
- package/dist/cli/commands/merge-driver.d.ts.map +1 -0
- package/dist/cli/commands/merge-driver.js +398 -0
- package/dist/cli/commands/merge-driver.js.map +1 -0
- package/dist/cli/commands/meta.d.ts +1 -1
- package/dist/cli/commands/meta.d.ts.map +1 -1
- package/dist/cli/commands/meta.js +533 -399
- package/dist/cli/commands/meta.js.map +1 -1
- package/dist/cli/commands/module.d.ts +1 -1
- package/dist/cli/commands/module.d.ts.map +1 -1
- package/dist/cli/commands/module.js +30 -25
- package/dist/cli/commands/module.js.map +1 -1
- package/dist/cli/commands/plan-import.d.ts +11 -0
- package/dist/cli/commands/plan-import.d.ts.map +1 -0
- package/dist/cli/commands/plan-import.js +516 -0
- package/dist/cli/commands/plan-import.js.map +1 -0
- package/dist/cli/commands/plan.d.ts +10 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +421 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/ralph.d.ts +1 -1
- package/dist/cli/commands/ralph.d.ts.map +1 -1
- package/dist/cli/commands/ralph.js +1097 -169
- package/dist/cli/commands/ralph.js.map +1 -1
- package/dist/cli/commands/refs.d.ts +13 -0
- package/dist/cli/commands/refs.d.ts.map +1 -0
- package/dist/cli/commands/refs.js +283 -0
- package/dist/cli/commands/refs.js.map +1 -0
- package/dist/cli/commands/search.d.ts +1 -1
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/search.js +199 -37
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/serve.d.ts +10 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +491 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/session.d.ts +25 -6
- package/dist/cli/commands/session.d.ts.map +1 -1
- package/dist/cli/commands/session.js +811 -127
- package/dist/cli/commands/session.js.map +1 -1
- package/dist/cli/commands/setup-seeding.d.ts +81 -0
- package/dist/cli/commands/setup-seeding.d.ts.map +1 -0
- package/dist/cli/commands/setup-seeding.js +292 -0
- package/dist/cli/commands/setup-seeding.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +77 -3
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +1233 -274
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/shadow.d.ts +1 -1
- package/dist/cli/commands/shadow.d.ts.map +1 -1
- package/dist/cli/commands/shadow.js +70 -50
- package/dist/cli/commands/shadow.js.map +1 -1
- package/dist/cli/commands/skill-crud.d.ts +58 -0
- package/dist/cli/commands/skill-crud.d.ts.map +1 -0
- package/dist/cli/commands/skill-crud.js +753 -0
- package/dist/cli/commands/skill-crud.js.map +1 -0
- package/dist/cli/commands/skill-diff.d.ts +27 -0
- package/dist/cli/commands/skill-diff.d.ts.map +1 -0
- package/dist/cli/commands/skill-diff.js +840 -0
- package/dist/cli/commands/skill-diff.js.map +1 -0
- package/dist/cli/commands/skill-install.d.ts +53 -0
- package/dist/cli/commands/skill-install.d.ts.map +1 -0
- package/dist/cli/commands/skill-install.js +452 -0
- package/dist/cli/commands/skill-install.js.map +1 -0
- package/dist/cli/commands/skill.d.ts +20 -0
- package/dist/cli/commands/skill.d.ts.map +1 -0
- package/dist/cli/commands/skill.js +36 -0
- package/dist/cli/commands/skill.js.map +1 -0
- package/dist/cli/commands/task.d.ts +1 -1
- package/dist/cli/commands/task.d.ts.map +1 -1
- package/dist/cli/commands/task.js +569 -346
- package/dist/cli/commands/task.js.map +1 -1
- package/dist/cli/commands/tasks.d.ts +26 -1
- package/dist/cli/commands/tasks.d.ts.map +1 -1
- package/dist/cli/commands/tasks.js +227 -122
- package/dist/cli/commands/tasks.js.map +1 -1
- package/dist/cli/commands/trait.d.ts +1 -1
- package/dist/cli/commands/trait.d.ts.map +1 -1
- package/dist/cli/commands/trait.js +166 -101
- package/dist/cli/commands/trait.js.map +1 -1
- package/dist/cli/commands/triage.d.ts +7 -0
- package/dist/cli/commands/triage.d.ts.map +1 -0
- package/dist/cli/commands/triage.js +569 -0
- package/dist/cli/commands/triage.js.map +1 -0
- package/dist/cli/commands/util.d.ts +7 -0
- package/dist/cli/commands/util.d.ts.map +1 -0
- package/dist/cli/commands/util.js +30 -0
- package/dist/cli/commands/util.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +1 -1
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/cli/commands/validate.js +264 -83
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/commands/workflow.d.ts +16 -0
- package/dist/cli/commands/workflow.d.ts.map +1 -0
- package/dist/cli/commands/workflow.js +851 -0
- package/dist/cli/commands/workflow.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +7 -0
- package/dist/cli/exit-codes.d.ts.map +1 -1
- package/dist/cli/exit-codes.js +26 -18
- package/dist/cli/exit-codes.js.map +1 -1
- package/dist/cli/help/content.d.ts.map +1 -1
- package/dist/cli/help/content.js +86 -71
- package/dist/cli/help/content.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +131 -19
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/introspection.d.ts +6 -2
- package/dist/cli/introspection.d.ts.map +1 -1
- package/dist/cli/introspection.js +11 -8
- package/dist/cli/introspection.js.map +1 -1
- package/dist/cli/output.d.ts +64 -4
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +235 -85
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/parse-utils.d.ts +21 -0
- package/dist/cli/parse-utils.d.ts.map +1 -0
- package/dist/cli/parse-utils.js +32 -0
- package/dist/cli/parse-utils.js.map +1 -0
- package/dist/cli/pid-utils.d.ts +72 -0
- package/dist/cli/pid-utils.d.ts.map +1 -0
- package/dist/cli/pid-utils.js +174 -0
- package/dist/cli/pid-utils.js.map +1 -0
- package/dist/cli/suggest.d.ts.map +1 -1
- package/dist/cli/suggest.js +1 -2
- package/dist/cli/suggest.js.map +1 -1
- package/dist/cli/validators.d.ts +43 -0
- package/dist/cli/validators.d.ts.map +1 -0
- package/dist/cli/validators.js +84 -0
- package/dist/cli/validators.js.map +1 -0
- package/dist/daemon/index.ts +52 -0
- package/dist/daemon/middleware/project-context.ts +126 -0
- package/dist/daemon/pid.ts +179 -0
- package/dist/daemon/project-context.ts +343 -0
- package/dist/daemon/routes/inbox.ts +164 -0
- package/dist/daemon/routes/items.ts +322 -0
- package/dist/daemon/routes/meta.ts +118 -0
- package/dist/daemon/routes/projects.ts +162 -0
- package/dist/daemon/routes/tasks.ts +327 -0
- package/dist/daemon/routes/triage.ts +468 -0
- package/dist/daemon/routes/validation.ts +248 -0
- package/dist/daemon/server.ts +408 -0
- package/dist/daemon/watcher.ts +195 -0
- package/dist/daemon/websocket/handler.ts +138 -0
- package/dist/daemon/websocket/heartbeat.ts +71 -0
- package/dist/daemon/websocket/pubsub.ts +125 -0
- package/dist/daemon/websocket/types.ts +66 -0
- package/dist/export/html.d.ts +19 -0
- package/dist/export/html.d.ts.map +1 -0
- package/dist/export/html.js +239 -0
- package/dist/export/html.js.map +1 -0
- package/dist/export/index.d.ts +10 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +10 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/json.d.ts +24 -0
- package/dist/export/json.d.ts.map +1 -0
- package/dist/export/json.js +198 -0
- package/dist/export/json.js.map +1 -0
- package/dist/export/triage.d.ts +51 -0
- package/dist/export/triage.d.ts.map +1 -0
- package/dist/export/triage.js +83 -0
- package/dist/export/triage.js.map +1 -0
- package/dist/export/types.d.ts +122 -0
- package/dist/export/types.d.ts.map +1 -0
- package/dist/export/types.js +9 -0
- package/dist/export/types.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/lib/claude-plugin-registry.d.ts +66 -0
- package/dist/lib/claude-plugin-registry.d.ts.map +1 -0
- package/dist/lib/claude-plugin-registry.js +318 -0
- package/dist/lib/claude-plugin-registry.js.map +1 -0
- package/dist/merge/arrays.d.ts +87 -0
- package/dist/merge/arrays.d.ts.map +1 -0
- package/dist/merge/arrays.js +164 -0
- package/dist/merge/arrays.js.map +1 -0
- package/dist/merge/file-type.d.ts +32 -0
- package/dist/merge/file-type.d.ts.map +1 -0
- package/dist/merge/file-type.js +70 -0
- package/dist/merge/file-type.js.map +1 -0
- package/dist/merge/index.d.ts +14 -0
- package/dist/merge/index.d.ts.map +1 -0
- package/dist/merge/index.js +11 -0
- package/dist/merge/index.js.map +1 -0
- package/dist/merge/objects.d.ts +46 -0
- package/dist/merge/objects.d.ts.map +1 -0
- package/dist/merge/objects.js +193 -0
- package/dist/merge/objects.js.map +1 -0
- package/dist/merge/parse.d.ts +23 -0
- package/dist/merge/parse.d.ts.map +1 -0
- package/dist/merge/parse.js +78 -0
- package/dist/merge/parse.js.map +1 -0
- package/dist/merge/resolve.d.ts +66 -0
- package/dist/merge/resolve.d.ts.map +1 -0
- package/dist/merge/resolve.js +189 -0
- package/dist/merge/resolve.js.map +1 -0
- package/dist/merge/types.d.ts +82 -0
- package/dist/merge/types.d.ts.map +1 -0
- package/dist/merge/types.js +8 -0
- package/dist/merge/types.js.map +1 -0
- package/dist/parser/agent-data-sections.d.ts +53 -0
- package/dist/parser/agent-data-sections.d.ts.map +1 -0
- package/dist/parser/agent-data-sections.js +118 -0
- package/dist/parser/agent-data-sections.js.map +1 -0
- package/dist/parser/alignment.d.ts +4 -4
- package/dist/parser/alignment.d.ts.map +1 -1
- package/dist/parser/alignment.js +27 -22
- package/dist/parser/alignment.js.map +1 -1
- package/dist/parser/assess.d.ts +5 -5
- package/dist/parser/assess.d.ts.map +1 -1
- package/dist/parser/assess.js +36 -32
- package/dist/parser/assess.js.map +1 -1
- package/dist/parser/config.d.ts +351 -0
- package/dist/parser/config.d.ts.map +1 -0
- package/dist/parser/config.js +326 -0
- package/dist/parser/config.js.map +1 -0
- package/dist/parser/convention-validation.d.ts +1 -1
- package/dist/parser/convention-validation.d.ts.map +1 -1
- package/dist/parser/convention-validation.js +21 -16
- package/dist/parser/convention-validation.js.map +1 -1
- package/dist/parser/coverage-cache.d.ts +49 -0
- package/dist/parser/coverage-cache.d.ts.map +1 -0
- package/dist/parser/coverage-cache.js +123 -0
- package/dist/parser/coverage-cache.js.map +1 -0
- package/dist/parser/daemon-status.d.ts +37 -0
- package/dist/parser/daemon-status.d.ts.map +1 -0
- package/dist/parser/daemon-status.js +67 -0
- package/dist/parser/daemon-status.js.map +1 -0
- package/dist/parser/doctor.d.ts +107 -0
- package/dist/parser/doctor.d.ts.map +1 -0
- package/dist/parser/doctor.js +366 -0
- package/dist/parser/doctor.js.map +1 -0
- package/dist/parser/fix.d.ts +1 -1
- package/dist/parser/fix.d.ts.map +1 -1
- package/dist/parser/fix.js +31 -27
- package/dist/parser/fix.js.map +1 -1
- package/dist/parser/index.d.ts +16 -11
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +16 -11
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/items.d.ts +8 -2
- package/dist/parser/items.d.ts.map +1 -1
- package/dist/parser/items.js +71 -35
- package/dist/parser/items.js.map +1 -1
- package/dist/parser/meta.d.ts +167 -9
- package/dist/parser/meta.d.ts.map +1 -1
- package/dist/parser/meta.js +379 -46
- package/dist/parser/meta.js.map +1 -1
- package/dist/parser/plan-document.d.ts +189 -0
- package/dist/parser/plan-document.d.ts.map +1 -0
- package/dist/parser/plan-document.js +340 -0
- package/dist/parser/plan-document.js.map +1 -0
- package/dist/parser/plans.d.ts +59 -0
- package/dist/parser/plans.d.ts.map +1 -0
- package/dist/parser/plans.js +239 -0
- package/dist/parser/plans.js.map +1 -0
- package/dist/parser/refs.d.ts +22 -9
- package/dist/parser/refs.d.ts.map +1 -1
- package/dist/parser/refs.js +102 -50
- package/dist/parser/refs.js.map +1 -1
- package/dist/parser/setup-status.d.ts +71 -0
- package/dist/parser/setup-status.d.ts.map +1 -0
- package/dist/parser/setup-status.js +269 -0
- package/dist/parser/setup-status.js.map +1 -0
- package/dist/parser/shadow.d.ts +150 -19
- package/dist/parser/shadow.d.ts.map +1 -1
- package/dist/parser/shadow.js +548 -187
- package/dist/parser/shadow.js.map +1 -1
- package/dist/parser/skill-render.d.ts +317 -0
- package/dist/parser/skill-render.d.ts.map +1 -0
- package/dist/parser/skill-render.js +943 -0
- package/dist/parser/skill-render.js.map +1 -0
- package/dist/parser/traits.d.ts +3 -3
- package/dist/parser/traits.d.ts.map +1 -1
- package/dist/parser/traits.js +2 -2
- package/dist/parser/traits.js.map +1 -1
- package/dist/parser/validate-skills.d.ts +32 -0
- package/dist/parser/validate-skills.d.ts.map +1 -0
- package/dist/parser/validate-skills.js +202 -0
- package/dist/parser/validate-skills.js.map +1 -0
- package/dist/parser/validate.d.ts +45 -3
- package/dist/parser/validate.d.ts.map +1 -1
- package/dist/parser/validate.js +622 -105
- package/dist/parser/validate.js.map +1 -1
- package/dist/parser/yaml.d.ts +83 -19
- package/dist/parser/yaml.d.ts.map +1 -1
- package/dist/parser/yaml.js +478 -173
- package/dist/parser/yaml.js.map +1 -1
- package/dist/ralph/cli-renderer.d.ts +8 -1
- package/dist/ralph/cli-renderer.d.ts.map +1 -1
- package/dist/ralph/cli-renderer.js +105 -34
- package/dist/ralph/cli-renderer.js.map +1 -1
- package/dist/ralph/events.d.ts +10 -10
- package/dist/ralph/events.d.ts.map +1 -1
- package/dist/ralph/events.js +277 -98
- package/dist/ralph/events.js.map +1 -1
- package/dist/ralph/index.d.ts +5 -2
- package/dist/ralph/index.d.ts.map +1 -1
- package/dist/ralph/index.js +9 -3
- package/dist/ralph/index.js.map +1 -1
- package/dist/ralph/loop-errors.d.ts +83 -0
- package/dist/ralph/loop-errors.d.ts.map +1 -0
- package/dist/ralph/loop-errors.js +150 -0
- package/dist/ralph/loop-errors.js.map +1 -0
- package/dist/ralph/subagent.d.ts +83 -0
- package/dist/ralph/subagent.d.ts.map +1 -0
- package/dist/ralph/subagent.js +174 -0
- package/dist/ralph/subagent.js.map +1 -0
- package/dist/ralph/wrap-up.d.ts +125 -0
- package/dist/ralph/wrap-up.d.ts.map +1 -0
- package/dist/ralph/wrap-up.js +270 -0
- package/dist/ralph/wrap-up.js.map +1 -0
- package/dist/schema/batch.d.ts +95 -0
- package/dist/schema/batch.d.ts.map +1 -0
- package/dist/schema/batch.js +24 -0
- package/dist/schema/batch.js.map +1 -0
- package/dist/schema/common.d.ts +2 -2
- package/dist/schema/common.d.ts.map +1 -1
- package/dist/schema/common.js +34 -31
- package/dist/schema/common.js.map +1 -1
- package/dist/schema/inbox.d.ts +12 -12
- package/dist/schema/inbox.js +4 -4
- package/dist/schema/inbox.js.map +1 -1
- package/dist/schema/index.d.ts +8 -5
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +8 -5
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/meta.d.ts +1454 -27
- package/dist/schema/meta.d.ts.map +1 -1
- package/dist/schema/meta.js +198 -21
- package/dist/schema/meta.js.map +1 -1
- package/dist/schema/plan.d.ts +285 -0
- package/dist/schema/plan.d.ts.map +1 -0
- package/dist/schema/plan.js +81 -0
- package/dist/schema/plan.js.map +1 -0
- package/dist/schema/spec.d.ts +72 -33
- package/dist/schema/spec.d.ts.map +1 -1
- package/dist/schema/spec.js +22 -9
- package/dist/schema/spec.js.map +1 -1
- package/dist/schema/task.d.ts +172 -161
- package/dist/schema/task.d.ts.map +1 -1
- package/dist/schema/task.js +21 -12
- package/dist/schema/task.js.map +1 -1
- package/dist/schema/triage.d.ts +266 -0
- package/dist/schema/triage.d.ts.map +1 -0
- package/dist/schema/triage.js +134 -0
- package/dist/schema/triage.js.map +1 -0
- package/dist/sessions/index.d.ts +2 -2
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +3 -3
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/store.d.ts +233 -1
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +628 -31
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +10 -10
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js +10 -9
- package/dist/sessions/types.js.map +1 -1
- package/dist/strings/errors.d.ts +51 -0
- package/dist/strings/errors.d.ts.map +1 -1
- package/dist/strings/errors.js +136 -106
- package/dist/strings/errors.js.map +1 -1
- package/dist/strings/guidance.d.ts.map +1 -1
- package/dist/strings/guidance.js +16 -16
- package/dist/strings/guidance.js.map +1 -1
- package/dist/strings/index.d.ts +4 -4
- package/dist/strings/index.d.ts.map +1 -1
- package/dist/strings/index.js +4 -4
- package/dist/strings/index.js.map +1 -1
- package/dist/strings/labels.d.ts +4 -0
- package/dist/strings/labels.d.ts.map +1 -1
- package/dist/strings/labels.js +45 -41
- package/dist/strings/labels.js.map +1 -1
- package/dist/strings/validation.d.ts.map +1 -1
- package/dist/strings/validation.js +71 -71
- package/dist/strings/validation.js.map +1 -1
- package/dist/utils/commit.d.ts +1 -1
- package/dist/utils/commit.d.ts.map +1 -1
- package/dist/utils/commit.js +28 -26
- package/dist/utils/commit.js.map +1 -1
- package/dist/utils/git.d.ts +1 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +40 -38
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/grep.js +11 -11
- package/dist/utils/grep.js.map +1 -1
- package/dist/utils/index.d.ts +7 -7
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +10 -10
- package/dist/utils/time.js.map +1 -1
- package/package.json +28 -5
- package/plugin/.claude-plugin/marketplace.json +17 -0
- package/plugin/.claude-plugin/plugin.json +5 -0
- package/plugin/plugins/kspec/skills/help/SKILL.md +42 -0
- package/plugin/plugins/kspec/skills/triage/SKILL.md +206 -0
- package/plugin/plugins/kspec/skills/triage/docs/automation.md +120 -0
- package/plugin/plugins/kspec/skills/triage/docs/inbox.md +144 -0
- package/plugin/plugins/kspec/skills/triage/docs/observations.md +85 -0
- package/templates/agents-sections/01-quick-start.md +22 -0
- package/templates/agents-sections/02-shadow-branch.md +34 -0
- package/templates/agents-sections/03-task-lifecycle.md +48 -0
- package/templates/agents-sections/04-pr-workflow.md +17 -0
- package/templates/agents-sections/05-commit-convention.md +27 -0
- package/templates/agents-sections/06-ralph-loop.md +45 -0
- package/templates/hooks/pre-commit +34 -0
- package/templates/skills/help/SKILL.md +37 -0
- package/templates/skills/manifest.yaml +15 -0
- package/templates/skills/triage/SKILL.md +199 -0
- package/templates/skills/triage/docs/automation.md +120 -0
- package/templates/skills/triage/docs/inbox.md +144 -0
- package/templates/skills/triage/docs/observations.md +85 -0
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill render, status, diff, and verify commands.
|
|
3
|
+
*
|
|
4
|
+
* AC: @skill-rendering ac-1 through ac-5 - kspec skill render
|
|
5
|
+
* AC: @skill-render-cli ac-1 through ac-4 - render/status/diff CLI
|
|
6
|
+
* AC: @skill-drift-detection ac-3, ac-4 - drift handling with --force
|
|
7
|
+
* AC: @multi-platform-render-cli ac-1 through ac-7 - multi-platform rendering
|
|
8
|
+
* AC: @skill-drift-detection-improvements ac-2 - kspec skill verify
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from "node:fs/promises";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import Table from "cli-table3";
|
|
14
|
+
import { createTwoFilesPatch } from "diff";
|
|
15
|
+
import { findMetaItemByRef, initContext, isSkill, loadMetaContext, loadSkillContent, } from "../../parser/index.js";
|
|
16
|
+
import { isKspecManagedSkill as isKspecManaged, KSPEC_MANAGED_MARKER, generateFrontmatter, getRenderer, getAllRenderers, getSkillSubdir, contentsEqual, directoriesEqual, migrateOldPluginPaths, } from "../../parser/skill-render.js";
|
|
17
|
+
import { errors } from "../../strings/errors.js";
|
|
18
|
+
import { EXIT_CODES } from "../exit-codes.js";
|
|
19
|
+
import { error, output, success } from "../output.js";
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Helpers
|
|
22
|
+
// ============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Get the sync status of a skill for a specific platform
|
|
25
|
+
* AC: @multi-platform-render-cli ac-3 - Per-platform status checking
|
|
26
|
+
*/
|
|
27
|
+
async function getMultiPlatformSyncStatus(ctx, projectRoot, skill, renderer) {
|
|
28
|
+
// Use the renderer's drift check
|
|
29
|
+
const driftStatus = await renderer.checkDrift(ctx.specDir, projectRoot, skill.id, { origin: skill.origin });
|
|
30
|
+
// Core skills on claude-code are plugin-provided, not locally rendered
|
|
31
|
+
if (driftStatus === "plugin-provided") {
|
|
32
|
+
return {
|
|
33
|
+
id: skill.id,
|
|
34
|
+
platform: renderer.platform,
|
|
35
|
+
status: "plugin-provided",
|
|
36
|
+
docsStatus: "no-docs",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Compute the effective output dir for this skill-platform pair
|
|
40
|
+
const effectiveOutputDir = renderer.defaultOutputDir;
|
|
41
|
+
// Map drift status to sync status
|
|
42
|
+
let status;
|
|
43
|
+
switch (driftStatus) {
|
|
44
|
+
case "in-sync":
|
|
45
|
+
status = "in-sync";
|
|
46
|
+
break;
|
|
47
|
+
case "drifted":
|
|
48
|
+
status = "drifted";
|
|
49
|
+
break;
|
|
50
|
+
case "not-rendered":
|
|
51
|
+
status = "not-rendered";
|
|
52
|
+
break;
|
|
53
|
+
case "no-hash":
|
|
54
|
+
// No hash stored - need to check if content matches expected
|
|
55
|
+
const renderedPath = path.join(projectRoot, effectiveOutputDir, getSkillSubdir(skill.id, skill.origin, renderer.platform), "SKILL.md");
|
|
56
|
+
try {
|
|
57
|
+
await fs.readFile(renderedPath, "utf-8");
|
|
58
|
+
// File exists but no hash - treat as needing sync
|
|
59
|
+
status = "drifted";
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
status = "not-rendered";
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
// Check docs status for this platform
|
|
67
|
+
const sourceDocsDir = path.join(ctx.specDir, "skills", skill.id, "docs");
|
|
68
|
+
const targetDocsDir = path.join(projectRoot, effectiveOutputDir, getSkillSubdir(skill.id, skill.origin, renderer.platform), "docs");
|
|
69
|
+
let docsStatus = "no-docs";
|
|
70
|
+
try {
|
|
71
|
+
await fs.stat(sourceDocsDir);
|
|
72
|
+
// Source docs exist, check target
|
|
73
|
+
try {
|
|
74
|
+
await fs.stat(targetDocsDir);
|
|
75
|
+
// Both exist, compare
|
|
76
|
+
const equal = await directoriesEqual(sourceDocsDir, targetDocsDir);
|
|
77
|
+
docsStatus = equal ? "in-sync" : "drifted";
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
docsStatus = "not-rendered";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// No source docs
|
|
85
|
+
docsStatus = "no-docs";
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
id: skill.id,
|
|
89
|
+
platform: renderer.platform,
|
|
90
|
+
status,
|
|
91
|
+
docsStatus,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get the expected rendered content for a skill
|
|
96
|
+
*/
|
|
97
|
+
export async function getExpectedRenderedContent(ctx, skill) {
|
|
98
|
+
const sourceContent = await loadSkillContent(ctx, skill);
|
|
99
|
+
if (!sourceContent) {
|
|
100
|
+
// No source content, generate placeholder
|
|
101
|
+
const frontmatter = generateFrontmatter(skill);
|
|
102
|
+
return `${frontmatter}\n${KSPEC_MANAGED_MARKER}\n\n# ${skill.name}\n\n${skill.description || ""}\n`;
|
|
103
|
+
}
|
|
104
|
+
// Generate rendered content with frontmatter and marker
|
|
105
|
+
const frontmatter = generateFrontmatter(skill);
|
|
106
|
+
// Check if source already has frontmatter - if so, strip it
|
|
107
|
+
const frontmatterMatch = sourceContent.match(/^---\n[\s\S]*?\n---\n?/);
|
|
108
|
+
const contentWithoutFrontmatter = frontmatterMatch
|
|
109
|
+
? sourceContent.slice(frontmatterMatch[0].length)
|
|
110
|
+
: sourceContent;
|
|
111
|
+
return `${frontmatter}\n${KSPEC_MANAGED_MARKER}\n${contentWithoutFrontmatter}`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Generate unified diff between two strings.
|
|
115
|
+
* Uses the 'diff' library for correct LCS-based diffing.
|
|
116
|
+
* AC: @skill-render-cli ac-4
|
|
117
|
+
* AC: @guard-script-and-diff-quality ac-2
|
|
118
|
+
*/
|
|
119
|
+
export function generateUnifiedDiff(actual, expected, actualPath, expectedPath) {
|
|
120
|
+
if (contentsEqual(actual, expected)) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
const patch = createTwoFilesPatch(actualPath, expectedPath, actual, expected, "", "", {
|
|
124
|
+
context: 3,
|
|
125
|
+
});
|
|
126
|
+
// Split into lines and remove the first line (Index: ...) which createTwoFilesPatch adds
|
|
127
|
+
const lines = patch.split("\n");
|
|
128
|
+
// createTwoFilesPatch outputs: "Index: ...\n===...\n--- ...\n+++ ...\n@@ ... @@\n..."
|
|
129
|
+
// Skip the "Index:" and "===" header lines to match our expected format
|
|
130
|
+
const startIdx = lines.findIndex((l) => l.startsWith("---"));
|
|
131
|
+
if (startIdx === -1)
|
|
132
|
+
return [];
|
|
133
|
+
// Remove trailing empty line that createTwoFilesPatch adds
|
|
134
|
+
const result = lines.slice(startIdx);
|
|
135
|
+
if (result.length > 0 && result[result.length - 1] === "") {
|
|
136
|
+
result.pop();
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Command Registration
|
|
142
|
+
// ============================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Register render/status/diff/verify skill commands
|
|
145
|
+
*/
|
|
146
|
+
export function registerSkillDiffCommands(skill) {
|
|
147
|
+
// AC: @skill-rendering ac-1 through ac-5 - kspec skill render
|
|
148
|
+
// AC: @skill-render-cli ac-1, ac-2 - kspec skill render / kspec skill render @skill-id
|
|
149
|
+
// AC: @skill-drift-detection ac-3, ac-4 - drift handling with --force
|
|
150
|
+
// AC: @multi-platform-render-cli ac-1 through ac-7 - multi-platform rendering
|
|
151
|
+
// AC: @trait-dry-run - supports --dry-run
|
|
152
|
+
// AC: @trait-error-guidance - provides error guidance
|
|
153
|
+
skill
|
|
154
|
+
.command("render [ref]")
|
|
155
|
+
.description("Render skills from shadow branch to platform-specific files on main branch")
|
|
156
|
+
.option("--clean", "Remove orphaned managed skill directories")
|
|
157
|
+
.option("--dry-run", "Show what would be changed without applying")
|
|
158
|
+
.option("--force", "Overwrite drifted skill files (manually edited)")
|
|
159
|
+
.option("--output-dir <path>", "Custom output directory (overrides platform default)")
|
|
160
|
+
.option("--skill <id>", "Render only a specific skill (deprecated, use positional arg)")
|
|
161
|
+
.action(async (ref, options) => {
|
|
162
|
+
try {
|
|
163
|
+
const ctx = await initContext();
|
|
164
|
+
if (!ctx.manifestPath) {
|
|
165
|
+
error(errors.project.noKspecProject);
|
|
166
|
+
console.log(chalk.gray("Try: kspec init to initialize a kspec project"));
|
|
167
|
+
process.exit(EXIT_CODES.ERROR);
|
|
168
|
+
}
|
|
169
|
+
const metaCtx = await loadMetaContext(ctx);
|
|
170
|
+
// Use rootDir (project root), not specDir (which is .kspec/ in shadow mode)
|
|
171
|
+
const projectRoot = ctx.rootDir;
|
|
172
|
+
const dryRun = options.dryRun || false;
|
|
173
|
+
const force = options.force || false;
|
|
174
|
+
const customOutputDir = options.outputDir;
|
|
175
|
+
const results = [];
|
|
176
|
+
const cleanResults = [];
|
|
177
|
+
const warnings = [];
|
|
178
|
+
// Get all skills (no platform filtering - we'll check per-platform)
|
|
179
|
+
let skillsToRender = metaCtx.skills;
|
|
180
|
+
// AC: @skill-render-cli ac-2 - Filter to specific skill if requested
|
|
181
|
+
// Support both positional ref argument and --skill option
|
|
182
|
+
const skillRef = ref || options.skill;
|
|
183
|
+
if (skillRef) {
|
|
184
|
+
// Resolve the ref to a skill (uses _type discriminant)
|
|
185
|
+
const item = findMetaItemByRef(metaCtx, skillRef);
|
|
186
|
+
if (!item || !isSkill(item)) {
|
|
187
|
+
error(`Skill not found: ${skillRef}`);
|
|
188
|
+
console.log(chalk.gray("Try: kspec skill list"));
|
|
189
|
+
process.exit(EXIT_CODES.NOT_FOUND);
|
|
190
|
+
}
|
|
191
|
+
const skill = item;
|
|
192
|
+
skillsToRender = skillsToRender.filter((s) => s.id === skill.id);
|
|
193
|
+
}
|
|
194
|
+
// Track which platforms we're using to create output directories and for clean
|
|
195
|
+
const usedPlatforms = new Set();
|
|
196
|
+
// AC: @multi-platform-render-cli ac-1, ac-2 - Render each skill to each of its platforms
|
|
197
|
+
for (const skill of skillsToRender) {
|
|
198
|
+
for (const platform of skill.platforms) {
|
|
199
|
+
// Get the renderer for this platform
|
|
200
|
+
const renderer = getRenderer(platform);
|
|
201
|
+
// AC: @multi-platform-render-cli ac-7 - Warn for unregistered platforms
|
|
202
|
+
if (!renderer) {
|
|
203
|
+
warnings.push(`${skill.id}: unregistered platform '${platform}' (skipped)`);
|
|
204
|
+
results.push({
|
|
205
|
+
id: skill.id,
|
|
206
|
+
platform,
|
|
207
|
+
action: "skipped",
|
|
208
|
+
path: "",
|
|
209
|
+
skipReason: `unregistered platform '${platform}'`,
|
|
210
|
+
});
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
usedPlatforms.add(platform);
|
|
214
|
+
// Core skills on claude-code are plugin-provided, skip local render
|
|
215
|
+
// But still run migration cleanup for old render paths
|
|
216
|
+
if (skill.origin === "core" && platform === "claude-code" && !customOutputDir) {
|
|
217
|
+
if (!dryRun) {
|
|
218
|
+
await migrateOldPluginPaths(projectRoot, skill.id);
|
|
219
|
+
}
|
|
220
|
+
results.push({
|
|
221
|
+
id: skill.id,
|
|
222
|
+
platform,
|
|
223
|
+
action: "skipped",
|
|
224
|
+
path: "",
|
|
225
|
+
skipReason: "core skill provided by npm package plugin",
|
|
226
|
+
skipCode: "plugin-provided",
|
|
227
|
+
});
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
// Determine output directory
|
|
231
|
+
const effectiveDir = customOutputDir || renderer.defaultOutputDir;
|
|
232
|
+
// Ensure output directory exists
|
|
233
|
+
const targetSkillsDir = path.join(projectRoot, effectiveDir);
|
|
234
|
+
if (!dryRun) {
|
|
235
|
+
await fs.mkdir(targetSkillsDir, { recursive: true });
|
|
236
|
+
}
|
|
237
|
+
// Check for drift before rendering
|
|
238
|
+
// AC: @skill-drift-detection ac-3, ac-4 - Check drift and skip without --force
|
|
239
|
+
const driftStatus = await renderer.checkDrift(ctx.specDir, projectRoot, skill.id, { outputDir: customOutputDir, origin: skill.origin });
|
|
240
|
+
// AC: @skill-drift-detection ac-3 - Skip drifted skills without --force
|
|
241
|
+
if (driftStatus === "drifted" && !force) {
|
|
242
|
+
const renderedPath = path.join(projectRoot, effectiveDir, getSkillSubdir(skill.id, skill.origin, platform), "SKILL.md");
|
|
243
|
+
results.push({
|
|
244
|
+
id: skill.id,
|
|
245
|
+
platform,
|
|
246
|
+
action: "skipped",
|
|
247
|
+
path: renderedPath,
|
|
248
|
+
skipReason: "drifted (use --force to overwrite)",
|
|
249
|
+
});
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
// AC: @skill-drift-detection ac-4 - Render (overwrite) when --force is used
|
|
253
|
+
// AC: @multi-platform-render-cli ac-4 - Pass custom outputDir to renderer
|
|
254
|
+
const result = await renderer.render(ctx, projectRoot, skill, {
|
|
255
|
+
dryRun,
|
|
256
|
+
storeHash: true,
|
|
257
|
+
outputDir: customOutputDir,
|
|
258
|
+
});
|
|
259
|
+
results.push({
|
|
260
|
+
id: result.id,
|
|
261
|
+
platform: result.platform,
|
|
262
|
+
action: result.action,
|
|
263
|
+
path: result.paths[0] || "",
|
|
264
|
+
skipReason: result.skipReason,
|
|
265
|
+
skipCode: result.skipCode,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Handle --clean: remove orphaned managed skills per platform
|
|
270
|
+
// AC: @skill-rendering ac-4, ac-5
|
|
271
|
+
// AC: @multi-platform-render-cli ac-5 - Per-platform cleanup
|
|
272
|
+
if (options.clean) {
|
|
273
|
+
// Build set of active skill subdirs per platform (accounts for namespacing)
|
|
274
|
+
const activeSubdirsByPlatform = new Map();
|
|
275
|
+
for (const skill of metaCtx.skills) {
|
|
276
|
+
for (const platform of skill.platforms) {
|
|
277
|
+
if (!activeSubdirsByPlatform.has(platform)) {
|
|
278
|
+
activeSubdirsByPlatform.set(platform, new Set());
|
|
279
|
+
}
|
|
280
|
+
activeSubdirsByPlatform.get(platform).add(getSkillSubdir(skill.id, skill.origin, platform));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Helper to check and clean a skill directory
|
|
284
|
+
async function cleanSkillDir(skillId, skillDir, activeSubdirs, subdir, platform, hasNestedSkills) {
|
|
285
|
+
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
286
|
+
// Skip if skill still exists in meta for this platform
|
|
287
|
+
if (activeSubdirs.has(subdir))
|
|
288
|
+
return;
|
|
289
|
+
// AC: @skill-rendering ac-4 - Only consider kspec-managed skills
|
|
290
|
+
const isManaged = await isKspecManaged(skillMdPath);
|
|
291
|
+
if (isManaged) {
|
|
292
|
+
// AC: @skill-rendering ac-5 - Remove orphaned directory
|
|
293
|
+
// AC: @skill-rendering ac-6 - Preserve active nested skills in namespace dirs
|
|
294
|
+
if (!dryRun) {
|
|
295
|
+
if (hasNestedSkills) {
|
|
296
|
+
// Directory contains nested skills — only remove the SKILL.md,
|
|
297
|
+
// not the entire directory tree
|
|
298
|
+
await fs.rm(skillMdPath, { force: true });
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
await fs.rm(skillDir, { recursive: true, force: true });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
cleanResults.push({
|
|
305
|
+
id: skillId,
|
|
306
|
+
path: hasNestedSkills ? skillMdPath : skillDir,
|
|
307
|
+
action: "removed",
|
|
308
|
+
platform,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
cleanResults.push({
|
|
313
|
+
id: skillId,
|
|
314
|
+
path: skillDir,
|
|
315
|
+
action: "skipped",
|
|
316
|
+
reason: "Not managed by kspec",
|
|
317
|
+
platform,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// Helper to scan and clean orphaned skills in a directory
|
|
322
|
+
async function scanAndClean(targetSkillsDir, activeSubdirs, platform) {
|
|
323
|
+
try {
|
|
324
|
+
const entries = await fs.readdir(targetSkillsDir, {
|
|
325
|
+
withFileTypes: true,
|
|
326
|
+
});
|
|
327
|
+
for (const entry of entries) {
|
|
328
|
+
if (!entry.isDirectory())
|
|
329
|
+
continue;
|
|
330
|
+
const skillDir = path.join(targetSkillsDir, entry.name);
|
|
331
|
+
// Check for SKILL.md in this directory
|
|
332
|
+
const hasSkillMd = await fs.access(path.join(skillDir, "SKILL.md")).then(() => true, () => false);
|
|
333
|
+
if (hasSkillMd) {
|
|
334
|
+
await cleanSkillDir(entry.name, skillDir, activeSubdirs, entry.name, platform);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// Output directory doesn't exist, nothing to clean
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Build separate active sets for default dir vs plugin dir
|
|
343
|
+
// Core skills on claude-code are active in plugin dir, NOT in .claude/skills/
|
|
344
|
+
const nonCoreActiveByPlatform = new Map();
|
|
345
|
+
const coreActiveByPlatform = new Map();
|
|
346
|
+
for (const skill of metaCtx.skills) {
|
|
347
|
+
for (const platform of skill.platforms) {
|
|
348
|
+
if (skill.origin === "core" && platform === "claude-code") {
|
|
349
|
+
if (!coreActiveByPlatform.has(platform)) {
|
|
350
|
+
coreActiveByPlatform.set(platform, new Set());
|
|
351
|
+
}
|
|
352
|
+
coreActiveByPlatform.get(platform).add(skill.id);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
if (!nonCoreActiveByPlatform.has(platform)) {
|
|
356
|
+
nonCoreActiveByPlatform.set(platform, new Set());
|
|
357
|
+
}
|
|
358
|
+
nonCoreActiveByPlatform.get(platform).add(getSkillSubdir(skill.id, skill.origin, platform));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// Clean each platform's output directory
|
|
363
|
+
const renderers = getAllRenderers();
|
|
364
|
+
for (const renderer of renderers) {
|
|
365
|
+
// Clean platform default directory (project/local skills only)
|
|
366
|
+
const nonCoreActive = nonCoreActiveByPlatform.get(renderer.platform) || new Set();
|
|
367
|
+
const outputDir = customOutputDir || renderer.defaultOutputDir;
|
|
368
|
+
await scanAndClean(path.join(projectRoot, outputDir), nonCoreActive, renderer.platform);
|
|
369
|
+
// Core skills on claude-code are now plugin-provided.
|
|
370
|
+
// Clean old namespaced dirs and legacy plugin-rendered paths.
|
|
371
|
+
if (renderer.platform === "claude-code" && !customOutputDir) {
|
|
372
|
+
// Clean orphaned legacy plugin-rendered core skills (.claude/plugins/kspec/skills/<id>/)
|
|
373
|
+
const coreActive = coreActiveByPlatform.get(renderer.platform) || new Set();
|
|
374
|
+
const legacyPluginSkillsDir = path.join(projectRoot, ".claude", "plugins", "kspec", "skills");
|
|
375
|
+
await scanAndClean(legacyPluginSkillsDir, coreActive, renderer.platform);
|
|
376
|
+
// Clean old namespaced dirs (.claude/skills/kspec/<id>/) that may remain from PR #440
|
|
377
|
+
const oldNamespaceDir = path.join(projectRoot, renderer.defaultOutputDir, "kspec");
|
|
378
|
+
try {
|
|
379
|
+
const entries = await fs.readdir(oldNamespaceDir, { withFileTypes: true });
|
|
380
|
+
for (const entry of entries) {
|
|
381
|
+
if (!entry.isDirectory()) {
|
|
382
|
+
// Handle orphaned SKILL.md at namespace root
|
|
383
|
+
if (entry.name === "SKILL.md") {
|
|
384
|
+
const skillMdPath = path.join(oldNamespaceDir, "SKILL.md");
|
|
385
|
+
const isManaged = await isKspecManaged(skillMdPath);
|
|
386
|
+
if (isManaged && !dryRun) {
|
|
387
|
+
await fs.rm(skillMdPath, { force: true });
|
|
388
|
+
}
|
|
389
|
+
if (isManaged) {
|
|
390
|
+
cleanResults.push({
|
|
391
|
+
id: "kspec",
|
|
392
|
+
path: skillMdPath,
|
|
393
|
+
action: "removed",
|
|
394
|
+
platform: renderer.platform,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const nestedDir = path.join(oldNamespaceDir, entry.name);
|
|
401
|
+
const nestedHasSkillMd = await fs.access(path.join(nestedDir, "SKILL.md")).then(() => true, () => false);
|
|
402
|
+
if (nestedHasSkillMd) {
|
|
403
|
+
const isManaged = await isKspecManaged(path.join(nestedDir, "SKILL.md"));
|
|
404
|
+
if (isManaged) {
|
|
405
|
+
if (!dryRun) {
|
|
406
|
+
await fs.rm(nestedDir, { recursive: true, force: true });
|
|
407
|
+
}
|
|
408
|
+
cleanResults.push({
|
|
409
|
+
id: entry.name,
|
|
410
|
+
path: nestedDir,
|
|
411
|
+
action: "removed",
|
|
412
|
+
platform: renderer.platform,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Clean up empty kspec dir
|
|
418
|
+
if (!dryRun) {
|
|
419
|
+
try {
|
|
420
|
+
const remaining = await fs.readdir(oldNamespaceDir);
|
|
421
|
+
if (remaining.length === 0) {
|
|
422
|
+
await fs.rm(oldNamespaceDir, { recursive: true, force: true });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// Already removed or doesn't exist
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
// Old namespace dir doesn't exist
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Output results
|
|
437
|
+
// AC: @trait-dry-run ac-6 - JSON output includes dry_run field
|
|
438
|
+
output({
|
|
439
|
+
dry_run: dryRun,
|
|
440
|
+
rendered: results,
|
|
441
|
+
cleaned: cleanResults,
|
|
442
|
+
warnings,
|
|
443
|
+
}, () => {
|
|
444
|
+
// AC: @trait-dry-run ac-3 - Clear indication this is a preview
|
|
445
|
+
if (dryRun) {
|
|
446
|
+
console.log(chalk.yellow("DRY RUN - No changes made"));
|
|
447
|
+
console.log();
|
|
448
|
+
}
|
|
449
|
+
// AC: @multi-platform-render-cli ac-7 - Show warnings for unregistered platforms
|
|
450
|
+
if (warnings.length > 0) {
|
|
451
|
+
console.log(chalk.yellow("Warnings:"));
|
|
452
|
+
for (const warning of warnings) {
|
|
453
|
+
console.log(` ${chalk.yellow("!")} ${warning}`);
|
|
454
|
+
}
|
|
455
|
+
console.log();
|
|
456
|
+
}
|
|
457
|
+
// Render results
|
|
458
|
+
const created = results.filter((r) => r.action === "created");
|
|
459
|
+
const updated = results.filter((r) => r.action === "updated");
|
|
460
|
+
const unchanged = results.filter((r) => r.action === "unchanged");
|
|
461
|
+
// AC: @skill-drift-detection ac-3 - Track skipped drifted skills
|
|
462
|
+
const skippedDrifted = results.filter((r) => r.action === "skipped" && r.skipReason?.includes("drifted"));
|
|
463
|
+
const skippedUnregistered = results.filter((r) => r.action === "skipped" && r.skipReason?.includes("unregistered"));
|
|
464
|
+
// AC: @multi-platform-render-cli ac-6 - Include Platform column in output
|
|
465
|
+
if (created.length > 0) {
|
|
466
|
+
console.log(chalk.green(`Created: ${created.length} skill(s)`));
|
|
467
|
+
for (const r of created) {
|
|
468
|
+
console.log(` ${chalk.green("+")} ${r.id} ${chalk.gray(`[${r.platform}]`)}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (updated.length > 0) {
|
|
472
|
+
console.log(chalk.blue(`Updated: ${updated.length} skill(s)`));
|
|
473
|
+
for (const r of updated) {
|
|
474
|
+
console.log(` ${chalk.blue("~")} ${r.id} ${chalk.gray(`[${r.platform}]`)}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (unchanged.length > 0 && results.length <= 10) {
|
|
478
|
+
console.log(chalk.gray(`Unchanged: ${unchanged.length} skill(s)`));
|
|
479
|
+
}
|
|
480
|
+
// AC: @skill-drift-detection ac-3 - Show warning for skipped drifted skills
|
|
481
|
+
if (skippedDrifted.length > 0) {
|
|
482
|
+
console.log();
|
|
483
|
+
console.log(chalk.yellow(`Skipped: ${skippedDrifted.length} drifted skill(s)`));
|
|
484
|
+
for (const r of skippedDrifted) {
|
|
485
|
+
console.log(` ${chalk.yellow("!")} ${r.id} ${chalk.gray(`[${r.platform}]`)}: ${r.skipReason || "drifted"}`);
|
|
486
|
+
}
|
|
487
|
+
console.log();
|
|
488
|
+
console.log(chalk.yellow("Use --force to overwrite drifted skills"));
|
|
489
|
+
}
|
|
490
|
+
// Clean results
|
|
491
|
+
if (cleanResults.length > 0) {
|
|
492
|
+
const removed = cleanResults.filter((r) => r.action === "removed");
|
|
493
|
+
const skipped = cleanResults.filter((r) => r.action === "skipped");
|
|
494
|
+
if (removed.length > 0) {
|
|
495
|
+
console.log();
|
|
496
|
+
console.log(chalk.red(`Removed: ${removed.length} orphaned skill(s)`));
|
|
497
|
+
for (const r of removed) {
|
|
498
|
+
// AC: @multi-platform-render-cli ac-6 - Platform column in clean output
|
|
499
|
+
console.log(` ${chalk.red("-")} ${r.id} ${chalk.gray(`[${r.platform}]`)}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (skipped.length > 0) {
|
|
503
|
+
console.log(chalk.gray(`Skipped: ${skipped.length} unmanaged skill(s)`));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Summary
|
|
507
|
+
console.log();
|
|
508
|
+
if (dryRun) {
|
|
509
|
+
console.log(chalk.yellow("No changes were made. Run without --dry-run to apply."));
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
const changedCount = created.length + updated.length;
|
|
513
|
+
const cleanedCount = cleanResults.filter((r) => r.action === "removed").length;
|
|
514
|
+
if (changedCount > 0 || cleanedCount > 0) {
|
|
515
|
+
success(`Rendered ${changedCount} skill(s)${cleanedCount > 0 ? `, cleaned ${cleanedCount}` : ""}`);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
console.log(chalk.gray("No changes needed"));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
catch (err) {
|
|
524
|
+
error("Failed to render skills", err);
|
|
525
|
+
process.exit(EXIT_CODES.ERROR);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
// AC: @skill-render-cli ac-3 - kspec skill status
|
|
529
|
+
// AC: @multi-platform-render-cli ac-3 - Per-platform status rows
|
|
530
|
+
skill
|
|
531
|
+
.command("status")
|
|
532
|
+
.description("Show sync status of rendered skills")
|
|
533
|
+
.action(async () => {
|
|
534
|
+
try {
|
|
535
|
+
const ctx = await initContext();
|
|
536
|
+
if (!ctx.manifestPath) {
|
|
537
|
+
error(errors.project.noKspecProject);
|
|
538
|
+
console.log(chalk.gray("Try: kspec init to initialize a kspec project"));
|
|
539
|
+
process.exit(EXIT_CODES.ERROR);
|
|
540
|
+
}
|
|
541
|
+
const metaCtx = await loadMetaContext(ctx);
|
|
542
|
+
const projectRoot = ctx.rootDir;
|
|
543
|
+
// Check all skills (not just claude-code)
|
|
544
|
+
const skillsToCheck = metaCtx.skills;
|
|
545
|
+
if (skillsToCheck.length === 0) {
|
|
546
|
+
console.log(chalk.yellow("No skills found"));
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
// AC: @multi-platform-render-cli ac-3 - Build status per skill-platform pair
|
|
550
|
+
const statusResults = [];
|
|
551
|
+
for (const skill of skillsToCheck) {
|
|
552
|
+
for (const platform of skill.platforms) {
|
|
553
|
+
const renderer = getRenderer(platform);
|
|
554
|
+
if (!renderer) {
|
|
555
|
+
// Unregistered platform - show as not-rendered
|
|
556
|
+
statusResults.push({
|
|
557
|
+
id: skill.id,
|
|
558
|
+
platform,
|
|
559
|
+
status: "not-rendered",
|
|
560
|
+
docsStatus: "no-docs",
|
|
561
|
+
warning: `unregistered platform '${platform}'`,
|
|
562
|
+
});
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const status = await getMultiPlatformSyncStatus(ctx, projectRoot, skill, renderer);
|
|
566
|
+
statusResults.push(status);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
// Output results
|
|
570
|
+
output(statusResults, () => {
|
|
571
|
+
// AC: @multi-platform-render-cli ac-3 - Table shows Platform column
|
|
572
|
+
const table = new Table({
|
|
573
|
+
head: [
|
|
574
|
+
chalk.bold("ID"),
|
|
575
|
+
chalk.bold("Platform"),
|
|
576
|
+
chalk.bold("Status"),
|
|
577
|
+
chalk.bold("Docs"),
|
|
578
|
+
],
|
|
579
|
+
style: {
|
|
580
|
+
head: [],
|
|
581
|
+
border: [],
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
for (const result of statusResults) {
|
|
585
|
+
const statusColor = result.status === "in-sync"
|
|
586
|
+
? chalk.green
|
|
587
|
+
: result.status === "drifted"
|
|
588
|
+
? chalk.yellow
|
|
589
|
+
: result.status === "plugin-provided"
|
|
590
|
+
? chalk.cyan
|
|
591
|
+
: chalk.gray;
|
|
592
|
+
const docsStatusColor = result.docsStatus === "in-sync"
|
|
593
|
+
? chalk.green
|
|
594
|
+
: result.docsStatus === "drifted"
|
|
595
|
+
? chalk.yellow
|
|
596
|
+
: chalk.gray;
|
|
597
|
+
table.push([
|
|
598
|
+
result.id,
|
|
599
|
+
result.platform,
|
|
600
|
+
statusColor(result.status + (result.warning ? " (!)" : "")),
|
|
601
|
+
docsStatusColor(result.docsStatus || "-"),
|
|
602
|
+
]);
|
|
603
|
+
}
|
|
604
|
+
console.log(table.toString());
|
|
605
|
+
// Summary
|
|
606
|
+
const inSync = statusResults.filter((r) => r.status === "in-sync").length;
|
|
607
|
+
const drifted = statusResults.filter((r) => r.status === "drifted").length;
|
|
608
|
+
const notRendered = statusResults.filter((r) => r.status === "not-rendered").length;
|
|
609
|
+
const pluginProvided = statusResults.filter((r) => r.status === "plugin-provided").length;
|
|
610
|
+
const warningCount = statusResults.filter((r) => r.warning).length;
|
|
611
|
+
console.log();
|
|
612
|
+
if (drifted > 0) {
|
|
613
|
+
console.log(chalk.yellow(`${drifted} skill(s) drifted - run 'kspec skill render' to sync`));
|
|
614
|
+
}
|
|
615
|
+
if (notRendered > 0) {
|
|
616
|
+
console.log(chalk.gray(`${notRendered} skill(s) not rendered`));
|
|
617
|
+
}
|
|
618
|
+
if (pluginProvided > 0) {
|
|
619
|
+
console.log(chalk.cyan(`${pluginProvided} skill(s) plugin-provided`));
|
|
620
|
+
}
|
|
621
|
+
if (warningCount > 0) {
|
|
622
|
+
console.log(chalk.yellow(`${warningCount} skill(s) with warnings`));
|
|
623
|
+
}
|
|
624
|
+
if (inSync + pluginProvided === statusResults.length) {
|
|
625
|
+
console.log(chalk.green("All skills in sync"));
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
catch (err) {
|
|
630
|
+
error("Failed to check skill status", err);
|
|
631
|
+
process.exit(EXIT_CODES.ERROR);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
// AC: @skill-render-cli ac-4 - kspec skill diff
|
|
635
|
+
skill
|
|
636
|
+
.command("diff <ref>")
|
|
637
|
+
.description("Show diff between source and rendered skill")
|
|
638
|
+
.action(async (ref) => {
|
|
639
|
+
try {
|
|
640
|
+
const ctx = await initContext();
|
|
641
|
+
if (!ctx.manifestPath) {
|
|
642
|
+
error(errors.project.noKspecProject);
|
|
643
|
+
console.log(chalk.gray("Try: kspec init to initialize a kspec project"));
|
|
644
|
+
process.exit(EXIT_CODES.ERROR);
|
|
645
|
+
}
|
|
646
|
+
const metaCtx = await loadMetaContext(ctx);
|
|
647
|
+
const item = findMetaItemByRef(metaCtx, ref);
|
|
648
|
+
if (!item) {
|
|
649
|
+
error(`Skill not found: ${ref}`);
|
|
650
|
+
console.log(chalk.gray("Try: kspec skill list"));
|
|
651
|
+
process.exit(EXIT_CODES.NOT_FOUND);
|
|
652
|
+
}
|
|
653
|
+
// Check it's a skill (uses _type discriminant)
|
|
654
|
+
if (!isSkill(item)) {
|
|
655
|
+
error(`Item ${ref} is not a skill`);
|
|
656
|
+
process.exit(EXIT_CODES.ERROR);
|
|
657
|
+
}
|
|
658
|
+
const skill = item;
|
|
659
|
+
const projectRoot = ctx.rootDir;
|
|
660
|
+
// Core skills on claude-code are plugin-provided
|
|
661
|
+
if (skill.origin === "core") {
|
|
662
|
+
output({
|
|
663
|
+
id: skill.id,
|
|
664
|
+
hasDiff: false,
|
|
665
|
+
diff: [],
|
|
666
|
+
pluginProvided: true,
|
|
667
|
+
}, () => {
|
|
668
|
+
console.log(chalk.blue(`${skill.id}: plugin-provided (no local render to diff)`));
|
|
669
|
+
});
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
// Get expected rendered content
|
|
673
|
+
const expectedContent = await getExpectedRenderedContent(ctx, skill);
|
|
674
|
+
// Get actual rendered content
|
|
675
|
+
const renderedPath = path.join(projectRoot, ".claude/skills", skill.id, "SKILL.md");
|
|
676
|
+
let actualContent = "";
|
|
677
|
+
try {
|
|
678
|
+
actualContent = await fs.readFile(renderedPath, "utf-8");
|
|
679
|
+
}
|
|
680
|
+
catch {
|
|
681
|
+
// File doesn't exist
|
|
682
|
+
}
|
|
683
|
+
// Generate diff
|
|
684
|
+
const diff = generateUnifiedDiff(actualContent, expectedContent, `a/${skill.id}/SKILL.md`, `b/${skill.id}/SKILL.md`);
|
|
685
|
+
// Output
|
|
686
|
+
output({
|
|
687
|
+
id: skill.id,
|
|
688
|
+
hasDiff: diff.length > 0,
|
|
689
|
+
diff,
|
|
690
|
+
}, () => {
|
|
691
|
+
if (diff.length === 0) {
|
|
692
|
+
console.log(chalk.green(`${skill.id}: in sync`));
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
console.log(chalk.yellow(`${skill.id}: drifted`));
|
|
696
|
+
console.log();
|
|
697
|
+
// Print colored diff
|
|
698
|
+
for (const line of diff) {
|
|
699
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
700
|
+
console.log(chalk.green(line));
|
|
701
|
+
}
|
|
702
|
+
else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
703
|
+
console.log(chalk.red(line));
|
|
704
|
+
}
|
|
705
|
+
else if (line.startsWith("@@")) {
|
|
706
|
+
console.log(chalk.cyan(line));
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
console.log(line);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
catch (err) {
|
|
716
|
+
error("Failed to generate diff", err);
|
|
717
|
+
process.exit(EXIT_CODES.ERROR);
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
// AC: @skill-drift-detection-improvements ac-2 - kspec skill verify
|
|
721
|
+
skill
|
|
722
|
+
.command("verify")
|
|
723
|
+
.description("Verify rendered skills match their source (reports drift with guidance)")
|
|
724
|
+
.option("--json", "Output as JSON")
|
|
725
|
+
.action(async (options) => {
|
|
726
|
+
try {
|
|
727
|
+
const ctx = await initContext();
|
|
728
|
+
if (!ctx.manifestPath) {
|
|
729
|
+
error(errors.project.noKspecProject);
|
|
730
|
+
console.log(chalk.gray("Try: kspec init to initialize a kspec project"));
|
|
731
|
+
process.exit(EXIT_CODES.ERROR);
|
|
732
|
+
}
|
|
733
|
+
const metaCtx = await loadMetaContext(ctx);
|
|
734
|
+
const projectRoot = ctx.rootDir;
|
|
735
|
+
const skillsToCheck = metaCtx.skills;
|
|
736
|
+
if (skillsToCheck.length === 0) {
|
|
737
|
+
output([], () => {
|
|
738
|
+
console.log(chalk.yellow("No skills found"));
|
|
739
|
+
});
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const results = [];
|
|
743
|
+
for (const skill of skillsToCheck) {
|
|
744
|
+
for (const platform of skill.platforms) {
|
|
745
|
+
const renderer = getRenderer(platform);
|
|
746
|
+
if (!renderer) {
|
|
747
|
+
results.push({
|
|
748
|
+
id: skill.id,
|
|
749
|
+
platform,
|
|
750
|
+
status: "not-rendered",
|
|
751
|
+
guidance: `Unregistered platform '${platform}'. No renderer available.`,
|
|
752
|
+
});
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
const driftStatus = await renderer.checkDrift(ctx.specDir, projectRoot, skill.id, { origin: skill.origin });
|
|
756
|
+
switch (driftStatus) {
|
|
757
|
+
case "in-sync":
|
|
758
|
+
results.push({ id: skill.id, platform, status: "ok" });
|
|
759
|
+
break;
|
|
760
|
+
case "drifted":
|
|
761
|
+
results.push({
|
|
762
|
+
id: skill.id,
|
|
763
|
+
platform,
|
|
764
|
+
status: "drifted",
|
|
765
|
+
guidance: `Rendered file has been modified. Run 'kspec skill render ${skill.id} --force' to overwrite with source, or 'kspec skill diff ${skill.id}' to review changes.`,
|
|
766
|
+
});
|
|
767
|
+
break;
|
|
768
|
+
case "not-rendered":
|
|
769
|
+
results.push({
|
|
770
|
+
id: skill.id,
|
|
771
|
+
platform,
|
|
772
|
+
status: "not-rendered",
|
|
773
|
+
guidance: `Not yet rendered. Run 'kspec skill render ${skill.id}' to generate.`,
|
|
774
|
+
});
|
|
775
|
+
break;
|
|
776
|
+
case "no-hash":
|
|
777
|
+
results.push({
|
|
778
|
+
id: skill.id,
|
|
779
|
+
platform,
|
|
780
|
+
status: "no-hash",
|
|
781
|
+
guidance: `No render hash stored. Run 'kspec skill render ${skill.id}' to render and store hash.`,
|
|
782
|
+
});
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
const driftedResults = results.filter((r) => r.status === "drifted");
|
|
788
|
+
const okResults = results.filter((r) => r.status === "ok");
|
|
789
|
+
const notRenderedResults = results.filter((r) => r.status === "not-rendered");
|
|
790
|
+
const noHashResults = results.filter((r) => r.status === "no-hash");
|
|
791
|
+
output(results, () => {
|
|
792
|
+
if (driftedResults.length === 0 && notRenderedResults.length === 0 && noHashResults.length === 0) {
|
|
793
|
+
console.log(chalk.green(`All ${okResults.length} rendered skill(s) verified — no drift detected.`));
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
// Show drifted skills with guidance
|
|
797
|
+
if (driftedResults.length > 0) {
|
|
798
|
+
console.log(chalk.yellow.bold(`\nDrifted (${driftedResults.length}):`));
|
|
799
|
+
for (const r of driftedResults) {
|
|
800
|
+
console.log(` ${chalk.yellow("●")} ${r.id} [${r.platform}]`);
|
|
801
|
+
console.log(` ${chalk.gray(r.guidance)}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
// Show not-rendered
|
|
805
|
+
if (notRenderedResults.length > 0) {
|
|
806
|
+
console.log(chalk.gray.bold(`\nNot rendered (${notRenderedResults.length}):`));
|
|
807
|
+
for (const r of notRenderedResults) {
|
|
808
|
+
console.log(` ${chalk.gray("○")} ${r.id} [${r.platform}]`);
|
|
809
|
+
console.log(` ${chalk.gray(r.guidance)}`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
// Show no-hash
|
|
813
|
+
if (noHashResults.length > 0) {
|
|
814
|
+
console.log(chalk.gray.bold(`\nNo hash (${noHashResults.length}):`));
|
|
815
|
+
for (const r of noHashResults) {
|
|
816
|
+
console.log(` ${chalk.gray("○")} ${r.id} [${r.platform}]`);
|
|
817
|
+
console.log(` ${chalk.gray(r.guidance)}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
// Summary line
|
|
821
|
+
if (okResults.length > 0) {
|
|
822
|
+
console.log(chalk.green(`\n${okResults.length} skill(s) verified OK.`));
|
|
823
|
+
}
|
|
824
|
+
// Actionable summary
|
|
825
|
+
if (driftedResults.length > 0) {
|
|
826
|
+
console.log(chalk.yellow(`\nTo sync all drifted skills: kspec skill render --force`));
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
// Exit with non-zero if any skills drifted
|
|
830
|
+
if (driftedResults.length > 0) {
|
|
831
|
+
process.exit(EXIT_CODES.ERROR);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
catch (err) {
|
|
835
|
+
error("Failed to verify skills", err);
|
|
836
|
+
process.exit(EXIT_CODES.ERROR);
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
//# sourceMappingURL=skill-diff.js.map
|