@dzhechkov/skills-feature-adr 1.3.13 → 1.3.16

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 ADDED
@@ -0,0 +1,119 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@dzhechkov/skills-feature-adr` are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.3.13] - 2026-07-03
9
+
10
+ ### Added
11
+
12
+ - **Durable half of the pattern-memory loop** via `dz teach` / `dz recall` — patterns produced by the QE steps now persist across sessions through the harness memory layer (complements the session-local aqe loop from 1.3.12).
13
+
14
+ ## [1.3.12] - 2026-07-03
15
+
16
+ ### Added
17
+
18
+ - **aqe pattern-memory loop in Steps 0/8/9** (Direct modes, non-blocking): query stored patterns at Step 0, store QE outcomes at Steps 8/9 — panel-recommended integration.
19
+ - Consolidation maintenance docs for the pattern memory.
20
+
21
+ ## [1.3.11] - 2026-07-03
22
+
23
+ ### Added
24
+
25
+ - README Troubleshooting section: running `npx` inside a monorepo with npm workspaces.
26
+
27
+ ## [1.3.10] - 2026-07-03
28
+
29
+ ### Fixed
30
+
31
+ - **All confirmed P1/P2 findings from the package audit** (adversarially verified):
32
+ - Installer safety: per-file overwrite guard, path-traversal containment, keysarium-preserve behavior, `.bak` backups on `update`, `isFile` checks.
33
+ - Metadata: removed stale `main` entry; live `bugs` / `homepage` URLs.
34
+ - Docs: Step 3.5/9 step-count accounting corrected.
35
+
36
+ ## [1.3.9] - 2026-07-03
37
+
38
+ ### Changed
39
+
40
+ - Onboarding pointers now lead to the public rendered GitHub page.
41
+
42
+ ## [1.3.8] - 2026-07-03
43
+
44
+ ### Added
45
+
46
+ - Team Onboarding playbook now ships inside the package (`docs/`), publicly viewable via unpkg.
47
+
48
+ ## [1.3.7] - 2026-07-03
49
+
50
+ ### Added
51
+
52
+ - README pointer to the Team Onboarding playbook.
53
+
54
+ ## [1.3.6] - 2026-07-03
55
+
56
+ ### Added
57
+
58
+ - "Self-Learning Layers" section in the README.
59
+
60
+ ## [1.3.5] - 2026-07-03
61
+
62
+ ### Added
63
+
64
+ - "Fable?" column and model-routing guidance in the Pipeline Steps table.
65
+
66
+ ## [1.3.4] - 2026-07-03
67
+
68
+ ### Added
69
+
70
+ - Detailed Spec-Driven Development (SDD) section published in the npm README.
71
+
72
+ ## [1.3.3] - 2026-06-16
73
+
74
+ ### Fixed
75
+
76
+ - CLI rejects unknown options — a typo can no longer fall through to a destructive operation (#302).
77
+ - Template-source manifest added; `update` keeps optional components that were installed via `init` flags (#305).
78
+ - Reconciled template drift with sibling toolkits (governance pass, #310).
79
+
80
+ ## [1.3.2] - 2026-06-11
81
+
82
+ ### Fixed
83
+
84
+ - Defused destructive `prepublishOnly` template-sync hook (#280).
85
+ - Metadata + provenance corrections: registry category, `sources.json`, LICENSE (#286).
86
+ - Shipped as part of the P0–P5 audit publish wave (#288).
87
+
88
+ ## [1.3.1] - 2026-05-06
89
+
90
+ ### Fixed
91
+
92
+ - **`--with-learning` and `--knowledge-extractor` flags now actually install their components.** In 1.3.0 the flags were advertised by the CLI but the `prepublishOnly` step (`scripts/sync-templates.js`) did not bundle the corresponding template files, so `init` emitted `Template source not found` warnings and silently skipped them. The published tarball now includes:
93
+ - `lib/memory-protocol.md`
94
+ - `lib/reward-tracker.md`
95
+ - `.claude/rules/reward-learning.md`
96
+ - `.claude/skills/knowledge-extractor/` (full directory: `SKILL.md` + `modules/` + `references/` + `templates/`)
97
+ - `.claude/commands/harvest.md`
98
+ - **`update` no longer reports `Unknown component` for skipped optional components.** When a template source was missing, `init` still recorded its key in the manifest's `components` array. On a later `update` pass, those orphan keys triggered noisy warnings. `init` now only writes optional component keys whose templates were actually present and whose installation produced files.
99
+ - The `[optional]` lines printed in the success summary now reflect what was actually installed (no longer claims `✓` for components whose templates were missing).
100
+
101
+ ### Changed
102
+
103
+ - `scripts/sync-templates.js`:
104
+ - `FEATURE_ADR_FILTERS.commands` extended to also match `harvest*.md` (alongside `feature-adr*.md`), so `templates/.claude/commands/` ships both `feature-adr.md` and `harvest.md`.
105
+ - New step `2d` copies the three optional learning files (`lib/memory-protocol.md`, `lib/reward-tracker.md`, `.claude/rules/reward-learning.md`) when they exist in the project root.
106
+ - New step `2e` copies the entire `.claude/skills/knowledge-extractor/` directory.
107
+ - Each optional source is reported individually with `[OK]` / `[WARN]` lines.
108
+
109
+ ### Migration notes
110
+
111
+ - **No action required for existing installs** that ran with `--with-learning` or `--knowledge-extractor` against 1.3.0 — the manifest there records the keys but the files are missing on disk. Re-run with `--force` after upgrading to refill the missing templates:
112
+ ```bash
113
+ npx @dzhechkov/skills-feature-adr@1.3.1 init --with-learning --knowledge-extractor --force
114
+ ```
115
+ - Installs that used neither optional flag are unaffected.
116
+
117
+ ## [1.3.0]
118
+
119
+ Initial published baseline (April 2026). 11-step pipeline with Complexity Router (S/M/L/XL), 15 agentic-qe skills, three install modes (`--full-qe`, `--full-qe-extended`, `--with-learning`, `--knowledge-extractor`).
package/LICENSE CHANGED
@@ -19,3 +19,16 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
+
23
+ ---
24
+
25
+ Third-party licenses
26
+
27
+ This package bundles the frontend-design skill template, which is licensed
28
+ under the Apache License, Version 2.0 (not MIT). The full Apache-2.0 license
29
+ text ships alongside the skill at:
30
+
31
+ templates/.claude/skills/frontend-design/LICENSE.txt
32
+
33
+ All other contents of this package are covered by the MIT license above.
34
+ The package's SPDX license expression is: MIT AND Apache-2.0
package/README.md CHANGED
@@ -263,6 +263,7 @@ features/<feature-slug>/
263
263
  ├── 02_research.md ← L/XL only
264
264
  ├── 03_adr/
265
265
  │ └── 001-<decision-slug>.md ← M+ only
266
+ ├── 03.5_ideation_report.md ← M+ only
266
267
  ├── 04_domain_model.md ← L/XL only
267
268
  ├── 05_architecture.md ← M+ only
268
269
  ├── 06_implementation_plan.md ← Always
@@ -421,9 +422,15 @@ Hooks and events only **collect** raw data — distilling it into reusable patte
421
422
  `aqe learning consolidate` periodically (e.g. weekly, or after a heavy QE run);
422
423
  `aqe learning stats` shows the experience backlog.
423
424
  - **C (dz harness)** — run `dz consolidate` to harvest session learnings into the lexical store
424
- (mirrored to the agentdb vector store if present); inspect the result with `dz recall`. The
425
- SessionEnd hook automates this **only on clean session ends** — crashed or killed sessions are
426
- missed.
425
+ (mirrored to the agentdb vector store if present); inspect the result with `dz recall`.
426
+ Consolidation now runs automatically on **PreCompact**Claude Code fires this before every
427
+ context compaction (both auto-compaction and manual `/compact`), so long or heavily-compacted
428
+ sessions consolidate at each compaction boundary rather than only at a clean end. It is
429
+ **throttled** (a `.dz/.last-consolidate` marker enforces a minimum of ~15 min between auto-runs)
430
+ so rapid compactions don't reload the embedding model repeatedly. **SessionEnd** remains as a
431
+ secondary trigger for clean exits, and you can still run `dz consolidate` manually anytime.
432
+ PreCompact is the primary reliable trigger — earlier the SessionEnd-only hook missed long,
433
+ compacted, or crashed sessions, which left consolidation effectively manual.
427
434
 
428
435
  **Degradation symptom:** patterns stop improving while raw logs keep growing. If recall/injection
429
436
  quality plateaus, you're collecting but not distilling — consolidate.
package/bin/cli.js CHANGED
File without changes
@@ -8,7 +8,7 @@ self-learning options, and the team conventions.
8
8
 
9
9
  - 📦 npm: <https://www.npmjs.com/package/@dzhechkov/skills-feature-adr>
10
10
  - 📄 Full reference README: same npm page (this doc is the *team playbook*, not a reference dump)
11
- - 🧭 Deep design: `packages/@dzhechkov/skills-meta/feature-adr/SKILL.md` + its `references/`
11
+ - 🧭 Deep design: `node_modules/@dzhechkov/skills-feature-adr/templates/.claude/skills/feature-adr/SKILL.md` + its `references/` (or browse on unpkg: <https://unpkg.com/@dzhechkov/skills-feature-adr/templates/.claude/skills/feature-adr/SKILL.md>)
12
12
 
13
13
  ---
14
14
 
@@ -216,7 +216,13 @@ reusable patterns is a separate consolidation step:
216
216
  returns zero useful patterns. Run `aqe learning consolidate` periodically (weekly, or after a
217
217
  heavy QE run); `aqe learning stats` shows the backlog.
218
218
  - **C** — run `dz consolidate` (harvests session learnings into the lexical store, mirrored to the
219
- vector store if present); inspect with `dz recall`. The SessionEnd hook covers **clean exits only**.
219
+ vector store if present); inspect with `dz recall`. Consolidation now runs automatically on
220
+ **PreCompact** (Claude Code fires this before every context compaction — auto and manual
221
+ `/compact`), so long/compacted sessions consolidate at each compaction boundary, not only at a
222
+ clean end. It's **throttled** via a `.dz/.last-consolidate` marker (~15 min min between auto-runs).
223
+ SessionEnd remains a secondary trigger for clean exits; manual `dz consolidate` works anytime.
224
+ PreCompact is the primary reliable trigger (SessionEnd alone missed long/compacted/crashed
225
+ sessions).
220
226
 
221
227
  **Degradation symptom:** patterns stop improving while raw logs keep growing → you're collecting,
222
228
  not distilling. Consolidate.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/skills-feature-adr",
3
- "version": "1.3.13",
3
+ "version": "1.3.16",
4
4
  "description": "Adaptive Feature Development skill pack for Claude Code — 11-step pipeline with Complexity Router (S/M/L/XL), ADR-driven architecture, 15 agentic-qe skills, multi-agent fleet QE. Supports --full-qe, --full-qe-extended, --with-learning, and --knowledge-extractor modes.",
5
5
  "bin": {
6
6
  "skills-feature-adr": "./bin/cli.js"
@@ -10,6 +10,7 @@
10
10
  "src/",
11
11
  "templates/",
12
12
  "LICENSE",
13
+ "CHANGELOG.md",
13
14
  "docs/"
14
15
  ],
15
16
  "keywords": [
@@ -33,7 +34,7 @@
33
34
  "harvest"
34
35
  ],
35
36
  "author": "dzhechko",
36
- "license": "MIT",
37
+ "license": "MIT AND Apache-2.0",
37
38
  "engines": {
38
39
  "node": ">=16.0.0"
39
40
  },
@@ -56,5 +57,6 @@
56
57
  },
57
58
  "publishConfig": {
58
59
  "access": "public"
59
- }
60
- }
60
+ },
61
+ "scripts": {}
62
+ }
package/src/cli.js CHANGED
@@ -8,10 +8,10 @@ const { bold, cyan, dim, green, yellow, red, info, error } = require('./utils');
8
8
  // ---------------------------------------------------------------------------
9
9
  function showBanner() {
10
10
  console.log('');
11
- console.log(cyan('\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557'));
11
+ console.log(cyan('\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557'));
12
12
  console.log(cyan('\u2551') + bold(' FEATURE-ADR \u2014 Adaptive Feature Development ') + cyan('\u2551'));
13
13
  console.log(cyan('\u2551') + ' 11-step pipeline with Complexity Router (S/M/L/XL) ' + cyan('\u2551'));
14
- console.log(cyan('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D'));
14
+ console.log(cyan('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D'));
15
15
  console.log('');
16
16
  }
17
17
 
@@ -6,7 +6,7 @@ const {
6
6
  green, red, yellow, cyan, bold, dim,
7
7
  info, success, warn, error: logError,
8
8
  fileExists, readJSON, readManifest,
9
- MANIFEST_FILE, getComponentFilter, COMPONENTS,
9
+ MANIFEST_FILE, fromManifestPath, getComponentFilter, COMPONENTS,
10
10
  } = require('../utils');
11
11
 
12
12
  // ---------------------------------------------------------------------------
@@ -40,7 +40,9 @@ function checkFilesExist(manifest, targetDir) {
40
40
  const suspicious = [];
41
41
 
42
42
  for (const relPath of files) {
43
- const absPath = path.resolve(resolvedTarget, relPath);
43
+ // Normalize both separator kinds — old manifests may carry native
44
+ // (backslash) separators written on another OS.
45
+ const absPath = path.resolve(resolvedTarget, fromManifestPath(relPath));
44
46
 
45
47
  // Manifest entries must stay inside the project — anything that
46
48
  // resolves outside is suspicious (possible tampering), not checked.
@@ -276,7 +278,7 @@ function checkKeysariumIntegration(targetDir) {
276
278
  if (!keysariumManifest) {
277
279
  return {
278
280
  passed: true,
279
- detail: 'Detected but manifest unreadable',
281
+ detail: ' .keysarium.json exists but is unreadable (corrupted?) — treating as not installed',
280
282
  };
281
283
  }
282
284
 
@@ -7,7 +7,8 @@ const {
7
7
  info, success, warn, error: logError, step,
8
8
  fileExists, readJSON,
9
9
  ensureDir, getRelativePaths, getRelativePathsFiltered,
10
- createManifest, writeManifest, getTemplatesDir,
10
+ createManifest, readManifest, writeManifest, getTemplatesDir,
11
+ toManifestPath, fromManifestPath,
11
12
  COMPONENTS, OPTIONAL_COMPONENTS, MANIFEST_FILE, getComponentFilter,
12
13
  } = require('../utils');
13
14
 
@@ -20,18 +21,29 @@ const KEYSARIUM_MANIFEST = '.keysarium.json';
20
21
  function detectKeysarium(targetDir) {
21
22
  const manifestPath = path.join(targetDir, KEYSARIUM_MANIFEST);
22
23
  if (!fileExists(manifestPath)) return null;
23
- return readJSON(manifestPath);
24
+ const manifest = readJSON(manifestPath);
25
+ if (manifest === null) {
26
+ // File exists but did not parse — say so distinctly instead of silently
27
+ // pretending keysarium is absent.
28
+ console.log(yellow('⚠') + ' .keysarium.json exists but is unreadable (corrupted?) — treating as not installed');
29
+ return null;
30
+ }
31
+ return manifest;
24
32
  }
25
33
 
26
34
  function showKeysariumIntegration(keysariumManifest) {
27
35
  console.log('');
28
36
  console.log(cyan(' \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510'));
29
- console.log(cyan(' \u2502') + bold(' @dzhechkov/keysarium detected!') + ' ' + cyan('\u2502'));
37
+ // Interior width is 50 columns (the borders above/below have 50 dashes) \u2014
38
+ // every content line must render exactly 50 visible chars wide.
39
+ console.log(cyan(' \u2502') + bold(' @dzhechkov/keysarium detected!') + ' ' + cyan('\u2502'));
30
40
  const v = keysariumManifest.version;
31
41
  const kv = typeof v === 'string' && v.length ? v : 'unknown';
32
- console.log(cyan(' \u2502') + ` Version: ${dim(kv)}` + ' '.repeat(Math.max(0, 39 - kv.length)) + cyan('\u2502'));
33
- console.log(cyan(' \u2502') + ' Feature ADR integrates with existing Keysarium. ' + cyan('\u2502'));
34
- console.log(cyan(' \u2502') + ' Shared: .claude/commands, rules, skills ' + cyan('\u2502'));
42
+ // ' Version: ' is 10 chars, so padding = 50 - 10 - kv.length; the Math.max
43
+ // clamp keeps over-long version strings from producing a negative repeat.
44
+ console.log(cyan(' \u2502') + ` Version: ${dim(kv)}` + ' '.repeat(Math.max(0, 40 - kv.length)) + cyan('\u2502'));
45
+ console.log(cyan(' \u2502') + ' Feature ADR integrates with existing Keysarium. ' + cyan('\u2502'));
46
+ console.log(cyan(' \u2502') + ' Shared: .claude/commands, rules, skills ' + cyan('\u2502'));
35
47
  console.log(cyan(' \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518'));
36
48
  console.log('');
37
49
  }
@@ -61,16 +73,18 @@ function installComponent(key, comp, templatesDir, targetDir, opts) {
61
73
  // Build the file list from the TEMPLATE source, never a scan of the destination — otherwise
62
74
  // user-created files inside a component dir get adopted into the manifest and a later
63
75
  // `remove` deletes them (finding #8).
76
+ // Manifest `rel` entries are stored POSIX-normalized (toManifestPath) so a
77
+ // manifest written on Windows keeps working on posix and vice versa.
64
78
  let entries;
65
79
  if (comp.isFile) {
66
- entries = [{ srcFile: src, destFile: destRoot, rel: comp.src }];
80
+ entries = [{ srcFile: src, destFile: destRoot, rel: toManifestPath(comp.src) }];
67
81
  } else {
68
82
  const filterFn = getComponentFilter(comp);
69
83
  const rels = filterFn ? getRelativePathsFiltered(src, filterFn) : getRelativePaths(src);
70
84
  entries = rels.map((rel) => ({
71
85
  srcFile: path.join(src, rel),
72
86
  destFile: path.join(destRoot, rel),
73
- rel: path.join(comp.src, rel),
87
+ rel: toManifestPath(path.join(comp.src, rel)),
74
88
  }));
75
89
  }
76
90
 
@@ -103,6 +117,25 @@ function printPreservedBlock(preserved, dryRun) {
103
117
  }
104
118
  }
105
119
 
120
+ // Files tracked by the PREVIOUS install's manifest that the current template
121
+ // set no longer provides. They were ours (the old manifest says so), so a
122
+ // --force reinstall must clean them up or they stay on disk untracked forever.
123
+ // Contained-path check (path.resolve + prefix) before every candidate — same
124
+ // discipline remove.js uses; anything resolving outside the project is skipped.
125
+ function findOrphans(oldManifest, newFileSet, resolvedTarget) {
126
+ if (!oldManifest || !Array.isArray(oldManifest.files)) return [];
127
+ const orphans = [];
128
+ for (const oldRel of oldManifest.files) {
129
+ const posixRel = toManifestPath(oldRel);
130
+ if (newFileSet.has(posixRel)) continue;
131
+ const absPath = path.resolve(resolvedTarget, fromManifestPath(oldRel));
132
+ if (absPath === resolvedTarget || !absPath.startsWith(resolvedTarget + path.sep)) continue;
133
+ if (!fileExists(absPath)) continue;
134
+ orphans.push({ rel: posixRel, absPath });
135
+ }
136
+ return orphans;
137
+ }
138
+
106
139
  // Resolve which optional component keys to install based on flags
107
140
  function getOptionalKeys(flags, keysariumDetected) {
108
141
  if (keysariumDetected) return []; // keysarium already provides these
@@ -133,6 +166,7 @@ async function run(options) {
133
166
  const manifestPath = path.join(targetDir, MANIFEST_FILE);
134
167
 
135
168
  // ── a) Check for existing installation ─────────────────────────────────
169
+ let oldManifest = null; // previous install's manifest (for orphan cleanup)
136
170
  if (fileExists(manifestPath)) {
137
171
  if (!force) {
138
172
  warn('Feature ADR skill pack is already installed in this directory.');
@@ -140,6 +174,7 @@ async function run(options) {
140
174
  process.exit(1);
141
175
  }
142
176
  warn('Existing Feature ADR installation found \u2014 overwriting (--force).');
177
+ oldManifest = readManifest(targetDir);
143
178
  }
144
179
 
145
180
  // ── b) Detect keysarium integration ────────────────────────────────────
@@ -253,6 +288,20 @@ async function run(options) {
253
288
  if (preservedFiles.length > 0) {
254
289
  printPreservedBlock(preservedFiles, true);
255
290
  }
291
+ if (force && oldManifest) {
292
+ const dryOrphans = findOrphans(
293
+ oldManifest,
294
+ new Set([...installedFiles, ...preservedFiles]),
295
+ path.resolve(targetDir)
296
+ );
297
+ if (dryOrphans.length > 0) {
298
+ console.log('');
299
+ info(`${dryOrphans.length} orphaned file(s) from the previous install would be removed:`);
300
+ for (const o of dryOrphans) {
301
+ console.log(` ${yellow('⚠')} ${o.rel}`);
302
+ }
303
+ }
304
+ }
256
305
  console.log('');
257
306
  warn('Dry run — no files were written.');
258
307
  process.exit(0);
@@ -281,6 +330,34 @@ async function run(options) {
281
330
  writeManifest(targetDir, manifest);
282
331
  info(`Created ${MANIFEST_FILE} manifest`);
283
332
 
333
+ // ── f2) Orphan cleanup (--force reinstall) ─────────────────────────────
334
+ // Files the OLD manifest tracked that the new install no longer provides
335
+ // would otherwise stay on disk untracked forever. They were ours — the old
336
+ // manifest says so — so delete them now that the new install is complete.
337
+ if (force && oldManifest) {
338
+ const orphans = findOrphans(
339
+ oldManifest,
340
+ new Set([...installedFiles, ...preservedFiles]),
341
+ path.resolve(targetDir)
342
+ );
343
+ const removedOrphans = [];
344
+ for (const o of orphans) {
345
+ try {
346
+ fs.unlinkSync(o.absPath);
347
+ removedOrphans.push(o.rel);
348
+ } catch (err) {
349
+ warn(`Could not remove orphaned file ${o.rel}: ${err.message}`);
350
+ }
351
+ }
352
+ if (removedOrphans.length > 0) {
353
+ console.log('');
354
+ console.log(`${green('✓')} removed ${removedOrphans.length} orphaned file(s) from the previous install:`);
355
+ for (const rel of removedOrphans) {
356
+ console.log(` ${dim(rel)}`);
357
+ }
358
+ }
359
+ }
360
+
284
361
  // ── g) Success summary ─────────────────────────────────────────────────
285
362
  console.log('');
286
363
  success(bold('Feature ADR skill pack installed!'));
@@ -7,7 +7,7 @@ const {
7
7
  green, red, yellow, cyan, bold, dim,
8
8
  info, success, warn, error: logError, step,
9
9
  fileExists, readManifest,
10
- MANIFEST_FILE,
10
+ MANIFEST_FILE, fromManifestPath,
11
11
  } = require('../utils');
12
12
 
13
13
  // ---------------------------------------------------------------------------
@@ -88,7 +88,9 @@ function isKeysariumShared(relParts) {
88
88
  // 'shared' — lives in a keysarium-shared directory (preserve)
89
89
  // 'ok' — safe to remove
90
90
  function classifyManifestEntry(relPath, resolvedTarget, keysariumDetected) {
91
- const absPath = path.resolve(resolvedTarget, relPath);
91
+ // Manifest entries may carry either separator kind (a manifest written on
92
+ // Windows has backslashes) — normalize to native before resolving.
93
+ const absPath = path.resolve(resolvedTarget, fromManifestPath(relPath));
92
94
 
93
95
  if (absPath !== resolvedTarget && !absPath.startsWith(resolvedTarget + path.sep)) {
94
96
  return { absPath, kind: 'outside' };
@@ -188,6 +190,13 @@ async function run(options) {
188
190
 
189
191
  // ── c) Confirm unless --force ─────────────────────────────────────────
190
192
  if (!force) {
193
+ // Without a TTY, rl.question never resolves (CI / piped stdin) — the
194
+ // process would hang or silently exit. Reject with a copy-paste command.
195
+ if (!process.stdin.isTTY) {
196
+ logError('non-interactive session detected — re-run with --force to confirm removal');
197
+ info(`Run: ${cyan('npx @dzhechkov/skills-feature-adr remove --force')}`);
198
+ process.exit(1);
199
+ }
191
200
  const confirmed = await confirmPrompt(
192
201
  yellow('This will remove all Feature ADR skill pack files. Continue? (y/N) ')
193
202
  );
@@ -8,6 +8,7 @@ const {
8
8
  copyDirRecursive, copyDirFiltered, fileExists, readJSON,
9
9
  ensureDir, getRelativePaths, getRelativePathsFiltered, diffFiles,
10
10
  readManifest, writeManifest, getTemplatesDir,
11
+ toManifestPath,
11
12
  COMPONENTS, OPTIONAL_COMPONENTS, MANIFEST_FILE, getComponentFilter,
12
13
  } = require('../utils');
13
14
 
@@ -201,10 +202,14 @@ async function run(options) {
201
202
  // them). If the template source is missing, keep the previous manifest
202
203
  // entries for this component verbatim.
203
204
  if (!fileExists(srcPath)) {
204
- const kept = prevFiles.filter(
205
- (rel) => rel === comp.src || rel.startsWith(comp.src + path.sep)
206
- );
207
- allFiles.push(...kept);
205
+ // Compare in POSIX form so old manifests with native (backslash)
206
+ // separators still match; re-store the kept entries POSIX-normalized.
207
+ const compPosix = toManifestPath(comp.src);
208
+ const kept = prevFiles.filter((rel) => {
209
+ const relPosix = toManifestPath(rel);
210
+ return relPosix === compPosix || relPosix.startsWith(compPosix + '/');
211
+ });
212
+ allFiles.push(...kept.map(toManifestPath));
208
213
  warn(`component '${key}' not found in current templates — manifest entries kept as-is`);
209
214
  continue;
210
215
  }
@@ -213,7 +218,7 @@ async function run(options) {
213
218
  // readdir it — without this branch the 3 learning files silently drop out of
214
219
  // the manifest on every rebuild, and a later `remove` strands them.
215
220
  if (comp.isFile) {
216
- if (fileExists(destPath)) allFiles.push(comp.src);
221
+ if (fileExists(destPath)) allFiles.push(toManifestPath(comp.src));
217
222
  continue;
218
223
  }
219
224
 
@@ -221,7 +226,7 @@ async function run(options) {
221
226
  const paths = filterFn
222
227
  ? getRelativePathsFiltered(srcPath, filterFn)
223
228
  : getRelativePaths(srcPath);
224
- allFiles.push(...paths.map((rel) => path.join(comp.src, rel)));
229
+ allFiles.push(...paths.map((rel) => toManifestPath(path.join(comp.src, rel))));
225
230
  }
226
231
  }
227
232
 
package/src/utils.js CHANGED
@@ -208,6 +208,20 @@ function diffFiles(srcDir, destDir, filterFn) {
208
208
 
209
209
  const MANIFEST_FILE = '.skills-feature-adr.json';
210
210
 
211
+ // Manifest entries are always stored with POSIX '/' separators so a manifest
212
+ // written on Windows keeps working on posix and vice versa (portability).
213
+
214
+ // WRITE side: normalize a path to POSIX form before storing it in a manifest.
215
+ function toManifestPath(p) {
216
+ return String(p).replace(/\\/g, '/');
217
+ }
218
+
219
+ // READ side: accept BOTH separator kinds (old manifests may carry native
220
+ // backslashes) and return a native-separator path safe for path.resolve/join.
221
+ function fromManifestPath(p) {
222
+ return String(p).split(/[\\/]+/).join(path.sep);
223
+ }
224
+
211
225
  function readManifest(targetDir) {
212
226
  return readJSON(path.join(targetDir, MANIFEST_FILE));
213
227
  }
@@ -369,6 +383,8 @@ module.exports = {
369
383
 
370
384
  // Manifest
371
385
  MANIFEST_FILE,
386
+ toManifestPath,
387
+ fromManifestPath,
372
388
  readManifest,
373
389
  writeManifest,
374
390
  createManifest,
@@ -34,11 +34,13 @@ All artifacts use numbered prefix matching their step:
34
34
  01_requirements.md
35
35
  02_research.md
36
36
  03_adr/001-{decision-slug}.md
37
+ 03.5_ideation_report.md
37
38
  04_domain_model.md
38
39
  05_architecture.md
39
40
  06_implementation_plan.md
40
41
  07_code_changes/change_manifest.md
41
42
  08_qe_report.md
43
+ 09_fleet_qe_assessment.md
42
44
  ```
43
45
 
44
46
  ## Complexity Tier Rules
@@ -73,6 +75,7 @@ It DOES share:
73
75
  → Step 6: plan
74
76
  → Step 7: code
75
77
  → Step 8: QE
78
+ → Step 9: Fleet QE (L/XL)
76
79
  → Done: README.md + code changes in repo
77
80
  ```
78
81