@damian87/omp 0.10.0 → 0.13.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.
Files changed (114) hide show
  1. package/.github/copilot-instructions.md +16 -0
  2. package/.github/skills/jira-ticket/SKILL.md +4 -4
  3. package/.github/skills/omp-autopilot/SKILL.md +4 -0
  4. package/.github/skills/research-codebase/SKILL.md +4 -0
  5. package/.github/skills/schedule/SKILL.md +4 -0
  6. package/.github/skills/team/SKILL.md +4 -0
  7. package/.github/skills/ultrawork/SKILL.md +4 -0
  8. package/.github/skills/weighted-consensus/SKILL.md +4 -0
  9. package/README.md +4 -1
  10. package/dist/src/cli.js +134 -4
  11. package/dist/src/cli.js.map +1 -1
  12. package/dist/src/commands/comms.d.ts +2 -0
  13. package/dist/src/commands/comms.js +110 -0
  14. package/dist/src/commands/comms.js.map +1 -0
  15. package/dist/src/commands/council.d.ts +2 -0
  16. package/dist/src/commands/council.js +77 -0
  17. package/dist/src/commands/council.js.map +1 -0
  18. package/dist/src/commands/env.d.ts +2 -0
  19. package/dist/src/commands/env.js +95 -0
  20. package/dist/src/commands/env.js.map +1 -0
  21. package/dist/src/commands/gateway.d.ts +3 -0
  22. package/dist/src/commands/gateway.js +129 -0
  23. package/dist/src/commands/gateway.js.map +1 -0
  24. package/dist/src/commands/memory.d.ts +7 -0
  25. package/dist/src/commands/memory.js +202 -0
  26. package/dist/src/commands/memory.js.map +1 -0
  27. package/dist/src/commands/mode.d.ts +4 -0
  28. package/dist/src/commands/mode.js +119 -0
  29. package/dist/src/commands/mode.js.map +1 -0
  30. package/dist/src/commands/schedule.d.ts +2 -0
  31. package/dist/src/commands/schedule.js +91 -0
  32. package/dist/src/commands/schedule.js.map +1 -0
  33. package/dist/src/commands/team.d.ts +2 -0
  34. package/dist/src/commands/team.js +146 -0
  35. package/dist/src/commands/team.js.map +1 -0
  36. package/dist/src/commands/utils.d.ts +13 -0
  37. package/dist/src/commands/utils.js +68 -0
  38. package/dist/src/commands/utils.js.map +1 -0
  39. package/dist/src/copilot/doctor.d.ts +1 -0
  40. package/dist/src/copilot/doctor.js +226 -27
  41. package/dist/src/copilot/doctor.js.map +1 -1
  42. package/dist/src/copilot/launch.js +13 -5
  43. package/dist/src/copilot/launch.js.map +1 -1
  44. package/dist/src/copilot/setup.js +13 -0
  45. package/dist/src/copilot/setup.js.map +1 -1
  46. package/dist/src/cost/index.d.ts +3 -0
  47. package/dist/src/cost/index.js +4 -0
  48. package/dist/src/cost/index.js.map +1 -0
  49. package/dist/src/cost/ledger.d.ts +21 -0
  50. package/dist/src/cost/ledger.js +72 -0
  51. package/dist/src/cost/ledger.js.map +1 -0
  52. package/dist/src/cost/summary.d.ts +22 -0
  53. package/dist/src/cost/summary.js +68 -0
  54. package/dist/src/cost/summary.js.map +1 -0
  55. package/dist/src/cost/tokenize.d.ts +7 -0
  56. package/dist/src/cost/tokenize.js +24 -0
  57. package/dist/src/cost/tokenize.js.map +1 -0
  58. package/dist/src/goal.js +6 -8
  59. package/dist/src/goal.js.map +1 -1
  60. package/dist/src/instructions-memory.js +26 -3
  61. package/dist/src/instructions-memory.js.map +1 -1
  62. package/dist/src/memory-review/apply.d.ts +7 -0
  63. package/dist/src/memory-review/apply.js +75 -0
  64. package/dist/src/memory-review/apply.js.map +1 -0
  65. package/dist/src/memory-review/config.d.ts +22 -0
  66. package/dist/src/memory-review/config.js +54 -0
  67. package/dist/src/memory-review/config.js.map +1 -0
  68. package/dist/src/memory-review/guard.d.ts +5 -0
  69. package/dist/src/memory-review/guard.js +37 -0
  70. package/dist/src/memory-review/guard.js.map +1 -0
  71. package/dist/src/memory-review/index.d.ts +17 -0
  72. package/dist/src/memory-review/index.js +87 -0
  73. package/dist/src/memory-review/index.js.map +1 -0
  74. package/dist/src/memory-review/prompt.d.ts +18 -0
  75. package/dist/src/memory-review/prompt.js +89 -0
  76. package/dist/src/memory-review/prompt.js.map +1 -0
  77. package/dist/src/memory-review/spawn.d.ts +2 -0
  78. package/dist/src/memory-review/spawn.js +51 -0
  79. package/dist/src/memory-review/spawn.js.map +1 -0
  80. package/dist/src/memory-review/transcript.d.ts +24 -0
  81. package/dist/src/memory-review/transcript.js +212 -0
  82. package/dist/src/memory-review/transcript.js.map +1 -0
  83. package/dist/src/memory-review/trigger.d.ts +21 -0
  84. package/dist/src/memory-review/trigger.js +27 -0
  85. package/dist/src/memory-review/trigger.js.map +1 -0
  86. package/dist/src/project-memory.d.ts +9 -0
  87. package/dist/src/project-memory.js +72 -1
  88. package/dist/src/project-memory.js.map +1 -1
  89. package/dist/src/state.js +25 -37
  90. package/dist/src/state.js.map +1 -1
  91. package/dist/src/utils/fs.d.ts +14 -0
  92. package/dist/src/utils/fs.js +32 -0
  93. package/dist/src/utils/fs.js.map +1 -0
  94. package/dist/src/utils/paths.d.ts +14 -0
  95. package/dist/src/utils/paths.js +21 -0
  96. package/dist/src/utils/paths.js.map +1 -0
  97. package/docs/general-skills.md +1 -0
  98. package/docs/memory-mode.md +94 -0
  99. package/hooks/hooks.json +9 -2
  100. package/package.json +1 -1
  101. package/plugin.json +1 -1
  102. package/scripts/error.mjs +9 -7
  103. package/scripts/lib/cost-ledger.mjs +91 -0
  104. package/scripts/lib/hook-input.mjs +51 -0
  105. package/scripts/lib/hook-output.mjs +53 -11
  106. package/scripts/lib/memory-review-trigger.mjs +59 -0
  107. package/scripts/lib/minify.mjs +80 -0
  108. package/scripts/lib/pending-directives.mjs +36 -0
  109. package/scripts/post-tool-use-failure.mjs +21 -0
  110. package/scripts/post-tool-use.mjs +71 -8
  111. package/scripts/pre-tool-use.mjs +8 -6
  112. package/scripts/prompt-submit.mjs +12 -5
  113. package/scripts/session-end.mjs +15 -5
  114. package/scripts/session-start.mjs +9 -4
