@damian87/omp 0.4.0 → 0.5.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 (59) hide show
  1. package/.github/agents/researcher.md +7 -6
  2. package/.github/copilot-instructions.md +23 -0
  3. package/.github/skills/daily-log/SKILL.md +64 -0
  4. package/.github/skills/goal/SKILL.md +33 -0
  5. package/.github/skills/weighted-consensus/SKILL.md +12 -4
  6. package/README.md +16 -0
  7. package/dist/src/cli.js +198 -10
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/comms/index.d.ts +116 -0
  10. package/dist/src/comms/index.js +258 -0
  11. package/dist/src/comms/index.js.map +1 -0
  12. package/dist/src/comms/resolve-session.d.ts +35 -0
  13. package/dist/src/comms/resolve-session.js +53 -0
  14. package/dist/src/comms/resolve-session.js.map +1 -0
  15. package/dist/src/council/config.d.ts +1 -0
  16. package/dist/src/council/config.js +5 -0
  17. package/dist/src/council/config.js.map +1 -1
  18. package/dist/src/council/engine.d.ts +2 -0
  19. package/dist/src/council/engine.js +26 -3
  20. package/dist/src/council/engine.js.map +1 -1
  21. package/dist/src/council/index.d.ts +1 -1
  22. package/dist/src/council/index.js.map +1 -1
  23. package/dist/src/council/prompts.js +0 -1
  24. package/dist/src/council/prompts.js.map +1 -1
  25. package/dist/src/council/synth.js +1 -1
  26. package/dist/src/council/synth.js.map +1 -1
  27. package/dist/src/council/types.d.ts +2 -0
  28. package/dist/src/daily-log.d.ts +18 -0
  29. package/dist/src/daily-log.js +138 -0
  30. package/dist/src/daily-log.js.map +1 -0
  31. package/dist/src/goal.d.ts +4 -0
  32. package/dist/src/goal.js +44 -0
  33. package/dist/src/goal.js.map +1 -0
  34. package/dist/src/instructions-memory.d.ts +9 -0
  35. package/dist/src/instructions-memory.js +72 -0
  36. package/dist/src/instructions-memory.js.map +1 -0
  37. package/dist/src/mcp/tools/daily-log.d.ts +2 -0
  38. package/dist/src/mcp/tools/daily-log.js +148 -0
  39. package/dist/src/mcp/tools/daily-log.js.map +1 -0
  40. package/dist/src/omp-root.d.ts +1 -0
  41. package/dist/src/omp-root.js +19 -0
  42. package/dist/src/omp-root.js.map +1 -0
  43. package/dist/src/project-memory.d.ts +13 -0
  44. package/dist/src/project-memory.js +105 -0
  45. package/dist/src/project-memory.js.map +1 -0
  46. package/dist/src/state.d.ts +17 -0
  47. package/dist/src/state.js +101 -0
  48. package/dist/src/state.js.map +1 -0
  49. package/dist/src/trace.d.ts +19 -0
  50. package/dist/src/trace.js +74 -0
  51. package/dist/src/trace.js.map +1 -0
  52. package/package.json +3 -1
  53. package/scripts/lib/daily-log.mjs +155 -0
  54. package/scripts/lib/hook-output.mjs +2 -1
  55. package/scripts/lib/omp-root.mjs +15 -0
  56. package/scripts/lib/project-memory.mjs +21 -0
  57. package/scripts/prompt-submit.mjs +14 -2
  58. package/scripts/session-end.mjs +6 -1
  59. package/scripts/session-start.mjs +52 -2
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: researcher
3
- description: Map the codebase or external docs to answer a specific question. Returns evidence, not opinions. Backed by the oh-my-copilot MCP tools (notepad / project_memory / shared_memory / trace) for note-taking across turns.
3
+ description: Map the codebase or external docs to answer a specific question. Returns evidence, not opinions. Backed by the oh-my-copilot CLI (omp daily-log / project-memory / state) for note-taking across turns.
4
4
  ---
5
5
 
6
6
  # researcher
@@ -17,10 +17,11 @@ Find evidence and document it. Do not propose changes.
17
17
  - Open questions that need more digging (if any).
18
18
 
19
19
  ## Guidance
