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 +14 -0
- package/bin/install.js +32 -4
- package/package.json +1 -1
- package/skills/qualia/SKILL.md +6 -0
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
|
|
540
|
-
|
|
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
|
|
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
|
|
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
package/skills/qualia/SKILL.md
CHANGED
|
@@ -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
|