@massu/core 2.4.0 → 2.5.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/CHANGELOG.md +65 -0
- package/dist/cli.js +153 -34
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,71 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
10
10
|
> redistributed under the **Apache License 2.0**. Full license + NOTICE:
|
|
11
11
|
> `packages/core/assets/embedder/MODEL-LICENSE`.
|
|
12
12
|
|
|
13
|
+
## [2.5.0] - 2026-08-01
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **`install-hooks` ADDED a hook registration instead of REPLACING it, so every version bump
|
|
18
|
+
doubled the hooks — and that defect BLOCKED ITS OWN REMEDY.** `mergeHookEntries` deduped by
|
|
19
|
+
EXACT COMMAND STRING, and the emitted command embeds the version, so
|
|
20
|
+
`npx -y @massu/core@1.16.2 hook-runner user-prompt` and the `@2.4.0` form were two different
|
|
21
|
+
strings, two different hooks, BOTH kept. The command was simultaneously the payload and the
|
|
22
|
+
identity, so any payload change forged a new identity.
|
|
23
|
+
|
|
24
|
+
Measured, not theoretical: one workspace carried `1.15.5` x16 **and** `1.15.2` x15 at once,
|
|
25
|
+
with 15 hook names firing TWICE per event and writing double telemetry indefinitely. Worse,
|
|
26
|
+
because upgrading duplicated, nobody upgraded — 9 of 10 workspaces on this machine sat on a
|
|
27
|
+
pre-2.0.0 version whose `better-sqlite3` ABI did not match the running Node, and one had
|
|
28
|
+
**26,119 `NODE_MODULE_VERSION` hook failures**: a total massu outage. It stayed invisible
|
|
29
|
+
because a crashing hook still **exits 0**.
|
|
30
|
+
|
|
31
|
+
Registration identity is now the `hook-runner` subcommand — a stable CLI contract (`cli.ts`
|
|
32
|
+
dispatches on exactly that token), invariant under both a version bump and a launch-mechanism
|
|
33
|
+
change. Non-massu entries keep the historical exact-command semantics, so a customer's own
|
|
34
|
+
hooks are never touched or collapsed.
|
|
35
|
+
|
|
36
|
+
Proof it can fail (CR-72, planted in the real tree, restored byte-identical by sha256):
|
|
37
|
+
reverting the identity branch to exact-command dedup turns
|
|
38
|
+
`installer-registration-identity-drift-guard.test.ts` RED — that guard drives the REAL
|
|
39
|
+
`installHooks()` over a real temp project seeded with an older-version block, a node-direct
|
|
40
|
+
block and a customer hook.
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- **A version-stable launcher shim, cutting ~0.86s of npm bootstrap off each hook fire.**
|
|
45
|
+
`npx -y @massu/core@<v> …` spends ~0.86s starting npm before any massu code runs. Measured
|
|
46
|
+
interleaved, min-of-5: `npx` 0.97s, `npx --offline` 0.88s, `node <cli.js>` 0.11s — so the
|
|
47
|
+
cost is npm's own startup, NOT the registry, and no npx flag addresses it.
|
|
48
|
+
|
|
49
|
+
`install-hooks` now writes `~/.massu/bin/massu-hook` and emits
|
|
50
|
+
`"~/.massu/bin/massu-hook" <version> hook-runner <name>`. That path is VERSION-STABLE, and
|
|
51
|
+
the shim re-resolves the runtime AT FIRE TIME, exec'ing npx when it is absent.
|
|
52
|
+
|
|
53
|
+
This deliberately does NOT bake a versioned absolute path, which would reintroduce the class
|
|
54
|
+
P-003 (v1.9.4) eliminated: such a path is "invalidated by cache clears, global-install
|
|
55
|
+
relocations, or npx upgrades — silently 404-ing the hooks". Probing at emit time says nothing
|
|
56
|
+
about invalidation after emit. Three fail-safe layers instead — shim missing at emit emits
|
|
57
|
+
npx; runtime missing at fire execs npx; both present takes the fast path — so no single
|
|
58
|
+
missing artifact is fatal and a stale registration self-heals.
|
|
59
|
+
|
|
60
|
+
Measured after: fast path **0.11s vs npx 1.07s (9.7x)**. The fallback is mutation-proven — a
|
|
61
|
+
shim with its npx branch removed FAILS the same no-runtime case (exit 27), so the test is not
|
|
62
|
+
vacuous. `exec` in both branches preserves stdin, stdout and the exit code.
|
|
63
|
+
|
|
64
|
+
Semver MINOR: `massuHookIdentity`, `massuShimPath`, `massuShimBody`, `installMassuShim`,
|
|
65
|
+
`resolveMassuShim`, `massuRuntimeDir`, `massuRuntimeCliPath`, `resolveMassuRuntimeCli` and
|
|
66
|
+
`materializeMassuRuntime` are new exports. `MASSU_NO_NODE_DIRECT=1` forces the npx form.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- **CR-70 Layer 3 amended: installer symmetry is no longer npx-specific.** Both emitters read
|
|
71
|
+
one resolver and agree on mechanism AND pinned version. The drift-guard previously asserted
|
|
72
|
+
the literal `'npx'`, which pinned an implementation detail rather than the property — it
|
|
73
|
+
would go RED on a correct mechanism change and GREEN on a wrong one that used npx on both
|
|
74
|
+
sides. Three assertions were amended on the same grounds
|
|
75
|
+
(`init-hook-paths-no-absolute`, `init-mcp-json-pin`, `cli.test.ts`); each keeps its original
|
|
76
|
+
protection of a visible pinned version and a known launch mechanism.
|
|
77
|
+
|
|
13
78
|
## [2.4.0] - 2026-07-31
|
|
14
79
|
|
|
15
80
|
### Fixed
|
package/dist/cli.js
CHANGED
|
@@ -15980,14 +15980,23 @@ __export(init_exports, {
|
|
|
15980
15980
|
getInstallerVersion: () => getInstallerVersion,
|
|
15981
15981
|
initMemoryDir: () => initMemoryDir,
|
|
15982
15982
|
installHooks: () => installHooks,
|
|
15983
|
+
installMassuShim: () => installMassuShim,
|
|
15983
15984
|
isTemplateName: () => isTemplateName,
|
|
15984
15985
|
listTemplates: () => listTemplates,
|
|
15986
|
+
massuHookIdentity: () => massuHookIdentity,
|
|
15987
|
+
massuRuntimeCliPath: () => massuRuntimeCliPath,
|
|
15988
|
+
massuRuntimeDir: () => massuRuntimeDir,
|
|
15989
|
+
massuShimBody: () => massuShimBody,
|
|
15990
|
+
massuShimPath: () => massuShimPath,
|
|
15991
|
+
materializeMassuRuntime: () => materializeMassuRuntime,
|
|
15985
15992
|
mergeHooksConfig: () => mergeHooksConfig,
|
|
15986
15993
|
parseInitArgs: () => parseInitArgs,
|
|
15987
15994
|
printInitHelp: () => printInitHelp,
|
|
15988
15995
|
registerMcpServer: () => registerMcpServer,
|
|
15989
15996
|
renderConfigYaml: () => renderConfigYaml,
|
|
15990
15997
|
resolveHooksDir: () => resolveHooksDir,
|
|
15998
|
+
resolveMassuRuntimeCli: () => resolveMassuRuntimeCli,
|
|
15999
|
+
resolveMassuShim: () => resolveMassuShim,
|
|
15991
16000
|
resolveTemplatesDir: () => resolveTemplatesDir,
|
|
15992
16001
|
runInit: () => runInit,
|
|
15993
16002
|
validateWrittenConfig: () => validateWrittenConfig,
|
|
@@ -15997,6 +16006,7 @@ import { closeSync as closeSync4, existsSync as existsSync19, fsyncSync as fsync
|
|
|
15997
16006
|
import { resolve as resolve9, basename as basename6, dirname as dirname12 } from "path";
|
|
15998
16007
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
15999
16008
|
import { homedir as homedir7 } from "os";
|
|
16009
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
16000
16010
|
import { stringify as yamlStringify, parse as yamlParse } from "yaml";
|
|
16001
16011
|
function detectFramework(projectRoot) {
|
|
16002
16012
|
const result = {
|
|
@@ -16570,11 +16580,8 @@ function registerMcpServer(projectRoot) {
|
|
|
16570
16580
|
return false;
|
|
16571
16581
|
}
|
|
16572
16582
|
const version = getInstallerVersion();
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
command: "npx",
|
|
16576
|
-
args: ["-y", `@massu/core@${version}`]
|
|
16577
|
-
};
|
|
16583
|
+
const shim = resolveMassuShim();
|
|
16584
|
+
servers.massu = shim !== null ? { type: "stdio", command: shim, args: [version] } : { type: "stdio", command: "npx", args: ["-y", `@massu/core@${version}`] };
|
|
16578
16585
|
existing.mcpServers = servers;
|
|
16579
16586
|
writeFileSync5(mcpPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
16580
16587
|
return true;
|
|
@@ -16591,7 +16598,98 @@ function resolveHooksDir() {
|
|
|
16591
16598
|
}
|
|
16592
16599
|
return "node_modules/@massu/core/dist/hooks";
|
|
16593
16600
|
}
|
|
16594
|
-
function
|
|
16601
|
+
function massuRuntimeDir(version) {
|
|
16602
|
+
return resolve9(homedir7(), ".massu", "runtime", version);
|
|
16603
|
+
}
|
|
16604
|
+
function massuRuntimeCliPath(version) {
|
|
16605
|
+
return resolve9(massuRuntimeDir(version), "node_modules", "@massu", "core", "dist", "cli.js");
|
|
16606
|
+
}
|
|
16607
|
+
function resolveMassuRuntimeCli(version) {
|
|
16608
|
+
if (process.env.MASSU_NO_NODE_DIRECT === "1") return null;
|
|
16609
|
+
const cli = massuRuntimeCliPath(version);
|
|
16610
|
+
if (!existsSync19(cli)) return null;
|
|
16611
|
+
try {
|
|
16612
|
+
execFileSync2(process.execPath, [cli, "--version"], { stdio: "ignore", timeout: 2e4 });
|
|
16613
|
+
return cli;
|
|
16614
|
+
} catch {
|
|
16615
|
+
return null;
|
|
16616
|
+
}
|
|
16617
|
+
}
|
|
16618
|
+
function materializeMassuRuntime(version) {
|
|
16619
|
+
if (process.env.MASSU_NO_NODE_DIRECT === "1") return false;
|
|
16620
|
+
if (resolveMassuRuntimeCli(version) !== null) return true;
|
|
16621
|
+
const dir = massuRuntimeDir(version);
|
|
16622
|
+
try {
|
|
16623
|
+
mkdirSync10(dir, { recursive: true });
|
|
16624
|
+
execFileSync2(
|
|
16625
|
+
"npm",
|
|
16626
|
+
["install", "--prefix", dir, `@massu/core@${version}`, "--no-audit", "--no-fund", "--loglevel=error"],
|
|
16627
|
+
{ stdio: "ignore", timeout: 3e5 }
|
|
16628
|
+
);
|
|
16629
|
+
} catch {
|
|
16630
|
+
return false;
|
|
16631
|
+
}
|
|
16632
|
+
return resolveMassuRuntimeCli(version) !== null;
|
|
16633
|
+
}
|
|
16634
|
+
function massuShimPath() {
|
|
16635
|
+
return resolve9(homedir7(), ".massu", "bin", "massu-hook");
|
|
16636
|
+
}
|
|
16637
|
+
function massuShimBody() {
|
|
16638
|
+
return `#!/bin/sh
|
|
16639
|
+
# massu launcher shim \u2014 GENERATED, do not edit. Recreated by \`massu install-hooks\`.
|
|
16640
|
+
#
|
|
16641
|
+
# usage: massu-hook <version> <subcommand...>
|
|
16642
|
+
#
|
|
16643
|
+
# Resolves the massu runtime AT FIRE TIME so a deleted/moved runtime degrades to npx
|
|
16644
|
+
# instead of silently 404-ing the hook (the P-003 failure class). exec preserves stdin,
|
|
16645
|
+
# stdout, stderr and the exit code.
|
|
16646
|
+
set -u
|
|
16647
|
+
if [ "$#" -lt 1 ]; then
|
|
16648
|
+
echo "massu-hook: missing <version> argument" >&2
|
|
16649
|
+
exit 2
|
|
16650
|
+
fi
|
|
16651
|
+
MASSU_VERSION="$1"
|
|
16652
|
+
shift
|
|
16653
|
+
MASSU_CLI="\${HOME}/.massu/runtime/\${MASSU_VERSION}/node_modules/@massu/core/dist/cli.js"
|
|
16654
|
+
if [ -f "$MASSU_CLI" ]; then
|
|
16655
|
+
exec node "$MASSU_CLI" "$@"
|
|
16656
|
+
fi
|
|
16657
|
+
exec npx -y "@massu/core@\${MASSU_VERSION}" "$@"
|
|
16658
|
+
`;
|
|
16659
|
+
}
|
|
16660
|
+
function installMassuShim() {
|
|
16661
|
+
if (process.env.MASSU_NO_NODE_DIRECT === "1") return false;
|
|
16662
|
+
if (process.platform === "win32") return false;
|
|
16663
|
+
const shim = massuShimPath();
|
|
16664
|
+
try {
|
|
16665
|
+
mkdirSync10(dirname12(shim), { recursive: true });
|
|
16666
|
+
writeFileSync5(shim, massuShimBody(), "utf-8");
|
|
16667
|
+
chmodSync5(shim, 493);
|
|
16668
|
+
} catch {
|
|
16669
|
+
return false;
|
|
16670
|
+
}
|
|
16671
|
+
try {
|
|
16672
|
+
execFileSync2(shim, [], { stdio: "ignore", timeout: 1e4 });
|
|
16673
|
+
return false;
|
|
16674
|
+
} catch (e2) {
|
|
16675
|
+
const status = e2.status;
|
|
16676
|
+
return status === 2;
|
|
16677
|
+
}
|
|
16678
|
+
}
|
|
16679
|
+
function resolveMassuShim() {
|
|
16680
|
+
if (process.env.MASSU_NO_NODE_DIRECT === "1") return null;
|
|
16681
|
+
if (process.platform === "win32") return null;
|
|
16682
|
+
const shim = massuShimPath();
|
|
16683
|
+
if (!existsSync19(shim)) return null;
|
|
16684
|
+
try {
|
|
16685
|
+
execFileSync2(shim, [], { stdio: "ignore", timeout: 1e4 });
|
|
16686
|
+
return null;
|
|
16687
|
+
} catch (e2) {
|
|
16688
|
+
return e2.status === 2 ? shim : null;
|
|
16689
|
+
}
|
|
16690
|
+
}
|
|
16691
|
+
function hookCmd(version, hookName, shim) {
|
|
16692
|
+
if (shim !== null) return `"${shim}" ${version} hook-runner ${hookName}`;
|
|
16595
16693
|
return `npx -y @massu/core@${version} hook-runner ${hookName}`;
|
|
16596
16694
|
}
|
|
16597
16695
|
function getHookTimeout(name) {
|
|
@@ -16599,11 +16697,12 @@ function getHookTimeout(name) {
|
|
|
16599
16697
|
}
|
|
16600
16698
|
function buildHooksConfig(_hooksDir) {
|
|
16601
16699
|
const version = getInstallerVersion();
|
|
16700
|
+
const shim = resolveMassuShim();
|
|
16602
16701
|
return {
|
|
16603
16702
|
SessionStart: [
|
|
16604
16703
|
{
|
|
16605
16704
|
hooks: [
|
|
16606
|
-
{ type: "command", command: hookCmd(version, "session-start"), timeout: getHookTimeout("session-start") }
|
|
16705
|
+
{ type: "command", command: hookCmd(version, "session-start", shim), timeout: getHookTimeout("session-start") }
|
|
16607
16706
|
]
|
|
16608
16707
|
}
|
|
16609
16708
|
],
|
|
@@ -16618,26 +16717,26 @@ function buildHooksConfig(_hooksDir) {
|
|
|
16618
16717
|
{
|
|
16619
16718
|
matcher: "Bash|Write|Edit",
|
|
16620
16719
|
hooks: [
|
|
16621
|
-
{ type: "command", command: hookCmd(version, "pre-tool-use-gate"), timeout: getHookTimeout("pre-tool-use-gate") }
|
|
16720
|
+
{ type: "command", command: hookCmd(version, "pre-tool-use-gate", shim), timeout: getHookTimeout("pre-tool-use-gate") }
|
|
16622
16721
|
]
|
|
16623
16722
|
}
|
|
16624
16723
|
],
|
|
16625
16724
|
PostToolUse: [
|
|
16626
16725
|
{
|
|
16627
16726
|
hooks: [
|
|
16628
|
-
{ type: "command", command: hookCmd(version, "post-tool-use"), timeout: getHookTimeout("post-tool-use") },
|
|
16629
|
-
{ type: "command", command: hookCmd(version, "quality-event"), timeout: getHookTimeout("quality-event") },
|
|
16630
|
-
{ type: "command", command: hookCmd(version, "cost-tracker"), timeout: getHookTimeout("cost-tracker") }
|
|
16727
|
+
{ type: "command", command: hookCmd(version, "post-tool-use", shim), timeout: getHookTimeout("post-tool-use") },
|
|
16728
|
+
{ type: "command", command: hookCmd(version, "quality-event", shim), timeout: getHookTimeout("quality-event") },
|
|
16729
|
+
{ type: "command", command: hookCmd(version, "cost-tracker", shim), timeout: getHookTimeout("cost-tracker") }
|
|
16631
16730
|
]
|
|
16632
16731
|
},
|
|
16633
16732
|
{
|
|
16634
16733
|
matcher: "Edit|Write",
|
|
16635
16734
|
hooks: [
|
|
16636
|
-
{ type: "command", command: hookCmd(version, "post-edit-context"), timeout: getHookTimeout("post-edit-context") },
|
|
16735
|
+
{ type: "command", command: hookCmd(version, "post-edit-context", shim), timeout: getHookTimeout("post-edit-context") },
|
|
16637
16736
|
// Auto-learning pipeline — classifies failures and detects fixes on
|
|
16638
16737
|
// file changes. See Phase 5-6 of the autodetect plan.
|
|
16639
|
-
{ type: "command", command: hookCmd(version, "fix-detector"), timeout: getHookTimeout("fix-detector") },
|
|
16640
|
-
{ type: "command", command: hookCmd(version, "classify-failure"), timeout: getHookTimeout("classify-failure") }
|
|
16738
|
+
{ type: "command", command: hookCmd(version, "fix-detector", shim), timeout: getHookTimeout("fix-detector") },
|
|
16739
|
+
{ type: "command", command: hookCmd(version, "classify-failure", shim), timeout: getHookTimeout("classify-failure") }
|
|
16641
16740
|
]
|
|
16642
16741
|
},
|
|
16643
16742
|
{
|
|
@@ -16645,35 +16744,35 @@ function buildHooksConfig(_hooksDir) {
|
|
|
16645
16744
|
hooks: [
|
|
16646
16745
|
// Incident + rule enforcement pipelines fire on Write-only (incidents
|
|
16647
16746
|
// are authored as .md files; rules are enforced after new-file drops).
|
|
16648
|
-
{ type: "command", command: hookCmd(version, "incident-pipeline"), timeout: getHookTimeout("incident-pipeline") },
|
|
16649
|
-
{ type: "command", command: hookCmd(version, "rule-enforcement-pipeline"), timeout: getHookTimeout("rule-enforcement-pipeline") }
|
|
16747
|
+
{ type: "command", command: hookCmd(version, "incident-pipeline", shim), timeout: getHookTimeout("incident-pipeline") },
|
|
16748
|
+
{ type: "command", command: hookCmd(version, "rule-enforcement-pipeline", shim), timeout: getHookTimeout("rule-enforcement-pipeline") }
|
|
16650
16749
|
]
|
|
16651
16750
|
}
|
|
16652
16751
|
],
|
|
16653
16752
|
Stop: [
|
|
16654
16753
|
{
|
|
16655
16754
|
hooks: [
|
|
16656
|
-
{ type: "command", command: hookCmd(version, "session-end"), timeout: getHookTimeout("session-end") },
|
|
16755
|
+
{ type: "command", command: hookCmd(version, "session-end", shim), timeout: getHookTimeout("session-end") },
|
|
16657
16756
|
// Session-end auto-learning aggregation (failure-class roll-up).
|
|
16658
|
-
{ type: "command", command: hookCmd(version, "auto-learning-pipeline"), timeout: getHookTimeout("auto-learning-pipeline") }
|
|
16757
|
+
{ type: "command", command: hookCmd(version, "auto-learning-pipeline", shim), timeout: getHookTimeout("auto-learning-pipeline") }
|
|
16659
16758
|
]
|
|
16660
16759
|
}
|
|
16661
16760
|
],
|
|
16662
16761
|
PreCompact: [
|
|
16663
16762
|
{
|
|
16664
16763
|
hooks: [
|
|
16665
|
-
{ type: "command", command: hookCmd(version, "pre-compact"), timeout: getHookTimeout("pre-compact") }
|
|
16764
|
+
{ type: "command", command: hookCmd(version, "pre-compact", shim), timeout: getHookTimeout("pre-compact") }
|
|
16666
16765
|
]
|
|
16667
16766
|
}
|
|
16668
16767
|
],
|
|
16669
16768
|
UserPromptSubmit: [
|
|
16670
16769
|
{
|
|
16671
16770
|
hooks: [
|
|
16672
|
-
{ type: "command", command: hookCmd(version, "user-prompt"), timeout: getHookTimeout("user-prompt") },
|
|
16673
|
-
{ type: "command", command: hookCmd(version, "intent-suggester"), timeout: getHookTimeout("intent-suggester") },
|
|
16771
|
+
{ type: "command", command: hookCmd(version, "user-prompt", shim), timeout: getHookTimeout("user-prompt") },
|
|
16772
|
+
{ type: "command", command: hookCmd(version, "intent-suggester", shim), timeout: getHookTimeout("intent-suggester") },
|
|
16674
16773
|
// plan-living-memory-slice-1 P3-002: automatic relevant-recall.
|
|
16675
16774
|
// Injects a compact "🧠 Relevant memory" block; fails open (empty).
|
|
16676
|
-
{ type: "command", command: hookCmd(version, "memory-recall"), timeout: getHookTimeout("memory-recall") }
|
|
16775
|
+
{ type: "command", command: hookCmd(version, "memory-recall", shim), timeout: getHookTimeout("memory-recall") }
|
|
16677
16776
|
]
|
|
16678
16777
|
}
|
|
16679
16778
|
]
|
|
@@ -16711,21 +16810,31 @@ function mergeHooksConfig(existing, additions) {
|
|
|
16711
16810
|
}
|
|
16712
16811
|
return merged;
|
|
16713
16812
|
}
|
|
16813
|
+
function massuHookIdentity(command) {
|
|
16814
|
+
if (!/@massu\/core|[/\\]\.?massu[/\\]/.test(command)) return null;
|
|
16815
|
+
const m3 = /\bhook-runner\s+([A-Za-z0-9._-]+)/.exec(command);
|
|
16816
|
+
return m3 ? m3[1] : null;
|
|
16817
|
+
}
|
|
16714
16818
|
function mergeHookEntries(existing, additions) {
|
|
16715
|
-
const
|
|
16819
|
+
const seenCommands = /* @__PURE__ */ new Set();
|
|
16820
|
+
const seenIdentities = /* @__PURE__ */ new Set();
|
|
16716
16821
|
const result = [];
|
|
16717
|
-
|
|
16718
|
-
if (!entry || typeof entry.command !== "string")
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
16723
|
-
|
|
16724
|
-
|
|
16725
|
-
|
|
16726
|
-
|
|
16822
|
+
const take = (entry) => {
|
|
16823
|
+
if (!entry || typeof entry.command !== "string") return;
|
|
16824
|
+
const identity = massuHookIdentity(entry.command);
|
|
16825
|
+
if (identity !== null) {
|
|
16826
|
+
if (seenIdentities.has(identity)) return;
|
|
16827
|
+
seenIdentities.add(identity);
|
|
16828
|
+
seenCommands.add(entry.command);
|
|
16829
|
+
result.push(entry);
|
|
16830
|
+
return;
|
|
16831
|
+
}
|
|
16832
|
+
if (seenCommands.has(entry.command)) return;
|
|
16833
|
+
seenCommands.add(entry.command);
|
|
16727
16834
|
result.push(entry);
|
|
16728
|
-
}
|
|
16835
|
+
};
|
|
16836
|
+
for (const entry of additions ?? []) take(entry);
|
|
16837
|
+
for (const entry of existing ?? []) take(entry);
|
|
16729
16838
|
return result;
|
|
16730
16839
|
}
|
|
16731
16840
|
function installHooks(projectRoot) {
|
|
@@ -18716,6 +18825,16 @@ async function runInstallHooks() {
|
|
|
18716
18825
|
console.log("Massu AI - Hook Installation");
|
|
18717
18826
|
console.log("============================");
|
|
18718
18827
|
console.log("");
|
|
18828
|
+
const version = getInstallerVersion();
|
|
18829
|
+
const shimOk = installMassuShim();
|
|
18830
|
+
const runtimeOk = materializeMassuRuntime(version);
|
|
18831
|
+
if (shimOk && runtimeOk) {
|
|
18832
|
+
console.log(" Launcher: shim + runtime ready (fast path, ~9x cheaper per hook)");
|
|
18833
|
+
} else if (shimOk) {
|
|
18834
|
+
console.log(" Launcher: shim ready, runtime unavailable \u2014 shim will fall back to npx at fire time");
|
|
18835
|
+
} else {
|
|
18836
|
+
console.log(" Launcher: npx (shim unavailable \u2014 works, ~0.9s slower per hook)");
|
|
18837
|
+
}
|
|
18719
18838
|
const { count } = installHooks(projectRoot);
|
|
18720
18839
|
console.log(` Installed ${count} hooks in .claude/settings.local.json`);
|
|
18721
18840
|
console.log("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massu/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 73 total), 56 workflow commands, 11 agents, 20+ patterns",
|
|
6
6
|
"main": "./dist/cli.js",
|