gm-skill 2.0.2488 → 2.0.2490
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/gm-plugkit/package.json +1 -1
- package/gm.json +1 -1
- package/package.json +1 -1
- package/scripts/migrate-memory-to-tencentdb.mjs +194 -0
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2490",
|
|
4
4
|
"description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform wasm, verifies SHA256, and launches agentplug-runner (the native wasm host) as the spool watcher daemon.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/gm.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2490",
|
|
4
4
|
"description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Migrates gm's default memory corpus (.gm/memories/*.md + any legacy
|
|
3
|
+
// .gm/rs-learn.db, if present) into the opt-in tencentdb_backend
|
|
4
|
+
// (memory.tencentdb_backend in gm.config.json). Discards superfluous
|
|
5
|
+
// content (git-log-derivable facts, dated audit entries, historical
|
|
6
|
+
// "we used to" framing) using the same heuristic rs-plugkit's own
|
|
7
|
+
// memorize-fire already applies at write time (orchestrator/memorize.rs's
|
|
8
|
+
// is_derivable_state), so migrated content is held to the same bar new
|
|
9
|
+
// memories already are, not a looser one.
|
|
10
|
+
//
|
|
11
|
+
// Idempotent: re-running produces zero new writes for content already
|
|
12
|
+
// migrated, since the target backend dedupes by the same
|
|
13
|
+
// namespace|text content-hash key scheme gm's memorize verb uses.
|
|
14
|
+
//
|
|
15
|
+
// Usage:
|
|
16
|
+
// node scripts/migrate-memory-to-tencentdb.mjs --project <path> [--namespace default] [--dry-run]
|
|
17
|
+
//
|
|
18
|
+
// Requires the target project to have a live gm-plugkit spool watcher
|
|
19
|
+
// (boots one if .gm/exec-spool is missing, same as any other gm session)
|
|
20
|
+
// and memory.tencentdb_backend.enabled=true with the target namespace
|
|
21
|
+
// listed in gm.config.json -- this script drives the memorize verb, never
|
|
22
|
+
// writes .gm/tencentdb-memory files directly, so the write always goes
|
|
23
|
+
// through the same dedup/embed/index path a live agent dispatch would.
|
|
24
|
+
|
|
25
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
import { execFileSync } from "node:child_process";
|
|
28
|
+
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
|
+
const DRY_RUN = args.includes("--dry-run");
|
|
31
|
+
const projectIdx = args.indexOf("--project");
|
|
32
|
+
const PROJECT = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd();
|
|
33
|
+
const nsIdx = args.indexOf("--namespace");
|
|
34
|
+
const NAMESPACE = nsIdx >= 0 ? args[nsIdx + 1] : "default";
|
|
35
|
+
|
|
36
|
+
const SPOOL_IN = join(PROJECT, ".gm", "exec-spool", "in");
|
|
37
|
+
const SPOOL_OUT = join(PROJECT, ".gm", "exec-spool", "out");
|
|
38
|
+
const MEMORIES_DIR = join(PROJECT, ".gm", "memories");
|
|
39
|
+
const RS_LEARN_DB = join(PROJECT, ".gm", "rs-learn.db");
|
|
40
|
+
|
|
41
|
+
// Mirrors rs-plugkit's orchestrator/memorize.rs::is_derivable_state exactly
|
|
42
|
+
// (pattern list kept in sync by hand -- both are small and rarely change;
|
|
43
|
+
// a mismatch here would only ever under- or over-reject, never corrupt
|
|
44
|
+
// data, since the actual write still goes through gm's own memorize verb
|
|
45
|
+
// which re-applies its own copy of this check).
|
|
46
|
+
function isDerivableState(text) {
|
|
47
|
+
const t = text.trim();
|
|
48
|
+
if (t.length > 40 && /^[0-9a-fA-F]+$/.test(t)) {
|
|
49
|
+
return "memo is a hex hash; git log is the source of truth";
|
|
50
|
+
}
|
|
51
|
+
const lower = t.toLowerCase();
|
|
52
|
+
const bad = [
|
|
53
|
+
["we used to ", "historical framing belongs in git log + CHANGELOG"],
|
|
54
|
+
["used to do", "historical framing belongs in git log + CHANGELOG"],
|
|
55
|
+
["previously did", "historical framing belongs in git log + CHANGELOG"],
|
|
56
|
+
["(fixed)", "past-tense fix markers belong in commit messages"],
|
|
57
|
+
["fixed in commit", "commit-fix references belong in git log"],
|
|
58
|
+
["fix in commit", "commit-fix references belong in git log"],
|
|
59
|
+
["changelog:", "changelog entries live in CHANGELOG.md"],
|
|
60
|
+
["changelog entry", "changelog entries live in CHANGELOG.md"],
|
|
61
|
+
["dated audit", "dated audit entries belong in git log"],
|
|
62
|
+
["(added 20", "dated annotations belong in git log"],
|
|
63
|
+
["commit hash", "commit hashes are derivable from git log"],
|
|
64
|
+
["recent commit", "recent commits are derivable from git log"],
|
|
65
|
+
["git blame says", "git blame is derivable from the repo"],
|
|
66
|
+
];
|
|
67
|
+
for (const [pat, reason] of bad) {
|
|
68
|
+
if (lower.includes(pat)) return reason;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Parses memory_md.rs's frontmatter format:
|
|
74
|
+
// ---\nkey: ...\nns: ...\ncreated: <ms>\nupdated: <ms>\n---\n\n<body>\n
|
|
75
|
+
function parseMemoryFile(raw) {
|
|
76
|
+
const m = raw.match(/^---\n([\s\S]*?)\n---\n\n([\s\S]*)$/);
|
|
77
|
+
if (!m) return null;
|
|
78
|
+
const [, frontmatter, body] = m;
|
|
79
|
+
const fields = {};
|
|
80
|
+
for (const line of frontmatter.split("\n")) {
|
|
81
|
+
const idx = line.indexOf(":");
|
|
82
|
+
if (idx === -1) continue;
|
|
83
|
+
fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
84
|
+
}
|
|
85
|
+
return { key: fields.key, ns: fields.ns, created: fields.created, updated: fields.updated, text: body.trimEnd() };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function listMemoryFiles(namespace) {
|
|
89
|
+
const dir = namespace === "default"
|
|
90
|
+
? MEMORIES_DIR
|
|
91
|
+
: join(PROJECT, ".gm", "disciplines", namespace, "memories");
|
|
92
|
+
if (!existsSync(dir)) return [];
|
|
93
|
+
return readdirSync(dir)
|
|
94
|
+
.filter((f) => f.endsWith(".md"))
|
|
95
|
+
.map((f) => join(dir, f))
|
|
96
|
+
.filter((p) => statSync(p).isFile());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function probeLegacyRsLearnDb() {
|
|
100
|
+
if (!existsSync(RS_LEARN_DB)) return { present: false };
|
|
101
|
+
// rs-learn.db is a retired sqlite/libsql file; rs-plugkit's own
|
|
102
|
+
// legacy_reaper.rs deletes it outright on its next reap pass (no
|
|
103
|
+
// migration path -- the crate implementing it is gone, its schema was
|
|
104
|
+
// never documented, and nothing in the current codebase reads it). This
|
|
105
|
+
// script only reports its presence/size so an operator knows it existed
|
|
106
|
+
// and was NOT migrated (it predates the current .gm/memories corpus,
|
|
107
|
+
// which is the actual source of truth this script migrates from).
|
|
108
|
+
const size = statSync(RS_LEARN_DB).size;
|
|
109
|
+
return { present: true, sizeBytes: size, note: "not migrated -- retired format with no readable schema; will be deleted by rs-plugkit's own legacy_reaper on its next pass" };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function dispatchVerb(verb, body, timeoutMs = 30_000) {
|
|
113
|
+
const n = `${Date.now()}${Math.floor(Math.random() * 1e6)}`;
|
|
114
|
+
const inDir = join(SPOOL_IN, verb);
|
|
115
|
+
const outPath = join(SPOOL_OUT, `${verb}-${n}.json`);
|
|
116
|
+
execFileSync("mkdir", ["-p", inDir]);
|
|
117
|
+
execFileSync("node", ["-e", `require('fs').writeFileSync(${JSON.stringify(join(inDir, `${n}.txt`))}, ${JSON.stringify(JSON.stringify(body))})`]);
|
|
118
|
+
const deadline = Date.now() + timeoutMs;
|
|
119
|
+
while (Date.now() < deadline) {
|
|
120
|
+
if (existsSync(outPath)) {
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(readFileSync(outPath, "utf8"));
|
|
123
|
+
} catch {
|
|
124
|
+
// file still being written; keep polling
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
execFileSync("sleep", ["0.2"]);
|
|
128
|
+
}
|
|
129
|
+
throw new Error(`dispatch timeout waiting for ${outPath}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function main() {
|
|
133
|
+
console.log(`[migrate] project=${PROJECT} namespace=${NAMESPACE} dry-run=${DRY_RUN}`);
|
|
134
|
+
|
|
135
|
+
const legacy = probeLegacyRsLearnDb();
|
|
136
|
+
if (legacy.present) {
|
|
137
|
+
console.log(`[migrate] legacy .gm/rs-learn.db found (${legacy.sizeBytes} bytes) -- ${legacy.note}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const files = listMemoryFiles(NAMESPACE);
|
|
141
|
+
console.log(`[migrate] found ${files.length} memory files in namespace "${NAMESPACE}"`);
|
|
142
|
+
|
|
143
|
+
let kept = 0;
|
|
144
|
+
let discarded = 0;
|
|
145
|
+
let errored = 0;
|
|
146
|
+
const discardedSamples = [];
|
|
147
|
+
|
|
148
|
+
for (const path of files) {
|
|
149
|
+
const raw = readFileSync(path, "utf8");
|
|
150
|
+
const parsed = parseMemoryFile(raw);
|
|
151
|
+
if (!parsed || !parsed.text) {
|
|
152
|
+
errored++;
|
|
153
|
+
console.log(`[migrate] ERROR: ${path} does not match the expected memory_md.rs frontmatter format, skipping`);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const reason = isDerivableState(parsed.text);
|
|
157
|
+
if (reason) {
|
|
158
|
+
discarded++;
|
|
159
|
+
if (discardedSamples.length < 10) {
|
|
160
|
+
discardedSamples.push({ key: parsed.key, reason, preview: parsed.text.slice(0, 80) });
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (DRY_RUN) {
|
|
165
|
+
kept++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const resp = dispatchVerb("memorize", { text: parsed.text, namespace: NAMESPACE, kind: "l0" });
|
|
170
|
+
if (resp.ok) {
|
|
171
|
+
kept++;
|
|
172
|
+
} else {
|
|
173
|
+
errored++;
|
|
174
|
+
console.log(`[migrate] ERROR migrating ${parsed.key}: ${resp.error || JSON.stringify(resp)}`);
|
|
175
|
+
}
|
|
176
|
+
} catch (e) {
|
|
177
|
+
errored++;
|
|
178
|
+
console.log(`[migrate] ERROR migrating ${parsed.key}: ${e.message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log(`[migrate] summary: kept=${kept} discarded=${discarded} errored=${errored} total=${files.length}`);
|
|
183
|
+
if (discardedSamples.length) {
|
|
184
|
+
console.log(`[migrate] sample of discarded entries (up to 10):`);
|
|
185
|
+
for (const s of discardedSamples) {
|
|
186
|
+
console.log(`[migrate] ${s.key}: ${s.reason} -- "${s.preview}${s.preview.length === 80 ? "..." : ""}"`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (DRY_RUN) {
|
|
190
|
+
console.log(`[migrate] dry-run: no writes performed. Re-run without --dry-run to migrate ${kept} memories.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main();
|