20
- - Use the MCP tools when available:
21
- - `notepad_write_working` to stash interim findings between turns.
22
- - `project_memory_add_note` for facts worth keeping for the whole project.
23
- - `trace_timeline` if the question involves event history.
20
+ - Use the `omp` CLI for note-taking (run as shell commands):
21
+ - `omp daily-log add "<text>"` to record interim findings as you go.
22
+ - `omp project-memory add-note "<text>"` for facts worth keeping for the whole project.
23
+ - `omp state write <key> <json>` / `omp state read <key>` for transient structured scratch between turns.
24
+ - `omp trace timeline` if the question involves event history.
24
25
  - Cite, don't paraphrase. Quote the line you found.
25
26
  - If you can't find an answer, say so plainly — don't invent one.
26
- - Do not modify code or files unrelated to research artifacts (notepad, project memory).
27
+ - Do not modify code or files unrelated to research artifacts (daily log, project memory).
@@ -33,3 +33,26 @@ Skip when the session was fewer than 3 turns or contained no user corrections.
33
33
  Invoke `/self-evolve` **at most once per session.** After it reports back, do not invoke it again — even if the user replies "thanks" or "done" to the report itself. Just acknowledge and end the session.
34
34
 
35
35
  The skill lives at `.github/skills/self-evolve/SKILL.md`. It is self-contained: it tells you which signals count as corrections, where to log them, and when a recurring pattern should become a draft skill.
