@pcircle/memesh 4.9.0 → 4.9.4
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.de.md +1 -1
- package/README.md +2 -2
- package/README.zh-TW.md +2 -2
- package/dashboard/dist/index.html +10 -10
- package/dist/core/doctor-fixes.d.ts +19 -0
- package/dist/core/doctor-fixes.d.ts.map +1 -0
- package/dist/core/doctor-fixes.js +104 -0
- package/dist/core/doctor-fixes.js.map +1 -0
- package/dist/core/doctor.d.ts +1 -1
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +3 -3
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +2 -2
- package/dist/core/operations.js.map +1 -1
- package/dist/core/schema-export.js +1 -1
- package/dist/core/schema-export.js.map +1 -1
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +30 -10
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/mcp/THIRD_PARTY_NOTICES.txt +2 -2
- package/dist/mcp/server.js +45 -16
- package/dist/mcp/server.js.map +1 -1
- package/dist/skills-manifest.json +17 -12
- package/dist/transports/agent-messaging.d.ts +4 -0
- package/dist/transports/agent-messaging.d.ts.map +1 -1
- package/dist/transports/agent-messaging.js +12 -2
- package/dist/transports/agent-messaging.js.map +1 -1
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +60735 -1770
- package/dist/transports/cli/cli.js.map +6 -1
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +32 -1
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +3 -3
- package/dist/transports/mcp/handlers.js +3 -3
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/docs/platforms/agent-messaging.md +10 -1
- package/hooks/hooks.json +1 -1
- package/package.json +6 -3
- package/scripts/check-plugin-hook-artifact.mjs +212 -0
- package/scripts/hooks/_shared.js +150 -1
- package/scripts/hooks/session-start.js +90 -4
- package/scripts/hooks/session-summary.js +17 -4
- package/scripts/hooks/user-prompt-intent.js +71 -11
- package/scripts/lib/npm-bin.mjs +123 -0
- package/scripts/upgrade-plugin.sh +24 -0
|
@@ -19,7 +19,62 @@
|
|
|
19
19
|
// Gated by `autoCapture` flag (same as other memesh write hooks).
|
|
20
20
|
|
|
21
21
|
import { pathToFileURL } from 'url';
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
findAutoUpdateConsent,
|
|
24
|
+
isAutoCaptureEnabled,
|
|
25
|
+
parseAutoUpdateConsent,
|
|
26
|
+
readUpdateCheckCache,
|
|
27
|
+
resolvePluginRoot,
|
|
28
|
+
writeAutoUpdateConsent,
|
|
29
|
+
} from './_shared.js';
|
|
30
|
+
import { join } from 'path';
|
|
31
|
+
import { existsSync, readFileSync } from 'fs';
|
|
32
|
+
|
|
33
|
+
let installChannelMod = null;
|
|
34
|
+
try {
|
|
35
|
+
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
36
|
+
const modulePath = join(pluginRoot, 'dist/core/install-channel.js');
|
|
37
|
+
if (existsSync(modulePath)) installChannelMod = await import(pathToFileURL(modulePath).href);
|
|
38
|
+
} catch {
|
|
39
|
+
// Source checkouts without a build remain non-blocking and cannot self-update.
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function currentInstallChannel() {
|
|
43
|
+
try {
|
|
44
|
+
return installChannelMod?.getCurrentInstallChannel({
|
|
45
|
+
packageRoot: resolvePluginRoot(import.meta.url),
|
|
46
|
+
}) ?? 'unknown';
|
|
47
|
+
} catch {
|
|
48
|
+
return 'unknown';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function currentInstalledVersion() {
|
|
53
|
+
try {
|
|
54
|
+
const pkg = JSON.parse(readFileSync(join(resolvePluginRoot(import.meta.url), 'package.json'), 'utf8'));
|
|
55
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
56
|
+
} catch { return null; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function recordUpdateConsent(sessionId, prompt) {
|
|
60
|
+
const current = currentInstalledVersion();
|
|
61
|
+
if (!current || !sessionId) return null;
|
|
62
|
+
const channel = currentInstallChannel();
|
|
63
|
+
// Only npm-global has a hook-owned installer. Other channels receive an
|
|
64
|
+
// actionable notice at SessionStart and must not turn an "Upgrade" word
|
|
65
|
+
// into a misleading approval marker for a different installation path.
|
|
66
|
+
if (channel !== 'npm-global') return null;
|
|
67
|
+
const cache = readUpdateCheckCache(current);
|
|
68
|
+
const latest = cache?.latestVersion;
|
|
69
|
+
if (typeof latest !== 'string' || !latest) return null;
|
|
70
|
+
const pending = findAutoUpdateConsent(sessionId, current, latest, channel);
|
|
71
|
+
if (!pending || !['pending'].includes(pending.decision)) return null;
|
|
72
|
+
const decision = parseAutoUpdateConsent(prompt);
|
|
73
|
+
if (!decision) return null;
|
|
74
|
+
return writeAutoUpdateConsent(
|
|
75
|
+
sessionId, current, latest, pending.channel ?? 'unknown', decision,
|
|
76
|
+
) ? decision : null;
|
|
77
|
+
}
|
|
23
78
|
|
|
24
79
|
// Patterns compiled at module load — invalid regex MUST fail loudly. Do
|
|
25
80
|
// NOT move into a try block "for safety": a regex compile error is a
|
|
@@ -121,8 +176,6 @@ if (isMainModule) {
|
|
|
121
176
|
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
122
177
|
process.stdin.on('end', () => {
|
|
123
178
|
try {
|
|
124
|
-
if (!isAutoCaptureEnabled(process.env)) return process.exit(0);
|
|
125
|
-
|
|
126
179
|
// Distinguish empty stdin (legitimate degenerate event) from malformed
|
|
127
180
|
// input (protocol drift). Both stay non-blocking, but only malformed
|
|
128
181
|
// input is logged — empty is normal, garbage indicates a real bug.
|
|
@@ -142,14 +195,21 @@ if (isMainModule) {
|
|
|
142
195
|
// we accept either name to survive a similar rename. If both are absent
|
|
143
196
|
// or non-string, detectRememberIntent's type guard returns false safely.
|
|
144
197
|
const prompt = data.prompt ?? data.user_prompt ?? '';
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
198
|
+
const updateDecision = recordUpdateConsent(data.session_id, prompt);
|
|
199
|
+
const rememberIntent = detectRememberIntent(prompt);
|
|
200
|
+
if (!rememberIntent && !updateDecision) return process.exit(0);
|
|
201
|
+
// Update consent is a user-authorized control decision, not memory
|
|
202
|
+
// capture; it must still be recorded when auto-capture is disabled.
|
|
203
|
+
if (!isAutoCaptureEnabled(process.env) && !updateDecision) return process.exit(0);
|
|
204
|
+
|
|
205
|
+
const contexts = [];
|
|
206
|
+
if (updateDecision) {
|
|
207
|
+
contexts.push(updateDecision === 'approved'
|
|
208
|
+
? 'The user explicitly approved the MeMesh upgrade. The Stop hook may now update the consented installation.'
|
|
209
|
+
: 'The user declined the MeMesh upgrade for this session. Do not install it or ask again in this session.');
|
|
210
|
+
}
|
|
211
|
+
if (rememberIntent) contexts.push(buildHint());
|
|
212
|
+
const out = { hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: contexts.join('\n\n') } };
|
|
153
213
|
process.stdout.write(JSON.stringify(out));
|
|
154
214
|
process.exit(0);
|
|
155
215
|
} catch (err) {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { execFileSync, execSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Run npm / npx from a build script, on every platform we support.
|
|
5
|
+
*
|
|
6
|
+
* Two separate Windows problems, and fixing only the first is what made this
|
|
7
|
+
* take two attempts:
|
|
8
|
+
*
|
|
9
|
+
* 1. npm ships on Windows as `npm.cmd`. `execFileSync('npm', …)` does not
|
|
10
|
+
* consult `PATHEXT` — that is a shell behaviour and execFile uses no
|
|
11
|
+
* shell — so it fails with `spawnSync npm ENOENT` while the identical
|
|
12
|
+
* command typed into a terminal works.
|
|
13
|
+
*
|
|
14
|
+
* 2. Naming it `npm.cmd` then fails with `spawnSync npm.cmd EINVAL`. Since
|
|
15
|
+
* the fix for CVE-2024-27980, Node refuses to spawn `.cmd` and `.bat`
|
|
16
|
+
* files without `shell: true`, because the Windows command interpreter
|
|
17
|
+
* re-parses the argument list. On Windows there is no shell-free way to
|
|
18
|
+
* invoke npm.
|
|
19
|
+
*
|
|
20
|
+
* So `shell: true` is required there, not a shortcut — and it is scoped to
|
|
21
|
+
* Windows, so macOS and Linux keep passing arguments as an array with no
|
|
22
|
+
* interpreter in the path at all.
|
|
23
|
+
*
|
|
24
|
+
* What `shell: true` costs is argument re-parsing, which matters only for
|
|
25
|
+
* arguments that are not literals. There is exactly one — the tarball name
|
|
26
|
+
* `npm pack` prints — and `assertSafeShellArg()` below is how it is made safe
|
|
27
|
+
* rather than assumed safe. Call it on anything that did not come from this
|
|
28
|
+
* repository's own source.
|
|
29
|
+
*
|
|
30
|
+
* Both scripts on the publish path had written this by hand:
|
|
31
|
+
*
|
|
32
|
+
* check-consumer-audit.mjs -> audit:prod -> verify:release -> prepublishOnly
|
|
33
|
+
* run-tests-isolated.mjs -> test:isolated -> prepublishOnly
|
|
34
|
+
*
|
|
35
|
+
* One owner, so a third caller cannot get it wrong a third way.
|
|
36
|
+
*/
|
|
37
|
+
const isWindows = process.platform === 'win32';
|
|
38
|
+
|
|
39
|
+
export const NPM = isWindows ? 'npm.cmd' : 'npm';
|
|
40
|
+
export const NPX = isWindows ? 'npx.cmd' : 'npx';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reject anything that could mean something to a command interpreter.
|
|
44
|
+
*
|
|
45
|
+
* Deliberately an allow-list. A deny-list of shell metacharacters has to be
|
|
46
|
+
* complete to be correct, and `cmd.exe` gives `%`, `^` and `&` meanings that a
|
|
47
|
+
* POSIX-shaped deny-list would miss.
|
|
48
|
+
*/
|
|
49
|
+
export function assertSafeShellArg(value, what) {
|
|
50
|
+
// `:` and `~` are here for one reason: Windows absolute paths
|
|
51
|
+
// (`C:\Users\RUNNER~1\AppData\Local\Temp\...` — 8.3 short names carry the `~`,
|
|
52
|
+
// and that is literally what `os.tmpdir()` returns on a Windows runner).
|
|
53
|
+
// `smoke-packed-artifact.mjs` passes an absolute `os.tmpdir()` path to
|
|
54
|
+
// `npm pack --pack-destination` and to `npm install`, so without it
|
|
55
|
+
// `npm run test:packaged` throws on Windows before packing anything —
|
|
56
|
+
// fail-closed, but it means the packaged smoke test cannot run there at all.
|
|
57
|
+
// Neither has meaning to cmd.exe (`~` is a POSIX-shell nicety, and the POSIX
|
|
58
|
+
// branch never reaches here — it uses execFileSync with no shell at all).
|
|
59
|
+
// Every character that DOES mean something to cmd.exe (`%^&|<>"'` and
|
|
60
|
+
// whitespace) stays excluded.
|
|
61
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9._@:~\-+=/\\]+$/.test(value)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`${what} is not a safe argument to pass through a shell: ${JSON.stringify(value)}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Spawn `bin` with `args`.
|
|
71
|
+
*
|
|
72
|
+
* POSIX: no shell, arguments stay an array, nothing is re-parsed.
|
|
73
|
+
*
|
|
74
|
+
* Windows: every argument is validated, then the command line is built here and
|
|
75
|
+
* handed to `execSync`. Passing an ARRAY together with `shell: true` is
|
|
76
|
+
* deprecated (DEP0190) precisely because the arguments are concatenated without
|
|
77
|
+
* escaping — doing the concatenation ourselves, after checking each part, is
|
|
78
|
+
* the same operation with the check that makes it sound, and it does not print
|
|
79
|
+
* a deprecation warning on every release gate.
|
|
80
|
+
*
|
|
81
|
+
* The usual advice — "use execFile with an argument array, never build a shell
|
|
82
|
+
* string" — is right, and is what the POSIX branch does. It does not apply to
|
|
83
|
+
* the Windows branch because there is no execFile that works: npm is a `.cmd`,
|
|
84
|
+
* and Node refuses to exec one without a shell. The choice there is not
|
|
85
|
+
* array-vs-string, it is checked-vs-unchecked. Every argument reaching the
|
|
86
|
+
* concatenation has passed `assertSafeShellArg`, which is an allow-list, so no
|
|
87
|
+
* character with meaning to `cmd.exe` can be in one. `tests/consumer-audit-
|
|
88
|
+
* gate.test.ts` pins that, including the `%` and `^` forms a POSIX-shaped
|
|
89
|
+
* deny-list would miss.
|
|
90
|
+
*/
|
|
91
|
+
function runSync(bin, args, opts) {
|
|
92
|
+
if (!isWindows) return execFileSync(bin, args, opts);
|
|
93
|
+
const safeArgs = args.map((arg, i) => assertSafeShellArg(arg, `${bin} argument ${i} (${arg})`));
|
|
94
|
+
return execSync([bin, ...safeArgs].join(' '), opts);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** `npm <args>`, spawned correctly for the platform. */
|
|
98
|
+
export function npmSync(args, opts = {}) {
|
|
99
|
+
return runSync(NPM, args, opts);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** `npx <args>`, spawned correctly for the platform. */
|
|
103
|
+
export function npxSync(args, opts = {}) {
|
|
104
|
+
return runSync(NPX, args, opts);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* `baseEnv` with every spelling of `npm_config_cache` removed and the private
|
|
109
|
+
* cache set. Windows environment names are case-insensitive and Node keeps
|
|
110
|
+
* the lexicographically first of two keys that differ only in case (`N`
|
|
111
|
+
* before `n`); Vitest adds an upper-cased copy of every variable to its
|
|
112
|
+
* Windows workers. So `{ ...process.env, npm_config_cache }` handed npm the
|
|
113
|
+
* runner's `NPM_CONFIG_CACHE=C:\npm\cache` and never the private directory —
|
|
114
|
+
* measured on windows-latest with the fake npm in
|
|
115
|
+
* `tests/consumer-audit-gate.test.ts`.
|
|
116
|
+
*/
|
|
117
|
+
export function envWithNpmCache(cacheDir, baseEnv = process.env) {
|
|
118
|
+
const env = Object.fromEntries(
|
|
119
|
+
Object.entries(baseEnv).filter(([key]) => key.toLowerCase() !== 'npm_config_cache'),
|
|
120
|
+
);
|
|
121
|
+
env.npm_config_cache = cacheDir;
|
|
122
|
+
return env;
|
|
123
|
+
}
|
|
@@ -91,6 +91,11 @@ MARKETPLACE_DIR="$CLAUDE_CONFIG_ROOT/plugins/marketplaces/pcircle-memesh"
|
|
|
91
91
|
INSTALL_REGISTRY="$CLAUDE_CONFIG_ROOT/plugins/installed_plugins.json"
|
|
92
92
|
CACHE_ROOT="$CLAUDE_CONFIG_ROOT/plugins/cache/pcircle-memesh/memesh"
|
|
93
93
|
LOCK_DIR="$CACHE_ROOT.lock"
|
|
94
|
+
# Use the checker shipped beside this upgrade script, not a file newly staged
|
|
95
|
+
# from the target commit. This keeps upgrades from legacy marketplace commits
|
|
96
|
+
# testable and prevents a target archive from disabling its own validation.
|
|
97
|
+
UPGRADE_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
98
|
+
PLUGIN_ARTIFACT_CHECKER="$UPGRADE_SCRIPT_DIR/check-plugin-hook-artifact.mjs"
|
|
94
99
|
|
|
95
100
|
# ─── Pre-flight ────────────────────────────────────────────────────────────
|
|
96
101
|
if [ ! -d "$MARKETPLACE_DIR" ]; then
|
|
@@ -514,6 +519,25 @@ echo "==> Installing runtime deps (this may take a minute)..."
|
|
|
514
519
|
exit 1
|
|
515
520
|
}
|
|
516
521
|
|
|
522
|
+
# Validate a real staged plugin before moving it into the live cache. The
|
|
523
|
+
# host registry may load hooks immediately after the swap; a manifest that
|
|
524
|
+
# names a missing script would otherwise surface as a frightening `127` in the
|
|
525
|
+
# user's next session. Tiny legacy marketplace fixtures (and genuinely old
|
|
526
|
+
# plugin archives) have no plugin wiring to validate, so preserve their
|
|
527
|
+
# historical cache-refresh behavior; once wiring is present, the checker is
|
|
528
|
+
# mandatory and fail-closed. The checker itself comes from this script's
|
|
529
|
+
# installed release, never from the target archive being validated.
|
|
530
|
+
if [ -f "$STAGE_PATH/hooks/hooks.json" ] || [ -f "$STAGE_PATH/.claude-plugin/plugin.json" ] || [ -f "$STAGE_PATH/.codex-plugin/plugin.json" ]; then
|
|
531
|
+
if [ ! -f "$PLUGIN_ARTIFACT_CHECKER" ]; then
|
|
532
|
+
echo "ERROR: plugin artifact checker is missing beside the upgrade script — the live cache at $NEW_INSTALL_PATH was not touched" >&2
|
|
533
|
+
exit 1
|
|
534
|
+
fi
|
|
535
|
+
if ! node "$PLUGIN_ARTIFACT_CHECKER" --root "$STAGE_PATH" --skip-pack; then
|
|
536
|
+
echo "ERROR: staged plugin artifact integrity check failed — the live cache at $NEW_INSTALL_PATH was not touched" >&2
|
|
537
|
+
exit 1
|
|
538
|
+
fi
|
|
539
|
+
fi
|
|
540
|
+
|
|
517
541
|
# ─── 5. Swap the staged copy in ───────────────────────────────────────────
|
|
518
542
|
if [ -e "$NEW_INSTALL_PATH" ] || [ -L "$NEW_INSTALL_PATH" ]; then
|
|
519
543
|
HAD_LIVE_CACHE=1
|