@@ -13,6 +13,22 @@ Default behaviours installed with this repo. Override per project as needed.
13
13
  - Read the diff before committing.
14
14
  - If unsure about scope, ask.
15
15
 
16
+ ## Cost/token discipline
17
+ Cost data is local, best-effort, and estimated. `omp cost [--today] [--session <id>]`
18
+ summarizes prompt/tool token estimates from the hook ledger; it is not provider billing.
19
+
20
+ The cost hooks apply when this plugin's `hooks/hooks.json` is active in a Copilot CLI
21
+ session. They give session-wide visibility for skills invoked inside that session, not
22
+ standalone coverage for copied skills, raw shell scripts, or external CLIs.
23
+
24
+ Before rerunning noisy commands or failed edits, inspect the latest output and narrow the
25
+ next attempt. Prefer bounded summaries for large logs. Oversized postToolUse output is
26
+ minimized before it re-enters model context, with raw output preserved on disk and savings
27
+ recorded in the cost ledger. Diagnostics (errors, stack traces, assertions) are preserved
28
+ inline; other trimmed detail must be recovered by re-reading the raw file path noted in the
29
+ hook output (an extra tool call), so full fidelity depends on the model following that pointer.
30
+ Budget gates and retry-cost guidance are not current live behavior.
31
+
16
32
  ## Skills
17
33
  Slash commands under `.github/skills/<name>/SKILL.md` are auto-discovered by Copilot. See `omp list` for the catalog active in this project.
18
34
 
@@ -18,21 +18,21 @@ Use `/jira-ticket` when work tracking is requested.
18
18
  ### Create
19
19
  - Build from an approved plan or implementation slice (a plan file or markdown)
20
20
  - Include: Summary, Description, Acceptance Criteria
21
- - Render the payload with `omp jira render <plan-file>` — this **never** writes to Jira. To create/comment/update, run `omp jira apply <plan-file-or-ticket-key>`, which defaults to **dry-run** and only writes when Jira is configured with `JIRA_MODE=live` and the user has explicitly confirmed.
21
+ - Render the payload with `omp jira render <plan-file>` — this **never** writes to Jira. To create/comment/update, run `omp jira apply <plan-file-or-ticket-key>`, which defaults to **dry-run** and only writes when Jira is configured with `JIRA_MODE=live`, the user has explicitly confirmed, and the CLI has explicit body/field support for the operation you need.
22
22
 
23
23
  ### Comment
24
24
  - Add implementation evidence, verification results, or status updates
25
25
  - Format for readability (use Jira wiki markup, not Markdown)
26
- - Apply with `omp jira apply <ticket-key> --comment` (dry-run by default; preview before confirming)
26
+ - Draft the comment payload and preview via dry-run only; do not run live comment writes from this skill until the CLI accepts an explicit comment body/body file.
27
27
 
28
28
  ### Safe update
29
29
  - Only update known simple fields (summary, description, labels)
