@skillsmith/core 0.12.3 → 0.12.5

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.
Files changed (188) hide show
  1. package/CHANGELOG.md +168 -0
  2. package/README.md +2 -2
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/config/owned-lock.acquire.d.ts +8 -0
  5. package/dist/src/config/owned-lock.acquire.d.ts.map +1 -1
  6. package/dist/src/config/owned-lock.acquire.js +19 -4
  7. package/dist/src/config/owned-lock.acquire.js.map +1 -1
  8. package/dist/src/config/owned-lock.claim.d.ts +4 -2
  9. package/dist/src/config/owned-lock.claim.d.ts.map +1 -1
  10. package/dist/src/config/owned-lock.claim.js +42 -8
  11. package/dist/src/config/owned-lock.claim.js.map +1 -1
  12. package/dist/src/config/owned-lock.d.ts.map +1 -1
  13. package/dist/src/config/owned-lock.js +1 -0
  14. package/dist/src/config/owned-lock.js.map +1 -1
  15. package/dist/src/config/owned-lock.test.js +126 -2
  16. package/dist/src/config/owned-lock.test.js.map +1 -1
  17. package/dist/src/config/owned-lock.types.d.ts +9 -0
  18. package/dist/src/config/owned-lock.types.d.ts.map +1 -1
  19. package/dist/src/db/createDatabase.d.ts.map +1 -1
  20. package/dist/src/db/createDatabase.js +0 -2
  21. package/dist/src/db/createDatabase.js.map +1 -1
  22. package/dist/src/exports/services.install.d.ts +1 -0
  23. package/dist/src/exports/services.install.d.ts.map +1 -1
  24. package/dist/src/exports/services.install.js +5 -0
  25. package/dist/src/exports/services.install.js.map +1 -1
  26. package/dist/src/index.d.ts +1 -1
  27. package/dist/src/index.js +1 -1
  28. package/dist/src/install/fan-out.cleanup.d.ts +37 -0
  29. package/dist/src/install/fan-out.cleanup.d.ts.map +1 -0
  30. package/dist/src/install/fan-out.cleanup.js +75 -0
  31. package/dist/src/install/fan-out.cleanup.js.map +1 -0
  32. package/dist/src/install/fan-out.d.ts +41 -31
  33. package/dist/src/install/fan-out.d.ts.map +1 -1
  34. package/dist/src/install/fan-out.js +213 -112
  35. package/dist/src/install/fan-out.js.map +1 -1
  36. package/dist/src/install/fan-out.leftovers.d.ts +60 -0
  37. package/dist/src/install/fan-out.leftovers.d.ts.map +1 -0
  38. package/dist/src/install/fan-out.leftovers.js +251 -0
  39. package/dist/src/install/fan-out.leftovers.js.map +1 -0
  40. package/dist/src/install/fan-out.locks.d.ts +12 -0
  41. package/dist/src/install/fan-out.locks.d.ts.map +1 -0
  42. package/dist/src/install/fan-out.locks.js +114 -0
  43. package/dist/src/install/fan-out.locks.js.map +1 -0
  44. package/dist/src/install/fan-out.manifest.d.ts +64 -0
  45. package/dist/src/install/fan-out.manifest.d.ts.map +1 -0
  46. package/dist/src/install/fan-out.manifest.js +130 -0
  47. package/dist/src/install/fan-out.manifest.js.map +1 -0
  48. package/dist/src/install/fan-out.overwrite.d.ts +51 -0
  49. package/dist/src/install/fan-out.overwrite.d.ts.map +1 -0
  50. package/dist/src/install/fan-out.overwrite.js +343 -0
  51. package/dist/src/install/fan-out.overwrite.js.map +1 -0
  52. package/dist/src/install/remove-if-same.d.ts +58 -0
  53. package/dist/src/install/remove-if-same.d.ts.map +1 -0
  54. package/dist/src/install/remove-if-same.js +222 -0
  55. package/dist/src/install/remove-if-same.js.map +1 -0
  56. package/dist/src/provenance/backfill.d.ts.map +1 -1
  57. package/dist/src/provenance/backfill.js +15 -2
  58. package/dist/src/provenance/backfill.js.map +1 -1
  59. package/dist/src/security/scanner/SecurityScanner.scanners.d.ts.map +1 -1
  60. package/dist/src/security/scanner/SecurityScanner.scanners.js +8 -1
  61. package/dist/src/security/scanner/SecurityScanner.scanners.js.map +1 -1
  62. package/dist/src/security/scanner/SecurityScanner.value-gate.d.ts +22 -2
  63. package/dist/src/security/scanner/SecurityScanner.value-gate.d.ts.map +1 -1
  64. package/dist/src/security/scanner/SecurityScanner.value-gate.js +143 -6
  65. package/dist/src/security/scanner/SecurityScanner.value-gate.js.map +1 -1
  66. package/dist/src/security/scanner/SecurityScanner.weak-passwords.d.ts +2 -2
  67. package/dist/src/security/scanner/SecurityScanner.weak-passwords.d.ts.map +1 -1
  68. package/dist/src/security/scanner/SecurityScanner.weak-passwords.js +185 -190
  69. package/dist/src/security/scanner/SecurityScanner.weak-passwords.js.map +1 -1
  70. package/dist/src/security/scanner/patterns.d.ts +47 -2
  71. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  72. package/dist/src/security/scanner/patterns.js +48 -3
  73. package/dist/src/security/scanner/patterns.js.map +1 -1
  74. package/dist/src/security/scanner/patterns.sensitive-path.d.ts +31 -3
  75. package/dist/src/security/scanner/patterns.sensitive-path.d.ts.map +1 -1
  76. package/dist/src/security/scanner/patterns.sensitive-path.js +59 -4
  77. package/dist/src/security/scanner/patterns.sensitive-path.js.map +1 -1
  78. package/dist/src/services/agent-pack/prompt-source.d.ts.map +1 -1
  79. package/dist/src/services/agent-pack/prompt-source.js +3 -2
  80. package/dist/src/services/agent-pack/prompt-source.js.map +1 -1
  81. package/dist/src/services/skill-installation.content.d.ts +7 -2
  82. package/dist/src/services/skill-installation.content.d.ts.map +1 -1
  83. package/dist/src/services/skill-installation.content.js +75 -28
  84. package/dist/src/services/skill-installation.content.js.map +1 -1
  85. package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
  86. package/dist/src/services/skill-installation.helpers.js +8 -0
  87. package/dist/src/services/skill-installation.helpers.js.map +1 -1
  88. package/dist/src/services/skill-installation.io.d.ts +1 -29
  89. package/dist/src/services/skill-installation.io.d.ts.map +1 -1
  90. package/dist/src/services/skill-installation.io.dirs.d.ts +15 -0
  91. package/dist/src/services/skill-installation.io.dirs.d.ts.map +1 -0
  92. package/dist/src/services/skill-installation.io.dirs.js +84 -0
  93. package/dist/src/services/skill-installation.io.dirs.js.map +1 -0
  94. package/dist/src/services/skill-installation.io.js +6 -176
  95. package/dist/src/services/skill-installation.io.js.map +1 -1
  96. package/dist/src/services/skill-installation.io.rollback.d.ts +138 -0
  97. package/dist/src/services/skill-installation.io.rollback.d.ts.map +1 -0
  98. package/dist/src/services/skill-installation.io.rollback.js +218 -0
  99. package/dist/src/services/skill-installation.io.rollback.js.map +1 -0
  100. package/dist/src/services/skill-installation.io.write.d.ts +69 -0
  101. package/dist/src/services/skill-installation.io.write.d.ts.map +1 -0
  102. package/dist/src/services/skill-installation.io.write.js +417 -0
  103. package/dist/src/services/skill-installation.io.write.js.map +1 -0
  104. package/dist/src/services/skill-installation.service.d.ts.map +1 -1
  105. package/dist/src/services/skill-installation.service.finalize.d.ts +62 -0
  106. package/dist/src/services/skill-installation.service.finalize.d.ts.map +1 -0
  107. package/dist/src/services/skill-installation.service.finalize.js +136 -0
  108. package/dist/src/services/skill-installation.service.finalize.js.map +1 -0
  109. package/dist/src/services/skill-installation.service.js +66 -80
  110. package/dist/src/services/skill-installation.service.js.map +1 -1
  111. package/dist/src/services/skill-installation.target-guard.d.ts +82 -0
  112. package/dist/src/services/skill-installation.target-guard.d.ts.map +1 -0
  113. package/dist/src/services/skill-installation.target-guard.js +410 -0
  114. package/dist/src/services/skill-installation.target-guard.js.map +1 -0
  115. package/dist/src/services/skill-installation.types.d.ts +13 -1
  116. package/dist/src/services/skill-installation.types.d.ts.map +1 -1
  117. package/dist/src/services/skill-installation.types.js.map +1 -1
  118. package/dist/src/services/skill-installation.uninstall.d.ts +3 -1
  119. package/dist/src/services/skill-installation.uninstall.d.ts.map +1 -1
  120. package/dist/src/services/skill-installation.uninstall.helpers.d.ts +59 -0
  121. package/dist/src/services/skill-installation.uninstall.helpers.d.ts.map +1 -0
  122. package/dist/src/services/skill-installation.uninstall.helpers.js +124 -0
  123. package/dist/src/services/skill-installation.uninstall.helpers.js.map +1 -0
  124. package/dist/src/services/skill-installation.uninstall.js +148 -25
  125. package/dist/src/services/skill-installation.uninstall.js.map +1 -1
  126. package/dist/src/utils/safe-fs.d.ts +69 -0
  127. package/dist/src/utils/safe-fs.d.ts.map +1 -1
  128. package/dist/src/utils/safe-fs.js +113 -2
  129. package/dist/src/utils/safe-fs.js.map +1 -1
  130. package/dist/tests/install/fan-out.test.js +1951 -13
  131. package/dist/tests/install/fan-out.test.js.map +1 -1
  132. package/dist/tests/install/remove-if-same.test.d.ts +2 -0
  133. package/dist/tests/install/remove-if-same.test.d.ts.map +1 -0
  134. package/dist/tests/install/remove-if-same.test.js +298 -0
  135. package/dist/tests/install/remove-if-same.test.js.map +1 -0
  136. package/dist/tests/integration/fan-out-lock-contention.test.d.ts +11 -0
  137. package/dist/tests/integration/fan-out-lock-contention.test.d.ts.map +1 -0
  138. package/dist/tests/integration/fan-out-lock-contention.test.js +53 -0
  139. package/dist/tests/integration/fan-out-lock-contention.test.js.map +1 -0
  140. package/dist/tests/provenance/backfill.test.js +43 -0
  141. package/dist/tests/provenance/backfill.test.js.map +1 -1
  142. package/dist/tests/security/scanner-regression-guard.test.js +33 -5
  143. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  144. package/dist/tests/security/sensitive-path-adversarial-review.test.js +120 -0
  145. package/dist/tests/security/sensitive-path-adversarial-review.test.js.map +1 -1
  146. package/dist/tests/security/sensitive-path-boolean-flag.test.d.ts +2 -0
  147. package/dist/tests/security/sensitive-path-boolean-flag.test.d.ts.map +1 -0
  148. package/dist/tests/security/sensitive-path-boolean-flag.test.js +151 -0
  149. package/dist/tests/security/sensitive-path-boolean-flag.test.js.map +1 -0
  150. package/dist/tests/security/sensitive-path-fp.test.js +71 -5
  151. package/dist/tests/security/sensitive-path-fp.test.js.map +1 -1
  152. package/dist/tests/security/sensitive-path-prefixed-secrets.test.d.ts +2 -0
  153. package/dist/tests/security/sensitive-path-prefixed-secrets.test.d.ts.map +1 -0
  154. package/dist/tests/security/sensitive-path-prefixed-secrets.test.js +176 -0
  155. package/dist/tests/security/sensitive-path-prefixed-secrets.test.js.map +1 -0
  156. package/dist/tests/security/weak-password-lexicon.test.js +14 -4
  157. package/dist/tests/security/weak-password-lexicon.test.js.map +1 -1
  158. package/dist/tests/unit/services/skill-installation.content.test.js +23 -0
  159. package/dist/tests/unit/services/skill-installation.content.test.js.map +1 -1
  160. package/dist/tests/unit/services/skill-installation.io.client-agent-path.test.js +35 -14
  161. package/dist/tests/unit/services/skill-installation.io.client-agent-path.test.js.map +1 -1
  162. package/dist/tests/unit/services/skill-installation.io.rollback.test.d.ts +2 -0
  163. package/dist/tests/unit/services/skill-installation.io.rollback.test.d.ts.map +1 -0
  164. package/dist/tests/unit/services/skill-installation.io.rollback.test.js +984 -0
  165. package/dist/tests/unit/services/skill-installation.io.rollback.test.js.map +1 -0
  166. package/dist/tests/unit/services/skill-installation.io.rollback.unit.test.d.ts +2 -0
  167. package/dist/tests/unit/services/skill-installation.io.rollback.unit.test.d.ts.map +1 -0
  168. package/dist/tests/unit/services/skill-installation.io.rollback.unit.test.js +148 -0
  169. package/dist/tests/unit/services/skill-installation.io.rollback.unit.test.js.map +1 -0
  170. package/dist/tests/unit/services/skill-installation.service.error-codes.test.js +19 -0
  171. package/dist/tests/unit/services/skill-installation.service.error-codes.test.js.map +1 -1
  172. package/dist/tests/unit/services/skill-installation.service.finalize.test.d.ts +2 -0
  173. package/dist/tests/unit/services/skill-installation.service.finalize.test.d.ts.map +1 -0
  174. package/dist/tests/unit/services/skill-installation.service.finalize.test.js +147 -0
  175. package/dist/tests/unit/services/skill-installation.service.finalize.test.js.map +1 -0
  176. package/dist/tests/unit/services/skill-installation.service.test.js +43 -0
  177. package/dist/tests/unit/services/skill-installation.service.test.js.map +1 -1
  178. package/dist/tests/unit/services/skill-installation.target-guard.test.d.ts +2 -0
  179. package/dist/tests/unit/services/skill-installation.target-guard.test.d.ts.map +1 -0
  180. package/dist/tests/unit/services/skill-installation.target-guard.test.js +590 -0
  181. package/dist/tests/unit/services/skill-installation.target-guard.test.js.map +1 -0
  182. package/dist/tests/unit/services/skill-installation.uninstall.guard.test.d.ts +2 -0
  183. package/dist/tests/unit/services/skill-installation.uninstall.guard.test.d.ts.map +1 -0
  184. package/dist/tests/unit/services/skill-installation.uninstall.guard.test.js +473 -0
  185. package/dist/tests/unit/services/skill-installation.uninstall.guard.test.js.map +1 -0
  186. package/dist/tests/utils/safe-fs.test.js +129 -2
  187. package/dist/tests/utils/safe-fs.test.js.map +1 -1
  188. package/package.json +1 -1
