@skillsmith/core 0.12.3 → 0.12.4

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 +164 -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
@@ -0,0 +1,410 @@
1
+ /**
2
+ * @fileoverview Pre-write install-target safety guard.
3
+ * @module @skillsmith/core/services/skill-installation.target-guard
4
+ * @see SMI-6529 Wave A0
5
+ *
6
+ * On a real user machine, `skillsmith update --all` overwrote uncommitted
7
+ * local work in git-cloned skill directories and wrote into the wrong
8
+ * directory (a target named after the upstream repo, not the directory
9
+ * being updated). `checkInstallTarget()` is the single pre-write gate every
10
+ * `install()`/`installFromContent()` call runs BEFORE any content fetch or
11
+ * disk write — it never mutates anything itself, only inspects the
12
+ * filesystem and the manifest and reports whether the write may proceed.
13
+ *
14
+ * Rules are evaluated in order; the first that fails wins:
15
+ * (pre) A matching manifest entry's `installPath` must be a usable, real
16
+ * absolute path — a missing or non-absolute value refuses rather than
17
+ * reaching `path.resolve()`/`fs.realpath()` (SMI-6529 L17).
18
+ * (a) `expectedInstallPath` (set by `update`) must match `installPath`
19
+ * exactly, AND must exist on disk — `force` does NOT override this.
20
+ * (b) `installPath` missing (ENOENT) -> fresh install, nothing else to
21
+ * check, UNLESS the manifest key it will occupy already has an entry
22
+ * whose recorded path is a live conflict (SMI-6529 M8) — see below.
23
+ * Any other `lstat` error is rethrown (fail closed).
24
+ * (c) `installPath` must be a directory, or a symlink that resolves to a
25
+ * directory inside `skillsDir`.
26
+ * (d) Filesystem-only git-working-tree check (never spawns git): a `.git`
27
+ * entry (file or directory) at `installPath` or any ancestor up to and
28
+ * including `skillsDir` refuses the write — `force` does NOT override
29
+ * this. Walks BOTH the lexical ancestor chain and (SMI-6529 H1) the
30
+ * REALPATH ancestor chain, since a symlinked `installPath` can resolve
31
+ * into a git clone whose ancestry `path.dirname()` on the symlink's own
32
+ * lexical string never crosses into.
33
+ * (e) The manifest must already track this exact path (by realpath) —
34
+ * an untracked pre-existing directory is refused — `force` does NOT
35
+ * override this either. Even a path-matching entry refuses if it is
36
+ * itself untracked by Skillsmith's trust model (`provenance:'local'`
37
+ * or `source:'unknown'` — ADR-139 adoption or a user's local
38
+ * assertion) — `force` does NOT override this either.
39
+ * (f) Tracked + matching path: existing `ALREADY_INSTALLED` behavior
40
+ * (refuse without `force`, proceed with it).
41
+ */
42
+ import * as fs from 'fs/promises';
43
+ import * as path from 'path';
44
+ /** `fs.realpath`, falling back to a lexical `path.resolve` when the path can't be resolved
45
+ * (e.g. it doesn't exist, or a component was removed mid-check) — never throws. */
46
+ async function resolveRealOrFallback(target) {
47
+ try {
48
+ return await fs.realpath(target);
49
+ }
50
+ catch {
51
+ return path.resolve(target);
52
+ }
53
+ }
54
+ async function pathHasGitEntry(dir) {
55
+ try {
56
+ // lstat (not stat): a `.git` FILE (worktree pointer) or directory both count,
57
+ // and we don't want a `.git` symlink silently resolved away.
58
+ await fs.lstat(path.join(dir, '.git'));
59
+ return 'found';
60
+ }
61
+ catch (err) {
62
+ const code = err.code;
63
+ if (code === 'ENOENT' || code === 'ENOTDIR')
64
+ return 'absent';
65
+ // SMI-6529 L16: fail CLOSED — an lstat error we can't interpret as
66
+ // "definitely absent" (EACCES, EIO, an unreadable ancestor, etc.) must
67
+ // never silently read as "no .git here." The CALLER still refuses the
68
+ // write (same as a real hit) so it never guesses wrong in the data-loss
69
+ // direction — but the message says which is which (#16).
70
+ return { errorCode: code ?? 'UNKNOWN' };
71
+ }
72
+ }
73
+ /** Walk from `startAbs` up through `stopAtAbs` (inclusive), returning the first
74
+ * directory with a `.git` entry (or the first unverifiable one), or `null` if
75
+ * none is found before `stopAtAbs`. */
76
+ async function walkForGitEntry(startAbs, stopAtAbs) {
77
+ let current = startAbs;
78
+ // Bound the walk: every real caller builds installPath as a descendant of
79
+ // skillsDir (so this always terminates via the `current === stopAtAbs`
80
+ // check below), but a future caller that doesn't must never loop forever.
81
+ for (let i = 0; i < 64; i++) {
82
+ const check = await pathHasGitEntry(current);
83
+ if (check === 'found')
84
+ return { kind: 'found', path: current };
85
+ if (typeof check === 'object')
86
+ return { kind: 'error', path: current, errorCode: check.errorCode };
87
+ if (current === stopAtAbs)
88
+ return null;
89
+ const parent = path.dirname(current);
90
+ if (parent === current)
91
+ return null; // filesystem root — stopAtAbs was never an ancestor
92
+ current = parent;
93
+ }
94
+ return null;
95
+ }
96
+ /**
97
+ * Walk from `installPath` up through its ancestors, inclusive of both
98
+ * `installPath` and `skillsDir` (never above `skillsDir`), checking each
99
+ * level for a `.git` entry. Filesystem-only — never spawns `git`. Returns the
100
+ * directory the `.git` entry was found in (for SMI-6529 L13's tailored tip),
101
+ * or `null` if none was found.
102
+ *
103
+ * SMI-6529 H1: a symlinked `installPath` (e.g. a fan-out symlink, or one
104
+ * planted by an attacker) can resolve into a git clone living elsewhere
105
+ * inside `skillsDir` — walking the LEXICAL chain alone misses this, because
106
+ * `path.dirname()` on the symlink's own path string jumps straight to the
107
+ * symlink's own lexical parent, never through the target it points at (only
108
+ * the FINAL path segment of any one `fs` call is exempt from symlink
109
+ * resolution; the "climb to parent" step here works on the string alone).
110
+ * Concretely: `skillsDir/pdf` -> `skillsDir/clone/docs/pdf` where `clone` (not
111
+ * `skillsDir/pdf`'s own lexical parent) has `.git` — only a walk starting from
112
+ * `installPath`'s REALPATH ever visits `clone`. Bounded by `skillsDir`'s own
113
+ * realpath (never climbs above it) — safe because rule (c) has already
114
+ * proven a symlinked `installPath`'s realpath resolves inside (real)
115
+ * `skillsDir` before this ever runs.
116
+ */
117
+ async function hasGitAncestorBetween(installPath, skillsDir) {
118
+ const resolvedSkillsDir = path.resolve(skillsDir);
119
+ const resolvedInstall = path.resolve(installPath);
120
+ const lexicalHit = await walkForGitEntry(resolvedInstall, resolvedSkillsDir);
121
+ if (lexicalHit)
122
+ return lexicalHit;
123
+ const realInstall = await resolveRealOrFallback(installPath);
124
+ if (realInstall === resolvedInstall)
125
+ return null; // no symlink involved — already covered above
126
+ const realSkillsDir = await resolveRealOrFallback(skillsDir);
127
+ return walkForGitEntry(realInstall, realSkillsDir);
128
+ }
129
+ /** Rule (c): `installPath` is a usable directory, either directly or via a symlink
130
+ * that resolves to a directory inside `skillsDir`. */
131
+ async function isUsableDirectory(installPath, skillsDir, stats) {
132
+ if (stats.isDirectory())
133
+ return true;
134
+ if (!stats.isSymbolicLink())
135
+ return false;
136
+ try {
137
+ const followed = await fs.stat(installPath);
138
+ if (!followed.isDirectory())
139
+ return false;
140
+ // SMI-4758: resolve BOTH sides through the identical helper (never a raw
141
+ // fs.realpath on one side and a fallback-guarded resolve on the other) —
142
+ // asymmetric methodology is exactly the bug class this comparison must
143
+ // avoid, not merely a lint-satisfying detail.
144
+ const real = await resolveRealOrFallback(installPath);
145
+ const resolvedSkillsDir = await resolveRealOrFallback(skillsDir);
146
+ return real === resolvedSkillsDir || real.startsWith(resolvedSkillsDir + path.sep);
147
+ }
148
+ catch {
149
+ return false; // broken symlink or unreadable target
150
+ }
151
+ }
152
+ /**
153
+ * SMI-6529 L17: a manifest entry's `installPath` is typed as required but
154
+ * "runtime JSON may omit it" (see `SkillManifestEntry.installPath`'s own doc
155
+ * comment). Every rule below that reads it (rule (e)'s tracked-path
156
+ * comparison, and (b)'s M8 stale-vs-live check) needs it to be a real
157
+ * absolute path — otherwise it would reach `path.resolve()`/`fs.realpath()`
158
+ * and either throw a raw TypeError (on `undefined`) or silently resolve
159
+ * against `process.cwd()` (on a relative string) instead of surfacing a
160
+ * clear, structured refusal.
161
+ */
162
+ function hasUsableInstallPath(entry) {
163
+ return typeof entry.installPath === 'string' && path.isAbsolute(entry.installPath);
164
+ }
165
+ /** SMI-6529 M8: does `p` still exist on disk? Fails CLOSED on an unexpected
166
+ * lstat error (EACCES, etc.) — treated as "still there" so the caller refuses
167
+ * rather than guessing a manifest conflict away. */
168
+ async function pathStillExists(p) {
169
+ try {
170
+ await fs.lstat(p);
171
+ return true;
172
+ }
173
+ catch (err) {
174
+ if (err.code === 'ENOENT')
175
+ return false;
176
+ return true;
177
+ }
178
+ }
179
+ /**
180
+ * The pre-write gate: decide, without changing anything, whether an install
181
+ * or update may write into `installPath`. The rules (pre) and (a)–(f) in the
182
+ * module comment are checked in order, and the first to fail wins. Callers
183
+ * run it before any content fetch or disk write.
184
+ */
185
+ export async function checkInstallTarget(params) {
186
+ const { installPath, skillsDir, manifestEntry, force, expectedInstallPath } = params;
187
+ // (pre) SMI-6529 L17 — see hasUsableInstallPath's doc comment.
188
+ if (manifestEntry !== undefined && !hasUsableInstallPath(manifestEntry)) {
189
+ return {
190
+ ok: false,
191
+ code: 'INSTALL_TARGET_UNTRACKED',
192
+ error: 'Manifest entry for "' +
193
+ path.basename(installPath) +
194
+ '" has a missing or invalid installPath; refusing to trust it.',
195
+ tips: ['Repair the manifest entry: apply_manifest_reconcile'],
196
+ };
197
+ }
198
+ // (a) `update` records the exact directory it diffed against — refuse to
199
+ // write anywhere else, even under force. SMI-4758/F6: resolve BOTH sides
200
+ // through the identical helper (never a raw path.resolve on one side and a
201
+ // realpath'd value on the other) — a symlinked path spelling (macOS
202
+ // /var -> /private/var) must never produce a false mismatch.
203
+ if (expectedInstallPath !== undefined) {
204
+ const resolvedExpected = await resolveRealOrFallback(expectedInstallPath);
205
+ const resolvedInstall = await resolveRealOrFallback(installPath);
206
+ if (resolvedExpected !== resolvedInstall) {
207
+ return {
208
+ ok: false,
209
+ code: 'INSTALL_TARGET_MISMATCH',
210
+ error: 'Update would write to "' +
211
+ installPath +
212
+ '", but the skill being updated lives at "' +
213
+ expectedInstallPath +
214
+ '"; refusing.',
215
+ };
216
+ }
217
+ // SMI-6529 M5: the path MATCHES, but if it doesn't exist on disk at all,
218
+ // `update` is not "reinstalling the directory it compared against" — it
219
+ // would silently CREATE a brand-new directory at a path whose skill was
220
+ // deleted or moved after the diff ran. Refuse explicitly instead of
221
+ // falling through to rule (b)'s fresh-ENOENT "ok" path, which exists for
222
+ // a genuinely NEW install, not an update.
223
+ if (!(await pathStillExists(expectedInstallPath))) {
224
+ return {
225
+ ok: false,
226
+ code: 'INSTALL_TARGET_MISMATCH',
227
+ error: 'Update target "' +
228
+ expectedInstallPath +
229
+ '" no longer exists on disk; refusing to create a new directory there.',
230
+ };
231
+ }
232
+ }
233
+ // (b)
234
+ let stats;
235
+ try {
236
+ stats = await fs.lstat(installPath);
237
+ }
238
+ catch (err) {
239
+ if (err.code === 'ENOENT') {
240
+ // SMI-6529 M8: a fresh (recomputed) install path is only safe to treat
241
+ // as a brand-new install when the manifest key it will occupy is
242
+ // either unclaimed or points at a directory that's ALSO gone (the
243
+ // round-1 F8-documented stale-entry case). If the SAME manifest key's
244
+ // entry still points at a DIFFERENT directory that (a) still exists on
245
+ // disk, or (b) is marked provenance:'local' (a user assertion that
246
+ // must never be silently superseded, regardless of whether its
247
+ // recorded directory still exists), this is a real conflict — refuse
248
+ // rather than silently adopting the key for an unrelated new install.
249
+ if (manifestEntry) {
250
+ if (manifestEntry.provenance === 'local') {
251
+ return {
252
+ ok: false,
253
+ code: 'INSTALL_TARGET_UNTRACKED',
254
+ error: 'Manifest entry for "' +
255
+ path.basename(installPath) +
256
+ '" is marked local / not installed by Skillsmith; refusing to reuse it for a new install.',
257
+ tips: [
258
+ 'If this is actually a registry skill, relink it first: apply_manifest_reconcile (action: relink)',
259
+ ],
260
+ };
261
+ }
262
+ if (await pathStillExists(manifestEntry.installPath)) {
263
+ return {
264
+ ok: false,
265
+ code: 'INSTALL_TARGET_MISMATCH',
266
+ error: 'Manifest entry for "' +
267
+ path.basename(installPath) +
268
+ '" already points at "' +
269
+ manifestEntry.installPath +
270
+ '", which still exists; refusing to install at a different path under the same tracked name.',
271
+ };
272
+ }
273
+ // Stale entry: recorded path is gone and not marked local — the
274
+ // documented round-1 (F8) behavior — proceed as a fresh install,
275
+ // reusing this manifest key.
276
+ }
277
+ return { ok: true, preExisted: false };
278
+ }
279
+ throw err; // fail closed — do not guess at an unexpected lstat error
280
+ }
281
+ // (c)
282
+ if (!(await isUsableDirectory(installPath, skillsDir, stats))) {
283
+ return {
284
+ ok: false,
285
+ code: 'INSTALL_TARGET_NOT_DIRECTORY',
286
+ error: 'Install target "' + installPath + '" exists but is not a directory.',
287
+ };
288
+ }
289
+ // SMI-6529 N5 (round 4): containment — a CALLER (e.g. a pre-flight that
290
+ // computed `installPath` from a different client's manifest entry than the
291
+ // `skillsDir` it passed) could hand this function an `installPath` that
292
+ // isn't actually inside `skillsDir` at all. Without this check, rule (d)'s
293
+ // ancestor walk below never meets `skillsDir` and silently climbs all the
294
+ // way to the filesystem root. Valid if EITHER the lexical OR the realpath
295
+ // comparison holds — matching rule (c)'s own permissiveness for a
296
+ // legitimate symlink whose target resolves inside `skillsDir` even though
297
+ // its own lexical spelling doesn't; refuse only when BOTH fail.
298
+ const resolvedInstallForContainment = path.resolve(installPath);
299
+ const resolvedSkillsDirForContainment = path.resolve(skillsDir);
300
+ const lexicallyContained = resolvedInstallForContainment === resolvedSkillsDirForContainment ||
301
+ resolvedInstallForContainment.startsWith(resolvedSkillsDirForContainment + path.sep);
302
+ if (!lexicallyContained) {
303
+ const realInstallForContainment = await resolveRealOrFallback(installPath);
304
+ const realSkillsDirForContainment = await resolveRealOrFallback(skillsDir);
305
+ const reallyContained = realInstallForContainment === realSkillsDirForContainment ||
306
+ realInstallForContainment.startsWith(realSkillsDirForContainment + path.sep);
307
+ if (!reallyContained) {
308
+ // Round 5: MISMATCH, not NOT_DIRECTORY — the target may well be a
309
+ // directory; what's wrong is that it isn't where installs belong.
310
+ return {
311
+ ok: false,
312
+ code: 'INSTALL_TARGET_MISMATCH',
313
+ error: 'Install target "' +
314
+ installPath +
315
+ '" is not inside skills directory "' +
316
+ skillsDir +
317
+ '"; refusing.',
318
+ };
319
+ }
320
+ }
321
+ // (d) Filesystem-only — never spawns git. `force` does NOT override this.
322
+ const gitHit = await hasGitAncestorBetween(installPath, skillsDir);
323
+ if (gitHit) {
324
+ // SMI-6529 #16 (round 4): a fail-closed "couldn't verify" (EACCES, etc.)
325
+ // gets an ACCURATE message naming the real reason, never the false claim
326
+ // that a git working tree was actually found — L16's refusal itself is
327
+ // unchanged (same code, still refuses).
328
+ if (gitHit.kind === 'error') {
329
+ return {
330
+ ok: false,
331
+ code: 'INSTALL_TARGET_GIT_WORKTREE',
332
+ error: 'Could not check "' +
333
+ gitHit.path +
334
+ '" for a git repository: ' +
335
+ gitHit.errorCode +
336
+ '. Refusing to proceed.',
337
+ };
338
+ }
339
+ // SMI-6529 L13: tailor the message/tip depending on whether `.git` was
340
+ // found AT installPath itself vs. an ancestor (or skillsDir) — naming
341
+ // the actual git root makes the remediation command runnable as-is
342
+ // instead of pointed at a directory that isn't really the clone root.
343
+ const resolvedInstall = path.resolve(installPath);
344
+ const realInstall = await resolveRealOrFallback(installPath);
345
+ const atInstallItself = gitHit.path === resolvedInstall || gitHit.path === realInstall;
346
+ return {
347
+ ok: false,
348
+ code: 'INSTALL_TARGET_GIT_WORKTREE',
349
+ error: atInstallItself
350
+ ? 'Install target "' + installPath + '" is a git working tree — refusing to overwrite it.'
351
+ : 'Install target "' +
352
+ installPath +
353
+ '" lives inside a git working tree rooted at "' +
354
+ gitHit.path +
355
+ '" — refusing to overwrite it.',
356
+ tips: [
357
+ atInstallItself
358
+ ? 'This skill is a git clone. Update it with: git -C "' + installPath + '" pull --ff-only'
359
+ : 'This skill lives inside a git clone rooted at "' +
360
+ gitHit.path +
361
+ '". Update it with: git -C "' +
362
+ gitHit.path +
363
+ '" pull --ff-only',
364
+ ],
365
+ };
366
+ }
367
+ // (e) `force` does NOT override this either.
368
+ const realInstall = await resolveRealOrFallback(installPath);
369
+ const realEntry = manifestEntry ? await resolveRealOrFallback(manifestEntry.installPath) : null;
370
+ if (!manifestEntry || realEntry !== realInstall) {
371
+ return {
372
+ ok: false,
373
+ code: 'INSTALL_TARGET_UNTRACKED',
374
+ error: 'A directory already exists at "' +
375
+ installPath +
376
+ '" that Skillsmith didn\'t install (or the manifest entry points elsewhere); refusing to overwrite it.',
377
+ tips: ['Move the existing directory aside, then retry', 'Or remove it, then retry'],
378
+ };
379
+ }
380
+ // SMI-6529 F5 / ADR-155 §3+§5: a path-matching entry that is ITSELF
381
+ // untracked by Skillsmith's own trust model — an ADR-139 adoption
382
+ // (source:'unknown') or a user's explicit provenance:'local' assertion —
383
+ // must still refuse, even though the path matches. `force` means "reinstall
384
+ // what Skillsmith installed," not "overwrite an adopted or local skill";
385
+ // `force` does NOT override this.
386
+ if (manifestEntry.provenance === 'local' || manifestEntry.source === 'unknown') {
387
+ return {
388
+ ok: false,
389
+ code: 'INSTALL_TARGET_UNTRACKED',
390
+ error: 'Skill at "' +
391
+ installPath +
392
+ '" is marked local / not installed by Skillsmith; refusing to overwrite it.',
393
+ tips: [
394
+ 'If this is actually a registry skill, relink it first: apply_manifest_reconcile (action: relink)',
395
+ ],
396
+ };
397
+ }
398
+ // (f)
399
+ if (!force) {
400
+ return {
401
+ ok: false,
402
+ code: 'ALREADY_INSTALLED',
403
+ error: 'Skill "' +
404
+ path.basename(installPath) +
405
+ '" is already installed. Use force=true to reinstall.',
406
+ };
407
+ }
408
+ return { ok: true, preExisted: true };
409
+ }
410
+ //# sourceMappingURL=skill-installation.target-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-installation.target-guard.js","sourceRoot":"","sources":["../../../src/services/skill-installation.target-guard.ts"],"names":[],"mappings":"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"}
@@ -61,6 +61,18 @@ export interface InstallOptions {
61
61
  conflictAction?: ConflictAction;
62
62
  /** SMI-3863: User has confirmed they want to install despite security warnings */
63
63
  confirmed?: boolean;
64
+ /**
65
+ * SMI-6529 Wave A0: set by `update` to the exact directory it diffed
66
+ * against (the manifest entry's `installPath`) — `install()` refuses to
67
+ * write anywhere else, even with `force: true`. Closes the "repo name !=
68
+ * install directory name" data-loss class (e.g. a registry lookup or a
69
+ * raw-URL `repo` resolving to a DIFFERENT on-disk name than the directory
70
+ * `update` actually compared), where a force-reinstall silently wrote a
71
+ * second, wrongly-named directory instead of overwriting the one the user
72
+ * meant to update. Omitted by a plain `install()` call, which has no
73
+ * "directory it compared against" to enforce.
74
+ */
75
+ expectedInstallPath?: string;
64
76
  }
