@kynetic-ai/spec 0.1.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -17
- package/dist/acp/client.d.ts +18 -4
- package/dist/acp/client.d.ts.map +1 -1
- package/dist/acp/client.js +44 -26
- package/dist/acp/client.js.map +1 -1
- package/dist/acp/framing.d.ts +2 -2
- package/dist/acp/framing.d.ts.map +1 -1
- package/dist/acp/framing.js +37 -29
- package/dist/acp/framing.js.map +1 -1
- package/dist/acp/index.d.ts +6 -7
- package/dist/acp/index.d.ts.map +1 -1
- package/dist/acp/index.js +3 -3
- package/dist/acp/index.js.map +1 -1
- package/dist/acp/types.d.ts +5 -5
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js +18 -18
- package/dist/acp/types.js.map +1 -1
- package/dist/agents/adapters.d.ts.map +1 -1
- package/dist/agents/adapters.js +24 -13
- package/dist/agents/adapters.js.map +1 -1
- package/dist/agents/index.d.ts +2 -2
- package/dist/agents/index.js +2 -2
- package/dist/agents/spawner.d.ts +4 -4
- package/dist/agents/spawner.d.ts.map +1 -1
- package/dist/agents/spawner.js +6 -6
- package/dist/agents/spawner.js.map +1 -1
- package/dist/cli/batch-context.d.ts +43 -0
- package/dist/cli/batch-context.d.ts.map +1 -0
- package/dist/cli/batch-context.js +93 -0
- package/dist/cli/batch-context.js.map +1 -0
- package/dist/cli/batch-exec.d.ts +107 -0
- package/dist/cli/batch-exec.d.ts.map +1 -0
- package/dist/cli/batch-exec.js +706 -0
- package/dist/cli/batch-exec.js.map +1 -0
- package/dist/cli/batch.d.ts +4 -2
- package/dist/cli/batch.d.ts.map +1 -1
- package/dist/cli/batch.js +15 -14
- package/dist/cli/batch.js.map +1 -1
- package/dist/cli/command-annotations.d.ts +23 -0
- package/dist/cli/command-annotations.d.ts.map +1 -0
- package/dist/cli/command-annotations.js +27 -0
- package/dist/cli/command-annotations.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +46 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +377 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/commands/batch.d.ts +20 -0
- package/dist/cli/commands/batch.d.ts.map +1 -0
- package/dist/cli/commands/batch.js +214 -0
- package/dist/cli/commands/batch.js.map +1 -0
- package/dist/cli/commands/clone-for-testing.d.ts +1 -1
- package/dist/cli/commands/clone-for-testing.d.ts.map +1 -1
- package/dist/cli/commands/clone-for-testing.js +37 -47
- package/dist/cli/commands/clone-for-testing.js.map +1 -1
- package/dist/cli/commands/derive.d.ts +1 -1
- package/dist/cli/commands/derive.d.ts.map +1 -1
- package/dist/cli/commands/derive.js +141 -88
- package/dist/cli/commands/derive.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +11 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +152 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/export.d.ts +12 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +134 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/help.d.ts +1 -1
- package/dist/cli/commands/help.d.ts.map +1 -1
- package/dist/cli/commands/help.js +163 -37
- package/dist/cli/commands/help.js.map +1 -1
- package/dist/cli/commands/inbox.d.ts +1 -1
- package/dist/cli/commands/inbox.d.ts.map +1 -1
- package/dist/cli/commands/inbox.js +178 -56
- package/dist/cli/commands/inbox.js.map +1 -1
- package/dist/cli/commands/index.d.ts +31 -19
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +31 -19
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +5 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +108 -57
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/item.d.ts +1 -1
- package/dist/cli/commands/item.d.ts.map +1 -1
- package/dist/cli/commands/item.js +557 -274
- package/dist/cli/commands/item.js.map +1 -1
- package/dist/cli/commands/link.d.ts +1 -1
- package/dist/cli/commands/link.d.ts.map +1 -1
- package/dist/cli/commands/link.js +55 -46
- package/dist/cli/commands/link.js.map +1 -1
- package/dist/cli/commands/log.d.ts +1 -1
- package/dist/cli/commands/log.d.ts.map +1 -1
- package/dist/cli/commands/log.js +58 -51
- package/dist/cli/commands/log.js.map +1 -1
- package/dist/cli/commands/merge-driver.d.ts +19 -0
- package/dist/cli/commands/merge-driver.d.ts.map +1 -0
- package/dist/cli/commands/merge-driver.js +398 -0
- package/dist/cli/commands/merge-driver.js.map +1 -0
- package/dist/cli/commands/meta.d.ts +1 -1
- package/dist/cli/commands/meta.d.ts.map +1 -1
- package/dist/cli/commands/meta.js +534 -399
- package/dist/cli/commands/meta.js.map +1 -1
- package/dist/cli/commands/module.d.ts +1 -1
- package/dist/cli/commands/module.d.ts.map +1 -1
- package/dist/cli/commands/module.js +30 -25
- package/dist/cli/commands/module.js.map +1 -1
- package/dist/cli/commands/plan-import.d.ts +11 -0
- package/dist/cli/commands/plan-import.d.ts.map +1 -0
- package/dist/cli/commands/plan-import.js +547 -0
- package/dist/cli/commands/plan-import.js.map +1 -0
- package/dist/cli/commands/plan.d.ts +10 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +421 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/ralph.d.ts +1 -1
- package/dist/cli/commands/ralph.d.ts.map +1 -1
- package/dist/cli/commands/ralph.js +1109 -170
- package/dist/cli/commands/ralph.js.map +1 -1
- package/dist/cli/commands/refs.d.ts +13 -0
- package/dist/cli/commands/refs.d.ts.map +1 -0
- package/dist/cli/commands/refs.js +283 -0
- package/dist/cli/commands/refs.js.map +1 -0
- package/dist/cli/commands/search.d.ts +1 -1
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/search.js +199 -37
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/serve.d.ts +10 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +491 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/session.d.ts +25 -6
- package/dist/cli/commands/session.d.ts.map +1 -1
- package/dist/cli/commands/session.js +810 -127
- package/dist/cli/commands/session.js.map +1 -1
- package/dist/cli/commands/setup-seeding.d.ts +81 -0
- package/dist/cli/commands/setup-seeding.d.ts.map +1 -0
- package/dist/cli/commands/setup-seeding.js +292 -0
- package/dist/cli/commands/setup-seeding.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +77 -3
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +1267 -274
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/shadow.d.ts +1 -1
- package/dist/cli/commands/shadow.d.ts.map +1 -1
- package/dist/cli/commands/shadow.js +70 -50
- package/dist/cli/commands/shadow.js.map +1 -1
- package/dist/cli/commands/skill-crud.d.ts +58 -0
- package/dist/cli/commands/skill-crud.d.ts.map +1 -0
- package/dist/cli/commands/skill-crud.js +753 -0
- package/dist/cli/commands/skill-crud.js.map +1 -0
- package/dist/cli/commands/skill-diff.d.ts +27 -0
- package/dist/cli/commands/skill-diff.d.ts.map +1 -0
- package/dist/cli/commands/skill-diff.js +840 -0
- package/dist/cli/commands/skill-diff.js.map +1 -0
- package/dist/cli/commands/skill-install.d.ts +56 -0
- package/dist/cli/commands/skill-install.d.ts.map +1 -0
- package/dist/cli/commands/skill-install.js +509 -0
- package/dist/cli/commands/skill-install.js.map +1 -0
- package/dist/cli/commands/skill.d.ts +20 -0
- package/dist/cli/commands/skill.d.ts.map +1 -0
- package/dist/cli/commands/skill.js +36 -0
- package/dist/cli/commands/skill.js.map +1 -0
- package/dist/cli/commands/task.d.ts +1 -1
- package/dist/cli/commands/task.d.ts.map +1 -1
- package/dist/cli/commands/task.js +584 -350
- package/dist/cli/commands/task.js.map +1 -1
- package/dist/cli/commands/tasks.d.ts +26 -1
- package/dist/cli/commands/tasks.d.ts.map +1 -1
- package/dist/cli/commands/tasks.js +225 -122
- package/dist/cli/commands/tasks.js.map +1 -1
- package/dist/cli/commands/trait.d.ts +1 -1
- package/dist/cli/commands/trait.d.ts.map +1 -1
- package/dist/cli/commands/trait.js +166 -101
- package/dist/cli/commands/trait.js.map +1 -1
- package/dist/cli/commands/triage.d.ts +7 -0
- package/dist/cli/commands/triage.d.ts.map +1 -0
- package/dist/cli/commands/triage.js +483 -0
- package/dist/cli/commands/triage.js.map +1 -0
- package/dist/cli/commands/util.d.ts +7 -0
- package/dist/cli/commands/util.d.ts.map +1 -0
- package/dist/cli/commands/util.js +30 -0
- package/dist/cli/commands/util.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +1 -1
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/cli/commands/validate.js +264 -83
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/commands/workflow.d.ts +16 -0
- package/dist/cli/commands/workflow.d.ts.map +1 -0
- package/dist/cli/commands/workflow.js +851 -0
- package/dist/cli/commands/workflow.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +7 -0
- package/dist/cli/exit-codes.d.ts.map +1 -1
- package/dist/cli/exit-codes.js +26 -18
- package/dist/cli/exit-codes.js.map +1 -1
- package/dist/cli/help/content.d.ts.map +1 -1
- package/dist/cli/help/content.js +86 -71
- package/dist/cli/help/content.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +131 -19
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/introspection.d.ts +6 -2
- package/dist/cli/introspection.d.ts.map +1 -1
- package/dist/cli/introspection.js +11 -8
- package/dist/cli/introspection.js.map +1 -1
- package/dist/cli/output.d.ts +64 -4
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +237 -85
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/parse-utils.d.ts +21 -0
- package/dist/cli/parse-utils.d.ts.map +1 -0
- package/dist/cli/parse-utils.js +32 -0
- package/dist/cli/parse-utils.js.map +1 -0
- package/dist/cli/pid-utils.d.ts +72 -0
- package/dist/cli/pid-utils.d.ts.map +1 -0
- package/dist/cli/pid-utils.js +174 -0
- package/dist/cli/pid-utils.js.map +1 -0
- package/dist/cli/suggest.d.ts.map +1 -1
- package/dist/cli/suggest.js +1 -2
- package/dist/cli/suggest.js.map +1 -1
- package/dist/cli/validators.d.ts +43 -0
- package/dist/cli/validators.d.ts.map +1 -0
- package/dist/cli/validators.js +84 -0
- package/dist/cli/validators.js.map +1 -0
- package/dist/daemon/index.ts +52 -0
- package/dist/daemon/middleware/project-context.ts +126 -0
- package/dist/daemon/pid.ts +179 -0
- package/dist/daemon/project-context.ts +343 -0
- package/dist/daemon/routes/inbox.ts +164 -0
- package/dist/daemon/routes/items.ts +322 -0
- package/dist/daemon/routes/meta.ts +118 -0
- package/dist/daemon/routes/projects.ts +162 -0
- package/dist/daemon/routes/tasks.ts +327 -0
- package/dist/daemon/routes/triage.ts +402 -0
- package/dist/daemon/routes/validation.ts +248 -0
- package/dist/daemon/server.ts +408 -0
- package/dist/daemon/watcher.ts +195 -0
- package/dist/daemon/websocket/handler.ts +138 -0
- package/dist/daemon/websocket/heartbeat.ts +71 -0
- package/dist/daemon/websocket/pubsub.ts +125 -0
- package/dist/daemon/websocket/types.ts +66 -0
- package/dist/export/html.d.ts +19 -0
- package/dist/export/html.d.ts.map +1 -0
- package/dist/export/html.js +239 -0
- package/dist/export/html.js.map +1 -0
- package/dist/export/index.d.ts +10 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +10 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/json.d.ts +24 -0
- package/dist/export/json.d.ts.map +1 -0
- package/dist/export/json.js +198 -0
- package/dist/export/json.js.map +1 -0
- package/dist/export/triage.d.ts +51 -0
- package/dist/export/triage.d.ts.map +1 -0
- package/dist/export/triage.js +83 -0
- package/dist/export/triage.js.map +1 -0
- package/dist/export/types.d.ts +122 -0
- package/dist/export/types.d.ts.map +1 -0
- package/dist/export/types.js +9 -0
- package/dist/export/types.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/lib/claude-plugin-registry.d.ts +66 -0
- package/dist/lib/claude-plugin-registry.d.ts.map +1 -0
- package/dist/lib/claude-plugin-registry.js +318 -0
- package/dist/lib/claude-plugin-registry.js.map +1 -0
- package/dist/merge/arrays.d.ts +87 -0
- package/dist/merge/arrays.d.ts.map +1 -0
- package/dist/merge/arrays.js +164 -0
- package/dist/merge/arrays.js.map +1 -0
- package/dist/merge/file-type.d.ts +32 -0
- package/dist/merge/file-type.d.ts.map +1 -0
- package/dist/merge/file-type.js +70 -0
- package/dist/merge/file-type.js.map +1 -0
- package/dist/merge/index.d.ts +14 -0
- package/dist/merge/index.d.ts.map +1 -0
- package/dist/merge/index.js +11 -0
- package/dist/merge/index.js.map +1 -0
- package/dist/merge/objects.d.ts +46 -0
- package/dist/merge/objects.d.ts.map +1 -0
- package/dist/merge/objects.js +193 -0
- package/dist/merge/objects.js.map +1 -0
- package/dist/merge/parse.d.ts +23 -0
- package/dist/merge/parse.d.ts.map +1 -0
- package/dist/merge/parse.js +78 -0
- package/dist/merge/parse.js.map +1 -0
- package/dist/merge/resolve.d.ts +66 -0
- package/dist/merge/resolve.d.ts.map +1 -0
- package/dist/merge/resolve.js +189 -0
- package/dist/merge/resolve.js.map +1 -0
- package/dist/merge/types.d.ts +82 -0
- package/dist/merge/types.d.ts.map +1 -0
- package/dist/merge/types.js +8 -0
- package/dist/merge/types.js.map +1 -0
- package/dist/parser/agent-data-sections.d.ts +53 -0
- package/dist/parser/agent-data-sections.d.ts.map +1 -0
- package/dist/parser/agent-data-sections.js +118 -0
- package/dist/parser/agent-data-sections.js.map +1 -0
- package/dist/parser/alignment.d.ts +4 -4
- package/dist/parser/alignment.d.ts.map +1 -1
- package/dist/parser/alignment.js +27 -22
- package/dist/parser/alignment.js.map +1 -1
- package/dist/parser/assess.d.ts +5 -5
- package/dist/parser/assess.d.ts.map +1 -1
- package/dist/parser/assess.js +36 -32
- package/dist/parser/assess.js.map +1 -1
- package/dist/parser/config.d.ts +457 -0
- package/dist/parser/config.d.ts.map +1 -0
- package/dist/parser/config.js +373 -0
- package/dist/parser/config.js.map +1 -0
- package/dist/parser/convention-validation.d.ts +1 -1
- package/dist/parser/convention-validation.d.ts.map +1 -1
- package/dist/parser/convention-validation.js +21 -16
- package/dist/parser/convention-validation.js.map +1 -1
- package/dist/parser/coverage-cache.d.ts +49 -0
- package/dist/parser/coverage-cache.d.ts.map +1 -0
- package/dist/parser/coverage-cache.js +123 -0
- package/dist/parser/coverage-cache.js.map +1 -0
- package/dist/parser/daemon-status.d.ts +37 -0
- package/dist/parser/daemon-status.d.ts.map +1 -0
- package/dist/parser/daemon-status.js +67 -0
- package/dist/parser/daemon-status.js.map +1 -0
- package/dist/parser/doctor.d.ts +107 -0
- package/dist/parser/doctor.d.ts.map +1 -0
- package/dist/parser/doctor.js +366 -0
- package/dist/parser/doctor.js.map +1 -0
- package/dist/parser/fix.d.ts +1 -1
- package/dist/parser/fix.d.ts.map +1 -1
- package/dist/parser/fix.js +31 -27
- package/dist/parser/fix.js.map +1 -1
- package/dist/parser/index.d.ts +16 -11
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +16 -11
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/items.d.ts +8 -2
- package/dist/parser/items.d.ts.map +1 -1
- package/dist/parser/items.js +71 -35
- package/dist/parser/items.js.map +1 -1
- package/dist/parser/meta.d.ts +167 -9
- package/dist/parser/meta.d.ts.map +1 -1
- package/dist/parser/meta.js +379 -46
- package/dist/parser/meta.js.map +1 -1
- package/dist/parser/plan-document.d.ts +197 -0
- package/dist/parser/plan-document.d.ts.map +1 -0
- package/dist/parser/plan-document.js +341 -0
- package/dist/parser/plan-document.js.map +1 -0
- package/dist/parser/plans.d.ts +59 -0
- package/dist/parser/plans.d.ts.map +1 -0
- package/dist/parser/plans.js +239 -0
- package/dist/parser/plans.js.map +1 -0
- package/dist/parser/refs.d.ts +22 -9
- package/dist/parser/refs.d.ts.map +1 -1
- package/dist/parser/refs.js +102 -50
- package/dist/parser/refs.js.map +1 -1
- package/dist/parser/setup-status.d.ts +71 -0
- package/dist/parser/setup-status.d.ts.map +1 -0
- package/dist/parser/setup-status.js +269 -0
- package/dist/parser/setup-status.js.map +1 -0
- package/dist/parser/shadow.d.ts +150 -19
- package/dist/parser/shadow.d.ts.map +1 -1
- package/dist/parser/shadow.js +548 -187
- package/dist/parser/shadow.js.map +1 -1
- package/dist/parser/skill-render.d.ts +317 -0
- package/dist/parser/skill-render.d.ts.map +1 -0
- package/dist/parser/skill-render.js +943 -0
- package/dist/parser/skill-render.js.map +1 -0
- package/dist/parser/traits.d.ts +3 -3
- package/dist/parser/traits.d.ts.map +1 -1
- package/dist/parser/traits.js +2 -2
- package/dist/parser/traits.js.map +1 -1
- package/dist/parser/validate-skills.d.ts +32 -0
- package/dist/parser/validate-skills.d.ts.map +1 -0
- package/dist/parser/validate-skills.js +202 -0
- package/dist/parser/validate-skills.js.map +1 -0
- package/dist/parser/validate.d.ts +45 -3
- package/dist/parser/validate.d.ts.map +1 -1
- package/dist/parser/validate.js +622 -105
- package/dist/parser/validate.js.map +1 -1
- package/dist/parser/yaml.d.ts +83 -19
- package/dist/parser/yaml.d.ts.map +1 -1
- package/dist/parser/yaml.js +478 -173
- package/dist/parser/yaml.js.map +1 -1
- package/dist/ralph/cli-renderer.d.ts +8 -1
- package/dist/ralph/cli-renderer.d.ts.map +1 -1
- package/dist/ralph/cli-renderer.js +105 -34
- package/dist/ralph/cli-renderer.js.map +1 -1
- package/dist/ralph/events.d.ts +10 -10
- package/dist/ralph/events.d.ts.map +1 -1
- package/dist/ralph/events.js +301 -98
- package/dist/ralph/events.js.map +1 -1
- package/dist/ralph/index.d.ts +5 -2
- package/dist/ralph/index.d.ts.map +1 -1
- package/dist/ralph/index.js +9 -3
- package/dist/ralph/index.js.map +1 -1
- package/dist/ralph/loop-errors.d.ts +83 -0
- package/dist/ralph/loop-errors.d.ts.map +1 -0
- package/dist/ralph/loop-errors.js +150 -0
- package/dist/ralph/loop-errors.js.map +1 -0
- package/dist/ralph/subagent.d.ts +94 -0
- package/dist/ralph/subagent.d.ts.map +1 -0
- package/dist/ralph/subagent.js +193 -0
- package/dist/ralph/subagent.js.map +1 -0
- package/dist/ralph/wrap-up.d.ts +125 -0
- package/dist/ralph/wrap-up.d.ts.map +1 -0
- package/dist/ralph/wrap-up.js +270 -0
- package/dist/ralph/wrap-up.js.map +1 -0
- package/dist/schema/batch.d.ts +97 -0
- package/dist/schema/batch.d.ts.map +1 -0
- package/dist/schema/batch.js +24 -0
- package/dist/schema/batch.js.map +1 -0
- package/dist/schema/common.d.ts +8 -2
- package/dist/schema/common.d.ts.map +1 -1
- package/dist/schema/common.js +42 -31
- package/dist/schema/common.js.map +1 -1
- package/dist/schema/inbox.d.ts +12 -12
- package/dist/schema/inbox.js +4 -4
- package/dist/schema/inbox.js.map +1 -1
- package/dist/schema/index.d.ts +8 -5
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +8 -5
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/meta.d.ts +1454 -27
- package/dist/schema/meta.d.ts.map +1 -1
- package/dist/schema/meta.js +198 -21
- package/dist/schema/meta.js.map +1 -1
- package/dist/schema/plan.d.ts +285 -0
- package/dist/schema/plan.d.ts.map +1 -0
- package/dist/schema/plan.js +81 -0
- package/dist/schema/plan.js.map +1 -0
- package/dist/schema/spec.d.ts +72 -33
- package/dist/schema/spec.d.ts.map +1 -1
- package/dist/schema/spec.js +22 -9
- package/dist/schema/spec.js.map +1 -1
- package/dist/schema/task.d.ts +172 -161
- package/dist/schema/task.d.ts.map +1 -1
- package/dist/schema/task.js +21 -12
- package/dist/schema/task.js.map +1 -1
- package/dist/schema/triage.d.ts +266 -0
- package/dist/schema/triage.d.ts.map +1 -0
- package/dist/schema/triage.js +134 -0
- package/dist/schema/triage.js.map +1 -0
- package/dist/sessions/index.d.ts +2 -2
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +3 -3
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/store.d.ts +241 -1
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +810 -31
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +10 -10
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js +10 -9
- package/dist/sessions/types.js.map +1 -1
- package/dist/strings/errors.d.ts +55 -0
- package/dist/strings/errors.d.ts.map +1 -1
- package/dist/strings/errors.js +138 -106
- package/dist/strings/errors.js.map +1 -1
- package/dist/strings/guidance.d.ts.map +1 -1
- package/dist/strings/guidance.js +16 -16
- package/dist/strings/guidance.js.map +1 -1
- package/dist/strings/index.d.ts +4 -4
- package/dist/strings/index.d.ts.map +1 -1
- package/dist/strings/index.js +4 -4
- package/dist/strings/index.js.map +1 -1
- package/dist/strings/labels.d.ts +4 -0
- package/dist/strings/labels.d.ts.map +1 -1
- package/dist/strings/labels.js +45 -41
- package/dist/strings/labels.js.map +1 -1
- package/dist/strings/validation.d.ts.map +1 -1
- package/dist/strings/validation.js +71 -71
- package/dist/strings/validation.js.map +1 -1
- package/dist/triage/actions.d.ts +27 -0
- package/dist/triage/actions.d.ts.map +1 -0
- package/dist/triage/actions.js +95 -0
- package/dist/triage/actions.js.map +1 -0
- package/dist/triage/constants.d.ts +6 -0
- package/dist/triage/constants.d.ts.map +1 -0
- package/dist/triage/constants.js +7 -0
- package/dist/triage/constants.js.map +1 -0
- package/dist/triage/index.d.ts +3 -0
- package/dist/triage/index.d.ts.map +1 -0
- package/dist/triage/index.js +3 -0
- package/dist/triage/index.js.map +1 -0
- package/dist/utils/commit.d.ts +1 -1
- package/dist/utils/commit.d.ts.map +1 -1
- package/dist/utils/commit.js +28 -26
- package/dist/utils/commit.js.map +1 -1
- package/dist/utils/git.d.ts +1 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +40 -38
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/grep.js +11 -11
- package/dist/utils/grep.js.map +1 -1
- package/dist/utils/index.d.ts +7 -7
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +10 -10
- package/dist/utils/time.js.map +1 -1
- package/package.json +28 -5
- package/plugin/.claude-plugin/marketplace.json +17 -0
- package/plugin/.claude-plugin/plugin.json +5 -0
- package/plugin/plugins/kspec/skills/create-workflow/SKILL.md +235 -0
- package/plugin/plugins/kspec/skills/help/SKILL.md +42 -0
- package/plugin/plugins/kspec/skills/observations/SKILL.md +143 -0
- package/plugin/plugins/kspec/skills/plan/SKILL.md +343 -0
- package/plugin/plugins/kspec/skills/reflect/SKILL.md +161 -0
- package/plugin/plugins/kspec/skills/review/SKILL.md +193 -0
- package/plugin/plugins/kspec/skills/task-work/SKILL.md +303 -0
- package/plugin/plugins/kspec/skills/triage/SKILL.md +206 -0
- package/plugin/plugins/kspec/skills/triage/docs/automation.md +120 -0
- package/plugin/plugins/kspec/skills/triage/docs/inbox.md +144 -0
- package/plugin/plugins/kspec/skills/triage/docs/observations.md +85 -0
- package/plugin/plugins/kspec/skills/triage-automation/SKILL.md +140 -0
- package/plugin/plugins/kspec/skills/triage-inbox/SKILL.md +232 -0
- package/plugin/plugins/kspec/skills/writing-specs/SKILL.md +340 -0
- package/templates/agents-sections/01-quick-start.md +22 -0
- package/templates/agents-sections/02-shadow-branch.md +34 -0
- package/templates/agents-sections/03-task-lifecycle.md +48 -0
- package/templates/agents-sections/04-pr-workflow.md +17 -0
- package/templates/agents-sections/05-commit-convention.md +27 -0
- package/templates/agents-sections/06-ralph-loop.md +45 -0
- package/templates/hooks/pre-commit +34 -0
- package/templates/skills/create-workflow/SKILL.md +228 -0
- package/templates/skills/help/SKILL.md +37 -0
- package/templates/skills/manifest.yaml +60 -0
- package/templates/skills/observations/SKILL.md +137 -0
- package/templates/skills/plan/SKILL.md +336 -0
- package/templates/skills/reflect/SKILL.md +155 -0
- package/templates/skills/review/SKILL.md +186 -0
- package/templates/skills/task-work/SKILL.md +296 -0
- package/templates/skills/triage/SKILL.md +199 -0
- package/templates/skills/triage/docs/automation.md +120 -0
- package/templates/skills/triage/docs/inbox.md +144 -0
- package/templates/skills/triage/docs/observations.md +85 -0
- package/templates/skills/triage-automation/SKILL.md +134 -0
- package/templates/skills/triage-inbox/SKILL.md +225 -0
- package/templates/skills/writing-specs/SKILL.md +333 -0
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Skill Renderers
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for platform-specific skill renderers and provides
|
|
5
|
+
* the Claude Code implementation. Each renderer writes platform-specific output
|
|
6
|
+
* (SKILL.md with frontmatter, sidecar config files, supporting directories)
|
|
7
|
+
* to a configurable output directory.
|
|
8
|
+
*
|
|
9
|
+
* AC: @claude-code-renderer ac-1 - renderClaudeCodeSkill creates .claude/skills/<id>/SKILL.md with YAML frontmatter
|
|
10
|
+
* AC: @claude-code-renderer ac-2 - rendered output has YAML frontmatter delimiters with name and description fields
|
|
11
|
+
* AC: @claude-code-renderer ac-3 - skill body content appears verbatim below frontmatter
|
|
12
|
+
* AC: @claude-code-renderer ac-4 - rendered files appear as unstaged changes on main branch
|
|
13
|
+
*
|
|
14
|
+
* AC: @skill-drift-detection ac-1 - Skill shows as in-sync when not manually edited
|
|
15
|
+
* AC: @skill-drift-detection ac-2 - Skill shows as drifted when manually edited
|
|
16
|
+
* AC: @skill-drift-detection ac-5 - Render hash stored in .kspec/skills/<id>/.render-hash
|
|
17
|
+
*
|
|
18
|
+
* AC: @consolidate-skill-render ac-3 - hash/drift functions exported from this module
|
|
19
|
+
*
|
|
20
|
+
* AC: @platform-renderer-trait ac-1 - platform-specific output files written to configured output directory
|
|
21
|
+
* AC: @platform-renderer-trait ac-2 - PlatformRenderResult returned with id, platform, action, paths
|
|
22
|
+
* AC: @platform-renderer-trait ac-3 - supporting directories (references/, scripts/, assets/) copied
|
|
23
|
+
* AC: @platform-renderer-trait ac-4 - dryRun mode: no files written, result reflects what would happen
|
|
24
|
+
* AC: @platform-renderer-trait ac-5 - custom outputDir goes to custom path instead of platform default
|
|
25
|
+
* AC: @platform-renderer-trait ac-6 - per-platform render hash written to .render-hash-<platform>
|
|
26
|
+
*
|
|
27
|
+
* AC: @codex-renderer ac-1 - .agents/skills/<id>/SKILL.md with only name and description in frontmatter
|
|
28
|
+
* AC: @codex-renderer ac-2 - .agents/skills/<id>/agents/openai.yaml sidecar with platform_config.codex fields
|
|
29
|
+
* AC: @codex-renderer ac-3 - no sidecar created when no platform_config.codex
|
|
30
|
+
* AC: @codex-renderer ac-4 - rendered file contains <!-- kspec-managed --> marker
|
|
31
|
+
* AC: @codex-renderer ac-5 - supporting directories (references/, scripts/, assets/) copied
|
|
32
|
+
* AC: @codex-renderer ac-6 - hash written to .render-hash-codex
|
|
33
|
+
*/
|
|
34
|
+
import * as crypto from "node:crypto";
|
|
35
|
+
import * as fs from "node:fs/promises";
|
|
36
|
+
import * as path from "node:path";
|
|
37
|
+
import yaml from "yaml";
|
|
38
|
+
import { loadSkillContent } from "./meta.js";
|
|
39
|
+
/**
|
|
40
|
+
* Marker comment that identifies skill directories managed by kspec
|
|
41
|
+
*/
|
|
42
|
+
export const KSPEC_MANAGED_MARKER = "<!-- kspec-managed -->";
|
|
43
|
+
/**
|
|
44
|
+
* Generate YAML frontmatter for a skill.
|
|
45
|
+
* AC: @claude-code-renderer ac-2 - YAML frontmatter with name and description fields
|
|
46
|
+
* AC: @claude-code-renderer-extended ac-1 - portable fields (license, allowed-tools)
|
|
47
|
+
* AC: @claude-code-renderer-extended ac-2 - user-invocable from platform_config
|
|
48
|
+
* AC: @claude-code-renderer-extended ac-3 - context and agent from platform_config
|
|
49
|
+
* AC: @claude-code-renderer-extended ac-4 - disable-model-invocation from platform_config
|
|
50
|
+
* AC: @claude-code-renderer-extended ac-5 - only portable fields when no platform_config
|
|
51
|
+
* AC: @claude-code-renderer-extended ac-8 - snake_case to kebab-case conversion
|
|
52
|
+
*/
|
|
53
|
+
export function generateFrontmatter(skill) {
|
|
54
|
+
const frontmatter = {
|
|
55
|
+
name: skill.id,
|
|
56
|
+
description: skill.description || skill.name,
|
|
57
|
+
};
|
|
58
|
+
// AC: @claude-code-renderer-extended ac-1 - Add portable fields
|
|
59
|
+
if (skill.license) {
|
|
60
|
+
frontmatter.license = skill.license;
|
|
61
|
+
}
|
|
62
|
+
if (skill.allowed_tools && skill.allowed_tools.length > 0) {
|
|
63
|
+
frontmatter["allowed-tools"] = skill.allowed_tools;
|
|
64
|
+
}
|
|
65
|
+
if (skill.compatibility) {
|
|
66
|
+
frontmatter.compatibility = skill.compatibility;
|
|
67
|
+
}
|
|
68
|
+
// AC: @claude-code-renderer-extended ac-2, ac-3, ac-4 - Add Claude Code platform fields
|
|
69
|
+
const claudeCodeConfig = skill.platform_config?.claude_code;
|
|
70
|
+
if (claudeCodeConfig) {
|
|
71
|
+
// AC: @claude-code-renderer-extended ac-2 - user_invocable
|
|
72
|
+
if (claudeCodeConfig.user_invocable !== undefined) {
|
|
73
|
+
frontmatter["user-invocable"] = claudeCodeConfig.user_invocable;
|
|
74
|
+
}
|
|
75
|
+
// AC: @claude-code-renderer-extended ac-4 - disable_model_invocation
|
|
76
|
+
if (claudeCodeConfig.disable_model_invocation !== undefined) {
|
|
77
|
+
frontmatter["disable-model-invocation"] = claudeCodeConfig.disable_model_invocation;
|
|
78
|
+
}
|
|
79
|
+
// AC: @claude-code-renderer-extended ac-3 - context and agent
|
|
80
|
+
if (claudeCodeConfig.context) {
|
|
81
|
+
frontmatter.context = claudeCodeConfig.context;
|
|
82
|
+
}
|
|
83
|
+
if (claudeCodeConfig.agent) {
|
|
84
|
+
frontmatter.agent = claudeCodeConfig.agent;
|
|
85
|
+
}
|
|
86
|
+
// Other Claude Code fields
|
|
87
|
+
if (claudeCodeConfig.model) {
|
|
88
|
+
frontmatter.model = claudeCodeConfig.model;
|
|
89
|
+
}
|
|
90
|
+
if (claudeCodeConfig.argument_hint) {
|
|
91
|
+
frontmatter["argument-hint"] = claudeCodeConfig.argument_hint;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return `---\n${yaml.stringify(frontmatter).trim()}\n---`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Plugin system constants for Claude Code core skill rendering.
|
|
98
|
+
* Core skills render into the plugin directory so Claude Code discovers
|
|
99
|
+
* them as /kspec:<id> commands.
|
|
100
|
+
* AC: @skill-rendering ac-7
|
|
101
|
+
*/
|
|
102
|
+
export const PLUGIN_DIR = ".claude/plugins/kspec";
|
|
103
|
+
export const PLUGIN_SKILLS_DIR = ".claude/plugins/kspec/skills";
|
|
104
|
+
/**
|
|
105
|
+
* Get the rendered skill subdirectory segment for a given platform.
|
|
106
|
+
* Core skills on codex are prefixed with kspec- for namespace clarity.
|
|
107
|
+
* Core skills on claude-code just use the skill id (plugin path provides namespace).
|
|
108
|
+
* All other combinations use the skill id directly.
|
|
109
|
+
* AC: @skill-rendering ac-7
|
|
110
|
+
*/
|
|
111
|
+
export function getSkillSubdir(skillId, origin, platform) {
|
|
112
|
+
if (origin === "core" && platform === "codex") {
|
|
113
|
+
return `kspec-${skillId}`;
|
|
114
|
+
}
|
|
115
|
+
// Core+claude-code: just ID (plugin path provides namespace)
|
|
116
|
+
// Project/local: just ID (flat under .claude/skills/)
|
|
117
|
+
return skillId;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Convenience wrapper for LoadedSkill objects (Claude Code platform).
|
|
121
|
+
*/
|
|
122
|
+
export function getClaudeCodeSkillSubdir(skill) {
|
|
123
|
+
return getSkillSubdir(skill.id, skill.origin, "claude-code");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get the target directory for rendered skills on main branch.
|
|
127
|
+
* Core skills on claude-code are plugin-provided (returns null).
|
|
128
|
+
* Project/local skills go to .claude/skills/.
|
|
129
|
+
* AC: @skill-rendering ac-7
|
|
130
|
+
*/
|
|
131
|
+
function getRenderedSkillPath(projectRoot, skillId, origin) {
|
|
132
|
+
if (origin === "core") {
|
|
133
|
+
// Core skills are plugin-provided, not locally rendered
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return path.join(projectRoot, ".claude", "skills", skillId);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if two contents are equal (for idempotency check)
|
|
140
|
+
*/
|
|
141
|
+
export function contentsEqual(a, b) {
|
|
142
|
+
return a.trim() === b.trim();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Recursively copy a directory
|
|
146
|
+
*/
|
|
147
|
+
export async function copyDirectory(src, dest) {
|
|
148
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
149
|
+
for (const entry of entries) {
|
|
150
|
+
const srcPath = path.join(src, entry.name);
|
|
151
|
+
const destPath = path.join(dest, entry.name);
|
|
152
|
+
if (entry.isDirectory()) {
|
|
153
|
+
await fs.mkdir(destPath, { recursive: true });
|
|
154
|
+
await copyDirectory(srcPath, destPath);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
await fs.copyFile(srcPath, destPath);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Compute SHA256 hash of content
|
|
163
|
+
* AC: @skill-drift-detection ac-5 - Hash computation for render tracking
|
|
164
|
+
*/
|
|
165
|
+
export function computeContentHash(content) {
|
|
166
|
+
return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get the path to the render hash file for a skill
|
|
170
|
+
* AC: @skill-drift-detection ac-5 - Hash stored in .kspec/skills/<id>/.render-hash
|
|
171
|
+
* AC: @consolidate-skill-render ac-3 - exported from skill-render.ts
|
|
172
|
+
*/
|
|
173
|
+
export function getRenderHashPath(specDir, skillId) {
|
|
174
|
+
return path.join(specDir, "skills", skillId, ".render-hash");
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Read the stored render hash for a skill
|
|
178
|
+
* AC: @skill-drift-detection ac-5 - Read hash from .kspec/skills/<id>/.render-hash
|
|
179
|
+
* AC: @consolidate-skill-render ac-3 - exported from skill-render.ts
|
|
180
|
+
*/
|
|
181
|
+
export async function readRenderHash(specDir, skillId) {
|
|
182
|
+
try {
|
|
183
|
+
const hashPath = getRenderHashPath(specDir, skillId);
|
|
184
|
+
const content = await fs.readFile(hashPath, "utf-8");
|
|
185
|
+
return content.trim();
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Write the render hash for a skill
|
|
193
|
+
* AC: @skill-drift-detection ac-5 - Store hash in .kspec/skills/<id>/.render-hash
|
|
194
|
+
* AC: @consolidate-skill-render ac-3 - exported from skill-render.ts
|
|
195
|
+
*/
|
|
196
|
+
export async function writeRenderHash(specDir, skillId, hash) {
|
|
197
|
+
const hashPath = getRenderHashPath(specDir, skillId);
|
|
198
|
+
await fs.mkdir(path.dirname(hashPath), { recursive: true });
|
|
199
|
+
await fs.writeFile(hashPath, hash + "\n", "utf-8");
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check if a rendered skill has drifted from its last render
|
|
203
|
+
* AC: @skill-drift-detection ac-1, ac-2 - Drift detection via hash comparison
|
|
204
|
+
* AC: @consolidate-skill-render ac-3 - exported from skill-render.ts
|
|
205
|
+
*
|
|
206
|
+
* Returns:
|
|
207
|
+
* - "not-rendered": Rendered file doesn't exist
|
|
208
|
+
* - "in-sync": Rendered file matches stored hash
|
|
209
|
+
* - "drifted": Rendered file differs from stored hash (manually edited)
|
|
210
|
+
* - "no-hash": Rendered file exists but no stored hash (first render or hash deleted)
|
|
211
|
+
*/
|
|
212
|
+
export async function checkSkillDrift(specDir, projectRoot, skillId, origin) {
|
|
213
|
+
const basePath = getRenderedSkillPath(projectRoot, skillId, origin);
|
|
214
|
+
if (!basePath) {
|
|
215
|
+
return "plugin-provided"; // Core skills are provided by npm package plugin
|
|
216
|
+
}
|
|
217
|
+
const renderedPath = path.join(basePath, "SKILL.md");
|
|
218
|
+
// Check if rendered file exists
|
|
219
|
+
let renderedContent;
|
|
220
|
+
try {
|
|
221
|
+
renderedContent = await fs.readFile(renderedPath, "utf-8");
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return "not-rendered";
|
|
225
|
+
}
|
|
226
|
+
// Get stored hash
|
|
227
|
+
const storedHash = await readRenderHash(specDir, skillId);
|
|
228
|
+
if (!storedHash) {
|
|
229
|
+
return "no-hash";
|
|
230
|
+
}
|
|
231
|
+
// Compare hashes
|
|
232
|
+
const currentHash = computeContentHash(renderedContent);
|
|
233
|
+
return currentHash === storedHash ? "in-sync" : "drifted";
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Recursively check if two directories have the same contents
|
|
237
|
+
*/
|
|
238
|
+
export async function directoriesEqual(src, dest) {
|
|
239
|
+
try {
|
|
240
|
+
const srcEntries = await fs.readdir(src, { withFileTypes: true });
|
|
241
|
+
const destEntries = await fs.readdir(dest, { withFileTypes: true });
|
|
242
|
+
// Different number of entries = not equal
|
|
243
|
+
if (srcEntries.length !== destEntries.length) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
// Create a map of dest entries for quick lookup
|
|
247
|
+
const destMap = new Map(destEntries.map((e) => [e.name, e]));
|
|
248
|
+
for (const srcEntry of srcEntries) {
|
|
249
|
+
const destEntry = destMap.get(srcEntry.name);
|
|
250
|
+
if (!destEntry) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
const srcPath = path.join(src, srcEntry.name);
|
|
254
|
+
const destPath = path.join(dest, srcEntry.name);
|
|
255
|
+
if (srcEntry.isDirectory() && destEntry.isDirectory()) {
|
|
256
|
+
if (!(await directoriesEqual(srcPath, destPath))) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else if (srcEntry.isFile() && destEntry.isFile()) {
|
|
261
|
+
const srcContent = await fs.readFile(srcPath, "utf-8");
|
|
262
|
+
const destContent = await fs.readFile(destPath, "utf-8");
|
|
263
|
+
if (!contentsEqual(srcContent, destContent)) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
// Type mismatch
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// Supporting Directories (AC: @platform-renderer-trait ac-3, @claude-code-renderer-extended ac-7)
|
|
280
|
+
// ============================================================================
|
|
281
|
+
/** Known supporting directory names */
|
|
282
|
+
const SUPPORTING_DIRS = ["references", "scripts", "assets", "docs"];
|
|
283
|
+
/**
|
|
284
|
+
* Copy supporting directories from source skill to rendered output
|
|
285
|
+
* AC: @platform-renderer-trait ac-3 - Supporting directories copied to platform output
|
|
286
|
+
* AC: @claude-code-renderer-extended ac-7 - references/, scripts/, assets/ copied
|
|
287
|
+
*/
|
|
288
|
+
async function copySupportingDirectories(sourceSkillDir, targetSkillDir, dryRun) {
|
|
289
|
+
const results = {};
|
|
290
|
+
for (const dirName of SUPPORTING_DIRS) {
|
|
291
|
+
const sourceDir = path.join(sourceSkillDir, dirName);
|
|
292
|
+
const targetDir = path.join(targetSkillDir, dirName);
|
|
293
|
+
try {
|
|
294
|
+
const stats = await fs.stat(sourceDir);
|
|
295
|
+
if (!stats.isDirectory()) {
|
|
296
|
+
results[dirName] = "skipped";
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
// Check if target exists
|
|
300
|
+
let targetExists = false;
|
|
301
|
+
try {
|
|
302
|
+
await fs.stat(targetDir);
|
|
303
|
+
targetExists = true;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// Target doesn't exist
|
|
307
|
+
}
|
|
308
|
+
if (!targetExists) {
|
|
309
|
+
results[dirName] = "created";
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
// Compare directories
|
|
313
|
+
const equal = await directoriesEqual(sourceDir, targetDir);
|
|
314
|
+
results[dirName] = equal ? "unchanged" : "updated";
|
|
315
|
+
}
|
|
316
|
+
// Apply changes
|
|
317
|
+
if (!dryRun && results[dirName] !== "unchanged") {
|
|
318
|
+
if (targetExists) {
|
|
319
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
320
|
+
}
|
|
321
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
322
|
+
await copyDirectory(sourceDir, targetDir);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
// Source directory doesn't exist
|
|
327
|
+
results[dirName] = "skipped";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return results;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Render a skill to Claude Code format.
|
|
334
|
+
*
|
|
335
|
+
* Reads skill content from .kspec/skills/<id>/SKILL.md and writes to
|
|
336
|
+
* .claude/skills/<id>/SKILL.md with YAML frontmatter containing name and description.
|
|
337
|
+
*
|
|
338
|
+
* AC: @claude-code-renderer ac-1 - Creates .claude/skills/<id>/SKILL.md with YAML frontmatter
|
|
339
|
+
* AC: @claude-code-renderer ac-2 - YAML frontmatter has name and description fields
|
|
340
|
+
* AC: @claude-code-renderer ac-3 - Skill body content appears verbatim below frontmatter
|
|
341
|
+
* AC: @claude-code-renderer ac-4 - Files are written to disk as unstaged changes (no git commit)
|
|
342
|
+
*
|
|
343
|
+
* @param ctx - Kspec context with specDir pointing to .kspec/
|
|
344
|
+
* @param projectRoot - Root directory of the project (where .claude/ will be created)
|
|
345
|
+
* @param skill - The skill to render
|
|
346
|
+
* @param options - Render options (dryRun, etc.)
|
|
347
|
+
* @returns Result indicating what action was taken
|
|
348
|
+
*/
|
|
349
|
+
export async function renderClaudeCodeSkill(ctx, projectRoot, skill, options = {}) {
|
|
350
|
+
const dryRun = options.dryRun ?? false;
|
|
351
|
+
const storeHash = options.storeHash ?? false;
|
|
352
|
+
const targetDir = getRenderedSkillPath(projectRoot, skill.id, skill.origin);
|
|
353
|
+
// Core skills are plugin-provided; skip local render
|
|
354
|
+
if (!targetDir) {
|
|
355
|
+
return {
|
|
356
|
+
id: skill.id,
|
|
357
|
+
action: "skipped",
|
|
358
|
+
path: "",
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const targetSkillMd = path.join(targetDir, "SKILL.md");
|
|
362
|
+
// AC: @claude-code-renderer ac-3 - Load source content verbatim
|
|
363
|
+
const sourceContent = await loadSkillContent(ctx, skill);
|
|
364
|
+
if (!sourceContent) {
|
|
365
|
+
// No source content, but skill exists in meta - create placeholder
|
|
366
|
+
const frontmatter = generateFrontmatter(skill);
|
|
367
|
+
const renderedContent = `${frontmatter}\n${KSPEC_MANAGED_MARKER}\n\n# ${skill.name}\n\n${skill.description || ""}\n`;
|
|
368
|
+
// AC: @claude-code-renderer ac-4 - Write to disk (unstaged)
|
|
369
|
+
if (!dryRun) {
|
|
370
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
371
|
+
await fs.writeFile(targetSkillMd, renderedContent, "utf-8");
|
|
372
|
+
// AC: @skill-drift-detection ac-5 - Store hash of rendered output
|
|
373
|
+
if (storeHash) {
|
|
374
|
+
const hash = computeContentHash(renderedContent);
|
|
375
|
+
await writeRenderHash(ctx.specDir, skill.id, hash);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
id: skill.id,
|
|
380
|
+
action: "created",
|
|
381
|
+
path: targetSkillMd,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
// AC: @claude-code-renderer ac-2 - Generate frontmatter with name and description
|
|
385
|
+
const frontmatter = generateFrontmatter(skill);
|
|
386
|
+
// Check if source already has frontmatter - if so, strip it
|
|
387
|
+
const frontmatterMatch = sourceContent.match(/^---\n[\s\S]*?\n---\n?/);
|
|
388
|
+
const contentWithoutFrontmatter = frontmatterMatch
|
|
389
|
+
? sourceContent.slice(frontmatterMatch[0].length)
|
|
390
|
+
: sourceContent;
|
|
391
|
+
// AC: @claude-code-renderer ac-1, ac-3 - Build rendered content with frontmatter + verbatim body
|
|
392
|
+
const renderedContent = `${frontmatter}\n${KSPEC_MANAGED_MARKER}\n${contentWithoutFrontmatter}`;
|
|
393
|
+
// Check if target exists and compare for idempotency
|
|
394
|
+
let targetExists = false;
|
|
395
|
+
let targetContent = "";
|
|
396
|
+
try {
|
|
397
|
+
targetContent = await fs.readFile(targetSkillMd, "utf-8");
|
|
398
|
+
targetExists = true;
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
// Target doesn't exist
|
|
402
|
+
}
|
|
403
|
+
// Determine action based on comparison
|
|
404
|
+
let action;
|
|
405
|
+
if (!targetExists) {
|
|
406
|
+
action = "created";
|
|
407
|
+
}
|
|
408
|
+
else if (contentsEqual(renderedContent, targetContent)) {
|
|
409
|
+
action = "unchanged";
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
action = "updated";
|
|
413
|
+
}
|
|
414
|
+
// AC: @claude-code-renderer ac-4 - Apply changes to disk (no git commit)
|
|
415
|
+
if (!dryRun && action !== "unchanged") {
|
|
416
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
417
|
+
await fs.writeFile(targetSkillMd, renderedContent, "utf-8");
|
|
418
|
+
// AC: @skill-drift-detection ac-5 - Store hash of rendered output
|
|
419
|
+
if (storeHash) {
|
|
420
|
+
const hash = computeContentHash(renderedContent);
|
|
421
|
+
await writeRenderHash(ctx.specDir, skill.id, hash);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// AC: @claude-code-renderer-extended ac-7 - Copy supporting directories (references/, scripts/, assets/, docs/)
|
|
425
|
+
const sourceSkillDir = path.join(ctx.specDir, "skills", skill.id);
|
|
426
|
+
const supportingDirsAction = await copySupportingDirectories(sourceSkillDir, targetDir, dryRun);
|
|
427
|
+
// For backward compatibility, also expose docsAction
|
|
428
|
+
const docsAction = supportingDirsAction.docs || "skipped";
|
|
429
|
+
return {
|
|
430
|
+
id: skill.id,
|
|
431
|
+
action,
|
|
432
|
+
path: targetSkillMd,
|
|
433
|
+
docsAction,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Check if a skill directory is managed by kspec.
|
|
438
|
+
* Looks for the KSPEC_MANAGED_MARKER in the SKILL.md file.
|
|
439
|
+
*/
|
|
440
|
+
export async function isKspecManagedSkill(skillMdPath) {
|
|
441
|
+
try {
|
|
442
|
+
const content = await fs.readFile(skillMdPath, "utf-8");
|
|
443
|
+
return content.includes(KSPEC_MANAGED_MARKER);
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// ============================================================================
|
|
450
|
+
// Per-Platform Hash Functions (AC: @platform-renderer-trait ac-6)
|
|
451
|
+
// ============================================================================
|
|
452
|
+
/**
|
|
453
|
+
* Get the path to the per-platform render hash file for a skill
|
|
454
|
+
* AC: @platform-renderer-trait ac-6 - Per-platform hash stored in .render-hash-<platform>
|
|
455
|
+
*/
|
|
456
|
+
export function getPlatformRenderHashPath(specDir, skillId, platform) {
|
|
457
|
+
return path.join(specDir, "skills", skillId, `.render-hash-${platform}`);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Read the stored render hash for a skill on a specific platform
|
|
461
|
+
* AC: @platform-renderer-trait ac-6 - Read per-platform hash
|
|
462
|
+
* AC: @claude-code-renderer-extended ac-6 - fallback to legacy hash
|
|
463
|
+
*/
|
|
464
|
+
export async function readPlatformRenderHash(specDir, skillId, platform) {
|
|
465
|
+
try {
|
|
466
|
+
const hashPath = getPlatformRenderHashPath(specDir, skillId, platform);
|
|
467
|
+
const content = await fs.readFile(hashPath, "utf-8");
|
|
468
|
+
return content.trim();
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
// Fall back to legacy hash (non-platform-specific)
|
|
472
|
+
return readRenderHash(specDir, skillId);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Migrate legacy .render-hash to platform-specific .render-hash-<platform>
|
|
477
|
+
* AC: @claude-code-renderer-extended ac-6 - hash migration
|
|
478
|
+
*/
|
|
479
|
+
export async function migrateLegacyRenderHash(specDir, skillId, platform) {
|
|
480
|
+
const legacyHashPath = getRenderHashPath(specDir, skillId);
|
|
481
|
+
const platformHashPath = getPlatformRenderHashPath(specDir, skillId, platform);
|
|
482
|
+
try {
|
|
483
|
+
// Check if platform-specific hash already exists
|
|
484
|
+
await fs.access(platformHashPath);
|
|
485
|
+
return false; // Already migrated
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// Platform-specific doesn't exist, check for legacy
|
|
489
|
+
}
|
|
490
|
+
try {
|
|
491
|
+
const legacyHash = await fs.readFile(legacyHashPath, "utf-8");
|
|
492
|
+
// Write to platform-specific location
|
|
493
|
+
await writePlatformRenderHash(specDir, skillId, platform, legacyHash.trim());
|
|
494
|
+
return true; // Migrated successfully
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
return false; // No legacy hash to migrate
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Write the render hash for a skill on a specific platform
|
|
502
|
+
* AC: @platform-renderer-trait ac-6 - Store per-platform hash in .render-hash-<platform>
|
|
503
|
+
*/
|
|
504
|
+
export async function writePlatformRenderHash(specDir, skillId, platform, hash) {
|
|
505
|
+
const hashPath = getPlatformRenderHashPath(specDir, skillId, platform);
|
|
506
|
+
await fs.mkdir(path.dirname(hashPath), { recursive: true });
|
|
507
|
+
await fs.writeFile(hashPath, hash + "\n", "utf-8");
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Check if a rendered skill has drifted from its last render for a specific platform
|
|
511
|
+
* AC: @platform-renderer-trait ac-6 - Platform-specific drift detection
|
|
512
|
+
*/
|
|
513
|
+
export async function checkPlatformSkillDrift(specDir, projectRoot, skillId, platform, outputDir, origin) {
|
|
514
|
+
// Core skills on claude-code are plugin-provided, not locally rendered
|
|
515
|
+
if (origin === "core" && platform === "claude-code" && !outputDir) {
|
|
516
|
+
return "plugin-provided";
|
|
517
|
+
}
|
|
518
|
+
// Determine the rendered path
|
|
519
|
+
let renderedPath;
|
|
520
|
+
if (outputDir) {
|
|
521
|
+
const subdir = getSkillSubdir(skillId, origin, platform);
|
|
522
|
+
renderedPath = path.join(projectRoot, outputDir, subdir, "SKILL.md");
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
const platformOutputDir = getPlatformDefaultOutputDir(platform);
|
|
526
|
+
const subdir = getSkillSubdir(skillId, origin, platform);
|
|
527
|
+
renderedPath = path.join(projectRoot, platformOutputDir, subdir, "SKILL.md");
|
|
528
|
+
}
|
|
529
|
+
// Check if rendered file exists
|
|
530
|
+
let renderedContent;
|
|
531
|
+
try {
|
|
532
|
+
renderedContent = await fs.readFile(renderedPath, "utf-8");
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
return "not-rendered";
|
|
536
|
+
}
|
|
537
|
+
// AC: @claude-code-renderer-extended ac-6 - Migrate legacy hash if needed
|
|
538
|
+
await migrateLegacyRenderHash(specDir, skillId, platform);
|
|
539
|
+
// Get stored hash (try platform-specific first, then fall back to legacy)
|
|
540
|
+
const storedHash = await readPlatformRenderHash(specDir, skillId, platform);
|
|
541
|
+
if (!storedHash) {
|
|
542
|
+
return "no-hash";
|
|
543
|
+
}
|
|
544
|
+
// Compare hashes
|
|
545
|
+
const currentHash = computeContentHash(renderedContent);
|
|
546
|
+
return currentHash === storedHash ? "in-sync" : "drifted";
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Get the default output directory for a platform
|
|
550
|
+
*/
|
|
551
|
+
function getPlatformDefaultOutputDir(platform) {
|
|
552
|
+
switch (platform) {
|
|
553
|
+
case "claude-code":
|
|
554
|
+
return ".claude/skills";
|
|
555
|
+
case "codex":
|
|
556
|
+
return ".agents/skills";
|
|
557
|
+
default:
|
|
558
|
+
return `.${platform}/skills`;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Base render function that handles shared logic across all platform renderers:
|
|
563
|
+
* content loading, frontmatter stripping, idempotency check, file write,
|
|
564
|
+
* hash storage, and supporting directory copy.
|
|
565
|
+
*
|
|
566
|
+
* AC: @skill-module-split ac-2 - Shared render logic in base function
|
|
567
|
+
* AC: @platform-renderer-trait ac-1 through ac-6
|
|
568
|
+
*/
|
|
569
|
+
export async function renderSkillBase(ctx, projectRoot, skill, options, config, defaultOutputDir, skillSubdir) {
|
|
570
|
+
const dryRun = options.dryRun ?? false;
|
|
571
|
+
const storeHash = options.storeHash ?? false;
|
|
572
|
+
const outputDir = options.outputDir || defaultOutputDir;
|
|
573
|
+
const targetDir = path.join(projectRoot, outputDir, skillSubdir || skill.id);
|
|
574
|
+
const targetSkillMd = path.join(targetDir, "SKILL.md");
|
|
575
|
+
const paths = [];
|
|
576
|
+
// Load source content
|
|
577
|
+
const sourceContent = await loadSkillContent(ctx, skill);
|
|
578
|
+
// Generate platform-specific frontmatter
|
|
579
|
+
const frontmatter = config.generateFrontmatter(skill);
|
|
580
|
+
// Build rendered content: frontmatter + marker + body
|
|
581
|
+
let renderedContent;
|
|
582
|
+
if (!sourceContent) {
|
|
583
|
+
renderedContent = `${frontmatter}\n${KSPEC_MANAGED_MARKER}\n\n# ${skill.name}\n\n${skill.description || ""}\n`;
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
const frontmatterMatch = sourceContent.match(/^---\n[\s\S]*?\n---\n?/);
|
|
587
|
+
const contentWithoutFrontmatter = frontmatterMatch
|
|
588
|
+
? sourceContent.slice(frontmatterMatch[0].length)
|
|
589
|
+
: sourceContent;
|
|
590
|
+
renderedContent = `${frontmatter}\n${KSPEC_MANAGED_MARKER}\n${contentWithoutFrontmatter}`;
|
|
591
|
+
}
|
|
592
|
+
// Idempotency check
|
|
593
|
+
let targetExists = false;
|
|
594
|
+
let targetContent = "";
|
|
595
|
+
try {
|
|
596
|
+
targetContent = await fs.readFile(targetSkillMd, "utf-8");
|
|
597
|
+
targetExists = true;
|
|
598
|
+
}
|
|
599
|
+
catch {
|
|
600
|
+
// Target doesn't exist
|
|
601
|
+
}
|
|
602
|
+
let action;
|
|
603
|
+
if (!targetExists) {
|
|
604
|
+
action = "created";
|
|
605
|
+
}
|
|
606
|
+
else if (contentsEqual(renderedContent, targetContent)) {
|
|
607
|
+
action = "unchanged";
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
action = "updated";
|
|
611
|
+
}
|
|
612
|
+
// Write SKILL.md
|
|
613
|
+
if (!dryRun && action !== "unchanged") {
|
|
614
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
615
|
+
await fs.writeFile(targetSkillMd, renderedContent, "utf-8");
|
|
616
|
+
}
|
|
617
|
+
paths.push(targetSkillMd);
|
|
618
|
+
// Write additional files (e.g., sidecar for codex)
|
|
619
|
+
let additionalContentChanged = false;
|
|
620
|
+
if (config.writeAdditionalFiles) {
|
|
621
|
+
const additional = await config.writeAdditionalFiles(ctx, skill, targetDir, dryRun);
|
|
622
|
+
paths.push(...additional.paths);
|
|
623
|
+
additionalContentChanged = additional.contentChanged;
|
|
624
|
+
}
|
|
625
|
+
// Store per-platform hash
|
|
626
|
+
const contentChanged = action !== "unchanged" || additionalContentChanged;
|
|
627
|
+
if (!dryRun && storeHash && contentChanged) {
|
|
628
|
+
// Get additional content for hash (e.g., sidecar content)
|
|
629
|
+
const additionalHashContent = config.getAdditionalHashContent?.(renderedContent);
|
|
630
|
+
const hashContent = additionalHashContent
|
|
631
|
+
? renderedContent + "\n" + additionalHashContent
|
|
632
|
+
: renderedContent;
|
|
633
|
+
const hash = computeContentHash(hashContent);
|
|
634
|
+
await writePlatformRenderHash(ctx.specDir, skill.id, config.platform, hash);
|
|
635
|
+
if (config.writeLegacyHash) {
|
|
636
|
+
await writeRenderHash(ctx.specDir, skill.id, hash);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// Copy supporting directories
|
|
640
|
+
const sourceSkillDir = path.join(ctx.specDir, "skills", skill.id);
|
|
641
|
+
const supportingDirsAction = await copySupportingDirectories(sourceSkillDir, targetDir, dryRun);
|
|
642
|
+
for (const [dirName, dirAction] of Object.entries(supportingDirsAction)) {
|
|
643
|
+
if (dirAction !== "skipped") {
|
|
644
|
+
paths.push(path.join(targetDir, dirName));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
id: skill.id,
|
|
649
|
+
platform: config.platform,
|
|
650
|
+
action,
|
|
651
|
+
paths,
|
|
652
|
+
supportingDirsAction,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
// ============================================================================
|
|
656
|
+
// Claude Code Renderer (implements PlatformRenderer)
|
|
657
|
+
// ============================================================================
|
|
658
|
+
/**
|
|
659
|
+
* Migrate old plugin paths for core skills.
|
|
660
|
+
* Cleans up old render targets (.claude/skills/<id>/, .claude/skills/kspec/<id>/,
|
|
661
|
+
* .claude/plugins/kspec/) when kspec-managed marker is present.
|
|
662
|
+
* Called from renderer and from CLI after marketplace registration.
|
|
663
|
+
*/
|
|
664
|
+
export async function migrateOldPluginPaths(projectRoot, skillId) {
|
|
665
|
+
const defaultOutputDir = ".claude/skills";
|
|
666
|
+
// Old flat path: .claude/skills/<id>/ (pre-#440)
|
|
667
|
+
const oldFlatPath = path.join(projectRoot, defaultOutputDir, skillId, "SKILL.md");
|
|
668
|
+
try {
|
|
669
|
+
const content = await fs.readFile(oldFlatPath, "utf-8");
|
|
670
|
+
if (content.includes(KSPEC_MANAGED_MARKER)) {
|
|
671
|
+
await fs.rm(path.join(projectRoot, defaultOutputDir, skillId), { recursive: true, force: true });
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
// Old path doesn't exist
|
|
676
|
+
}
|
|
677
|
+
// Old namespaced path: .claude/skills/kspec/<id>/ (PR #440)
|
|
678
|
+
const oldNamespacedPath = path.join(projectRoot, defaultOutputDir, "kspec", skillId, "SKILL.md");
|
|
679
|
+
try {
|
|
680
|
+
const content = await fs.readFile(oldNamespacedPath, "utf-8");
|
|
681
|
+
if (content.includes(KSPEC_MANAGED_MARKER)) {
|
|
682
|
+
await fs.rm(path.join(projectRoot, defaultOutputDir, "kspec", skillId), { recursive: true, force: true });
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch {
|
|
686
|
+
// Old path doesn't exist
|
|
687
|
+
}
|
|
688
|
+
// Old monolithic: .claude/skills/kspec/SKILL.md
|
|
689
|
+
const oldMonolithicPath = path.join(projectRoot, defaultOutputDir, "kspec", "SKILL.md");
|
|
690
|
+
try {
|
|
691
|
+
const content = await fs.readFile(oldMonolithicPath, "utf-8");
|
|
692
|
+
if (content.includes(KSPEC_MANAGED_MARKER)) {
|
|
693
|
+
await fs.rm(oldMonolithicPath, { force: true });
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
// Old path doesn't exist
|
|
698
|
+
}
|
|
699
|
+
// Old plugin render target: .claude/plugins/kspec/skills/<id>/
|
|
700
|
+
const oldPluginPath = path.join(projectRoot, PLUGIN_SKILLS_DIR, skillId, "SKILL.md");
|
|
701
|
+
try {
|
|
702
|
+
const content = await fs.readFile(oldPluginPath, "utf-8");
|
|
703
|
+
if (content.includes(KSPEC_MANAGED_MARKER)) {
|
|
704
|
+
await fs.rm(path.join(projectRoot, PLUGIN_SKILLS_DIR, skillId), { recursive: true, force: true });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
// Old path doesn't exist
|
|
709
|
+
}
|
|
710
|
+
// Clean up empty parent directories
|
|
711
|
+
for (const dir of [
|
|
712
|
+
path.join(projectRoot, defaultOutputDir, "kspec"),
|
|
713
|
+
path.join(projectRoot, PLUGIN_SKILLS_DIR),
|
|
714
|
+
path.join(projectRoot, PLUGIN_DIR),
|
|
715
|
+
]) {
|
|
716
|
+
try {
|
|
717
|
+
const entries = await fs.readdir(dir);
|
|
718
|
+
if (entries.length === 0) {
|
|
719
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
catch {
|
|
723
|
+
// Dir doesn't exist or not empty
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Claude Code platform renderer implementation
|
|
729
|
+
* AC: @platform-renderer-trait ac-1 through ac-6
|
|
730
|
+
*/
|
|
731
|
+
export const claudeCodeRenderer = {
|
|
732
|
+
platform: "claude-code",
|
|
733
|
+
defaultOutputDir: ".claude/skills",
|
|
734
|
+
async render(ctx, projectRoot, skill, options = {}) {
|
|
735
|
+
const dryRun = options.dryRun ?? false;
|
|
736
|
+
// AC: @skill-rendering ac-7 - Core skills are provided by the npm package plugin
|
|
737
|
+
// directory. Skip local rendering; migration cleanup is handled separately.
|
|
738
|
+
if (skill.origin === "core" && !options.outputDir) {
|
|
739
|
+
// Run migration cleanup for old paths
|
|
740
|
+
if (!dryRun) {
|
|
741
|
+
await migrateOldPluginPaths(projectRoot, skill.id);
|
|
742
|
+
}
|
|
743
|
+
return {
|
|
744
|
+
id: skill.id,
|
|
745
|
+
platform: this.platform,
|
|
746
|
+
action: "skipped",
|
|
747
|
+
paths: [],
|
|
748
|
+
skipReason: "core skill provided by npm package plugin",
|
|
749
|
+
skipCode: "plugin-provided",
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
// Project/local skills render to .claude/skills/
|
|
753
|
+
const result = await renderSkillBase(ctx, projectRoot, skill, options, {
|
|
754
|
+
platform: this.platform,
|
|
755
|
+
generateFrontmatter,
|
|
756
|
+
writeLegacyHash: true,
|
|
757
|
+
}, this.defaultOutputDir, skill.id);
|
|
758
|
+
return result;
|
|
759
|
+
},
|
|
760
|
+
async checkDrift(specDir, projectRoot, skillId, options) {
|
|
761
|
+
return checkPlatformSkillDrift(specDir, projectRoot, skillId, this.platform, options?.outputDir, options?.origin);
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
// ============================================================================
|
|
765
|
+
// Codex Renderer (implements PlatformRenderer)
|
|
766
|
+
// AC: @codex-renderer ac-1 through ac-6
|
|
767
|
+
// ============================================================================
|
|
768
|
+
/**
|
|
769
|
+
* Generate minimal YAML frontmatter for Codex (name + description only)
|
|
770
|
+
* AC: @codex-renderer ac-1 - Codex frontmatter contains only name and description
|
|
771
|
+
*/
|
|
772
|
+
function generateCodexFrontmatter(skill) {
|
|
773
|
+
const frontmatter = {
|
|
774
|
+
name: skill.id,
|
|
775
|
+
description: skill.description || skill.name,
|
|
776
|
+
};
|
|
777
|
+
return `---\n${yaml.stringify(frontmatter).trim()}\n---`;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Generate Codex sidecar openai.yaml content from platform_config.codex
|
|
781
|
+
* AC: @codex-renderer ac-2 - sidecar agents/openai.yaml with Codex config fields
|
|
782
|
+
*/
|
|
783
|
+
function generateCodexSidecarYaml(skill) {
|
|
784
|
+
const codexConfig = skill.platform_config?.codex;
|
|
785
|
+
if (!codexConfig) {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
// Build the sidecar structure per Codex docs
|
|
789
|
+
const sidecar = {};
|
|
790
|
+
// Interface section (display_name, short_description, icons, colors)
|
|
791
|
+
const interfaceSection = {};
|
|
792
|
+
if (codexConfig.display_name) {
|
|
793
|
+
interfaceSection.display_name = codexConfig.display_name;
|
|
794
|
+
}
|
|
795
|
+
if (codexConfig.short_description) {
|
|
796
|
+
interfaceSection.short_description = codexConfig.short_description;
|
|
797
|
+
}
|
|
798
|
+
if (codexConfig.icon_small) {
|
|
799
|
+
interfaceSection.icon_small = codexConfig.icon_small;
|
|
800
|
+
}
|
|
801
|
+
if (codexConfig.icon_large) {
|
|
802
|
+
interfaceSection.icon_large = codexConfig.icon_large;
|
|
803
|
+
}
|
|
804
|
+
if (codexConfig.brand_color) {
|
|
805
|
+
interfaceSection.brand_color = codexConfig.brand_color;
|
|
806
|
+
}
|
|
807
|
+
if (codexConfig.default_prompt) {
|
|
808
|
+
interfaceSection.default_prompt = codexConfig.default_prompt;
|
|
809
|
+
}
|
|
810
|
+
if (Object.keys(interfaceSection).length > 0) {
|
|
811
|
+
sidecar.interface = interfaceSection;
|
|
812
|
+
}
|
|
813
|
+
// Policy section (allow_implicit_invocation)
|
|
814
|
+
if (codexConfig.allow_implicit_invocation !== undefined) {
|
|
815
|
+
sidecar.policy = {
|
|
816
|
+
allow_implicit_invocation: codexConfig.allow_implicit_invocation,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
// Only return content if there's something to write
|
|
820
|
+
if (Object.keys(sidecar).length === 0) {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
return yaml.stringify(sidecar);
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Codex platform renderer implementation
|
|
827
|
+
* AC: @codex-renderer ac-1 - Creates .agents/skills/<id>/SKILL.md with minimal frontmatter
|
|
828
|
+
* AC: @codex-renderer ac-2 - Creates sidecar agents/openai.yaml when platform_config.codex exists
|
|
829
|
+
* AC: @codex-renderer ac-3 - No sidecar created when no platform_config.codex
|
|
830
|
+
* AC: @codex-renderer ac-4 - Rendered file contains <!-- kspec-managed --> marker
|
|
831
|
+
* AC: @codex-renderer ac-5 - Supporting directories copied to output
|
|
832
|
+
* AC: @codex-renderer ac-6 - Hash written to .render-hash-codex
|
|
833
|
+
*/
|
|
834
|
+
export const codexRenderer = {
|
|
835
|
+
platform: "codex",
|
|
836
|
+
defaultOutputDir: ".agents/skills",
|
|
837
|
+
async render(ctx, projectRoot, skill, options = {}) {
|
|
838
|
+
// Pre-compute sidecar content so it's available for both hash and write
|
|
839
|
+
const sidecarContent = generateCodexSidecarYaml(skill);
|
|
840
|
+
const codexSubdir = getSkillSubdir(skill.id, skill.origin, "codex");
|
|
841
|
+
return renderSkillBase(ctx, projectRoot, skill, options, {
|
|
842
|
+
platform: this.platform,
|
|
843
|
+
generateFrontmatter: generateCodexFrontmatter,
|
|
844
|
+
// AC: @skill-drift-detection-improvements ac-1 - Include sidecar in hash
|
|
845
|
+
getAdditionalHashContent: () => sidecarContent,
|
|
846
|
+
// AC: @codex-renderer ac-2, ac-3 - Write sidecar agents/openai.yaml
|
|
847
|
+
writeAdditionalFiles: async (_ctx, _skill, targetDir, dryRun) => {
|
|
848
|
+
const paths = [];
|
|
849
|
+
let contentChanged = false;
|
|
850
|
+
if (sidecarContent) {
|
|
851
|
+
const sidecarDir = path.join(targetDir, "agents");
|
|
852
|
+
const sidecarPath = path.join(sidecarDir, "openai.yaml");
|
|
853
|
+
// Check existing sidecar
|
|
854
|
+
let sidecarExists = false;
|
|
855
|
+
let existingSidecar = "";
|
|
856
|
+
try {
|
|
857
|
+
existingSidecar = await fs.readFile(sidecarPath, "utf-8");
|
|
858
|
+
sidecarExists = true;
|
|
859
|
+
}
|
|
860
|
+
catch {
|
|
861
|
+
// Doesn't exist
|
|
862
|
+
}
|
|
863
|
+
const sidecarAction = !sidecarExists
|
|
864
|
+
? "created"
|
|
865
|
+
: contentsEqual(sidecarContent, existingSidecar)
|
|
866
|
+
? "unchanged"
|
|
867
|
+
: "updated";
|
|
868
|
+
if (!dryRun && sidecarAction !== "unchanged") {
|
|
869
|
+
await fs.mkdir(sidecarDir, { recursive: true });
|
|
870
|
+
await fs.writeFile(sidecarPath, sidecarContent, "utf-8");
|
|
871
|
+
}
|
|
872
|
+
paths.push(sidecarPath);
|
|
873
|
+
contentChanged = sidecarAction !== "unchanged";
|
|
874
|
+
}
|
|
875
|
+
return { paths, contentChanged };
|
|
876
|
+
},
|
|
877
|
+
}, this.defaultOutputDir, codexSubdir);
|
|
878
|
+
},
|
|
879
|
+
// AC: @skill-drift-detection-improvements ac-1 - Include sidecar content in drift hash
|
|
880
|
+
async checkDrift(specDir, projectRoot, skillId, options) {
|
|
881
|
+
const codexSubdir = getSkillSubdir(skillId, options?.origin, "codex");
|
|
882
|
+
const platformOutputDir = options?.outputDir || this.defaultOutputDir;
|
|
883
|
+
const skillDir = path.join(projectRoot, platformOutputDir, codexSubdir);
|
|
884
|
+
const renderedPath = path.join(skillDir, "SKILL.md");
|
|
885
|
+
// Check if rendered file exists
|
|
886
|
+
let renderedContent;
|
|
887
|
+
try {
|
|
888
|
+
renderedContent = await fs.readFile(renderedPath, "utf-8");
|
|
889
|
+
}
|
|
890
|
+
catch {
|
|
891
|
+
return "not-rendered";
|
|
892
|
+
}
|
|
893
|
+
// Migrate legacy hash if needed
|
|
894
|
+
await migrateLegacyRenderHash(specDir, skillId, this.platform);
|
|
895
|
+
// Get stored hash
|
|
896
|
+
const storedHash = await readPlatformRenderHash(specDir, skillId, this.platform);
|
|
897
|
+
if (!storedHash) {
|
|
898
|
+
return "no-hash";
|
|
899
|
+
}
|
|
900
|
+
// Read sidecar content if it exists
|
|
901
|
+
const sidecarPath = path.join(skillDir, "agents", "openai.yaml");
|
|
902
|
+
let sidecarContent = null;
|
|
903
|
+
try {
|
|
904
|
+
sidecarContent = await fs.readFile(sidecarPath, "utf-8");
|
|
905
|
+
}
|
|
906
|
+
catch {
|
|
907
|
+
// No sidecar file
|
|
908
|
+
}
|
|
909
|
+
// Combine content for hash (must match render-time computation)
|
|
910
|
+
const combinedContent = sidecarContent
|
|
911
|
+
? renderedContent + "\n" + sidecarContent
|
|
912
|
+
: renderedContent;
|
|
913
|
+
const currentHash = computeContentHash(combinedContent);
|
|
914
|
+
return currentHash === storedHash ? "in-sync" : "drifted";
|
|
915
|
+
},
|
|
916
|
+
};
|
|
917
|
+
// ============================================================================
|
|
918
|
+
// Renderer Registry
|
|
919
|
+
// ============================================================================
|
|
920
|
+
/** Map of platform name to renderer implementation */
|
|
921
|
+
const rendererRegistry = new Map([
|
|
922
|
+
["claude-code", claudeCodeRenderer],
|
|
923
|
+
["codex", codexRenderer],
|
|
924
|
+
]);
|
|
925
|
+
/**
|
|
926
|
+
* Get the renderer for a specific platform
|
|
927
|
+
*/
|
|
928
|
+
export function getRenderer(platform) {
|
|
929
|
+
return rendererRegistry.get(platform);
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Get all registered renderers
|
|
933
|
+
*/
|
|
934
|
+
export function getAllRenderers() {
|
|
935
|
+
return Array.from(rendererRegistry.values());
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Register a new platform renderer
|
|
939
|
+
*/
|
|
940
|
+
export function registerRenderer(renderer) {
|
|
941
|
+
rendererRegistry.set(renderer.platform, renderer);
|
|
942
|
+
}
|
|
943
|
+
//# sourceMappingURL=skill-render.js.map
|