gm-copilot-cli 2.0.682 → 2.0.683
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/agents/memorize.md +80 -0
- package/copilot-profile.md +1 -1
- package/hooks/hooks.json +12 -2
- package/hooks/post-tool-use-hook.js +34 -0
- package/hooks/pre-tool-use-hook.js +45 -0
- package/hooks/prompt-submit-hook.js +19 -0
- package/index.html +1 -1
- package/manifest.yml +1 -1
- package/package.json +1 -1
- package/tools.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memorize
|
|
3
|
+
description: Background memory agent. Classifies context and writes to AGENTS.md + rs-learn. No memory dir, no MEMORY.md.
|
|
4
|
+
agent: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Memorize — Background Memory Agent
|
|
8
|
+
|
|
9
|
+
Writes facts to two places only: **AGENTS.md** (non-obvious technical caveats) and **rs-learn** (all classified facts via fast ingest).
|
|
10
|
+
|
|
11
|
+
Resolve at start of every run:
|
|
12
|
+
|
|
13
|
+
- **Project root** = `process.cwd()` when invoked. `AGENTS.md` is `<project root>/AGENTS.md`.
|
|
14
|
+
|
|
15
|
+
## STEP 1: CLASSIFY
|
|
16
|
+
|
|
17
|
+
Examine the ## CONTEXT TO MEMORIZE section at the end of this prompt. For each fact, classify as:
|
|
18
|
+
|
|
19
|
+
- user: user role, goals, preferences, knowledge
|
|
20
|
+
- feedback: guidance on approach — corrections AND confirmations
|
|
21
|
+
- project: ongoing work, goals, bugs, incidents, decisions
|
|
22
|
+
- reference: pointers to external systems, URLs, paths
|
|
23
|
+
|
|
24
|
+
Discard:
|
|
25
|
+
- Obvious facts derivable from reading the code
|
|
26
|
+
- Active task state or session progress
|
|
27
|
+
- Facts that would not be useful in a future session
|
|
28
|
+
|
|
29
|
+
## STEP 2: INGEST INTO RS-LEARN
|
|
30
|
+
|
|
31
|
+
For each classified fact, invoke `exec:memorize` (HTTP-preferred, subprocess fallback — fast either way):
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
exec:memorize
|
|
35
|
+
<type>/<slug>
|
|
36
|
+
<fact body — one to three self-contained sentences>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Line 1 of the body is the source tag (e.g. `feedback/terse-responses`, `project/merge-freeze`). Lines 2+ are the fact itself. Use kebab-case slugs.
|
|
40
|
+
|
|
41
|
+
To invalidate previously-memorized content (correction or retraction):
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
exec:forget
|
|
45
|
+
by-source <tag>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or by content:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
exec:forget
|
|
52
|
+
by-query <2-6 search words>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## STEP 3: AGENTS.md
|
|
56
|
+
|
|
57
|
+
A non-obvious technical caveat qualifies if it required multiple failed runs to discover and would not be apparent from reading code or docs.
|
|
58
|
+
|
|
59
|
+
For each qualifying fact from context:
|
|
60
|
+
- Read AGENTS.md first if not already read this run
|
|
61
|
+
- If AGENTS.md already covers it → skip
|
|
62
|
+
- If genuinely non-obvious → append to the appropriate section
|
|
63
|
+
|
|
64
|
+
Never add: obvious patterns, active task progress, redundant restatements.
|
|
65
|
+
|
|
66
|
+
## STEP 4: AGENTS.md → RS-LEARN MIGRATION (BENCHMARK + DRAIN)
|
|
67
|
+
|
|
68
|
+
AGENTS.md is the **always-on context buffer** — every prompt sees it. rs-learn is the **conditional retrieval store** — only relevant facts surface. The migration loop turns AGENTS.md into a benchmark for rs-learn's recall quality:
|
|
69
|
+
|
|
70
|
+
1. Pick **5 random items** from AGENTS.md (sections, paragraphs, or numbered points). Don't pick the most recent additions — pick the oldest stable items.
|
|
71
|
+
2. For each item, derive a 2-6 word query that a future agent would naturally use to find this fact.
|
|
72
|
+
3. Run `exec:recall` with that query.
|
|
73
|
+
4. Decide:
|
|
74
|
+
- **Recall accurate AND complete** → the rs-learn store has internalized this fact; **remove it from AGENTS.md**. Frees buffer space and confirms learning.
|
|
75
|
+
- **Recall partial / outdated / missing** → keep the AGENTS.md item AND ingest a refined version of the fact via `exec:memorize` so next round it can pass. Note the outcome in your run log.
|
|
76
|
+
5. Record the audit cycle: how many items checked, how many removed, how many refined. Append this single-line summary to AGENTS.md under a `## Learning audit` section so future audits can see drift over time.
|
|
77
|
+
|
|
78
|
+
Why: AGENTS.md grows monotonically without this loop. rs-learn already filters by relevance per-prompt, so duplicating stable facts in AGENTS.md just inflates the always-on context. The migration drains AGENTS.md into the retrieval store as the store proves it can recall. Failed migrations leave the fact in AGENTS.md (safe default) and improve the store. Success rate over time = a metric for how well gm is learning this project.
|
|
79
|
+
|
|
80
|
+
Don't migrate if the fact is genuinely about agent meta-behavior that must be active every prompt (e.g. "always invoke gm:gm first") — those stay permanently.
|
package/copilot-profile.md
CHANGED
package/hooks/hooks.json
CHANGED
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
{
|
|
9
9
|
"type": "command",
|
|
10
10
|
"command": "${COPILOT_EXTENSION_DIR}/bin/plugkit hook pre-tool-use",
|
|
11
|
-
"timeout":
|
|
11
|
+
"timeout": 3600
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "command",
|
|
15
|
+
"command": "node ${COPILOT_EXTENSION_DIR}/hooks/pre-tool-use-hook.js",
|
|
16
|
+
"timeout": 2000
|
|
12
17
|
}
|
|
13
18
|
]
|
|
14
19
|
}
|
|
@@ -33,6 +38,11 @@
|
|
|
33
38
|
"type": "command",
|
|
34
39
|
"command": "${COPILOT_EXTENSION_DIR}/bin/plugkit hook prompt-submit",
|
|
35
40
|
"timeout": 60000
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "command",
|
|
44
|
+
"command": "node ${COPILOT_EXTENSION_DIR}/hooks/prompt-submit-hook.js",
|
|
45
|
+
"timeout": 3000
|
|
36
46
|
}
|
|
37
47
|
]
|
|
38
48
|
}
|
|
@@ -44,7 +54,7 @@
|
|
|
44
54
|
{
|
|
45
55
|
"type": "command",
|
|
46
56
|
"command": "${COPILOT_EXTENSION_DIR}/bin/plugkit hook session-end",
|
|
47
|
-
"timeout":
|
|
57
|
+
"timeout": 15000
|
|
48
58
|
},
|
|
49
59
|
{
|
|
50
60
|
"type": "command",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
let raw = '';
|
|
5
|
+
try { raw = fs.readFileSync(0, 'utf8'); } catch (_) {}
|
|
6
|
+
if (!raw.trim()) raw = process.env.CLAUDE_HOOK_INPUT || '{}';
|
|
7
|
+
const input = JSON.parse(raw);
|
|
8
|
+
const toolName = input.tool_name || input.tool_use?.name || '';
|
|
9
|
+
const toolOutput = input.tool_result || input.output || '';
|
|
10
|
+
const gmDir = path.join(process.cwd(), '.gm');
|
|
11
|
+
const tsPath = path.join(gmDir, 'turn-state.json');
|
|
12
|
+
const readState = () => { try { return JSON.parse(fs.readFileSync(tsPath, 'utf8')); } catch (_) { return { firstToolFired: false, execCallsSinceMemorize: 0, recallFiredThisTurn: false }; } };
|
|
13
|
+
const writeState = (s) => { try { if (!fs.existsSync(gmDir)) fs.mkdirSync(gmDir, { recursive: true }); fs.writeFileSync(tsPath, JSON.stringify(s), 'utf8'); } catch (_) {} };
|
|
14
|
+
const state = readState();
|
|
15
|
+
const messages = [];
|
|
16
|
+
if (!state.firstToolFired) {
|
|
17
|
+
state.firstToolFired = true;
|
|
18
|
+
state.firstToolName = toolName;
|
|
19
|
+
}
|
|
20
|
+
const isMemorize = toolName === 'Agent' && /memorize/i.test(JSON.stringify(input.tool_input || input.tool_use?.input || {}));
|
|
21
|
+
if (isMemorize) {
|
|
22
|
+
state.execCallsSinceMemorize = 0;
|
|
23
|
+
try { fs.unlinkSync(path.join(gmDir, 'no-memorize-this-turn')); } catch (_) {}
|
|
24
|
+
}
|
|
25
|
+
if (toolName === 'Bash') {
|
|
26
|
+
const cmd = (input.tool_input && input.tool_input.command) || (input.tool_use && input.tool_use.input && input.tool_use.input.command) || '';
|
|
27
|
+
if (/^\s*exec:recall\b/.test(cmd)) state.recallFiredThisTurn = true;
|
|
28
|
+
if (toolOutput && typeof toolOutput === 'string' && toolOutput.length > 20 && !/^\s*exec:(recall|memorize|codesearch|wait|sleep|status|runner|type|kill-port|close|pause)/.test(cmd)) {
|
|
29
|
+
state.execCallsSinceMemorize = (state.execCallsSinceMemorize || 0) + 1;
|
|
30
|
+
messages.push('exec: run completed. MEMORIZE CHECK: did this output resolve any prior unknown? If YES → spawn Agent(subagent_type=\'gm:memorize\', model=\'haiku\', run_in_background=true, prompt=\'## CONTEXT TO MEMORIZE\\n<fact>\') NOW. Skipping = memory leak. (Counter: ' + state.execCallsSinceMemorize + '/3 before hard block.)');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
writeState(state);
|
|
34
|
+
if (messages.length) process.stdout.write(JSON.stringify({ systemMessage: messages.join('\n\n') }));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
let raw = '';
|
|
5
|
+
try { raw = fs.readFileSync(0, 'utf8'); } catch (_) {}
|
|
6
|
+
if (!raw.trim()) raw = process.env.CLAUDE_HOOK_INPUT || '{}';
|
|
7
|
+
const input = JSON.parse(raw);
|
|
8
|
+
const toolName = input.tool_name || input.tool_use?.name || '';
|
|
9
|
+
const toolInput = input.tool_input || input.tool_use?.input || {};
|
|
10
|
+
const skillName = toolInput.skill || toolInput.name || '';
|
|
11
|
+
const gmDir = path.join(process.cwd(), '.gm');
|
|
12
|
+
const needsGmPath = path.join(gmDir, 'needs-gm');
|
|
13
|
+
const lastskillPath = path.join(gmDir, 'lastskill');
|
|
14
|
+
const isSkillTool = toolName === 'Skill' || toolName === 'skill';
|
|
15
|
+
if (isSkillTool && skillName) {
|
|
16
|
+
try {
|
|
17
|
+
if (!fs.existsSync(gmDir)) fs.mkdirSync(gmDir, { recursive: true });
|
|
18
|
+
fs.writeFileSync(lastskillPath, skillName, 'utf8');
|
|
19
|
+
if (skillName === 'gm' || skillName === 'gm:gm') {
|
|
20
|
+
try { fs.unlinkSync(needsGmPath); } catch (_) {}
|
|
21
|
+
}
|
|
22
|
+
} catch (_) {}
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
if (fs.existsSync(needsGmPath)) {
|
|
26
|
+
process.stdout.write(JSON.stringify({ decision: 'block', reason: 'HARD CONSTRAINT: invoke the Skill tool with skill: "gm:gm" before any other tool. The gm:gm skill must be the first action after every user message.' }));
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
const turnStatePath = path.join(gmDir, 'turn-state.json');
|
|
30
|
+
const noMemoPath = path.join(gmDir, 'no-memorize-this-turn');
|
|
31
|
+
const turnState = (() => { try { return JSON.parse(fs.readFileSync(turnStatePath, 'utf8')); } catch (_) { return null; } })();
|
|
32
|
+
if (turnState && (turnState.execCallsSinceMemorize || 0) >= 3 && !fs.existsSync(noMemoPath)) {
|
|
33
|
+
const isMemAgent = toolName === 'Agent' && /memorize/i.test(JSON.stringify(toolInput || {}));
|
|
34
|
+
if (!isMemAgent) {
|
|
35
|
+
process.stdout.write(JSON.stringify({ decision: 'block', reason: '3+ exec results have resolved unknowns without a memorize call. HARD BLOCK until you spawn at least one Agent(subagent_type=\'gm:memorize\', model=\'haiku\', run_in_background=true, prompt=\'## CONTEXT TO MEMORIZE\\n<fact>\') OR write file .gm/no-memorize-this-turn (containing reason) to declare nothing memorable. Saying "I will memorize" is NOT a memorize call — only the Agent tool counts.' }));
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const lastSkill = (() => { try { return fs.readFileSync(lastskillPath, 'utf8').trim(); } catch (_) { return ''; } })();
|
|
40
|
+
const isFileEdit = ['Write', 'Edit', 'NotebookEdit'].includes(toolName);
|
|
41
|
+
const WRITE_BLOCKED_PHASES = new Set(['gm-complete', 'update-docs']);
|
|
42
|
+
if (isFileEdit && WRITE_BLOCKED_PHASES.has(lastSkill)) {
|
|
43
|
+
process.stdout.write(JSON.stringify({ decision: 'block', reason: 'File edits are not permitted in ' + lastSkill + ' phase. Regress to gm-execute if changes are needed, or invoke gm-emit to re-emit.' }));
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const gmDir = path.join(process.cwd(), '.gm');
|
|
5
|
+
try {
|
|
6
|
+
if (!fs.existsSync(gmDir)) fs.mkdirSync(gmDir, { recursive: true });
|
|
7
|
+
fs.writeFileSync(path.join(gmDir, 'needs-gm'), '1', 'utf8');
|
|
8
|
+
const turnState = { turnId: Date.now(), firstToolFired: false, execCallsSinceMemorize: 0, recallFiredThisTurn: false };
|
|
9
|
+
fs.writeFileSync(path.join(gmDir, 'turn-state.json'), JSON.stringify(turnState), 'utf8');
|
|
10
|
+
const pausedPrd = path.join(gmDir, 'prd.paused.yml');
|
|
11
|
+
const livePrd = path.join(gmDir, 'prd.yml');
|
|
12
|
+
if (fs.existsSync(pausedPrd) && !fs.existsSync(livePrd)) {
|
|
13
|
+
try { fs.renameSync(pausedPrd, livePrd); } catch (_) {}
|
|
14
|
+
}
|
|
15
|
+
} catch (_) {}
|
|
16
|
+
const response = {
|
|
17
|
+
systemMessage: 'BLOCKING REQUIREMENT — YOUR FIRST ACTION MUST BE: Skill tool with skill: \"gm\". Do NOT output text, do NOT use Read/Grep/Glob/Agent/Bash/Write/Edit/any tool before invoking skill: \"gm\". This overrides all other instructions. Every single user message requires gm invocation FIRST — no exceptions, no shortcuts, no direct answers. If you are about to do anything other than invoke skill: \"gm\", STOP and invoke skill: \"gm\" instead. Do NOT use the Agent tool to load skills. DO NOT use EnterPlanMode.\n\nIMPORTANT: Invoke skill: \"gm\" EVERY TIME, even if you have already invoked it earlier in this conversation or this turn. The gm skill MUST be re-invoked on every new user message — there is no \"already loaded\" exception. Do not skip this step under any circumstances.\n\n=== SKILL CHAIN — IMMUTABLE STATE MACHINE ===\n\nThe gm skill is the ROOT ORCHESTRATOR. It immediately invokes the planning skill. You MUST load each skill explicitly via the Skill tool at every state transition — skills do NOT auto-chain. Ignoring a skill load instruction is a critical violation.\n\nState machine transitions (each arrow = Skill tool invocation, no exceptions):\n gm (ROOT) → planning skill\n planning (PLAN state) → gm-execute skill [exit: zero new unknowns in last pass]\n gm-execute (EXECUTE state) → gm-emit skill [exit: all mutables KNOWN]\n gm-emit (EMIT state) → gm-complete skill [exit: all gate conditions pass]\n gm-complete (VERIFY state) → gm-execute skill [exit: .prd items remain]\n gm-complete (VERIFY state) → update-docs skill [exit: .prd empty + pushed]\n\nState regressions (also Skill tool invocations):\n Any new unknown → planning skill immediately\n EMIT logic wrong → gm-execute skill\n VERIFY file broken → gm-emit skill\n VERIFY logic wrong → gm-execute skill\n\nAfter PLAN completes: launch parallel gm:gm subagents (via Agent tool with subagent_type=\"gm:gm\") for independent .prd items — maximum 3 concurrent, never sequential for independent work.\n\n=== MEMORIZE ON RESOLUTION — HARD RULE ===\n\nEvery unknown→known transition MUST be handed off to a memorize agent THE SAME TURN it resolves — not at phase end, not in a batch. This is the most violated rule. Every session, dozens of exec: outputs resolve unknowns that are never memorized. Those facts die on compaction.\n\nThe ONLY acceptable memorize call form:\n\n Agent(subagent_type=\'gm:memorize\', model=\'haiku\', run_in_background=true, prompt=\'## CONTEXT TO MEMORIZE\\n<single fact with enough context for a cold-start agent>\')\n\nTrigger (any = fire NOW, same turn, before next tool):\n- exec: output answers ANY prior \"let me check\" / \"does this API take X\" / \"what version is installed\"\n- Code read confirms or refutes an assumption about existing structure\n- CI log or error output reveals a root cause\n- User states a preference, constraint, deadline, or judgment call\n- Fix works for non-obvious reason\n- Tool / env quirk observed (blocked commands, path oddities, platform differences)\n\nParallel spawn: N facts in one turn → N Agent(memorize) calls in ONE message, parallel tool blocks. NEVER serialize.\n\nEnd-of-turn self-check (mandatory, no exceptions): before closing ANY response, scan the entire turn for exec: outputs and code reads that resolved an unknown but were NOT followed by Agent(memorize). Spawn ALL missed ones now. \"I\'ll memorize this\" in text is NOT a memorize call — only the Agent tool call counts.\n\nSkipping memorize = memory leak = critical bug. Saying you will memorize ≠ memorizing.\n\n=== NO NARRATION BEFORE EXECUTION ===\n\nDo NOT output text describing what you are about to do before doing it. Run the tool first. State findings AFTER. Pattern: tool call → tool result → brief text summary of what was found. NOT: text describing upcoming tool → tool call.\n\n\"I\'ll check the file:\" followed by Read = violation.\n\"Let me search for X\" followed by exec:codesearch = violation.\n\"Now I\'ll fix Y\" followed by Edit = violation.\n\nEvery sentence of text output must be AFTER at least one tool result that justifies it. No pre-announcement narration.'
|
|
18
|
+
};
|
|
19
|
+
process.stdout.write(JSON.stringify(response));
|
package/index.html
CHANGED
|
@@ -929,7 +929,7 @@ body { display: flex; flex-direction: column; min-height: 100vh; }
|
|
|
929
929
|
<section>
|
|
930
930
|
<div class="gm-section-label"><span class="slash">//</span>status</div>
|
|
931
931
|
<div class="panel">
|
|
932
|
-
<div class="panel-head"><span>release · v2.0.
|
|
932
|
+
<div class="panel-head"><span>release · v2.0.683</span><span>probably emerging</span></div>
|
|
933
933
|
<div class="panel-body">
|
|
934
934
|
<div class="row">
|
|
935
935
|
<span class="code"><span style="color:var(--panel-accent)">●</span></span>
|
package/manifest.yml
CHANGED
package/package.json
CHANGED