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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.2488",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.2488",
3
+ "version": "2.0.2490",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.2488",
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();