36
+
37
+ ## Daily log & repo goal
38
+
39
+ Keep lightweight memory in `.omp/` so the next session has context. **Be sparse** — these are
40
+ memory aids, not a transcript. Float kills their value.
41
+
42
+ - **Repo goal** (`.omp/goal.md`, via `/goal`): set/update the one-line objective when it is
43
+ established or genuinely shifts. Not every session.
44
+ - **Daily log** (`.omp/memory/daily/<date>.md`, via `/daily-log`):
45
+ - When a heavy mode (`/ralph`, `/ralplan`, `/omp-autopilot`, `/team`, `/ultrawork`,
46
+ `/ultraqa`) starts, set today's goal from its objective: `omp daily-log set-goal "<text>"`.
47
+ - When such a mode finishes, add **one** concise summary entry — `omp daily-log add "<text>"`:
48
+ what changed, key decisions, next step.
49
+ - Otherwise add an entry only at a genuine milestone you judge worth remembering.
50
+ - **Project memory**: `omp project-memory add-directive "<rule>"` for a must-follow rule —
51
+ directives are **injected at every session start** (never on-demand), so add only real,
52
+ lasting rules and keep them few. `omp project-memory add-note "<title>" [--body "<text>"]`
53
+ for durable facts; list them with `omp project-memory index` and load one with
54
+ `omp project-memory read <id>` — bodies stay on disk until asked, so notes never bloat context.
55
+ - **Caps & retention:** keep directives to a handful (only the first ~12 are injected at start);
56
+ a few daily entries per day — decisions and outcomes, not narration; skip trivial sessions;
57
+ never log per skill invocation. Trim old day-files with `omp daily-log prune [--keep-days <n>]`
58
+ (default 30).
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: daily-log
3
+ description: Keep a simple per-project daily memory log under the project's daily-memory directory. Review it at session start when relevant, set today's goal, append milestones, and summarize before wrapping up. Use when the user says /daily-log, asks to record/recall what was done, or at end of session.
4
+ ---
5
+
6
+ # Daily Log
7
+
8
+ A lightweight, human-readable journal stored **per project** at `.omp/memory/daily/<YYYY-MM-DD>.md`.
9
+ Each day has a `## Goal` and a timestamped `## Log`. It is written and read through the `omp`
10
+ CLI (run these as shell commands):
11
+
12
+ - `omp daily-log set-goal "<text>"` — set/replace today's goal (the day's intent).
13
+ - `omp daily-log add "<text>"` — append a timestamped bullet to today's log.
14
+ - `omp daily-log read [--days N]` — read today + the previous N days (default 1).
15
+
16
+ The command writes under the current project's `.omp/`, so every project keeps its own log
17
+ automatically.
18
+
19
+ ## When to use each
20
+
21
+ ### At session start (review)
22
+ The SessionStart hook injects a one-line breadcrumb, e.g.:
23
+
24
+ ```
25
+ [DAILY LOG] Goal: <today's goal>
26
+ N entries logged in the last 7 days — run `omp daily-log read` to load if relevant.
27
+ ```
28
+
29
+ If the breadcrumb looks relevant to what the user is asking for — same feature, an open
30
+ goal, or continuation of yesterday's work — run `omp daily-log read` to pull the full recent
31
+ log. If the new request is clearly unrelated, **skip it**; loading is optional.
32
+
33
+ ### When intent is established
34
+ Once you and the user agree on what today is about, run `omp daily-log set-goal "<text>"` with a
35
+ short one-line goal (e.g. "Ship the daily-log feature").
36
+
37
+ ### At milestones
38
+ After a meaningful step — a decision made, a feature landed, a blocker hit — run
39
+ `omp daily-log add "<text>"` with a concise note. Record *what changed and why*, not narration.
40
+ Good: "Chose breadcrumb+lazy-load over always-inject to keep start message small."
41
+ Avoid: "Ran the build."
42
+
43
+ ### Resume nudge (start of the next session)
44
+ There is no per-turn nudge. Instead, if a session did real work but wrote nothing,
45
+ the **next** SessionStart injects a one-line reminder:
46
+
47
+ ```
48
+ [DAILY LOG] Your last session made progress but recorded nothing ...
49
+ ```
50
+
51
+ Treat it as a prompt to capture what changed before moving on — or, better, summarize
52
+ into the log *before ending* the session (below), so the nudge never needs to fire.
53
+
54
+ ### Before wrapping up (the real end-of-session write)
55
+ When the user signals they're done, **summarize the session into the log** with one or two
56
+ `omp daily-log add "<text>"` commands: what was accomplished, key decisions, and the next step
57
+ for tomorrow. The SessionEnd hook cannot reach the model, so this end-of-session summary happens here.
58
+
59
+ ## Notes
60
+ - Keep entries short; the log is a memory aid, not a transcript.
61
+ - The file is safe to hand-edit: the CLI preserves your lines in the Goal and Log
62
+ sections verbatim (only blank spacer lines between entries are dropped on rewrite).
63
+ - Do not put secrets in the log — it is a plain file committed nowhere by this skill but
64
+ readable by anyone with repo access.
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: goal
3
+ description: Set or show the repo's durable objective — what we want to achieve in this repo — stored per project at .omp/goal.md. Use when the user says /goal, states the repo's north-star, or asks "what are we trying to achieve here".
4
+ ---
5
+
6
+ # Goal
7
+
8
+ The repo's **durable objective** — the north-star for this project — stored per project at
9
+ `.omp/goal.md`. It is distinct from a daily log's per-day goal: this is the long-lived "what
10
+ we want to achieve in this repo", set rarely and updated only when the objective changes.
11
+
12
+ It is read and written through the `omp` CLI (run these as shell commands):
13
+
14
+ - `omp goal set "<objective>"` — set/replace the repo objective (one concise line).
15
+ - `omp goal read` — print the current repo objective.
16
+
17
+ The command writes under the current project's `.omp/`, so every repo keeps its own goal
18
+ automatically.
19
+
20
+ ## When to use
21
+
22
+ - **`/goal <text>`** — the user states the objective; run `omp goal set "<one concise line>"`.
23
+ - **`/goal`** (no text) — run `omp goal read` and show the current objective. If none is set,
24
+ offer to set one.
25
+ - The SessionStart hook surfaces the goal as a `[REPO GOAL] …` line, so it is already in
26
+ context at the start of each session — only run `omp goal read` when the user asks explicitly.
27
+
28
+ ## Notes
29
+
30
+ - Keep it to a single north-star sentence; it is not a task list. Use the daily log
31
+ (`/daily-log`) for day-to-day progress toward this goal.
32
+ - Update it when the objective genuinely shifts — not every session.
33
+ - Do not put secrets in it; `.omp/goal.md` is a plain file readable by anyone with repo access.
@@ -26,9 +26,10 @@ synthesizer model reasons over their answers — using each member's weight as a
26
26
  3. **Synthesis** — a low-context synthesizer model merges survivors into a final
27
27
  `verdict`, `confidence`, `rationale`, and `minority_report`. Evidence quality
28
28
  can override weight.
29
- 4. **Graceful degradation** — timed-out, errored, unavailable, or unparseable
30
- members are dropped; the council still synthesizes from the rest, and only
31
- fails if fewer than `min-survivors` (default 2) remain.
29
+ 4. **Graceful degradation** — errored, unavailable, or unparseable members are
30
+ dropped; timed-out members that produced valid JSON are recovered as
31
+ survivors. The council synthesizes from all survivors and only fails if fewer
32
+ than `min-survivors` (default 2) remain.
32
33
 
