@ngockhoale/ukit 2.5.0 → 2.5.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
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 2.5.1 - 2026-09-18
6
+
7
+ Post-release fixes from C20 reviewer advisories.
8
+
9
+ - **`projectImportant` symlink fallback**: the odd-open-error fallback imported
10
+ `stat` as `lstat`, which follows symlinks — a broken symlink degraded to `missing`
11
+ and a symlink-to-file to `unreadable` instead of the correct `unsafe-type`
12
+ (spec §6.1). Now uses real `lstat`; fixed in `src/core/projectImportant.js` and both
13
+ installed-runtime mirrors (parity-locked).
14
+ - **`inspectProjectImportant` guard**: a non-string `projectRoot` threw `TypeError`
15
+ before the try block instead of degrading to `unreadable`, violating the
16
+ "no documented entry point throws" contract. Guard added (src + mirrors).
17
+ - **Uninstall backup exhaustion surfaced**: when all 101 backup-name candidates are
18
+ occupied, `PROJECT_IMPORTANT.md` was silently preserved without a backup. Now emits
19
+ a `backupWarnings` line telling the user to free a slot and rerun.
20
+ - **Test tightening**: the packed-install smoke now asserts `project-important.sh`
21
+ occupies SessionStart index 0 (spec: must run first); corrected a stale "~5%
22
+ headroom" comment on the unpacked-size ceiling.
23
+
5
24
  ## 2.5.0 - 2026-09-18
6
25
 
7
26
  C20 feature release (14 handoff tasks + 3-lane review): project-owner instructions via a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex, OpenCode, and omp (Oh My Pi).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import fs from 'node:fs';
20
- import { open, stat as lstat, readFile } from 'node:fs/promises';
20
+ import { open, lstat, readFile } from 'node:fs/promises';
21
21
  import path from 'node:path';
22
22
 
23
23
  import {
@@ -112,6 +112,9 @@ function invalidResult(state) {
112
112
  */
113
113
  export async function inspectProjectImportant(options = {}) {
114
114
  const { projectRoot, config } = options;
115
+ if (typeof projectRoot !== 'string' || projectRoot.length === 0) {
116
+ return invalidResult('unreadable');
117
+ }
115
118
  const target = path.join(projectRoot, PROJECT_IMPORTANT_FILENAME);
116
119
 
117
120
  let handle;
@@ -89,6 +89,10 @@ async function planImportantBackup(projectRoot) {
89
89
  }
90
90
 
91
91
  plan.exhausted = true;
92
+ plan.backupWarnings.push(
93
+ `[UKit] Backup name space exhausted (${IMPORTANT_BACKUP_COLLISION_LIMIT + 1} candidates in use); `
94
+ + `${PROJECT_IMPORTANT_FILENAME} preserved in place without a backup. Free a backup slot and rerun \`ukit uninstall\`.`,
95
+ );
92
96
  return plan;
93
97
  }
94
98
 
@@ -25,7 +25,7 @@
25
25
  */
26
26
 
27
27
  import fs from 'node:fs';
28
- import { open, stat as lstat } from 'node:fs/promises';
28
+ import { open, lstat } from 'node:fs/promises';
29
29
  import path from 'node:path';
30
30
  import { fileURLToPath } from 'node:url';
31
31
 
@@ -129,6 +129,9 @@ function invalidResult(state) {
129
129
  */
130
130
  export async function inspectProjectImportant(options = {}) {
131
131
  const { projectRoot, config } = options;
132
+ if (typeof projectRoot !== 'string' || projectRoot.length === 0) {
133
+ return invalidResult('unreadable');
134
+ }
132
135
  const target = path.join(projectRoot, PROJECT_IMPORTANT_FILENAME);
133
136
 
134
137
  let handle;