@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 +119 -0
- package/LICENSE +13 -0
- package/README.md +10 -3
- package/bin/cli.js +0 -0
- package/docs/team-onboarding.md +8 -2
- package/package.json +6 -4
- package/src/cli.js +2 -2
- package/src/commands/doctor.js +5 -3
- package/src/commands/init.js +85 -8
- package/src/commands/remove.js +11 -2
- package/src/commands/update.js +11 -6
- package/src/utils.js +16 -0
- package/templates/.claude/rules/feature-adr-conventions.md +3 -0
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`.
|
|
425
|
-
|
|
426
|
-
|
|
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
|
package/docs/team-onboarding.md
CHANGED
|
@@ -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: `
|
|
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`.
|
|
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.
|
|
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
|
|
package/src/commands/doctor.js
CHANGED
|
@@ -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
|
-
|
|
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: '
|
|
281
|
+
detail: '⚠ .keysarium.json exists but is unreadable (corrupted?) — treating as not installed',
|
|
280
282
|
};
|
|
281
283
|
}
|
|
282
284
|
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
console.log(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!'));
|
package/src/commands/remove.js
CHANGED
|
@@ -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
|
-
|
|
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
|
);
|
package/src/commands/update.js
CHANGED
|
@@ -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
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
207
|
-
|
|
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
|
|