30
- - Apply with `omp jira apply <ticket-key> --update` (dry-run by default)
30
+ - Draft update fields and preview via dry-run only; do not run live update writes from this skill until the CLI accepts explicit update fields.
31
31
  - Do not guess transitions, issue links, project keys, or secrets
32
32
 
33
33
  ## Rules
34
34
 
35
- - Always preview with `omp jira render` or `omp jira apply … --dry-run` (the default) before any live write
35
+ - Always preview with `omp jira render` or `omp jira apply … --dry-run` (the default); live writes require explicit user confirmation plus CLI support for the exact payload being written
36
36
  - If Jira config is missing, the commands stay in dry-run and print the payload — never fail silently
37
37
  - Do not guess project keys, transitions, or credentials
38
38
  - Keep acceptance criteria testable and specific
@@ -73,3 +73,7 @@ Before claiming done:
73
73
  - [ ] Lint clean (if applicable)
74
74
  - [ ] `/verify` or `/ultraqa` produced PASS evidence
75
75
  - [ ] No uncommitted work left behind
76
+
77
+ ## Cost/token note
78
+
79
+ This skill can drive multiple tool calls or long-running output. Use `omp cost [--today] [--session <id>]` for local hook-ledger estimates only; it is not provider billing. Keep injected summaries concise and prefer bounded output when rerunning noisy commands.
@@ -56,3 +56,7 @@ Show concise summary to user with key file references. Ask if they have follow-u
56
56
  ### 7. Follow-ups
57
57
 
58
58
  If the user has follow-ups, read `reference/follow-up.md` for the append protocol.
59
+
60
+ ## Cost/token note
61
+
62
+ This skill can drive multiple tool calls or long-running output. Use `omp cost [--today] [--session <id>]` for local hook-ledger estimates only; it is not provider billing. Keep injected summaries concise and prefer bounded output when rerunning noisy commands.
@@ -69,3 +69,7 @@ omp schedule add --id nightly-tests --cron "0 2 * * *" \
69
69
  --prompt "Run the test suite; if anything fails, open an issue with the log." \
70
70
  --allow-all-tools --max-runs 7
71
71
  ```
72
+
73
+ ## Cost/token note
74
+
75
+ This skill can drive multiple tool calls or long-running output. Use `omp cost [--today] [--session <id>]` for local hook-ledger estimates only; it is not provider billing. Keep injected summaries concise and prefer bounded output when rerunning noisy commands.
@@ -127,3 +127,7 @@ Use `/ralplan` before `/team` to produce the plan. Use `/verify` after completio
127
127
  - Each pane is an independent session — no shared state
128
128
  - Workers can message each other via `omp team api send-message` (runtime mode only)
129
129
  - If tasks depend on each other, use `/ralph` instead
130
+
131
+ ## Cost/token note
132
+
133
+ This skill can drive multiple tool calls or long-running output. Use `omp cost [--today] [--session <id>]` for local hook-ledger estimates only; it is not provider billing. Keep injected summaries concise and prefer bounded output when rerunning noisy commands.
@@ -71,3 +71,7 @@ Summarise: completed, failed, blocked. Then clear the tracked state with `omp ul
71
71
  - `Completed` — items done with evidence
72
72
  - `Failed/blockers` — items that couldn't be completed and why
73
73
  - `Verification` — test/lint/build results per wave
74
+
75
+ ## Cost/token note
76
+
77
+ This skill can drive multiple tool calls or long-running output. Use `omp cost [--today] [--session <id>]` for local hook-ledger estimates only; it is not provider billing. Keep injected summaries concise and prefer bounded output when rerunning noisy commands.
@@ -127,3 +127,7 @@ default roster is used.
127
127
 
128
128
  Pair with `/code-review` (pass a review rubric + the diff as context) for a
129
129
  multi-model review verdict, or with `/ralplan` to weigh competing plans.
130
+
131
+ ## Cost/token note
132
+
133
+ This skill can drive multiple tool calls or long-running output. Use `omp cost [--today] [--session <id>]` for local hook-ledger estimates only; it is not provider billing. Keep injected summaries concise and prefer bounded output when rerunning noisy commands.
package/README.md CHANGED
@@ -55,7 +55,7 @@ That's it.
55
55
  - **Persistent execution** — Ralph, UltraQA, and Ultrawork keep going until the goal is verified
56
56
  - **File-state coordination** — workers swap typed messages over an outbox/inbox cursor with atomic `O_EXCL` task locks; no broker or daemon to babysit
57
57
  - **Chat bridge** — `omp gateway` runs long-lived chat connectors (Slack today, more next) so you can DM Copilot from anywhere
