qualia-framework 7.2.1 → 7.2.2

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,20 @@ 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
+ ## [7.2.2] - 2026-06-27 (install UX — masked codes, clean references, update-on-/qualia)
12
+
13
+ ### Fixed
14
+ - **Install code is masked** — the `QS-NAME-##` code now shows as `*` in the
15
+ interactive prompt and is hidden in piped/log output, so it never appears on screen.
16
+ - **`archetypes` EISDIR warning gone** — the `references/` flat copy now skips
17
+ directories (the `archetypes/` tree is handled by the recursive canonical copy);
18
+ a clean install reports zero warnings.
19
+
20
+ ### Added
21
+ - **Update notice on `/qualia`** — the router surfaces the cached "update available"
22
+ notice (the same one `auto-update.js` writes for the session-start banner), so an
23
+ operator who only ever runs `/qualia` still learns a new framework version is out.
24
+
11
25
  ## [7.2.1] - 2026-06-27 (system-audit makeover — portability, least-privilege, the one-loop)
12
26
 
13
27
  Outcome of a full four-system audit. Doc-truth and portability fixes plus the
package/bin/install.js CHANGED
@@ -507,6 +507,29 @@ function closeRl() {
507
507
  if (SHARED_RL) { try { SHARED_RL.close(); } catch {} SHARED_RL = null; }
508
508
  }
509
509
 
510
+ // Ask a question while masking the typed answer with '*' (for install codes).
511
+ // The prompt itself is written before muting, so only keystrokes are hidden.
512
+ function questionMasked(rl, prompt) {
513
+ return new Promise((resolve) => {
514
+ rl.question(prompt, (answer) => {
515
+ rl.stdoutMuted = false;
516
+ rl.output.write("\n");
517
+ resolve(answer);
518
+ });
519
+ rl.stdoutMuted = true;
520
+ if (!rl.__maskPatched) {
521
+ rl.__maskPatched = true;
522
+ const origWrite = rl._writeToOutput.bind(rl);
523
+ rl._writeToOutput = function (str) {
524
+ if (!rl.stdoutMuted) return origWrite(str);
525
+ // Pass control sequences (newlines) through; mask printable input.
526
+ if (str === "\n" || str === "\r\n" || str === "\r") return rl.output.write(str);
527
+ return rl.output.write("*");
528
+ };
529
+ }
530
+ });
531
+ }
532
+
510
533
  // Read every available stdin line into an array. Resolves immediately on
511
534
  // 'end'. Used only when stdin is piped (legacy `echo ... | install`).
512
535
  function bufferStdin() {
@@ -536,8 +559,9 @@ function askCode() {
536
559
  if (!IS_INTERACTIVE) {
537
560
  printHeader();
538
561
  const line = nextPipedLine();
539
- // Echo the prompt + answer for log readability.
540
- process.stdout.write(` ${WHITE}Enter install code (or "EMPLOYEE" for no-code install):${RESET} ${line}\n`);
562
+ // Echo the prompt for log readability, but never reveal the code itself.
563
+ const shown = isEmployeeKeyword(line) ? String(line).trim().toUpperCase() : (line ? "*".repeat(String(line).trim().length || 1) : "");
564
+ process.stdout.write(` ${WHITE}Enter install code (or "EMPLOYEE" for no-code install):${RESET} ${shown}\n`);
541
565
  resolve(String(line || "").trim());
542
566
  return;
543
567
  }
@@ -546,7 +570,7 @@ function askCode() {
546
570
  console.log(` ${DIM}OWNER / team member? Enter your install code (QS-NAME-##).${RESET}`);
547
571
  console.log(` ${DIM}New employee without a code? Type ${RESET}${TEAL}EMPLOYEE${RESET}${DIM} to install in employee mode.${RESET}`);
548
572
  console.log("");
549
- rl.question(` ${WHITE}Install code or "EMPLOYEE":${RESET} `, (answer) => {
573
+ questionMasked(rl, ` ${WHITE}Install code or "EMPLOYEE":${RESET} `).then((answer) => {
550
574
  resolve(String(answer || "").trim());
551
575
  });
552
576
  });
@@ -1028,7 +1052,11 @@ async function main() {
1028
1052
  const refDest = path.join(CLAUDE_DIR, "qualia-references");
1029
1053
  if (fs.existsSync(refDir)) {
1030
1054
  if (!fs.existsSync(refDest)) fs.mkdirSync(refDest, { recursive: true });
1031
- for (const file of fs.readdirSync(refDir)) {
1055
+ for (const entry of fs.readdirSync(refDir, { withFileTypes: true })) {
1056
+ // Skip nested dirs (e.g. archetypes/) — they are handled by the canonical
1057
+ // tree copy below. Reading a directory as a text file throws EISDIR.
1058
+ if (entry.isDirectory()) continue;
1059
+ const file = entry.name;
1032
1060
  try {
1033
1061
  copyTextTransform(path.join(refDir, file), path.join(refDest, file), claudeText);
1034
1062
  ok(file);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualia-framework",
3
- "version": "7.2.1",
3
+ "version": "7.2.2",
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"
@@ -39,6 +39,12 @@ node ${QUALIA_BIN}/last-report.js 2>/dev/null
39
39
  ```
40
40
  Exit 0 → it prints a one-line digest of the newest session report (`Last session ({date}, {age}d ago): {summary} → next: {next}`). Exit 1 → no reports yet (nothing to surface). When a project is loaded and a digest exists, print that line **at the very TOP of your output**, before the banner — so the first thing the operator (or a teammate picking the project up) sees is exactly where the last session ended.
41
41
 
42
+ Surface a pending framework update — read the same sticky notice the session-start banner uses (written by `auto-update.js` when npm ships a newer version), so an operator who only ever types `/qualia` still learns an update is out:
43
+ ```bash
44
+ node -e "try{const fs=require('fs'),os=require('os'),p=require('path');const h=process.env.QUALIA_HOME||p.join(os.homedir(),'.claude');const n=JSON.parse(fs.readFileSync(p.join(h,'.qualia-update-available.json'),'utf8'));const cur=(JSON.parse(fs.readFileSync(p.join(h,'.qualia-config.json'),'utf8')).version)||n.current;if(n&&n.latest&&n.latest!==cur)console.log('UPDATE_AVAILABLE '+cur+' -> '+n.latest)}catch{}"
45
+ ```
46
+ If it prints `UPDATE_AVAILABLE {cur} -> {latest}`, show one line at the **very top** of your reply (above the last-session digest and banner): `▲ Framework update available: v{cur} → v{latest}. Run: npx qualia-framework@latest install`. If it prints nothing, say nothing. Never run a live `npm view` here — `/qualia` must stay instant; this only reads the cached notice.
47
+
42
48
  Read conversation context — what has the user been doing, what errors occurred.
43
49
 
44
50
  ### 2. Classify and Route