65
77
  /** Dependency intelligence result from an install */
66
78
  export interface DepIntelResult {
@@ -97,7 +109,7 @@ export interface OptimizationInfo {
97
109
  * stable across releases. Adding a new code requires (a) a real new failure
98
110
  * return path and (b) updating downstream funnel queries.
99
111
  */
100
- export type InstallErrorCode = 'REGISTRY_LOOKUP_UNAVAILABLE' | 'REGISTRY_SKILL_NOT_FOUND' | 'QUARANTINED' | 'ALREADY_INSTALLED' | 'FETCH_FAILED' | 'VALIDATION_FAILED' | 'SKIP_SCAN_FORBIDDEN' | 'SCAN_REJECTED' | 'CONFIRMATION_REQUIRED' | 'INVALID_CONTENT' | 'UNKNOWN';
112
+ export type InstallErrorCode = 'REGISTRY_LOOKUP_UNAVAILABLE' | 'REGISTRY_SKILL_NOT_FOUND' | 'QUARANTINED' | 'ALREADY_INSTALLED' | 'FETCH_FAILED' | 'VALIDATION_FAILED' | 'SKIP_SCAN_FORBIDDEN' | 'SCAN_REJECTED' | 'CONFIRMATION_REQUIRED' | 'INVALID_CONTENT' | 'INSTALL_TARGET_MISMATCH' | 'INSTALL_TARGET_NOT_DIRECTORY' | 'INSTALL_TARGET_GIT_WORKTREE' | 'INSTALL_TARGET_UNTRACKED' | 'UNKNOWN';
101
113
  /** Result of an install operation */
102
114
  export interface InstallResult {
103
115
  success: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"skill-installation.types.d.ts","sourceRoot":"","sources":["../../../src/services/skill-installation.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skill-installation.types.d.ts","sourceRoot":"","sources":["../../../src/services/skill-installation.types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"skill-installation.types.js","sourceRoot":"","sources":["../../../src/services/skill-installation.types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA0SH,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,iDAAiD;AACjD,MAAM,CAAC,MAAM,0BAA0B,GAAsC;IAC3E,QAAQ,EAAE;QACR,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,SAAS;KAC5B;IACD,OAAO,EAAE;QACP,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,SAAS;KAC5B;IACD,SAAS,EAAE;QACT,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,SAAS;KAC5B;IACD,KAAK,EAAE;QACL,aAAa,EAAE,GAAG;QAClB,gBAAgB,EAAE,UAAU;KAC7B;IACD,YAAY,EAAE;QACZ,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,OAAO;KAC1B;IACD,OAAO,EAAE;QACP,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,OAAO;KAC1B;IACD,6BAA6B;IAC7B,QAAQ,EAAE;QACR,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,SAAS;KAC5B;IACD,UAAU,EAAE;QACV,aAAa,EAAE,EAAE,EAAE,+DAA+D;QAClF,gBAAgB,EAAE,OAAO;KAC1B;CACF,CAAA"}
1
+ {"version":3,"file":"skill-installation.types.js","sourceRoot":"","sources":["../../../src/services/skill-installation.types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA2TH,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,iDAAiD;AACjD,MAAM,CAAC,MAAM,0BAA0B,GAAsC;IAC3E,QAAQ,EAAE;QACR,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,SAAS;KAC5B;IACD,OAAO,EAAE;QACP,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,SAAS;KAC5B;IACD,SAAS,EAAE;QACT,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,SAAS;KAC5B;IACD,KAAK,EAAE;QACL,aAAa,EAAE,GAAG;QAClB,gBAAgB,EAAE,UAAU;KAC7B;IACD,YAAY,EAAE;QACZ,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,OAAO;KAC1B;IACD,OAAO,EAAE;QACP,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,OAAO;KAC1B;IACD,6BAA6B;IAC7B,QAAQ,EAAE;QACR,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,SAAS;KAC5B;IACD,UAAU,EAAE;QACV,aAAa,EAAE,EAAE,EAAE,+DAA+D;QAClF,gBAAgB,EAAE,OAAO;KAC1B;CACF,CAAA"}
@@ -5,7 +5,9 @@
5
5
  *
6
6
  * Split out of `skill-installation.helpers.ts` to stay under the 500-line
7
7
  * standard once ADR-139's adoption logic was added — mirrors the existing
8
- * `skill-installation.io.ts` sibling-split convention. `performUninstall`
8
+ * `skill-installation.io.ts` sibling-split convention. Round 25 split its own
9
+ * helpers out again, into `skill-installation.uninstall.helpers.ts`, for the
10
+ * same reason. `performUninstall`
9
11
  * has exactly one internal consumer (`skill-installation.service.ts`) and
10
12
  * is not part of `@skillsmith/core`'s public export surface.
11
13
  */
@@ -1 +1 @@
1
- {"version":3,"file":"skill-installation.uninstall.d.ts","sourceRoot":"","sources":["../../../src/services/skill-installation.uninstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,8CAA8C,CAAA;AAC7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAGtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AAEvE;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAsC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC;IAAE,KAAK,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CAmCtF;AAED,2EAA2E;AAC3E,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,OAAO,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,eAAe,CAAA;IACzB,mBAAmB,EAAE,yBAAyB,CAAA;IAC9C,UAAU,EAAE,gBAAgB,CAAA;IAC5B;6EACyE;IACzE,MAAM,CAAC,EAAE,QAAQ,CAAA;CAClB,GAAG,OAAO,CAAC,eAAe,CAAC,CAgI3B"}
1
+ {"version":3,"file":"skill-installation.uninstall.d.ts","sourceRoot":"","sources":["../../../src/services/skill-installation.uninstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,8CAA8C,CAAA;AAC7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAGtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AAQvE;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAsC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC;IAAE,KAAK,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CAmCtF;AAED,2EAA2E;AAC3E,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,OAAO,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,eAAe,CAAA;IACzB,mBAAmB,EAAE,yBAAyB,CAAA;IAC9C,UAAU,EAAE,gBAAgB,CAAA;IAC5B;6EACyE;IACzE,MAAM,CAAC,EAAE,QAAQ,CAAA;CAClB,GAAG,OAAO,CAAC,eAAe,CAAC,CAgQ3B"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @fileoverview Helpers for `performUninstall`: what it found before removing,
3
+ * progress reporting that cannot change the outcome, record comparison, and
4
+ * the parked-leftover warnings.
5
+ * @module @skillsmith/core/services/skill-installation.uninstall.helpers
6
+ *
7
+ * Split out of `skill-installation.uninstall.ts` in SMI-6529 round 25, when
8
+ * that file reached 504 lines against the 500-line standard — the same
9
+ * sibling-split convention as `skill-installation.io.ts`. These are the pieces
10
+ * `performUninstall` calls but does not itself define; nothing here is part of
11
+ * `@skillsmith/core`'s public export surface.
12
+ */
13
+ import type { Stats } from 'fs';
14
+ import type { ProgressCallback, SkillManifestEntry } from './skill-installation.types.js';
15
+ /**
16
+ * SMI-6529 round 15 (cross-model review, Critical): what uninstall found at
17
+ * `installPath` before removing it. A folder with `.git` at its root is a git
18
+ * working tree, which git owns (ADR-155), so it is refused with or without
19
+ * `force`: deleting it would take unpushed commits and uncommitted edits with
20
+ * it. A symlink or a file needs no such check, since removing it leaves what
21
+ * it points at alone. Returns the entry's identity (null when nothing is
22
+ * there), so the delete can confirm it is still the same entry.
23
+ */
24
+ export declare function inspectForRemoval(installPath: string): Promise<{
25
+ stat: Stats | null;
26
+ } | {
27
+ refusal: string;
28
+ }>;
29
+ /**
30
+ * Report progress without letting a caller's listener change the outcome.
31
+ * Round 18 (cross-model review): a listener that threw after the folder was
32
+ * already removed reached the generic catch, which reported neither what had
33
+ * been removed nor what was left parked.
34
+ *
35
+ * Round 28 (pre-merge gate, PR-07): the failure is still swallowed as far as
36
+ * the uninstall's outcome goes — that part was deliberate and stays — but it
37
+ * is no longer swallowed as far as the USER goes. It is pushed onto
38
+ * `problems`, which the caller surfaces alongside its other warnings. An empty
39
+ * catch here was the same "could not tell" silence this wave removed
40
+ * everywhere else, in the one place that reports to the user for a living.
41
+ */
42
+ export declare function notify(onProgress: ProgressCallback, problems: string[], ...args: Parameters<ProgressCallback>): void;
43
+ /**
44
+ * Whether `entry` is still, field for field, the record this uninstall
45
+ * loaded. Round 18 (cross-model review): timestamps alone are not a unique
46
+ * generation token — two installs can share a millisecond, an older writer
47
+ * can omit them, and a copied record keeps them — so every field has to
48
+ * match. An install that claims the same name writes its own values, so any
49
+ * difference means a different generation. A durable generation id belongs
50
+ * with A1's manifest work (SMI-6531).
51
+ */
52
+ export declare function sameRecord(entry: SkillManifestEntry, loaded: SkillManifestEntry): boolean;
53
+ /**
54
+ * Warnings naming anything an earlier removal left parked next to
55
+ * `installPath`. Round 17 (cross-model review): every exit after the removal
56
+ * reports these, not only the successful one.
57
+ */
58
+ export declare function parkedWarnings(installPath: string): Promise<string[]>;
59
+ //# sourceMappingURL=skill-installation.uninstall.helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-installation.uninstall.helpers.d.ts","sourceRoot":"","sources":["../../../src/services/skill-installation.uninstall.helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,CAAA;AAK/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AAEzF;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,IAAI,EAAE,KAAK,GAAG,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAwCvD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CACpB,UAAU,EAAE,gBAAgB,EAC5B,QAAQ,EAAE,MAAM,EAAE,EAClB,GAAG,IAAI,EAAE,UAAU,CAAC,gBAAgB,CAAC,GACpC,IAAI,CAWN;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAEzF;AAqBD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAK3E"}