58
- - **Lifecycle hooks** — `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `SessionEnd`, `Error`
58
+ - **Lifecycle hooks** — `sessionStart`, `userPromptSubmitted`, `preToolUse`, `postToolUse`, `postToolUseFailure`, `sessionEnd`, `errorOccurred`
59
59
  - **Doctor included** — `omp doctor` verifies plugin manifest, skills discovery, hooks, and the underlying `copilot` CLI in one shot
60
60
 
61
61
  ---
@@ -84,6 +84,7 @@ That's it.
84
84
 
85
85
  - **Context & history as CLI subcommands** — `omp state` (key-value with TTL), `omp project-memory` (notes + directives), `omp trace` (per-session timeline + summary), `omp goal` / `omp memory sync` (managed repo context), `omp daily-log`
86
86
  - **Lightweight Copilot context** — managed instructions keep only the repo goal plus on-demand memory commands; set `OMP_DISABLE_INSTRUCTIONS_MEMORY=1` to skip writing the managed block entirely
87
+ - **Estimated cost ledger** — `omp cost [--today] [--session <id>]` summarizes local prompt/tool token estimates recorded by hooks. These are best-effort estimates, not provider billing.
87
88
  - **File-state worker coordination** — outbox JSONL + byte cursor, atomic `O_EXCL` task locks, optimistic CAS on claim
88
89
  - **Idle nudge** — content-based pane idle detection that pokes stuck workers
89
90
  - **Mode-state loops** — single source of truth per loop (Ralph/Ultrawork/UltraQA state files)
@@ -195,6 +196,7 @@ omp ralph start "<task>" [--max-iterations N]
195
196
  omp ultrawork start "<objective>" [--task-count N]
196
197
  omp ultraqa start "<goal>" [--max-cycles N]
197
198
  omp council "<question>" [--models a,b,c] [--context @file] [--json] # multi-model council
199
+ omp cost [--today] [--session <id>] # summarize estimated hook-ledger tokens
198
200
  omp comms status | send | recv | ask # drive a running copilot tmux session
199
201
  omp gateway serve [--only slack] # run chat connectors (today: slack)
200
202
  omp gateway status [--json] # per-connector readiness (no sockets)
@@ -272,6 +274,7 @@ omp grows in vertical slices. Items aren't pinned to specific semver versions
272
274
  - **Slack outbound — `omp gateway notify`** — stateless REST `chat.postMessage` from any process (cron `--notify-target`, in-session `/slack <message>`, ad-hoc `omp gateway notify --text "..."`). Default destination from `SLACK_HOME_CHANNEL`; explicit `--target slack:C…/G…/D…/U…` overrides; `U…` auto-resolves to a DM via `conversations.open`.
273
275
  - **Weighted-consensus council** — multi-model council with role weights + minority report. Via `omp council` or `/weighted-consensus`.
274
276
  - **Suggest** — `omp suggest "<task>"` recommends a slash-skill workflow without launching one.
277
+ - **Estimated cost ledger** — hook-driven prompt/tool token estimates are visible through `omp cost`; oversized `postToolUse` output is minimized before it re-enters model context, with raw output preserved on disk (diagnostics kept inline; other trimmed detail is recoverable by re-reading the raw path the hook reports — an extra tool call, so best with capable models). Budget gates and retry-cost guidance remain next-step optimization work, not shipped behavior yet.
275
278
 
276
279
  ### Up next
277
280
 
package/dist/src/cli.js CHANGED
@@ -26,7 +26,7 @@ function printResult(result, json) {
26
26
  }
27
27
  }
28
28
  function help() {
29
- return `oh-my-copilot\n\nRun \`omp\` with no arguments to launch copilot (permissions bypass OFF).\nUse \`omp help\` to show this list.\n\nCommands:\n (no args) launch copilot (bypass OFF by default)\n version [--json]\n list [--json]\n setup [--dry-run] [--scope project|user] [--plugin-root <dir>] [--json]\n doctor [--json] [--copilot-bin <path>] [--skip-copilot]\n launch -- <args...>\n --madmax [args...] (bare-flag launch with permissions bypass; alias of --yolo)\n team <N:role> "<task>" [--name <name>] [--json]\n team status <name> [--json]\n team shutdown <name> [--json]\n team api claim-task --input '<json>' [--json]\n team api transition-task-status --input '<json>' [--json]\n team api send-message --input '<json>' [--json]\n team api broadcast --input '<json>' [--json]\n team api mailbox-list --input '<json>' [--json]\n team api mailbox-mark-delivered --input '<json>' [--json]\n council "<question>" [--models a,b,c|m:role:weight] [--context <text|@file>] [--rubric <text|@file>] [--synth <model>] [--probe] [--timeout <ms>] [--synth-timeout <ms>] [--min-survivors <n>] [--max-concurrency <n>] [--tmp-dir <dir>] [--json]\n comms status [--session <name>] [--json] (is copilot on + online? auto-discovers session)\n comms send --text "<prompt>" [--force] [--session <name>] [--json]\n comms recv [--wait] [--lines <n>] [--timeout <ms>] [--session <name>] [--json]\n comms ask --text "<prompt>" [--force] [--lines <n>] [--timeout <ms>] [--session <name>] [--json]\n gateway serve [--only <name>[,<name>]] (run all configured connectors; today: slack)\n gateway status [--json] [--only <name>[,...]] (per-connector readiness; no sockets opened)\n gateway doctor [--json] [--only <name>[,...]] (alias for 'gateway status')\n gateway notify --text "<msg>" [--target slack:C\\|D\\|G\\|U... [:thread_ts]] [--thread-ts <ts>] [--json]\n (one-shot outbound Slack post; falls back to SLACK_HOME_CHANNEL)\n slack serve (deprecated alias for 'gateway serve --only slack')\n slack doctor [--json] (deprecated alias for 'gateway status --only slack')\n env init [--force] (interactive: write ~/.omp/.env with Slack tokens + optional SLACK_HOME_CHANNEL)\n non-interactive: set OMP_INIT_BOT_TOKEN/OMP_INIT_APP_TOKEN/OMP_INIT_HOME_CHANNEL\n (env vars preferred over --bot-token/--app-token/--home-channel flags)\n (--session is optional when exactly one omp-<digits> tmux session is running)\n${registeredCommandHelpLines().join("\n")}\n ralph start "<task>" [--max-iterations <n>] [--session-id <id>] [--json]\n ralph status [--json]\n ralph tick [--json]\n ralph cancel [--json]\n ultrawork start "<objective>" [--task-count <n>] [--summary <s>] [--json]\n ultrawork status [--json]\n ultrawork cancel [--json]\n ultraqa start "<goal>" [--max-cycles <n>] [--json]\n ultraqa cycle pass|fail|pending [--json]\n ultraqa status [--json]\n ultraqa cancel [--json]\n schedule add --id <id> --cron "<expr>" --prompt "<text>" [--bin copilot] [--model <m>] [--cwd <dir>] [--timeout <ms>] [--max-runs <n>] [--ttl-hours <h>] [--allow-all-tools] [--notify-target slack:<ID>] [--dry-run] [--json]\n schedule list [--json]\n schedule status <id> [--json]\n schedule run-now <id> [--json]\n schedule remove <id> [--json]\n goal set "<objective>" [--json]\n goal read [--json]\n memory sync [--json] (render goal+directives into copilot-instructions.md)\n daily-log set-goal "<text>" [--json]\n daily-log add "<text>" [--json]\n daily-log read [--days <n>] [--json]\n daily-log prune [--keep-days <n>] [--json]\n state write <key> <val> [--ttl <s>] | read|delete|status <key> | list | cleanup [--json]\n project-memory read [<id>] | index | add-note "<title>" [--body "<text>"] | add-directive "<rule>" [--json]\n trace timeline [<sessionId>] [--limit <n>] | summary [<sessionId>] | add <sessionId> <event> [<json>] [--json]\n catalog list [--json]\n catalog validate [--json]\n catalog capability <id> [--json]\n project inspect [--json]\n skill install <skill-dir> [--root <repo>] [--scope project|user] [--dry-run] [--json]\n lint:skills [--root <repo>]\n sync:dry-run [--root <repo>]\n jira:dry-run [--root <repo>]\n jira render <plan-file> [--root <repo>] [--json]\n jira apply <ticket-key-or-plan-file> --comment|--update|--transition|--link [--dry-run] [--json]\n`;
29
+ return `oh-my-copilot\n\nRun \`omp\` with no arguments to launch copilot (permissions bypass OFF).\nUse \`omp help\` to show this list.\n\nCommands:\n (no args) launch copilot (bypass OFF by default)\n version [--json]\n list [--json]\n setup [--dry-run] [--scope project|user] [--plugin-root <dir>] [--json]\n doctor [--json] [--copilot-bin <path>] [--skip-copilot] [--hooks]\n cost [--json] [--session <id>] [--days <n>]\n launch -- <args...>\n --madmax [args...] (bare-flag launch with permissions bypass; alias of --yolo)\n team <N:role> "<task>" [--name <name>] [--json]\n team status <name> [--json]\n team shutdown <name> [--json]\n team api claim-task --input '<json>' [--json]\n team api transition-task-status --input '<json>' [--json]\n team api send-message --input '<json>' [--json]\n team api broadcast --input '<json>' [--json]\n team api mailbox-list --input '<json>' [--json]\n team api mailbox-mark-delivered --input '<json>' [--json]\n council "<question>" [--models a,b,c|m:role:weight] [--context <text|@file>] [--rubric <text|@file>] [--synth <model>] [--probe] [--timeout <ms>] [--synth-timeout <ms>] [--min-survivors <n>] [--max-concurrency <n>] [--tmp-dir <dir>] [--json]\n comms status [--session <name>] [--json] (is copilot on + online? auto-discovers session)\n comms send --text "<prompt>" [--force] [--session <name>] [--json]\n comms recv [--wait] [--lines <n>] [--timeout <ms>] [--session <name>] [--json]\n comms ask --text "<prompt>" [--force] [--lines <n>] [--timeout <ms>] [--session <name>] [--json]\n gateway serve [--only <name>[,<name>]] (run all configured connectors; today: slack)\n gateway status [--json] [--only <name>[,...]] (per-connector readiness; no sockets opened)\n gateway doctor [--json] [--only <name>[,...]] (alias for 'gateway status')\n gateway notify --text "<msg>" [--target slack:C\\|D\\|G\\|U... [:thread_ts]] [--thread-ts <ts>] [--json]\n (one-shot outbound Slack post; falls back to SLACK_HOME_CHANNEL)\n slack serve (deprecated alias for 'gateway serve --only slack')\n slack doctor [--json] (deprecated alias for 'gateway status --only slack')\n env init [--force] (interactive: write ~/.omp/.env with Slack tokens + optional SLACK_HOME_CHANNEL)\n non-interactive: set OMP_INIT_BOT_TOKEN/OMP_INIT_APP_TOKEN/OMP_INIT_HOME_CHANNEL\n (env vars preferred over --bot-token/--app-token/--home-channel flags)\n (--session is optional when exactly one omp-<digits> tmux session is running)\n${registeredCommandHelpLines().join("\n")}\n ralph start "<task>" [--max-iterations <n>] [--session-id <id>] [--json]\n ralph status [--json]\n ralph tick [--json]\n ralph cancel [--json]\n ultrawork start "<objective>" [--task-count <n>] [--summary <s>] [--json]\n ultrawork status [--json]\n ultrawork cancel [--json]\n ultraqa start "<goal>" [--max-cycles <n>] [--json]\n ultraqa cycle pass|fail|pending [--json]\n ultraqa status [--json]\n ultraqa cancel [--json]\n schedule add --id <id> --cron "<expr>" --prompt "<text>" [--bin copilot] [--model <m>] [--cwd <dir>] [--timeout <ms>] [--max-runs <n>] [--ttl-hours <h>] [--allow-all-tools] [--notify-target slack:<ID>] [--dry-run] [--json]\n schedule list [--json]\n schedule status <id> [--json]\n schedule run-now <id> [--json]\n schedule remove <id> [--json]\n goal set "<objective>" [--json]\n goal read [--json]\n memory sync [--json] (render goal+directives into copilot-instructions.md)\n config get [--json] | config set memory-mode on|off | config set memory-review-model <slug> | config set memory-review-min-messages <n> [--global]\n (--global writes ~/.omp/config.json; applies to every project. project .omp/config.json overrides it)\n memory-review --session <uuid|latest> [--model <slug>] [--json] (cheap-model end-of-session review; opt-in via memory-mode)\n daily-log set-goal "<text>" [--json]\n daily-log add "<text>" [--json]\n daily-log read [--days <n>] [--json]\n daily-log prune [--keep-days <n>] [--json]\n state write <key> <val> [--ttl <s>] | read|delete|status <key> | list | cleanup [--json]\n project-memory read [<id>] | index | add-note "<title>" [--body "<text>"] | add-directive "<rule>" | prune-notes --keep <n>|--older-than <days> [--json]\n trace timeline [<sessionId>] [--limit <n>] | summary [<sessionId>] | add <sessionId> <event> [<json>] [--json]\n catalog list [--json]\n catalog validate [--json]\n catalog capability <id> [--json]\n project inspect [--json]\n skill install <skill-dir> [--root <repo>] [--scope project|user] [--dry-run] [--json]\n lint:skills [--root <repo>]\n sync:dry-run [--root <repo>]\n jira:dry-run [--root <repo>]\n jira render <plan-file> [--root <repo>] [--json]\n jira apply <ticket-key-or-plan-file> --comment|--update|--transition|--link [--dry-run] [--json]\n`;
30
30
  }
