@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
package/dist/parser/validate.js
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides schema validation, reference validation, and orphan detection.
|
|
5
5
|
*/
|
|
6
|
-
import * as fs from
|
|
7
|
-
import * as path from
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { TraitIndex } from
|
|
6
|
+
import * as fs from "node:fs/promises";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { AgentSchema, ConventionSchema, isSkill, ManifestSchema, MetaManifestSchema, ObservationSchema, SkillSchema, SpecItemSchema, TaskSchema, TasksFileSchema, UlidSchema, WorkflowSchema, } from "../schema/index.js";
|
|
9
|
+
import { findMetaManifest, getSkillContentPath, loadMetaContext } from "./meta.js";
|
|
10
|
+
import { loadPlans } from "./plans.js";
|
|
11
|
+
import { ReferenceIndex, validateRefs, } from "./refs.js";
|
|
12
|
+
import { TraitIndex } from "./traits.js";
|
|
13
|
+
import { expandIncludePattern, extractItemsFromRaw, findTaskFiles, loadSpecFile, readYamlFile, } from "./yaml.js";
|
|
13
14
|
// ============================================================
|
|
14
15
|
// SCHEMA VALIDATION
|
|
15
16
|
// ============================================================
|
|
@@ -25,7 +26,7 @@ async function validateManifestFile(filePath) {
|
|
|
25
26
|
for (const issue of result.error.issues) {
|
|
26
27
|
errors.push({
|
|
27
28
|
file: filePath,
|
|
28
|
-
path: issue.path.join(
|
|
29
|
+
path: issue.path.join("."),
|
|
29
30
|
message: issue.message,
|
|
30
31
|
details: issue,
|
|
31
32
|
});
|
|
@@ -52,7 +53,7 @@ async function validateTasksFile(filePath) {
|
|
|
52
53
|
if (Array.isArray(raw)) {
|
|
53
54
|
taskList = raw;
|
|
54
55
|
}
|
|
55
|
-
else if (raw && typeof raw ===
|
|
56
|
+
else if (raw && typeof raw === "object" && "tasks" in raw) {
|
|
56
57
|
// Try full TasksFile schema first
|
|
57
58
|
const fileResult = TasksFileSchema.safeParse(raw);
|
|
58
59
|
if (!fileResult.success) {
|
|
@@ -67,7 +68,7 @@ async function validateTasksFile(filePath) {
|
|
|
67
68
|
else {
|
|
68
69
|
errors.push({
|
|
69
70
|
file: filePath,
|
|
70
|
-
message:
|
|
71
|
+
message: "Invalid tasks file format: expected array or { tasks: [...] }",
|
|
71
72
|
});
|
|
72
73
|
return errors;
|
|
73
74
|
}
|
|
@@ -79,7 +80,7 @@ async function validateTasksFile(filePath) {
|
|
|
79
80
|
for (const issue of result.error.issues) {
|
|
80
81
|
errors.push({
|
|
81
82
|
file: filePath,
|
|
82
|
-
path: `tasks[${i}].${issue.path.join(
|
|
83
|
+
path: `tasks[${i}].${issue.path.join(".")}`,
|
|
83
84
|
message: issue.message,
|
|
84
85
|
details: issue,
|
|
85
86
|
});
|
|
@@ -103,7 +104,7 @@ async function validateSpecFile(filePath) {
|
|
|
103
104
|
try {
|
|
104
105
|
const raw = await readYamlFile(filePath);
|
|
105
106
|
// Recursively validate spec items
|
|
106
|
-
validateSpecItemRecursive(raw, filePath,
|
|
107
|
+
validateSpecItemRecursive(raw, filePath, "", errors);
|
|
107
108
|
}
|
|
108
109
|
catch (err) {
|
|
109
110
|
errors.push({
|
|
@@ -127,7 +128,7 @@ async function validateMetaManifestFile(filePath) {
|
|
|
127
128
|
for (const issue of manifestResult.error.issues) {
|
|
128
129
|
errors.push({
|
|
129
130
|
file: filePath,
|
|
130
|
-
path: issue.path.join(
|
|
131
|
+
path: issue.path.join("."),
|
|
131
132
|
message: issue.message,
|
|
132
133
|
details: issue,
|
|
133
134
|
});
|
|
@@ -135,7 +136,10 @@ async function validateMetaManifestFile(filePath) {
|
|
|
135
136
|
return errors;
|
|
136
137
|
}
|
|
137
138
|
// Validate each agent with strict ULID validation
|
|
138
|
-
if (raw &&
|
|
139
|
+
if (raw &&
|
|
140
|
+
typeof raw === "object" &&
|
|
141
|
+
"agents" in raw &&
|
|
142
|
+
Array.isArray(raw.agents)) {
|
|
139
143
|
const agents = raw.agents;
|
|
140
144
|
for (let i = 0; i < agents.length; i++) {
|
|
141
145
|
const agent = agents[i];
|
|
@@ -144,27 +148,30 @@ async function validateMetaManifestFile(filePath) {
|
|
|
144
148
|
for (const issue of agentResult.error.issues) {
|
|
145
149
|
errors.push({
|
|
146
150
|
file: filePath,
|
|
147
|
-
path: `agents[${i}].${issue.path.join(
|
|
151
|
+
path: `agents[${i}].${issue.path.join(".")}`,
|
|
148
152
|
message: issue.message,
|
|
149
153
|
details: issue,
|
|
150
154
|
});
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
157
|
// Strict ULID validation
|
|
154
|
-
if (agent && typeof agent ===
|
|
158
|
+
if (agent && typeof agent === "object" && "_ulid" in agent) {
|
|
155
159
|
const ulidResult = UlidSchema.safeParse(agent._ulid);
|
|
156
160
|
if (!ulidResult.success) {
|
|
157
161
|
errors.push({
|
|
158
162
|
file: filePath,
|
|
159
163
|
path: `agents[${i}]._ulid`,
|
|
160
|
-
message:
|
|
164
|
+
message: "Invalid ULID format (expected 26 characters)",
|
|
161
165
|
});
|
|
162
166
|
}
|
|
163
167
|
}
|
|
164
168
|
}
|
|
165
169
|
}
|
|
166
170
|
// Validate each workflow with strict ULID validation
|
|
167
|
-
if (raw &&
|
|
171
|
+
if (raw &&
|
|
172
|
+
typeof raw === "object" &&
|
|
173
|
+
"workflows" in raw &&
|
|
174
|
+
Array.isArray(raw.workflows)) {
|
|
168
175
|
const workflows = raw.workflows;
|
|
169
176
|
for (let i = 0; i < workflows.length; i++) {
|
|
170
177
|
const workflow = workflows[i];
|
|
@@ -173,28 +180,32 @@ async function validateMetaManifestFile(filePath) {
|
|
|
173
180
|
for (const issue of workflowResult.error.issues) {
|
|
174
181
|
errors.push({
|
|
175
182
|
file: filePath,
|
|
176
|
-
path: `workflows[${i}].${issue.path.join(
|
|
183
|
+
path: `workflows[${i}].${issue.path.join(".")}`,
|
|
177
184
|
message: issue.message,
|
|
178
185
|
details: issue,
|
|
179
186
|
});
|
|
180
187
|
}
|
|
181
188
|
}
|
|
182
189
|
// Strict ULID validation
|
|
183
|
-
if (workflow && typeof workflow ===
|
|
190
|
+
if (workflow && typeof workflow === "object" && "_ulid" in workflow) {
|
|
184
191
|
const ulidResult = UlidSchema.safeParse(workflow._ulid);
|
|
185
192
|
if (!ulidResult.success) {
|
|
186
193
|
errors.push({
|
|
187
194
|
file: filePath,
|
|
188
195
|
path: `workflows[${i}]._ulid`,
|
|
189
|
-
message:
|
|
196
|
+
message: "Invalid ULID format (expected 26 characters)",
|
|
190
197
|
});
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
201
|
}
|
|
195
202
|
// Validate each convention with strict ULID validation
|
|
196
|
-
if (raw &&
|
|
197
|
-
|
|
203
|
+
if (raw &&
|
|
204
|
+
typeof raw === "object" &&
|
|
205
|
+
"conventions" in raw &&
|
|
206
|
+
Array.isArray(raw.conventions)) {
|
|
207
|
+
const conventions = raw
|
|
208
|
+
.conventions;
|
|
198
209
|
for (let i = 0; i < conventions.length; i++) {
|
|
199
210
|
const convention = conventions[i];
|
|
200
211
|
const conventionResult = ConventionSchema.safeParse(convention);
|
|
@@ -202,28 +213,34 @@ async function validateMetaManifestFile(filePath) {
|
|
|
202
213
|
for (const issue of conventionResult.error.issues) {
|
|
203
214
|
errors.push({
|
|
204
215
|
file: filePath,
|
|
205
|
-
path: `conventions[${i}].${issue.path.join(
|
|
216
|
+
path: `conventions[${i}].${issue.path.join(".")}`,
|
|
206
217
|
message: issue.message,
|
|
207
218
|
details: issue,
|
|
208
219
|
});
|
|
209
220
|
}
|
|
210
221
|
}
|
|
211
222
|
// Strict ULID validation
|
|
212
|
-
if (convention &&
|
|
223
|
+
if (convention &&
|
|
224
|
+
typeof convention === "object" &&
|
|
225
|
+
"_ulid" in convention) {
|
|
213
226
|
const ulidResult = UlidSchema.safeParse(convention._ulid);
|
|
214
227
|
if (!ulidResult.success) {
|
|
215
228
|
errors.push({
|
|
216
229
|
file: filePath,
|
|
217
230
|
path: `conventions[${i}]._ulid`,
|
|
218
|
-
message:
|
|
231
|
+
message: "Invalid ULID format (expected 26 characters)",
|
|
219
232
|
});
|
|
220
233
|
}
|
|
221
234
|
}
|
|
222
235
|
}
|
|
223
236
|
}
|
|
224
237
|
// Validate each observation with strict ULID validation
|
|
225
|
-
if (raw &&
|
|
226
|
-
|
|
238
|
+
if (raw &&
|
|
239
|
+
typeof raw === "object" &&
|
|
240
|
+
"observations" in raw &&
|
|
241
|
+
Array.isArray(raw.observations)) {
|
|
242
|
+
const observations = raw
|
|
243
|
+
.observations;
|
|
227
244
|
for (let i = 0; i < observations.length; i++) {
|
|
228
245
|
const observation = observations[i];
|
|
229
246
|
const observationResult = ObservationSchema.safeParse(observation);
|
|
@@ -231,20 +248,54 @@ async function validateMetaManifestFile(filePath) {
|
|
|
231
248
|
for (const issue of observationResult.error.issues) {
|
|
232
249
|
errors.push({
|
|
233
250
|
file: filePath,
|
|
234
|
-
path: `observations[${i}].${issue.path.join(
|
|
251
|
+
path: `observations[${i}].${issue.path.join(".")}`,
|
|
235
252
|
message: issue.message,
|
|
236
253
|
details: issue,
|
|
237
254
|
});
|
|
238
255
|
}
|
|
239
256
|
}
|
|
240
257
|
// Strict ULID validation
|
|
241
|
-
if (observation &&
|
|
258
|
+
if (observation &&
|
|
259
|
+
typeof observation === "object" &&
|
|
260
|
+
"_ulid" in observation) {
|
|
242
261
|
const ulidResult = UlidSchema.safeParse(observation._ulid);
|
|
243
262
|
if (!ulidResult.success) {
|
|
244
263
|
errors.push({
|
|
245
264
|
file: filePath,
|
|
246
265
|
path: `observations[${i}]._ulid`,
|
|
247
|
-
message:
|
|
266
|
+
message: "Invalid ULID format (expected 26 characters)",
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// AC: @skill-validation ac-4 - validate each skill with strict ULID validation
|
|
273
|
+
if (raw &&
|
|
274
|
+
typeof raw === "object" &&
|
|
275
|
+
"skills" in raw &&
|
|
276
|
+
Array.isArray(raw.skills)) {
|
|
277
|
+
const skills = raw.skills;
|
|
278
|
+
for (let i = 0; i < skills.length; i++) {
|
|
279
|
+
const skill = skills[i];
|
|
280
|
+
const skillResult = SkillSchema.safeParse(skill);
|
|
281
|
+
if (!skillResult.success) {
|
|
282
|
+
for (const issue of skillResult.error.issues) {
|
|
283
|
+
errors.push({
|
|
284
|
+
file: filePath,
|
|
285
|
+
path: `skills[${i}].${issue.path.join(".")}`,
|
|
286
|
+
message: issue.message,
|
|
287
|
+
details: issue,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Strict ULID validation
|
|
292
|
+
if (skill && typeof skill === "object" && "_ulid" in skill) {
|
|
293
|
+
const ulidResult = UlidSchema.safeParse(skill._ulid);
|
|
294
|
+
if (!ulidResult.success) {
|
|
295
|
+
errors.push({
|
|
296
|
+
file: filePath,
|
|
297
|
+
path: `skills[${i}]._ulid`,
|
|
298
|
+
message: "Invalid ULID format (expected 26 characters)",
|
|
248
299
|
});
|
|
249
300
|
}
|
|
250
301
|
}
|
|
@@ -263,16 +314,18 @@ async function validateMetaManifestFile(filePath) {
|
|
|
263
314
|
* Recursively validate spec items in a structure
|
|
264
315
|
*/
|
|
265
316
|
function validateSpecItemRecursive(raw, file, pathPrefix, errors) {
|
|
266
|
-
if (!raw || typeof raw !==
|
|
317
|
+
if (!raw || typeof raw !== "object")
|
|
267
318
|
return;
|
|
268
319
|
// Check if this is a spec item (has _ulid)
|
|
269
|
-
if (
|
|
320
|
+
if ("_ulid" in raw) {
|
|
270
321
|
const result = SpecItemSchema.safeParse(raw);
|
|
271
322
|
if (!result.success) {
|
|
272
323
|
for (const issue of result.error.issues) {
|
|
273
324
|
errors.push({
|
|
274
325
|
file,
|
|
275
|
-
path: pathPrefix
|
|
326
|
+
path: pathPrefix
|
|
327
|
+
? `${pathPrefix}.${issue.path.join(".")}`
|
|
328
|
+
: issue.path.join("."),
|
|
276
329
|
message: issue.message,
|
|
277
330
|
details: issue,
|
|
278
331
|
});
|
|
@@ -280,13 +333,22 @@ function validateSpecItemRecursive(raw, file, pathPrefix, errors) {
|
|
|
280
333
|
}
|
|
281
334
|
}
|
|
282
335
|
// Recurse into nested structures
|
|
283
|
-
const nestedFields = [
|
|
336
|
+
const nestedFields = [
|
|
337
|
+
"modules",
|
|
338
|
+
"features",
|
|
339
|
+
"requirements",
|
|
340
|
+
"constraints",
|
|
341
|
+
"decisions",
|
|
342
|
+
"items",
|
|
343
|
+
];
|
|
284
344
|
const obj = raw;
|
|
285
345
|
for (const field of nestedFields) {
|
|
286
346
|
if (field in obj && Array.isArray(obj[field])) {
|
|
287
347
|
const arr = obj[field];
|
|
288
348
|
for (let i = 0; i < arr.length; i++) {
|
|
289
|
-
const newPath = pathPrefix
|
|
349
|
+
const newPath = pathPrefix
|
|
350
|
+
? `${pathPrefix}.${field}[${i}]`
|
|
351
|
+
: `${field}[${i}]`;
|
|
290
352
|
validateSpecItemRecursive(arr[i], file, newPath, errors);
|
|
291
353
|
}
|
|
292
354
|
}
|
|
@@ -305,20 +367,20 @@ function findOrphans(tasks, items, index) {
|
|
|
305
367
|
const allItems = [...tasks, ...items];
|
|
306
368
|
// Fields that contain references
|
|
307
369
|
const refFields = [
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
370
|
+
"depends_on",
|
|
371
|
+
"blocked_by",
|
|
372
|
+
"implements",
|
|
373
|
+
"relates_to",
|
|
374
|
+
"tests",
|
|
375
|
+
"supersedes",
|
|
376
|
+
"spec_ref",
|
|
377
|
+
"context",
|
|
316
378
|
];
|
|
317
379
|
for (const item of allItems) {
|
|
318
380
|
const obj = item;
|
|
319
381
|
for (const field of refFields) {
|
|
320
382
|
const value = obj[field];
|
|
321
|
-
if (typeof value ===
|
|
383
|
+
if (typeof value === "string" && value.startsWith("@")) {
|
|
322
384
|
const resolved = index.resolve(value);
|
|
323
385
|
if (resolved.ok) {
|
|
324
386
|
referenced.add(resolved.ulid);
|
|
@@ -326,7 +388,7 @@ function findOrphans(tasks, items, index) {
|
|
|
326
388
|
}
|
|
327
389
|
else if (Array.isArray(value)) {
|
|
328
390
|
for (const v of value) {
|
|
329
|
-
if (typeof v ===
|
|
391
|
+
if (typeof v === "string" && v.startsWith("@")) {
|
|
330
392
|
const resolved = index.resolve(v);
|
|
331
393
|
if (resolved.ok) {
|
|
332
394
|
referenced.add(resolved.ulid);
|
|
@@ -338,12 +400,12 @@ function findOrphans(tasks, items, index) {
|
|
|
338
400
|
}
|
|
339
401
|
// Find items not in the referenced set
|
|
340
402
|
// Skip entry point types: modules are spec entry points, tasks are work items
|
|
341
|
-
const entryPointTypes = [
|
|
403
|
+
const entryPointTypes = ["module", "task", "epic", "bug", "spike", "infra"];
|
|
342
404
|
for (const item of items) {
|
|
343
405
|
// Only check spec items, not tasks
|
|
344
406
|
if (!referenced.has(item._ulid)) {
|
|
345
407
|
// Skip entry point types
|
|
346
|
-
if (entryPointTypes.includes(item.type ||
|
|
408
|
+
if (entryPointTypes.includes(item.type || ""))
|
|
347
409
|
continue;
|
|
348
410
|
// Skip nested items - they're implicitly referenced by their parent
|
|
349
411
|
// _path indicates nesting (e.g., "features[0].requirements[2]")
|
|
@@ -352,7 +414,7 @@ function findOrphans(tasks, items, index) {
|
|
|
352
414
|
orphans.push({
|
|
353
415
|
ulid: item._ulid,
|
|
354
416
|
title: item.title,
|
|
355
|
-
type: item.type ||
|
|
417
|
+
type: item.type || "unknown",
|
|
356
418
|
file: item._sourceFile,
|
|
357
419
|
});
|
|
358
420
|
}
|
|
@@ -368,12 +430,14 @@ function findOrphans(tasks, items, index) {
|
|
|
368
430
|
*/
|
|
369
431
|
function detectTraitCycles(items, index) {
|
|
370
432
|
const errors = [];
|
|
371
|
-
const traits = items.filter(item => item.type ===
|
|
433
|
+
const traits = items.filter((item) => item.type === "trait");
|
|
372
434
|
// Build adjacency list: trait ULID → trait ULIDs it references
|
|
373
435
|
const graph = new Map();
|
|
374
436
|
const traitInfo = new Map();
|
|
375
437
|
for (const trait of traits) {
|
|
376
|
-
const ref = trait.slugs?.[0]
|
|
438
|
+
const ref = trait.slugs?.[0]
|
|
439
|
+
? `@${trait.slugs[0]}`
|
|
440
|
+
: `@${trait._ulid.slice(0, 8)}`;
|
|
377
441
|
traitInfo.set(trait._ulid, { ref, title: trait.title });
|
|
378
442
|
const dependencies = [];
|
|
379
443
|
if (trait.traits && trait.traits.length > 0) {
|
|
@@ -419,7 +483,7 @@ function detectTraitCycles(items, index) {
|
|
|
419
483
|
if (cycle) {
|
|
420
484
|
const info = traitInfo.get(cycle[0]);
|
|
421
485
|
if (info) {
|
|
422
|
-
const cycleRefs = cycle.map(ulid => {
|
|
486
|
+
const cycleRefs = cycle.map((ulid) => {
|
|
423
487
|
const cycleInfo = traitInfo.get(ulid);
|
|
424
488
|
return cycleInfo ? cycleInfo.ref : `@${ulid.slice(0, 8)}`;
|
|
425
489
|
});
|
|
@@ -427,7 +491,7 @@ function detectTraitCycles(items, index) {
|
|
|
427
491
|
traitRef: info.ref,
|
|
428
492
|
traitTitle: info.title,
|
|
429
493
|
cycle: cycleRefs,
|
|
430
|
-
message: `Circular trait reference: ${cycleRefs.join(
|
|
494
|
+
message: `Circular trait reference: ${cycleRefs.join(" → ")} → ${cycleRefs[0]}`,
|
|
431
495
|
});
|
|
432
496
|
}
|
|
433
497
|
// Mark all traits in cycle as visited to avoid duplicate errors
|
|
@@ -443,21 +507,45 @@ function detectTraitCycles(items, index) {
|
|
|
443
507
|
// COMPLETENESS VALIDATION
|
|
444
508
|
// ============================================================
|
|
445
509
|
/**
|
|
446
|
-
*
|
|
510
|
+
* Recursively find all test files in a directory
|
|
511
|
+
*/
|
|
512
|
+
async function findTestFilesRecursive(dir) {
|
|
513
|
+
const testFiles = [];
|
|
514
|
+
try {
|
|
515
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
516
|
+
for (const entry of entries) {
|
|
517
|
+
const fullPath = path.join(dir, entry.name);
|
|
518
|
+
if (entry.isDirectory()) {
|
|
519
|
+
// Recurse into subdirectories
|
|
520
|
+
const subFiles = await findTestFilesRecursive(fullPath);
|
|
521
|
+
testFiles.push(...subFiles);
|
|
522
|
+
}
|
|
523
|
+
else if (entry.isFile() &&
|
|
524
|
+
(entry.name.endsWith(".test.ts") || entry.name.endsWith(".test.js"))) {
|
|
525
|
+
testFiles.push(fullPath);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
// Directory doesn't exist or can't be read - return empty
|
|
531
|
+
}
|
|
532
|
+
return testFiles;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Scan test files for AC annotations to build coverage index.
|
|
536
|
+
* Recursively scans all subdirectories under tests/.
|
|
447
537
|
* Returns a Set of covered ACs in format "@spec-ref ac-N"
|
|
448
538
|
*/
|
|
449
|
-
async function scanTestCoverage(rootDir) {
|
|
539
|
+
export async function scanTestCoverage(rootDir) {
|
|
450
540
|
const coveredACs = new Set();
|
|
451
|
-
const testsDir = path.join(rootDir,
|
|
541
|
+
const testsDir = path.join(rootDir, "tests");
|
|
452
542
|
try {
|
|
453
543
|
// Check if tests directory exists
|
|
454
544
|
await fs.access(testsDir);
|
|
455
|
-
//
|
|
456
|
-
const
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
const filePath = path.join(testsDir, file);
|
|
460
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
545
|
+
// Recursively find all test files
|
|
546
|
+
const testFiles = await findTestFilesRecursive(testsDir);
|
|
547
|
+
for (const filePath of testFiles) {
|
|
548
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
461
549
|
// Match AC annotations: // AC: @spec-ref ac-N
|
|
462
550
|
// Also handle multiple ACs on one line: // AC: @spec-ref ac-1, ac-2
|
|
463
551
|
const acPattern = /\/\/\s*AC:\s*(@[\w-]+)(?:\s+(ac-\d+(?:\s*,\s*ac-\d+)*))?/g;
|
|
@@ -467,7 +555,7 @@ async function scanTestCoverage(rootDir) {
|
|
|
467
555
|
const acList = match[2]; // "ac-1, ac-2" or just "ac-1" or undefined
|
|
468
556
|
if (acList) {
|
|
469
557
|
// Split by comma and trim
|
|
470
|
-
const acs = acList.split(
|
|
558
|
+
const acs = acList.split(",").map((ac) => ac.trim());
|
|
471
559
|
for (const ac of acs) {
|
|
472
560
|
coveredACs.add(`${specRef} ${ac}`);
|
|
473
561
|
}
|
|
@@ -480,68 +568,97 @@ async function scanTestCoverage(rootDir) {
|
|
|
480
568
|
}
|
|
481
569
|
}
|
|
482
570
|
}
|
|
483
|
-
catch (
|
|
571
|
+
catch (_err) {
|
|
484
572
|
// Tests directory doesn't exist or can't be read - that's ok
|
|
485
573
|
}
|
|
486
574
|
return coveredACs;
|
|
487
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Compute coverage status for acceptance criteria of a spec item.
|
|
578
|
+
* Shared utility used by both daemon API and JSON export.
|
|
579
|
+
*
|
|
580
|
+
* @param item - The spec item containing acceptance_criteria
|
|
581
|
+
* @param coveredACs - Set of covered AC references from scanTestCoverage()
|
|
582
|
+
* @returns Array of ACs with covered field populated
|
|
583
|
+
*/
|
|
584
|
+
export function computeACCoverage(item, coveredACs) {
|
|
585
|
+
if (!item.acceptance_criteria || item.acceptance_criteria.length === 0) {
|
|
586
|
+
return [];
|
|
587
|
+
}
|
|
588
|
+
return item.acceptance_criteria.map((ac, index) => {
|
|
589
|
+
const acId = `ac-${index + 1}`;
|
|
590
|
+
const possibleRefs = [];
|
|
591
|
+
// Try with primary slug
|
|
592
|
+
if (item.slugs && item.slugs.length > 0) {
|
|
593
|
+
possibleRefs.push(`@${item.slugs[0]} ${acId}`);
|
|
594
|
+
possibleRefs.push(`@${item.slugs[0]}`);
|
|
595
|
+
}
|
|
596
|
+
// Try with ULID (short form)
|
|
597
|
+
possibleRefs.push(`@${item._ulid.slice(0, 8)} ${acId}`);
|
|
598
|
+
possibleRefs.push(`@${item._ulid.slice(0, 8)}`);
|
|
599
|
+
const covered = possibleRefs.some((ref) => coveredACs.has(ref));
|
|
600
|
+
return { ...ac, covered };
|
|
601
|
+
});
|
|
602
|
+
}
|
|
488
603
|
/**
|
|
489
604
|
* Check spec items for completeness
|
|
490
605
|
* AC: @spec-completeness ac-1, ac-2, ac-3
|
|
491
606
|
* AC: @trait-validation ac-1, ac-2, ac-3
|
|
492
607
|
*/
|
|
493
|
-
async function checkCompleteness(items,
|
|
608
|
+
async function checkCompleteness(items, _index, rootDir, traitIndex) {
|
|
494
609
|
const warnings = [];
|
|
495
610
|
// Scan test files for AC coverage
|
|
496
611
|
const coveredACs = await scanTestCoverage(rootDir);
|
|
497
612
|
for (const item of items) {
|
|
498
|
-
const itemRef = item.slugs?.[0]
|
|
499
|
-
|
|
613
|
+
const itemRef = item.slugs?.[0]
|
|
614
|
+
? `@${item.slugs[0]}`
|
|
615
|
+
: `@${item._ulid.slice(0, 8)}`;
|
|
616
|
+
const isTrait = item.type === "trait";
|
|
500
617
|
// AC: @spec-completeness ac-1
|
|
501
618
|
// AC: @trait-type ac-2 - Traits should have acceptance criteria for completeness
|
|
502
619
|
// Check for missing acceptance criteria
|
|
503
620
|
if (!item.acceptance_criteria || item.acceptance_criteria.length === 0) {
|
|
504
621
|
warnings.push({
|
|
505
|
-
type:
|
|
622
|
+
type: "missing_acceptance_criteria",
|
|
506
623
|
itemRef,
|
|
507
624
|
itemTitle: item.title,
|
|
508
|
-
message: `${isTrait ?
|
|
625
|
+
message: `${isTrait ? "Trait" : "Item"} ${itemRef} has no acceptance criteria`,
|
|
509
626
|
});
|
|
510
627
|
}
|
|
511
628
|
// AC: @spec-completeness ac-2
|
|
512
629
|
// AC: @trait-type ac-3 - Traits should have description for completeness
|
|
513
630
|
// Check for missing description
|
|
514
|
-
if (!item.description || item.description.trim() ===
|
|
631
|
+
if (!item.description || item.description.trim() === "") {
|
|
515
632
|
warnings.push({
|
|
516
|
-
type:
|
|
633
|
+
type: "missing_description",
|
|
517
634
|
itemRef,
|
|
518
635
|
itemTitle: item.title,
|
|
519
|
-
message: `${isTrait ?
|
|
636
|
+
message: `${isTrait ? "Trait" : "Item"} ${itemRef} has no description`,
|
|
520
637
|
});
|
|
521
638
|
}
|
|
522
639
|
// AC: @spec-completeness ac-3
|
|
523
640
|
// Check for status inconsistency between parent and children
|
|
524
|
-
if (item.status?.implementation ===
|
|
641
|
+
if (item.status?.implementation === "implemented") {
|
|
525
642
|
// Check if this item has children with not_started status
|
|
526
643
|
const childFields = [
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
644
|
+
"modules",
|
|
645
|
+
"features",
|
|
646
|
+
"requirements",
|
|
647
|
+
"constraints",
|
|
648
|
+
"epics",
|
|
649
|
+
"themes",
|
|
650
|
+
"capabilities",
|
|
534
651
|
];
|
|
535
652
|
for (const field of childFields) {
|
|
536
653
|
const children = item[field];
|
|
537
654
|
if (Array.isArray(children)) {
|
|
538
655
|
for (const child of children) {
|
|
539
|
-
if (child.status?.implementation ===
|
|
656
|
+
if (child.status?.implementation === "not_started") {
|
|
540
657
|
const childRef = child.slugs?.[0]
|
|
541
658
|
? `@${child.slugs[0]}`
|
|
542
|
-
: `@${child._ulid?.slice(0, 8) ||
|
|
659
|
+
: `@${child._ulid?.slice(0, 8) || "unknown"}`;
|
|
543
660
|
warnings.push({
|
|
544
|
-
type:
|
|
661
|
+
type: "status_inconsistency",
|
|
545
662
|
itemRef,
|
|
546
663
|
itemTitle: item.title,
|
|
547
664
|
message: `Parent ${itemRef} is implemented but child ${childRef} is not_started`,
|
|
@@ -568,7 +685,7 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
|
|
|
568
685
|
possibleRefs.push(`@${item._ulid.slice(0, 8)} ${ac.id}`);
|
|
569
686
|
possibleRefs.push(`@${item._ulid.slice(0, 8)}`);
|
|
570
687
|
// Check if any of these references are covered
|
|
571
|
-
const isCovered = possibleRefs.some(ref => coveredACs.has(ref));
|
|
688
|
+
const isCovered = possibleRefs.some((ref) => coveredACs.has(ref));
|
|
572
689
|
if (!isCovered) {
|
|
573
690
|
uncoveredACs.push(ac.id);
|
|
574
691
|
}
|
|
@@ -576,11 +693,11 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
|
|
|
576
693
|
// Only warn if there are uncovered ACs
|
|
577
694
|
if (uncoveredACs.length > 0) {
|
|
578
695
|
warnings.push({
|
|
579
|
-
type:
|
|
696
|
+
type: "missing_test_coverage",
|
|
580
697
|
itemRef,
|
|
581
698
|
itemTitle: item.title,
|
|
582
699
|
message: `Item ${itemRef} has ${uncoveredACs.length} AC(s) without test coverage`,
|
|
583
|
-
details: `Uncovered: ${uncoveredACs.join(
|
|
700
|
+
details: `Uncovered: ${uncoveredACs.join(", ")}`,
|
|
584
701
|
});
|
|
585
702
|
}
|
|
586
703
|
}
|
|
@@ -599,7 +716,7 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
|
|
|
599
716
|
possibleRefs.push(`@${trait.ulid.slice(0, 8)} ${ac.id}`);
|
|
600
717
|
possibleRefs.push(`@${trait.ulid.slice(0, 8)}`);
|
|
601
718
|
// Check if any of these references are covered
|
|
602
|
-
const isCovered = possibleRefs.some(ref => coveredACs.has(ref));
|
|
719
|
+
const isCovered = possibleRefs.some((ref) => coveredACs.has(ref));
|
|
603
720
|
if (!isCovered) {
|
|
604
721
|
uncoveredTraitACs.push({ traitSlug: trait.slug, acId: ac.id });
|
|
605
722
|
}
|
|
@@ -608,9 +725,9 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
|
|
|
608
725
|
if (uncoveredTraitACs.length > 0) {
|
|
609
726
|
const details = uncoveredTraitACs
|
|
610
727
|
.map(({ traitSlug, acId }) => `@${traitSlug} ${acId}`)
|
|
611
|
-
.join(
|
|
728
|
+
.join(", ");
|
|
612
729
|
warnings.push({
|
|
613
|
-
type:
|
|
730
|
+
type: "missing_test_coverage",
|
|
614
731
|
itemRef,
|
|
615
732
|
itemTitle: item.title,
|
|
616
733
|
message: `Item ${itemRef} has ${uncoveredTraitACs.length} inherited trait AC(s) without test coverage`,
|
|
@@ -618,10 +735,270 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
|
|
|
618
735
|
});
|
|
619
736
|
}
|
|
620
737
|
}
|
|
738
|
+
// AC: @trait-retrospective ac-2, ac-3
|
|
739
|
+
// Check retrospective specs have required verification metadata
|
|
740
|
+
const isRetrospective = item.traits?.includes("@trait-retrospective");
|
|
741
|
+
if (isRetrospective) {
|
|
742
|
+
const isImplementedOrVerified = item.status?.implementation === "implemented" ||
|
|
743
|
+
item.status?.implementation === "verified";
|
|
744
|
+
// AC: @trait-retrospective ac-2
|
|
745
|
+
// Retrospective specs with implemented/verified status must have verification metadata
|
|
746
|
+
if (isImplementedOrVerified) {
|
|
747
|
+
if (!item.verified_at || !item.verified_by) {
|
|
748
|
+
warnings.push({
|
|
749
|
+
type: "status_inconsistency",
|
|
750
|
+
itemRef,
|
|
751
|
+
itemTitle: item.title,
|
|
752
|
+
message: `Retrospective spec ${itemRef} is ${item.status?.implementation} but missing verified_at/verified_by`,
|
|
753
|
+
details: "Specs using @trait-retrospective must have verified_at and verified_by fields when marked as implemented or verified",
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
// Inverse validation: If verification metadata exists, status should be implemented/verified
|
|
758
|
+
if ((item.verified_at || item.verified_by) && !isImplementedOrVerified) {
|
|
759
|
+
warnings.push({
|
|
760
|
+
type: "status_inconsistency",
|
|
761
|
+
itemRef,
|
|
762
|
+
itemTitle: item.title,
|
|
763
|
+
message: `Retrospective spec ${itemRef} has verification metadata but status is not implemented/verified`,
|
|
764
|
+
details: "Retrospective specs with verified_at/verified_by should have implementation status set to 'implemented' or 'verified'",
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
}
|
|
621
768
|
}
|
|
622
769
|
return warnings;
|
|
623
770
|
}
|
|
624
771
|
// ============================================================
|
|
772
|
+
// AC SCHEMA DRIFT DETECTION
|
|
773
|
+
// ============================================================
|
|
774
|
+
/**
|
|
775
|
+
* Known schema fields for SpecItem
|
|
776
|
+
* These are the actual fields defined in SpecItemSchema
|
|
777
|
+
*/
|
|
778
|
+
const SPEC_ITEM_FIELDS = new Set([
|
|
779
|
+
"_ulid",
|
|
780
|
+
"slugs",
|
|
781
|
+
"title",
|
|
782
|
+
"type",
|
|
783
|
+
"status",
|
|
784
|
+
"maturity",
|
|
785
|
+
"implementation",
|
|
786
|
+
"priority",
|
|
787
|
+
"tags",
|
|
788
|
+
"description",
|
|
789
|
+
"acceptance_criteria",
|
|
790
|
+
"depends_on",
|
|
791
|
+
"implements",
|
|
792
|
+
"relates_to",
|
|
793
|
+
"tests",
|
|
794
|
+
"traits",
|
|
795
|
+
"supersedes",
|
|
796
|
+
"traceability",
|
|
797
|
+
"created",
|
|
798
|
+
"created_by",
|
|
799
|
+
"deprecated_in",
|
|
800
|
+
"superseded_by",
|
|
801
|
+
"verified_at",
|
|
802
|
+
"verified_by",
|
|
803
|
+
"notes",
|
|
804
|
+
]);
|
|
805
|
+
/**
|
|
806
|
+
* Known schema fields for Task
|
|
807
|
+
* These are the actual fields defined in TaskSchema
|
|
808
|
+
*/
|
|
809
|
+
const TASK_FIELDS = new Set([
|
|
810
|
+
"_ulid",
|
|
811
|
+
"slugs",
|
|
812
|
+
"title",
|
|
813
|
+
"type",
|
|
814
|
+
"description",
|
|
815
|
+
"spec_ref",
|
|
816
|
+
"derivation",
|
|
817
|
+
"meta_ref",
|
|
818
|
+
"plan_ref",
|
|
819
|
+
"origin",
|
|
820
|
+
"status",
|
|
821
|
+
"blocked_by",
|
|
822
|
+
"closed_reason",
|
|
823
|
+
"depends_on",
|
|
824
|
+
"context",
|
|
825
|
+
"priority",
|
|
826
|
+
"complexity",
|
|
827
|
+
"tags",
|
|
828
|
+
"assignee",
|
|
829
|
+
"vcs_refs",
|
|
830
|
+
"created_at",
|
|
831
|
+
"started_at",
|
|
832
|
+
"completed_at",
|
|
833
|
+
"notes",
|
|
834
|
+
"todos",
|
|
835
|
+
"automation",
|
|
836
|
+
]);
|
|
837
|
+
/**
|
|
838
|
+
* Known fields that are referenced but are parse-time or conceptual only
|
|
839
|
+
* These are common pseudo-fields that ACs reference but don't exist in schema
|
|
840
|
+
*/
|
|
841
|
+
const CONCEPTUAL_FIELDS = new Set([
|
|
842
|
+
"children", // Hierarchy is parse-time only
|
|
843
|
+
"parent", // Parent references are derived, not stored
|
|
844
|
+
"modules", // These are container structures in YAML, not item fields
|
|
845
|
+
"features",
|
|
846
|
+
"requirements",
|
|
847
|
+
"constraints",
|
|
848
|
+
"decisions",
|
|
849
|
+
"epics",
|
|
850
|
+
"themes",
|
|
851
|
+
"capabilities",
|
|
852
|
+
]);
|
|
853
|
+
/**
|
|
854
|
+
* Extract field reference patterns from AC text
|
|
855
|
+
* Looks for patterns like:
|
|
856
|
+
* - item.field
|
|
857
|
+
* - spec.field
|
|
858
|
+
* - task.field
|
|
859
|
+
* - status.field
|
|
860
|
+
* - spec_ref.field
|
|
861
|
+
* - the field
|
|
862
|
+
* - their field
|
|
863
|
+
* - item's field
|
|
864
|
+
*/
|
|
865
|
+
/**
|
|
866
|
+
* File extensions to exclude from field matching
|
|
867
|
+
* These are common file extensions that would otherwise match our patterns
|
|
868
|
+
*/
|
|
869
|
+
const FILE_EXTENSIONS = new Set([
|
|
870
|
+
"yaml",
|
|
871
|
+
"yml",
|
|
872
|
+
"json",
|
|
873
|
+
"js",
|
|
874
|
+
"ts",
|
|
875
|
+
"md",
|
|
876
|
+
"txt",
|
|
877
|
+
"html",
|
|
878
|
+
"css",
|
|
879
|
+
"log",
|
|
880
|
+
]);
|
|
881
|
+
function extractFieldReferences(text) {
|
|
882
|
+
const references = [];
|
|
883
|
+
// Pattern: context.field (e.g., item.status, spec_ref.children)
|
|
884
|
+
// This matches entity.property patterns in prose
|
|
885
|
+
const dotPattern = /\b(item|spec|task|status|spec_ref|task_ref|meta_ref|plan_ref)\.(\w+)/gi;
|
|
886
|
+
let match;
|
|
887
|
+
while ((match = dotPattern.exec(text)) !== null) {
|
|
888
|
+
const field = match[2].toLowerCase();
|
|
889
|
+
// Skip file extensions (e.g., spec.yaml, task.json)
|
|
890
|
+
if (FILE_EXTENSIONS.has(field)) {
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
references.push({ context: match[1].toLowerCase(), field });
|
|
894
|
+
}
|
|
895
|
+
// Pattern: "the <field>" when discussing items/tasks (common in ACs)
|
|
896
|
+
// Only match known schema-like words to avoid false positives
|
|
897
|
+
const thePattern = /\bthe\s+(status|children|parent|spec_ref|depends_on|traits|tags|notes|todos|acceptance_criteria)\b/gi;
|
|
898
|
+
while ((match = thePattern.exec(text)) !== null) {
|
|
899
|
+
references.push({ context: "item", field: match[1].toLowerCase() });
|
|
900
|
+
}
|
|
901
|
+
return references;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Check if a field exists in the appropriate schema
|
|
905
|
+
*/
|
|
906
|
+
function isValidSchemaField(context, field) {
|
|
907
|
+
// If it's a known conceptual field, it's technically invalid but we can give a specific message
|
|
908
|
+
if (CONCEPTUAL_FIELDS.has(field)) {
|
|
909
|
+
return { valid: false, reason: `'${field}' is a parse-time/conceptual field, not stored in schema` };
|
|
910
|
+
}
|
|
911
|
+
// Check based on context
|
|
912
|
+
switch (context) {
|
|
913
|
+
case "spec":
|
|
914
|
+
case "item":
|
|
915
|
+
if (SPEC_ITEM_FIELDS.has(field)) {
|
|
916
|
+
return { valid: true };
|
|
917
|
+
}
|
|
918
|
+
break;
|
|
919
|
+
case "task":
|
|
920
|
+
if (TASK_FIELDS.has(field)) {
|
|
921
|
+
return { valid: true };
|
|
922
|
+
}
|
|
923
|
+
break;
|
|
924
|
+
case "status":
|
|
925
|
+
// status.maturity, status.implementation are valid for specs
|
|
926
|
+
if (field === "maturity" || field === "implementation") {
|
|
927
|
+
return { valid: true };
|
|
928
|
+
}
|
|
929
|
+
break;
|
|
930
|
+
case "spec_ref":
|
|
931
|
+
case "task_ref":
|
|
932
|
+
case "meta_ref":
|
|
933
|
+
case "plan_ref":
|
|
934
|
+
// These are string references, not objects with fields
|
|
935
|
+
return { valid: false, reason: `'${context}' is a reference string, not an object with fields` };
|
|
936
|
+
}
|
|
937
|
+
// Check if field exists in either schema (might be ambiguous context)
|
|
938
|
+
if (SPEC_ITEM_FIELDS.has(field) || TASK_FIELDS.has(field)) {
|
|
939
|
+
return { valid: true };
|
|
940
|
+
}
|
|
941
|
+
return { valid: false, reason: `'${field}' is not a known schema field` };
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Check acceptance criteria for field references that don't exist in schema
|
|
945
|
+
* Catches drift between spec prose and implementation reality.
|
|
946
|
+
*/
|
|
947
|
+
export function checkACSchemaReferences(items) {
|
|
948
|
+
const warnings = [];
|
|
949
|
+
for (const item of items) {
|
|
950
|
+
if (!item.acceptance_criteria || item.acceptance_criteria.length === 0) {
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
const itemRef = item.slugs?.[0]
|
|
954
|
+
? `@${item.slugs[0]}`
|
|
955
|
+
: `@${item._ulid.slice(0, 8)}`;
|
|
956
|
+
for (const ac of item.acceptance_criteria) {
|
|
957
|
+
// Check all three parts of the AC for field references
|
|
958
|
+
const textToCheck = `${ac.given} ${ac.when} ${ac.then}`;
|
|
959
|
+
const fieldRefs = extractFieldReferences(textToCheck);
|
|
960
|
+
for (const { context, field } of fieldRefs) {
|
|
961
|
+
const result = isValidSchemaField(context, field);
|
|
962
|
+
if (!result.valid) {
|
|
963
|
+
warnings.push({
|
|
964
|
+
type: "ac_schema_field_mismatch",
|
|
965
|
+
itemRef,
|
|
966
|
+
itemTitle: item.title,
|
|
967
|
+
message: `AC ${ac.id} references '${context}.${field}' which doesn't exist in schema`,
|
|
968
|
+
details: result.reason,
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return warnings;
|
|
975
|
+
}
|
|
976
|
+
// ============================================================
|
|
977
|
+
// SKILL CONTENT VALIDATION
|
|
978
|
+
// ============================================================
|
|
979
|
+
/**
|
|
980
|
+
* Validate that skill meta entries have corresponding SKILL.md files.
|
|
981
|
+
* AC: @skill-content-model ac-3 - missing content file reports validation error
|
|
982
|
+
*/
|
|
983
|
+
async function validateSkillContentFiles(ctx, skills) {
|
|
984
|
+
const errors = [];
|
|
985
|
+
for (const skill of skills) {
|
|
986
|
+
const contentPath = getSkillContentPath(ctx, skill.id);
|
|
987
|
+
try {
|
|
988
|
+
await fs.access(contentPath);
|
|
989
|
+
}
|
|
990
|
+
catch {
|
|
991
|
+
// File doesn't exist - report validation error
|
|
992
|
+
errors.push({
|
|
993
|
+
file: skill._sourceFile || "kynetic.meta.yaml",
|
|
994
|
+
path: `skills.${skill.id}`,
|
|
995
|
+
message: `Skill '${skill.id}' is missing content file at ${contentPath}`,
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return errors;
|
|
1000
|
+
}
|
|
1001
|
+
// ============================================================
|
|
625
1002
|
// AUTOMATION VALIDATION
|
|
626
1003
|
// ============================================================
|
|
627
1004
|
/**
|
|
@@ -631,12 +1008,14 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
|
|
|
631
1008
|
function checkAutomationEligibility(tasks, index) {
|
|
632
1009
|
const warnings = [];
|
|
633
1010
|
for (const task of tasks) {
|
|
634
|
-
const taskRef = task.slugs?.[0]
|
|
1011
|
+
const taskRef = task.slugs?.[0]
|
|
1012
|
+
? `@${task.slugs[0]}`
|
|
1013
|
+
: `@${task._ulid.slice(0, 8)}`;
|
|
635
1014
|
// AC: @task-automation-eligibility ac-21
|
|
636
1015
|
// Warn if eligible but no spec_ref
|
|
637
|
-
if (task.automation ===
|
|
1016
|
+
if (task.automation === "eligible" && !task.spec_ref) {
|
|
638
1017
|
warnings.push({
|
|
639
|
-
type:
|
|
1018
|
+
type: "automation_eligible_no_spec",
|
|
640
1019
|
itemRef: taskRef,
|
|
641
1020
|
itemTitle: task.title,
|
|
642
1021
|
message: `Task ${taskRef} is automation: eligible but has no spec_ref - eligible tasks should have linked specs`,
|
|
@@ -644,11 +1023,11 @@ function checkAutomationEligibility(tasks, index) {
|
|
|
644
1023
|
}
|
|
645
1024
|
// AC: @task-automation-eligibility ac-23
|
|
646
1025
|
// Warn if eligible but spec_ref doesn't resolve
|
|
647
|
-
if (task.automation ===
|
|
1026
|
+
if (task.automation === "eligible" && task.spec_ref) {
|
|
648
1027
|
const specResult = index.resolve(task.spec_ref);
|
|
649
1028
|
if (!specResult.ok) {
|
|
650
1029
|
warnings.push({
|
|
651
|
-
type:
|
|
1030
|
+
type: "automation_eligible_no_spec",
|
|
652
1031
|
itemRef: taskRef,
|
|
653
1032
|
itemTitle: task.title,
|
|
654
1033
|
message: `Task ${taskRef} is automation: eligible but spec_ref ${task.spec_ref} cannot be resolved`,
|
|
@@ -659,6 +1038,81 @@ function checkAutomationEligibility(tasks, index) {
|
|
|
659
1038
|
return warnings;
|
|
660
1039
|
}
|
|
661
1040
|
// ============================================================
|
|
1041
|
+
// SKILL VALIDATION
|
|
1042
|
+
// ============================================================
|
|
1043
|
+
/**
|
|
1044
|
+
* Validate skill depends_on references
|
|
1045
|
+
* AC: @skill-validation ac-2 - broken depends_on ref reports warning
|
|
1046
|
+
*/
|
|
1047
|
+
function validateSkillDependsOn(skills, index) {
|
|
1048
|
+
const warnings = [];
|
|
1049
|
+
for (const skill of skills) {
|
|
1050
|
+
if (!skill.depends_on || skill.depends_on.length === 0) {
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
for (const depRef of skill.depends_on) {
|
|
1054
|
+
const result = index.resolve(depRef);
|
|
1055
|
+
if (!result.ok) {
|
|
1056
|
+
warnings.push({
|
|
1057
|
+
ref: depRef,
|
|
1058
|
+
sourceFile: skill._sourceFile,
|
|
1059
|
+
sourceUlid: skill._ulid,
|
|
1060
|
+
field: "depends_on",
|
|
1061
|
+
warning: "deprecated_target", // Reuse warning type for broken refs
|
|
1062
|
+
message: `Skill '${skill.id}' depends_on reference '${depRef}' cannot be resolved`,
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
// Check that the resolved item is actually a skill (uses _type discriminant)
|
|
1067
|
+
if (!isSkill(result.item)) {
|
|
1068
|
+
warnings.push({
|
|
1069
|
+
ref: depRef,
|
|
1070
|
+
sourceFile: skill._sourceFile,
|
|
1071
|
+
sourceUlid: skill._ulid,
|
|
1072
|
+
field: "depends_on",
|
|
1073
|
+
warning: "deprecated_target",
|
|
1074
|
+
message: `Skill '${skill.id}' depends_on reference '${depRef}' points to non-skill item`,
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return warnings;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Find orphaned skill directories (directories with no corresponding meta entry)
|
|
1084
|
+
* AC: @skill-validation ac-3 - orphaned directory reports warning
|
|
1085
|
+
*/
|
|
1086
|
+
async function findOrphanedSkillDirectories(ctx, skills) {
|
|
1087
|
+
const warnings = [];
|
|
1088
|
+
const skillsDir = path.join(ctx.specDir, "skills");
|
|
1089
|
+
try {
|
|
1090
|
+
// Check if skills directory exists
|
|
1091
|
+
await fs.access(skillsDir);
|
|
1092
|
+
// Read all directories in skills/
|
|
1093
|
+
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
1094
|
+
const directories = entries.filter(entry => entry.isDirectory());
|
|
1095
|
+
// Build set of skill IDs from meta
|
|
1096
|
+
const skillIds = new Set(skills.map(s => s.id));
|
|
1097
|
+
// Find orphaned directories
|
|
1098
|
+
for (const dir of directories) {
|
|
1099
|
+
if (!skillIds.has(dir.name)) {
|
|
1100
|
+
warnings.push({
|
|
1101
|
+
ref: `@${dir.name}`,
|
|
1102
|
+
sourceFile: path.join(skillsDir, dir.name),
|
|
1103
|
+
field: "directory",
|
|
1104
|
+
warning: "deprecated_target", // Reuse warning type
|
|
1105
|
+
message: `Orphaned skill directory '${dir.name}' has no corresponding meta entry`,
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
catch {
|
|
1111
|
+
// Skills directory doesn't exist - that's fine
|
|
1112
|
+
}
|
|
1113
|
+
return warnings;
|
|
1114
|
+
}
|
|
1115
|
+
// ============================================================
|
|
662
1116
|
// MAIN VALIDATION
|
|
663
1117
|
// ============================================================
|
|
664
1118
|
/**
|
|
@@ -699,9 +1153,10 @@ export async function validate(ctx, options = {}) {
|
|
|
699
1153
|
result.stats.itemsChecked += manifestItems.length;
|
|
700
1154
|
}
|
|
701
1155
|
// Find and validate task files
|
|
1156
|
+
// Exclude test fixtures which have their own self-contained references
|
|
702
1157
|
const taskFiles = await findTaskFiles(ctx.rootDir);
|
|
703
|
-
const specTaskFiles = await findTaskFiles(path.join(ctx.rootDir,
|
|
704
|
-
const allTaskFiles = [...new Set([...taskFiles, ...specTaskFiles])];
|
|
1158
|
+
const specTaskFiles = await findTaskFiles(path.join(ctx.rootDir, "spec"));
|
|
1159
|
+
const allTaskFiles = [...new Set([...taskFiles, ...specTaskFiles])].filter((f) => !f.includes("/fixtures/"));
|
|
705
1160
|
for (const taskFile of allTaskFiles) {
|
|
706
1161
|
if (runSchema) {
|
|
707
1162
|
const taskErrors = await validateTasksFile(taskFile);
|
|
@@ -715,7 +1170,7 @@ export async function validate(ctx, options = {}) {
|
|
|
715
1170
|
if (Array.isArray(raw)) {
|
|
716
1171
|
taskList = raw;
|
|
717
1172
|
}
|
|
718
|
-
else if (raw && typeof raw ===
|
|
1173
|
+
else if (raw && typeof raw === "object" && "tasks" in raw) {
|
|
719
1174
|
taskList = raw.tasks || [];
|
|
720
1175
|
}
|
|
721
1176
|
for (const t of taskList) {
|
|
@@ -762,13 +1217,46 @@ export async function validate(ctx, options = {}) {
|
|
|
762
1217
|
...metaCtx.workflows,
|
|
763
1218
|
...metaCtx.conventions,
|
|
764
1219
|
...metaCtx.observations,
|
|
1220
|
+
...metaCtx.skills, // Include skills for reference indexing
|
|
765
1221
|
];
|
|
1222
|
+
// Load plans for reference validation
|
|
1223
|
+
// AC: @plan-validation ac-10
|
|
1224
|
+
const allPlans = await loadPlans(ctx);
|
|
766
1225
|
// Reference validation
|
|
767
|
-
if (runRefs &&
|
|
768
|
-
|
|
1226
|
+
if (runRefs &&
|
|
1227
|
+
(allTasks.length > 0 ||
|
|
1228
|
+
allItems.length > 0 ||
|
|
1229
|
+
allMetaItems.length > 0 ||
|
|
1230
|
+
allPlans.length > 0)) {
|
|
1231
|
+
const index = new ReferenceIndex(allTasks, allItems, allMetaItems, allPlans);
|
|
769
1232
|
const refResult = validateRefs(index, allTasks, allItems);
|
|
770
|
-
|
|
771
|
-
|
|
1233
|
+
// AC: @config-validation ac-2 ac-3 — strict_refs controls error vs warning
|
|
1234
|
+
// When strictRefs is false, demote "not_found" ref errors to warnings
|
|
1235
|
+
if (options.strictRefs === false) {
|
|
1236
|
+
// Move not_found errors to warnings
|
|
1237
|
+
const notFoundErrors = refResult.errors.filter((e) => e.error === "not_found");
|
|
1238
|
+
const otherErrors = refResult.errors.filter((e) => e.error !== "not_found");
|
|
1239
|
+
result.refErrors = otherErrors;
|
|
1240
|
+
result.refWarnings = [
|
|
1241
|
+
...refResult.warnings,
|
|
1242
|
+
...notFoundErrors.map((e) => ({
|
|
1243
|
+
ref: e.ref,
|
|
1244
|
+
sourceFile: e.sourceFile,
|
|
1245
|
+
sourceUlid: e.sourceUlid,
|
|
1246
|
+
field: e.field,
|
|
1247
|
+
warning: "deprecated_target", // Reuse existing warning type
|
|
1248
|
+
message: e.message,
|
|
1249
|
+
})),
|
|
1250
|
+
];
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
// Default/strict behavior: not_found refs are errors
|
|
1254
|
+
result.refErrors = refResult.errors;
|
|
1255
|
+
result.refWarnings = refResult.warnings;
|
|
1256
|
+
}
|
|
1257
|
+
// AC: @skill-validation ac-2 - validate skill depends_on references
|
|
1258
|
+
const skillDependsOnWarnings = validateSkillDependsOn(metaCtx.skills, index);
|
|
1259
|
+
result.refWarnings.push(...skillDependsOnWarnings);
|
|
772
1260
|
// AC: @trait-edge-cases ac-2
|
|
773
1261
|
// Detect circular trait references
|
|
774
1262
|
result.traitCycleErrors = detectTraitCycles(allItems, index);
|
|
@@ -782,11 +1270,28 @@ export async function validate(ctx, options = {}) {
|
|
|
782
1270
|
if (runCompleteness) {
|
|
783
1271
|
// Build trait index for trait AC coverage validation
|
|
784
1272
|
const traitIndex = new TraitIndex(allItems, index);
|
|
785
|
-
|
|
1273
|
+
const completenessWarnings = await checkCompleteness(allItems, index, ctx.rootDir, traitIndex);
|
|
786
1274
|
// AC: @task-automation-eligibility ac-21, ac-23
|
|
787
1275
|
// Check automation eligibility warnings for tasks
|
|
788
1276
|
const automationWarnings = checkAutomationEligibility(allTasks, index);
|
|
789
|
-
|
|
1277
|
+
completenessWarnings.push(...automationWarnings);
|
|
1278
|
+
// AC: @config-validation ac-1 — require_acceptance promotes missing AC to errors
|
|
1279
|
+
if (options.requireAcceptance) {
|
|
1280
|
+
const missingAC = completenessWarnings.filter((w) => w.type === "missing_acceptance_criteria");
|
|
1281
|
+
const otherWarnings = completenessWarnings.filter((w) => w.type !== "missing_acceptance_criteria");
|
|
1282
|
+
// Promote missing AC warnings to schema errors
|
|
1283
|
+
for (const w of missingAC) {
|
|
1284
|
+
result.schemaErrors.push({
|
|
1285
|
+
file: "completeness",
|
|
1286
|
+
path: w.itemRef,
|
|
1287
|
+
message: w.message,
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
result.completenessWarnings = otherWarnings;
|
|
1291
|
+
}
|
|
1292
|
+
else {
|
|
1293
|
+
result.completenessWarnings = completenessWarnings;
|
|
1294
|
+
}
|
|
790
1295
|
}
|
|
791
1296
|
}
|
|
792
1297
|
// Meta manifest validation (AC-meta-manifest-2, AC-meta-manifest-3)
|
|
@@ -798,20 +1303,32 @@ export async function validate(ctx, options = {}) {
|
|
|
798
1303
|
workflows: metaCtx.workflows.length,
|
|
799
1304
|
conventions: metaCtx.conventions.length,
|
|
800
1305
|
observations: metaCtx.observations.length,
|
|
1306
|
+
skills: metaCtx.skills.length,
|
|
801
1307
|
};
|
|
802
1308
|
// Validate meta manifest schema with strict ULID validation
|
|
803
1309
|
if (runSchema) {
|
|
804
1310
|
const metaErrors = await validateMetaManifestFile(metaManifestPath);
|
|
805
1311
|
// Prefix all meta errors with "meta:"
|
|
806
1312
|
for (const err of metaErrors) {
|
|
807
|
-
err.path = err.path ? `meta:${err.path}` :
|
|
1313
|
+
err.path = err.path ? `meta:${err.path}` : "meta:";
|
|
808
1314
|
}
|
|
809
1315
|
result.schemaErrors.push(...metaErrors);
|
|
810
1316
|
result.stats.filesChecked++;
|
|
1317
|
+
// AC: @skill-content-model ac-3 - validate skill content files exist
|
|
1318
|
+
if (metaCtx.skills.length > 0) {
|
|
1319
|
+
const skillContentErrors = await validateSkillContentFiles(ctx, metaCtx.skills);
|
|
1320
|
+
result.schemaErrors.push(...skillContentErrors);
|
|
1321
|
+
}
|
|
1322
|
+
// AC: @skill-validation ac-3 - detect orphaned skill directories
|
|
1323
|
+
const orphanedSkillWarnings = await findOrphanedSkillDirectories(ctx, metaCtx.skills);
|
|
1324
|
+
result.refWarnings.push(...orphanedSkillWarnings);
|
|
811
1325
|
}
|
|
812
1326
|
}
|
|
813
1327
|
// Set valid flag
|
|
814
|
-
result.valid =
|
|
1328
|
+
result.valid =
|
|
1329
|
+
result.schemaErrors.length === 0 &&
|
|
1330
|
+
result.refErrors.length === 0 &&
|
|
1331
|
+
result.traitCycleErrors.length === 0;
|
|
815
1332
|
return result;
|
|
816
1333
|
}
|
|
817
1334
|
//# sourceMappingURL=validate.js.map
|