33
34
  ## Agent execution steps (FOLLOW EXACTLY)
34
35
 
@@ -48,7 +49,7 @@ degradation, and synthesis for you — do NOT try to call models yourself:
48
49
  ```
49
50
  omp council "<question>" [--models a,b,c | model:role:weight,...] \
50
51
  [--context <text|@file>] [--rubric <text|@file>] \
51
- [--synth <model>] [--probe] [--timeout <ms>] \
52
+ [--synth <model>] [--probe] [--timeout <ms>] [--synth-timeout <ms>] \
52
53
  [--min-survivors <n>] [--max-concurrency <n>] [--tmp-dir <dir>] [--json]
53
54
  ```
54
55
 
@@ -62,6 +63,12 @@ omp council "<question>" [--models a,b,c | model:role:weight,...] \
62
63
  default; the normal run already reports unavailable models inline).
63
64
  - `--json` — emit the full structured result for programmatic use; omit it for a
64
65
  readable verdict + dropped-member summary.
66
+ - `--synth-timeout <ms>` — synthesizer timeout (default: 2× `--timeout`). The
67
+ synth processes all member outputs so may need longer than individual members.
68
+ The synth prompt does NOT include the shared context (members already digested
69
+ it), so this is mainly needed for councils with many members or long rationale.
70
+ - Timed-out members that produced valid JSON before the kill signal are
71
+ automatically recovered as survivors — they are not dropped.
65
72
 
66
73
  If `omp council` is not found (the published `omp` predates this feature), fall
67
74
  back to the local build from the repo root: `node dist/src/cli.js council "<question>" ...`
@@ -94,6 +101,7 @@ default roster is used.
94
101
  "synthesizer": "gpt-4.1",
95
102
  "minSurvivors": 2,
96
103
  "maxConcurrency": 4,
104
+ "synthTimeoutMs": 240000,
97
105
  "probe": false,