31
31
  async function resolveExistingInputPath(value) {
32
32
  const { existsSync } = await import("node:fs");
@@ -40,6 +40,33 @@ async function resolveExistingInputPath(value) {
40
40
  return direct;
41
41
  }
42
42
  const BARE_LAUNCH_FLAGS = new Set(["--madmax", "--yolo"]);
43
+ // Snapshot existing Copilot session dirs BEFORE launch, so after a headless run
44
+ // we can identify the exact session it created (instead of guessing "latest").
45
+ async function snapshotSessionsForReview() {
46
+ try {
47
+ const { listSessionIds } = await import("./memory-review/transcript.js");
48
+ return listSessionIds();
49
+ }
50
+ catch {
51
+ return [];
52
+ }
53
+ }
54
+ // Headless `copilot -p` skips hooks, so when omp launched copilot in headless
55
+ // mode we detach the end-of-session memory review here. We review the session
56
+ // that appeared since `before` — never a guessed "latest" — and skip if none
57
+ // is identifiable. Best-effort: a failure here must never affect the launch.
58
+ async function maybeTriggerHeadlessReview(argv, cwd, before) {
59
+ try {
60
+ const { triggerHeadlessReview } = await import("./memory-review/trigger.js");
61
+ const { newestSessionSince } = await import("./memory-review/transcript.js");
62
+ const cliPath = join(packageRootFromImportMeta(import.meta.url), "dist", "src", "cli.js");
63
+ const sessionId = newestSessionSince(before) ?? "";
64
+ triggerHeadlessReview({ cwd, argv, cliPath, sessionId });
65
+ }
66
+ catch {
67
+ // never fail a launch on the review trigger
68
+ }
69
+ }
43
70
  export async function runCli(argv = process.argv.slice(2)) {
44
71
  const [group, command, value] = argv;
45
72
  const json = hasFlag(argv, "--json");
@@ -70,11 +97,14 @@ export async function runCli(argv = process.argv.slice(2)) {
70
97
  // case argv is empty, so normalizeCopilotLaunchArgs emits no --yolo.
71
98
  if (!group || BARE_LAUNCH_FLAGS.has(group)) {
72
99
  const { launchCopilot } = await import("./copilot/launch.js");
100
+ const launchCwd = flagValue(argv, "--root") ?? process.cwd();
101
+ const beforeSessions = await snapshotSessionsForReview();
73
102
  const result = await launchCopilot({
74
103
  args: argv,
75
104
  bin: flagValue(argv, "--bin"),
76
- cwd: flagValue(argv, "--root") ?? process.cwd(),
105
+ cwd: launchCwd,
77
106
  });
107
+ await maybeTriggerHeadlessReview(argv, launchCwd, beforeSessions);
78
108
  return json
79
109
  ? { ok: result.ok, exitCode: result.exitCode, output: result }
80
110
  : {
@@ -107,20 +137,32 @@ export async function runCli(argv = process.argv.slice(2)) {
107
137
  importMetaUrl: import.meta.url,
108
138
  copilotBin: flagValue(argv, "--copilot-bin"),
109
139
  skipCopilot: hasFlag(argv, "--skip-copilot"),
140
+ checkHooks: hasFlag(argv, "--hooks"),
110
141
  });
111
142
  return json
112
143
  ? { ok: report.ok, exitCode: report.ok ? 0 : 1, output: report }
113
144
  : { ok: report.ok, exitCode: report.ok ? 0 : 1, message: formatDoctor(report) };
114
145
  }
146
+ if (group === "cost") {
147
+ const { summarizeCost, formatCostSummary } = await import("./cost/summary.js");
148
+ const summary = summarizeCost(flagValue(argv, "--root") ?? process.cwd(), {
149
+ sessionId: flagValue(argv, "--session"),
150
+ today: hasFlag(argv, "--today"),
151
+ });
152
+ return json ? { ok: true, output: summary } : { ok: true, message: formatCostSummary(summary) };
153
+ }
115
154
  if (group === "launch") {
116
155
  const dashIndex = argv.indexOf("--");
117
156
  const passthrough = dashIndex >= 0 ? argv.slice(dashIndex + 1) : argv.slice(1);
118
157
  const { launchCopilot } = await import("./copilot/launch.js");
158
+ const launchCwd = flagValue(argv, "--root") ?? process.cwd();
159
+ const beforeSessions = await snapshotSessionsForReview();
119
160
  const result = await launchCopilot({
120
161
  args: passthrough,
121
162
  bin: flagValue(argv, "--bin"),
122
- cwd: flagValue(argv, "--root") ?? process.cwd(),
163
+ cwd: launchCwd,
123
164
  });
165
+ await maybeTriggerHeadlessReview(passthrough, launchCwd, beforeSessions);
124
166
  return json
125
167
  ? { ok: result.ok, exitCode: result.exitCode, output: result }
126
168
  : {
@@ -192,6 +234,80 @@ export async function runCli(argv = process.argv.slice(2)) {
192
234
  }
193
235
  return { ok: false, exitCode: 1, message: "Unknown memory subcommand. Try: memory sync" };
194
236
  }
237
+ if (group === "config") {
238
+ const { readMemoryConfig, setMemoryConfigValue } = await import("./memory-review/config.js");
239
+ const cwd = flagValue(argv, "--root") ?? process.cwd();
240
+ // OMP_HOME_OVERRIDE relocates the global ~/.omp config dir (test seam; also
241
+ // lets users point at a custom home). Undefined => os.homedir() default.
242
+ const homeDir = process.env.OMP_HOME_OVERRIDE || undefined;
243
+ // `--global` writes to ~/.omp/config.json so the setting applies everywhere.
244
+ const scope = hasFlag(argv, "--global") ? "global" : "project";
245
+ if (command === "get" || command === undefined) {
246
+ const cfg = readMemoryConfig(cwd, { homeDir });
247
+ return json
248
+ ? { ok: true, output: cfg }
249
+ : {
250
+ ok: true,
251
+ message: `memory-mode=${cfg.memoryMode}\nmemory-review-model=${cfg.memoryReviewModel}\nmemory-review-min-messages=${cfg.memoryReviewMinMessages}`,
252
+ };
253
+ }
254
+ if (command === "set") {
255
+ const setVal = argv[3];
256
+ const where = scope === "global" ? " (global ~/.omp)" : "";
257
+ if (value === "memory-mode") {
258
+ if (setVal !== "on" && setVal !== "off") {
259
+ return { ok: false, exitCode: 1, message: "usage: omp config set memory-mode on|off [--global]" };
260
+ }
261
+ setMemoryConfigValue(cwd, "memoryMode", setVal, { scope, homeDir });
262
+ return json ? { ok: true, output: { memoryMode: setVal, scope } } : { ok: true, message: `memory-mode=${setVal}${where}` };
263
+ }
264
+ if (value === "memory-review-model") {
265
+ if (!setVal || setVal.startsWith("-")) {
266
+ return { ok: false, exitCode: 1, message: "usage: omp config set memory-review-model <slug> [--global]" };
267
+ }
268
+ setMemoryConfigValue(cwd, "memoryReviewModel", setVal, { scope, homeDir });
269
+ return json ? { ok: true, output: { memoryReviewModel: setVal, scope } } : { ok: true, message: `memory-review-model=${setVal}${where}` };
270
+ }
271
+ if (value === "memory-review-min-messages") {
272
+ const n = Number(setVal);
273
+ if (!Number.isFinite(n) || n < 0) {
274
+ return { ok: false, exitCode: 1, message: "usage: omp config set memory-review-min-messages <non-negative integer> [--global]" };
275
+ }
276
+ setMemoryConfigValue(cwd, "memoryReviewMinMessages", String(Math.floor(n)), { scope, homeDir });
277
+ return json ? { ok: true, output: { memoryReviewMinMessages: Math.floor(n), scope } } : { ok: true, message: `memory-review-min-messages=${Math.floor(n)}${where}` };
278
+ }
279
+ return { ok: false, exitCode: 1, message: "Unknown config key. Try: memory-mode | memory-review-model | memory-review-min-messages" };
280
+ }
281
+ return { ok: false, exitCode: 1, message: "Unknown config subcommand. Try: config get | config set <key> <value>" };
282
+ }
283
+ if (group === "memory-review") {
284
+ const { runMemoryReview } = await import("./memory-review/index.js");
285
+ const { createReviewSpawn } = await import("./memory-review/spawn.js");
286
+ const { isValidSessionId, latestSessionId } = await import("./memory-review/transcript.js");
287
+ const cwd = flagValue(argv, "--root") ?? process.cwd();
288
+ let sessionId = flagValue(argv, "--session") ?? "";
289
+ if (!sessionId || sessionId === "latest") {
290
+ sessionId = latestSessionId() ?? "";
291
+ }
292
+ if (!sessionId) {
293
+ return { ok: false, exitCode: 1, message: "usage: omp memory-review --session <uuid|latest>" };
294
+ }
295
+ if (!isValidSessionId(sessionId)) {
296
+ return { ok: false, exitCode: 1, message: "invalid --session id" };
297
+ }
298
+ const res = await runMemoryReview({
299
+ cwd,
300
+ sessionId,
301
+ spawn: createReviewSpawn(flagValue(argv, "--bin")),
302
+ model: flagValue(argv, "--model"),
303
+ });
304
+ return json
305
+ ? { ok: true, output: res }
306
+ : {
307
+ ok: true,
308
+ message: res.ran ? `memory-review ran: ${JSON.stringify(res.summary)}` : `memory-review skipped: ${res.reason}`,
309
+ };
310
+ }
195
311
  if (group === "daily-log") {
196
312
  const { setDailyGoal, addLogEntry, readDailyLog, pruneDailyLog } = await import("./daily-log.js");
197
313
  const cwd = flagValue(argv, "--root") ?? process.cwd();
@@ -304,6 +420,20 @@ export async function runCli(argv = process.argv.slice(2)) {
304
420
  if (command === "index") {
305
421
  return { ok: true, output: { notes: pm.noteIndex(cwd) } };
306
422
  }
423
+ if (command === "prune-notes") {
424
+ const keepRaw = flagValue(argv, "--keep");
425
+ const olderRaw = flagValue(argv, "--older-than");
426
+ const keep = keepRaw !== undefined && Number.isFinite(Number(keepRaw)) ? Number(keepRaw) : undefined;
427
+ const olderThanDays = olderRaw !== undefined && Number.isFinite(Number(olderRaw)) ? Number(olderRaw) : undefined;
428
+ if (keep === undefined && olderThanDays === undefined) {
429
+ return { ok: false, exitCode: 1, message: "usage: omp project-memory prune-notes --keep <n> | --older-than <days>" };
430
+ }
431
+ const removed = pm.pruneNotes(cwd, { keep, olderThanDays });
432
+ syncInstructionsMemory(cwd); // refresh the managed block after pruning
433
+ return json
434
+ ? { ok: true, output: { removed } }
435
+ : { ok: true, message: `pruned ${removed.length} note${removed.length === 1 ? "" : "s"}` };
436
+ }
307
437
  if (command === "read" || command === undefined) {
308
438
  // `read <id>` loads one note's body on demand; bare `read` returns the
309
439
  // bounded summary (directives + note index — never note bodies).
@@ -318,7 +448,7 @@ export async function runCli(argv = process.argv.slice(2)) {
318
448
  return {
319
449
  ok: false,
320
450
  exitCode: 1,
321
- message: 'Unknown project-memory subcommand. Try: project-memory read [<id>] | index | add-note "<title>" [--body "<text>"] | add-directive "<rule>"',
451
+ message: 'Unknown project-memory subcommand. Try: project-memory read [<id>] | index | add-note "<title>" [--body "<text>"] | add-directive "<rule>" | prune-notes --keep <n>|--older-than <days>',
322
452
  };
323
453
  }
324
454
  if (group === "trace") {