@kynetic-ai/spec 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -17
- package/dist/acp/client.d.ts +18 -4
- package/dist/acp/client.d.ts.map +1 -1
- package/dist/acp/client.js +44 -26
- package/dist/acp/client.js.map +1 -1
- package/dist/acp/framing.d.ts +2 -2
- package/dist/acp/framing.d.ts.map +1 -1
- package/dist/acp/framing.js +37 -29
- package/dist/acp/framing.js.map +1 -1
- package/dist/acp/index.d.ts +6 -7
- package/dist/acp/index.d.ts.map +1 -1
- package/dist/acp/index.js +3 -3
- package/dist/acp/index.js.map +1 -1
- package/dist/acp/types.d.ts +5 -5
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js +18 -18
- package/dist/acp/types.js.map +1 -1
- package/dist/agents/adapters.d.ts.map +1 -1
- package/dist/agents/adapters.js +24 -13
- package/dist/agents/adapters.js.map +1 -1
- package/dist/agents/index.d.ts +2 -2
- package/dist/agents/index.js +2 -2
- package/dist/agents/spawner.d.ts +4 -4
- package/dist/agents/spawner.d.ts.map +1 -1
- package/dist/agents/spawner.js +6 -6
- package/dist/agents/spawner.js.map +1 -1
- package/dist/cli/batch-context.d.ts +43 -0
- package/dist/cli/batch-context.d.ts.map +1 -0
- package/dist/cli/batch-context.js +93 -0
- package/dist/cli/batch-context.js.map +1 -0
- package/dist/cli/batch-exec.d.ts +116 -0
- package/dist/cli/batch-exec.d.ts.map +1 -0
- package/dist/cli/batch-exec.js +694 -0
- package/dist/cli/batch-exec.js.map +1 -0
- package/dist/cli/batch.d.ts +4 -2
- package/dist/cli/batch.d.ts.map +1 -1
- package/dist/cli/batch.js +15 -14
- package/dist/cli/batch.js.map +1 -1
- package/dist/cli/command-annotations.d.ts +23 -0
- package/dist/cli/command-annotations.d.ts.map +1 -0
- package/dist/cli/command-annotations.js +27 -0
- package/dist/cli/command-annotations.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +46 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +377 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/commands/batch.d.ts +20 -0
- package/dist/cli/commands/batch.d.ts.map +1 -0
- package/dist/cli/commands/batch.js +214 -0
- package/dist/cli/commands/batch.js.map +1 -0
- package/dist/cli/commands/clone-for-testing.d.ts +1 -1
- package/dist/cli/commands/clone-for-testing.d.ts.map +1 -1
- package/dist/cli/commands/clone-for-testing.js +37 -47
- package/dist/cli/commands/clone-for-testing.js.map +1 -1
- package/dist/cli/commands/derive.d.ts +1 -1
- package/dist/cli/commands/derive.d.ts.map +1 -1
- package/dist/cli/commands/derive.js +140 -88
- package/dist/cli/commands/derive.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +11 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +152 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/export.d.ts +12 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +134 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/help.d.ts +1 -1
- package/dist/cli/commands/help.d.ts.map +1 -1
- package/dist/cli/commands/help.js +163 -37
- package/dist/cli/commands/help.js.map +1 -1
- package/dist/cli/commands/inbox.d.ts +1 -1
- package/dist/cli/commands/inbox.d.ts.map +1 -1
- package/dist/cli/commands/inbox.js +178 -56
- package/dist/cli/commands/inbox.js.map +1 -1
- package/dist/cli/commands/index.d.ts +31 -19
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +31 -19
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +5 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +108 -57
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/item.d.ts +1 -1
- package/dist/cli/commands/item.d.ts.map +1 -1
- package/dist/cli/commands/item.js +557 -274
- package/dist/cli/commands/item.js.map +1 -1
- package/dist/cli/commands/link.d.ts +1 -1
- package/dist/cli/commands/link.d.ts.map +1 -1
- package/dist/cli/commands/link.js +55 -46
- package/dist/cli/commands/link.js.map +1 -1
- package/dist/cli/commands/log.d.ts +1 -1
- package/dist/cli/commands/log.d.ts.map +1 -1
- package/dist/cli/commands/log.js +57 -51
- package/dist/cli/commands/log.js.map +1 -1
- package/dist/cli/commands/merge-driver.d.ts +19 -0
- package/dist/cli/commands/merge-driver.d.ts.map +1 -0
- package/dist/cli/commands/merge-driver.js +398 -0
- package/dist/cli/commands/merge-driver.js.map +1 -0
- package/dist/cli/commands/meta.d.ts +1 -1
- package/dist/cli/commands/meta.d.ts.map +1 -1
- package/dist/cli/commands/meta.js +533 -399
- package/dist/cli/commands/meta.js.map +1 -1
- package/dist/cli/commands/module.d.ts +1 -1
- package/dist/cli/commands/module.d.ts.map +1 -1
- package/dist/cli/commands/module.js +30 -25
- package/dist/cli/commands/module.js.map +1 -1
- package/dist/cli/commands/plan-import.d.ts +11 -0
- package/dist/cli/commands/plan-import.d.ts.map +1 -0
- package/dist/cli/commands/plan-import.js +516 -0
- package/dist/cli/commands/plan-import.js.map +1 -0
- package/dist/cli/commands/plan.d.ts +10 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +421 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/ralph.d.ts +1 -1
- package/dist/cli/commands/ralph.d.ts.map +1 -1
- package/dist/cli/commands/ralph.js +1097 -169
- package/dist/cli/commands/ralph.js.map +1 -1
- package/dist/cli/commands/refs.d.ts +13 -0
- package/dist/cli/commands/refs.d.ts.map +1 -0
- package/dist/cli/commands/refs.js +283 -0
- package/dist/cli/commands/refs.js.map +1 -0
- package/dist/cli/commands/search.d.ts +1 -1
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/search.js +199 -37
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/serve.d.ts +10 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +491 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/session.d.ts +25 -6
- package/dist/cli/commands/session.d.ts.map +1 -1
- package/dist/cli/commands/session.js +811 -127
- package/dist/cli/commands/session.js.map +1 -1
- package/dist/cli/commands/setup-seeding.d.ts +81 -0
- package/dist/cli/commands/setup-seeding.d.ts.map +1 -0
- package/dist/cli/commands/setup-seeding.js +292 -0
- package/dist/cli/commands/setup-seeding.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +77 -3
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +1233 -274
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/shadow.d.ts +1 -1
- package/dist/cli/commands/shadow.d.ts.map +1 -1
- package/dist/cli/commands/shadow.js +70 -50
- package/dist/cli/commands/shadow.js.map +1 -1
- package/dist/cli/commands/skill-crud.d.ts +58 -0
- package/dist/cli/commands/skill-crud.d.ts.map +1 -0
- package/dist/cli/commands/skill-crud.js +753 -0
- package/dist/cli/commands/skill-crud.js.map +1 -0
- package/dist/cli/commands/skill-diff.d.ts +27 -0
- package/dist/cli/commands/skill-diff.d.ts.map +1 -0
- package/dist/cli/commands/skill-diff.js +840 -0
- package/dist/cli/commands/skill-diff.js.map +1 -0
- package/dist/cli/commands/skill-install.d.ts +53 -0
- package/dist/cli/commands/skill-install.d.ts.map +1 -0
- package/dist/cli/commands/skill-install.js +452 -0
- package/dist/cli/commands/skill-install.js.map +1 -0
- package/dist/cli/commands/skill.d.ts +20 -0
- package/dist/cli/commands/skill.d.ts.map +1 -0
- package/dist/cli/commands/skill.js +36 -0
- package/dist/cli/commands/skill.js.map +1 -0
- package/dist/cli/commands/task.d.ts +1 -1
- package/dist/cli/commands/task.d.ts.map +1 -1
- package/dist/cli/commands/task.js +569 -346
- package/dist/cli/commands/task.js.map +1 -1
- package/dist/cli/commands/tasks.d.ts +26 -1
- package/dist/cli/commands/tasks.d.ts.map +1 -1
- package/dist/cli/commands/tasks.js +227 -122
- package/dist/cli/commands/tasks.js.map +1 -1
- package/dist/cli/commands/trait.d.ts +1 -1
- package/dist/cli/commands/trait.d.ts.map +1 -1
- package/dist/cli/commands/trait.js +166 -101
- package/dist/cli/commands/trait.js.map +1 -1
- package/dist/cli/commands/triage.d.ts +7 -0
- package/dist/cli/commands/triage.d.ts.map +1 -0
- package/dist/cli/commands/triage.js +569 -0
- package/dist/cli/commands/triage.js.map +1 -0
- package/dist/cli/commands/util.d.ts +7 -0
- package/dist/cli/commands/util.d.ts.map +1 -0
- package/dist/cli/commands/util.js +30 -0
- package/dist/cli/commands/util.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +1 -1
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/cli/commands/validate.js +264 -83
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/commands/workflow.d.ts +16 -0
- package/dist/cli/commands/workflow.d.ts.map +1 -0
- package/dist/cli/commands/workflow.js +851 -0
- package/dist/cli/commands/workflow.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +7 -0
- package/dist/cli/exit-codes.d.ts.map +1 -1
- package/dist/cli/exit-codes.js +26 -18
- package/dist/cli/exit-codes.js.map +1 -1
- package/dist/cli/help/content.d.ts.map +1 -1
- package/dist/cli/help/content.js +86 -71
- package/dist/cli/help/content.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +131 -19
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/introspection.d.ts +6 -2
- package/dist/cli/introspection.d.ts.map +1 -1
- package/dist/cli/introspection.js +11 -8
- package/dist/cli/introspection.js.map +1 -1
- package/dist/cli/output.d.ts +64 -4
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +235 -85
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/parse-utils.d.ts +21 -0
- package/dist/cli/parse-utils.d.ts.map +1 -0
- package/dist/cli/parse-utils.js +32 -0
- package/dist/cli/parse-utils.js.map +1 -0
- package/dist/cli/pid-utils.d.ts +72 -0
- package/dist/cli/pid-utils.d.ts.map +1 -0
- package/dist/cli/pid-utils.js +174 -0
- package/dist/cli/pid-utils.js.map +1 -0
- package/dist/cli/suggest.d.ts.map +1 -1
- package/dist/cli/suggest.js +1 -2
- package/dist/cli/suggest.js.map +1 -1
- package/dist/cli/validators.d.ts +43 -0
- package/dist/cli/validators.d.ts.map +1 -0
- package/dist/cli/validators.js +84 -0
- package/dist/cli/validators.js.map +1 -0
- package/dist/daemon/index.ts +52 -0
- package/dist/daemon/middleware/project-context.ts +126 -0
- package/dist/daemon/pid.ts +179 -0
- package/dist/daemon/project-context.ts +343 -0
- package/dist/daemon/routes/inbox.ts +164 -0
- package/dist/daemon/routes/items.ts +322 -0
- package/dist/daemon/routes/meta.ts +118 -0
- package/dist/daemon/routes/projects.ts +162 -0
- package/dist/daemon/routes/tasks.ts +327 -0
- package/dist/daemon/routes/triage.ts +468 -0
- package/dist/daemon/routes/validation.ts +248 -0
- package/dist/daemon/server.ts +408 -0
- package/dist/daemon/watcher.ts +195 -0
- package/dist/daemon/websocket/handler.ts +138 -0
- package/dist/daemon/websocket/heartbeat.ts +71 -0
- package/dist/daemon/websocket/pubsub.ts +125 -0
- package/dist/daemon/websocket/types.ts +66 -0
- package/dist/export/html.d.ts +19 -0
- package/dist/export/html.d.ts.map +1 -0
- package/dist/export/html.js +239 -0
- package/dist/export/html.js.map +1 -0
- package/dist/export/index.d.ts +10 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +10 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/json.d.ts +24 -0
- package/dist/export/json.d.ts.map +1 -0
- package/dist/export/json.js +198 -0
- package/dist/export/json.js.map +1 -0
- package/dist/export/triage.d.ts +51 -0
- package/dist/export/triage.d.ts.map +1 -0
- package/dist/export/triage.js +83 -0
- package/dist/export/triage.js.map +1 -0
- package/dist/export/types.d.ts +122 -0
- package/dist/export/types.d.ts.map +1 -0
- package/dist/export/types.js +9 -0
- package/dist/export/types.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/lib/claude-plugin-registry.d.ts +66 -0
- package/dist/lib/claude-plugin-registry.d.ts.map +1 -0
- package/dist/lib/claude-plugin-registry.js +318 -0
- package/dist/lib/claude-plugin-registry.js.map +1 -0
- package/dist/merge/arrays.d.ts +87 -0
- package/dist/merge/arrays.d.ts.map +1 -0
- package/dist/merge/arrays.js +164 -0
- package/dist/merge/arrays.js.map +1 -0
- package/dist/merge/file-type.d.ts +32 -0
- package/dist/merge/file-type.d.ts.map +1 -0
- package/dist/merge/file-type.js +70 -0
- package/dist/merge/file-type.js.map +1 -0
- package/dist/merge/index.d.ts +14 -0
- package/dist/merge/index.d.ts.map +1 -0
- package/dist/merge/index.js +11 -0
- package/dist/merge/index.js.map +1 -0
- package/dist/merge/objects.d.ts +46 -0
- package/dist/merge/objects.d.ts.map +1 -0
- package/dist/merge/objects.js +193 -0
- package/dist/merge/objects.js.map +1 -0
- package/dist/merge/parse.d.ts +23 -0
- package/dist/merge/parse.d.ts.map +1 -0
- package/dist/merge/parse.js +78 -0
- package/dist/merge/parse.js.map +1 -0
- package/dist/merge/resolve.d.ts +66 -0
- package/dist/merge/resolve.d.ts.map +1 -0
- package/dist/merge/resolve.js +189 -0
- package/dist/merge/resolve.js.map +1 -0
- package/dist/merge/types.d.ts +82 -0
- package/dist/merge/types.d.ts.map +1 -0
- package/dist/merge/types.js +8 -0
- package/dist/merge/types.js.map +1 -0
- package/dist/parser/agent-data-sections.d.ts +53 -0
- package/dist/parser/agent-data-sections.d.ts.map +1 -0
- package/dist/parser/agent-data-sections.js +118 -0
- package/dist/parser/agent-data-sections.js.map +1 -0
- package/dist/parser/alignment.d.ts +4 -4
- package/dist/parser/alignment.d.ts.map +1 -1
- package/dist/parser/alignment.js +27 -22
- package/dist/parser/alignment.js.map +1 -1
- package/dist/parser/assess.d.ts +5 -5
- package/dist/parser/assess.d.ts.map +1 -1
- package/dist/parser/assess.js +36 -32
- package/dist/parser/assess.js.map +1 -1
- package/dist/parser/config.d.ts +351 -0
- package/dist/parser/config.d.ts.map +1 -0
- package/dist/parser/config.js +326 -0
- package/dist/parser/config.js.map +1 -0
- package/dist/parser/convention-validation.d.ts +1 -1
- package/dist/parser/convention-validation.d.ts.map +1 -1
- package/dist/parser/convention-validation.js +21 -16
- package/dist/parser/convention-validation.js.map +1 -1
- package/dist/parser/coverage-cache.d.ts +49 -0
- package/dist/parser/coverage-cache.d.ts.map +1 -0
- package/dist/parser/coverage-cache.js +123 -0
- package/dist/parser/coverage-cache.js.map +1 -0
- package/dist/parser/daemon-status.d.ts +37 -0
- package/dist/parser/daemon-status.d.ts.map +1 -0
- package/dist/parser/daemon-status.js +67 -0
- package/dist/parser/daemon-status.js.map +1 -0
- package/dist/parser/doctor.d.ts +107 -0
- package/dist/parser/doctor.d.ts.map +1 -0
- package/dist/parser/doctor.js +366 -0
- package/dist/parser/doctor.js.map +1 -0
- package/dist/parser/fix.d.ts +1 -1
- package/dist/parser/fix.d.ts.map +1 -1
- package/dist/parser/fix.js +31 -27
- package/dist/parser/fix.js.map +1 -1
- package/dist/parser/index.d.ts +16 -11
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +16 -11
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/items.d.ts +8 -2
- package/dist/parser/items.d.ts.map +1 -1
- package/dist/parser/items.js +71 -35
- package/dist/parser/items.js.map +1 -1
- package/dist/parser/meta.d.ts +167 -9
- package/dist/parser/meta.d.ts.map +1 -1
- package/dist/parser/meta.js +379 -46
- package/dist/parser/meta.js.map +1 -1
- package/dist/parser/plan-document.d.ts +189 -0
- package/dist/parser/plan-document.d.ts.map +1 -0
- package/dist/parser/plan-document.js +340 -0
- package/dist/parser/plan-document.js.map +1 -0
- package/dist/parser/plans.d.ts +59 -0
- package/dist/parser/plans.d.ts.map +1 -0
- package/dist/parser/plans.js +239 -0
- package/dist/parser/plans.js.map +1 -0
- package/dist/parser/refs.d.ts +22 -9
- package/dist/parser/refs.d.ts.map +1 -1
- package/dist/parser/refs.js +102 -50
- package/dist/parser/refs.js.map +1 -1
- package/dist/parser/setup-status.d.ts +71 -0
- package/dist/parser/setup-status.d.ts.map +1 -0
- package/dist/parser/setup-status.js +269 -0
- package/dist/parser/setup-status.js.map +1 -0
- package/dist/parser/shadow.d.ts +150 -19
- package/dist/parser/shadow.d.ts.map +1 -1
- package/dist/parser/shadow.js +548 -187
- package/dist/parser/shadow.js.map +1 -1
- package/dist/parser/skill-render.d.ts +317 -0
- package/dist/parser/skill-render.d.ts.map +1 -0
- package/dist/parser/skill-render.js +943 -0
- package/dist/parser/skill-render.js.map +1 -0
- package/dist/parser/traits.d.ts +3 -3
- package/dist/parser/traits.d.ts.map +1 -1
- package/dist/parser/traits.js +2 -2
- package/dist/parser/traits.js.map +1 -1
- package/dist/parser/validate-skills.d.ts +32 -0
- package/dist/parser/validate-skills.d.ts.map +1 -0
- package/dist/parser/validate-skills.js +202 -0
- package/dist/parser/validate-skills.js.map +1 -0
- package/dist/parser/validate.d.ts +45 -3
- package/dist/parser/validate.d.ts.map +1 -1
- package/dist/parser/validate.js +622 -105
- package/dist/parser/validate.js.map +1 -1
- package/dist/parser/yaml.d.ts +83 -19
- package/dist/parser/yaml.d.ts.map +1 -1
- package/dist/parser/yaml.js +478 -173
- package/dist/parser/yaml.js.map +1 -1
- package/dist/ralph/cli-renderer.d.ts +8 -1
- package/dist/ralph/cli-renderer.d.ts.map +1 -1
- package/dist/ralph/cli-renderer.js +105 -34
- package/dist/ralph/cli-renderer.js.map +1 -1
- package/dist/ralph/events.d.ts +10 -10
- package/dist/ralph/events.d.ts.map +1 -1
- package/dist/ralph/events.js +277 -98
- package/dist/ralph/events.js.map +1 -1
- package/dist/ralph/index.d.ts +5 -2
- package/dist/ralph/index.d.ts.map +1 -1
- package/dist/ralph/index.js +9 -3
- package/dist/ralph/index.js.map +1 -1
- package/dist/ralph/loop-errors.d.ts +83 -0
- package/dist/ralph/loop-errors.d.ts.map +1 -0
- package/dist/ralph/loop-errors.js +150 -0
- package/dist/ralph/loop-errors.js.map +1 -0
- package/dist/ralph/subagent.d.ts +83 -0
- package/dist/ralph/subagent.d.ts.map +1 -0
- package/dist/ralph/subagent.js +174 -0
- package/dist/ralph/subagent.js.map +1 -0
- package/dist/ralph/wrap-up.d.ts +125 -0
- package/dist/ralph/wrap-up.d.ts.map +1 -0
- package/dist/ralph/wrap-up.js +270 -0
- package/dist/ralph/wrap-up.js.map +1 -0
- package/dist/schema/batch.d.ts +95 -0
- package/dist/schema/batch.d.ts.map +1 -0
- package/dist/schema/batch.js +24 -0
- package/dist/schema/batch.js.map +1 -0
- package/dist/schema/common.d.ts +2 -2
- package/dist/schema/common.d.ts.map +1 -1
- package/dist/schema/common.js +34 -31
- package/dist/schema/common.js.map +1 -1
- package/dist/schema/inbox.d.ts +12 -12
- package/dist/schema/inbox.js +4 -4
- package/dist/schema/inbox.js.map +1 -1
- package/dist/schema/index.d.ts +8 -5
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +8 -5
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/meta.d.ts +1454 -27
- package/dist/schema/meta.d.ts.map +1 -1
- package/dist/schema/meta.js +198 -21
- package/dist/schema/meta.js.map +1 -1
- package/dist/schema/plan.d.ts +285 -0
- package/dist/schema/plan.d.ts.map +1 -0
- package/dist/schema/plan.js +81 -0
- package/dist/schema/plan.js.map +1 -0
- package/dist/schema/spec.d.ts +72 -33
- package/dist/schema/spec.d.ts.map +1 -1
- package/dist/schema/spec.js +22 -9
- package/dist/schema/spec.js.map +1 -1
- package/dist/schema/task.d.ts +172 -161
- package/dist/schema/task.d.ts.map +1 -1
- package/dist/schema/task.js +21 -12
- package/dist/schema/task.js.map +1 -1
- package/dist/schema/triage.d.ts +266 -0
- package/dist/schema/triage.d.ts.map +1 -0
- package/dist/schema/triage.js +134 -0
- package/dist/schema/triage.js.map +1 -0
- package/dist/sessions/index.d.ts +2 -2
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +3 -3
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/store.d.ts +233 -1
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +628 -31
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +10 -10
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js +10 -9
- package/dist/sessions/types.js.map +1 -1
- package/dist/strings/errors.d.ts +51 -0
- package/dist/strings/errors.d.ts.map +1 -1
- package/dist/strings/errors.js +136 -106
- package/dist/strings/errors.js.map +1 -1
- package/dist/strings/guidance.d.ts.map +1 -1
- package/dist/strings/guidance.js +16 -16
- package/dist/strings/guidance.js.map +1 -1
- package/dist/strings/index.d.ts +4 -4
- package/dist/strings/index.d.ts.map +1 -1
- package/dist/strings/index.js +4 -4
- package/dist/strings/index.js.map +1 -1
- package/dist/strings/labels.d.ts +4 -0
- package/dist/strings/labels.d.ts.map +1 -1
- package/dist/strings/labels.js +45 -41
- package/dist/strings/labels.js.map +1 -1
- package/dist/strings/validation.d.ts.map +1 -1
- package/dist/strings/validation.js +71 -71
- package/dist/strings/validation.js.map +1 -1
- package/dist/utils/commit.d.ts +1 -1
- package/dist/utils/commit.d.ts.map +1 -1
- package/dist/utils/commit.js +28 -26
- package/dist/utils/commit.js.map +1 -1
- package/dist/utils/git.d.ts +1 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +40 -38
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/grep.js +11 -11
- package/dist/utils/grep.js.map +1 -1
- package/dist/utils/index.d.ts +7 -7
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +10 -10
- package/dist/utils/time.js.map +1 -1
- package/package.json +28 -5
- package/plugin/.claude-plugin/marketplace.json +17 -0
- package/plugin/.claude-plugin/plugin.json +5 -0
- package/plugin/plugins/kspec/skills/help/SKILL.md +42 -0
- package/plugin/plugins/kspec/skills/triage/SKILL.md +206 -0
- package/plugin/plugins/kspec/skills/triage/docs/automation.md +120 -0
- package/plugin/plugins/kspec/skills/triage/docs/inbox.md +144 -0
- package/plugin/plugins/kspec/skills/triage/docs/observations.md +85 -0
- package/templates/agents-sections/01-quick-start.md +22 -0
- package/templates/agents-sections/02-shadow-branch.md +34 -0
- package/templates/agents-sections/03-task-lifecycle.md +48 -0
- package/templates/agents-sections/04-pr-workflow.md +17 -0
- package/templates/agents-sections/05-commit-convention.md +27 -0
- package/templates/agents-sections/06-ralph-loop.md +45 -0
- package/templates/hooks/pre-commit +34 -0
- package/templates/skills/help/SKILL.md +37 -0
- package/templates/skills/manifest.yaml +15 -0
- package/templates/skills/triage/SKILL.md +199 -0
- package/templates/skills/triage/docs/automation.md +120 -0
- package/templates/skills/triage/docs/inbox.md +144 -0
- package/templates/skills/triage/docs/observations.md +85 -0
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides context for starting/resuming work sessions.
|
|
5
5
|
*/
|
|
6
|
-
import chalk from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { EXIT_CODES } from
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { getReadyTasks, initContext, loadAllItems, loadAllTasks, loadInboxItems, loadSessionContext, ReferenceIndex, } from "../../parser/index.js";
|
|
8
|
+
import { ShadowError, shadowPull, } from "../../parser/shadow.js";
|
|
9
|
+
import { getAllSessionLogSummaries, getSessionLogDetail, resolveSessionId, readEvents, readSessionContext, computeSessionLogStats, computeToolUsageStats, computeTimePeriodStats, searchSessionEvents, } from "../../sessions/store.js";
|
|
10
|
+
import { errors, hints, sessionHeaders, sessionPrompt, } from "../../strings/index.js";
|
|
11
|
+
import { formatCommitGuidance, formatRelativeTime, getCurrentBranch, getRecentCommits, getWorkingTreeStatus, isGitRepo, parseTimeSpec, } from "../../utils/index.js";
|
|
12
|
+
import { EXIT_CODES } from "../exit-codes.js";
|
|
13
|
+
import { error, isJsonMode, isNoteSuperseded, output } from "../output.js";
|
|
13
14
|
// ─── Data Gathering ──────────────────────────────────────────────────────────
|
|
14
15
|
function toActiveTaskSummary(task, index) {
|
|
15
16
|
const lastNote = task.notes.length > 0 ? task.notes[task.notes.length - 1] : null;
|
|
16
|
-
const incompleteTodos = task.todos.filter(t => !t.done).length;
|
|
17
|
+
const incompleteTodos = task.todos.filter((t) => !t.done).length;
|
|
17
18
|
return {
|
|
18
19
|
ref: index.shortUlid(task._ulid),
|
|
19
20
|
title: task.title,
|
|
@@ -35,14 +36,14 @@ function toReadyTaskSummary(task, index) {
|
|
|
35
36
|
tags: task.tags,
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
|
-
function toBlockedTaskSummary(task,
|
|
39
|
+
function toBlockedTaskSummary(task, _allTasks, index) {
|
|
39
40
|
// Find unmet dependencies
|
|
40
41
|
const unmetDeps = [];
|
|
41
42
|
for (const depRef of task.depends_on) {
|
|
42
43
|
const result = index.resolve(depRef);
|
|
43
44
|
if (result.ok) {
|
|
44
45
|
const depItem = result.item;
|
|
45
|
-
if (
|
|
46
|
+
if ("status" in depItem && depItem.status !== "completed") {
|
|
46
47
|
unmetDeps.push(depRef);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -58,7 +59,7 @@ function toCompletedTaskSummary(task, index) {
|
|
|
58
59
|
return {
|
|
59
60
|
ref: index.shortUlid(task._ulid),
|
|
60
61
|
title: task.title,
|
|
61
|
-
completed_at: task.completed_at ||
|
|
62
|
+
completed_at: task.completed_at || "",
|
|
62
63
|
closed_reason: task.closed_reason || null,
|
|
63
64
|
origin: task.origin,
|
|
64
65
|
};
|
|
@@ -66,15 +67,25 @@ function toCompletedTaskSummary(task, index) {
|
|
|
66
67
|
function collectRecentNotes(tasks, index, options) {
|
|
67
68
|
const allNotes = [];
|
|
68
69
|
for (const task of tasks) {
|
|
70
|
+
// Only include notes from in_progress, pending_review, or completed tasks
|
|
71
|
+
const taskStatus = task.status;
|
|
72
|
+
if (!["in_progress", "pending_review", "needs_work", "completed"].includes(taskStatus)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
69
75
|
for (const note of task.notes) {
|
|
70
76
|
const noteDate = new Date(note.created_at);
|
|
71
77
|
// Filter by since date if provided
|
|
72
78
|
if (options.since && noteDate < options.since) {
|
|
73
79
|
continue;
|
|
74
80
|
}
|
|
81
|
+
// Filter out superseded notes
|
|
82
|
+
if (isNoteSuperseded(note, task.notes)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
75
85
|
allNotes.push({
|
|
76
86
|
task_ref: index.shortUlid(task._ulid),
|
|
77
87
|
task_title: task.title,
|
|
88
|
+
task_status: taskStatus,
|
|
78
89
|
note_ulid: note._ulid.slice(0, 8),
|
|
79
90
|
created_at: note.created_at,
|
|
80
91
|
author: note.author || null,
|
|
@@ -113,7 +124,7 @@ function collectIncompleteTodos(tasks, index, options) {
|
|
|
113
124
|
* Gather session context data
|
|
114
125
|
*/
|
|
115
126
|
export async function gatherSessionContext(ctx, options) {
|
|
116
|
-
const limit = parseInt(options.limit ||
|
|
127
|
+
const limit = parseInt(options.limit || "10", 10);
|
|
117
128
|
const sinceDate = options.since ? parseTimeSpec(options.since) : null;
|
|
118
129
|
const showGit = options.git !== false; // default true
|
|
119
130
|
// Load all data
|
|
@@ -124,43 +135,63 @@ export async function gatherSessionContext(ctx, options) {
|
|
|
124
135
|
// Compute stats
|
|
125
136
|
const stats = {
|
|
126
137
|
total_tasks: allTasks.length,
|
|
127
|
-
in_progress: allTasks.filter((t) => t.status ===
|
|
128
|
-
|
|
138
|
+
in_progress: allTasks.filter((t) => t.status === "in_progress").length,
|
|
139
|
+
needs_work: allTasks.filter((t) => t.status === "needs_work").length,
|
|
140
|
+
pending_review: allTasks.filter((t) => t.status === "pending_review")
|
|
141
|
+
.length,
|
|
129
142
|
ready: getReadyTasks(allTasks).length,
|
|
130
|
-
blocked: allTasks.filter((t) => t.status ===
|
|
131
|
-
completed: allTasks.filter((t) => t.status ===
|
|
143
|
+
blocked: allTasks.filter((t) => t.status === "blocked").length,
|
|
144
|
+
completed: allTasks.filter((t) => t.status === "completed").length,
|
|
132
145
|
inbox_items: inboxItems.length,
|
|
133
146
|
};
|
|
134
|
-
// Get active tasks
|
|
147
|
+
// Get active tasks (in_progress + needs_work, optionally filtered to automation-eligible only)
|
|
148
|
+
// AC: @cli-ralph ac-16
|
|
135
149
|
const activeTasks = allTasks
|
|
136
|
-
.filter((t) => t.status ===
|
|
150
|
+
.filter((t) => t.status === "in_progress" || t.status === "needs_work")
|
|
151
|
+
.filter((t) => !options.eligible || t.automation === "eligible")
|
|
137
152
|
.sort((a, b) => a.priority - b.priority)
|
|
138
153
|
.slice(0, options.full ? undefined : limit)
|
|
139
154
|
.map((t) => toActiveTaskSummary(t, index));
|
|
140
155
|
// Get pending review tasks
|
|
141
156
|
const pendingReviewTasks = allTasks
|
|
142
|
-
.filter((t) => t.status ===
|
|
157
|
+
.filter((t) => t.status === "pending_review")
|
|
143
158
|
.sort((a, b) => a.priority - b.priority)
|
|
144
159
|
.slice(0, options.full ? undefined : limit)
|
|
145
160
|
.map((t) => toActiveTaskSummary(t, index));
|
|
146
|
-
// Get recent notes from active tasks
|
|
147
|
-
|
|
161
|
+
// Get recent notes from active, pending_review, and recently completed tasks
|
|
162
|
+
// AC: @cmd-session-start ac-1, ac-2
|
|
163
|
+
// Collect notes per-status first to prevent one status from starving others
|
|
164
|
+
const noteLimitPerStatus = options.full ? limit : Math.ceil(limit / 3);
|
|
165
|
+
const inProgressNotes = collectRecentNotes(allTasks.filter((t) => t.status === "in_progress" || t.status === "needs_work"), index, { limit: noteLimitPerStatus, since: sinceDate });
|
|
166
|
+
const pendingReviewNotes = collectRecentNotes(allTasks.filter((t) => t.status === "pending_review"), index, { limit: noteLimitPerStatus, since: sinceDate });
|
|
167
|
+
const recentlyCompletedForNotes = allTasks
|
|
168
|
+
.filter((t) => t.status === "completed" && t.completed_at)
|
|
169
|
+
.sort((a, b) => {
|
|
170
|
+
const aDate = new Date(a.completed_at || 0);
|
|
171
|
+
const bDate = new Date(b.completed_at || 0);
|
|
172
|
+
return bDate.getTime() - aDate.getTime();
|
|
173
|
+
})
|
|
174
|
+
.slice(0, 5); // Last 3-5 completed tasks per AC-2
|
|
175
|
+
const completedNotes = collectRecentNotes(recentlyCompletedForNotes, index, { limit: noteLimitPerStatus, since: sinceDate });
|
|
176
|
+
// Combine notes from all statuses, preserving representation from each
|
|
177
|
+
const recentNotes = [...inProgressNotes, ...pendingReviewNotes, ...completedNotes]
|
|
178
|
+
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
148
179
|
// Get incomplete todos from active tasks
|
|
149
|
-
const activeTodos = collectIncompleteTodos(allTasks.filter((t) => t.status ===
|
|
180
|
+
const activeTodos = collectIncompleteTodos(allTasks.filter((t) => t.status === "in_progress" || t.status === "needs_work"), index, { limit: options.full ? limit * 2 : limit });
|
|
150
181
|
// Get ready tasks (optionally filtered to automation-eligible only)
|
|
151
182
|
const readyTasks = getReadyTasks(allTasks)
|
|
152
|
-
.filter((t) => !options.eligible || t.automation ===
|
|
183
|
+
.filter((t) => !options.eligible || t.automation === "eligible")
|
|
153
184
|
.slice(0, options.full ? undefined : limit)
|
|
154
185
|
.map((t) => toReadyTaskSummary(t, index));
|
|
155
186
|
// Get blocked tasks
|
|
156
187
|
const blockedTasks = allTasks
|
|
157
|
-
.filter((t) => t.status ===
|
|
188
|
+
.filter((t) => t.status === "blocked")
|
|
158
189
|
.slice(0, options.full ? undefined : limit)
|
|
159
190
|
.map((t) => toBlockedTaskSummary(t, allTasks, index));
|
|
160
191
|
// Get recently completed tasks
|
|
161
192
|
const recentlyCompleted = allTasks
|
|
162
193
|
.filter((t) => {
|
|
163
|
-
if (t.status !==
|
|
194
|
+
if (t.status !== "completed" || !t.completed_at)
|
|
164
195
|
return false;
|
|
165
196
|
const completedDate = new Date(t.completed_at);
|
|
166
197
|
if (sinceDate && completedDate < sinceDate)
|
|
@@ -225,6 +256,30 @@ export async function gatherSessionContext(ctx, options) {
|
|
|
225
256
|
stats,
|
|
226
257
|
};
|
|
227
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Get iteration stats - tasks completed/started since a given time.
|
|
261
|
+
* AC: @ralph-task-limit ac-detection
|
|
262
|
+
*/
|
|
263
|
+
export async function getIterationStats(ctx, since) {
|
|
264
|
+
const allTasks = await loadAllTasks(ctx);
|
|
265
|
+
const completedSince = allTasks.filter((t) => {
|
|
266
|
+
if (t.status !== "completed" || !t.completed_at)
|
|
267
|
+
return false;
|
|
268
|
+
return new Date(t.completed_at) >= since;
|
|
269
|
+
});
|
|
270
|
+
const startedSince = allTasks.filter((t) => {
|
|
271
|
+
if (!t.started_at)
|
|
272
|
+
return false;
|
|
273
|
+
return new Date(t.started_at) >= since;
|
|
274
|
+
});
|
|
275
|
+
// Use first slug or ULID prefix as ref
|
|
276
|
+
const getRef = (t) => t.slugs.length > 0 ? `@${t.slugs[0]}` : `@${t._ulid.slice(0, 8)}`;
|
|
277
|
+
return {
|
|
278
|
+
tasks_completed: completedSince.length,
|
|
279
|
+
tasks_started: startedSince.length,
|
|
280
|
+
completed_refs: completedSince.map(getRef),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
228
283
|
/**
|
|
229
284
|
* Perform session checkpoint - check for uncommitted work before ending session.
|
|
230
285
|
*
|
|
@@ -241,11 +296,13 @@ export async function performCheckpoint(ctx, options) {
|
|
|
241
296
|
// Load tasks
|
|
242
297
|
const allTasks = await loadAllTasks(ctx);
|
|
243
298
|
// Check for in-progress tasks
|
|
244
|
-
const inProgressTasks = allTasks.filter((t) => t.status ===
|
|
299
|
+
const inProgressTasks = allTasks.filter((t) => t.status === "in_progress");
|
|
245
300
|
for (const task of inProgressTasks) {
|
|
246
|
-
const ref = task.slugs[0]
|
|
301
|
+
const ref = task.slugs[0]
|
|
302
|
+
? `@${task.slugs[0]}`
|
|
303
|
+
: `@${task._ulid.slice(0, 8)}`;
|
|
247
304
|
issues.push({
|
|
248
|
-
type:
|
|
305
|
+
type: "in_progress_task",
|
|
249
306
|
description: `Task ${ref} is still in progress: ${task.title}`,
|
|
250
307
|
details: {
|
|
251
308
|
ref,
|
|
@@ -257,7 +314,7 @@ export async function performCheckpoint(ctx, options) {
|
|
|
257
314
|
const incompleteTodos = task.todos.filter((t) => !t.done);
|
|
258
315
|
for (const todo of incompleteTodos) {
|
|
259
316
|
issues.push({
|
|
260
|
-
type:
|
|
317
|
+
type: "incomplete_todo",
|
|
261
318
|
description: `Incomplete todo on ${ref}: ${todo.text}`,
|
|
262
319
|
details: {
|
|
263
320
|
task_ref: ref,
|
|
@@ -275,7 +332,7 @@ export async function performCheckpoint(ctx, options) {
|
|
|
275
332
|
workingTree.unstaged.length +
|
|
276
333
|
workingTree.untracked.length;
|
|
277
334
|
issues.push({
|
|
278
|
-
type:
|
|
335
|
+
type: "uncommitted_changes",
|
|
279
336
|
description: `${changeCount} uncommitted changes in working tree`,
|
|
280
337
|
details: {
|
|
281
338
|
staged: workingTree.staged.length,
|
|
@@ -287,14 +344,17 @@ export async function performCheckpoint(ctx, options) {
|
|
|
287
344
|
}
|
|
288
345
|
// Build instructions based on issues
|
|
289
346
|
if (issues.length > 0 && !options.force) {
|
|
290
|
-
instructions.push('
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const
|
|
347
|
+
instructions.push("If you've been instructed to ignore this message or are working on");
|
|
348
|
+
instructions.push("unrelated things to any in progress tasks then disregard this message,");
|
|
349
|
+
instructions.push("otherwise before ending this session, please:");
|
|
350
|
+
const hasInProgress = issues.some((i) => i.type === "in_progress_task");
|
|
351
|
+
const hasUncommitted = issues.some((i) => i.type === "uncommitted_changes");
|
|
352
|
+
const hasIncompleteTodos = issues.some((i) => i.type === "incomplete_todo");
|
|
294
353
|
let step = 1;
|
|
295
354
|
if (hasInProgress) {
|
|
296
|
-
instructions.push(`${step++}.
|
|
297
|
-
instructions.push(`${step++}.
|
|
355
|
+
instructions.push(`${step++}. Read in-progress task notes to get full context of the current task status`);
|
|
356
|
+
instructions.push(`${step++}. Add notes documenting current state if any context is missing from this session`);
|
|
357
|
+
instructions.push(`${step++}. Complete the task if you've completed the objectives and no AC are left uncovered\notherwise leave it in progress for a future session`);
|
|
298
358
|
}
|
|
299
359
|
if (hasIncompleteTodos) {
|
|
300
360
|
instructions.push(`${step++}. Complete or acknowledge incomplete todos on active tasks`);
|
|
@@ -305,16 +365,17 @@ export async function performCheckpoint(ctx, options) {
|
|
|
305
365
|
if (inProgressTasks.length > 0) {
|
|
306
366
|
const task = inProgressTasks[0];
|
|
307
367
|
const guidance = formatCommitGuidance(task, { wip: true });
|
|
308
|
-
instructions.push(
|
|
309
|
-
instructions.push(
|
|
368
|
+
instructions.push("");
|
|
369
|
+
instructions.push("Suggested WIP commit:");
|
|
310
370
|
instructions.push(` ${guidance.message}`);
|
|
311
|
-
instructions.push(
|
|
371
|
+
instructions.push("");
|
|
312
372
|
for (const trailer of guidance.trailers) {
|
|
313
373
|
instructions.push(` ${trailer}`);
|
|
314
374
|
}
|
|
315
375
|
}
|
|
316
376
|
}
|
|
317
|
-
instructions.push(
|
|
377
|
+
instructions.push("");
|
|
378
|
+
instructions.push("Use: kspec task @task to get current task state");
|
|
318
379
|
instructions.push('Use: kspec task note @task "Progress notes..." to document state');
|
|
319
380
|
instructions.push('Use: kspec task complete @task --reason "Summary" if task is done');
|
|
320
381
|
}
|
|
@@ -329,7 +390,7 @@ export async function performCheckpoint(ctx, options) {
|
|
|
329
390
|
message = `[kspec] Session checkpoint: ${issues.length} issue(s) acknowledged - allowing stop`;
|
|
330
391
|
}
|
|
331
392
|
else if (ok) {
|
|
332
|
-
message =
|
|
393
|
+
message = "[kspec] Session checkpoint passed - ready to end session";
|
|
333
394
|
}
|
|
334
395
|
else {
|
|
335
396
|
message = `[kspec] Session checkpoint: ${issues.length} issue(s) need attention`;
|
|
@@ -348,17 +409,17 @@ function formatCheckpointResult(result) {
|
|
|
348
409
|
}
|
|
349
410
|
else {
|
|
350
411
|
console.log(chalk.yellow(result.message));
|
|
351
|
-
console.log(
|
|
412
|
+
console.log("");
|
|
352
413
|
for (const issue of result.issues) {
|
|
353
|
-
const icon = issue.type ===
|
|
354
|
-
? chalk.yellow(
|
|
355
|
-
: issue.type ===
|
|
356
|
-
? chalk.blue(
|
|
357
|
-
: chalk.gray(
|
|
414
|
+
const icon = issue.type === "uncommitted_changes"
|
|
415
|
+
? chalk.yellow("⚠")
|
|
416
|
+
: issue.type === "in_progress_task"
|
|
417
|
+
? chalk.blue("●")
|
|
418
|
+
: chalk.gray("○");
|
|
358
419
|
console.log(` ${icon} ${issue.description}`);
|
|
359
420
|
}
|
|
360
421
|
if (result.instructions.length > 0) {
|
|
361
|
-
console.log(
|
|
422
|
+
console.log("");
|
|
362
423
|
for (const instruction of result.instructions) {
|
|
363
424
|
console.log(chalk.gray(instruction));
|
|
364
425
|
}
|
|
@@ -379,26 +440,27 @@ function formatSessionContext(ctx, options) {
|
|
|
379
440
|
// Stats summary
|
|
380
441
|
const pendingReviewNote = ctx.stats.pending_review > 0
|
|
381
442
|
? `${ctx.stats.pending_review} awaiting review, `
|
|
382
|
-
:
|
|
383
|
-
const inboxNote = ctx.stats.inbox_items > 0
|
|
384
|
-
? ` | Inbox: ${ctx.stats.inbox_items}`
|
|
385
|
-
: '';
|
|
443
|
+
: "";
|
|
444
|
+
const inboxNote = ctx.stats.inbox_items > 0 ? ` | Inbox: ${ctx.stats.inbox_items}` : "";
|
|
386
445
|
console.log(chalk.gray(`Tasks: ${ctx.stats.in_progress} active, ${pendingReviewNote}${ctx.stats.ready} ready, ` +
|
|
387
446
|
`${ctx.stats.blocked} blocked, ${ctx.stats.completed}/${ctx.stats.total_tasks} completed${inboxNote}`));
|
|
388
447
|
// Session context section (focus, threads, questions)
|
|
389
|
-
if (ctx.context &&
|
|
390
|
-
|
|
448
|
+
if (ctx.context &&
|
|
449
|
+
(ctx.context.focus ||
|
|
450
|
+
ctx.context.threads.length > 0 ||
|
|
451
|
+
ctx.context.open_questions.length > 0)) {
|
|
452
|
+
console.log("\n--- Session Context ---");
|
|
391
453
|
if (ctx.context.focus) {
|
|
392
|
-
console.log(` ${chalk.cyan(
|
|
454
|
+
console.log(` ${chalk.cyan("Focus:")} ${ctx.context.focus}`);
|
|
393
455
|
}
|
|
394
456
|
if (ctx.context.threads.length > 0) {
|
|
395
|
-
console.log(` ${chalk.cyan(
|
|
457
|
+
console.log(` ${chalk.cyan("Active Threads:")}`);
|
|
396
458
|
for (const thread of ctx.context.threads) {
|
|
397
459
|
console.log(` - ${thread}`);
|
|
398
460
|
}
|
|
399
461
|
}
|
|
400
462
|
if (ctx.context.open_questions.length > 0) {
|
|
401
|
-
console.log(` ${chalk.cyan(
|
|
463
|
+
console.log(` ${chalk.cyan("Open Questions:")}`);
|
|
402
464
|
for (const question of ctx.context.open_questions) {
|
|
403
465
|
console.log(` - ${question}`);
|
|
404
466
|
}
|
|
@@ -410,11 +472,11 @@ function formatSessionContext(ctx, options) {
|
|
|
410
472
|
for (const task of ctx.active_tasks) {
|
|
411
473
|
const started = task.started_at
|
|
412
474
|
? chalk.gray(` (started ${formatRelativeTime(new Date(task.started_at))})`)
|
|
413
|
-
:
|
|
475
|
+
: "";
|
|
414
476
|
const priority = task.priority <= 2
|
|
415
477
|
? chalk.red(`P${task.priority}`)
|
|
416
478
|
: chalk.gray(`P${task.priority}`);
|
|
417
|
-
console.log(` ${chalk.blue(
|
|
479
|
+
console.log(` ${chalk.blue("[in_progress]")} ${priority} ${task.ref} ${task.title}${started}`);
|
|
418
480
|
}
|
|
419
481
|
}
|
|
420
482
|
else {
|
|
@@ -427,7 +489,7 @@ function formatSessionContext(ctx, options) {
|
|
|
427
489
|
const priority = task.priority <= 2
|
|
428
490
|
? chalk.red(`P${task.priority}`)
|
|
429
491
|
: chalk.gray(`P${task.priority}`);
|
|
430
|
-
console.log(` ${chalk.yellow(
|
|
492
|
+
console.log(` ${chalk.yellow("[pending_review]")} ${priority} ${task.ref} ${task.title}`);
|
|
431
493
|
}
|
|
432
494
|
}
|
|
433
495
|
// Recently completed section
|
|
@@ -436,46 +498,73 @@ function formatSessionContext(ctx, options) {
|
|
|
436
498
|
const observationPromotedTasks = [];
|
|
437
499
|
for (const task of ctx.recently_completed) {
|
|
438
500
|
const completedAge = formatRelativeTime(new Date(task.completed_at));
|
|
439
|
-
let reason =
|
|
501
|
+
let reason = "";
|
|
440
502
|
if (task.closed_reason) {
|
|
441
503
|
const maxLen = isBrief ? 60 : 120;
|
|
442
504
|
const truncated = task.closed_reason.length > maxLen
|
|
443
|
-
? task.closed_reason.slice(0, maxLen).trim()
|
|
505
|
+
? `${task.closed_reason.slice(0, maxLen).trim()}...`
|
|
444
506
|
: task.closed_reason;
|
|
445
507
|
reason = chalk.gray(` - ${truncated}`);
|
|
446
508
|
}
|
|
447
|
-
console.log(` ${chalk.green(
|
|
509
|
+
console.log(` ${chalk.green("[completed]")} ${task.ref} ${task.title} ${chalk.gray(`(${completedAge})`)}${reason}`);
|
|
448
510
|
// Track tasks that came from observations
|
|
449
|
-
if (task.origin ===
|
|
511
|
+
if (task.origin === "observation_promotion") {
|
|
450
512
|
observationPromotedTasks.push(task.ref);
|
|
451
513
|
}
|
|
452
514
|
}
|
|
453
515
|
// Show reminder about resolving observations
|
|
454
516
|
if (observationPromotedTasks.length > 0) {
|
|
455
|
-
console.log(chalk.yellow(`\n ℹ Consider resolving linked observations: ${observationPromotedTasks.join(
|
|
517
|
+
console.log(chalk.yellow(`\n ℹ Consider resolving linked observations: ${observationPromotedTasks.join(", ")}`));
|
|
456
518
|
console.log(chalk.gray(` Run: kspec meta observations --pending-resolution`));
|
|
457
519
|
}
|
|
458
520
|
}
|
|
459
|
-
// Recent notes section
|
|
521
|
+
// Recent notes section - grouped by task status
|
|
522
|
+
// AC: @cmd-session-start ac-1, ac-2
|
|
460
523
|
if (ctx.recent_notes.length > 0) {
|
|
461
524
|
console.log(`\n${sessionHeaders.recentNotes}`);
|
|
462
|
-
|
|
525
|
+
// Group notes by task status
|
|
526
|
+
const inProgressNotes = ctx.recent_notes.filter((n) => n.task_status === "in_progress");
|
|
527
|
+
const pendingReviewNotes = ctx.recent_notes.filter((n) => n.task_status === "pending_review");
|
|
528
|
+
const completedNotes = ctx.recent_notes.filter((n) => n.task_status === "completed");
|
|
529
|
+
// Helper to format a single note
|
|
530
|
+
const formatNote = (note) => {
|
|
463
531
|
const age = formatRelativeTime(new Date(note.created_at));
|
|
464
|
-
const author = note.author ? chalk.gray(` by ${note.author}`) :
|
|
465
|
-
console.log(`
|
|
532
|
+
const author = note.author ? chalk.gray(` by ${note.author}`) : "";
|
|
533
|
+
console.log(` ${chalk.yellow(age)} on ${note.task_ref}${author}:`);
|
|
466
534
|
// Truncate content in brief mode
|
|
467
535
|
let content = note.content.trim();
|
|
468
536
|
if (isBrief && content.length > 200) {
|
|
469
|
-
content = content.slice(0, 200).trim()
|
|
537
|
+
content = `${content.slice(0, 200).trim()}...`;
|
|
470
538
|
}
|
|
471
539
|
// Indent content, limit lines in brief mode
|
|
472
|
-
const lines = content.split(
|
|
540
|
+
const lines = content.split("\n");
|
|
473
541
|
const maxLines = isBrief ? 3 : lines.length;
|
|
474
542
|
for (const line of lines.slice(0, maxLines)) {
|
|
475
|
-
console.log(`
|
|
543
|
+
console.log(` ${chalk.white(line)}`);
|
|
476
544
|
}
|
|
477
545
|
if (isBrief && lines.length > maxLines) {
|
|
478
|
-
console.log(chalk.gray(`
|
|
546
|
+
console.log(chalk.gray(` ... (${lines.length - maxLines} more lines)`));
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
// AC: @cmd-session-start ac-1 - In Progress notes
|
|
550
|
+
if (inProgressNotes.length > 0) {
|
|
551
|
+
console.log(` ${chalk.blue("In Progress:")}`);
|
|
552
|
+
for (const note of inProgressNotes) {
|
|
553
|
+
formatNote(note);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
// AC: @cmd-session-start ac-1 - Pending Review notes (grouped separately)
|
|
557
|
+
if (pendingReviewNotes.length > 0) {
|
|
558
|
+
console.log(` ${chalk.yellow("Pending Review:")}`);
|
|
559
|
+
for (const note of pendingReviewNotes) {
|
|
560
|
+
formatNote(note);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// AC: @cmd-session-start ac-2 - Recently Completed notes
|
|
564
|
+
if (completedNotes.length > 0) {
|
|
565
|
+
console.log(` ${chalk.green("Recently Completed:")}`);
|
|
566
|
+
for (const note of completedNotes) {
|
|
567
|
+
formatNote(note);
|
|
479
568
|
}
|
|
480
569
|
}
|
|
481
570
|
}
|
|
@@ -483,7 +572,7 @@ function formatSessionContext(ctx, options) {
|
|
|
483
572
|
if (ctx.active_todos.length > 0) {
|
|
484
573
|
console.log(`\n${sessionHeaders.incompleteTodos}`);
|
|
485
574
|
for (const todo of ctx.active_todos) {
|
|
486
|
-
console.log(` ${chalk.yellow(
|
|
575
|
+
console.log(` ${chalk.yellow("[ ]")} ${todo.task_ref}#${todo.id}: ${todo.text}`);
|
|
487
576
|
}
|
|
488
577
|
}
|
|
489
578
|
// Ready tasks section
|
|
@@ -493,7 +582,7 @@ function formatSessionContext(ctx, options) {
|
|
|
493
582
|
const priority = task.priority <= 2
|
|
494
583
|
? chalk.red(`P${task.priority}`)
|
|
495
584
|
: chalk.gray(`P${task.priority}`);
|
|
496
|
-
const tags = task.tags.length > 0 ? chalk.cyan(` #${task.tags.join(
|
|
585
|
+
const tags = task.tags.length > 0 ? chalk.cyan(` #${task.tags.join(" #")}`) : "";
|
|
497
586
|
console.log(` ${priority} ${task.ref} ${task.title}${tags}`);
|
|
498
587
|
}
|
|
499
588
|
}
|
|
@@ -501,12 +590,12 @@ function formatSessionContext(ctx, options) {
|
|
|
501
590
|
if (ctx.blocked_tasks.length > 0) {
|
|
502
591
|
console.log(`\n${sessionHeaders.blocked}`);
|
|
503
592
|
for (const task of ctx.blocked_tasks) {
|
|
504
|
-
console.log(` ${chalk.red(
|
|
593
|
+
console.log(` ${chalk.red("[blocked]")} ${task.ref} ${task.title}`);
|
|
505
594
|
if (task.blocked_by.length > 0) {
|
|
506
|
-
console.log(chalk.gray(` Blockers: ${task.blocked_by.join(
|
|
595
|
+
console.log(chalk.gray(` Blockers: ${task.blocked_by.join(", ")}`));
|
|
507
596
|
}
|
|
508
597
|
if (task.unmet_deps.length > 0) {
|
|
509
|
-
console.log(chalk.gray(` Waiting on: ${task.unmet_deps.join(
|
|
598
|
+
console.log(chalk.gray(` Waiting on: ${task.unmet_deps.join(", ")}`));
|
|
510
599
|
}
|
|
511
600
|
}
|
|
512
601
|
}
|
|
@@ -523,12 +612,12 @@ function formatSessionContext(ctx, options) {
|
|
|
523
612
|
console.log(`\n${sessionHeaders.inbox}`);
|
|
524
613
|
for (const item of ctx.inbox_items) {
|
|
525
614
|
const age = formatRelativeTime(new Date(item.created_at));
|
|
526
|
-
const author = item.added_by ? ` by ${item.added_by}` :
|
|
527
|
-
const tags = item.tags.length > 0 ? chalk.cyan(` [${item.tags.join(
|
|
615
|
+
const author = item.added_by ? ` by ${item.added_by}` : "";
|
|
616
|
+
const tags = item.tags.length > 0 ? chalk.cyan(` [${item.tags.join(", ")}]`) : "";
|
|
528
617
|
// Truncate text in brief mode
|
|
529
618
|
let text = item.text;
|
|
530
619
|
if (isBrief && text.length > 60) {
|
|
531
|
-
text = text.slice(0, 60).trim()
|
|
620
|
+
text = `${text.slice(0, 60).trim()}...`;
|
|
532
621
|
}
|
|
533
622
|
console.log(` ${chalk.magenta(item.ref)} ${chalk.gray(`(${age}${author})`)}${tags}`);
|
|
534
623
|
console.log(` ${text}`);
|
|
@@ -539,22 +628,22 @@ function formatSessionContext(ctx, options) {
|
|
|
539
628
|
if (ctx.working_tree && !ctx.working_tree.clean) {
|
|
540
629
|
console.log(`\n${sessionHeaders.workingTree}`);
|
|
541
630
|
if (ctx.working_tree.staged.length > 0) {
|
|
542
|
-
console.log(chalk.green(
|
|
631
|
+
console.log(chalk.green(" Staged:"));
|
|
543
632
|
for (const file of ctx.working_tree.staged) {
|
|
544
633
|
console.log(` ${chalk.green(file.status[0].toUpperCase())} ${file.path}`);
|
|
545
634
|
}
|
|
546
635
|
}
|
|
547
636
|
if (ctx.working_tree.unstaged.length > 0) {
|
|
548
|
-
console.log(chalk.red(
|
|
637
|
+
console.log(chalk.red(" Modified:"));
|
|
549
638
|
for (const file of ctx.working_tree.unstaged) {
|
|
550
639
|
console.log(` ${chalk.red(file.status[0].toUpperCase())} ${file.path}`);
|
|
551
640
|
}
|
|
552
641
|
}
|
|
553
642
|
if (ctx.working_tree.untracked.length > 0) {
|
|
554
|
-
console.log(chalk.gray(
|
|
643
|
+
console.log(chalk.gray(" Untracked:"));
|
|
555
644
|
const limit = isBrief ? 5 : ctx.working_tree.untracked.length;
|
|
556
645
|
for (const path of ctx.working_tree.untracked.slice(0, limit)) {
|
|
557
|
-
console.log(` ${chalk.gray(
|
|
646
|
+
console.log(` ${chalk.gray("?")} ${path}`);
|
|
558
647
|
}
|
|
559
648
|
if (isBrief && ctx.working_tree.untracked.length > limit) {
|
|
560
649
|
console.log(chalk.gray(` ... and ${ctx.working_tree.untracked.length - limit} more`));
|
|
@@ -568,19 +657,19 @@ function formatSessionContext(ctx, options) {
|
|
|
568
657
|
const quickCommands = [];
|
|
569
658
|
if (ctx.active_tasks.length > 0) {
|
|
570
659
|
const ref = ctx.active_tasks[0].ref;
|
|
571
|
-
quickCommands.push(`kspec task note @${ref} "Progress..." ${chalk.gray(
|
|
572
|
-
quickCommands.push(`kspec task complete @${ref} --reason "..." ${chalk.gray(
|
|
660
|
+
quickCommands.push(`kspec task note @${ref} "Progress..." ${chalk.gray("# document work")}`);
|
|
661
|
+
quickCommands.push(`kspec task complete @${ref} --reason "..." ${chalk.gray("# finish task")}`);
|
|
573
662
|
}
|
|
574
663
|
else if (ctx.ready_tasks.length > 0) {
|
|
575
664
|
const ref = ctx.ready_tasks[0].ref;
|
|
576
|
-
quickCommands.push(`kspec task start @${ref} ${chalk.gray(
|
|
665
|
+
quickCommands.push(`kspec task start @${ref} ${chalk.gray("# begin work")}`);
|
|
577
666
|
}
|
|
578
667
|
if (ctx.inbox_items.length > 0) {
|
|
579
668
|
const ref = ctx.inbox_items[0].ref;
|
|
580
|
-
quickCommands.push(`kspec inbox promote @${ref} --title "..." ${chalk.gray(
|
|
669
|
+
quickCommands.push(`kspec inbox promote @${ref} --title "..." ${chalk.gray("# convert to task")}`);
|
|
581
670
|
}
|
|
582
671
|
if (ctx.working_tree && !ctx.working_tree.clean) {
|
|
583
|
-
quickCommands.push(`git add . && git commit -m "..." ${chalk.gray(
|
|
672
|
+
quickCommands.push(`git add . && git commit -m "..." ${chalk.gray("# commit changes")}`);
|
|
584
673
|
}
|
|
585
674
|
if (quickCommands.length > 0) {
|
|
586
675
|
console.log(`\n${sessionHeaders.quickCommands}`);
|
|
@@ -588,24 +677,24 @@ function formatSessionContext(ctx, options) {
|
|
|
588
677
|
console.log(` ${hint}`);
|
|
589
678
|
}
|
|
590
679
|
}
|
|
591
|
-
console.log(
|
|
680
|
+
console.log(""); // Final newline
|
|
592
681
|
}
|
|
593
682
|
// ─── Command Registration ────────────────────────────────────────────────────
|
|
594
683
|
async function sessionStartAction(options) {
|
|
595
684
|
try {
|
|
596
685
|
const ctx = await initContext();
|
|
597
|
-
// AC-2
|
|
686
|
+
// AC: @shadow-sync ac-2 - Pull remote changes before showing session context
|
|
598
687
|
let syncResult = null;
|
|
599
688
|
if (ctx.shadow?.enabled) {
|
|
600
689
|
syncResult = await shadowPull(ctx.shadow.worktreeDir);
|
|
601
|
-
// AC-3
|
|
690
|
+
// AC: @shadow-sync ac-3 - Warn about conflicts but continue with local state
|
|
602
691
|
if (syncResult.hadConflict) {
|
|
603
|
-
console.log(chalk.yellow(
|
|
604
|
-
console.log(chalk.gray(
|
|
605
|
-
console.log(
|
|
692
|
+
console.log(chalk.yellow("⚠ Shadow sync conflict detected. Run `kspec shadow resolve` to fix."));
|
|
693
|
+
console.log(chalk.gray(" Continuing with local state..."));
|
|
694
|
+
console.log("");
|
|
606
695
|
}
|
|
607
696
|
else if (syncResult.pulled) {
|
|
608
|
-
console.log(chalk.gray(
|
|
697
|
+
console.log(chalk.gray("ℹ Synced shadow branch from remote"));
|
|
609
698
|
}
|
|
610
699
|
}
|
|
611
700
|
const sessionCtx = await gatherSessionContext(ctx, options);
|
|
@@ -625,20 +714,20 @@ async function readStdinIfAvailable() {
|
|
|
625
714
|
return null;
|
|
626
715
|
}
|
|
627
716
|
return new Promise((resolve) => {
|
|
628
|
-
let data =
|
|
717
|
+
let data = "";
|
|
629
718
|
const timeout = setTimeout(() => {
|
|
630
719
|
process.stdin.removeAllListeners();
|
|
631
720
|
resolve(data || null);
|
|
632
721
|
}, 100); // 100ms timeout for stdin
|
|
633
|
-
process.stdin.setEncoding(
|
|
634
|
-
process.stdin.on(
|
|
722
|
+
process.stdin.setEncoding("utf8");
|
|
723
|
+
process.stdin.on("data", (chunk) => {
|
|
635
724
|
data += chunk;
|
|
636
725
|
});
|
|
637
|
-
process.stdin.on(
|
|
726
|
+
process.stdin.on("end", () => {
|
|
638
727
|
clearTimeout(timeout);
|
|
639
728
|
resolve(data || null);
|
|
640
729
|
});
|
|
641
|
-
process.stdin.on(
|
|
730
|
+
process.stdin.on("error", () => {
|
|
642
731
|
clearTimeout(timeout);
|
|
643
732
|
resolve(null);
|
|
644
733
|
});
|
|
@@ -686,10 +775,14 @@ async function sessionCheckpointAction(options) {
|
|
|
686
775
|
if (isJsonMode()) {
|
|
687
776
|
if (!result.ok) {
|
|
688
777
|
// Build reason message with issues and instructions
|
|
689
|
-
const issueLines = result.issues
|
|
690
|
-
|
|
778
|
+
const issueLines = result.issues
|
|
779
|
+
.map((i) => `- ${i.description}`)
|
|
780
|
+
.join("\n");
|
|
781
|
+
const instructionLines = result.instructions
|
|
782
|
+
.filter((i) => i.trim())
|
|
783
|
+
.join("\n");
|
|
691
784
|
const reason = `${result.message}\n\nIssues:\n${issueLines}\n\n${instructionLines}`;
|
|
692
|
-
console.log(JSON.stringify({ decision:
|
|
785
|
+
console.log(JSON.stringify({ decision: "block", reason }));
|
|
693
786
|
}
|
|
694
787
|
// If ok, exit silently (Claude Code expects no output when allowing stop)
|
|
695
788
|
}
|
|
@@ -701,45 +794,636 @@ async function sessionCheckpointAction(options) {
|
|
|
701
794
|
}
|
|
702
795
|
}
|
|
703
796
|
catch (err) {
|
|
797
|
+
// Handle RUNNING_FROM_SHADOW gracefully - skip with warning instead of erroring
|
|
798
|
+
// This happens when the stop hook runs while cwd is inside .kspec/ directory
|
|
799
|
+
if (err instanceof ShadowError && err.code === "RUNNING_FROM_SHADOW") {
|
|
800
|
+
if (!isJsonMode()) {
|
|
801
|
+
console.log(chalk.yellow("[kspec] Session checkpoint skipped - running from inside .kspec/ directory"));
|
|
802
|
+
}
|
|
803
|
+
// Allow stop to proceed (exit successfully, no JSON output blocks the stop)
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
704
806
|
error(errors.failures.runCheckpoint, err);
|
|
705
807
|
process.exit(EXIT_CODES.ERROR);
|
|
706
808
|
}
|
|
707
809
|
}
|
|
810
|
+
const VALID_SORT_FIELDS = [
|
|
811
|
+
"started_at",
|
|
812
|
+
"duration",
|
|
813
|
+
"events",
|
|
814
|
+
"iterations",
|
|
815
|
+
"tasks_completed",
|
|
816
|
+
];
|
|
817
|
+
/**
|
|
818
|
+
* Format a duration in milliseconds to a human-readable string.
|
|
819
|
+
*/
|
|
820
|
+
function formatDuration(ms) {
|
|
821
|
+
if (ms < 0)
|
|
822
|
+
return "—";
|
|
823
|
+
const totalSec = Math.floor(ms / 1000);
|
|
824
|
+
const hours = Math.floor(totalSec / 3600);
|
|
825
|
+
const minutes = Math.floor((totalSec % 3600) / 60);
|
|
826
|
+
if (hours > 0) {
|
|
827
|
+
return `${hours}h ${minutes}m`;
|
|
828
|
+
}
|
|
829
|
+
if (minutes > 0) {
|
|
830
|
+
return `${minutes}m`;
|
|
831
|
+
}
|
|
832
|
+
return `${totalSec}s`;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Sort session summaries by the specified field.
|
|
836
|
+
* Default: started_at descending.
|
|
837
|
+
*
|
|
838
|
+
* AC: @session-log-list ac-5
|
|
839
|
+
*/
|
|
840
|
+
function sortSessions(sessions, sortField) {
|
|
841
|
+
return [...sessions].sort((a, b) => {
|
|
842
|
+
switch (sortField) {
|
|
843
|
+
case "started_at":
|
|
844
|
+
return (new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
|
|
845
|
+
case "duration":
|
|
846
|
+
return b.duration_ms - a.duration_ms;
|
|
847
|
+
case "events":
|
|
848
|
+
return b.event_count - a.event_count;
|
|
849
|
+
case "iterations":
|
|
850
|
+
return b.iteration_count - a.iteration_count;
|
|
851
|
+
case "tasks_completed":
|
|
852
|
+
return b.tasks_completed - a.tasks_completed;
|
|
853
|
+
default:
|
|
854
|
+
return (new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Format the session log list as a table.
|
|
860
|
+
*
|
|
861
|
+
* AC: @session-log-list ac-1
|
|
862
|
+
*/
|
|
863
|
+
function formatSessionLogList(sessions) {
|
|
864
|
+
if (sessions.length === 0) {
|
|
865
|
+
// AC: @session-log-list ac-6
|
|
866
|
+
console.log("No sessions found.");
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
// Table header
|
|
870
|
+
console.log(chalk.gray(`${"ID".padEnd(10)} ${"Status".padEnd(11)} ${"Agent".padEnd(20)} ${"Started".padEnd(16)} ${"Duration".padEnd(10)} ${"Events".padEnd(8)} ${"Iters".padEnd(7)} Tasks`));
|
|
871
|
+
console.log(chalk.gray("─".repeat(95)));
|
|
872
|
+
for (const s of sessions) {
|
|
873
|
+
const id = s.id.slice(0, 8);
|
|
874
|
+
const statusColor = s.status === "completed"
|
|
875
|
+
? chalk.green
|
|
876
|
+
: s.status === "active"
|
|
877
|
+
? chalk.blue
|
|
878
|
+
: chalk.yellow;
|
|
879
|
+
const status = statusColor(s.status.padEnd(11));
|
|
880
|
+
const agent = s.agent_type.slice(0, 20).padEnd(20);
|
|
881
|
+
const started = formatRelativeTime(new Date(s.started_at)).padEnd(16);
|
|
882
|
+
const duration = formatDuration(s.duration_ms).padEnd(10);
|
|
883
|
+
const events = String(s.event_count).padEnd(8);
|
|
884
|
+
const iters = String(s.iteration_count).padEnd(7);
|
|
885
|
+
const tasks = String(s.tasks_completed);
|
|
886
|
+
console.log(`${chalk.yellow(id)} ${status} ${chalk.gray(agent)} ${chalk.gray(started)} ${duration} ${events} ${iters} ${tasks}`);
|
|
887
|
+
}
|
|
888
|
+
console.log(chalk.gray(`\n${sessions.length} session(s)`));
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Session log list action handler.
|
|
892
|
+
*/
|
|
893
|
+
async function sessionLogListAction(options) {
|
|
894
|
+
try {
|
|
895
|
+
const ctx = await initContext();
|
|
896
|
+
let sessions = await getAllSessionLogSummaries(ctx.specDir);
|
|
897
|
+
// AC: @session-log-list ac-2 - Filter by status
|
|
898
|
+
if (options.status) {
|
|
899
|
+
const statusFilter = options.status;
|
|
900
|
+
sessions = sessions.filter((s) => s.status === statusFilter);
|
|
901
|
+
}
|
|
902
|
+
// AC: @session-log-list ac-4 - Filter by agent type
|
|
903
|
+
if (options.agent) {
|
|
904
|
+
const agentFilter = options.agent;
|
|
905
|
+
sessions = sessions.filter((s) => s.agent_type === agentFilter);
|
|
906
|
+
}
|
|
907
|
+
// AC: @session-log-list ac-3 - Filter by since date
|
|
908
|
+
if (options.since) {
|
|
909
|
+
const sinceDate = parseTimeSpec(options.since);
|
|
910
|
+
if (sinceDate) {
|
|
911
|
+
sessions = sessions.filter((s) => new Date(s.started_at) >= sinceDate);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
// AC: @session-log-list ac-5 - Sort
|
|
915
|
+
const sortField = options.sort && VALID_SORT_FIELDS.includes(options.sort)
|
|
916
|
+
? options.sort
|
|
917
|
+
: "started_at";
|
|
918
|
+
sessions = sortSessions(sessions, sortField);
|
|
919
|
+
// AC: @session-log-list ac-7 - Limit output count
|
|
920
|
+
if (options.count) {
|
|
921
|
+
// AC: @trait-filterable-list ac-8
|
|
922
|
+
output({ count: sessions.length }, () => {
|
|
923
|
+
console.log(sessions.length);
|
|
924
|
+
});
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
// Apply --limit (after filtering/sorting, before display)
|
|
928
|
+
if (options.limit) {
|
|
929
|
+
const limit = parseInt(options.limit, 10);
|
|
930
|
+
if (!Number.isNaN(limit) && limit > 0) {
|
|
931
|
+
sessions = sessions.slice(0, limit);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
output(sessions, () => formatSessionLogList(sessions));
|
|
935
|
+
}
|
|
936
|
+
catch (err) {
|
|
937
|
+
error("Failed to list session logs", err);
|
|
938
|
+
process.exit(EXIT_CODES.ERROR);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Format an event timestamp as relative time from session start.
|
|
943
|
+
*/
|
|
944
|
+
function formatEventTimestamp(eventTs, sessionStartTs) {
|
|
945
|
+
const relativeMs = eventTs - sessionStartTs;
|
|
946
|
+
const totalSec = Math.floor(relativeMs / 1000);
|
|
947
|
+
const minutes = Math.floor(totalSec / 60);
|
|
948
|
+
const seconds = totalSec % 60;
|
|
949
|
+
if (minutes > 0) {
|
|
950
|
+
return `+${minutes}m${seconds}s`;
|
|
951
|
+
}
|
|
952
|
+
return `+${seconds}s`;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Summarize event data for display.
|
|
956
|
+
* Returns a short string describing the event payload.
|
|
957
|
+
*/
|
|
958
|
+
function summarizeEventData(event) {
|
|
959
|
+
const data = event.data;
|
|
960
|
+
if (!data)
|
|
961
|
+
return "";
|
|
962
|
+
// Handle tool_call events
|
|
963
|
+
if (event.type === "session.update") {
|
|
964
|
+
const update = data.update;
|
|
965
|
+
if (update?.sessionUpdate === "tool_call") {
|
|
966
|
+
const toolName = update._meta?.claudeCode?.toolName || "unknown";
|
|
967
|
+
const command = update.rawInput?.command;
|
|
968
|
+
if (command) {
|
|
969
|
+
const truncated = command.length > 60 ? command.slice(0, 57) + "..." : command;
|
|
970
|
+
return `${toolName}: ${truncated}`;
|
|
971
|
+
}
|
|
972
|
+
return toolName;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// Handle prompt.sent events
|
|
976
|
+
if (event.type === "prompt.sent") {
|
|
977
|
+
const prompt = data.prompt;
|
|
978
|
+
if (prompt) {
|
|
979
|
+
const truncated = prompt.length > 60 ? prompt.slice(0, 57) + "..." : prompt;
|
|
980
|
+
return truncated;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
// Handle session.start/end
|
|
984
|
+
if (event.type === "session.start") {
|
|
985
|
+
return "Session started";
|
|
986
|
+
}
|
|
987
|
+
if (event.type === "session.end") {
|
|
988
|
+
const reason = data.reason;
|
|
989
|
+
return reason ? `Session ended: ${reason}` : "Session ended";
|
|
990
|
+
}
|
|
991
|
+
// Default: show first key
|
|
992
|
+
const keys = Object.keys(data);
|
|
993
|
+
if (keys.length > 0) {
|
|
994
|
+
return `{${keys.slice(0, 3).join(", ")}${keys.length > 3 ? ", ..." : ""}}`;
|
|
995
|
+
}
|
|
996
|
+
return "";
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Format the session log show output.
|
|
1000
|
+
*
|
|
1001
|
+
* AC: @session-log-show ac-1
|
|
1002
|
+
*/
|
|
1003
|
+
function formatSessionLogShow(detail, events, contextSnapshot, sessionStartTs) {
|
|
1004
|
+
// AC: @session-log-show ac-1 - Session metadata
|
|
1005
|
+
console.log(chalk.bold(`Session ${detail.id.slice(0, 8)}`));
|
|
1006
|
+
console.log(chalk.gray("─".repeat(60)));
|
|
1007
|
+
console.log(` ID: ${detail.id}`);
|
|
1008
|
+
const statusColor = detail.status === "completed"
|
|
1009
|
+
? chalk.green
|
|
1010
|
+
: detail.status === "active"
|
|
1011
|
+
? chalk.blue
|
|
1012
|
+
: chalk.yellow;
|
|
1013
|
+
console.log(` Status: ${statusColor(detail.status)}`);
|
|
1014
|
+
console.log(` Agent: ${detail.agent_type}`);
|
|
1015
|
+
if (detail.task_id) {
|
|
1016
|
+
console.log(` Task: ${detail.task_id}`);
|
|
1017
|
+
}
|
|
1018
|
+
console.log(` Started: ${detail.started_at}`);
|
|
1019
|
+
if (detail.ended_at) {
|
|
1020
|
+
console.log(` Ended: ${detail.ended_at}`);
|
|
1021
|
+
}
|
|
1022
|
+
console.log(` Duration: ${formatDuration(detail.duration_ms)}`);
|
|
1023
|
+
console.log(` Events: ${detail.event_count}`);
|
|
1024
|
+
console.log(` Iterations: ${detail.iteration_count}`);
|
|
1025
|
+
// AC: @session-log-show ac-2 - Per-iteration summary
|
|
1026
|
+
if (detail.iterations.length > 0) {
|
|
1027
|
+
console.log("\n" + chalk.bold("Iterations"));
|
|
1028
|
+
console.log(chalk.gray("─".repeat(60)));
|
|
1029
|
+
for (const iter of detail.iterations) {
|
|
1030
|
+
const taskInfo = [];
|
|
1031
|
+
if (iter.tasks_started.length > 0) {
|
|
1032
|
+
taskInfo.push(`started: ${iter.tasks_started.join(", ")}`);
|
|
1033
|
+
}
|
|
1034
|
+
if (iter.tasks_completed.length > 0) {
|
|
1035
|
+
taskInfo.push(`completed: ${iter.tasks_completed.join(", ")}`);
|
|
1036
|
+
}
|
|
1037
|
+
const taskStr = taskInfo.length > 0 ? ` | ${taskInfo.join(" | ")}` : "";
|
|
1038
|
+
console.log(` ${chalk.cyan(`[${iter.iteration}]`)} ${iter.event_count} events${taskStr}`);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
// AC: @session-log-show ac-3 - Event timeline
|
|
1042
|
+
if (events !== null) {
|
|
1043
|
+
console.log("\n" + chalk.bold("Events"));
|
|
1044
|
+
console.log(chalk.gray("─".repeat(60)));
|
|
1045
|
+
if (events.length === 0) {
|
|
1046
|
+
console.log(chalk.gray(" No events to display."));
|
|
1047
|
+
}
|
|
1048
|
+
else {
|
|
1049
|
+
for (const event of events) {
|
|
1050
|
+
const timestamp = formatEventTimestamp(event.ts, sessionStartTs);
|
|
1051
|
+
const summary = summarizeEventData(event);
|
|
1052
|
+
const typeColor = event.type === "session.start" || event.type === "session.end"
|
|
1053
|
+
? chalk.green
|
|
1054
|
+
: event.type === "session.update"
|
|
1055
|
+
? chalk.blue
|
|
1056
|
+
: chalk.gray;
|
|
1057
|
+
console.log(` ${chalk.yellow(timestamp.padEnd(10))} ${typeColor(event.type.padEnd(16))} ${chalk.gray(summary)}`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
// AC: @session-log-show ac-6 - Context snapshot
|
|
1062
|
+
if (contextSnapshot !== null) {
|
|
1063
|
+
console.log("\n" + chalk.bold("Context Snapshot"));
|
|
1064
|
+
console.log(chalk.gray("─".repeat(60)));
|
|
1065
|
+
console.log(JSON.stringify(contextSnapshot, null, 2));
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Session log show action handler.
|
|
1070
|
+
*/
|
|
1071
|
+
async function sessionLogShowAction(sessionRef, options) {
|
|
1072
|
+
try {
|
|
1073
|
+
const ctx = await initContext();
|
|
1074
|
+
// AC: @session-log-show ac-7, ac-8, ac-9 - Resolve session ID
|
|
1075
|
+
const resolution = await resolveSessionId(ctx.specDir, sessionRef);
|
|
1076
|
+
if (!resolution.ok) {
|
|
1077
|
+
if (resolution.error === "not_found") {
|
|
1078
|
+
// AC: @session-log-show ac-9
|
|
1079
|
+
error(`Session not found: ${sessionRef}`);
|
|
1080
|
+
process.exit(EXIT_CODES.NOT_FOUND);
|
|
1081
|
+
}
|
|
1082
|
+
else {
|
|
1083
|
+
// AC: @session-log-show ac-8
|
|
1084
|
+
error(`Ambiguous session ID prefix. Matches:\n ${resolution.matches.join("\n ")}\nPlease provide a more specific prefix.`);
|
|
1085
|
+
process.exit(EXIT_CODES.VALIDATION_FAILED);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const sessionId = resolution.id;
|
|
1089
|
+
// Get session detail
|
|
1090
|
+
const detail = await getSessionLogDetail(ctx.specDir, sessionId);
|
|
1091
|
+
if (!detail) {
|
|
1092
|
+
error(`Session not found: ${sessionId}`);
|
|
1093
|
+
process.exit(EXIT_CODES.NOT_FOUND);
|
|
1094
|
+
}
|
|
1095
|
+
// AC: @session-log-show ac-3, ac-4, ac-5 - Event timeline
|
|
1096
|
+
let events = null;
|
|
1097
|
+
if (options.events) {
|
|
1098
|
+
let allEvents = await readEvents(ctx.specDir, sessionId);
|
|
1099
|
+
// AC: @session-log-show ac-4 - Filter by type
|
|
1100
|
+
if (options.type) {
|
|
1101
|
+
const typeFilter = options.type;
|
|
1102
|
+
allEvents = allEvents.filter((e) => e.type === typeFilter);
|
|
1103
|
+
}
|
|
1104
|
+
// AC: @session-log-show ac-5 - Limit to last N events
|
|
1105
|
+
if (options.limit) {
|
|
1106
|
+
const limit = parseInt(options.limit, 10);
|
|
1107
|
+
if (!Number.isNaN(limit) && limit > 0) {
|
|
1108
|
+
allEvents = allEvents.slice(-limit);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
events = allEvents;
|
|
1112
|
+
}
|
|
1113
|
+
// AC: @session-log-show ac-6 - Context snapshot
|
|
1114
|
+
let contextSnapshot = null;
|
|
1115
|
+
if (options.context) {
|
|
1116
|
+
const iterNum = parseInt(options.context, 10);
|
|
1117
|
+
if (!Number.isNaN(iterNum) && iterNum > 0) {
|
|
1118
|
+
contextSnapshot = await readSessionContext(ctx.specDir, sessionId, iterNum);
|
|
1119
|
+
if (contextSnapshot === null) {
|
|
1120
|
+
error(`No context snapshot found for iteration ${iterNum}`);
|
|
1121
|
+
process.exit(EXIT_CODES.NOT_FOUND);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
error(`Invalid iteration number: ${options.context}`);
|
|
1126
|
+
process.exit(EXIT_CODES.USAGE_ERROR);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
const sessionStartTs = new Date(detail.started_at).getTime();
|
|
1130
|
+
// Build JSON output structure
|
|
1131
|
+
const jsonOutput = {
|
|
1132
|
+
...detail,
|
|
1133
|
+
...(events !== null ? { events } : {}),
|
|
1134
|
+
...(contextSnapshot !== null ? { context: contextSnapshot } : {}),
|
|
1135
|
+
};
|
|
1136
|
+
output(jsonOutput, () => formatSessionLogShow(detail, events, contextSnapshot, sessionStartTs));
|
|
1137
|
+
}
|
|
1138
|
+
catch (err) {
|
|
1139
|
+
error("Failed to show session log", err);
|
|
1140
|
+
process.exit(EXIT_CODES.ERROR);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Format a duration in milliseconds to human-readable format.
|
|
1145
|
+
* Reuses formatDuration from session log list but handles hours/minutes/seconds.
|
|
1146
|
+
*/
|
|
1147
|
+
function formatDurationLong(ms) {
|
|
1148
|
+
if (ms < 0)
|
|
1149
|
+
return "—";
|
|
1150
|
+
const totalSec = Math.floor(ms / 1000);
|
|
1151
|
+
const hours = Math.floor(totalSec / 3600);
|
|
1152
|
+
const minutes = Math.floor((totalSec % 3600) / 60);
|
|
1153
|
+
const seconds = totalSec % 60;
|
|
1154
|
+
if (hours > 0 && minutes > 0) {
|
|
1155
|
+
return `${hours}h ${minutes}m`;
|
|
1156
|
+
}
|
|
1157
|
+
if (hours > 0) {
|
|
1158
|
+
return `${hours}h`;
|
|
1159
|
+
}
|
|
1160
|
+
if (minutes > 0) {
|
|
1161
|
+
return `${minutes}m ${seconds}s`;
|
|
1162
|
+
}
|
|
1163
|
+
return `${seconds}s`;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Format the session log stats output.
|
|
1167
|
+
*
|
|
1168
|
+
* AC: @session-log-stats ac-1, ac-2, ac-3
|
|
1169
|
+
*/
|
|
1170
|
+
function formatSessionLogStats(stats, toolUsage, timePeriods, groupBy) {
|
|
1171
|
+
// AC: @session-log-stats ac-1 - Totals
|
|
1172
|
+
console.log(chalk.bold("Session Statistics"));
|
|
1173
|
+
console.log(chalk.gray("─".repeat(50)));
|
|
1174
|
+
console.log(` Total Sessions: ${stats.total_sessions}`);
|
|
1175
|
+
console.log(` Total Events: ${stats.total_events}`);
|
|
1176
|
+
console.log(` Total Iterations: ${stats.total_iterations}`);
|
|
1177
|
+
console.log(` Tasks Completed: ${stats.total_tasks_completed}`);
|
|
1178
|
+
console.log(` Total Duration: ${formatDurationLong(stats.total_duration_ms)}`);
|
|
1179
|
+
// AC: @session-log-stats ac-2 - Averages
|
|
1180
|
+
console.log("\n" + chalk.bold("Averages"));
|
|
1181
|
+
console.log(chalk.gray("─".repeat(50)));
|
|
1182
|
+
console.log(` Avg Duration/Session: ${formatDurationLong(stats.avg_duration_ms)}`);
|
|
1183
|
+
console.log(` Avg Iterations/Session: ${stats.avg_iterations_per_session}`);
|
|
1184
|
+
console.log(` Avg Tasks/Session: ${stats.avg_tasks_per_session}`);
|
|
1185
|
+
// AC: @session-log-stats ac-3 - Status breakdown
|
|
1186
|
+
if (stats.status_breakdown.length > 0) {
|
|
1187
|
+
console.log("\n" + chalk.bold("Status Breakdown"));
|
|
1188
|
+
console.log(chalk.gray("─".repeat(50)));
|
|
1189
|
+
for (const item of stats.status_breakdown) {
|
|
1190
|
+
const statusColor = item.status === "completed"
|
|
1191
|
+
? chalk.green
|
|
1192
|
+
: item.status === "active"
|
|
1193
|
+
? chalk.blue
|
|
1194
|
+
: chalk.yellow;
|
|
1195
|
+
console.log(` ${statusColor(item.status.padEnd(12))} ${String(item.count).padEnd(6)} ${item.percentage}%`);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
// AC: @session-log-stats ac-6 - Tool usage
|
|
1199
|
+
if (toolUsage !== null && toolUsage.length > 0) {
|
|
1200
|
+
console.log("\n" + chalk.bold("Top Tool Usage"));
|
|
1201
|
+
console.log(chalk.gray("─".repeat(50)));
|
|
1202
|
+
for (const tool of toolUsage) {
|
|
1203
|
+
console.log(` ${tool.tool_name.padEnd(20)} ${String(tool.count).padEnd(8)} ${tool.percentage}%`);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
// AC: @session-log-stats ac-7 - Time periods
|
|
1207
|
+
if (timePeriods !== null && timePeriods.length > 0) {
|
|
1208
|
+
const label = groupBy === "week" ? "By Week" : "By Day";
|
|
1209
|
+
console.log("\n" + chalk.bold(label));
|
|
1210
|
+
console.log(chalk.gray("─".repeat(50)));
|
|
1211
|
+
console.log(chalk.gray(` ${"Period".padEnd(14)} ${"Sessions".padEnd(10)} ${"Tasks".padEnd(8)} Duration`));
|
|
1212
|
+
for (const period of timePeriods) {
|
|
1213
|
+
console.log(` ${period.period.padEnd(14)} ${String(period.sessions_count).padEnd(10)} ${String(period.tasks_completed).padEnd(8)} ${formatDurationLong(period.total_duration_ms)}`);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Session log stats action handler.
|
|
1219
|
+
*/
|
|
1220
|
+
async function sessionLogStatsAction(options) {
|
|
1221
|
+
try {
|
|
1222
|
+
const ctx = await initContext();
|
|
1223
|
+
let sessions = await getAllSessionLogSummaries(ctx.specDir);
|
|
1224
|
+
// AC: @session-log-stats ac-4 - Filter by since
|
|
1225
|
+
if (options.since) {
|
|
1226
|
+
const sinceDate = parseTimeSpec(options.since);
|
|
1227
|
+
if (sinceDate) {
|
|
1228
|
+
sessions = sessions.filter((s) => new Date(s.started_at) >= sinceDate);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
// AC: @session-log-stats ac-5 - Filter by agent type
|
|
1232
|
+
if (options.agent) {
|
|
1233
|
+
const agentFilter = options.agent;
|
|
1234
|
+
sessions = sessions.filter((s) => s.agent_type === agentFilter);
|
|
1235
|
+
}
|
|
1236
|
+
// AC: @session-log-stats ac-8 - No sessions match criteria
|
|
1237
|
+
if (sessions.length === 0) {
|
|
1238
|
+
output({ message: "No sessions match criteria" }, () => {
|
|
1239
|
+
console.log("No sessions match criteria.");
|
|
1240
|
+
});
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
// Compute base stats
|
|
1244
|
+
const stats = computeSessionLogStats(sessions);
|
|
1245
|
+
// AC: @session-log-stats ac-6 - Tool usage (optional)
|
|
1246
|
+
let toolUsage = null;
|
|
1247
|
+
if (options.toolUsage) {
|
|
1248
|
+
const sessionIds = sessions.map((s) => s.id);
|
|
1249
|
+
toolUsage = await computeToolUsageStats(ctx.specDir, sessionIds);
|
|
1250
|
+
}
|
|
1251
|
+
// AC: @session-log-stats ac-7 - Time periods (optional)
|
|
1252
|
+
let timePeriods = null;
|
|
1253
|
+
let groupBy = null;
|
|
1254
|
+
if (options.byDay) {
|
|
1255
|
+
groupBy = "day";
|
|
1256
|
+
timePeriods = computeTimePeriodStats(sessions, "day");
|
|
1257
|
+
}
|
|
1258
|
+
else if (options.byWeek) {
|
|
1259
|
+
groupBy = "week";
|
|
1260
|
+
timePeriods = computeTimePeriodStats(sessions, "week");
|
|
1261
|
+
}
|
|
1262
|
+
// Build output structure
|
|
1263
|
+
const jsonOutput = { stats };
|
|
1264
|
+
if (toolUsage !== null) {
|
|
1265
|
+
jsonOutput.tool_usage = toolUsage;
|
|
1266
|
+
}
|
|
1267
|
+
if (timePeriods !== null) {
|
|
1268
|
+
jsonOutput.time_periods = timePeriods;
|
|
1269
|
+
}
|
|
1270
|
+
output(jsonOutput, () => formatSessionLogStats(stats, toolUsage, timePeriods, groupBy));
|
|
1271
|
+
}
|
|
1272
|
+
catch (err) {
|
|
1273
|
+
error("Failed to compute session log stats", err);
|
|
1274
|
+
process.exit(EXIT_CODES.ERROR);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Format relative timestamp from event timestamp (Unix ms) to session start.
|
|
1279
|
+
*/
|
|
1280
|
+
function formatSearchTimestamp(eventTs) {
|
|
1281
|
+
return new Date(eventTs).toISOString();
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Format the session log search output.
|
|
1285
|
+
*
|
|
1286
|
+
* AC: @session-log-search ac-1, ac-4
|
|
1287
|
+
*/
|
|
1288
|
+
function formatSessionLogSearch(results) {
|
|
1289
|
+
if (results.length === 0) {
|
|
1290
|
+
// AC: @session-log-search ac-6
|
|
1291
|
+
console.log("No matches found.");
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
let totalMatches = 0;
|
|
1295
|
+
for (const session of results) {
|
|
1296
|
+
totalMatches += session.matches.length;
|
|
1297
|
+
}
|
|
1298
|
+
console.log(chalk.bold(`Found ${totalMatches} match(es) in ${results.length} session(s)`));
|
|
1299
|
+
console.log(chalk.gray("─".repeat(60)));
|
|
1300
|
+
for (const session of results) {
|
|
1301
|
+
// Session header
|
|
1302
|
+
console.log(`\n${chalk.cyan(`Session ${session.session_id.slice(0, 8)}`)} ` +
|
|
1303
|
+
`${chalk.gray(`(${session.agent_type}, started ${formatRelativeTime(new Date(session.started_at))})`)}`);
|
|
1304
|
+
// AC: @session-log-search ac-4 - Show matches with session ID, timestamp, type, excerpt
|
|
1305
|
+
for (const match of session.matches) {
|
|
1306
|
+
const ts = formatSearchTimestamp(match.timestamp);
|
|
1307
|
+
const typeColor = match.event_type === "session.start" || match.event_type === "session.end"
|
|
1308
|
+
? chalk.green
|
|
1309
|
+
: match.event_type === "session.update"
|
|
1310
|
+
? chalk.blue
|
|
1311
|
+
: chalk.gray;
|
|
1312
|
+
console.log(` ${chalk.yellow(ts)} ${typeColor(match.event_type.padEnd(16))}`);
|
|
1313
|
+
// Content excerpt on next line, indented
|
|
1314
|
+
console.log(` ${chalk.gray(match.content_excerpt)}`);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Session log search action handler.
|
|
1320
|
+
*
|
|
1321
|
+
* AC: @session-log-search ac-1 through ac-7
|
|
1322
|
+
*/
|
|
1323
|
+
async function sessionLogSearchAction(pattern, options) {
|
|
1324
|
+
try {
|
|
1325
|
+
const ctx = await initContext();
|
|
1326
|
+
// Parse options - validate limit as positive integer
|
|
1327
|
+
let limit = 50;
|
|
1328
|
+
if (options.limit) {
|
|
1329
|
+
const parsed = parseInt(options.limit, 10);
|
|
1330
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
1331
|
+
error(`Invalid limit: ${options.limit}. Must be a positive integer.`);
|
|
1332
|
+
process.exit(EXIT_CODES.USAGE_ERROR);
|
|
1333
|
+
}
|
|
1334
|
+
limit = parsed;
|
|
1335
|
+
}
|
|
1336
|
+
const sinceDate = options.since ? parseTimeSpec(options.since) : undefined;
|
|
1337
|
+
// AC: @session-log-search ac-1, ac-2, ac-3, ac-5, ac-7
|
|
1338
|
+
const results = await searchSessionEvents(ctx.specDir, pattern, {
|
|
1339
|
+
eventType: options.type,
|
|
1340
|
+
sinceDate: sinceDate || undefined,
|
|
1341
|
+
agentType: options.agent,
|
|
1342
|
+
limit,
|
|
1343
|
+
});
|
|
1344
|
+
// AC: @session-log-search ac-6 - No matches found message
|
|
1345
|
+
// exit code 0 regardless (per @trait-semantic-exit-codes ac-5)
|
|
1346
|
+
output(results, () => formatSessionLogSearch(results));
|
|
1347
|
+
}
|
|
1348
|
+
catch (err) {
|
|
1349
|
+
error("Failed to search session logs", err);
|
|
1350
|
+
process.exit(EXIT_CODES.ERROR);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
708
1353
|
/**
|
|
709
1354
|
* Register the 'session' command group and aliases
|
|
710
1355
|
*/
|
|
711
1356
|
export function registerSessionCommands(program) {
|
|
712
1357
|
const session = program
|
|
713
|
-
.command(
|
|
714
|
-
.description(
|
|
1358
|
+
.command("session")
|
|
1359
|
+
.description("Session management and context");
|
|
715
1360
|
session
|
|
716
|
-
.command(
|
|
717
|
-
.alias(
|
|
718
|
-
.description(
|
|
719
|
-
.option(
|
|
720
|
-
.option(
|
|
721
|
-
.option(
|
|
722
|
-
.option(
|
|
723
|
-
.option(
|
|
1361
|
+
.command("start")
|
|
1362
|
+
.alias("resume")
|
|
1363
|
+
.description("Surface relevant context for starting a new working session")
|
|
1364
|
+
.option("--brief", "Compact summary (default)")
|
|
1365
|
+
.option("--full", "Comprehensive context dump")
|
|
1366
|
+
.option("--since <time>", "Filter by recency (ISO8601 or relative: 1h, 2d, 1w)")
|
|
1367
|
+
.option("--no-git", "Skip git commit information")
|
|
1368
|
+
.option("-n, --limit <n>", "Limit items per section", "10")
|
|
724
1369
|
.action(sessionStartAction);
|
|
1370
|
+
// Session log subcommand group
|
|
1371
|
+
const log = session
|
|
1372
|
+
.command("log")
|
|
1373
|
+
.description("Session log analysis commands");
|
|
1374
|
+
log
|
|
1375
|
+
.command("list")
|
|
1376
|
+
.description("List session logs with summary statistics")
|
|
1377
|
+
.option("-s, --status <status>", "Filter by status (active, completed, abandoned)")
|
|
1378
|
+
.option("--agent <type>", "Filter by agent type")
|
|
1379
|
+
.option("--since <time>", "Only show sessions started after this time (ISO8601 or relative: 1h, 2d, 1w)")
|
|
1380
|
+
.option("--sort <field>", "Sort by field (started_at, duration, events, iterations, tasks_completed)", "started_at")
|
|
1381
|
+
.option("--count", "Show only the count of matching sessions")
|
|
1382
|
+
.option("-n, --limit <n>", "Limit number of sessions shown")
|
|
1383
|
+
.action(sessionLogListAction);
|
|
1384
|
+
log
|
|
1385
|
+
.command("show <session-id>")
|
|
1386
|
+
.description("Show detailed view of a single session")
|
|
1387
|
+
.option("-e, --events", "Include chronological event timeline")
|
|
1388
|
+
.option("-t, --type <type>", "Filter events by type (e.g., tool.call)")
|
|
1389
|
+
.option("-n, --limit <n>", "Show only the last N events")
|
|
1390
|
+
.option("-c, --context <n>", "Show context snapshot for iteration N")
|
|
1391
|
+
.action(sessionLogShowAction);
|
|
1392
|
+
log
|
|
1393
|
+
.command("stats")
|
|
1394
|
+
.description("Aggregate analytics across sessions")
|
|
1395
|
+
.option("--since <time>", "Only include sessions started after this time (ISO8601 or relative: 1h, 2d, 1w)")
|
|
1396
|
+
.option("--agent <type>", "Only include sessions with this agent type")
|
|
1397
|
+
.option("--tool-usage", "Display top 10 tool calls by frequency")
|
|
1398
|
+
.option("--by-day", "Group stats by day")
|
|
1399
|
+
.option("--by-week", "Group stats by week")
|
|
1400
|
+
.action(sessionLogStatsAction);
|
|
1401
|
+
log
|
|
1402
|
+
.command("search <pattern>")
|
|
1403
|
+
.description("Search across session events by content")
|
|
1404
|
+
.option("-t, --type <type>", "Only search events of this type (e.g., session.update)")
|
|
1405
|
+
.option("--since <time>", "Only search sessions started after this time (ISO8601 or relative: 1h, 2d, 1w)")
|
|
1406
|
+
.option("--agent <type>", "Only search sessions with this agent type")
|
|
1407
|
+
.option("-n, --limit <n>", "Maximum matches to return (default: 50)")
|
|
1408
|
+
.action(sessionLogSearchAction);
|
|
725
1409
|
session
|
|
726
|
-
.command(
|
|
727
|
-
.description(
|
|
728
|
-
.option(
|
|
1410
|
+
.command("checkpoint")
|
|
1411
|
+
.description("Pre-stop hook: check for uncommitted work before ending session")
|
|
1412
|
+
.option("--force", "Allow session end regardless of issues")
|
|
729
1413
|
.action(sessionCheckpointAction);
|
|
730
1414
|
session
|
|
731
|
-
.command(
|
|
732
|
-
.description(
|
|
1415
|
+
.command("prompt-check")
|
|
1416
|
+
.description("UserPromptSubmit hook: inject spec-first reminder")
|
|
733
1417
|
.action(sessionPromptCheckAction);
|
|
734
1418
|
// Top-level alias: kspec context
|
|
735
1419
|
program
|
|
736
|
-
.command(
|
|
737
|
-
.description(
|
|
738
|
-
.option(
|
|
739
|
-
.option(
|
|
740
|
-
.option(
|
|
741
|
-
.option(
|
|
742
|
-
.option(
|
|
1420
|
+
.command("context")
|
|
1421
|
+
.description("Alias for session start - surface session context")
|
|
1422
|
+
.option("--brief", "Compact summary (default)")
|
|
1423
|
+
.option("--full", "Comprehensive context dump")
|
|
1424
|
+
.option("--since <time>", "Filter by recency (ISO8601 or relative: 1h, 2d, 1w)")
|
|
1425
|
+
.option("--no-git", "Skip git commit information")
|
|
1426
|
+
.option("-n, --limit <n>", "Limit items per section", "10")
|
|
743
1427
|
.action(sessionStartAction);
|
|
744
1428
|
}
|
|
745
1429
|
//# sourceMappingURL=session.js.map
|