@skilly-hand/skilly-hand 0.16.1 → 0.17.0

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
@@ -16,6 +16,24 @@ All notable changes to this project are documented in this file.
16
16
  ### Removed
17
17
  - _None._
18
18
 
19
+ ## [0.17.0] - 2026-04-08
20
+ [View on npm](https://www.npmjs.com/package/@skilly-hand/skilly-hand/v/0.17.0)
21
+
22
+ ### Added
23
+ - Added sandbox harness and matrix integration tests (`scripts/test-in-sandbox.mjs`, `tests/sandbox-harness.test.js`, `tests/sandbox-matrix.test.js`) and wired them into the root test pipeline.
24
+ - Added required `review-rangers` final-gate guidance to the spec-driven-development verify flow and validation checklist.
25
+
26
+ ### Changed
27
+ - Updated installer reconciliation logic to remove stale managed targets when agent selection narrows, while preserving/restoring backups for retained targets.
28
+ - Updated backup behavior to skip backup creation for files already marked as managed content.
29
+ - Updated root `npm test` to run sandbox integration verification after the node test suite.
30
+
31
+ ### Fixed
32
+ - Fixed uninstall and re-install behavior for narrowed agent selections by restoring original files and cleaning obsolete managed artifacts.
33
+
34
+ ### Removed
35
+ - _None._
36
+
19
37
  ## [0.16.1] - 2026-04-08
20
38
  [View on npm](https://www.npmjs.com/package/@skilly-hand/skilly-hand/v/0.16.1)
21
39
 
@@ -22,7 +22,7 @@ Do not use this skill for:
22
22
  1. Define the spec in `.sdd/active/<feature-name>/spec.md`.
23
23
  2. Review and refine scope, constraints, and tasks.
24
24
  3. Execute one small task at a time.
25
- 4. Verify each task and the end-to-end outcome.
25
+ 4. Verify each task and the end-to-end outcome, ending with a required `review-rangers` final gate.
26
26
  5. Archive to `.sdd/archive/` when complete.
27
27
 
28
28
  Recommended task size:
@@ -14,7 +14,7 @@ Coordinate planning, implementation, and verification through explicit checkpoin
14
14
  1. PLAN: Produce or update the spec.
15
15
  2. REVIEW CHECKPOINT: Confirm the plan is approved.
16
16
  3. APPLY: Execute agreed task batch.
17
- 4. VERIFY CHECKPOINT: Validate outputs against the spec.
17
+ 4. VERIFY CHECKPOINT: Validate outputs against the spec and run the required final `review-rangers` gate.
18
18
  5. REPEAT: Continue by phase or task batch.
19
19
  6. ARCHIVE: Move completed work from `.sdd/active/` to `.sdd/archive/`.
20
20
 
@@ -15,7 +15,15 @@ Validate that implementation matches the approved spec and passes quality checks
15
15
  2. Run task-level verification evidence checks.
16
16
  3. Run feature-level validation commands.
17
17
  4. Confirm constraints (`MUST`, `MUST NOT`) were respected.
18
- 5. Report pass/fail per area with concrete evidence.
18
+ 5. Run a final structured `review-rangers` pass over the full change set.
19
+ 6. Report pass/fail per area with concrete evidence.
20
+
21
+ ### Required Final Gate (`review-rangers`)
22
+
23
+ - Validate selected agent targets vs actual instruction files/symlinks written.
24
+ - Validate stale managed target cleanup after re-install/reselection.
25
+ - Validate backup and restore safety (including uninstall restore behavior).
26
+ - Any unresolved `review-rangers` blocker keeps verification in failed state.
19
27
 
20
28
  ## Quality Bar
21
29
 
@@ -28,5 +28,6 @@ Use this checklist before implementation and again before archive.
28
28
  - [ ] All planned tasks are complete.
29
29
  - [ ] Feature-level validation passes.
30
30
  - [ ] Constraints were respected.
31
+ - [ ] Final `review-rangers` gate completed with no unresolved blockers.
31
32
  - [ ] No unintended scope creep.
32
33
  - [ ] Work is moved from `.sdd/active/` to `.sdd/archive/`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/skilly-hand",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "license": "CC-BY-NC-4.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -28,7 +28,7 @@
28
28
  "catalog:check": "node ./scripts/check-catalog.mjs",
29
29
  "catalog:sync": "node ./scripts/sync-catalog-readme.mjs",
30
30
  "agentic:self:sync": "node ./scripts/sync-self-agentic.mjs",
31
- "test": "node --test tests/*.test.js",
31
+ "test": "node --test tests/*.test.js && node ./scripts/test-in-sandbox.mjs",
32
32
  "security:check": "node ./scripts/security-check.mjs",
33
33
  "verify:packlist": "node ./scripts/verify-packlist.mjs",
34
34
  "verify:versions": "node ./scripts/verify-versions.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/catalog",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/cli",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/core",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -175,7 +175,11 @@ async function ensureManagedTextFile(targetPath, content, backupsDir, lockData)
175
175
  return;
176
176
  }
177
177
 
178
- await backupPathIfNeeded(targetPath, backupsDir, lockData);
178
+ // Do not back up previously managed content; backups are for restoring
179
+ // user-authored files replaced by managed files.
180
+ if (!current.includes(MANAGED_MARKER)) {
181
+ await backupPathIfNeeded(targetPath, backupsDir, lockData);
182
+ }
179
183
  }