@@ -11,60 +11,18 @@
11
11
  *
12
12
  * Every fan-out is recorded in `~/.skillsmith/links/manifest.json` so
13
13
  * `removeLinks(skillId)` can tear down both copies and symlinks atomically
14
- * during uninstall.
14
+ * during uninstall. The manifest itself lives in fan-out.manifest.ts.
15
15
  *
16
16
  * @module @skillsmith/core/install/fan-out
17
17
  */
18
- import { homedir } from 'node:os';
19
18
  import * as path from 'node:path';
20
19
  import * as fs from 'node:fs';
21
20
  import * as fsp from 'node:fs/promises';
22
21
  import { CLIENT_NATIVE_PATHS, CANONICAL_CLIENT } from './paths.js';
23
- import { assertNotRealUserHome } from '../services/skill-manifest.js';
24
- const MANIFEST_VERSION = 1;
25
- /**
26
- * Resolve the manifest file path. Sits under `~/.skillsmith/` so it shares
27
- * the existing allow-list entry for that directory (DEFAULT_ALLOWED_DIRS in
28
- * `pathValidation.ts`).
29
- */
30
- export function getLinkManifestPath() {
31
- return path.join(homedir(), '.skillsmith', 'links', 'manifest.json');
32
- }
33
- /**
34
- * Load the manifest from disk. Returns an empty manifest if the file is
35
- * missing or unreadable — fan-out is best-effort, never crash on cold start.
36
- */
37
- export async function loadManifest() {
38
- const manifestPath = getLinkManifestPath();
39
- try {
40
- const raw = await fsp.readFile(manifestPath, 'utf-8');
41
- const parsed = JSON.parse(raw);
42
- if (parsed?.version !== MANIFEST_VERSION || !Array.isArray(parsed.links)) {
43
- return { version: MANIFEST_VERSION, links: [] };
44
- }
45
- return parsed;
46
- }
47
- catch {
48
- return { version: MANIFEST_VERSION, links: [] };
49
- }
50
- }
51
- /**
52
- * Persist the manifest atomically (write-temp + rename) so a crash mid-write
53
- * never leaves the file in a partial state.
54
- */
55
- export async function saveManifest(manifest) {
56
- const manifestPath = getLinkManifestPath();
57
- // SMI-6343 follow-up (adversarial review): a fourth parallel manifest-write
58
- // implementation (a different file, `links/manifest.json`, but the same
59
- // homedir-derived-with-no-override shape). The $HOME sandbox
60
- // (vitest.setup.ts) was this file's only defense until this guard.
61
- assertNotRealUserHome(manifestPath, 'write');
62
- const dir = path.dirname(manifestPath);
63
- await fsp.mkdir(dir, { recursive: true });
64
- const tmp = `${manifestPath}.${process.pid}.tmp`;
65
- await fsp.writeFile(tmp, JSON.stringify(manifest, null, 2), 'utf-8');
66
- await fsp.rename(tmp, manifestPath);
67
- }
22
+ import { assertOverwritable, leftoverBackupWarning, listLeftoverBackups, recoverDestination, replaceDestination, withDestinationLock, } from './fan-out.overwrite.js';
23
+ import { getLinkManifestPath, loadManifest, readManifestFile, unreadableManifestError, updateManifest, } from './fan-out.manifest.js';
24
+ import { removeRecordedLink, undoUnrecordedWrite } from './fan-out.cleanup.js';
25
+ export { getLinkManifestPath, loadManifest, saveManifest } from './fan-out.manifest.js';
68
26
  /**
69
27
  * Detect a fan-out cycle: refuses to link `from → to` when `to` is already
70
28
  * the source of any entry, OR when `from` is already the destination of any
@@ -110,28 +68,22 @@ async function copyDirectoryRecursive(src, dest) {
110
68
  }
111
69
  }
112
70
  }
113
- async function pathExists(p) {
71
+ /**
72
+ * Round 27 (cross-model review): this returned a plain boolean, so a
73
+ * permission or I/O failure was reported to the user as "does not exist" —
74
+ * sending them to reinstall a skill that is sitting right where they left it.
75
+ * Only absence is absence.
76
+ */
77
+ async function inspectPath(p) {
114
78
  try {
115
79
  await fsp.access(p, fs.constants.F_OK);
116
- return true;
80
+ return { kind: 'present' };
117
81
  }
118
- catch {
119
- return false;
120
- }
121
- }
122
- async function removePath(p) {
123
- try {
124
- const stat = await fsp.lstat(p);
125
- if (stat.isSymbolicLink() || stat.isFile()) {
126
- await fsp.unlink(p);
127
- }
128
- else {
129
- await fsp.rm(p, { recursive: true, force: true });
130
- }
131
- }
132
- catch {
133
- // Best-effort — uninstall should not fail because cleanup races with
134
- // an external editor that already moved the file.
82
+ catch (err) {
83
+ const code = err?.code;
84
+ if (code === 'ENOENT' || code === 'ENOTDIR')
85
+ return { kind: 'absent' };
86
+ return { kind: 'unknown', reason: code ?? (err instanceof Error ? err.message : String(err)) };
135
87
  }
136
88
  }
137
89
  /**
@@ -151,74 +103,223 @@ export async function addLink(opts) {
151
103
  }
152
104
  const fromDir = path.join(CLIENT_NATIVE_PATHS[fromClient], skillId);
153
105
  const toDir = path.join(CLIENT_NATIVE_PATHS[toClient], skillId);
154
- if (!(await pathExists(fromDir))) {
106
+ const source = await inspectPath(fromDir);
107
+ if (source.kind === 'absent') {
155
108
  throw new Error(`addLink: source skill '${skillId}' does not exist at ${fromDir} — install for ${fromClient} first`);
156
109
  }
157
- const manifest = await loadManifest();
158
- const cycle = detectCycle(manifest, fromDir, toDir);
110
+ if (source.kind === 'unknown') {
111
+ throw new Error(`addLink: the source skill at ${fromDir} could not be checked (${source.reason}), so ` +
112
+ `nothing was changed.`);
113
+ }
114
+ const cycle = detectCycle(await loadManifest(), fromDir, toDir);
159
115
  if (cycle)
160
116
  throw new Error(`addLink: ${cycle}`);
161
- if (await pathExists(toDir)) {
162
- if (!force) {
163
- throw new Error(`addLink: ${toDir} already exists. Pass force: true (CLI: --force) to overwrite.`);
164
- }
165
- await removePath(toDir);
166
- }
167
- await fsp.mkdir(path.dirname(toDir), { recursive: true });
168
- let kind = 'copy';
169
- let fellBackToCopy = false;
170
- if (preferSymlink) {
171
- // Use a relative symlink so the manifest entry stays portable across
172
- // homedir changes (matters for users who rsync their home directory).
173
- const relTarget = path.relative(path.dirname(toDir), fromDir);
117
+ // SMI-6529 round 6: everything that reads or writes the destination runs
118
+ // under its lock, so a concurrent call can never touch another call's
119
+ // staging or backup folder (see fan-out.overwrite.ts).
120
+ return withDestinationLock(toDir, async () => {
121
+ // Round 9: fail before writing anything if the manifest can't be read,
122
+ // since the new copy could never be recorded.
123
+ const read = await readManifestFile();
124
+ if (read.state === 'unreadable')
125
+ throw unreadableManifestError(read.reason);
126
+ const manifest = read.manifest;
127
+ const recovery = await recoverDestination(toDir, manifest);
128
+ let existing = null;
174
129
  try {
175
- await fsp.symlink(relTarget, toDir, 'dir');
176
- kind = 'symlink';
130
+ existing = await fsp.lstat(toDir);
177
131
  }
178
132
  catch (err) {
179
- const code = err.code;
180
- if (code === 'EPERM' || code === 'ENOSYS') {
181
- await copyDirectoryRecursive(fromDir, toDir);
182
- kind = 'copy';
183
- fellBackToCopy = true;
133
+ if (err.code !== 'ENOENT')
134
+ throw err;
135
+ }
136
+ if (existing) {
137
+ if (!force) {
138
+ throw new Error(`addLink: ${toDir} already exists. Pass force: true (CLI: --force) to overwrite. ` +
139
+ `If it is an empty directory, an interrupted install may have left it; Skillsmith ` +
140
+ `does not remove it for you, since an empty directory you made looks the same.`);
141
+ }
142
+ // Round 10: say why there's no record, rather than "not recorded".
143
+ if (read.state === 'corrupt' && !existing.isSymbolicLink()) {
144
+ throw new Error(`addLink: ${toDir} already exists, and the fan-out link manifest at ` +
145
+ `${getLinkManifestPath()} could not be parsed, so nothing shows Skillsmith made ` +
146
+ `this copy. Fix or move the manifest, or remove ${toDir} yourself, then retry.`);
147
+ }
148
+ await assertOverwritable(toDir, existing, manifest);
149
+ }
150
+ // Round 11: a symlink is replaced with no backup, so keep its target in
151
+ // case the new record can't be saved and it has to be put back.
152
+ const oldLinkTarget = existing?.isSymbolicLink() ? await fsp.readlink(toDir) : undefined;
153
+ let kind = 'copy';
154
+ let fellBackToCopy = false;
155
+ // The new copy or symlink is written to a staging path and swapped into
156
+ // place; a failure here never touches the existing destination. `placed`
157
+ // is what this call put in place, so an undo only removes that.
158
+ const { placed, warnings: swapWarnings } = await replaceDestination(toDir, async (staged) => {
159
+ if (preferSymlink) {
160
+ // A relative symlink keeps the manifest entry portable across homedir
161
+ // changes. It is computed for the FINAL location, not the staging path.
162
+ const relTarget = path.relative(path.dirname(toDir), fromDir);
163
+ try {
164
+ await fsp.symlink(relTarget, staged, 'dir');
165
+ kind = 'symlink';
166
+ }
167
+ catch (err) {
168
+ const code = err.code;
169
+ if (code === 'EPERM' || code === 'ENOSYS') {
170
+ await copyDirectoryRecursive(fromDir, staged);
171
+ kind = 'copy';
172
+ fellBackToCopy = true;
173
+ }
174
+ else {
175
+ throw err;
176
+ }
177
+ }
184
178
  }
185
179
  else {
186
- throw err;
180
+ await copyDirectoryRecursive(fromDir, staged);
181
+ }
182
+ });
183
+ const record = {
184
+ skillId,
185
+ from: fromDir,
186
+ to: toDir,
187
+ kind,
188
+ createdAt: new Date().toISOString(),
189
+ };
190
+ // SMI-6529 N7: a force-overwrite of an already-recorded destination
191
+ // replaces its manifest entry rather than appending a second one.
192
+ const resolvedToDir = path.resolve(toDir);
193
+ let manifestWarning;
194
+ try {
195
+ manifestWarning = await updateManifest((current) => {
196
+ current.links = current.links.filter((l) => path.resolve(l.to) !== resolvedToDir);
197
+ current.links.push(record);
198
+ });
199
+ }
200
+ catch (err) {
201
+ if (!existing || oldLinkTarget !== undefined) {
202
+ await undoUnrecordedWrite(toDir, err, oldLinkTarget, placed);
203
+ }
204
+ throw err;
205
+ }
206
+ const leftovers = await listLeftoverBackups(toDir);
207
+ const warnings = [
208
+ ...(manifestWarning ? [manifestWarning] : []),
209
+ ...swapWarnings,
210
+ ...(recovery.unreadable ? [recovery.unreadable] : []),
211
+ ...(leftovers.unreadable ? [leftovers.unreadable] : []),
212
+ ...leftovers.folders.map(leftoverBackupWarning),
213
+ ];
214
+ return warnings.length > 0 ? { record, fellBackToCopy, warnings } : { record, fellBackToCopy };
215
+ });
216
+ }
217
+ /**
218
+ * Warning for an uninstall that couldn't use the manifest (rounds 11–12). By
219
+ * then the canonical skill is gone, so a second uninstall stops at "not
220
+ * installed" and never gets here: any fan-out copy left is the user's to
221
+ * remove. List the ones on disk so they know where to look.
222
+ */
223
+ async function unusableManifestWarning(read, skillId) {
224
+ const why = read.state === 'corrupt' ? 'could not be parsed' : `could not be read (${read.reason})`;
225
+ const canonical = path.resolve(CLIENT_NATIVE_PATHS[CANONICAL_CLIENT]);
226
+ const candidates = [];
227
+ const unreadable = [];
228
+ for (const root of new Set(Object.values(CLIENT_NATIVE_PATHS))) {
229
+ if (path.resolve(root) === canonical)
230
+ continue;
231
+ const dir = path.join(root, skillId);
232
+ try {
233
+ await fsp.lstat(dir);
234
+ candidates.push(dir);
235
+ }
236
+ catch (err) {
237
+ // Round 27 (cross-model review): a folder that could not be inspected is
238
+ // not a folder that isn't there, and this message went on to promise
239
+ // there was "nothing to clean up".
240
+ const code = err?.code;
241
+ if (code !== 'ENOENT' && code !== 'ENOTDIR') {
242
+ unreadable.push(`${dir} (${code ?? (err instanceof Error ? err.message : String(err))})`);
187
243
  }
188
244
  }
189
245
  }
190
- else {
191
- await copyDirectoryRecursive(fromDir, toDir);
192
- }
193
- const record = {
194
- skillId,
195
- from: fromDir,
196
- to: toDir,
197
- kind,
198
- createdAt: new Date().toISOString(),
199
- };
200
- manifest.links.push(record);
201
- await saveManifest(manifest);
202
- return { record, fellBackToCopy };
246
+ const found = candidates.length > 0
247
+ ? ` These may be fan-out copies Skillsmith made; check each one and delete it yourself ` +
248
+ `if you don't need it: ${candidates.join(', ')}.`
249
+ : '';
250
+ const couldNotCheck = unreadable.length > 0
251
+ ? ` These could not be checked, so anything under them is not reported here: ` +
252
+ `${unreadable.join(', ')}.`
253
+ : '';
254
+ const next = found === '' && couldNotCheck === ''
255
+ ? ` No other client's skills folder has a ${skillId} folder, so there is nothing to clean up.`
256
+ : `${found}${couldNotCheck}`;
257
+ return (`the fan-out link manifest at ${getLinkManifestPath()} ${why}, so no fan-out copies of ` +
258
+ `${skillId} were checked or removed.${next}`);
203
259
  }
204
260
  /**
205
- * Remove every fan-out link recorded for `skillId`. Returns the number of
206
- * destinations removed. Safe to call when no manifest exists (returns 0).
261
+ * Remove every fan-out link recorded for `skillId`. Safe to call when no
262
+ * manifest exists (returns `{ removed: 0, refused: [] }`).
207
263
  *
208
264
  * Uninstall callers should invoke this BEFORE removing the canonical
209
265
  * directory so symlinks resolve cleanly during their lstat checks.
266
+ *
267
+ * SMI-6529 N6 (round 4): a destination `removeRecordedLink` refuses (a
268
+ * recorded copy that now contains `.git` at its root, or a removal error)
269
+ * keeps its manifest entry instead of being unconditionally dropped — the
270
+ * old unconditional `manifest.links = manifest.links.filter(...)` discarded
271
+ * the record for EVERY matching link regardless of whether the on-disk
272
+ * removal actually succeeded, so a refused destination could never be
273
+ * retried and its refusal was never reported anywhere.
210
274
  */
211
275
  export async function removeLinks(skillId) {
212
- const manifest = await loadManifest();
213
- const matching = manifest.links.filter((l) => l.skillId === skillId);
276
+ const read = await readManifestFile();
277
+ // Round 11: an unusable manifest used to read as empty, so an uninstall
278
+ // said nothing and left every fan-out copy behind. Say so instead, and
279
+ // leave the file alone.
280
+ if (read.state === 'unreadable' || read.state === 'corrupt') {
281
+ return { removed: 0, refused: [], warnings: [await unusableManifestWarning(read, skillId)] };
282
+ }
283
+ const matching = read.manifest.links.filter((l) => l.skillId === skillId);
214
284
  if (matching.length === 0)
215
- return 0;
285
+ return { removed: 0, refused: [] };
286
+ const refused = [];
287
+ const warnings = [];
288
+ const removedTargets = new Set();
216
289
  for (const link of matching) {
217
- await removePath(link.to);
290
+ // SMI-6529 round 6: same per-destination lock as addLink. The record is
291
+ // dropped while that lock is still held (round 10): filtering after the
292
+ // loop also dropped a record a later re-link had saved, leaving its copy
293
+ // untracked. Round 11: drop the records the manifest holds for this
294
+ // destination now, not the one read above; a force refresh that ran
295
+ // first had replaced it, and dropping only the old one left a record for
296
+ // the folder just removed. Every writer of a destination's records holds
297
+ // its lock, so this read is current.
298
+ const resolvedTo = path.resolve(link.to);
299
+ const step = await withDestinationLock(link.to, async () => {
300
+ const outcome = await removeRecordedLink(link.to);
301
+ const manifestWarning = outcome.removed
302
+ ? await updateManifest((current) => {
303
+ current.links = current.links.filter((l) => l.skillId !== skillId || path.resolve(l.to) !== resolvedTo);
304
+ })
305
+ : undefined;
306
+ return { outcome, manifestWarning, leftovers: await listLeftoverBackups(link.to) };
307
+ });
308
+ if (step.manifestWarning)
309
+ warnings.push(step.manifestWarning);
310
+ if (step.outcome.removed) {
311
+ removedTargets.add(link.to);
312
+ }
313
+ else {
314
+ refused.push({ to: link.to, reason: step.outcome.reason });
315
+ }
316
+ if (step.leftovers.unreadable)
317
+ warnings.push(step.leftovers.unreadable);
318
+ warnings.push(...step.leftovers.folders.map(leftoverBackupWarning));
218
319
  }
219
- manifest.links = manifest.links.filter((l) => l.skillId !== skillId);
220
- await saveManifest(manifest);
221
- return matching.length;
320
+ return warnings.length > 0
321
+ ? { removed: removedTargets.size, refused, warnings }
322
+ : { removed: removedTargets.size, refused };
222
323
  }
223
324
  /**
224
325
  * List all fan-out links for a skill (or all skills when `skillId` is
@@ -1 +1 @@
1
- {"version":3,"file":"fan-out.js","sourceRoot":"","sources":["../../../src/install/fan-out.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fan-out.js","sourceRoot":"","sources":["../../../src/install/fan-out.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import type { Stats } from 'node:fs';
2
+ import type { LinkManifest } from './fan-out.manifest.js';
3
+ /** What a leftover scan found, and whether it could look at all. */
4
+ export interface LeftoverScan {
5
+ /** Hidden folders beside the destination that an interrupted operation left. */
6
+ folders: string[];
7
+ /**
8
+ * Set when the parent folder could not be listed. Round 24 (cross-model
9
+ * review): returning an empty list then reports "could not look" as "nothing
10
+ * is there", and the accepted residual's promise is that a displaced entry
11
+ * gets reported.
12
+ */
13
+ unreadable?: string;
14
+ }
15
+ export declare const BACKUP_TAG = ".skillsmith-backup-";
16
+ export declare const STAGING_TAG = ".skillsmith-staging-";
17
+ export declare function escapeRegExp(s: string): string;
18
+ /** Hidden sibling name prefix for `dest`, e.g. `.foo.skillsmith-backup-`. */
19
+ export declare function siblingPrefix(dest: string, tag: string): string;
20
+ /** Exact match for a `mkdtemp` folder made from `siblingPrefix(dest, tag)` (6-char suffix). */
21
+ export declare function siblingPattern(dest: string, tag: string): RegExp;
22
+ export declare function lstatOrNull(p: string): Promise<Stats | null>;
23
+ /** Whether the link manifest records `dest` as a copy Skillsmith made. */
24
+ export declare function isRecordedCopy(dest: string, manifest: LinkManifest): boolean;
25
+ /**
26
+ * Housekeeping under the destination lock, before any write:
27
+ * - remove an empty backup folder (a crash before anything was moved in);
28
+ * - restore a backup stranded by a crash between the rename-aside and the
29
+ * swap: the destination is missing, this is the only backup whose
30
+ * `original` is a real directory, and the manifest still records the
31
+ * destination as a copy. Backups are only ever made of recorded copies,
32
+ * so an unrecorded one has been uninstalled since, and restoring it would
33
+ * bring back a stale, untracked copy. With two or more, nothing says which
34
+ * is newest (a name can sort anywhere), so none is restored (round 9).
35
+ *
36
+ * Any other backup is left in place for `listLeftoverBackups` to report, and
37
+ * so is a staging folder left by a crashed write: round 14 (cross-model
38
+ * review) stopped deleting those here, since neither a folder's name nor its
39
+ * contents proves who owns it now. Returns the backup folders restored from.
40
+ */
41
+ export declare function recoverDestination(dest: string, manifest: LinkManifest): Promise<{
42
+ restored: string[];
43
+ unreadable?: string;
44
+ }>;
45
+ /**
46
+ * Backup folders for `dest` that are still present: copies an interrupted
47
+ * refresh left behind (after the swap, or unrestorable). Round 7: these are
48
+ * reported to the user rather than kept silently or deleted. Round 8: an
49
+ * empty one holds nothing to report and is removed. Round 9: on a volume
50
+ * that ignores case, a backup made under another spelling of the name
51
+ * (`.Foo.` for `foo`) belongs to this destination too, so it is reported;
52
+ * it is never removed or restored, since on a case-sensitive volume the
53
+ * same name belongs to a different destination. Round 14: a staging folder
54
+ * left by a crashed write is reported the same way. Round 15: so is what a
55
+ * crashed or failed removal left under a parked name. Call under the lock.
56
+ */
57
+ export declare function listLeftoverBackups(dest: string): Promise<LeftoverScan>;
58
+ /** User-facing warning for a leftover backup, staging folder or parked entry. */
59
+ export declare function leftoverBackupWarning(folder: string): string;
60
+ //# sourceMappingURL=fan-out.leftovers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fan-out.leftovers.d.ts","sourceRoot":"","sources":["../../../src/install/fan-out.leftovers.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAGzD,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,gFAAgF;IAChF,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAqBD,eAAO,MAAM,UAAU,wBAAwB,CAAA;AAC/C,eAAO,MAAM,WAAW,yBAAyB,CAAA;AAEjD,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,+FAA+F;AAC/F,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhE;AAiBD,wBAAsB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAOlE;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,OAAO,CAG5E;AAgBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8CtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CA8B7E;AAiCD,iFAAiF;AACjF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAoB5D"}