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 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 ts = new Date().toISOString().replace(/[:.]/g, "-");
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 ts = new Date().toISOString().replace(/[:.]/g, "-");
824
- const bak = `${claudeDest}.bak.${ts}`;
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
- const ts = new Date().toISOString().replace(/[:.]/g, "-");
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualia-framework",
3
- "version": "6.8.0",
3
+ "version": "6.8.1",
4
4
  "description": "Claude Code and Codex workflow framework by Qualia Solutions. Plan, build, verify, ship.",
5
5
  "bin": {
6
6
  "qualia-framework": "./bin/cli.js"