180
184
 
181
185
  await mkdir(path.dirname(targetPath), { recursive: true });
@@ -230,6 +234,69 @@ function buildInstallTargets(selectedAgents) {
230
234
  };
231
235
  }
232
236
 
237
+ async function reconcileManagedTargets({
238
+ previousLock,
239
+ selectedInstructionTargets,
240
+ selectedSkillTargets,
241
+ lockData
242
+ }) {
243
+ if (!previousLock) {
244
+ return;
245
+ }
246
+
247
+ const selectedInstructions = new Set(selectedInstructionTargets);
248
+ const selectedSkills = new Set(selectedSkillTargets);
249
+ const selectedTargets = new Set([...selectedInstructions, ...selectedSkills]);
250
+ const previousBackups = previousLock.backups || {};
251
+
252
+ for (const [targetPath, backupPath] of Object.entries(previousBackups)) {
253
+ if (!selectedTargets.has(targetPath)) {
254
+ continue;
255
+ }
256
+ if (await exists(backupPath)) {
257
+ lockData.backups[targetPath] = backupPath;
258
+ }
259
+ }
260
+
261
+ for (const symlinkPath of previousLock.managedSymlinks || []) {
262
+ if (selectedSkills.has(symlinkPath)) {
263
+ continue;
264
+ }
265
+
266
+ if (await exists(symlinkPath)) {
267
+ await rm(symlinkPath, { recursive: true, force: true });
268
+ }
269
+
270
+ const backupPath = previousBackups[symlinkPath];
271
+ if (backupPath && await exists(backupPath)) {
272
+ await mkdir(path.dirname(symlinkPath), { recursive: true });
273
+ await cp(backupPath, symlinkPath, { recursive: true, force: true });
274
+ }
275
+ }
276
+
277
+ for (const filePath of previousLock.managedFiles || []) {
278
+ if (selectedInstructions.has(filePath)) {
279
+ continue;
280
+ }
281
+
282
+ const backupPath = previousBackups[filePath];
283
+ if (backupPath && await exists(backupPath)) {
284
+ await mkdir(path.dirname(filePath), { recursive: true });
285
+ await cp(backupPath, filePath, { recursive: true, force: true });
286
+ continue;
287
+ }
288
+
289
+ if (!(await exists(filePath))) {
290
+ continue;
291
+ }
292
+
293
+ const content = await readFile(filePath, "utf8");
294
+ if (content.includes(MANAGED_MARKER)) {
295
+ await rm(filePath, { force: true });
296
+ }
297
+ }
298
+ }
299
+
233
300
  export async function installProject({
234
301
  cwd,
235
302
  agents,
@@ -259,6 +326,7 @@ export async function installProject({
259
326
  const targetCatalogDir = path.join(installRoot, "catalog");
260
327
  const backupsDir = path.join(installRoot, "backups");
261
328
  const lockPath = path.join(installRoot, "manifest.lock.json");
329
+ const previousLock = await exists(lockPath) ? await readJson(lockPath) : null;
262
330
  const lockData = {
263
331
  version: 1,
264
332
  generatedAt: plan.generatedAt,
@@ -289,15 +357,24 @@ export async function installProject({
289
357
  await writeFile(path.join(installRoot, "AGENTS.md"), agentsMarkdown, "utf8");
290
358
 
291
359
  const { instructionTargets, skillTargets } = buildInstallTargets(selectedAgents);
360
+ const absoluteInstructionTargets = instructionTargets.map((pathParts) => path.join(cwd, ...pathParts));
361
+ const absoluteSkillTargets = skillTargets.map((pathParts) => path.join(cwd, ...pathParts));
362
+
363
+ await reconcileManagedTargets({
364
+ previousLock,
365
+ selectedInstructionTargets: absoluteInstructionTargets,
366
+ selectedSkillTargets: absoluteSkillTargets,
367
+ lockData
368
+ });
292
369
 
293
- for (const pathParts of instructionTargets) {
294
- await ensureManagedTextFile(path.join(cwd, ...pathParts), agentsMarkdown, backupsDir, lockData);
370
+ for (const targetPath of absoluteInstructionTargets) {
371
+ await ensureManagedTextFile(targetPath, agentsMarkdown, backupsDir, lockData);
295
372
  }
296
373
 
297
374
  const skillsSourcePath = path.join(installRoot, "catalog");
298
375
 
299
- for (const pathParts of skillTargets) {
300
- await ensureSymlink(path.join(cwd, ...pathParts), skillsSourcePath, backupsDir, lockData);
376
+ for (const targetPath of absoluteSkillTargets) {
377
+ await ensureSymlink(targetPath, skillsSourcePath, backupsDir, lockData);
301
378
  }
302
379
 
303
380
  await writeJson(lockPath, lockData);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/detectors",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }