qualia-framework 6.8.0 → 6.8.1
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 +10 -0
- package/bin/install.js +53 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
> Note: git tags for historical versions were not retained; commit references are approximate
|
|
9
9
|
> and dates reflect commit history rather than npm publish timestamps.
|
|
10
10
|
|
|
11
|
+
## [6.8.1] - 2026-06-10 (installer hygiene — global hook sweep, bin purge, bak routing)
|
|
12
|
+
|
|
13
|
+
Found via a live audit of an installed `~/.claude`: the retired brain experiment's `brain-inject.js` was still wired under `UserPromptSubmit`, firing a failing node process on every user prompt, and had survived four releases because the settings merge never looked at that event.
|
|
14
|
+
|
|
15
|
+
### Fixed — installer
|
|
16
|
+
- **Settings hook sweep is now global.** The merge previously only cleaned events present in `qualiaHooks`, so deprecated entries under other events (`UserPromptSubmit`, etc.) were never pruned. The installer now sweeps every hook event for (a) retired Qualia hook commands and (b) `node "<CLAUDE_DIR>/..."` commands whose target file no longer exists, dropping empty blocks/events. User hooks (non-node or outside `CLAUDE_DIR`) are untouched.
|
|
17
|
+
- **`brain-inject.js` added to `DEPRECATED_HOOKS`** — the UserPromptSubmit half of the brain experiment was missing from the v6.8.0 prune list.
|
|
18
|
+
- **`bin/` orphan purge** — `DEPRECATED_BIN` removes the retired `build-brain-index.js`; bin/ previously had no deprecation pass at all.
|
|
19
|
+
- **`.bak` files routed to `backups/`** — `settings.json.bak.*` / `CLAUDE.md.bak.*` no longer accumulate in the `~/.claude` root across reinstalls; all three backup sites now write into a `backups/` subdir via a shared `bakPath()` helper.
|
|
20
|
+
|
|
11
21
|
## [6.8.0] - 2026-06-06 (audit remediation — installer, guards, trust-score, polish)
|
|
12
22
|
|
|
13
23
|
Closes the verified findings from the 2026-06-06 framework audit (4 CRITICAL · 7 HIGH · 33 MEDIUM, adversarially verified). Root cause of most CRITICALs was a stale/incomplete install, not source rot — fixed in the installer so a clean reinstall is complete and correct.
|
package/bin/install.js
CHANGED
|
@@ -210,13 +210,22 @@ function ensureCodexStatusLineConfig(existing) {
|
|
|
210
210
|
return `${next.slice(0, tuiMatch.index)}${tuiBlock}${next.slice(tuiMatch.index + tuiMatch[0].length)}`;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
// v6.8.1: .bak files go into a backups/ subdir next to the target instead of
|
|
214
|
+
// littering the target's directory (a dozen settings.json.bak.* files were
|
|
215
|
+
// accumulating in ~/.claude root across reinstalls).
|
|
216
|
+
function bakPath(dest) {
|
|
217
|
+
const dir = path.join(path.dirname(dest), "backups");
|
|
218
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
219
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
220
|
+
return path.join(dir, `${path.basename(dest)}.bak.${ts}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
213
223
|
function backupIfDifferent(dest, nextContent, label) {
|
|
214
224
|
if (!fs.existsSync(dest)) return false;
|
|
215
225
|
try {
|
|
216
226
|
const existing = fs.readFileSync(dest, "utf8");
|
|
217
227
|
if (existing === nextContent) return false;
|
|
218
|
-
const
|
|
219
|
-
const bak = `${dest}.bak.${ts}`;
|
|
228
|
+
const bak = bakPath(dest);
|
|
220
229
|
fs.copyFileSync(dest, bak);
|
|
221
230
|
ok(`Backed up existing ${label} -> ${path.basename(bak)}`);
|
|
222
231
|
return true;
|
|
@@ -683,6 +692,8 @@ async function main() {
|
|
|
683
692
|
"brain-pre-compact.js",
|
|
684
693
|
"brain-session-end.js",
|
|
685
694
|
"brain-session-start.js",
|
|
695
|
+
// v6.8.1: the UserPromptSubmit half of the brain experiment was missed.
|
|
696
|
+
"brain-inject.js",
|
|
686
697
|
];
|
|
687
698
|
for (const f of DEPRECATED_HOOKS) {
|
|
688
699
|
const p = path.join(hooksDest, f);
|
|
@@ -820,9 +831,8 @@ async function main() {
|
|
|
820
831
|
if (fs.existsSync(claudeDest)) {
|
|
821
832
|
const existing = fs.readFileSync(claudeDest, "utf8");
|
|
822
833
|
if (existing !== claudeMd) {
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
try { fs.copyFileSync(claudeDest, bak); ok(`Backed up existing CLAUDE.md → ${path.basename(bak)}`); } catch {}
|
|
834
|
+
const bak = bakPath(claudeDest);
|
|
835
|
+
try { fs.copyFileSync(claudeDest, bak); ok(`Backed up existing CLAUDE.md → backups/${path.basename(bak)}`); } catch {}
|
|
826
836
|
}
|
|
827
837
|
}
|
|
828
838
|
fs.writeFileSync(claudeDest, claudeText(claudeMd), "utf8");
|
|
@@ -842,6 +852,14 @@ async function main() {
|
|
|
842
852
|
try { fs.chmodSync(out, 0o755); } catch {}
|
|
843
853
|
ok(script.label);
|
|
844
854
|
}
|
|
855
|
+
// v6.8.1: purge retired bin scripts (same belt-and-suspenders as
|
|
856
|
+
// DEPRECATED_HOOKS — bin/ never had an orphan pass, so the brain
|
|
857
|
+
// experiment's indexer survived reinstalls).
|
|
858
|
+
const DEPRECATED_BIN = ["build-brain-index.js"];
|
|
859
|
+
for (const f of DEPRECATED_BIN) {
|
|
860
|
+
const p = path.join(binDest, f);
|
|
861
|
+
try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch {}
|
|
862
|
+
}
|
|
845
863
|
// Write a minimal root package.json so the installed CLI's `require("../package.json")`
|
|
846
864
|
// resolves post-install (bin/ lives at CLAUDE_DIR/bin, so the parent is CLAUDE_DIR).
|
|
847
865
|
fs.writeFileSync(
|
|
@@ -1204,6 +1222,35 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
1204
1222
|
}
|
|
1205
1223
|
}
|
|
1206
1224
|
|
|
1225
|
+
// v6.8.1: sweep ALL hook events for retired Qualia hooks and dead node
|
|
1226
|
+
// paths. The merge above only visits events present in qualiaHooks, so
|
|
1227
|
+
// entries under other events (e.g. the retired brain-inject.js under
|
|
1228
|
+
// UserPromptSubmit) survived every reinstall — firing a failing node
|
|
1229
|
+
// process on each user prompt. Runs after the hooks/ install, so an
|
|
1230
|
+
// existsSync miss means the file is truly gone, not not-yet-copied.
|
|
1231
|
+
const DEPRECATED_HOOK_CMDS = [
|
|
1232
|
+
"brain-inject.js", "build-brain-index.js", "block-env-edit.js",
|
|
1233
|
+
"brain-pre-compact.js", "brain-session-end.js", "brain-session-start.js",
|
|
1234
|
+
];
|
|
1235
|
+
const isDeadHookCmd = (cmd) => {
|
|
1236
|
+
if (typeof cmd !== "string") return false;
|
|
1237
|
+
if (DEPRECATED_HOOK_CMDS.some((f) => cmd.includes(f))) return true;
|
|
1238
|
+
const m = cmd.match(/^node "([^"]+)"$/);
|
|
1239
|
+
if (m && m[1].startsWith(CLAUDE_DIR + path.sep) && !fs.existsSync(m[1])) return true;
|
|
1240
|
+
return false;
|
|
1241
|
+
};
|
|
1242
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
1243
|
+
const blocks = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
|
|
1244
|
+
const cleaned = [];
|
|
1245
|
+
for (const block of blocks) {
|
|
1246
|
+
if (!block || !Array.isArray(block.hooks)) continue;
|
|
1247
|
+
const kept = block.hooks.filter((h) => !isDeadHookCmd(h && h.command));
|
|
1248
|
+
if (kept.length > 0) cleaned.push({ ...block, hooks: kept });
|
|
1249
|
+
}
|
|
1250
|
+
if (cleaned.length > 0) settings.hooks[event] = cleaned;
|
|
1251
|
+
else delete settings.hooks[event];
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1207
1254
|
// Permissions stay permissive; Qualia policy enforcement happens in hooks so
|
|
1208
1255
|
// OWNER overrides and EMPLOYEE blocks can share one source of truth. We still
|
|
1209
1256
|
// seed a scoped baseline allow-list (union-merged, never clobbering user
|
|
@@ -1238,9 +1285,7 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
1238
1285
|
// configs / custom permissions. Atomic write (tmp + rename) avoids partial
|
|
1239
1286
|
// writes; the .bak file is the recovery point if the merger ever misbehaves.
|
|
1240
1287
|
if (fs.existsSync(settingsPath)) {
|
|
1241
|
-
|
|
1242
|
-
const bak = `${settingsPath}.bak.${ts}`;
|
|
1243
|
-
try { fs.copyFileSync(settingsPath, bak); } catch {}
|
|
1288
|
+
try { fs.copyFileSync(settingsPath, bakPath(settingsPath)); } catch {}
|
|
1244
1289
|
}
|
|
1245
1290
|
const settingsTmp = `${settingsPath}.tmp.${process.pid}`;
|
|
1246
1291
|
fs.writeFileSync(settingsTmp, JSON.stringify(settings, null, 2));
|