98
106
  "members": [
99
107
  { "model": "gpt-5-mini", "role": "critic", "weight": 0.4 },
package/README.md CHANGED
@@ -284,3 +284,19 @@ To unlink and revert to the published package:
284
284
  npm unlink -g @damian87/omp
285
285
  npm i -g @damian87/omp
286
286
  ```
287
+
288
+ ## Releasing (maintainers)
289
+
290
+ One command runs the whole flow — quality gates → version bump → tag → push →
291
+ GitHub release → `npm publish`. It's a maintainer script (`tools/release.sh`),
292
+ **not** a published plugin skill, so it never ships in the package or appears in
293
+ the in-session skill catalog. npm 2FA is on, so pass an authenticator code via `--otp`.
294
+
295
+ ```bash
296
+ npm run release -- minor --otp 123456 # bump 0.x.0, tag, push, GH release, publish
297
+ npm run release -- patch --dry-run # validate everything, change nothing
298
+ npm run release:publish-only -- --otp 123456 # build + publish the CURRENT version (recover a half-done release)
299
+ ```
300
+
301
+ The script refuses to run unless you're on a clean, up-to-date `main` and the
302
+ gates (`test`, `lint:skills`, `check:catalog`, `build`) pass.
package/dist/src/cli.js CHANGED
@@ -22,7 +22,7 @@ function printResult(result, json) {
22
22
  }
23
23
  }
24
24
  function help() {
25
- 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>] [--min-survivors <n>] [--max-concurrency <n>] [--tmp-dir <dir>] [--json]\n mcp (run MCP server over stdio)\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 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`;
25
+ 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 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 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`;
26
26
  }
27
27
  async function resolveExistingInputPath(value) {
28
28
  const { existsSync } = await import("node:fs");
@@ -117,14 +117,6 @@ export async function runCli(argv = process.argv.slice(2)) {
117
117
  if (group === "council") {
118
118
  return await handleCouncilCommand(argv, json);
119
119
  }
120
- if (group === "mcp") {
121
- const { runMcpServer } = await import("./mcp/server.js");
122
- const { allTools } = await import("./mcp/tools/index.js");
123
- const { filterToolsByEnv } = await import("./mcp/server.js");
124
- const tools = filterToolsByEnv(allTools);
125
- await runMcpServer({ name: "oh-my-copilot", version: "0.1.0", tools });
126
- return { ok: true, message: "mcp server exited" };
127
- }
128
120
  if (group === "ralph") {
129
121
  return await handleModeCommand("ralph", argv, json);
130
122
  }
@@ -134,6 +126,195 @@ export async function runCli(argv = process.argv.slice(2)) {
134
126
  if (group === "ultraqa") {
135
127
  return await handleModeCommand("ultraqa", argv, json);
136
128
  }
129
+ if (group === "goal") {
130
+ const { readRepoGoal, writeRepoGoal } = await import("./goal.js");
131
+ const { syncInstructionsMemory } = await import("./instructions-memory.js");
132
+ const cwd = flagValue(argv, "--root") ?? process.cwd();
133
+ if (command === "set") {
134
+ if (!value || !value.trim() || value.startsWith("-")) {
135
+ return { ok: false, exitCode: 1, message: 'usage: omp goal set "<objective>"' };
136
+ }
137
+ const goal = writeRepoGoal(cwd, value);
138
+ syncInstructionsMemory(cwd); // refresh the managed block Copilot reads
139
+ return json ? { ok: true, output: { ok: true, goal } } : { ok: true, message: `repo goal set: ${goal}` };
140
+ }
141
+ if (command === "read" || command === undefined) {
142
+ const goal = readRepoGoal(cwd);
143
+ return json ? { ok: true, output: { goal } } : { ok: true, message: goal || "(no repo goal set)" };
144
+ }
145
+ return { ok: false, exitCode: 1, message: 'Unknown goal subcommand. Try: goal set "<text>" | goal read' };
146
+ }
147
+ if (group === "memory") {
148
+ const { syncInstructionsMemory } = await import("./instructions-memory.js");
149
+ const cwd = flagValue(argv, "--root") ?? process.cwd();
150
+ if (command === "sync" || command === undefined) {
151
+ const r = syncInstructionsMemory(cwd);
152
+ return json
153
+ ? { ok: r.wrote, output: r }
154
+ : { ok: r.wrote, message: r.wrote ? `memory synced to ${r.path}` : `could not write ${r.path}` };
155
+ }
156
+ return { ok: false, exitCode: 1, message: "Unknown memory subcommand. Try: memory sync" };
157
+ }
158
+ if (group === "daily-log") {
159
+ const { setDailyGoal, addLogEntry, readDailyLog, pruneDailyLog } = await import("./daily-log.js");
160
+ const cwd = flagValue(argv, "--root") ?? process.cwd();
161
+ if (command === "set-goal") {
162
+ if (!value || !value.trim() || value.startsWith("-")) {
163
+ return { ok: false, exitCode: 1, message: 'usage: omp daily-log set-goal "<text>"' };
164
+ }
165
+ const res = setDailyGoal(cwd, value);
166
+ return json ? { ok: true, output: { ok: true, ...res } } : { ok: true, message: `daily goal set (${res.date}): ${res.goal}` };
167
+ }
168
+ if (command === "add") {
169
+ if (!value || !value.trim() || value.startsWith("-")) {
170
+ return { ok: false, exitCode: 1, message: 'usage: omp daily-log add "<text>"' };
171
+ }
172
+ const res = addLogEntry(cwd, value);
173
+ return json ? { ok: true, output: { ok: true, ...res } } : { ok: true, message: `logged (${res.date}); ${res.count} entr${res.count === 1 ? "y" : "ies"} today` };
174
+ }
175
+ if (command === "read" || command === undefined) {
176
+ const daysRaw = flagValue(argv, "--days");
177
+ const days = daysRaw !== undefined && Number.isFinite(Number(daysRaw)) ? Number(daysRaw) : 1;
178
+ const text = readDailyLog(cwd, days);
179
+ return json ? { ok: true, output: { log: text } } : { ok: true, message: text || "(no daily log entries)" };
180
+ }
181
+ if (command === "prune") {
182
+ const keepRaw = flagValue(argv, "--keep-days");
183
+ const keepDays = keepRaw !== undefined && Number.isFinite(Number(keepRaw)) ? Number(keepRaw) : 30;
184
+ const removed = pruneDailyLog(cwd, keepDays);
185
+ return json
186
+ ? { ok: true, output: { removed } }
187
+ : { ok: true, message: `pruned ${removed.length} day-file(s) older than ${keepDays}d` };
188
+ }
189
+ return {
190
+ ok: false,
191
+ exitCode: 1,
192
+ message: 'Unknown daily-log subcommand. Try: daily-log set-goal "<text>" | add "<text>" | read [--days N] | prune [--keep-days N]',
193
+ };
194
+ }
195
+ if (group === "state") {
196
+ const s = await import("./state.js");
197
+ const cwd = flagValue(argv, "--root") ?? process.cwd();
198
+ if (command === "read" && value) {
199
+ return { ok: true, output: s.stateRead(cwd, value) };
200
+ }
201
+ if (command === "write" && value) {
202
+ const raw = argv[3];
203
+ if (raw === undefined)
204
+ return { ok: false, exitCode: 1, message: "usage: omp state write <key> <json-or-string> [--ttl <sec>]" };
205
+ let parsed;
206
+ try {
207
+ parsed = JSON.parse(raw);
208
+ }
209
+ catch {
210
+ parsed = raw;
211
+ }
212
+ const ttlRaw = flagValue(argv, "--ttl");
213
+ const ttl = ttlRaw !== undefined && Number.isFinite(Number(ttlRaw)) ? Number(ttlRaw) : undefined;
214
+ const expiresAt = s.stateWrite(cwd, value, parsed, ttl);
215
+ return json ? { ok: true, output: { ok: true, expiresAt } } : { ok: true, message: `wrote ${value}${expiresAt ? ` (expires ${expiresAt})` : ""}` };
216
+ }
217
+ if (command === "delete" && value) {
218
+ s.stateDelete(cwd, value);
219
+ return { ok: true, message: `deleted ${value}` };
220
+ }
221
+ if (command === "list") {
222
+ return { ok: true, output: { keys: s.stateList(cwd) } };
223
+ }
224
+ if (command === "cleanup") {
225
+ const deleted = s.stateCleanup(cwd);
226
+ return json ? { ok: true, output: { deleted } } : { ok: true, message: `cleaned ${deleted} expired` };
227
+ }
228
+ if (command === "status" && value) {
229
+ return { ok: true, output: s.stateStatus(cwd, value) };
230
+ }
231
+ return { ok: false, exitCode: 1, message: "Unknown state subcommand. Try: state read <key> | write <key> <val> [--ttl s] | delete <key> | list | cleanup | status <key>" };
232
+ }
233
+ if (group === "project-memory") {
234
+ const pm = await import("./project-memory.js");
235
+ const { syncInstructionsMemory } = await import("./instructions-memory.js");
236
+ const cwd = flagValue(argv, "--root") ?? process.cwd();
237
+ if (command === "add-note") {
238
+ // Accept a --title flag or a positional title; reject a flag-like value in
239
+ // either slot, so `add-note --title --body x` doesn't store "--body".
240
+ const titleFlag = flagValue(argv, "--title");
241
+ const title = titleFlag && !titleFlag.startsWith("-")
242
+ ? titleFlag
243
+ : value && !value.startsWith("-")
244
+ ? value
245
+ : undefined;
246
+ if (!title || !title.trim()) {
247
+ return { ok: false, exitCode: 1, message: 'usage: omp project-memory add-note "<title>" [--body "<text>"]' };
248
+ }
249
+ const id = pm.addNote(cwd, title, flagValue(argv, "--body"));
250
+ syncInstructionsMemory(cwd); // refresh the managed block Copilot reads
251
+ return json ? { ok: true, output: { ok: true, id } } : { ok: true, message: `note added: ${id}` };
252
+ }
253
+ if (command === "add-directive") {
254
+ const directiveFlag = flagValue(argv, "--directive");
255
+ const directive = directiveFlag && !directiveFlag.startsWith("-")
256
+ ? directiveFlag
257
+ : value && !value.startsWith("-")
258
+ ? value
259
+ : undefined;
260
+ if (!directive || !directive.trim()) {
261
+ return { ok: false, exitCode: 1, message: 'usage: omp project-memory add-directive "<rule>"' };
262
+ }
263
+ const count = pm.addDirective(cwd, directive);
264
+ syncInstructionsMemory(cwd); // refresh the managed block Copilot reads
265
+ return json ? { ok: true, output: { ok: true, count } } : { ok: true, message: `directive added (${count} total)` };
266
+ }
267
+ if (command === "index") {
268
+ return { ok: true, output: { notes: pm.noteIndex(cwd) } };
269
+ }
270
+ if (command === "read" || command === undefined) {
271
+ // `read <id>` loads one note's body on demand; bare `read` returns the
272
+ // bounded summary (directives + note index — never note bodies).
273
+ if (value && !value.startsWith("-")) {
274
+ const note = pm.readNote(cwd, value);
275
+ if (note === null)
276
+ return { ok: false, exitCode: 1, message: `no note with id: ${value}` };
277
+ return { ok: true, message: note };
278
+ }
279
+ return { ok: true, output: { directives: pm.readDirectives(cwd), notes: pm.noteIndex(cwd) } };
280
+ }
281
+ return {
282
+ ok: false,
283
+ exitCode: 1,
284
+ message: 'Unknown project-memory subcommand. Try: project-memory read [<id>] | index | add-note "<title>" [--body "<text>"] | add-directive "<rule>"',
285
+ };
286
+ }
287
+ if (group === "trace") {
288
+ const tr = await import("./trace.js");
289
+ const cwd = flagValue(argv, "--root") ?? process.cwd();
290
+ const sid = value && !value.startsWith("-") ? value : undefined;
291
+ if (command === "timeline" || command === undefined) {
292
+ const limitRaw = flagValue(argv, "--limit");
293
+ const limit = limitRaw !== undefined && Number.isFinite(Number(limitRaw)) ? Number(limitRaw) : 50;
294
+ return { ok: true, output: tr.traceTimeline(cwd, sid, limit) };
295
+ }
296
+ if (command === "summary") {
297
+ return { ok: true, output: tr.traceSummary(cwd, sid) };
298
+ }
299
+ if (command === "add" && sid) {
300
+ const event = argv[3];
301
+ if (!event)
302
+ return { ok: false, exitCode: 1, message: "usage: omp trace add <sessionId> <event> [json-payload]" };
303
+ const payloadRaw = argv[4];
304
+ let payload;
305
+ if (payloadRaw !== undefined) {
306
+ try {
307
+ payload = JSON.parse(payloadRaw);
308
+ }
309
+ catch {
310
+ payload = payloadRaw;
311
+ }
312
+ }
313
+ tr.appendTraceEntry(cwd, sid, { event, payload });
314
+ return { ok: true, message: `trace appended to ${sid}` };
315
+ }
316
+ return { ok: false, exitCode: 1, message: "Unknown trace subcommand. Try: trace timeline [sessionId] [--limit N] | summary [sessionId] | add <sessionId> <event> [json]" };
317
+ }
137
318
  if (group === "catalog") {
138
319
  if (command === "list") {
139
320
  const bundle = loadCatalogBundle();
@@ -328,10 +509,12 @@ async function handleCouncilCommand(argv, json) {
328
509
  const context = await readFlagOrFile(flagValue(argv, "--context"));
329
510
  const rubric = await readFlagOrFile(flagValue(argv, "--rubric"));
330
511
  let perMemberTimeoutMs;
512
+ let synthTimeoutMs;
331
513
  let minSurvivors;
332
514
  let maxConcurrency;
333
515
  try {
334
516
  perMemberTimeoutMs = parsePositiveIntFlag(flagValue(argv, "--timeout"), "--timeout");
517
+ synthTimeoutMs = parsePositiveIntFlag(flagValue(argv, "--synth-timeout"), "--synth-timeout");
335
518
  minSurvivors = parsePositiveIntFlag(flagValue(argv, "--min-survivors"), "--min-survivors");
336
519
  maxConcurrency = parsePositiveIntFlag(flagValue(argv, "--max-concurrency"), "--max-concurrency");
337
520
  }
@@ -351,10 +534,15 @@ async function handleCouncilCommand(argv, json) {
351
534
  synthesizerModel: flagValue(argv, "--synth"),
352
535
  probe,
353
536
  perMemberTimeoutMs,
537
+ synthTimeoutMs,
354
538
  minSurvivors,
355
539
  maxConcurrency,
356
540
  tmpDir: flagValue(argv, "--tmp-dir"),
357
- }, { cwd: flagValue(argv, "--root") ?? process.cwd(), bin: flagValue(argv, "--bin") });
541
+ }, {
542
+ cwd: flagValue(argv, "--root") ?? process.cwd(),
543
+ bin: flagValue(argv, "--bin"),
544
+ onProgress: json ? undefined : (msg) => process.stderr.write(`${msg}\n`),
545
+ });
358
546
  if (json) {
359
547
  return { ok: result.ok, exitCode: result.ok ? 0 : 1, output: result };
360
548
  }