@damian87/omp 0.12.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.
- package/dist/src/cli.js +125 -4
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/comms.d.ts +2 -0
- package/dist/src/commands/comms.js +110 -0
- package/dist/src/commands/comms.js.map +1 -0
- package/dist/src/commands/council.d.ts +2 -0
- package/dist/src/commands/council.js +77 -0
- package/dist/src/commands/council.js.map +1 -0
- package/dist/src/commands/env.d.ts +2 -0
- package/dist/src/commands/env.js +95 -0
- package/dist/src/commands/env.js.map +1 -0
- package/dist/src/commands/gateway.d.ts +3 -0
- package/dist/src/commands/gateway.js +129 -0
- package/dist/src/commands/gateway.js.map +1 -0
- package/dist/src/commands/memory.d.ts +7 -0
- package/dist/src/commands/memory.js +202 -0
- package/dist/src/commands/memory.js.map +1 -0
- package/dist/src/commands/mode.d.ts +4 -0
- package/dist/src/commands/mode.js +119 -0
- package/dist/src/commands/mode.js.map +1 -0
- package/dist/src/commands/schedule.d.ts +2 -0
- package/dist/src/commands/schedule.js +91 -0
- package/dist/src/commands/schedule.js.map +1 -0
- package/dist/src/commands/team.d.ts +2 -0
- package/dist/src/commands/team.js +146 -0
- package/dist/src/commands/team.js.map +1 -0
- package/dist/src/commands/utils.d.ts +13 -0
- package/dist/src/commands/utils.js +68 -0
- package/dist/src/commands/utils.js.map +1 -0
- package/dist/src/goal.js +6 -8
- package/dist/src/goal.js.map +1 -1
- package/dist/src/instructions-memory.js +26 -3
- package/dist/src/instructions-memory.js.map +1 -1
- package/dist/src/memory-review/apply.d.ts +7 -0
- package/dist/src/memory-review/apply.js +75 -0
- package/dist/src/memory-review/apply.js.map +1 -0
- package/dist/src/memory-review/config.d.ts +22 -0
- package/dist/src/memory-review/config.js +54 -0
- package/dist/src/memory-review/config.js.map +1 -0
- package/dist/src/memory-review/guard.d.ts +5 -0
- package/dist/src/memory-review/guard.js +37 -0
- package/dist/src/memory-review/guard.js.map +1 -0
- package/dist/src/memory-review/index.d.ts +17 -0
- package/dist/src/memory-review/index.js +87 -0
- package/dist/src/memory-review/index.js.map +1 -0
- package/dist/src/memory-review/prompt.d.ts +18 -0
- package/dist/src/memory-review/prompt.js +89 -0
- package/dist/src/memory-review/prompt.js.map +1 -0
- package/dist/src/memory-review/spawn.d.ts +2 -0
- package/dist/src/memory-review/spawn.js +51 -0
- package/dist/src/memory-review/spawn.js.map +1 -0
- package/dist/src/memory-review/transcript.d.ts +24 -0
- package/dist/src/memory-review/transcript.js +212 -0
- package/dist/src/memory-review/transcript.js.map +1 -0
- package/dist/src/memory-review/trigger.d.ts +21 -0
- package/dist/src/memory-review/trigger.js +27 -0
- package/dist/src/memory-review/trigger.js.map +1 -0
- package/dist/src/project-memory.d.ts +9 -0
- package/dist/src/project-memory.js +72 -1
- package/dist/src/project-memory.js.map +1 -1
- package/dist/src/state.js +25 -37
- package/dist/src/state.js.map +1 -1
- package/dist/src/utils/fs.d.ts +14 -0
- package/dist/src/utils/fs.js +32 -0
- package/dist/src/utils/fs.js.map +1 -0
- package/dist/src/utils/paths.d.ts +14 -0
- package/dist/src/utils/paths.js +21 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/docs/memory-mode.md +94 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/lib/memory-review-trigger.mjs +59 -0
- package/scripts/lib/pending-directives.mjs +36 -0
- package/scripts/session-end.mjs +8 -0
- package/scripts/session-start.mjs +4 -0
package/dist/src/state.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { existsSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { atomicWrite, ensureDir, readJSON } from "./utils/fs.js";
|
|
4
|
+
import { statePath } from "./utils/paths.js";
|
|
4
5
|
function kvDir(cwd) {
|
|
5
|
-
return
|
|
6
|
+
return statePath(cwd, "kv");
|
|
6
7
|
}
|
|
7
8
|
function kvPath(cwd, key) {
|
|
8
9
|
if (!/^[\w.-]+$/.test(key))
|
|
@@ -17,28 +18,21 @@ export function stateWrite(cwd, key, value, ttlSeconds) {
|
|
|
17
18
|
expiresAt: ttlSeconds != null ? new Date(Date.now() + ttlSeconds * 1000).toISOString() : undefined,
|
|
18
19
|
};
|
|
19
20
|
const path = kvPath(cwd, key);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
writeFileSync(tmp, JSON.stringify(entry, null, 2), "utf8");
|
|
23
|
-
renameSync(tmp, path);
|
|
21
|
+
ensureDir(path);
|
|
22
|
+
atomicWrite(path, JSON.stringify(entry, null, 2));
|
|
24
23
|
return entry.expiresAt;
|
|
25
24
|
}
|
|
26
25
|
/** Read a value. Returns { value: null } when missing; auto-deletes if expired. */
|
|
27
26
|
export function stateRead(cwd, key) {
|
|
28
27
|
const path = kvPath(cwd, key);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const entry = JSON.parse(readFileSync(path, "utf8"));
|
|
33
|
-
if (entry.expiresAt && Date.parse(entry.expiresAt) < Date.now()) {
|
|
34
|
-
unlinkSync(path);
|
|
35
|
-
return { value: null, expired: true };
|
|
36
|
-
}
|
|
37
|
-
return { value: entry.value };
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
28
|
+
const entry = readJSON(path, null);
|
|
29
|
+
if (!entry)
|
|
40
30
|
return { value: null };
|
|
31
|
+
if (entry.expiresAt && Date.parse(entry.expiresAt) < Date.now()) {
|
|
32
|
+
unlinkSync(path);
|
|
33
|
+
return { value: null, expired: true };
|
|
41
34
|
}
|
|
35
|
+
return { value: entry.value };
|
|
42
36
|
}
|
|
43
37
|
export function stateDelete(cwd, key) {
|
|
44
38
|
const path = kvPath(cwd, key);
|
|
@@ -55,15 +49,12 @@ export function stateList(cwd) {
|
|
|
55
49
|
for (const f of readdirSync(dir)) {
|
|
56
50
|
if (!f.endsWith(".json"))
|
|
57
51
|
continue;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
catch {
|
|
65
|
-
// skip unparseable
|
|
66
|
-
}
|
|
52
|
+
const entry = readJSON(join(dir, f), null);
|
|
53
|
+
if (!entry)
|
|
54
|
+
continue;
|
|
55
|
+
if (entry.expiresAt && Date.parse(entry.expiresAt) < now)
|
|
56
|
+
continue;
|
|
57
|
+
keys.push(f.replace(/\.json$/, ""));
|
|
67
58
|
}
|
|
68
59
|
return keys.sort();
|
|
69
60
|
}
|
|
@@ -78,15 +69,12 @@ export function stateCleanup(cwd) {
|
|
|
78
69
|
if (!f.endsWith(".json"))
|
|
79
70
|
continue;
|
|
80
71
|
const path = join(dir, f);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
// skip
|
|
72
|
+
const entry = readJSON(path, null);
|
|
73
|
+
if (!entry)
|
|
74
|
+
continue;
|
|
75
|
+
if (entry.expiresAt && Date.parse(entry.expiresAt) < now) {
|
|
76
|
+
unlinkSync(path);
|
|
77
|
+
deleted++;
|
|
90
78
|
}
|
|
91
79
|
}
|
|
92
80
|
return deleted;
|
package/dist/src/state.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomically write content to a file using a temporary file + rename.
|
|
3
|
+
* Ensures the target file is never left in a partially-written state.
|
|
4
|
+
*/
|
|
5
|
+
export declare function atomicWrite(path: string, content: string | Buffer): void;
|
|
6
|
+
/**
|
|
7
|
+
* Ensure the directory for the given path exists, creating it recursively if needed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ensureDir(path: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Read and parse JSON from a file, returning the fallback value if the file
|
|
12
|
+
* doesn't exist or cannot be parsed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function readJSON<T>(path: string, fallback: T): T;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Atomically write content to a file using a temporary file + rename.
|
|
5
|
+
* Ensures the target file is never left in a partially-written state.
|
|
6
|
+
*/
|
|
7
|
+
export function atomicWrite(path, content) {
|
|
8
|
+
const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
|
|
9
|
+
writeFileSync(tmp, content);
|
|
10
|
+
renameSync(tmp, path);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Ensure the directory for the given path exists, creating it recursively if needed.
|
|
14
|
+
*/
|
|
15
|
+
export function ensureDir(path) {
|
|
16
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read and parse JSON from a file, returning the fallback value if the file
|
|
20
|
+
* doesn't exist or cannot be parsed.
|
|
21
|
+
*/
|
|
22
|
+
export function readJSON(path, fallback) {
|
|
23
|
+
if (!existsSync(path))
|
|
24
|
+
return fallback;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return fallback;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../../src/utils/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAAwB;IAChE,MAAM,GAAG,GAAG,GAAG,IAAI,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IACvD,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC5B,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAI,IAAY,EAAE,QAAW;IACnD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IACvC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAM,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Construct a path within the project's .omp directory.
|
|
3
|
+
* @param cwd - The current working directory (project root)
|
|
4
|
+
* @param segments - Additional path segments to join
|
|
5
|
+
* @returns Absolute path to .omp/<segments>
|
|
6
|
+
*/
|
|
7
|
+
export declare function ompPath(cwd: string, ...segments: string[]): string;
|
|
8
|
+
/**
|
|
9
|
+
* Construct a path within the project's .omp/state directory.
|
|
10
|
+
* @param cwd - The current working directory (project root)
|
|
11
|
+
* @param segments - Additional path segments to join
|
|
12
|
+
* @returns Absolute path to .omp/state/<segments>
|
|
13
|
+
*/
|
|
14
|
+
export declare function statePath(cwd: string, ...segments: string[]): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { ompRoot } from "../omp-root.js";
|
|
3
|
+
/**
|
|
4
|
+
* Construct a path within the project's .omp directory.
|
|
5
|
+
* @param cwd - The current working directory (project root)
|
|
6
|
+
* @param segments - Additional path segments to join
|
|
7
|
+
* @returns Absolute path to .omp/<segments>
|
|
8
|
+
*/
|
|
9
|
+
export function ompPath(cwd, ...segments) {
|
|
10
|
+
return join(ompRoot(cwd), ".omp", ...segments);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Construct a path within the project's .omp/state directory.
|
|
14
|
+
* @param cwd - The current working directory (project root)
|
|
15
|
+
* @param segments - Additional path segments to join
|
|
16
|
+
* @returns Absolute path to .omp/state/<segments>
|
|
17
|
+
*/
|
|
18
|
+
export function statePath(cwd, ...segments) {
|
|
19
|
+
return join(ompRoot(cwd), ".omp", "state", ...segments);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/utils/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,GAAG,QAAkB;IACxD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,GAAG,QAAkB;IAC1D,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Memory mode (self-improving review loop)
|
|
2
|
+
|
|
3
|
+
Memory mode is an **opt-in** end-of-session review that mirrors Hermes Agent's
|
|
4
|
+
background-review fork, adapted to GitHub Copilot CLI. After a session ends, a
|
|
5
|
+
**cheap model** reads the session transcript and extracts durable knowledge into
|
|
6
|
+
oh-my-copilot's existing memory/skill stores — so the next session starts a
|
|
7
|
+
little smarter without you having to repeat yourself.
|
|
8
|
+
|
|
9
|
+
It is **off by default** because it adds one extra (cheap) model call per
|
|
10
|
+
session.
|
|
11
|
+
|
|
12
|
+
## Enable
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
omp config set memory-mode on # opt in (this project)
|
|
16
|
+
omp config set memory-review-model gpt-5-mini # optional; defaults to gpt-5-mini
|
|
17
|
+
omp config set memory-review-min-messages 4 # optional; skip sessions shorter than this (default 4)
|
|
18
|
+
omp config get # show current (effective) settings
|
|
19
|
+
|
|
20
|
+
# Set once for EVERY project (~/.omp/config.json):
|
|
21
|
+
omp config set memory-mode on --global
|
|
22
|
+
omp config set memory-review-model <slug> --global
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Resolution precedence (high → low):** `OMP_MEMORY_MODE` env › project
|
|
26
|
+
`.omp/config.json` › global `~/.omp/config.json` › defaults. So `--global` is the
|
|
27
|
+
"set it once" default, and a project's `.omp/config.json` overrides it per key.
|
|
28
|
+
|
|
29
|
+
## How it triggers
|
|
30
|
+
|
|
31
|
+
| Surface | Trigger | Notes |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| Interactive `copilot` (plugin installed) | `sessionEnd` hook | Detaches `omp memory-review` as a background process and returns immediately (hooks have a 5s budget). |
|
|
34
|
+
| `omp -p` / `omp launch -- -p` (headless) | wrapper, post-exit | Headless `copilot -p` skips hooks, so the omp wrapper detaches the review. It snapshots session dirs before launch and reviews the **exact** session created by that run — if it can't identify one, it skips rather than guess. |
|
|
35
|
+
| Manual | `omp memory-review --session <uuid\|latest>` | Run it yourself against any session (`latest` = newest by mtime). |
|
|
36
|
+
|
|
37
|
+
Both automatic triggers only **detach** the work. The review claims the session
|
|
38
|
+
atomically (`.oh-my-copilot/memory-review/.claim-<uuid>`), so even if the hook
|
|
39
|
+
and wrapper both fire, the review runs **exactly once**.
|
|
40
|
+
|
|
41
|
+
## What it writes
|
|
42
|
+
|
|
43
|
+
| Output | Destination | Applied? |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| **Notes** (durable facts) | project memory (`omp project-memory`) | ✅ applied — progressive disclosure, low blast radius |
|
|
46
|
+
| **Skill drafts** (procedures) | `.oh-my-copilot/self-evolve/drafts/<slug>/SKILL.md` | ⏸ human-promote — never auto-loaded (same as `/self-evolve`) |
|
|
47
|
+
| **Directives** (every-session rules) | `.oh-my-copilot/memory-review/pending-directives.md` | ⏸ **gated** — never auto-applied |
|
|
48
|
+
|
|
49
|
+
After the review writes notes it refreshes the managed block in
|
|
50
|
+
`.github/copilot-instructions.md`, listing the most recent note **titles** (capped,
|
|
51
|
+
newest-first) so the next session knows what it remembers; bodies stay on demand
|
|
52
|
+
(`omp project-memory read <id>`). The review also ensures `.omp/` and
|
|
53
|
+
`.oh-my-copilot/` are gitignored so memory (which may contain tool output) isn't
|
|
54
|
+
accidentally committed.
|
|
55
|
+
|
|
56
|
+
### Promoting a pending directive (manual, by design)
|
|
57
|
+
|
|
58
|
+
Directives are gated: they inject into every future session, so an injected/poisoned
|
|
59
|
+
one would steer everything. The review prompt treats the transcript as **data, not
|
|
60
|
+
instructions**, and only proposes directives from corrections / standing preferences —
|
|
61
|
+
never one-off task instructions. On the next session start you'll see a nudge:
|
|
62
|
+
`[MEMORY REVIEW] N proposed directive(s) await your review…`. To promote one:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cat .oh-my-copilot/memory-review/pending-directives.md # review the proposals
|
|
66
|
+
omp project-memory add-directive "User prefers concise replies" # apply the ones you want
|
|
67
|
+
# then delete the promoted line from pending-directives.md
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Skill drafts promote the same way as `/self-evolve`: move
|
|
71
|
+
`.oh-my-copilot/self-evolve/drafts/<slug>/` to `.github/skills/learned-<slug>/`.
|
|
72
|
+
|
|
73
|
+
### Pruning notes
|
|
74
|
+
|
|
75
|
+
Notes accumulate over time. Trim them anytime (never silent — requires a flag):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
omp project-memory prune-notes --keep 50 # keep the 50 newest
|
|
79
|
+
omp project-memory prune-notes --older-than 30 # drop notes older than 30 days
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Cost & observability
|
|
83
|
+
|
|
84
|
+
- The expensive reasoning already happened in the main session; the review is a
|
|
85
|
+
single cheap-model call over the transcript.
|
|
86
|
+
- Token cost is recorded in the cost ledger (`omp cost`) under the
|
|
87
|
+
`memory-review` event.
|
|
88
|
+
- Each run appends a line to `.omp/state/memory-review.log`.
|
|
89
|
+
|
|
90
|
+
## Disable
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
omp config set memory-mode off
|
|
94
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@damian87/omp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Multi-agent orchestration for GitHub Copilot CLI — autonomous loops (Autopilot, Ralph, UltraQA, Ultrawork), parallel tmux agent teams, a weighted-consensus model council, a Slack chat bridge, durable scheduled jobs, and in-session skills + custom agents. Zero learning curve.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-copilot",
|
|
3
3
|
"description": "Multi-agent orchestration skills for GitHub Copilot CLI — autopilot, ralph, ultrawork, ultraqa, team, council, code-review and more as in-session slash skills + custom agents.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Damian Borek",
|
|
7
7
|
"email": "borekdamian@yahoo.pl"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { ompRoot } from "./omp-root.mjs";
|
|
6
|
+
|
|
7
|
+
// sessionEnd hook → end-of-session memory review. The hook must return fast
|
|
8
|
+
// (5s timeout), so this only DETACHES `omp memory-review` and returns. The
|
|
9
|
+
// downstream claim guard de-dupes against the wrapper fallback. Fail-open:
|
|
10
|
+
// any error means "don't trigger", never throw into the hook.
|
|
11
|
+
|
|
12
|
+
function readMemoryMode(cwd) {
|
|
13
|
+
const env = process.env.OMP_MEMORY_MODE;
|
|
14
|
+
if (env === "on") return "on";
|
|
15
|
+
if (env === "off") return "off";
|
|
16
|
+
try {
|
|
17
|
+
const p = join(ompRoot(cwd), ".omp", "config.json");
|
|
18
|
+
if (!existsSync(p)) return "off";
|
|
19
|
+
const raw = JSON.parse(readFileSync(p, "utf8"));
|
|
20
|
+
return raw && raw.memoryMode === "on" ? "on" : "off";
|
|
21
|
+
} catch {
|
|
22
|
+
return "off";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function defaultDistPath() {
|
|
27
|
+
// scripts/lib/ -> packageRoot/dist/src/cli.js (present in the npm package and
|
|
28
|
+
// dev builds, but NOT in a plugin installed from GitHub — dist is gitignored).
|
|
29
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
return join(here, "..", "..", "dist", "src", "cli.js");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Decide how to invoke the omp CLI. An explicit cliPath (tests/dev) or a
|
|
34
|
+
* bundled dist runs via `node <path>`; otherwise — the normal install, where
|
|
35
|
+
* the plugin (from GitHub) has no dist but `omp` is installed globally via npm —
|
|
36
|
+
* invoke `omp` from PATH. Exported for testing. */
|
|
37
|
+
export function resolveMemoryReviewInvocation({ sessionId, cwd, cliPath, distPath = defaultDistPath(), exists = existsSync } = {}) {
|
|
38
|
+
const args = ["memory-review", "--session", sessionId, "--root", cwd];
|
|
39
|
+
if (cliPath) return { command: process.execPath, args: [cliPath, ...args] };
|
|
40
|
+
if (exists(distPath)) return { command: process.execPath, args: [distPath, ...args] };
|
|
41
|
+
return { command: "omp", args }; // plugin-from-GitHub: rely on the global omp CLI
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function triggerMemoryReview(options = {}) {
|
|
45
|
+
const { cwd, sessionId, spawn = nodeSpawn, cliPath, mode } = options;
|
|
46
|
+
const resolvedMode = mode ?? readMemoryMode(cwd);
|
|
47
|
+
if (resolvedMode !== "on") return { triggered: false, reason: "memory-mode off" };
|
|
48
|
+
if (!sessionId || sessionId === "unknown") return { triggered: false, reason: "no session id" };
|
|
49
|
+
try {
|
|
50
|
+
const { command, args } = resolveMemoryReviewInvocation({ sessionId, cwd, cliPath });
|
|
51
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
52
|
+
// Handle async spawn errors so they never surface as unhandled (fail-open).
|
|
53
|
+
if (child && typeof child.on === "function") child.on("error", () => {});
|
|
54
|
+
if (child && typeof child.unref === "function") child.unref();
|
|
55
|
+
return { triggered: true };
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return { triggered: false, reason: String(err?.message ?? err) };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { ompRoot } from "./omp-root.mjs";
|
|
4
|
+
|
|
5
|
+
// Memory-review writes proposed directives to a GATED pending queue (never
|
|
6
|
+
// auto-applied). Without a nudge that queue is invisible and rots, so the
|
|
7
|
+
// sessionStart hook surfaces a count + how to promote. Promotion stays manual:
|
|
8
|
+
// `omp project-memory add-directive "<rule>"` then remove the line.
|
|
9
|
+
|
|
10
|
+
function pendingPath(cwd) {
|
|
11
|
+
return join(ompRoot(cwd), ".oh-my-copilot", "memory-review", "pending-directives.md");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Count unchecked ("- [ ]") items in the pending-directives queue. */
|
|
15
|
+
export function countPendingDirectives(cwd) {
|
|
16
|
+
const p = pendingPath(cwd);
|
|
17
|
+
if (!existsSync(p)) return 0;
|
|
18
|
+
try {
|
|
19
|
+
return readFileSync(p, "utf8")
|
|
20
|
+
.split("\n")
|
|
21
|
+
.filter((line) => /^\s*-\s*\[\s*\]\s+\S/.test(line)).length;
|
|
22
|
+
} catch {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** SessionStart nudge string, or "" when nothing is pending. */
|
|
28
|
+
export function pendingDirectivesNudge(cwd) {
|
|
29
|
+
const n = countPendingDirectives(cwd);
|
|
30
|
+
if (n === 0) return "";
|
|
31
|
+
return (
|
|
32
|
+
`[MEMORY REVIEW] ${n} proposed directive${n === 1 ? "" : "s"} await your review in ` +
|
|
33
|
+
`.oh-my-copilot/memory-review/pending-directives.md — promote the ones you want with ` +
|
|
34
|
+
"`omp project-memory add-directive \"<rule>\"`, then delete the line."
|
|
35
|
+
);
|
|
36
|
+
}
|
package/scripts/session-end.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { endSession } from "./lib/daily-log.mjs";
|
|
|
6
6
|
import { ompRoot } from "./lib/omp-root.mjs";
|
|
7
7
|
import { failOpen } from "./lib/hook-output.mjs";
|
|
8
8
|
import { parseHookInput } from "./lib/hook-input.mjs";
|
|
9
|
+
import { triggerMemoryReview } from "./lib/memory-review-trigger.mjs";
|
|
9
10
|
|
|
10
11
|
const HOOK_NAME = "SessionEnd";
|
|
11
12
|
|
|
@@ -28,6 +29,13 @@ const HOOK_NAME = "SessionEnd";
|
|
|
28
29
|
// Arm a daily-log nudge for the next session if this one did work but
|
|
29
30
|
// logged nothing. endSession never throws.
|
|
30
31
|
endSession(directory);
|
|
32
|
+
// Memory mode (opt-in): detach the cheap-model end-of-session review.
|
|
33
|
+
// Best-effort and non-blocking — never delays the hook return.
|
|
34
|
+
try {
|
|
35
|
+
triggerMemoryReview({ cwd: directory, sessionId });
|
|
36
|
+
} catch {
|
|
37
|
+
// never block session end on the review trigger
|
|
38
|
+
}
|
|
31
39
|
failOpen();
|
|
32
40
|
} catch (err) {
|
|
33
41
|
console.error(`[hook ${HOOK_NAME}] failed: ${err?.message ?? err}`);
|
|
@@ -7,6 +7,7 @@ import { checkForUpdate, formatUpdateNotice } from "./lib/version-check.mjs";
|
|
|
7
7
|
import { scanScheduleResults } from "./lib/schedule-results.mjs";
|
|
8
8
|
import { readRepoGoal, readTodayGoal, recentEntryStats, startSession } from "./lib/daily-log.mjs";
|
|
9
9
|
import { readDirectives } from "./lib/project-memory.mjs";
|
|
10
|
+
import { pendingDirectivesNudge } from "./lib/pending-directives.mjs";
|
|
10
11
|
import { ompRoot } from "./lib/omp-root.mjs";
|
|
11
12
|
import { parseHookInput } from "./lib/hook-input.mjs";
|
|
12
13
|
|
|
@@ -78,6 +79,9 @@ function buildDailyLogBreadcrumb(directory) {
|
|
|
78
79
|
}
|
|
79
80
|
const repoGoal = readRepoGoal(directory);
|
|
80
81
|
if (repoGoal) parts.push(`[REPO GOAL] ${repoGoal}`);
|
|
82
|
+
// Memory-review's gated directive queue is invisible without a nudge.
|
|
83
|
+
const pendingNudge = pendingDirectivesNudge(directory);
|
|
84
|
+
if (pendingNudge) parts.push(pendingNudge);
|
|
81
85
|
const breadcrumb = buildDailyLogBreadcrumb(directory);
|
|
82
86
|
if (breadcrumb) parts.push(breadcrumb);
|
|
83
87
|
// Resets the per-session baseline and flushes a nudge when the prior session
|